@rnaga/wp-node
Version:
👉 **[View Full Documentation at rnaga.github.io/wp-node →](https://rnaga.github.io/wp-node/)**
626 lines (625 loc) • 28 kB
JavaScript
"use strict";
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 __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.Capabilities = void 0;
const config_1 = require("../config");
const constants_1 = require("../constants");
/* eslint-disable no-case-declarations */
const component_1 = require("../decorators/component");
const components_1 = require("./components");
const current_1 = require("./current");
const options_1 = require("./options");
const user_1 = require("./user");
const comment_util_1 = require("./utils/comment.util");
const meta_util_1 = require("./utils/meta.util");
const post_util_1 = require("./utils/post.util");
const taxonomy_util_1 = require("./utils/taxonomy.util");
const term_util_1 = require("./utils/term.util");
const user_util_1 = require("./utils/user.util");
const vars_1 = require("./vars");
let Capabilities = class Capabilities {
config;
components;
constructor(config, components) {
this.config = config;
this.components = components;
}
addCap(set, postType, key) {
if (!postType.capabilities || !postType.capabilities[key]) {
return;
}
set.add(postType.capabilities[key]);
}
async check(action, user, ...args) {
//let results: string[] = [];
let results = new Set();
let post, postType;
const options = this.components.get(options_1.Options);
const current = this.components.get(current_1.Current);
const postUtil = this.components.get(post_util_1.PostUtil);
const userUtil = this.components.get(user_util_1.UserUtil);
const commentUtil = this.components.get(comment_util_1.CommentUtil);
const metaUtil = this.components.get(meta_util_1.MetaUtil);
const taxonomyUtil = this.components.get(taxonomy_util_1.TaxonomyUtil);
const termUtil = this.components.get(term_util_1.TermUtil);
const vars = this.components.get(vars_1.Vars);
if (!(user instanceof user_1.User)) {
user = await userUtil.get(user);
}
const role = await user.role();
if (!user) {
return [constants_1.DO_NOT_ALLOW];
}
switch (action) {
case "remove_user":
// In multisite the user must be a super admin to remove themselves.
if (args[0] && user.props?.ID == args[0] && !role.isSuperAdmin()) {
results.add(constants_1.DO_NOT_ALLOW);
}
else {
results.add("remove_users");
}
break;
case "promote_user":
case "add_users":
results.add("promote_users");
break;
case "edit_user":
case "edit_users":
// Allow user to edit themselves.
if ("edit_user" === action && args[0] && user.props?.ID == args[0]) {
break;
}
const targetUser = await userUtil.get(args[0]);
const targetRole = await targetUser.role();
// In multisite the user must have manage_network_users caps. If editing a super admin, the user must be a super admin.
if (this.config.isMultiSite() &&
((!role.isSuperAdmin() &&
"edit_user" === action &&
targetRole.isSuperAdmin()) ||
!role.has("manage_network_users"))) {
results.add(constants_1.DO_NOT_ALLOW);
}
else {
results.add("edit_users"); // edit_user maps to edit_users.
}
break;
case "delete_post":
case "delete_page":
if (!args[0]) {
// When checking for the %s capability, you must always check it against a specific post.
results.add(constants_1.DO_NOT_ALLOW);
break;
}
post = await postUtil.get(args[0]);
if (!post.props?.ID) {
results.add(constants_1.DO_NOT_ALLOW);
break;
}
if ("revision" === post.props.post_type) {
results.add(constants_1.DO_NOT_ALLOW);
break;
}
if (parseInt((await options.get("page_for_posts")) ?? "-1") ==
post.props.ID ||
parseInt((await options.get("page_on_front")) ?? "-1") ==
post.props.ID) {
results.add("manage_options");
break;
}
postType = postUtil.getTypeObject(post.props.post_type);
// The post type %1$s is not registered, so it may not be reliable
// to check the capability %2$s against a post of that type.
if (!postType) {
results.add("edit_others_posts");
break;
}
if (!postType.mapMetaCap) {
this.addCap(results, postType, action);
break;
}
//If the post author is set and the user is the author...
if (post.props.post_author &&
user.props?.ID == post.props.post_author) {
// If the post is published or scheduled...
//if ( in_array( $post->post_status, array( 'publish', 'future' ), true ) ) {
if (["publish", "future"].includes(post.props.post_status)) {
this.addCap(results, postType, "delete_published_posts");
}
else if ("trash" === post.props.post_status) {
const postStatus = await post.meta.get("_wp_trash_meta_status");
if (postStatus && ["publish", "future"].includes(postStatus)) {
this.addCap(results, postType, "delete_published_posts");
}
else {
this.addCap(results, postType, "delete_posts");
}
}
else {
// If the post is draft...
this.addCap(results, postType, "delete_posts");
}
}
else {
// The user is trying to edit someone else's post.
this.addCap(results, postType, "delete_others_posts");
// The post is published or scheduled, extra cap required.
if (["publish", "future"].includes(post.props.post_status)) {
this.addCap(results, postType, "delete_published_posts");
}
else if ("private" === post.props.post_status) {
this.addCap(results, postType, "delete_private_posts");
}
}
break;
/*
* edit_post breaks down to edit_posts, edit_published_posts, or
* edit_others_posts.
*/
case "edit_post":
case "edit_page":
if (!args[0]) {
// When checking for the %s capability, you must always check it against a specific post.'
results.add(constants_1.DO_NOT_ALLOW);
break;
}
post = await postUtil.get(args[0]);
if (!post.props?.ID) {
results.add(constants_1.DO_NOT_ALLOW);
break;
}
if ("revision" === post.props.post_type) {
post = await postUtil.get(post.props.post_parent);
if (!post.props?.ID) {
results.add(constants_1.DO_NOT_ALLOW);
break;
}
}
postType = postUtil.getTypeObject(post.props.post_type);
if (!postType) {
// 'The post type %1$s is not registered,
// so it may not be reliable to check the capability %2$s against a post of that type.'
results.add("edit_others_posts");
break;
}
if (!postType.mapMetaCap) {
this.addCap(results, postType, action);
break;
}
// If the post author is set and the user is the author...
if (post.props.post_author &&
user.props?.ID == post.props.post_author) {
// If the post is published or scheduled...
if (["publish", "future"].includes(post.props.post_status)) {
this.addCap(results, postType, "edit_published_posts");
}
else if ("trash" === post.props.post_status) {
const postStatus = await post.meta.get("_wp_trash_meta_status");
if (postStatus && ["publish", "future"].includes(postStatus)) {
this.addCap(results, postType, "edit_published_posts");
}
else {
this.addCap(results, postType, "edit_posts");
}
}
else {
// If the post is draft...
this.addCap(results, postType, "edit_posts");
}
}
else {
// The user is trying to edit someone else's post.
this.addCap(results, postType, "edit_others_posts");
// The post is published or scheduled, extra cap required.
if (["publish", "future"].includes(post.props.post_status)) {
this.addCap(results, postType, "edit_published_posts");
}
else if ("private" === post.props.post_status) {
this.addCap(results, postType, "edit_private_posts");
}
}
break;
case "read_post":
case "read_page":
if (!args[0]) {
// 'When checking for the %s capability, you must always check it against a specific post.'
// 'When checking for the %s capability, you must always check it against a specific page.'
results.add(constants_1.DO_NOT_ALLOW);
break;
}
post = await postUtil.get(args[0]);
if (!post.props?.ID) {
results.add(constants_1.DO_NOT_ALLOW);
break;
}
if ("revision" === post.props.post_type) {
post = await postUtil.get(post.props.post_parent);
if (!post.props?.ID) {
results.add(constants_1.DO_NOT_ALLOW);
break;
}
}
postType = postUtil.getTypeObject(post.props.post_type);
if (!postType) {
// 'The post type %1$s is not registered,
// so it may not be reliable to check the capability %2$s against a post of that type.'
results.add("edit_others_posts");
break;
}
if (!postType.mapMetaCap) {
this.addCap(results, postType, action);
break;
}
const postStatusObject = postUtil.getStatusObject(await postUtil.getStatus(post));
if (!postStatusObject) {
// The post status %1$s is not registered,
// so it may not be reliable to check the capability %2$s against a post with that status.
results.add("edit_others_posts");
break;
}
if (postStatusObject.public) {
this.addCap(results, postType, "read");
break;
}
if (post.props.post_author &&
user.props?.ID == post.props.post_author) {
this.addCap(results, postType, "read");
}
else if (postStatusObject.private) {
this.addCap(results, postType, "read_private_posts");
}
else {
results = new Set(await this.check("edit_post", user, post.props.ID));
}
break;
case "publish_post":
if (!args[0]) {
// 'When checking for the %s capability, you must always check it against a specific post.'
results.add(constants_1.DO_NOT_ALLOW);
break;
}
post = await postUtil.get(args[0]);
if (!post.props?.ID) {
results.add(constants_1.DO_NOT_ALLOW);
break;
}
postType = postUtil.getTypeObject(post.props.post_type);
if (!postType) {
// The post type %1$s is not registered,
// so it may not be reliable to check the capability %2$s against a post of that type
results.add("edit_others_posts");
break;
}
this.addCap(results, postType, "publish_posts");
break;
case "edit_post_meta":
case "delete_post_meta":
case "add_post_meta":
case "edit_comment_meta":
case "delete_comment_meta":
case "add_comment_meta":
case "edit_term_meta":
case "delete_term_meta":
case "add_term_meta":
case "edit_user_meta":
case "delete_user_meta":
case "add_user_meta":
const objectType = action.split("_")[1] ?? "";
if (!args[0]) {
results.add(constants_1.DO_NOT_ALLOW);
break;
}
const objectId = parseInt(args[0]);
const objectSubType = await metaUtil.getObjectSubtype(objectType, objectId);
if (!objectSubType) {
results.add(constants_1.DO_NOT_ALLOW);
break;
}
results = new Set(await this.check(`edit_${objectType}`, user, objectId));
if (typeof args[1] == "string" && metaUtil.isProtected(args[1])) {
results.add(action);
}
break;
case "edit_comment":
if (!args[0]) {
results.add(constants_1.DO_NOT_ALLOW);
break;
}
const comment = await commentUtil.get(args[0]);
if (!comment.props?.comment_ID) {
results.add(constants_1.DO_NOT_ALLOW);
break;
}
post = await postUtil.get(comment.props.comment_post_ID);
/*
* If the post doesn't exist, we have an orphaned comment.
* Fall back to the edit_posts capability, instead.
*/
if (post.props?.ID) {
results = new Set(await this.check("edit_post", user, post.props.ID));
}
else {
results = new Set(await this.check("edit_posts", user));
}
break;
case "unfiltered_upload":
if (this.config.config.constants.ALLOW_UNFILTERED_UPLOADS &&
(!this.config.isMultiSite() || role.isSuperAdmin())) {
results.add(action);
}
else {
results.add(constants_1.DO_NOT_ALLOW);
}
break;
case "edit_css":
case "unfiltered_html":
// Disallow unfiltered_html for all users, even admins and super admins.
if (this.config.config.constants.DISALLOW_UNFILTERED_HTML) {
results.add(constants_1.DO_NOT_ALLOW);
}
else if (this.config.isMultiSite() && !role.isSuperAdmin()) {
results.add(constants_1.DO_NOT_ALLOW);
}
else {
results.add("unfiltered_html");
}
break;
case "edit_files":
case "edit_plugins":
case "edit_themes":
// Disallow the file editors.
if (this.config.config.constants.DISALLOW_FILE_EDIT) {
results.add(constants_1.DO_NOT_ALLOW);
}
else if (this.config.isMultiSite() && !role.isSuperAdmin()) {
results.add(constants_1.DO_NOT_ALLOW);
}
else {
results.add(action);
}
break;
case "update_plugins":
case "delete_plugins":
case "install_plugins":
case "upload_plugins":
case "update_themes":
case "delete_themes":
case "install_themes":
case "upload_themes":
case "update_core":
/*
* Disallow anything that creates, deletes, or updates core, plugin, or theme files.
* Files in uploads are excepted.
*/
if (this.config.isMultiSite() && !role.isSuperAdmin()) {
results.add(constants_1.DO_NOT_ALLOW);
}
else if ("upload_themes" === action) {
results.add("install_themes");
}
else if ("upload_plugins" === action) {
results.add("install_plugins");
}
else {
results.add(action);
}
break;
case "install_languages":
case "update_languages":
if (this.config.isMultiSite() && !role.isSuperAdmin()) {
results.add(constants_1.DO_NOT_ALLOW);
}
else {
results.add("install_languages");
}
break;
case "activate_plugins":
case "deactivate_plugins":
case "activate_plugin":
case "deactivate_plugin":
results.add("activate_plugins");
if (this.config.isMultiSite()) {
// update_, install_, and delete_ are handled above with is_super_admin().
const menuPerms = await options.get("menu_items", {
siteId: current.site?.props.site?.id,
default: {},
});
if (Array.isArray(menuPerms?.plugins)) {
results.add("manage_network_plugins");
}
}
break;
case "resume_plugin":
results.add("resume_plugins");
break;
case "resume_theme":
results.add("resume_themes");
break;
case "delete_user":
case "delete_users":
// If multisite only super admins can delete users.
if (this.config.isMultiSite() && !role.isSuperAdmin()) {
results.add(constants_1.DO_NOT_ALLOW);
break;
}
if (action == "delete_user") {
const context = vars.CONTEXT;
results = await context.hooks.filter.asyncApply("core_map_meta_cap_delete_user", results, context, user, ...args);
}
results.add("delete_users"); // delete_user maps to delete_users.
break;
case "create_users":
if (!this.config.isMultiSite()) {
results.add(action);
}
else if (role.isSuperAdmin() ||
1 ===
(await options.get("add_new_users", {
siteId: current.site?.props.site?.id,
}))) {
results.add(action);
}
else {
results.add(constants_1.DO_NOT_ALLOW);
}
break;
case "manage_links":
if (await options.get("link_manager_enabled")) {
results.add(action);
}
else {
results.add(constants_1.DO_NOT_ALLOW);
}
break;
case "customize":
results.add("edit_theme_options");
break;
case "delete_site":
if (this.config.isMultiSite()) {
results.add("manage_options");
}
else {
results.add(constants_1.DO_NOT_ALLOW);
}
break;
case "edit_term":
case "delete_term":
case "assign_term":
if (!args[0]) {
// 'When checking for the %s capability, you must always check it against a specific term.'
results.add(constants_1.DO_NOT_ALLOW);
break;
}
const term = await termUtil.get(args[0]);
if (!term.props?.term_id) {
results.add(constants_1.DO_NOT_ALLOW);
break;
}
const taxonomy = await taxonomyUtil.get(term.props.taxonomy ?? "");
if (taxonomy.isDefault) {
results.add(constants_1.DO_NOT_ALLOW);
break;
}
if ("delete_term" === action &&
((await options.get(`default_${term.props.taxonomy}`)) ==
term.props.term_id ||
(await options.get(`default_term_${term.props.taxonomy}`)) == term.props.term_id)) {
results.add(constants_1.DO_NOT_ALLOW);
break;
}
results = new Set(await this.check(taxonomy.props?.capabilities?.[`${action}s`], user, term.props.term_id));
break;
case "manage_post_tags":
case "edit_categories":
case "edit_post_tags":
case "delete_categories":
case "delete_post_tags":
results.add("manage_categories");
break;
case "assign_categories":
case "assign_post_tags":
results.add("edit_posts");
break;
case "create_sites":
case "delete_sites":
case "manage_network":
case "manage_sites":
case "manage_network_plugins":
case "manage_network_themes":
case "manage_network_options":
case "upgrade_network":
results.add(action);
break;
case "manage_network_users":
const context = vars.CONTEXT;
results = await context.hooks.filter.asyncApply("core_map_meta_cap_manage_network_users", results, context, user, ...args);
if (this.config.isMultiSite() &&
!results.has("manage_network_users") &&
!results.has(constants_1.DO_NOT_ALLOW)) {
results.add("manage_network_users");
}
break;
case "setup_network":
if (this.config.isMultiSite()) {
results.add("manage_network_options");
}
else {
results.add("manage_options");
}
break;
case "update_php":
if (this.config.isMultiSite() && !role.isSuperAdmin()) {
results.add(constants_1.DO_NOT_ALLOW);
}
else {
results.add("update_core");
}
break;
case "update_https":
if (this.config.isMultiSite() && !role.isSuperAdmin()) {
results.add(constants_1.DO_NOT_ALLOW);
}
else {
results.add("manage_options");
results.add("update_core");
}
break;
case "export_others_personal_data":
case "erase_others_personal_data":
case "manage_privacy_options":
results.add(this.config.isMultiSite() ? "manage_network" : "manage_options");
break;
case "create_app_password":
case "list_app_passwords":
case "read_app_password":
case "edit_app_password":
case "delete_app_passwords":
case "delete_app_password":
results = new Set(await this.check("edit_user", user, args[0]));
break;
default:
// Handle meta capabilities for custom post types.
postType = postUtil.getTypeObject("post");
if (postType?.capabilities &&
postType?.capabilities[action] &&
action != postType?.capabilities[action]) {
results = new Set(await this.check(postType.capabilities[action], user, ...args));
break;
}
// Block capabilities map to their post equivalent.
const blockCaps = [
"edit_blocks",
"edit_others_blocks",
"publish_blocks",
"read_private_blocks",
"delete_blocks",
"delete_private_blocks",
"delete_published_blocks",
"delete_others_blocks",
"edit_private_blocks",
"edit_published_blocks",
];
if (blockCaps.includes(action)) {
action = action.replace("_blocks", "_posts");
}
// If no meta caps match, return the original cap.
results.add(action);
}
const context = vars.CONTEXT;
results = await context.hooks.filter.asyncApply("core_map_meta_cap", results, context, action, user, ...args);
return Array.from(results);
}
};
exports.Capabilities = Capabilities;
exports.Capabilities = Capabilities = __decorate([
(0, component_1.component)(),
__metadata("design:paramtypes", [config_1.Config, components_1.Components])
], Capabilities);