diff --git a/lib/compat/wordpress-6.4/block-patterns.php b/lib/compat/wordpress-6.4/block-patterns.php index bbb910ff400d0..922dea910b47a 100644 --- a/lib/compat/wordpress-6.4/block-patterns.php +++ b/lib/compat/wordpress-6.4/block-patterns.php @@ -16,21 +16,20 @@ */ 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, - 'rest_controller_class' => 'Gutenberg_REST_Pattern_Categories_Controller', + 'query_var' => false, + 'rewrite' => false, + 'show_ui' => true, + '_builtin' => true, + 'show_in_nav_menus' => false, + 'show_in_rest' => true, + 'show_admin_column' => true, ); register_taxonomy( 'wp_pattern_category', array( 'wp_block' ), $args ); } diff --git a/lib/compat/wordpress-6.4/blocks.php b/lib/compat/wordpress-6.4/blocks.php index 073302dbab65f..74fa9253e45d5 100644 --- a/lib/compat/wordpress-6.4/blocks.php +++ b/lib/compat/wordpress-6.4/blocks.php @@ -21,27 +21,3 @@ 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 ); diff --git a/lib/compat/wordpress-6.4/class-gutenberg-rest-blocks-controller-6-4.php b/lib/compat/wordpress-6.4/class-gutenberg-rest-blocks-controller-6-4.php deleted file mode 100644 index bc91492e26979..0000000000000 --- a/lib/compat/wordpress-6.4/class-gutenberg-rest-blocks-controller-6-4.php +++ /dev/null @@ -1,75 +0,0 @@ -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; - } -} diff --git a/lib/compat/wordpress-6.4/class-gutenberg-rest-pattern-categories-controller.php b/lib/compat/wordpress-6.4/class-gutenberg-rest-pattern-categories-controller.php deleted file mode 100644 index e249d67e8acaa..0000000000000 --- a/lib/compat/wordpress-6.4/class-gutenberg-rest-pattern-categories-controller.php +++ /dev/null @@ -1,45 +0,0 @@ -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; - } -} diff --git a/lib/load.php b/lib/load.php index 86c3dadc692b8..72ec9e62a8d74 100644 --- a/lib/load.php +++ b/lib/load.php @@ -53,8 +53,6 @@ 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'; diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js index fcecb709ad8cc..e8cf4e34a120f 100644 --- a/packages/core-data/src/resolvers.js +++ b/packages/core-data/src/resolvers.js @@ -630,7 +630,6 @@ export const getUserPatternCategories = { per_page: -1, _fields: 'id,name,description,slug', - context: 'view', } ); diff --git a/packages/editor/src/components/index.js b/packages/editor/src/components/index.js index 99de8c83c6fbe..39b562806c109 100644 --- a/packages/editor/src/components/index.js +++ b/packages/editor/src/components/index.js @@ -62,7 +62,6 @@ 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'; diff --git a/packages/editor/src/components/post-taxonomies/pattern-categories-selector.js b/packages/editor/src/components/post-taxonomies/pattern-categories-selector.js deleted file mode 100644 index ac6a60aa00934..0000000000000 --- a/packages/editor/src/components/post-taxonomies/pattern-categories-selector.js +++ /dev/null @@ -1,119 +0,0 @@ -/** - * 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 ( - - ); -} - -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 ; - } - - return ; - }; -} - -addFilter( - 'editor.PostTaxonomyType', - 'core/pattern-category-selector', - patternCategorySelector -); diff --git a/packages/patterns/src/components/category-editor.js b/packages/patterns/src/components/category-editor.js deleted file mode 100644 index a394013af333b..0000000000000 --- a/packages/patterns/src/components/category-editor.js +++ /dev/null @@ -1,97 +0,0 @@ -/** - * WordPress dependencies - */ -import { __ } from '@wordpress/i18n'; -import { useMemo, useState } from '@wordpress/element'; -import { FormTokenField } from '@wordpress/components'; -import { useDebounce } from '@wordpress/compose'; -import { decodeEntities } from '@wordpress/html-entities'; - -/** - * Internal dependencies - */ -import CategorySelector from './category-selector'; - -const unescapeString = ( arg ) => decodeEntities( arg ); - -export const CATEGORY_SLUG = 'wp_pattern_category'; - -export default function CategoryEditor( { - categoryTerms, - onChange, - categoryMap, - canAddCategories, -} ) { - const categoryOptions = Array.from( categoryMap.values() ); - const [ search, setSearch ] = useState( '' ); - const debouncedSearch = useDebounce( setSearch, 500 ); - - const suggestions = useMemo( () => { - return Array.from( categoryMap.values() ) - .map( ( category ) => unescapeString( category.label ) ) - .filter( ( category ) => { - if ( search !== '' ) { - return category - .toLowerCase() - .includes( search.toLowerCase() ); - } - return true; - } ) - .sort( ( a, b ) => a.localeCompare( b ) ); - }, [ search, categoryMap ] ); - - function handleChange( termNames ) { - const uniqueTerms = termNames.reduce( ( terms, newTerm ) => { - if ( - ! terms.some( - ( term ) => term.toLowerCase() === newTerm.toLowerCase() - ) - ) { - terms.push( newTerm ); - } - return terms; - }, [] ); - - onChange( uniqueTerms ); - } - const isCategorySelected = ( selectedCategory ) => - categoryTerms.includes( selectedCategory.label ); - - const onCategorySelectChange = ( selectedCategory ) => { - if ( categoryTerms.includes( selectedCategory.label ) ) { - onChange( - categoryTerms.filter( - ( categoryTerm ) => categoryTerm !== selectedCategory.label - ) - ); - } else { - onChange( [ ...categoryTerms, selectedCategory.label ] ); - } - }; - - return ( - <> - { canAddCategories && ( - - ) } - { ! canAddCategories && categoryOptions.length > 0 && ( - - ) } - - ); -} diff --git a/packages/patterns/src/components/category-selector.js b/packages/patterns/src/components/category-selector.js index 84dae97e232f8..7f00350e278ec 100644 --- a/packages/patterns/src/components/category-selector.js +++ b/packages/patterns/src/components/category-selector.js @@ -2,48 +2,65 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { CheckboxControl, BaseControl } from '@wordpress/components'; +import { useMemo, useState } from '@wordpress/element'; +import { FormTokenField } from '@wordpress/components'; +import { useDebounce } from '@wordpress/compose'; import { decodeEntities } from '@wordpress/html-entities'; +const unescapeString = ( arg ) => { + return decodeEntities( arg ); +}; + +export const CATEGORY_SLUG = 'wp_pattern_category'; + export default function CategorySelector( { + categoryTerms, onChange, - isCategorySelected, - categoryOptions, - showLabel = true, + categoryMap, } ) { - const renderTerms = ( renderedTerms ) => { - return renderedTerms.map( ( category ) => { - return ( -
- onChange( category ) } - label={ decodeEntities( category.label ) } - /> -
- ); - } ); - }; + const [ search, setSearch ] = useState( '' ); + const debouncedSearch = useDebounce( setSearch, 500 ); + + const suggestions = useMemo( () => { + return Array.from( categoryMap.values() ) + .map( ( category ) => unescapeString( category.label ) ) + .filter( ( category ) => { + if ( search !== '' ) { + return category + .toLowerCase() + .includes( search.toLowerCase() ); + } + return true; + } ) + .sort( ( a, b ) => a.localeCompare( b ) ); + }, [ search, categoryMap ] ); + + function handleChange( termNames ) { + const uniqueTerms = termNames.reduce( ( terms, newTerm ) => { + if ( + ! terms.some( + ( term ) => term.toLowerCase() === newTerm.toLowerCase() + ) + ) { + terms.push( newTerm ); + } + return terms; + }, [] ); + + onChange( uniqueTerms ); + } return ( - - { showLabel && ( - - { __( 'Categories' ) } - - ) } -
- { renderTerms( categoryOptions ) } -
-
+ ); } diff --git a/packages/patterns/src/components/create-pattern-modal.js b/packages/patterns/src/components/create-pattern-modal.js index 26325b40e9e71..37dd725ef9226 100644 --- a/packages/patterns/src/components/create-pattern-modal.js +++ b/packages/patterns/src/components/create-pattern-modal.js @@ -24,7 +24,7 @@ import { PATTERN_DEFAULT_CATEGORY, PATTERN_SYNC_TYPES } from '../constants'; * Internal dependencies */ import { store as patternsStore } from '../store'; -import CategoryEditor, { CATEGORY_SLUG } from './category-editor'; +import CategorySelector, { CATEGORY_SLUG } from './category-selector'; import { unlock } from '../lock-unlock'; export default function CreatePatternModal( { @@ -42,46 +42,41 @@ export default function CreatePatternModal( { const { saveEntityRecord, invalidateResolution } = useDispatch( coreStore ); const { createErrorNotice } = useDispatch( noticesStore ); - const { corePatternCategories, userPatternCategories, canAddCategories } = - useSelect( ( select ) => { - const { - getUserPatternCategories, - getBlockPatternCategories, - canUser, - } = select( coreStore ); + const { corePatternCategories, userPatternCategories } = useSelect( + ( select ) => { + const { getUserPatternCategories, getBlockPatternCategories } = + select( coreStore ); return { corePatternCategories: getBlockPatternCategories(), userPatternCategories: getUserPatternCategories(), - canAddCategories: canUser( 'create', 'wp_pattern_category' ), }; - } ); + } + ); const categoryMap = useMemo( () => { // Merge the user and core pattern categories and remove any duplicates. const uniqueCategories = new Map(); - [ - ...userPatternCategories, - ...( canAddCategories ? corePatternCategories : [] ), - ].forEach( ( category ) => { - if ( - ! uniqueCategories.has( category.label ) && - // There are two core categories with `Post` label so explicitly remove the one with - // the `query` slug to avoid any confusion. - category.name !== 'query' - ) { - // We need to store the name separately as this is used as the slug in the - // taxonomy and may vary from the label. - uniqueCategories.set( category.label, { - label: category.label, - value: category.label, - name: category.name, - id: category.id, - } ); + [ ...userPatternCategories, ...corePatternCategories ].forEach( + ( category ) => { + if ( + ! uniqueCategories.has( category.label ) && + // There are two core categories with `Post` label so explicitly remove the one with + // the `query` slug to avoid any confusion. + category.name !== 'query' + ) { + // We need to store the name separately as this is used as the slug in the + // taxonomy and may vary from the label. + uniqueCategories.set( category.label, { + label: category.label, + value: category.label, + name: category.name, + } ); + } } - } ); + ); return uniqueCategories; - }, [ userPatternCategories, corePatternCategories, canAddCategories ] ); + }, [ userPatternCategories, corePatternCategories ] ); async function onCreate( patternTitle, sync ) { if ( ! title || isSaving ) { @@ -90,18 +85,11 @@ export default function CreatePatternModal( { try { setIsSaving( true ); - let categories; - if ( canAddCategories ) { - categories = await Promise.all( - categoryTerms.map( ( termName ) => - findOrCreateTerm( termName ) - ) - ); - } else { - categories = categoryTerms.map( - ( term ) => categoryMap.get( term ).id - ); - } + const categories = await Promise.all( + categoryTerms.map( ( termName ) => + findOrCreateTerm( termName ) + ) + ); const newPattern = await createPattern( patternTitle, @@ -179,11 +167,10 @@ export default function CreatePatternModal( { placeholder={ __( 'My pattern' ) } className="patterns-create-modal__name-input" /> -