diff --git a/lib/navigation.php b/lib/navigation.php
index 1053463bce240..7bfc3bd063c17 100644
--- a/lib/navigation.php
+++ b/lib/navigation.php
@@ -50,6 +50,18 @@ function gutenberg_register_navigation_post_type() {
'editor',
'revisions',
),
+ 'capabilities' => array(
+ 'edit_others_posts' => 'edit_theme_options',
+ 'delete_posts' => 'edit_theme_options',
+ 'publish_posts' => 'edit_theme_options',
+ 'create_posts' => 'edit_theme_options',
+ 'read_private_posts' => 'edit_theme_options',
+ 'delete_private_posts' => 'edit_theme_options',
+ 'delete_published_posts' => 'edit_theme_options',
+ 'delete_others_posts' => 'edit_theme_options',
+ 'edit_private_posts' => 'edit_theme_options',
+ 'edit_published_posts' => 'edit_theme_options',
+ ),
);
register_post_type( 'wp_navigation', $args );
diff --git a/packages/block-library/src/navigation/edit/index.js b/packages/block-library/src/navigation/edit/index.js
index 82355fa98972d..441fee4dbff23 100644
--- a/packages/block-library/src/navigation/edit/index.js
+++ b/packages/block-library/src/navigation/edit/index.js
@@ -39,7 +39,6 @@ import {
Button,
} from '@wordpress/components';
import { __ } from '@wordpress/i18n';
-import { store as noticeStore } from '@wordpress/notices';
/**
* Internal dependencies
@@ -54,6 +53,7 @@ import NavigationMenuSelector from './navigation-menu-selector';
import NavigationMenuNameControl from './navigation-menu-name-control';
import UnsavedInnerBlocks from './unsaved-inner-blocks';
import NavigationMenuDeleteControl from './navigation-menu-delete-control';
+import useNavigationNotice from './use-navigation-notice';
const EMPTY_ARRAY = [];
@@ -107,8 +107,6 @@ function Navigation( {
customPlaceholder: CustomPlaceholder = null,
customAppender: CustomAppender = null,
} ) {
- const noticeRef = useRef();
-
const {
openSubmenusOnClick,
overlayMenu,
@@ -192,8 +190,6 @@ function Navigation( {
__unstableMarkNextChangeAsNotPersistent,
} = useDispatch( blockEditorStore );
- const { createWarningNotice, removeNotice } = useDispatch( noticeStore );
-
const [
hasSavedUnsavedInnerBlocks,
setHasSavedUnsavedInnerBlocks,
@@ -220,6 +216,8 @@ function Navigation( {
hasResolvedCanUserUpdateNavigationEntity,
canUserDeleteNavigationEntity,
hasResolvedCanUserDeleteNavigationEntity,
+ canUserCreateNavigation,
+ hasResolvedCanUserCreateNavigation,
} = useNavigationMenu( ref );
const navRef = useRef();
@@ -307,7 +305,7 @@ function Navigation( {
setDetectedColor,
setDetectedBackgroundColor
);
- const subMenuElement = navRef.current.querySelector(
+ const subMenuElement = navRef.current?.querySelector(
'[data-type="core/navigation-link"] [data-type="core/navigation-link"]'
);
if ( subMenuElement ) {
@@ -336,52 +334,52 @@ function Navigation( {
}
}, [ clientId, ref, hasUncontrolledInnerBlocks, controlledInnerBlocks ] );
- useEffect( () => {
- const setPermissionsNotice = () => {
- if ( noticeRef.current ) {
- return;
- }
-
- noticeRef.current =
- 'block-library/core/navigation/permissions/update';
-
- createWarningNotice(
- __(
- 'You do not have permission to edit this Menu. Any changes made will not be saved.'
- ),
- {
- id: noticeRef.current,
- type: 'snackbar',
- }
- );
- };
+ const [ showCantEditNotice, hideCantEditNotice ] = useNavigationNotice( {
+ name: 'block-library/core/navigation/permissions/update',
+ message: __(
+ 'You do not have permission to edit this Menu. Any changes made will not be saved.'
+ ),
+ } );
- const removePermissionsNotice = () => {
- if ( ! noticeRef.current ) {
- return;
- }
- removeNotice( noticeRef.current );
- noticeRef.current = null;
- };
+ const [ showCantCreateNotice, hideCantCreateNotice ] = useNavigationNotice(
+ {
+ name: 'block-library/core/navigation/permissions/create',
+ message: __(
+ 'You do not have permission to create Navigation Menus.'
+ ),
+ }
+ );
+ useEffect( () => {
if ( ! isSelected && ! isInnerBlockSelected ) {
- removePermissionsNotice();
+ hideCantEditNotice();
+ hideCantCreateNotice();
}
- if (
- ( isSelected || isInnerBlockSelected ) &&
- hasResolvedCanUserUpdateNavigationEntity &&
- ! canUserUpdateNavigationEntity
- ) {
- setPermissionsNotice();
+ if ( isSelected || isInnerBlockSelected ) {
+ if (
+ hasResolvedCanUserUpdateNavigationEntity &&
+ ! canUserUpdateNavigationEntity
+ ) {
+ showCantEditNotice();
+ }
+
+ if (
+ ! ref &&
+ hasResolvedCanUserCreateNavigation &&
+ ! canUserCreateNavigation
+ ) {
+ showCantCreateNotice();
+ }
}
}, [
- ref,
- isEntityAvailable,
- hasResolvedCanUserUpdateNavigationEntity,
- canUserUpdateNavigationEntity,
isSelected,
isInnerBlockSelected,
+ canUserUpdateNavigationEntity,
+ hasResolvedCanUserUpdateNavigationEntity,
+ canUserCreateNavigation,
+ hasResolvedCanUserCreateNavigation,
+ ref,
] );
const startWithEmptyMenu = useCallback( () => {
@@ -488,6 +486,7 @@ function Navigation( {
onClose();
} }
onCreateNew={ startWithEmptyMenu }
+ showCreate={ canUserCreateNavigation }
/>
) }
@@ -642,11 +641,13 @@ function Navigation( {
hasResolvedNavigationMenus
}
clientId={ clientId }
+ canUserCreateNavigation={ canUserCreateNavigation }
/>
) }
- { ! isEntityAvailable && ! isPlaceholderShown && (
-
- ) }
+ { ! hasResolvedCanUserCreateNavigation ||
+ ( ! isEntityAvailable && ! isPlaceholderShown && (
+
+ ) ) }
{ ! isPlaceholderShown && (
-
-
-
-
+ { showCreate && (
+
+
+
+
+ ) }
>
);
}
diff --git a/packages/block-library/src/navigation/edit/placeholder/index.js b/packages/block-library/src/navigation/edit/placeholder/index.js
index a5ef385dfaa6d..bf2453488b8b6 100644
--- a/packages/block-library/src/navigation/edit/placeholder/index.js
+++ b/packages/block-library/src/navigation/edit/placeholder/index.js
@@ -31,6 +31,7 @@ const ExistingMenusDropdown = ( {
onFinish,
menus,
onCreateFromMenu,
+ showClassicMenus = false,
} ) => {
const toggleProps = {
variant: 'tertiary',
@@ -65,22 +66,24 @@ const ExistingMenusDropdown = ( {
);
} ) }
-
- { menus?.map( ( menu ) => {
- return (
-
- );
- } ) }
-
+ { showClassicMenus && (
+
+ { menus?.map( ( menu ) => {
+ return (
+
+ );
+ } ) }
+
+ ) }
>
) }
@@ -92,6 +95,7 @@ export default function NavigationPlaceholder( {
onFinish,
canSwitchNavigationMenu,
hasResolvedNavigationMenus,
+ canUserCreateNavigation = false,
} ) {
const [ selectedMenu, setSelectedMenu ] = useState();
const [ isCreatingFromMenu, setIsCreatingFromMenu ] = useState( false );
@@ -102,6 +106,10 @@ export default function NavigationPlaceholder( {
blocks,
navigationMenuTitle = null
) => {
+ if ( ! canUserCreateNavigation ) {
+ return;
+ }
+
const navigationMenu = await createNavigationMenu(
navigationMenuTitle,
blocks
@@ -176,8 +184,10 @@ export default function NavigationPlaceholder( {
{ ' ' }
{ __( 'Navigation' ) }
+
- { hasMenus || navigationMenus.length ? (
+
+ { hasMenus || navigationMenus?.length ? (
<>
>
) : undefined }
- { hasPages ? (
+ { canUserCreateNavigation && hasPages ? (
<>
>
) : undefined }
-
+
+ { canUserCreateNavigation && (
+
+ ) }
diff --git a/packages/block-library/src/navigation/edit/use-navigation-notice.js b/packages/block-library/src/navigation/edit/use-navigation-notice.js
new file mode 100644
index 0000000000000..2466c79c2e7d5
--- /dev/null
+++ b/packages/block-library/src/navigation/edit/use-navigation-notice.js
@@ -0,0 +1,37 @@
+/**
+ * WordPress dependencies
+ */
+import { useRef } from '@wordpress/element';
+import { useDispatch } from '@wordpress/data';
+import { store as noticeStore } from '@wordpress/notices';
+
+function useNavigationNotice( { name, message } = {} ) {
+ const noticeRef = useRef();
+
+ const { createWarningNotice, removeNotice } = useDispatch( noticeStore );
+
+ const showNotice = () => {
+ if ( noticeRef.current ) {
+ return;
+ }
+
+ noticeRef.current = name;
+
+ createWarningNotice( message, {
+ id: noticeRef.current,
+ type: 'snackbar',
+ } );
+ };
+
+ const hideNotice = () => {
+ if ( ! noticeRef.current ) {
+ return;
+ }
+ removeNotice( noticeRef.current );
+ noticeRef.current = null;
+ };
+
+ return [ showNotice, hideNotice ];
+}
+
+export default useNavigationNotice;
diff --git a/packages/block-library/src/navigation/editor.scss b/packages/block-library/src/navigation/editor.scss
index 545075e1a7af6..542bafc747c30 100644
--- a/packages/block-library/src/navigation/editor.scss
+++ b/packages/block-library/src/navigation/editor.scss
@@ -372,6 +372,7 @@ $color-control-label-height: 20px;
font-size: $default-font-size;
font-family: $default-font;
gap: $grid-unit-15 * 0.5;
+ align-items: center;
// Margins.
.components-dropdown,
diff --git a/packages/block-library/src/navigation/use-navigation-menu.js b/packages/block-library/src/navigation/use-navigation-menu.js
index 0ce579c778e0d..f647f814339f6 100644
--- a/packages/block-library/src/navigation/use-navigation-menu.js
+++ b/packages/block-library/src/navigation/use-navigation-menu.js
@@ -79,6 +79,11 @@ export default function useNavigationMenu( ref ) {
'canUser',
[ 'delete', 'navigation', ref ]
),
+ canUserCreateNavigation: canUser( 'create', 'navigation' ),
+ hasResolvedCanUserCreateNavigation: hasFinishedResolution(
+ 'canUser',
+ [ 'create', 'navigation' ]
+ ),
};
},
[ ref ]
diff --git a/packages/e2e-tests/specs/editor/blocks/navigation.test.js b/packages/e2e-tests/specs/editor/blocks/navigation.test.js
index e8fc8a228a10f..140ee38170520 100644
--- a/packages/e2e-tests/specs/editor/blocks/navigation.test.js
+++ b/packages/e2e-tests/specs/editor/blocks/navigation.test.js
@@ -20,6 +20,7 @@ import {
createUser,
loginUser,
deleteUser,
+ switchUserToAdmin,
} from '@wordpress/e2e-test-utils';
/**
@@ -761,6 +762,10 @@ describe( 'Navigation', () => {
} );
} );
+ afterEach( async () => {
+ await switchUserToAdmin();
+ } );
+
afterAll( async () => {
await deleteUser( contributorUsername );
} );
@@ -810,5 +815,28 @@ describe( 'Navigation', () => {
// Todo: removed once Nav Areas are removed from the Gutenberg Plugin.
expect( console ).toHaveErrored();
} );
+
+ it( 'shows a warning if user does not have permission to create navigation menus', async () => {
+ const noticeText =
+ 'You do not have permission to create Navigation Menus.';
+ // Switch to a Contributor role user - they should not have
+ // permission to update Navigations.
+ await loginUser( contributorUsername, contributorPassword );
+
+ await createNewPost();
+ await insertBlock( 'Navigation' );
+
+ // Make sure the snackbar error shows up
+ await page.waitForXPath(
+ `//*[contains(@class, 'components-snackbar__content')][ text()="${ noticeText }" ]`
+ );
+
+ // Expect a console 403 for request to Navigation Areas for lower permission users.
+ // This is because reading requires the `edit_theme_options` capability
+ // which the Contributor level user does not have.
+ // See: https://github.com/WordPress/gutenberg/blob/4cedaf0c4abb0aeac4bfd4289d63e9889efe9733/lib/class-wp-rest-block-navigation-areas-controller.php#L81-L91.
+ // Todo: removed once Nav Areas are removed from the Gutenberg Plugin.
+ expect( console ).toHaveErrored();
+ } );
} );
} );