Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 95 additions & 6 deletions src/wp-includes/capabilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -572,16 +572,105 @@ function map_meta_cap( $cap, $user_id, ...$args ) {
break;
}

$post = get_post( $comment->comment_post_ID );
$comment_type = get_comment_type_object( $comment->comment_type );

/*
* If the post doesn't exist, we have an orphaned comment.
* Fall back to the edit_posts capability, instead.
* Comment types using the default 'comment' capability model derive edit
* permission from the comment's parent post, preserving historical behavior.
* A registered type that opts into its own capabilities (its mapped
* `edit_comment` differs from the generic meta capability) is gated by those
* primitives instead, mirroring how registered post types map `edit_post`.
*/
if ( $post ) {
$caps = map_meta_cap( 'edit_post', $user_id, $post->ID );
if ( ! $comment_type || 'edit_comment' === $comment_type->cap->edit_comment ) {
$post = get_post( $comment->comment_post_ID );

/*
* If the post doesn't exist, we have an orphaned comment.
* Fall back to the edit_posts capability, instead.
*/
if ( $post ) {
$caps = map_meta_cap( 'edit_post', $user_id, $post->ID );
} else {
$caps = map_meta_cap( 'edit_posts', $user_id );
}
} elseif ( $comment->user_id && (int) $comment->user_id === (int) $user_id ) {
$caps[] = $comment_type->cap->edit_comments;
} else {
$caps[] = $comment_type->cap->edit_others_comments;
}
break;
case 'delete_comment':
if ( ! isset( $args[0] ) ) {
/* translators: %s: Capability name. */
$message = __( 'When checking for the %s capability, you must always check it against a specific comment.' );

_doing_it_wrong(
__FUNCTION__,
sprintf( $message, '<code>' . $cap . '</code>' ),
'7.1.0'
);

$caps[] = 'do_not_allow';
break;
}

$comment = get_comment( $args[0] );
if ( ! $comment ) {
$caps[] = 'do_not_allow';
break;
}

$comment_type = get_comment_type_object( $comment->comment_type );

/*
* As with editing, deletion of a default-model comment follows the
* comment's parent post. A type with its own capabilities is gated by its
* `delete_comments` primitive.
*/
if ( ! $comment_type || 'delete_comment' === $comment_type->cap->delete_comment ) {
$post = get_post( $comment->comment_post_ID );

if ( $post ) {
$caps = map_meta_cap( 'edit_post', $user_id, $post->ID );
} else {
$caps = map_meta_cap( 'edit_posts', $user_id );
}
} else {
$caps[] = $comment_type->cap->delete_comments;
}
break;
case 'moderate_comment':
if ( ! isset( $args[0] ) ) {
/* translators: %s: Capability name. */
$message = __( 'When checking for the %s capability, you must always check it against a specific comment.' );

_doing_it_wrong(
__FUNCTION__,
sprintf( $message, '<code>' . $cap . '</code>' ),
'7.1.0'
);

$caps[] = 'do_not_allow';
break;
}

$comment = get_comment( $args[0] );
if ( ! $comment ) {
$caps[] = 'do_not_allow';
break;
}

/*
* Moderating a default-model comment requires the global `moderate_comments`
* primitive, exactly as today. A type with its own capabilities is gated by
* its `moderate_comments` primitive (e.g. `moderate_reviews`).
*/
$comment_type = get_comment_type_object( $comment->comment_type );

if ( $comment_type ) {
$caps[] = $comment_type->cap->moderate_comments;
} else {
$caps = map_meta_cap( 'edit_posts', $user_id );
$caps[] = 'moderate_comments';
}
break;
case 'unfiltered_upload':
Expand Down
49 changes: 43 additions & 6 deletions src/wp-includes/class-wp-comment-type.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,32 @@ final class WP_Comment_Type {
*/
public $show_ui;

/**
* The string to use to build the read, edit, and delete capabilities.
*
* May be passed as an array to allow for alternative plurals when using
* this argument as a base to construct the capabilities, e.g.
* array( 'story', 'stories' ). Default 'comment'.
*
* @since 7.1.0
* @var string|array
*/
public $capability_type = 'comment';

/**
* Capabilities for this comment type.
*
* Built by {@see get_comment_type_capabilities()} from the
* `capability_type` and `capabilities` arguments. This is advisory metadata
* describing the capabilities associated with the comment type; the
* capability mapping in {@see map_meta_cap()} is not affected by this
* property in this release.
*
* @since 7.1.0
* @var stdClass
*/
public $cap;

/**
* Whether this comment type is a native or "built-in" comment type.
*
Expand Down Expand Up @@ -187,12 +213,14 @@ public function set_props( $args ) {
* treated as a provided value and overwrite the default name with false.
*/
$defaults = array(
'labels' => array(),
'description' => '',
'public' => true,
'internal' => false,
'show_ui' => null,
'_builtin' => false,
'labels' => array(),
'description' => '',
'public' => true,
'internal' => false,
'show_ui' => null,
'capability_type' => 'comment',
'capabilities' => array(),
'_builtin' => false,
);

$args = array_merge( $defaults, $args );
Expand All @@ -204,6 +232,15 @@ public function set_props( $args ) {

$args['name'] = $this->name;

// Build the capabilities object, then remove the input array from the props.
$this->cap = get_comment_type_capabilities( (object) $args );
unset( $args['capabilities'] );

// Collapse an array capability type back to its singular base.
if ( is_array( $args['capability_type'] ) ) {
$args['capability_type'] = $args['capability_type'][0];
}

foreach ( $args as $property_name => $property_value ) {
$this->$property_name = $property_value;
}
Expand Down
59 changes: 57 additions & 2 deletions src/wp-includes/comment.php
Original file line number Diff line number Diff line change
Expand Up @@ -365,8 +365,16 @@ function create_initial_comment_types() {
* the admin interface or by front-end users. Default true.
* @type bool $internal Whether the comment type is for internal use only and should be
* excluded from default public-facing contexts. Default false.
* @type bool $show_ui Whether to generate and allow a UI for managing this comment type
* in the admin. Default is value of $public.
* @type bool $show_ui Whether to generate and allow a UI for managing this comment
* type in the admin. Default is value of $public.
* @type string|array $capability_type The string to use to build the read, edit, and delete
* capabilities. May be passed as an array to allow for
* alternative plurals when using this argument as a base to
* construct the capabilities, e.g. array( 'story', 'stories' ).
* Default 'comment'.
* @type string[] $capabilities Array of capabilities for this comment type. $capability_type
* is used as a base to construct capabilities by default.
* See get_comment_type_capabilities().
* }
* @return WP_Comment_Type|WP_Error The registered comment type object on success,
* WP_Error object on failure.
Expand Down Expand Up @@ -566,6 +574,53 @@ function get_comment_type_labels( $comment_type_object ) {
return $labels;
}

/**
* Builds an object with all comment type capabilities out of a comment type object.
*
* Comment type capabilities use the `capability_type` argument as a base, if
* the capability is not set in the `capabilities` argument.
*
* This is advisory metadata describing the capabilities associated with a comment
* type, modeled on {@see get_post_type_capabilities()}. The capability mapping in
* {@see map_meta_cap()} is not affected by these capabilities in this release.
*
* The capability strings are built from the `capability_type` argument, which may
* be a string or an array. When a string, the plural is created by appending an
* 's'. When an array, the first element is the singular base and the second the
* plural base, e.g. array( 'story', 'stories' ).
*
* @since 7.1.0
*
* @param object $args Comment type registration arguments. Expects the
* `capability_type` and `capabilities` properties.
* @return object Object with all the capabilities as member variables.
*/
function get_comment_type_capabilities( $args ) {
if ( ! is_array( $args->capability_type ) ) {
$args->capability_type = array( $args->capability_type, $args->capability_type . 's' );
}

// Singular base for meta capabilities, plural base for primitive capabilities.
list( $singular_base, $plural_base ) = $args->capability_type;

$default_capabilities = array(
// Meta capabilities.
'edit_comment' => 'edit_' . $singular_base,
'read_comment' => 'read_' . $singular_base,
'delete_comment' => 'delete_' . $singular_base,
'moderate_comment' => 'moderate_' . $singular_base,
// Primitive capabilities used outside of map_meta_cap().
'edit_comments' => 'edit_' . $plural_base,
'edit_others_comments' => 'edit_others_' . $plural_base,
'delete_comments' => 'delete_' . $plural_base,
'moderate_comments' => 'moderate_' . $plural_base,
);

$capabilities = array_merge( $default_capabilities, $args->capabilities );

return (object) $capabilities;
}

/**
* Retrieves all of the WordPress supported comment statuses.
*
Expand Down
Loading