@rnaga/wp-node
Version:
👉 **[View Full Documentation at rnaga.github.io/wp-node →](https://rnaga.github.io/wp-node/)**
587 lines (586 loc) • 27.8 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PostCrud = void 0;
const zod_1 = require("zod");
const common_1 = require("../common");
const diff_1 = require("../common/diff");
const components_1 = require("../core/components");
const options_1 = require("../core/options");
const date_time_util_1 = require("../core/utils/date-time.util");
const post_util_1 = require("../core/utils/post.util");
const query_util_1 = require("../core/utils/query.util");
const taxonomy_util_1 = require("../core/utils/taxonomy.util");
const component_1 = require("../decorators/component");
const transactions_1 = require("../transactions");
const val = __importStar(require("../validators"));
const crud_1 = require("./crud");
const error_1 = require("./error");
const meta_crud_1 = require("./meta.crud");
const vars_1 = require("../core/vars");
const revision_util_1 = require("../core/utils/revision.util");
let PostCrud = class PostCrud extends crud_1.Crud {
constructor(components) {
super(components);
}
// _wp_translate_postdata
async translate(data) {
const postId = data.ID ?? undefined;
let diffData = {};
let currentPost = {};
if (postId) {
currentPost = (await this.getAsUpsert(postId)).data;
diffData = (0, diff_1.diffObject)(data, currentPost);
}
else {
diffData = structuredClone(data);
}
const { user, userId } = await this.getUser();
const postUtil = this.components.get(post_util_1.PostUtil);
const postType = postUtil.getTypeObject(data.post_type);
if (postId && !(await user.can("edit_post", postId))) {
throw new error_1.CrudError(error_1.StatusMessage.UNAUTHORIZED, "Sorry, you are not allowed to edit posts as this user");
}
if (!postId && !user.can(postType?.capabilities?.create_posts)) {
throw new error_1.CrudError(error_1.StatusMessage.UNAUTHORIZED, "Sorry, you are not allowed to edit posts as this user");
}
if (userId !== data.post_author &&
!(await user.can(postType?.capabilities?.edit_others_posts))) {
throw new error_1.CrudError(error_1.StatusMessage.UNAUTHORIZED, "Sorry, you are not allowed to edit posts as this user");
}
if (diffData.post_status &&
"private" == diffData.post_status &&
postType?.capabilities?.publish_posts &&
!(await user.can(postType.capabilities.publish_posts))) {
data.post_status = currentPost?.post_status ?? "pending";
}
const publishedStatuses = ["publish", "future"];
/*
* Posts 'submitted for approval' are submitted to $_POST the same as if they were being published.
* Change status from 'publish' to 'pending' if user lacks permissions to publish or to resave published posts.
*/
if (diffData.post_status &&
(!data.post_status || publishedStatuses.includes(data.post_status)) &&
!(await user.can(postType?.capabilities?.publish_posts)) &&
(!publishedStatuses.includes(currentPost?.post_status ?? "") ||
!postId ||
!(await user.can("edit_post", postId)))) {
data.post_status = "pending";
}
if (diffData.post_status && "auto-draft" === currentPost.post_status) {
data.post_status = "draft";
}
if (diffData.post_password &&
diffData.post_password?.length > 0 &&
!(await user.can(postType?.capabilities?.publish_posts))) {
data.post_password = currentPost.post_password ?? "";
}
if (diffData.post_categeory && diffData.post_categeory.length > 0) {
const category = await this.components.get(taxonomy_util_1.TaxonomyUtil).get("category");
if (!(await user.can(category.props?.capabilities?.assign_terms))) {
data.post_categeory = currentPost.post_categeory ?? undefined;
}
}
return data;
}
async formReturnData(post, options) {
const props = post.props;
if (!props) {
throw new error_1.CrudError(error_1.StatusMessage.BAD_REQUEST, "Invalid Post");
}
const { password, editable } = options;
if (!editable) {
props.post_content = common_1.formatting.trimMarkupComments(props.post_content);
props.post_excerpt = common_1.formatting.trimMarkupComments(props.post_excerpt);
if (!this.checkPasswordProtectedPost(props, password)) {
post.props.post_content = "";
post.props.post_excerpt = "";
post.props.post_password = "";
}
}
const author = await post.author();
const metas = await post.meta.props();
// public meta is key without _ prefix
const publicMetas = Object.entries(metas).reduce((acc, [key, value]) => {
if (key[0] !== "_") {
acc[key] = value;
}
return acc;
}, {});
return {
...props,
metas: editable ? metas : publicMetas,
author: {
ID: author?.ID,
display_name: author?.display_name,
user_nicename: author?.user_nicename,
},
tags: ((await post.terms("post_tag")) ?? []).map((term) => ({
term_id: term.term_id,
name: term.name,
slug: term.slug,
})),
categories: ((await post.terms("category")) ?? []).map((term) => ({
term_id: term.term_id,
name: term.name,
slug: term.slug,
})),
};
}
async getAsUpsert(postId, defaultValue = {}) {
const getData = (await this.get(postId, { context: "edit" })).data;
const postUtil = this.components.get(post_util_1.PostUtil);
const post = await postUtil.get(postId);
const data = {
...getData,
meta_input: await post.meta.props(),
tags_input: (await post.terms("post_tag"))?.map((tag) => tag.term_id),
post_categeory: (await post.terms("category"))?.map((category) => category.term_id),
};
return this.returnValue(val.trx.postUpsert.parse({ ...data, ...defaultValue }));
}
async checkPermission(action, postType, postId) {
const { user } = await this.getUser();
const postUtil = this.components.get(post_util_1.PostUtil);
const postTypeObject = postUtil.getTypeObject(postType);
return (postTypeObject?.capabilities &&
postTypeObject.capabilities[action] &&
(await user.can(postTypeObject.capabilities[action], postId)));
}
async get(postIdOrSlug, options) {
const { password = "", context = "view" } = options ?? {};
const postUtil = this.components.get(post_util_1.PostUtil);
const post = typeof postIdOrSlug == "string"
? await postUtil.getBySlug(postIdOrSlug)
: await postUtil.get(postIdOrSlug);
if (!post?.props?.ID ||
(options?.postType && post.props.post_type !== options.postType)) {
throw new error_1.CrudError(error_1.StatusMessage.NOT_FOUND, `Post not found - ${postIdOrSlug}`);
}
const postId = post.props.ID;
const { user } = await this.getUser();
const postType = post.props.post_type;
if (context == "edit" &&
!(await this.checkPermission("edit_post", postType, postId))) {
throw new error_1.CrudError(error_1.StatusMessage.UNAUTHORIZED, "Sorry, you are not allowed to edit this post");
}
// Private post
if (post.props.post_status == "private" &&
!(await this.canReadPrivatePosts(postType))) {
throw new error_1.CrudError(error_1.StatusMessage.UNAUTHORIZED, "Sorry, you are not allowed to view this post");
}
const role = await user.role();
if (role.is("anonymous") && post.props.post_status !== "publish") {
throw new error_1.CrudError(error_1.StatusMessage.UNAUTHORIZED, "Sorry, you are not allowed to view this post");
}
return this.returnValue(await this.formReturnData(post, {
password,
editable: context == "edit",
}), { protected: post.props.post_password != "" });
}
// edit_post
async update(postId, data) {
data.ID = postId;
const currentPost = (await this.getAsUpsert(postId, { context: "edit" }))
.data;
data.ID = currentPost.ID;
data.post_type = currentPost.post_type;
data.post_mime_type = currentPost.post_mime_type;
data = await this.translate(data);
if (data.meta_input) {
const metaCrud = this.components.get(meta_crud_1.MetaCrud);
await metaCrud.update("post", postId, data.meta_input, "sync");
data.meta_input = undefined;
}
const postTrx = this.components.get(transactions_1.PostTrx);
const result = await postTrx.upsert(data);
const postUtil = this.components.get(post_util_1.PostUtil);
const postType = postUtil.getTypeObject(data.post_type);
if (postType?.supports.includes("revisions") &&
data.post_content !== currentPost.post_content) {
const revisionTrx = this.components.get(transactions_1.RevisionTrx);
await revisionTrx.save(postId);
}
return this.returnValue(result);
}
// wp_write_post
async create(data) {
if (!data.post_type || !(await this.canEditPosts(data.post_type))) {
throw new error_1.CrudError(error_1.StatusMessage.UNAUTHORIZED, "post_type is not defined or you are not allowed to edit posts in this post type");
}
if (data.post_type !== "attachment") {
data.post_mime_type = "";
}
if (data.ID && data.ID > 0) {
return await this.update(data.ID, val.trx.postUpsert.parse(data));
}
data.ID = undefined;
data = await this.translate(data);
const postTrx = this.components.get(transactions_1.PostTrx);
return this.returnValue(await postTrx.upsert(data));
}
async delete(postId) {
const postUtil = this.components.get(post_util_1.PostUtil);
const post = await postUtil.get(postId);
if (!post.props?.ID) {
throw new error_1.CrudError(error_1.StatusMessage.NOT_FOUND, `Post not found - ${postId}`);
}
const postType = post.props.post_type;
if (!(await this.checkPermission("delete_post", postType, postId))) {
throw new error_1.CrudError(error_1.StatusMessage.UNAUTHORIZED, "Sorry, you are not allowed to delete this post");
}
const postTrx = this.components.get(transactions_1.PostTrx);
return this.returnValue(await postTrx.remove(postId, true).then((v) => typeof v !== "undefined"));
}
async trashOrUntrash(postId, trash) {
const postUtil = this.components.get(post_util_1.PostUtil);
const post = await postUtil.get(postId);
if (!post.props?.ID) {
throw new error_1.CrudError(error_1.StatusMessage.NOT_FOUND, `Post not found - ${postId}`);
}
const postType = post.props.post_type;
if (!(await this.checkPermission("edit_post", postType, postId))) {
throw new error_1.CrudError(error_1.StatusMessage.UNAUTHORIZED, "Sorry, you are not allowed to edit this post");
}
const postTrx = this.components.get(transactions_1.PostTrx);
return trash
? await postTrx.trash(postId).then((v) => typeof v !== "undefined")
: await postTrx.untrash(postId).then((v) => typeof v !== "undefined");
}
async trash(postId) {
return this.returnValue(await this.trashOrUntrash(postId, true));
}
async untrash(postId) {
return this.returnValue(await this.trashOrUntrash(postId, false));
}
async checkAutosavePermission(parent) {
const { user } = await this.getUser();
if (!parent.props) {
throw new error_1.CrudError(error_1.StatusMessage.BAD_REQUEST, "Invalid parameters.");
}
if (!(await user.can("edit_post", parent.props.ID))) {
throw new error_1.CrudError(error_1.StatusMessage.UNAUTHORIZED, "Sorry, you are not allowed to autosave of this post");
}
return true;
}
// class-wp-rest-autosaves-controller.php create_item
async autosave(parentId, data) {
const { user } = await this.getUser();
const vars = this.components.get(vars_1.Vars);
vars.DOING_AUTOSAVE = true;
const postUtil = this.components.get(post_util_1.PostUtil);
const parent = await postUtil.get(parentId);
await this.checkAutosavePermission(parent);
data = await this.translate(data);
data.post_parent = parentId;
const isDraft = data.post_status == "draft" || data.post_status == "auto-draft";
let autosaveId;
if (isDraft && user.props?.ID === data.post_author) {
/*
* Draft posts for the same author: autosaving updates the post and does not create a revision.
* Convert the post object to an array and add slashes, wp_update_post() expects escaped array.
*/
const postTrx = this.components.get(transactions_1.PostTrx);
autosaveId = await postTrx.upsert(common_1.formatting.slash(data));
}
else {
const revisionTrx = this.components.get(transactions_1.RevisionTrx);
autosaveId = await revisionTrx.autosave(data);
}
if (!autosaveId) {
throw new error_1.CrudError(error_1.StatusMessage.BAD_REQUEST, "Failed to autosave post");
}
const post = await postUtil.get(autosaveId);
if (!post.props) {
throw new error_1.CrudError(error_1.StatusMessage.BAD_REQUEST, "Failed to get autosave post after saving");
}
return this.returnValue(post.props);
}
async getAutosave(parentId) {
const { user } = await this.getUser();
const postUtil = this.components.get(post_util_1.PostUtil);
const parent = await postUtil.get(parentId);
await this.checkAutosavePermission(parent);
const revisionUtil = this.components.get(revision_util_1.RevisionUtil);
const autosave = await revisionUtil.getAutosave(parentId, user.props?.ID);
if (!autosave?.props) {
throw new error_1.CrudError(error_1.StatusMessage.NOT_FOUND, "Autosave not found");
}
return await this.getAsUpsert(autosave.props.ID);
}
async copy(postId) {
const { user } = await this.getUser();
const postUtil = this.components.get(post_util_1.PostUtil);
const post = await postUtil.get(postId);
if (!post.props || !user.props?.ID) {
throw new error_1.CrudError(error_1.StatusMessage.NOT_FOUND, "Post not found");
}
const userId = user.props.ID;
if (post.props.post_type == "attachment") {
throw new error_1.CrudError(error_1.StatusMessage.BAD_REQUEST, "Sorry, you are not allowed to copy attachments");
}
await this.checkPermission("edit_post", post.props.post_type, postId);
const queryUtil = this.components.get(query_util_1.QueryUtil);
const postTitle = await (async () => {
const targetPostTitle = post?.props?.post_type;
let postTitle = `Copy of ${post?.props?.post_title}`;
for (let i = 0; i < 10; i++) {
const copyPosts = await queryUtil.posts((query) => {
query
.where("post_title", postTitle)
.where("post_type", targetPostTitle)
.builder.limit(1);
});
if (!copyPosts) {
return postTitle;
}
postTitle = `Copy of ${post?.props?.post_title} ${i + 2}`;
}
const maxSuffix = 10;
return `Copy of ${post?.props?.post_title} ${Math.floor(Math.random() * (maxSuffix + 999990010 - maxSuffix + 1) + maxSuffix + 1)}`;
})();
const postUpsert = (await this.getAsUpsert(postId)).data;
postUpsert.ID = undefined;
postUpsert.post_status = "draft";
postUpsert.post_title = postTitle;
postUpsert.post_author = userId;
const copiedPostId = (await this.create(postUpsert)).data;
return this.returnValue(copiedPostId);
}
async list(args, options) {
const { password = "", postTypes = ["post"], context = "view", countGroupBy: includeCountGroupBy, } = options ?? {};
const queryUtil = this.components.get(query_util_1.QueryUtil);
const postUtil = this.components.get(post_util_1.PostUtil);
const parsedArgs = val.crud.postListParams.parse(args ?? {});
const { user } = await this.getUser();
const role = await user.role();
let canReadPrivatePosts = true;
for (const postType of postTypes) {
const postTypeObject = postUtil.getTypeObject(postType);
if (!postTypeObject) {
throw new error_1.CrudError(error_1.StatusMessage.BAD_REQUEST, `Invalid post type`);
}
if (context == "edit" && !(await this.canEditPosts(postType))) {
throw new error_1.CrudError(error_1.StatusMessage.UNAUTHORIZED, "Sorry, you are not allowed to edit posts in this post type");
}
canReadPrivatePosts =
canReadPrivatePosts && (await this.canReadPrivatePosts(postType));
}
let stickyPostIds = [];
if (parsedArgs.sticky) {
try {
stickyPostIds = zod_1.z
.array(zod_1.z.number())
.parse(await this.components
.get(options_1.Options)
.get("sticky_posts", { default: (0, common_1.phpSerialize)([]) }));
}
catch (e) {
stickyPostIds = [0];
}
}
const protectedPostIds = [];
const dateTimeUtil = this.components.get(date_time_util_1.DateTimeUtil);
const buildQuery = (query) => {
const { column } = query.alias;
const offset = parsedArgs.offset ?? (parsedArgs.page - 1) * parsedArgs.per_page;
const limit = parsedArgs.per_page;
query.builder.offset(offset).limit(limit).groupBy(column("posts", "ID"));
query.whereIn("post_type", postTypes);
// Check for mime types
if (postTypes.includes("attachment") &&
Array.isArray(options?.mimeTypes)) {
query.andWhere((query) => {
for (const mimeType of options.mimeTypes) {
// Use whereLike if mimeType does not have a slash
if (!mimeType.includes("/")) {
query.or.whereLike("post_mime_type", mimeType);
}
else {
query.or.where("post_mime_type", mimeType);
}
}
});
}
if (parsedArgs.orderby) {
query.builder.orderBy(column("posts", parsedArgs.orderby), parsedArgs.order);
}
if (role.is("anonymous")) {
query.where("post_status", "publish").andWhere((query) => {
query.where("post_password", password).or.where("post_password", "");
});
}
if (!canReadPrivatePosts) {
query.andWhereNot((query) => query.where("post_status", "private"));
}
if (stickyPostIds.length > 0 || Array.isArray(parsedArgs.include)) {
query.whereIn("ID", [...stickyPostIds, ...(parsedArgs.include ?? [])]);
}
if (Array.isArray(parsedArgs.exclude)) {
query.not.andWhere((query) => query.whereIn("ID", parsedArgs.exclude));
}
if (parsedArgs.search) {
query.andWhere((query) => {
const searchColumns = [
"post_title",
"post_excerpt",
"post_content",
];
for (const searchColumn of searchColumns) {
parsedArgs.search &&
query.or.whereLike(searchColumn, parsedArgs.search);
}
});
}
if (parsedArgs.meta && context == "edit") {
query
.withMeta()
.where("meta_key", parsedArgs.meta.key)
.where("meta_value", parsedArgs.meta.value);
}
if (parsedArgs.exclude_meta && context == "edit") {
query.withoutMeta(parsedArgs.exclude_meta.key, parsedArgs.exclude_meta.value);
}
const taxonomyNames = [];
for (const key of Object.keys(parsedArgs)) {
if (!parsedArgs[key] || key == "meta")
continue;
const value = parsedArgs[key];
switch (key) {
case "after":
case "before":
query.where("post_date", dateTimeUtil.get(new Date(value)).mySQLDatetime, key == "after" ? ">=" : "<=");
break;
case "modified_after":
case "modified_before":
query.where("post_date", dateTimeUtil.get(new Date(value)).mySQLDatetime, key == "modified_after" ? ">=" : "<=");
break;
case "author":
Array.isArray(value) && query.whereIn("post_author", value);
break;
case "author_exclude":
Array.isArray(value) &&
query.andWhereNot((query) => query.whereIn("post_author", value));
break;
case "slug":
query.where("post_name", value);
break;
case "status":
Array.isArray(value) && query.whereIn("post_status", value);
break;
case "status_exclude":
Array.isArray(value) &&
query.andWhereNot((query) => query.whereIn("post_status", value));
break;
case "categories":
case "categories_exclude":
taxonomyNames.push("category");
break;
case "tags":
case "tags_exclude":
taxonomyNames.push("post_tag");
}
}
if (taxonomyNames.length > 0) {
query.withTerms(taxonomyNames, (query) => {
query[parsedArgs.tax_relation === "OR" ? "orWhere" : "andWhere"]((query) => {
if (parsedArgs.categories || parsedArgs.tags) {
query.whereIn("term_id", [
...(parsedArgs.categories ?? []),
...(parsedArgs.tags ?? []),
]);
}
if (parsedArgs.categories_exclude || parsedArgs.tags_exclude) {
query.andWhereNot((query) => query.whereIn("term_id", [
...(parsedArgs.categories_exclude ?? []),
...(parsedArgs.tags_exclude ?? []),
]));
}
});
});
}
};
const posts = (await queryUtil.posts((query) => {
buildQuery(query);
})) ?? [];
const counts = await queryUtil.posts((query) => {
buildQuery(query);
query.count("posts", "ID");
}, val.query.resultCount);
const countGroupBy = (includeCountGroupBy
? await queryUtil.posts((query) => {
buildQuery(query);
query
.where("post_type", postTypes)
.countGroupby("posts", includeCountGroupBy);
}, val.query.resultCountGroupBy(includeCountGroupBy))
: []);
const data = [];
for (const post of postUtil.toPosts(posts)) {
const props = post.props;
if (props.post_password != "") {
protectedPostIds.push(props.ID);
}
data.push(await this.formReturnData(post, {
password,
editable: context == "edit",
}));
}
const pagination = this.pagination({
page: parsedArgs.page,
limit: parsedArgs.per_page,
count: counts?.count ?? 0,
});
return this.returnValue(data, {
pagination,
protected: protectedPostIds,
countGroupBy,
});
}
};
exports.PostCrud = PostCrud;
exports.PostCrud = PostCrud = __decorate([
(0, component_1.component)(),
__metadata("design:paramtypes", [components_1.Components])
], PostCrud);