diff --git a/packages/core-data/src/private-selectors.ts b/packages/core-data/src/private-selectors.ts index 9a9b2ef5100784..7eb67973c67281 100644 --- a/packages/core-data/src/private-selectors.ts +++ b/packages/core-data/src/private-selectors.ts @@ -73,3 +73,22 @@ export const getEntityRecordsPermissions = createRegistrySelector( ( select ) => ( state ) => [ state.userPermissions ] ) ); + +/** + * Returns the entity record permissions for the given entity record id. + * + * @param state Data state. + * @param kind Entity kind. + * @param name Entity name. + * @param id Entity record id. + * + * @return The entity record permissions. + */ +export function getEntityRecordPermissions( + state: State, + kind: string, + name: string, + id: string +) { + return getEntityRecordsPermissions( state, kind, name, [ id ] )[ 0 ]; +} diff --git a/packages/edit-site/src/components/page-patterns/use-patterns.js b/packages/edit-site/src/components/page-patterns/use-patterns.js index ecbc6adee75587..1827e0dfd89174 100644 --- a/packages/edit-site/src/components/page-patterns/use-patterns.js +++ b/packages/edit-site/src/components/page-patterns/use-patterns.js @@ -5,6 +5,7 @@ import { parse } from '@wordpress/blocks'; import { useSelect, createSelector } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; import { store as editorStore } from '@wordpress/editor'; +import { useMemo } from '@wordpress/element'; /** * Internal dependencies @@ -25,8 +26,9 @@ const EMPTY_PATTERN_LIST = []; const selectTemplateParts = createSelector( ( select, categoryId, search = '' ) => { - const { getEntityRecords, isResolving: isResolvingSelector } = - select( coreStore ); + const { getEntityRecords, isResolving: isResolvingSelector } = unlock( + select( coreStore ) + ); const { __experimentalGetDefaultTemplatePartAreas } = select( editorStore ); const query = { per_page: -1 }; @@ -260,7 +262,7 @@ export const usePatterns = ( categoryId, { search = '', syncStatus } = {} ) => { - return useSelect( + const { patterns, ...rest } = useSelect( ( select ) => { if ( postType === TEMPLATE_PART_POST_TYPE ) { return selectTemplateParts( select, categoryId, search ); @@ -283,6 +285,35 @@ export const usePatterns = ( }, [ categoryId, postType, search, syncStatus ] ); + + const ids = useMemo( + () => patterns?.map( ( record ) => record.id ) ?? [], + [ patterns ] + ); + + const permissions = useSelect( + ( select ) => { + const { getEntityRecordsPermissions } = unlock( + select( coreStore ) + ); + return getEntityRecordsPermissions( 'postType', postType, ids ); + }, + [ ids, postType ] + ); + + const patternsWithPermissions = useMemo( + () => + patterns?.map( ( record, index ) => ( { + ...record, + permissions: permissions[ index ], + } ) ) ?? [], + [ patterns, permissions ] + ); + + return { + ...rest, + patterns: patternsWithPermissions, + }; }; export default usePatterns; diff --git a/packages/edit-site/src/components/page-templates/index.js b/packages/edit-site/src/components/page-templates/index.js index 9c5db8fb1699d5..05c3b9673eaf98 100644 --- a/packages/edit-site/src/components/page-templates/index.js +++ b/packages/edit-site/src/components/page-templates/index.js @@ -3,7 +3,7 @@ */ import { __ } from '@wordpress/i18n'; import { useState, useMemo, useCallback, useEffect } from '@wordpress/element'; -import { useEntityRecords } from '@wordpress/core-data'; +import { privateApis as corePrivateApis } from '@wordpress/core-data'; import { DataViews, filterSortAndPaginate } from '@wordpress/dataviews'; import { privateApis as routerPrivateApis } from '@wordpress/router'; import { privateApis as editorPrivateApis } from '@wordpress/editor'; @@ -31,6 +31,7 @@ import { const { usePostActions } = unlock( editorPrivateApis ); const { useHistory, useLocation } = unlock( routerPrivateApis ); +const { useEntityRecordsWithPermissions } = unlock( corePrivateApis ); const EMPTY_ARRAY = []; @@ -134,13 +135,10 @@ export default function PageTemplates() { } ) ); }, [ activeView ] ); - const { records, isResolving: isLoadingData } = useEntityRecords( - 'postType', - TEMPLATE_POST_TYPE, - { + const { records, isResolving: isLoadingData } = + useEntityRecordsWithPermissions( 'postType', TEMPLATE_POST_TYPE, { per_page: -1, - } - ); + } ); const history = useHistory(); const onChangeSelection = useCallback( ( items ) => { diff --git a/packages/editor/src/components/post-actions/actions.js b/packages/editor/src/components/post-actions/actions.js index 190b8ea6ca32f5..47e49fb8f8f1da 100644 --- a/packages/editor/src/components/post-actions/actions.js +++ b/packages/editor/src/components/post-actions/actions.js @@ -3,7 +3,7 @@ */ import { external, trash, backup } from '@wordpress/icons'; import { addQueryArgs } from '@wordpress/url'; -import { useDispatch, useSelect, useRegistry } from '@wordpress/data'; +import { useDispatch, useSelect } from '@wordpress/data'; import { decodeEntities } from '@wordpress/html-entities'; import { store as coreStore } from '@wordpress/core-data'; import { __, _n, sprintf, _x } from '@wordpress/i18n'; @@ -88,7 +88,10 @@ const trashPostAction = { isPrimary: true, icon: trash, isEligible( item ) { - return ! [ 'auto-draft', 'trash' ].includes( item.status ); + return ( + ! [ 'auto-draft', 'trash' ].includes( item.status ) && + item.permissions?.delete + ); }, supportsBulk: true, hideModalHeader: true, @@ -240,40 +243,12 @@ const trashPostAction = { }, }; -function useCanUserEligibilityCheckPostType( capability, postType, action ) { - const registry = useRegistry(); - return useMemo( - () => ( { - ...action, - isEligible( item ) { - return ( - action.isEligible( item ) && - registry.select( coreStore ).canUser( capability, { - kind: 'postType', - name: postType, - id: item.id, - } ) - ); - }, - } ), - [ action, registry, capability, postType ] - ); -} - -function useTrashPostAction( postType ) { - return useCanUserEligibilityCheckPostType( - 'delete', - postType, - trashPostAction - ); -} - const permanentlyDeletePostAction = { id: 'permanently-delete', label: __( 'Permanently delete' ), supportsBulk: true, - isEligible( { status } ) { - return status === 'trash'; + isEligible( { status, permissions } ) { + return status === 'trash' && permissions?.delete; }, async callback( posts, { registry, onActionPerformed } ) { const { createSuccessNotice, createErrorNotice } = @@ -359,22 +334,14 @@ const permanentlyDeletePostAction = { }, }; -function usePermanentlyDeletePostAction( postType ) { - return useCanUserEligibilityCheckPostType( - 'delete', - postType, - permanentlyDeletePostAction - ); -} - const restorePostAction = { id: 'restore', label: __( 'Restore' ), isPrimary: true, icon: backup, supportsBulk: true, - isEligible( { status } ) { - return status === 'trash'; + isEligible( { status, permissions } ) { + return status === 'trash' && permissions?.update; }, async callback( posts, { registry, onActionPerformed } ) { const { createSuccessNotice, createErrorNotice } = @@ -474,14 +441,6 @@ const restorePostAction = { }, }; -function useRestorePostAction( postType ) { - return useCanUserEligibilityCheckPostType( - 'update', - postType, - restorePostAction - ); -} - const viewPostAction = { id: 'view-post', label: __( 'View' ), @@ -548,11 +507,15 @@ const renamePostAction = { ...Object.values( PATTERN_TYPES ), ].includes( post.type ) ) { - return true; + return post.permissions?.update; } // In the case of templates, we can only rename custom templates. if ( post.type === TEMPLATE_POST_TYPE ) { - return isTemplateRemovable( post ) && post.is_custom; + return ( + isTemplateRemovable( post ) && + post.is_custom && + post.permissions?.update + ); } // Make necessary checks for template parts and patterns. const isTemplatePart = post.type === TEMPLATE_PART_POST_TYPE; @@ -564,7 +527,7 @@ const renamePostAction = { isUserPattern || ( isTemplatePart && post.source === TEMPLATE_ORIGINS.custom ); const hasThemeFile = post?.has_theme_file; - return isCustomPattern && ! hasThemeFile; + return isCustomPattern && ! hasThemeFile && post.permissions?.update; }, RenderModal: ( { items, closeModal, onActionPerformed } ) => { const [ item ] = items; @@ -635,14 +598,6 @@ const renamePostAction = { }, }; -function useRenamePostAction( postType ) { - return useCanUserEligibilityCheckPostType( - 'update', - postType, - renamePostAction - ); -} - function ReorderModal( { items, closeModal, onActionPerformed } ) { const [ item, setItem ] = useState( items[ 0 ] ); const orderInput = item.menu_order; @@ -1004,11 +959,6 @@ export function usePostActions( { postType, onActionPerformed, context } ) { ); const duplicatePostAction = useDuplicatePostAction( postType ); - const trashPostActionForPostType = useTrashPostAction( postType ); - const permanentlyDeletePostActionForPostType = - usePermanentlyDeletePostAction( postType ); - const renamePostActionForPostType = useRenamePostAction( postType ); - const restorePostActionForPostType = useRestorePostAction( postType ); const reorderPagesAction = useReorderPagesAction( postType ); const isTemplateOrTemplatePart = [ TEMPLATE_POST_TYPE, @@ -1035,14 +985,13 @@ export function usePostActions( { postType, onActionPerformed, context } ) { userCanCreatePostType && duplicateTemplatePartAction, isPattern && userCanCreatePostType && duplicatePatternAction, - supportsTitle && renamePostActionForPostType, + supportsTitle && renamePostAction, reorderPagesAction, - ! isTemplateOrTemplatePart && restorePostActionForPostType, + ! isTemplateOrTemplatePart && ! isPattern && restorePostAction, + ! isTemplateOrTemplatePart && ! isPattern && trashPostAction, ! isTemplateOrTemplatePart && ! isPattern && - trashPostActionForPostType, - ! isTemplateOrTemplatePart && - permanentlyDeletePostActionForPostType, + permanentlyDeletePostAction, ...defaultActions, ].filter( Boolean ); // Filter actions based on provided context. If not provided @@ -1117,10 +1066,6 @@ export function usePostActions( { postType, onActionPerformed, context } ) { postTypeObject?.viewable, duplicatePostAction, reorderPagesAction, - trashPostActionForPostType, - restorePostActionForPostType, - renamePostActionForPostType, - permanentlyDeletePostActionForPostType, onActionPerformed, isLoaded, supportsRevisions, diff --git a/packages/editor/src/components/post-actions/index.js b/packages/editor/src/components/post-actions/index.js index b2730e760c4270..5b023956178938 100644 --- a/packages/editor/src/components/post-actions/index.js +++ b/packages/editor/src/components/post-actions/index.js @@ -29,26 +29,38 @@ const { export default function PostActions( { onActionPerformed, buttonProps } ) { const [ isActionsMenuOpen, setIsActionsMenuOpen ] = useState( false ); - const { item, postType } = useSelect( ( select ) => { + const { item, permissions, postType } = useSelect( ( select ) => { const { getCurrentPostType, getCurrentPostId } = select( editorStore ); - const { getEditedEntityRecord } = select( coreStore ); + const { getEditedEntityRecord, getEntityRecordPermissions } = unlock( + select( coreStore ) + ); const _postType = getCurrentPostType(); + const _id = getCurrentPostId(); return { - item: getEditedEntityRecord( + item: getEditedEntityRecord( 'postType', _postType, _id ), + permissions: getEntityRecordPermissions( 'postType', _postType, - getCurrentPostId() + _id ), postType: _postType, }; }, [] ); + const itemWithPermissions = useMemo( () => { + return { + ...item, + permissions, + }; + }, [ item, permissions ] ); const allActions = usePostActions( { postType, onActionPerformed } ); const actions = useMemo( () => { return allActions.filter( ( action ) => { - return ! action.isEligible || action.isEligible( item ); + return ( + ! action.isEligible || action.isEligible( itemWithPermissions ) + ); } ); - }, [ allActions, item ] ); + }, [ allActions, itemWithPermissions ] ); return ( { setIsActionsMenuOpen( false ); } }