diff --git a/src/wp-includes/fonts.php b/src/wp-includes/fonts.php index 690aaa5ffc343..74feee1ac9cf7 100644 --- a/src/wp-includes/fonts.php +++ b/src/wp-includes/fonts.php @@ -91,13 +91,31 @@ function wp_unregister_font_collection( string $slug ) { return WP_Font_Library::get_instance()->unregister_font_collection( $slug ); } +/** + * Retrieves font uploads directory information. + * + * Same as wp_font_dir() but "light weight" as it doesn't attempt to create the font uploads directory. + * Intended for use in themes, when only 'basedir' and 'baseurl' are needed, generally in all cases + * when not uploading files. + * + * @since 6.5.0 + * + * @see wp_font_dir() + * + * @return array See wp_font_dir() for description. + */ +function wp_get_font_dir() { + return wp_font_dir( false ); +} + /** * Returns an array containing the current fonts upload directory's path and URL. * * @since 6.5.0 * - * @return array $defaults { - * Array of information about the upload directory. + * @param bool $create_dir Optional. Whether to check and create the font uploads directory. Default true. + * @return array { + * Array of information about the font upload directory. * * @type string $path Base directory and subdirectory or full path to the fonts upload directory. * @type string $url Base URL and subdirectory or absolute URL to the fonts upload directory. @@ -107,13 +125,54 @@ function wp_unregister_font_collection( string $slug ) { * @type string|false $error False or error message. * } */ -function wp_get_font_dir() { +function wp_font_dir( $create_dir = true ) { + /* + * Allow extenders to manipulate the font directory consistently. + * + * Ensures the upload_dir filter is fired both when calling this function + * directly and when the upload directory is filtered in the Font Face + * REST API endpoint. + */ + add_filter( 'upload_dir', '_wp_filter_font_directory' ); + $font_dir = wp_upload_dir( null, $create_dir, false ); + remove_filter( 'upload_dir', '_wp_filter_font_directory' ); + return $font_dir; +} + +/** + * Returns the font directory for use by the font library. + * + * This function is a callback for the {@see 'upload_dir'} filter. It is not + * intended to be called directly. Use wp_get_font_dir() instead. + * + * The function can be used when extending the font library to modify the upload + * destination for font files via the upload_dir filter. The recommended way to + * do this is: + * + * ```php + * add_filter( 'upload_dir', '_wp_filter_font_directory' ); + * // Your code to upload or sideload a font file. + * remove_filter( 'upload_dir', '_wp_filter_font_directory' ); + * ``` + * + * @since 6.5.0 + * @access private + * + * @param string $font_dir The font directory. + * @return string The modified font directory. + */ +function _wp_filter_font_directory( $font_dir ) { + if ( doing_filter( 'font_dir' ) ) { + // Avoid an infinite loop. + return $font_dir; + } + $site_path = ''; if ( is_multisite() && ! ( is_main_network() && is_main_site() ) ) { $site_path = '/sites/' . get_current_blog_id(); } - $defaults = array( + $font_dir = array( 'path' => path_join( WP_CONTENT_DIR, 'fonts' ) . $site_path, 'url' => untrailingslashit( content_url( 'fonts' ) ) . $site_path, 'subdir' => '', @@ -129,9 +188,18 @@ function wp_get_font_dir() { * * @since 6.5.0 * - * @param array $defaults The original fonts directory data. + * @param array $font_dir { + * Array of information about the font upload directory. + * + * @type string $path Base directory and subdirectory or full path to the fonts upload directory. + * @type string $url Base URL and subdirectory or absolute URL to the fonts upload directory. + * @type string $subdir Subdirectory + * @type string $basedir Path without subdir. + * @type string $baseurl URL path without subdir. + * @type string|false $error False or error message. + * } */ - return apply_filters( 'font_dir', $defaults ); + return apply_filters( 'font_dir', $font_dir ); } /** diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php index 309fb126e1c79..c7f72d4ec1d9d 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php @@ -856,21 +856,8 @@ protected function sanitize_src( $value ) { */ protected function handle_font_file_upload( $file ) { add_filter( 'upload_mimes', array( 'WP_Font_Utils', 'get_allowed_font_mime_types' ) ); - - /* - * Set the upload directory to the fonts directory. - * - * wp_get_font_dir() contains the 'font_dir' hook, whose callbacks are - * likely to call wp_get_upload_dir(). - * - * To avoid an infinite loop, don't hook wp_get_font_dir() to 'upload_dir'. - * Instead, just pass its return value to the 'upload_dir' callback. - */ - $font_dir = wp_get_font_dir(); - $set_upload_dir = function () use ( $font_dir ) { - return $font_dir; - }; - add_filter( 'upload_dir', $set_upload_dir ); + // Filter the upload directory to return the fonts directory. + add_filter( 'upload_dir', '_wp_filter_font_directory' ); $overrides = array( 'upload_error_handler' => array( $this, 'handle_font_file_upload_error' ), @@ -887,7 +874,7 @@ protected function handle_font_file_upload( $file ) { $uploaded_file = wp_handle_upload( $file, $overrides ); - remove_filter( 'upload_dir', $set_upload_dir ); + remove_filter( 'upload_dir', '_wp_filter_font_directory' ); remove_filter( 'upload_mimes', array( 'WP_Font_Utils', 'get_allowed_font_mime_types' ) ); return $uploaded_file; diff --git a/tests/phpunit/tests/fonts/font-library/fontLibraryHooks.php b/tests/phpunit/tests/fonts/font-library/fontLibraryHooks.php index 083c12202aa34..c288a1ae93845 100644 --- a/tests/phpunit/tests/fonts/font-library/fontLibraryHooks.php +++ b/tests/phpunit/tests/fonts/font-library/fontLibraryHooks.php @@ -73,13 +73,13 @@ protected function upload_font_file( $font_filename ) { $font_file_path = DIR_TESTDATA . '/fonts/' . $font_filename; add_filter( 'upload_mimes', array( 'WP_Font_Utils', 'get_allowed_font_mime_types' ) ); - add_filter( 'upload_dir', 'wp_get_font_dir' ); + add_filter( 'upload_dir', '_wp_filter_font_directory' ); $font_file = wp_upload_bits( $font_filename, null, file_get_contents( $font_file_path ) ); - remove_filter( 'upload_dir', 'wp_get_font_dir' ); + remove_filter( 'upload_dir', '_wp_filter_font_directory' ); remove_filter( 'upload_mimes', array( 'WP_Font_Utils', 'get_allowed_font_mime_types' ) ); return $font_file; diff --git a/tests/phpunit/tests/fonts/font-library/wpFontsDir.php b/tests/phpunit/tests/fonts/font-library/wpFontsDir.php index a8f79888315bd..5021aefb59d95 100644 --- a/tests/phpunit/tests/fonts/font-library/wpFontsDir.php +++ b/tests/phpunit/tests/fonts/font-library/wpFontsDir.php @@ -69,4 +69,44 @@ function set_new_values( $defaults ) { $this->assertSame( static::$dir_defaults, $font_dir, 'The wp_get_font_dir() method should return the default values.' ); } + + /** + * @ticket 60652 + */ + public function test_fonts_dir_filters_do_not_trigger_infinite_loop() { + /* + * Naive filtering of uploads directory to return font directory. + * + * This emulates the approach a plugin developer may take to + * add the filter when extending the font library functionality. + */ + add_filter( 'upload_dir', '_wp_filter_font_directory' ); + + add_filter( + 'upload_dir', + function ( $upload_dir ) { + static $count = 0; + ++$count; + // The filter may be applied a couple of times, at five iterations assume an infinite loop. + if ( $count >= 5 ) { + $this->fail( 'Filtering the uploads directory triggered an infinite loop.' ); + } + return $upload_dir; + }, + 5 + ); + + /* + * Filter the font directory to return the uploads directory. + * + * This emulates moving font files back to the uploads directory due + * to file system structure. + */ + add_filter( 'font_dir', 'wp_get_upload_dir' ); + + wp_get_upload_dir(); + + // This will never be hit if an infinite loop is triggered. + $this->assertTrue( true ); + } }