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
48 changes: 43 additions & 5 deletions src/wp-includes/class-wp-comment-type.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,33 @@ final class WP_Comment_Type {
*/
public $internal = false;

/**
* The string to use to build the edit, delete, and moderate capabilities.
*
* May be registered as an array to allow for alternative plurals when using
* this argument as a base to construct the capabilities, e.g.
* array( 'story', 'stories' ). set_props() collapses the array form back to
* the singular base once the capabilities are built. Default 'comment'.
*
* @since 7.1.0
* @var string
*/
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 @@ -183,17 +210,28 @@ 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,
'_builtin' => false,
'labels' => array(),
'description' => '',
'public' => true,
'internal' => false,
'capability_type' => 'comment',
'capabilities' => array(),
'_builtin' => false,
);

$args = array_merge( $defaults, $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];
}

/*
* Comment types are never hierarchical. The property exists only so the shared
* label helper can pick a slot, and the hierarchical slot is deliberately null,
Expand Down
135 changes: 119 additions & 16 deletions src/wp-includes/comment.php
Original file line number Diff line number Diff line change
Expand Up @@ -434,22 +434,39 @@ function create_initial_comment_types() {
* @param array|string $args {
* Optional. Array or string of arguments for registering a comment type. Default empty array.
*
* @type string $label Name of the comment type. Usually plural.
* Default is the value of $labels['name'].
* @type string[] $labels An array of labels for this comment type. If not set, the
* default comment labels are used. See get_comment_type_labels()
* for a full list of supported labels.
* @type string $description A short descriptive summary of what the comment type is.
* Default empty.
* @type bool $public Whether the comment type is intended for use publicly either via
* the admin interface or by front-end users. Core does not
* currently act on this argument. Default true.
* @type bool $internal Whether the comment type is for internal use only. Internal types
* are excluded from comment queries and counts by default, through
* the {@see 'default_excluded_comment_types'} filter. Default false.
* @type bool $_builtin For internal core use only. Marks the type as native to
* WordPress, which blocks it from being re-registered or
* unregistered. Default false.
* @type string $label Name of the comment type. Usually plural.
* Default is the value of $labels['name'].
* @type string[] $labels An array of labels for this comment type. If not set, the
* default comment labels are used. See
* get_comment_type_labels() for a full list of supported
* labels.
* @type string $description A short descriptive summary of what the comment type is.
* Default empty.
* @type bool $public Whether the comment type is intended for use publicly
* either via the admin interface or by front-end users.
* Core does not currently act on this argument.
* Default true.
* @type bool $internal Whether the comment type is for internal use only.
* Internal types are excluded from comment queries and
* counts by default, through the
* {@see 'default_excluded_comment_types'} filter.
* Default false.
* @type string|array $capability_type The string to use to build the edit, delete, and moderate
* 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' ).
* These capabilities are advisory metadata and are not
* enforced by core's capability mapping in this release.
* Default 'comment'.
* @type string[] $capabilities Array of capabilities for this comment type.
* $capability_type is used as a base to construct
* capabilities by default. As with $capability_type, these
* are advisory metadata and are not enforced by core's
* capability mapping in this release.
* See get_comment_type_capabilities().
* @type bool $_builtin For internal core use only. Marks the type as native to
* WordPress, which blocks it from being re-registered or
* unregistered. Default false.
* }
* @return WP_Comment_Type|WP_Error The registered comment type object on success,
* WP_Error object on failure.
Expand Down Expand Up @@ -705,6 +722,92 @@ 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' ).
*
* Note: With the default `capability_type` of 'comment', most of the generated
* primitive capabilities (`edit_comments`, `edit_others_comments`,
* `delete_comments`) exist in no default role and are not consulted by the
* default capability mapping; `moderate_comments` is the only generated
* primitive that default roles grant. Consumers should check the meta
* capabilities together with a comment ID instead of testing those primitives
* directly.
*
* Note: In this release, `edit_comment` on the default 'comment' base is the
* only generated capability that map_meta_cap() resolves. The remaining meta
* capabilities, and every capability generated from a custom base, are treated
* as primitives: a check requires the literal capability, which no default role
* grants, so it denies everyone until comment type capability mapping is added.
*
* Note: Do not reuse a post type's `capability_type` as a comment type base.
* A base of 'post' generates `edit_comment => 'edit_post'`, which map_meta_cap()
* resolves through its post branch, so the check would be answered by a post
* with the passed comment's ID.
*
* Note: The `capability_type` property of the passed object is normalized to
* its array form as a side effect of calling this function, matching
* get_post_type_capabilities().
*
* @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.
*
* @type string $edit_comment Meta capability to edit a comment of this type.
* Default 'edit_comment'.
* @type string $delete_comment Meta capability to delete a comment of this type.
* Default 'delete_comment'.
* @type string $moderate_comment Meta capability to moderate a comment of this type.
* Default 'moderate_comment'.
* @type string $edit_comments Primitive capability to edit comments of this type.
* Default 'edit_comments'.
* @type string $edit_others_comments Primitive capability to edit comments of this type
* authored by other users. Default 'edit_others_comments'.
* @type string $delete_comments Primitive capability to delete comments of this type.
* Default 'delete_comments'.
* @type string $moderate_comments Primitive capability to moderate comments of this type.
* Default 'moderate_comments'.
* }
*/
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,
'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
176 changes: 176 additions & 0 deletions tests/phpunit/tests/comment/types.php
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,182 @@ public function test_registered_comment_type_action_receives_type_and_object() {
$this->assertSame( 'foo', $args[0][1]->name );
}

