From 522a1a93ca703f285feb51f31ffd951c9833afd7 Mon Sep 17 00:00:00 2001 From: Matias Benedetto Date: Tue, 26 Sep 2023 10:57:41 -0300 Subject: [PATCH 1/7] break the checking of user permission and file write permissions --- .../class-wp-rest-font-library-controller.php | 60 ++++++++++++++++++- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/lib/experimental/fonts/font-library/class-wp-rest-font-library-controller.php b/lib/experimental/fonts/font-library/class-wp-rest-font-library-controller.php index dcf94d93012a27..5064acb3a287d1 100644 --- a/lib/experimental/fonts/font-library/class-wp-rest-font-library-controller.php +++ b/lib/experimental/fonts/font-library/class-wp-rest-font-library-controller.php @@ -291,6 +291,22 @@ public function uninstall_schema() { public function uninstall_fonts( $request ) { $fonts_param = $request->get_param( 'fontFamilies' ); + if ( empty( $fonts_to_install ) ) { + return new WP_Error( + 'no_fonts_to_install', + __( 'No fonts to uninstall', 'gutenberg' ), + array( 'status' => 400 ) + ); + } + + $needs_write_permission = $this->needs_write_permission( $fonts_to_install ); + if ( $needs_write_permission ) { + $write_permission = $this->has_write_permission(); + if ( is_wp_error( $write_permission ) ) { + return $write_permission; + } + } + foreach ( $fonts_param as $font_data ) { $font = new WP_Font_Family( $font_data ); $result = $font->uninstall(); @@ -321,7 +337,17 @@ public function update_font_library_permissions_check() { ) ); } + return true; + } + /** + * Checks whether the user has write permissions to the temp and fonts directories. + * + * @since 6.4.0 + * + * @return true|WP_Error True if the user has write permissions, WP_Error object otherwise. + */ + private function has_write_permission () { // The update endpoints requires write access to the temp and the fonts directories. $temp_dir = get_temp_dir(); $upload_dir = wp_upload_dir()['basedir']; @@ -330,14 +356,35 @@ public function update_font_library_permissions_check() { 'rest_cannot_write_fonts_folder', __( 'Error: WordPress does not have permission to write the fonts folder on your server.', 'gutenberg' ), array( - 'status' => 500, + 'status' => 550, ) ); } - return true; } + /** + * Checks whether the request needs write permissions. + * + * @since 6.4.0 + * + * @param array[] $font_families Font families to install. + * @return bool Whether the request needs write permissions. + */ + private function needs_write_permission ( $font_families ) { + foreach ( $font_families as $font ) { + if ( isset( $font['fontFace'] ) ) { + foreach ( $font['fontFace'] as $face ) { + // If the font is being downloaded from a URL or uploaded, it needs write permissions. + if ( isset( $face['downloadFromUrl'] ) || isset( $face['uploadedFile'] ) ) { + return true; + } + } + } + } + return false; + } + /** * Installs new fonts. * @@ -369,11 +416,20 @@ public function install_fonts( $request ) { ); } + $needs_write_permission = $this->needs_write_permission( $fonts_to_install ); + if ( $needs_write_permission ) { + $write_permission = $this->has_write_permission(); + if ( is_wp_error( $write_permission ) ) { + return $write_permission; + } + } + // Get uploaded files (used when installing local fonts). $files = $request->get_file_params(); // Iterates the fonts data received and creates a new WP_Font_Family object for each one. $fonts_installed = array(); + foreach ( $fonts_to_install as $font_data ) { $font = new WP_Font_Family( $font_data ); $font->install( $files ); From fad89f5a88fa0c0862417a6ffc656bc255a663ae Mon Sep 17 00:00:00 2001 From: Matias Benedetto Date: Thu, 28 Sep 2023 15:41:01 -0300 Subject: [PATCH 2/7] return error 500 if the request to the install fonts endpoint needs write permissions and wordpress doens't have write permission on the server --- .../class-wp-rest-font-library-controller.php | 26 +++++++------------ 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/lib/experimental/fonts/font-library/class-wp-rest-font-library-controller.php b/lib/experimental/fonts/font-library/class-wp-rest-font-library-controller.php index 5064acb3a287d1..0c584406815c11 100644 --- a/lib/experimental/fonts/font-library/class-wp-rest-font-library-controller.php +++ b/lib/experimental/fonts/font-library/class-wp-rest-font-library-controller.php @@ -347,18 +347,12 @@ public function update_font_library_permissions_check() { * * @return true|WP_Error True if the user has write permissions, WP_Error object otherwise. */ - private function has_write_permission () { + private function has_write_permission() { // The update endpoints requires write access to the temp and the fonts directories. $temp_dir = get_temp_dir(); - $upload_dir = wp_upload_dir()['basedir']; + $upload_dir = WP_Font_Library::get_fonts_dir(); if ( ! is_writable( $temp_dir ) || ! wp_is_writable( $upload_dir ) ) { - return new WP_Error( - 'rest_cannot_write_fonts_folder', - __( 'Error: WordPress does not have permission to write the fonts folder on your server.', 'gutenberg' ), - array( - 'status' => 550, - ) - ); + return false; } return true; } @@ -371,7 +365,7 @@ private function has_write_permission () { * @param array[] $font_families Font families to install. * @return bool Whether the request needs write permissions. */ - private function needs_write_permission ( $font_families ) { + private function needs_write_permission( $font_families ) { foreach ( $font_families as $font ) { if ( isset( $font['fontFace'] ) ) { foreach ( $font['fontFace'] as $face ) { @@ -416,12 +410,12 @@ public function install_fonts( $request ) { ); } - $needs_write_permission = $this->needs_write_permission( $fonts_to_install ); - if ( $needs_write_permission ) { - $write_permission = $this->has_write_permission(); - if ( is_wp_error( $write_permission ) ) { - return $write_permission; - } + if ( $this->needs_write_permission( $fonts_to_install ) && ! $this->has_write_permission() ) { + return new WP_Error( + 'cannot_write_fonts_folder', + __( 'Error: WordPress does not have permission to write the fonts folder on your server.', 'gutenberg' ), + array( 'status' => 500 ) + ); } // Get uploaded files (used when installing local fonts). From a153caed899c6779934d2e7867ec8f97f8d2b4a0 Mon Sep 17 00:00:00 2001 From: Matias Benedetto Date: Thu, 28 Sep 2023 15:55:42 -0300 Subject: [PATCH 3/7] do not ask file write permission on uninstall endpoint --- .../class-wp-rest-font-library-controller.php | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/lib/experimental/fonts/font-library/class-wp-rest-font-library-controller.php b/lib/experimental/fonts/font-library/class-wp-rest-font-library-controller.php index 0c584406815c11..78391c5ee2deb7 100644 --- a/lib/experimental/fonts/font-library/class-wp-rest-font-library-controller.php +++ b/lib/experimental/fonts/font-library/class-wp-rest-font-library-controller.php @@ -289,9 +289,9 @@ public function uninstall_schema() { * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function uninstall_fonts( $request ) { - $fonts_param = $request->get_param( 'fontFamilies' ); + $fonts_to_uninstall = $request->get_param( 'fontFamilies' ); - if ( empty( $fonts_to_install ) ) { + if ( empty( $fonts_to_uninstall ) ) { return new WP_Error( 'no_fonts_to_install', __( 'No fonts to uninstall', 'gutenberg' ), @@ -299,15 +299,7 @@ public function uninstall_fonts( $request ) { ); } - $needs_write_permission = $this->needs_write_permission( $fonts_to_install ); - if ( $needs_write_permission ) { - $write_permission = $this->has_write_permission(); - if ( is_wp_error( $write_permission ) ) { - return $write_permission; - } - } - - foreach ( $fonts_param as $font_data ) { + foreach ( $fonts_to_uninstall as $font_data ) { $font = new WP_Font_Family( $font_data ); $result = $font->uninstall(); @@ -317,7 +309,7 @@ public function uninstall_fonts( $request ) { } } - return new WP_REST_Response( __( 'Font family uninstalled successfully.', 'gutenberg' ), 200 ); + return new WP_REST_Response( __( 'Font families uninstalled successfully.', 'gutenberg' ), 200 ); } /** From 9f726d46b6a3cbb78b3824e2be52da0bbcb15c9f Mon Sep 17 00:00:00 2001 From: Matias Benedetto Date: Fri, 29 Sep 2023 17:59:31 -0300 Subject: [PATCH 4/7] Standardize the output of install and uninstall fonts endpoints Co-authored-by: Jason Crist <146530+pbking@users.noreply.github.com> Co-authored-by: Jeff Ong <5375500+jffng@users.noreply.github.com> --- .../class-wp-rest-font-library-controller.php | 87 +++++++++++-------- 1 file changed, 52 insertions(+), 35 deletions(-) diff --git a/lib/experimental/fonts/font-library/class-wp-rest-font-library-controller.php b/lib/experimental/fonts/font-library/class-wp-rest-font-library-controller.php index 78391c5ee2deb7..90fb3c6973ca3f 100644 --- a/lib/experimental/fonts/font-library/class-wp-rest-font-library-controller.php +++ b/lib/experimental/fonts/font-library/class-wp-rest-font-library-controller.php @@ -291,25 +291,37 @@ public function uninstall_schema() { public function uninstall_fonts( $request ) { $fonts_to_uninstall = $request->get_param( 'fontFamilies' ); + $errors = array(); + $successes = array(); + if ( empty( $fonts_to_uninstall ) ) { - return new WP_Error( + $errors[] = new WP_Error( 'no_fonts_to_install', - __( 'No fonts to uninstall', 'gutenberg' ), - array( 'status' => 400 ) + __( 'No fonts to uninstall', 'gutenberg' ) + ); + $data = array( + 'successes' => $successes, + 'errors' => $errors, ); + $response = rest_ensure_response( $data ); + $response->set_status( 400 ); + return $response; } foreach ( $fonts_to_uninstall as $font_data ) { $font = new WP_Font_Family( $font_data ); $result = $font->uninstall(); - - // If there was an error uninstalling the font, return the error. if ( is_wp_error( $result ) ) { - return $result; + $errors[] = $result; + } else { + $successes[] = $result; } } - - return new WP_REST_Response( __( 'Font families uninstalled successfully.', 'gutenberg' ), 200 ); + $data = array( + 'successes' => $successes, + 'errors' => $errors, + ); + return rest_ensure_response( $data ); } /** @@ -394,47 +406,52 @@ public function install_fonts( $request ) { */ $fonts_to_install = json_decode( $fonts_param, true ); + $successes = array(); + $errors = array(); + $response_status = 200; + if ( empty( $fonts_to_install ) ) { - return new WP_Error( + $errors[] = new WP_Error( 'no_fonts_to_install', - __( 'No fonts to install', 'gutenberg' ), - array( 'status' => 400 ) + __( 'No fonts to install', 'gutenberg' ) ); + $response_status = 400; } if ( $this->needs_write_permission( $fonts_to_install ) && ! $this->has_write_permission() ) { - return new WP_Error( + $errors[] = new WP_Error( 'cannot_write_fonts_folder', - __( 'Error: WordPress does not have permission to write the fonts folder on your server.', 'gutenberg' ), - array( 'status' => 500 ) + __( 'Error: WordPress does not have permission to write the fonts folder on your server.', 'gutenberg' ) ); + $response_status = 500; } - // Get uploaded files (used when installing local fonts). - $files = $request->get_file_params(); - - // Iterates the fonts data received and creates a new WP_Font_Family object for each one. - $fonts_installed = array(); - - foreach ( $fonts_to_install as $font_data ) { - $font = new WP_Font_Family( $font_data ); - $font->install( $files ); - $fonts_installed[] = $font; - } - - if ( empty( $fonts_installed ) ) { - return new WP_Error( - 'error_installing_fonts', - __( 'Error installing fonts. No font was installed.', 'gutenberg' ), - array( 'status' => 500 ) + if ( ! empty( $errors ) ) { + $data = array( + 'successes' => $successes, + 'errors' => $errors, ); + $response = rest_ensure_response( $data ); + $response->set_status( $response_status ); + return $response; } - $response = array(); - foreach ( $fonts_installed as $font ) { - $response[] = $font->get_data(); + // Get uploaded files (used when installing local fonts). + $files = $request->get_file_params(); + foreach ( $fonts_to_install as $font_data ) { + $font = new WP_Font_Family( $font_data ); + $result = $font->install( $files ); + if ( is_wp_error( $result ) ) { + $errors[] = $result; + } else { + $successes[] = $result; + } } - return new WP_REST_Response( $response ); + $data = array( + 'successes' => $successes, + 'errors' => $errors, + ); + return rest_ensure_response( $data ); } } From 19ca6bc96a2600872be028d78b1cf3a036e50e41 Mon Sep 17 00:00:00 2001 From: Matias Benedetto Date: Fri, 29 Sep 2023 18:06:06 -0300 Subject: [PATCH 5/7] Handle standardized output from install and uninstall endpoints in the frontend Co-authored-by: Jason Crist <146530+pbking@users.noreply.github.com> Co-authored-by: Jeff Ong <5375500+jffng@users.noreply.github.com> --- .../font-library-modal/context.js | 49 ++++++++------- .../font-library-modal/font-collection.js | 53 ++++++++++++---- .../font-library-modal/installed-fonts.js | 39 +++++++++++- .../font-library-modal/local-fonts.js | 23 ++++--- .../font-library-modal/style.scss | 4 ++ .../utils/get-notice-from-response.js | 62 +++++++++++++++++++ 6 files changed, 183 insertions(+), 47 deletions(-) create mode 100644 packages/edit-site/src/components/global-styles/font-library-modal/utils/get-notice-from-response.js diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/context.js b/packages/edit-site/src/components/global-styles/font-library-modal/context.js index 78a238e1ba51bb..8ab067495fcc08 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/context.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/context.js @@ -9,8 +9,6 @@ import { useEntityRecords, store as coreStore, } from '@wordpress/core-data'; -import { store as noticesStore } from '@wordpress/notices'; -import { __ } from '@wordpress/i18n'; /** * Internal dependencies @@ -51,8 +49,6 @@ function FontLibraryProvider( { children } ) { const fontFamiliesHasChanges = !! globalStyles?.edits?.settings?.typography?.fontFamilies; - const { createErrorNotice } = useDispatch( noticesStore ); - const [ isInstalling, setIsInstalling ] = useState( false ); const [ refreshKey, setRefreshKey ] = useState( 0 ); @@ -202,7 +198,8 @@ function FontLibraryProvider( { children } ) { // Prepare formData to install. const formData = makeFormDataFromFontFamilies( fonts ); // Install the fonts (upload the font files to the server and create the post in the database). - const fontsInstalled = await fetchInstallFonts( formData ); + const response = await fetchInstallFonts( formData ); + const fontsInstalled = response?.successes || []; // Get intersecting font faces between the fonts we tried to installed and the fonts that were installed // (to avoid activating a non installed font). const fontToBeActivated = getIntersectingFontFaces( @@ -217,36 +214,40 @@ function FontLibraryProvider( { children } ) { ] ); refreshLibrary(); setIsInstalling( false ); - return true; - } catch ( e ) { + + return response; + } catch ( error ) { setIsInstalling( false ); - return false; + return { + errors: [ error ], + }; } } async function uninstallFont( font ) { try { // Uninstall the font (remove the font files from the server and the post from the database). - await fetchUninstallFonts( [ font ] ); + const response = await fetchUninstallFonts( [ font ] ); // Deactivate the font family (remove the font family from the global styles). - deactivateFontFamily( font ); - // Save the global styles to the database. - await saveSpecifiedEntityEdits( - 'root', - 'globalStyles', - globalStylesId, - [ 'settings.typography.fontFamilies' ] - ); + if ( ! response.errors ) { + deactivateFontFamily( font ); + // Save the global styles to the database. + await saveSpecifiedEntityEdits( + 'root', + 'globalStyles', + globalStylesId, + [ 'settings.typography.fontFamilies' ] + ); + } // Refresh the library (the the library font families from database). refreshLibrary(); - return true; - } catch ( e ) { + return response; + } catch ( error ) { // eslint-disable-next-line no-console - console.error( e ); - createErrorNotice( __( 'Error uninstalling fonts.' ), { - type: 'snackbar', - } ); - return false; + console.error( error ); + return { + errors: [ error ], + }; } } 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 56d1a03e63b32c..a3b697efcfb8b9 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 @@ -12,6 +12,7 @@ import { FlexItem, Flex, Button, + Notice, } from '@wordpress/components'; import { debounce } from '@wordpress/compose'; import { __ } from '@wordpress/i18n'; @@ -29,6 +30,7 @@ import CollectionFontDetails from './collection-font-details'; import { toggleFont } from './utils/toggleFont'; import { getFontsOutline } from './utils/fonts-outline'; import GoogleFontsConfirmDialog from './google-fonts-confirm-dialog'; +import { getNoticeFromInstallResponse } from './utils/get-notice-from-response'; const DEFAULT_CATEGORY = { id: 'all', @@ -45,13 +47,15 @@ function FontCollection( { id } ) { ); }; + const [ notice, setNotice ] = useState( null ); const [ selectedFont, setSelectedFont ] = useState( null ); const [ fontsToInstall, setFontsToInstall ] = useState( [] ); const [ filters, setFilters ] = useState( {} ); const [ renderConfirmDialog, setRenderConfirmDialog ] = useState( requiresPermission && ! getGoogleFontsPermissionFromStorage() ); - const { collections, getFontCollection } = useContext( FontLibraryContext ); + const { collections, getFontCollection, installFonts } = + useContext( FontLibraryContext ); const selectedCollection = collections.find( ( collection ) => collection.id === id ); @@ -76,6 +80,16 @@ function FontCollection( { id } ) { setSelectedFont( null ); }, [ id ] ); + // Reset notice after 5 seconds + useEffect( () => { + if ( notice ) { + const timeout = setTimeout( () => { + setNotice( null ); + }, 5000 ); + return () => clearTimeout( timeout ); + } + }, [ notice ] ); + const collectionFonts = useMemo( () => selectedCollection?.data?.fontFamilies ?? [], [ selectedCollection ] @@ -122,6 +136,13 @@ function FontCollection( { id } ) { setFontsToInstall( [] ); }; + const handleInstall = async () => { + const response = await installFonts( fontsToInstall ); + const installNotice = getNoticeFromInstallResponse( response ); + setNotice( installNotice ); + resetFontsToInstall(); + }; + return ( 0 && ( -