Skip to content

Commit

Permalink
Patterns: fix capabilities settings for pattern categories (#55379)
Browse files Browse the repository at this point in the history
Co-authored-by: Daniel Richards <[email protected]>
  • Loading branch information
2 people authored and Siobhan committed Oct 20, 2023
1 parent 687cb22 commit 6f83c92
Show file tree
Hide file tree
Showing 13 changed files with 482 additions and 95 deletions.
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',
__( 'Sorry, you are not allowed to create terms in this taxonomy.' ),
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 @@ -630,6 +630,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

0 comments on commit 6f83c92

Please sign in to comment.