/**
* @ticket 35214
*/
public function test_registered_comment_type_exposes_cap_object() {
register_comment_type( 'foo', array( 'capability_type' => 'review' ) );

$cobj = get_comment_type_object( 'foo' );

$this->assertSame( 'edit_reviews', $cobj->cap->edit_comments );
$this->assertSame( 'moderate_reviews', $cobj->cap->moderate_comments );
}

/**
* The built-in comment type's capabilities match the existing core comment capabilities.
*
* @ticket 35214
*/
public function test_built_in_comment_type_capabilities_are_backward_compatible() {
$cobj = get_comment_type_object( 'comment' );

$this->assertSame( 'edit_comment', $cobj->cap->edit_comment );
$this->assertSame( 'moderate_comments', $cobj->cap->moderate_comments );
}

/**
* @ticket 35214
*
* @covers ::get_comment_type_capabilities
*/
public function test_get_comment_type_capabilities_from_string() {
$caps = get_comment_type_capabilities(
(object) array(
'capability_type' => 'review',
'capabilities' => array(),
)
);

$this->assertSame( 'edit_review', $caps->edit_comment );
$this->assertSame( 'edit_reviews', $caps->edit_comments );
$this->assertSame( 'edit_others_reviews', $caps->edit_others_comments );
$this->assertSame( 'delete_review', $caps->delete_comment );
$this->assertSame( 'moderate_reviews', $caps->moderate_comments );
}

/**
* @ticket 35214
*
* @covers ::get_comment_type_capabilities
*/
public function test_get_comment_type_capabilities_honors_capabilities_override() {
$caps = get_comment_type_capabilities(
(object) array(
'capability_type' => 'comment',
'capabilities' => array(
'edit_comments' => 'manage_stuff',
),
)
);

$this->assertSame( 'manage_stuff', $caps->edit_comments );
}

/**
* The full set of meta and primitive capabilities is generated from the base.
*
* @ticket 35214
*
* @covers ::get_comment_type_capabilities
*/
public function test_get_comment_type_capabilities_generates_full_set() {
$caps = get_comment_type_capabilities(
(object) array(
'capability_type' => 'review',
'capabilities' => array(),
)
);

$expected = array(
// Meta capabilities.
'edit_comment' => 'edit_review',
'delete_comment' => 'delete_review',
'moderate_comment' => 'moderate_review',
// Primitive capabilities.
'edit_comments' => 'edit_reviews',
'edit_others_comments' => 'edit_others_reviews',
'delete_comments' => 'delete_reviews',
'moderate_comments' => 'moderate_reviews',
);

$this->assertSame( $expected, (array) $caps );
}

/**
* An array capability type supplies an explicit plural base for primitive caps.
*
* @ticket 35214
*
* @covers ::get_comment_type_capabilities
*/
public function test_get_comment_type_capabilities_from_array() {
$caps = get_comment_type_capabilities(
(object) array(
'capability_type' => array( 'story', 'stories' ),
'capabilities' => array(),
)
);

// Singular base drives the meta capabilities.
$this->assertSame( 'edit_story', $caps->edit_comment );
$this->assertSame( 'delete_story', $caps->delete_comment );
// Explicit plural base drives the primitive capabilities.
$this->assertSame( 'edit_stories', $caps->edit_comments );
$this->assertSame( 'delete_stories', $caps->delete_comments );
$this->assertSame( 'moderate_stories', $caps->moderate_comments );
}

/**
* A 'read_comment' meta capability is deliberately not generated: map_meta_cap() has no
* case for it, so advertising it would hand consumers a capability that denies everyone.
* Pinned so it is only added alongside its mapping.
*
* @ticket 35214
*
* @covers ::get_comment_type_capabilities
*/
public function test_get_comment_type_capabilities_omits_read_comment() {
$caps = get_comment_type_capabilities(
(object) array(
'capability_type' => 'review',
'capabilities' => array(),
)
);

$this->assertObjectNotHasProperty( 'read_comment', $caps );
$this->assertObjectNotHasProperty( 'read_comment', get_comment_type_object( 'comment' )->cap );
}

/**
* The built-in types all register without a capability type, so they share one cap set.
* Anything that gated on the existing comment capabilities keeps working for all four.
*
* @ticket 35214
*
* @dataProvider data_built_in_comment_types
*
* @param string $comment_type Built-in comment type name.
*/
public function test_built_in_comment_types_share_the_comment_capabilities( $comment_type ) {
$this->assertEquals(
get_comment_type_object( 'comment' )->cap,
get_comment_type_object( $comment_type )->cap
);
}

/**
* An override applies to meta capabilities as well as primitives, so a type can point a
* meta capability at a name that map_meta_cap() already resolves.
*
* @ticket 35214
*
* @covers ::get_comment_type_capabilities
*/
public function test_get_comment_type_capabilities_honors_a_meta_capability_override() {
$caps = get_comment_type_capabilities(
(object) array(
'capability_type' => 'review',
'capabilities' => array(
'edit_comment' => 'edit_review_item',
),
)
);

$this->assertSame( 'edit_review_item', $caps->edit_comment, 'The override should win.' );
$this->assertSame( 'delete_review', $caps->delete_comment, 'The rest should still derive from the base.' );
}

/**
* Comment types are never hierarchical. The default labels reserve the hierarchical
* slot as null, so honoring a provided value would resolve every label to null.
Expand Down
Loading
Loading