Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Patterns: fix capabilities settings for pattern categories #55379

Merged
merged 22 commits into from
Oct 20, 2023
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
e356103
Add capabilities settings to pattern categories so authors can add ca…
glendaviesnz Oct 16, 2023
0627b4b
Fix linting error
glendaviesnz Oct 16, 2023
df14aca
We don't need assign terms as we are not assigning these terms to posts
glendaviesnz Oct 16, 2023
906bc9e
Remove additional capabilities as the defaults work
glendaviesnz Oct 17, 2023
c562738
Try adding a pattern categories rest controller to handle the unique …
talldan Oct 17, 2023
bd8ff09
Don't allow adding of categories if user doesn't have capability, jus…
glendaviesnz Oct 17, 2023
ae34e49
Fix linting issues
glendaviesnz Oct 17, 2023
8fc5339
Fix linting issues
glendaviesnz Oct 17, 2023
0422988
More appeasing of the linting gods
glendaviesnz Oct 17, 2023
6e82662
Third time lucky with phpcs
glendaviesnz Oct 17, 2023
f8b3edd
Use a list of checkboxes instead of a multiselect for category select…
glendaviesnz Oct 18, 2023
aff2d01
Switch to using BaseControl to get the label
glendaviesnz Oct 18, 2023
ca568f0
Abstract out the author category select so it can be shared by the po…
glendaviesnz Oct 18, 2023
0552a35
Share the new category selector with the post editor
glendaviesnz Oct 18, 2023
1a13a45
Fix bug with selection not appearing if no categories selected
glendaviesnz Oct 18, 2023
9e3e9bc
remove unnecessary comment
glendaviesnz Oct 19, 2023
d8c4030
Fix padding of category select list
glendaviesnz Oct 19, 2023
c803d76
Update class names to reflect fact that selector not always in model
glendaviesnz Oct 19, 2023
9b844ae
Fix the taxonomy permissions on the wp_block rest endpoint
glendaviesnz Oct 19, 2023
0069a96
Fix linting errors
glendaviesnz Oct 19, 2023
e02d1e1
Fix file load naming
glendaviesnz Oct 19, 2023
2311222
Fix up pointless {}
glendaviesnz Oct 20, 2023
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
23 changes: 12 additions & 11 deletions lib/compat/wordpress-6.4/block-patterns.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,21 @@
*/
function gutenberg_register_taxonomy_patterns() {
$args = array(
'public' => true,
'publicly_queryable' => false,
'hierarchical' => false,
'labels' => array(
'public' => true,
'publicly_queryable' => false,
'hierarchical' => false,
'labels' => array(
'name' => _x( 'Pattern Categories', 'taxonomy general name' ),
'singular_name' => _x( 'Pattern Category', 'taxonomy singular name' ),
),
'query_var' => false,
'rewrite' => false,
'show_ui' => true,
'_builtin' => true,
'show_in_nav_menus' => false,
'show_in_rest' => true,
'show_admin_column' => true,
'query_var' => false,
'rewrite' => false,
'show_ui' => true,
'_builtin' => true,
'show_in_nav_menus' => false,
'show_in_rest' => true,
'show_admin_column' => true,
'rest_controller_class' => 'Gutenberg_REST_Pattern_Categories_Controller',
);
register_taxonomy( 'wp_pattern_category', array( 'wp_block' ), $args );
}
Expand Down
24 changes: 24 additions & 0 deletions lib/compat/wordpress-6.4/blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,27 @@ function gutenberg_add_custom_capabilities_to_wp_block( $args ) {
return $args;
}
add_filter( 'register_wp_block_post_type_args', 'gutenberg_add_custom_capabilities_to_wp_block', 10, 1 );

/**
* Updates the wp_block REST enpoint in order to modify the wp_pattern_category action
* links that are returned because as although the taxonomy is flat Author level users
* are only allowed to assign categories.
*
* Note: This should be removed when the minimum required WP version is >= 6.4.
*
* @see https://github.com/WordPress/gutenberg/pull/55379
*
* @param array $args Register post type args.
* @param string $post_type The post type string.
*
* @return array Register post type args.
*/
function gutenberg_update_patterns_block_rest_controller_class( $args, $post_type ) {
if ( 'wp_block' === $post_type ) {
$args['rest_controller_class'] = 'Gutenberg_REST_Blocks_Controller_6_4';
}

return $args;
}

add_filter( 'register_post_type_args', 'gutenberg_update_patterns_block_rest_controller_class', 11, 2 );
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php
/**
* Reusable blocks REST API: WP_REST_Blocks_Controller class
*
* @package WordPress
* @subpackage REST_API
* @since 5.0.0
*/

/**
* Controller which provides a REST endpoint for the editor to read, create,
* edit and delete reusable blocks. Blocks are stored as posts with the wp_block
* post type.
*
* @since 5.0.0
*
* @see WP_REST_Posts_Controller
* @see WP_REST_Controller
*/
class Gutenberg_REST_Blocks_Controller_6_4 extends Gutenberg_REST_Blocks_Controller {
/**
* Gets the link relations available for the post and current user.
*
* @since 6.4.0 Ensures that only users with `edit_terms` capability can add taxonomy terms.
*
* @param WP_Post $post Post object.
* @param WP_REST_Request $request Request object.
* @return array List of link relations.
*/
protected function get_available_actions( $post, $request ) {
if ( 'edit' !== $request['context'] ) {
return array();
}

$rels = array();

$post_type = get_post_type_object( $post->post_type );

if ( 'attachment' !== $this->post_type && current_user_can( $post_type->cap->publish_posts ) ) {
$rels[] = 'https://api.w.org/action-publish';
}

if ( current_user_can( 'unfiltered_html' ) ) {
$rels[] = 'https://api.w.org/action-unfiltered-html';
}

if ( 'post' === $post_type->name ) {
if ( current_user_can( $post_type->cap->edit_others_posts ) && current_user_can( $post_type->cap->publish_posts ) ) {
$rels[] = 'https://api.w.org/action-sticky';
}
}

if ( post_type_supports( $post_type->name, 'author' ) ) {
if ( current_user_can( $post_type->cap->edit_others_posts ) ) {
$rels[] = 'https://api.w.org/action-assign-author';
}
}

$taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );

foreach ( $taxonomies as $tax ) {
$tax_base = ! empty( $tax->rest_base ) ? $tax->rest_base : $tax->name;

if ( current_user_can( $tax->cap->edit_terms ) ) {
$rels[] = 'https://api.w.org/action-create-' . $tax_base;
}

if ( current_user_can( $tax->cap->assign_terms ) ) {
$rels[] = 'https://api.w.org/action-assign-' . $tax_base;
}
}

return $rels;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php
/**
* Gutenberg_REST_Pattern_Categories_Controller class
*
* Extends the WP_REST_Terms_Controller to handle permissions of pattern categories.
*
* @package Gutenberg
* @subpackage REST_API
* @since 6.4.0
*/

/**
* Extends the WP_REST_Terms_Controller to handle permissions of pattern categories.
*
* @access public
*/
class Gutenberg_REST_Pattern_Categories_Controller extends WP_REST_Terms_Controller {
/**
* Make pattern categories behave more like a hierarchical taxonomy in terms of permissions.
* Check the edit_terms cap to see whether term creation is possible.
*
* @since 6.4.0
*
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function create_item_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
if ( ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) {
return false;
}

$taxonomy_obj = get_taxonomy( $this->taxonomy );

// Patterns categories are a flat hierarchy (like tags), but work more like post categories in terms of permissions.
if ( ! current_user_can( $taxonomy_obj->cap->edit_terms ) ) {
return new WP_Error(
'rest_cannot_create',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not for this PR, but I think when syncing with Core it'd be good have some unit tests, e.g., something like

public function test_should_not_return_menus_for_users_without_permissions() {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for sure, I added one on the backport PR

__( 'Sorry, you are not allowed to create terms in this taxonomy.' ),
aaronrobertshaw marked this conversation as resolved.
Show resolved Hide resolved
array( 'status' => rest_authorization_required_code() )
);
}

return true;
}
}
2 changes: 2 additions & 0 deletions lib/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ function gutenberg_is_experiment_enabled( $name ) {
// WordPress 6.4 compat.
require_once __DIR__ . '/compat/wordpress-6.4/class-gutenberg-rest-global-styles-revisions-controller-6-4.php';
require_once __DIR__ . '/compat/wordpress-6.4/class-gutenberg-rest-block-patterns-controller.php';
require_once __DIR__ . '/compat/wordpress-6.4/class-gutenberg-rest-blocks-controller-6-4.php';
require_once __DIR__ . '/compat/wordpress-6.4/class-gutenberg-rest-pattern-categories-controller.php';
require_once __DIR__ . '/compat/wordpress-6.4/rest-api.php';
require_once __DIR__ . '/compat/wordpress-6.4/theme-previews.php';

Expand Down
1 change: 1 addition & 0 deletions packages/core-data/src/resolvers.js
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,7 @@ export const getUserPatternCategories =
{
per_page: -1,
_fields: 'id,name,description,slug',
context: 'view',
}
);

Expand Down
1 change: 1 addition & 0 deletions packages/editor/src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export {
export { default as PostTaxonomies } from './post-taxonomies';
export { FlatTermSelector as PostTaxonomiesFlatTermSelector } from './post-taxonomies/flat-term-selector';
export { HierarchicalTermSelector as PostTaxonomiesHierarchicalTermSelector } from './post-taxonomies/hierarchical-term-selector';
export { PatternCategoriesSelector as PostPatternCategoriesSelector } from './post-taxonomies/pattern-categories-selector';
export { default as PostTaxonomiesCheck } from './post-taxonomies/check';
export { default as PostTextEditor } from './post-text-editor';
export { default as PostTitle } from './post-title';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/**
* WordPress dependencies
*/
import { useSelect, useDispatch } from '@wordpress/data';
import { store as coreStore } from '@wordpress/core-data';
import { addFilter } from '@wordpress/hooks';
import { privateApis as patternsPrivateApis } from '@wordpress/patterns';

/**
* Internal dependencies
*/
import { unlock } from '../../lock-unlock';
import { store as editorStore } from '../../store';

const { CategorySelector } = unlock( patternsPrivateApis );

const EMPTY_ARRAY = [];

const DEFAULT_QUERY = {
per_page: -1,
orderby: 'name',
order: 'asc',
_fields: 'id,name,parent',
context: 'view',
};

/*
* Pattern categories are a flat taxonomy but do not allow Author users and below to create
* new categories, so this selector overrides the default flat taxonomy selector for
* wp_block post types and users without 'create' capability for wp_pattern_category.
*/
export function PatternCategoriesSelector( { slug } ) {
const { hasAssignAction, terms, availableTerms, taxonomy, loading } =
useSelect(
( select ) => {
const { getCurrentPost, getEditedPostAttribute } =
select( editorStore );
const { getTaxonomy, getEntityRecords, isResolving } =
select( coreStore );
const _taxonomy = getTaxonomy( slug );
const post = getCurrentPost();

return {
hasAssignAction: _taxonomy
? post._links?.[
'wp:action-assign-' + _taxonomy.rest_base
] ?? false
: false,
terms: _taxonomy
? getEditedPostAttribute( _taxonomy.rest_base )
: EMPTY_ARRAY,
loading: isResolving( 'getEntityRecords', [
'taxonomy',
slug,
DEFAULT_QUERY,
] ),
availableTerms:
getEntityRecords( 'taxonomy', slug, DEFAULT_QUERY ) ||
EMPTY_ARRAY,
taxonomy: _taxonomy,
};
},
[ slug ]
);

const { editPost } = useDispatch( editorStore );

if ( ! hasAssignAction || loading || availableTerms.length === 0 ) {
return null;
}

const onUpdateTerms = ( termIds ) => {
editPost( { [ taxonomy.rest_base ]: termIds } );
};

const onChange = ( term ) => {
const hasTerm = terms.includes( term.id );
const newTerms = hasTerm
? terms.filter( ( id ) => id !== term.id )
: [ ...terms, term.id ];
onUpdateTerms( newTerms );
};

const isCategorySelected = ( term ) => terms.includes( term.id );

const categoryOptions = availableTerms.map( ( term ) => ( {
...term,
label: term.name,
} ) );

return (
<CategorySelector
onChange={ onChange }
categoryOptions={ categoryOptions }
isCategorySelected={ isCategorySelected }
showLabel={ false }
/>
);
}

export default function patternCategorySelector( OriginalComponent ) {
return function ( props ) {
const canAddCategories = useSelect( ( select ) => {
const { canUser } = select( coreStore );
return canUser( 'create', 'wp_pattern_category' );
} );
if ( props.slug === 'wp_pattern_category' && ! canAddCategories ) {
return <PatternCategoriesSelector { ...props } />;
}

return <OriginalComponent { ...props } />;
};
}

addFilter(
'editor.PostTaxonomyType',
'core/pattern-category-selector',
patternCategorySelector
);
Loading
Loading