From 9e11bd92d4403cbf8fe8c9b2230ef8b3f2803c25 Mon Sep 17 00:00:00 2001 From: Grant Kinney Date: Thu, 22 Feb 2024 14:21:13 -0600 Subject: [PATCH 01/19] Adds meta cap for upload_fonts and gates REST API font uploads --- .../class-wp-rest-font-faces-controller.php | 8 +++++ lib/compat/wordpress-6.5/fonts/fonts.php | 29 +++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/lib/compat/wordpress-6.5/fonts/class-wp-rest-font-faces-controller.php b/lib/compat/wordpress-6.5/fonts/class-wp-rest-font-faces-controller.php index 8a4040e3397e0..cb89b5ad3c84b 100644 --- a/lib/compat/wordpress-6.5/fonts/class-wp-rest-font-faces-controller.php +++ b/lib/compat/wordpress-6.5/fonts/class-wp-rest-font-faces-controller.php @@ -326,6 +326,14 @@ public function create_item( $request ) { $settings = $request->get_param( 'font_face_settings' ); $file_params = $request->get_file_params(); + if ( ! empty( $file_params ) && ! current_user_can( 'upload_fonts' ) ) { + return new WP_Error( + 'rest_cannot_upload_fonts', + __( 'You are not allowed to upload font files.', 'gutenberg' ), + array( 'status' => 403 ) + ); + } + // Check that the necessary font face properties are unique. $query = new WP_Query( array( diff --git a/lib/compat/wordpress-6.5/fonts/fonts.php b/lib/compat/wordpress-6.5/fonts/fonts.php index 8307d5217ad42..36a44e8b0f6cf 100644 --- a/lib/compat/wordpress-6.5/fonts/fonts.php +++ b/lib/compat/wordpress-6.5/fonts/fonts.php @@ -84,6 +84,35 @@ function gutenberg_create_initial_post_types() { ); } +/** + * Filters the user capabilities to grant the 'upload_fonts' capability as necessary. + * + * To grant the 'upload_fonts' capability, files modifications must be allowed, the fonts directory must be + * writable, and the user must have the 'edit_theme_options' capability. + * + * @since 5.6.0 + * + * @param bool[] $allcaps An array of all the user's capabilities. + * @return bool[] Filtered array of the user's capabilities. + */ +function gutenberg_maybe_grant_upload_font_cap( $allcaps, $caps ) { + if ( ! in_array( 'upload_fonts', $caps, true ) ) { + return $allcaps; + } + + $fonts_dir = wp_get_font_dir()['path']; + if ( + wp_is_file_mod_allowed( 'can_upload_fonts' ) && + wp_is_writable( $fonts_dir ) && + ! empty( $allcaps['edit_theme_options'] ) + ) { + $allcaps['upload_fonts'] = true; + } + + return $allcaps; +} +add_filter( 'user_has_cap', 'gutenberg_maybe_grant_upload_font_cap', 10, 2 ); + /** * Initializes REST routes. * From 7846623da8454a7b5c4c1b488da3a4d39917d78a Mon Sep 17 00:00:00 2001 From: Grant Kinney Date: Thu, 22 Feb 2024 15:36:22 -0600 Subject: [PATCH 02/19] Shows/hides font upload and install tabs according to user capabilities --- lib/compat/wordpress-6.5/fonts/fonts.php | 15 +++++++ .../global-styles/font-library-modal/index.js | 42 ++++++++++++------- packages/editor/src/store/defaults.js | 2 + 3 files changed, 45 insertions(+), 14 deletions(-) diff --git a/lib/compat/wordpress-6.5/fonts/fonts.php b/lib/compat/wordpress-6.5/fonts/fonts.php index 36a44e8b0f6cf..04c0b1666dd1b 100644 --- a/lib/compat/wordpress-6.5/fonts/fonts.php +++ b/lib/compat/wordpress-6.5/fonts/fonts.php @@ -332,6 +332,21 @@ function _wp_before_delete_font_face( $post_id, $post ) { add_action( 'before_delete_post', '_wp_before_delete_font_face', 10, 2 ); } +/** + * Filters the block editor settings to enable or disable font uploads according to user capability. + * + * @since 6.5.0 + * + * @param array $settings Block editor settings. + * @return array Block editor settings. + */ +function gutenberg_font_uploads_settings( $settings ) { + $settings['fontUploadsEnabled'] = current_user_can( 'upload_fonts' ); + + return $settings; +} +add_filter( 'block_editor_settings_all', 'gutenberg_font_uploads_settings' ); + // @core-merge: Do not merge this back compat function, it is for supporting a legacy font family format only in Gutenberg. /** * Convert legacy font family posts to the new format. diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/index.js b/packages/edit-site/src/components/global-styles/font-library-modal/index.js index dc0fcd7ea373b..ff77f6dde53c3 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/index.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/index.js @@ -6,6 +6,9 @@ import { Modal, privateApis as componentsPrivateApis, } from '@wordpress/components'; +import { store as coreStore } from '@wordpress/core-data'; +import { useSelect } from '@wordpress/data'; +import { store as editorStore } from '@wordpress/editor'; import { useContext } from '@wordpress/element'; /** @@ -19,16 +22,15 @@ import { unlock } from '../../../lock-unlock'; const { Tabs } = unlock( componentsPrivateApis ); -const DEFAULT_TABS = [ - { - id: 'installed-fonts', - title: __( 'Library' ), - }, - { - id: 'upload-fonts', - title: __( 'Upload' ), - }, -]; +const DEFAULT_TAB = { + id: 'installed-fonts', + title: __( 'Library' ), +}; + +const UPLOAD_TAB = { + id: 'upload-fonts', + title: __( 'Upload' ), +}; const tabsFromCollections = ( collections ) => collections.map( ( { slug, name } ) => ( { @@ -44,11 +46,23 @@ function FontLibraryModal( { defaultTabId = 'installed-fonts', } ) { const { collections, setNotice } = useContext( FontLibraryContext ); + const canUserCreate = useSelect( ( select ) => { + const { canUser } = select( coreStore ); + return canUser( 'create', 'font-families' ); + }, [] ); + const fontUploadsEnabled = useSelect( ( select ) => { + const { getEditorSettings } = select( editorStore ); + return getEditorSettings().fontUploadsEnabled; + }, [] ); + + const tabs = [ DEFAULT_TAB ]; - const tabs = [ - ...DEFAULT_TABS, - ...tabsFromCollections( collections || [] ), - ]; + if ( canUserCreate && fontUploadsEnabled ) { + tabs.push( UPLOAD_TAB ); + // In the future, font faces can be configured to use a remote url rather than a file upload as the font src property. + // In that case, collections tabs should still be shown when font uploads are disabled. + tabs.push( ...tabsFromCollections( collections || [] ) ); + } // Reset notice when new tab is selected. const onSelect = () => { diff --git a/packages/editor/src/store/defaults.js b/packages/editor/src/store/defaults.js index e4f86b3a7dfb2..1eac4c726ddd3 100644 --- a/packages/editor/src/store/defaults.js +++ b/packages/editor/src/store/defaults.js @@ -10,6 +10,7 @@ import { SETTINGS_DEFAULTS } from '@wordpress/block-editor'; * @property {boolean} richEditingEnabled Whether rich editing is enabled or not * @property {boolean} codeEditingEnabled Whether code editing is enabled or not * @property {boolean} fontLibraryEnabled Whether the font library is enabled or not. + * @property {boolean} fontUploadsEnabled Whether uploading fonts in the font library is enabled or not. * @property {boolean} enableCustomFields Whether the WordPress custom fields are enabled or not. * true = the user has opted to show the Custom Fields panel at the bottom of the editor. * false = the user has opted to hide the Custom Fields panel at the bottom of the editor. @@ -28,6 +29,7 @@ export const EDITOR_SETTINGS_DEFAULTS = { richEditingEnabled: true, codeEditingEnabled: true, fontLibraryEnabled: true, + fontUploadsEnabled: true, enableCustomFields: undefined, defaultRenderingMode: 'post-only', }; From 314cad63ef0503c79706cc9264c0da478ec6c32f Mon Sep 17 00:00:00 2001 From: Grant Kinney Date: Thu, 22 Feb 2024 16:16:21 -0600 Subject: [PATCH 03/19] Prevents deleting fonts if user lacks capability --- .../font-library-modal/installed-fonts.js | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/installed-fonts.js b/packages/edit-site/src/components/global-styles/font-library-modal/installed-fonts.js index 99c99e44a43af..bcd37a18cebad 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/installed-fonts.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/installed-fonts.js @@ -12,6 +12,8 @@ import { Spinner, FlexItem, } from '@wordpress/components'; +import { store as coreStore } from '@wordpress/core-data'; +import { useSelect } from '@wordpress/data'; /** * Internal dependencies @@ -38,6 +40,20 @@ function InstalledFonts() { } = useContext( FontLibraryContext ); const [ isConfirmDeleteOpen, setIsConfirmDeleteOpen ] = useState( false ); + const customFontFamilyId = + libraryFontSelected?.source === 'custom' && libraryFontSelected?.id; + + const canUserDelete = useSelect( + ( select ) => { + const { canUser } = select( coreStore ); + return ( + customFontFamilyId && + canUser( 'delete', 'font-families', customFontFamilyId ) + ); + }, + [ customFontFamilyId ] + ); + const handleUnselectFont = () => { handleSetLibraryFontSelected( null ); }; @@ -84,7 +100,9 @@ function InstalledFonts() { : null; const shouldDisplayDeleteButton = - !! libraryFontSelected && libraryFontSelected?.source !== 'theme'; + !! libraryFontSelected && + libraryFontSelected?.source !== 'theme' && + canUserDelete; useEffect( () => { handleSelectFont( libraryFontSelected ); From efa01ec1c09d8b4ab73406dc1816146e57870eae Mon Sep 17 00:00:00 2001 From: Grant Kinney Date: Thu, 22 Feb 2024 16:47:31 -0600 Subject: [PATCH 04/19] Fixes comment block --- lib/compat/wordpress-6.5/fonts/fonts.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/compat/wordpress-6.5/fonts/fonts.php b/lib/compat/wordpress-6.5/fonts/fonts.php index 04c0b1666dd1b..833b9f72273e0 100644 --- a/lib/compat/wordpress-6.5/fonts/fonts.php +++ b/lib/compat/wordpress-6.5/fonts/fonts.php @@ -87,10 +87,10 @@ function gutenberg_create_initial_post_types() { /** * Filters the user capabilities to grant the 'upload_fonts' capability as necessary. * - * To grant the 'upload_fonts' capability, files modifications must be allowed, the fonts directory must be + * To grant the 'upload_fonts' capability, file modifications must be allowed, the fonts directory must be * writable, and the user must have the 'edit_theme_options' capability. * - * @since 5.6.0 + * @since 6.5.0 * * @param bool[] $allcaps An array of all the user's capabilities. * @return bool[] Filtered array of the user's capabilities. From dc57cd4c1bf1ffe96d287082aace09f8e5b040a4 Mon Sep 17 00:00:00 2001 From: Matias Benedetto Date: Fri, 23 Feb 2024 14:00:20 -0300 Subject: [PATCH 05/19] Display the font collection tabs when user can create font families. Disable upload tab when the user can't write font face assets. Disallow install button when the user can write font faces and the selected font has font faces --- .../font-library-modal/font-collection.js | 17 +++++++++++++++-- .../global-styles/font-library-modal/index.js | 9 +++++---- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/font-collection.js b/packages/edit-site/src/components/global-styles/font-library-modal/font-collection.js index b792a5c8246c0..850b74da3d91a 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/font-collection.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/font-collection.js @@ -23,6 +23,8 @@ import { import { debounce } from '@wordpress/compose'; import { sprintf, __, _x } from '@wordpress/i18n'; import { search, closeSmall } from '@wordpress/icons'; +import { useSelect } from '@wordpress/data'; +import { store as editorStore } from '@wordpress/editor'; /** * Internal dependencies @@ -215,6 +217,7 @@ function FontCollection( { slug } ) { ); } else if ( ! renderConfirmDialog && totalPages > 1 ) { @@ -400,16 +403,26 @@ function PaginationFooter( { page, totalPages, setPage } ) { ); } -function InstallFooter( { handleInstall, isDisabled } ) { +function InstallFooter( { handleInstall, isDisabled, selectedFont } ) { const { isInstalling } = useContext( FontLibraryContext ); + const fontUploadsEnabled = useSelect( ( select ) => { + const { getEditorSettings } = select( editorStore ); + return getEditorSettings().fontUploadsEnabled; + }, [] ); + + const isInstallButtonDisabled = + isDisabled || + isInstalling || + ( ! fontUploadsEnabled && selectedFont?.fontFace?.length ); + return (