UNPKG

@rnaga/wp-node

Version:

👉 **[View Full Documentation at rnaga.github.io/wp-node →](https://rnaga.github.io/wp-node/)**

590 lines (589 loc) • 28 kB
"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_category && diffData.post_category.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_category = currentPost.post_category ?? 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_category: (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.categories.length > 0) || (parsedArgs.tags && parsedArgs.tags.length > 0)) { query.whereIn("term_id", [ ...(parsedArgs.categories ?? []), ...(parsedArgs.tags ?? []), ]); } if ((parsedArgs.categories_exclude && parsedArgs.categories_exclude.length > 0) || (parsedArgs.tags_exclude && parsedArgs.tags_exclude.length > 0)) { 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);