Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Font Library: refactor endpoint permissions #54829

Merged
merged 8 commits into from
Sep 29, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -289,19 +289,39 @@ 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' );

$errors = array();
$successes = array();

if ( empty( $fonts_to_uninstall ) ) {
$errors[] = new WP_Error(
'no_fonts_to_install',
__( 'No fonts to uninstall', 'gutenberg' )
);
$data = array(
'successes' => $successes,
'errors' => $errors,
);
$response = rest_ensure_response( $data );
$response->set_status( 400 );
return $response;
}

foreach ( $fonts_param as $font_data ) {
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 family uninstalled successfully.', 'gutenberg' ), 200 );
$data = array(
'successes' => $successes,
'errors' => $errors,
);
return rest_ensure_response( $data );
}

/**
Expand All @@ -321,23 +341,48 @@ 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'];
$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' => 500,
)
);
return false;
}

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.
*
Expand All @@ -361,38 +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;
}

// 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 ( $this->needs_write_permission( $fonts_to_install ) && ! $this->has_write_permission() ) {
$errors[] = new WP_Error(
'cannot_write_fonts_folder',
__( 'Error: WordPress does not have permission to write the fonts folder on your server.', 'gutenberg' )
);
$response_status = 500;
}

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 );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 );

Expand Down Expand Up @@ -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(
Expand All @@ -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 ],
};
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
FlexItem,
Flex,
Button,
Notice,
} from '@wordpress/components';
import { debounce } from '@wordpress/compose';
import { __ } from '@wordpress/i18n';
Expand All @@ -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',
Expand 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
);
Expand All @@ -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 ]
Expand Down Expand Up @@ -122,6 +136,13 @@ function FontCollection( { id } ) {
setFontsToInstall( [] );
};

const handleInstall = async () => {
const response = await installFonts( fontsToInstall );
const installNotice = getNoticeFromInstallResponse( response );
setNotice( installNotice );
resetFontsToInstall();
};

return (
<TabLayout
title={
Expand All @@ -135,10 +156,7 @@ function FontCollection( { id } ) {
handleBack={ !! selectedFont && handleUnselectFont }
footer={
fontsToInstall.length > 0 && (
<Footer
fontsToInstall={ fontsToInstall }
resetFontsToInstall={ resetFontsToInstall }
/>
<Footer handleInstall={ handleInstall } />
)
}
>
Expand All @@ -153,6 +171,22 @@ function FontCollection( { id } ) {
<Spinner />
) }

{ notice && (
<>
<FlexItem>
<Spacer margin={ 2 } />
<Notice
isDismissible={ false }
status={ notice.type }
className="font-library-modal__font-collection__notice"
>
{ notice.message }
</Notice>
</FlexItem>
<Spacer margin={ 2 } />
</>
) }

{ ! renderConfirmDialog && ! selectedFont && (
<Flex>
<FlexItem>
Expand Down Expand Up @@ -232,13 +266,8 @@ function FontCollection( { id } ) {
);
}

function Footer( { fontsToInstall, resetFontsToInstall } ) {
const { installFonts, isInstalling } = useContext( FontLibraryContext );

const handleInstall = async () => {
await installFonts( fontsToInstall );
resetFontsToInstall();
};
function Footer( { handleInstall } ) {
const { isInstalling } = useContext( FontLibraryContext );

return (
<Flex justify="flex-end">
Expand Down
Loading
Loading