<?php /** * Font Library initialization. * * This file contains Font Library init calls. * * @package WordPress * @subpackage Font Library * @since 6.5.0 */ /** * Registers the routes for the objects of the controller. * * This function will not be merged into Core. However, the * code in the function will be. @core-merge annotation * provides instructions on where the could needs to go * in Core. * * @since 6.5.0 */ function gutenberg_create_initial_post_types() { // @core-merge: This code will go into Core's `create_initial_post_types()`. $args = array( 'labels' => array( 'name' => __( 'Font Families', 'gutenberg' ), 'singular_name' => __( 'Font Family', 'gutenberg' ), ), 'public' => false, '_builtin' => true, /* internal use only. don't use this when registering your own post type. */ 'hierarchical' => false, 'capabilities' => array( 'read' => 'edit_theme_options', 'read_private_posts' => 'edit_theme_options', 'create_posts' => 'edit_theme_options', 'publish_posts' => 'edit_theme_options', 'edit_posts' => 'edit_theme_options', 'edit_others_posts' => 'edit_theme_options', 'edit_published_posts' => 'edit_theme_options', 'delete_posts' => 'edit_theme_options', 'delete_others_posts' => 'edit_theme_options', 'delete_published_posts' => 'edit_theme_options', ), 'map_meta_cap' => true, 'query_var' => false, 'show_in_rest' => true, 'rest_base' => 'font-families', 'rest_controller_class' => 'WP_REST_Font_Families_Controller', // Disable autosave endpoints for font families. 'autosave_rest_controller_class' => 'stdClass', ); register_post_type( 'wp_font_family', $args ); register_post_type( 'wp_font_face', array( 'labels' => array( 'name' => __( 'Font Faces', 'gutenberg' ), 'singular_name' => __( 'Font Face', 'gutenberg' ), ), 'public' => false, '_builtin' => true, /* internal use only. don't use this when registering your own post type. */ 'hierarchical' => false, 'capabilities' => array( 'read' => 'edit_theme_options', 'read_private_posts' => 'edit_theme_options', 'create_posts' => 'edit_theme_options', 'publish_posts' => 'edit_theme_options', 'edit_posts' => 'edit_theme_options', 'edit_others_posts' => 'edit_theme_options', 'edit_published_posts' => 'edit_theme_options', 'delete_posts' => 'edit_theme_options', 'delete_others_posts' => 'edit_theme_options', 'delete_published_posts' => 'edit_theme_options', ), 'map_meta_cap' => true, 'query_var' => false, 'show_in_rest' => true, 'rest_base' => 'font-families/(?P<font_family_id>[\d]+)/font-faces', 'rest_controller_class' => 'WP_REST_Font_Faces_Controller', // Disable autosave endpoints for font faces. 'autosave_rest_controller_class' => 'stdClass', ) ); } /** * Initializes REST routes. * * @global string $wp_version The WordPress version string. */ function gutenberg_create_initial_rest_routes() { global $wp_version; // Runs only if the Font Library is not available in core ( i.e. in core < 6.5-alpha ). if ( version_compare( $wp_version, '6.5-alpha', '<' ) ) { $font_collections_controller = new WP_REST_Font_Collections_Controller(); $font_collections_controller->register_routes(); } } add_action( 'rest_api_init', 'gutenberg_create_initial_rest_routes' ); /** * Initializes REST routes and post types. * * @global string $wp_version The WordPress version string. */ function gutenberg_init_font_library() { global $wp_version; // Runs only if the Font Library is not available in core ( i.e. in core < 6.5-alpha ). if ( version_compare( $wp_version, '6.5-alpha', '<' ) ) { gutenberg_create_initial_post_types(); } } add_action( 'init', 'gutenberg_init_font_library' ); if ( ! function_exists( 'wp_register_font_collection' ) ) { /** * Registers a new font collection in the font library. * * See {@link https://schemas.wp.org/trunk/font-collection.json} for the schema * the font collection data must adhere to. * * @since 6.5.0 * * @param string $slug Font collection slug. May only contain alphanumeric characters, dashes, * and underscores. See sanitize_title(). * @param array $args { * Font collection data. * * @type string $name Required. Name of the font collection shown in the Font Library. * @type string $description Optional. A short descriptive summary of the font collection. Default empty. * @type array|string $font_families Required. Array of font family definitions that are in the collection, * or a string containing the path or URL to a JSON file containing the font collection. * @type array $categories Optional. Array of categories, each with a name and slug, that are used by the * fonts in the collection. Default empty. * } * @return WP_Font_Collection|WP_Error A font collection if it was registered * successfully, or WP_Error object on failure. */ function wp_register_font_collection( string $slug, array $args ) { return WP_Font_Library::get_instance()->register_font_collection( $slug, $args ); } } if ( ! function_exists( 'wp_unregister_font_collection' ) ) { /** * Unregisters a font collection from the Font Library. * * @since 6.5.0 * * @param string $slug Font collection slug. * @return bool True if the font collection was unregistered successfully, else false. */ function wp_unregister_font_collection( string $slug ) { return WP_Font_Library::get_instance()->unregister_font_collection( $slug ); } } function gutenberg_register_font_collections() { if ( null !== WP_Font_Library::get_instance()->get_font_collection( 'google-fonts' ) ) { return; } wp_register_font_collection( 'google-fonts', array( 'name' => _x( 'Google Fonts', 'font collection name', 'gutenberg' ), 'description' => __( 'Install from Google Fonts. Fonts are copied to and served from your site.', 'gutenberg' ), 'font_families' => 'https://s.w.org/images/fonts/wp-6.5/collections/google-fonts-with-preview.json', 'categories' => array( array( 'name' => _x( 'Sans Serif', 'font category', 'gutenberg' ), 'slug' => 'sans-serif', ), array( 'name' => _x( 'Display', 'font category', 'gutenberg' ), 'slug' => 'display', ), array( 'name' => _x( 'Serif', 'font category', 'gutenberg' ), 'slug' => 'serif', ), array( 'name' => _x( 'Handwriting', 'font category', 'gutenberg' ), 'slug' => 'handwriting', ), array( 'name' => _x( 'Monospace', 'font category', 'gutenberg' ), 'slug' => 'monospace', ), ), ) ); } add_action( 'init', 'gutenberg_register_font_collections', 11 ); // @core-merge: This code should probably go into Core's src/wp-includes/fonts.php. if ( ! function_exists( 'wp_get_font_dir' ) ) { /** * 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 ); } } // @core-merge: This code should probably go into Core's src/wp-includes/fonts.php. if ( ! function_exists( 'wp_font_dir' ) ) { /** * Returns an array containing the current fonts upload directory's path and URL. * * @since 6.5.0 * * @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. * @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. * } */ 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; } } // @core-merge: This code should probably go into Core's src/wp-includes/fonts.php. if ( ! function_exists( '_wp_filter_font_directory' ) ) { /** * 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; } $font_dir = array( 'path' => untrailingslashit( $font_dir['basedir'] ) . '/fonts', 'url' => untrailingslashit( $font_dir['baseurl'] ) . '/fonts', 'subdir' => '', 'basedir' => untrailingslashit( $font_dir['basedir'] ) . '/fonts', 'baseurl' => untrailingslashit( $font_dir['baseurl'] ) . '/fonts', 'error' => false, ); /** * Filters the fonts directory data. * * This filter allows developers to modify the fonts directory data. * * @since 6.5.0 * * @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', $font_dir ); } } // @core-merge: Filters should go in `src/wp-includes/default-filters.php`, // functions in a general file for font library. if ( ! function_exists( '_wp_after_delete_font_family' ) ) { /** * Deletes child font faces when a font family is deleted. * * @access private * @since 6.5.0 * * @param int $post_id Post ID. * @param WP_Post $post Post object. */ function _wp_after_delete_font_family( $post_id, $post ) { if ( 'wp_font_family' !== $post->post_type ) { return; } $font_faces = get_children( array( 'post_parent' => $post_id, 'post_type' => 'wp_font_face', ) ); foreach ( $font_faces as $font_face ) { wp_delete_post( $font_face->ID, true ); } } add_action( 'deleted_post', '_wp_after_delete_font_family', 10, 2 ); } if ( ! function_exists( '_wp_before_delete_font_face' ) ) { /** * Deletes associated font files when a font face is deleted. * * @access private * @since 6.5.0 * * @param int $post_id Post ID. * @param WP_Post $post Post object. */ function _wp_before_delete_font_face( $post_id, $post ) { if ( 'wp_font_face' !== $post->post_type ) { return; } $font_files = get_post_meta( $post_id, '_wp_font_face_file', false ); $font_dir = untrailingslashit( wp_get_font_dir()['basedir'] ); foreach ( $font_files as $font_file ) { wp_delete_file( $font_dir . '/' . $font_file ); } } add_action( 'before_delete_post', '_wp_before_delete_font_face', 10, 2 ); } // @core-merge: Do not merge this function, it is for deleting fonts from the wp-content/fonts directory only used in Gutenberg. /** * Deletes associated font files from wp-content/fonts, when a font face is deleted. * * @param int $post_id Post ID. * @param WP_Post $post Post object. */ function gutenberg_before_delete_font_face( $post_id, $post ) { if ( 'wp_font_face' !== $post->post_type ) { return; } $font_files = get_post_meta( $post_id, '_wp_font_face_file', false ); if ( empty( $font_files ) ) { return; } $site_path = ''; if ( is_multisite() && ! ( is_main_network() && is_main_site() ) ) { $site_path = '/sites/' . get_current_blog_id(); } $font_dir = path_join( WP_CONTENT_DIR, 'fonts' ) . $site_path; foreach ( $font_files as $font_file ) { $font_path = $font_dir . '/' . $font_file; if ( file_exists( $font_path ) ) { wp_delete_file( $font_path ); } } } add_action( 'before_delete_post', 'gutenberg_before_delete_font_face', 10, 2 ); // @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. * * @return void */ function gutenberg_convert_legacy_font_family_format() { if ( get_option( 'gutenberg_font_family_format_converted' ) ) { return; } $font_families = new WP_Query( array( 'post_type' => 'wp_font_family', // Set a maximum, but in reality there will be far less than this. 'posts_per_page' => 999, 'update_post_term_cache' => false, ) ); foreach ( $font_families->get_posts() as $font_family ) { $already_converted = get_post_meta( $font_family->ID, '_gutenberg_legacy_font_family', true ); if ( $already_converted ) { continue; } // Stash the old font family content in a meta field just in case we need it. update_post_meta( $font_family->ID, '_gutenberg_legacy_font_family', $font_family->post_content ); $font_family_json = json_decode( $font_family->post_content, true ); if ( ! $font_family_json ) { continue; } $font_faces = isset( $font_family_json['fontFace'] ) ? $font_family_json['fontFace'] : array(); unset( $font_family_json['fontFace'] ); // Save wp_font_face posts within the family. foreach ( $font_faces as $font_face ) { $args = array(); $args['post_type'] = 'wp_font_face'; $args['post_title'] = WP_Font_Utils::get_font_face_slug( $font_face ); $args['post_name'] = sanitize_title( $args['post_title'] ); $args['post_status'] = 'publish'; $args['post_parent'] = $font_family->ID; $args['post_content'] = wp_json_encode( $font_face ); $font_face_id = wp_insert_post( wp_slash( $args ) ); $file_urls = (array) ( isset( $font_face['src'] ) ? $font_face['src'] : array() ); foreach ( $file_urls as $file_url ) { // continue if the file is not local. if ( false === strpos( $file_url, site_url() ) ) { continue; } $relative_path = basename( $file_url ); update_post_meta( $font_face_id, '_wp_font_face_file', $relative_path ); } } // Update the font family post to remove the font face data. $args = array(); $args['ID'] = $font_family->ID; $args['post_title'] = isset( $font_family_json['name'] ) ? $font_family_json['name'] : ''; $args['post_name'] = sanitize_title( $font_family_json['slug'] ); unset( $font_family_json['name'] ); unset( $font_family_json['slug'] ); $args['post_content'] = wp_json_encode( $font_family_json ); wp_update_post( wp_slash( $args ) ); } update_option( 'gutenberg_font_family_format_converted', true ); } add_action( 'init', 'gutenberg_convert_legacy_font_family_format' );