updated plugin GP Premium version 2.5.0

This commit is contained in:
2024-10-09 12:44:25 +00:00
committed by Gitium
parent 627ec103fe
commit a35dc419bc
45 changed files with 2109 additions and 52 deletions

View File

@ -0,0 +1,134 @@
<?php
/**
* This file handles the Font Library CPT.
*
* @since 2.5.0
*
* @package GP Premium
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // No direct access, please.
}
/**
* Font library CPT class.
*/
class GeneratePress_Pro_Font_Library_CPT extends GeneratePress_Pro_Singleton {
/**
* Constructor.
*/
public function init() {
add_action( 'init', array( $this, 'register_cpt' ) );
}
/**
* Set up our custom post type.
*
* @since 2.5.0
*/
public function register_cpt() {
$labels = array(
'name' => _x( 'Fonts', 'Post Type General Name', 'gp-premium' ),
'singular_name' => _x( 'Font', 'Post Type Singular Name', 'gp-premium' ),
'menu_name' => __( 'Fonts', 'gp-premium' ),
'all_items' => __( 'All Fonts', 'gp-premium' ),
'add_new' => __( 'Add New Font', 'gp-premium' ),
'add_new_item' => __( 'Add New Font', 'gp-premium' ),
'new_item' => __( 'New Font', 'gp-premium' ),
'edit_item' => __( 'Edit Font', 'gp-premium' ),
'update_item' => __( 'Update Font', 'gp-premium' ),
'search_items' => __( 'Search Font', 'gp-premium' ),
'item_published' => __( 'Font published.', 'gp-premium' ),
'item_updated' => __( 'Font updated.', 'gp-premium' ),
'item_scheduled' => __( 'Font scheduled.', 'gp-premium' ),
'item_reverted_to_draft' => __( 'Font reverted to draft.', 'gp-premium' ),
);
$args = array(
'labels' => $labels,
'supports' => array( 'title', 'custom-fields' ),
'hierarchical' => false,
'public' => false,
'show_ui' => false,
'show_in_menu' => true,
'has_archive' => false,
'exclude_from_search' => true,
'show_in_rest' => true,
);
register_post_type( GeneratePress_Pro_Font_Library::FONT_LIBRARY_CPT, $args );
// Font variants.
register_post_meta(
GeneratePress_Pro_Font_Library::FONT_LIBRARY_CPT,
'gp_font_variants',
array(
'type' => 'array',
'show_in_rest' => false,
)
);
// Font family alias.
register_post_meta(
GeneratePress_Pro_Font_Library::FONT_LIBRARY_CPT,
'gp_font_family_alias',
array(
'type' => 'string',
'show_in_rest' => false,
)
);
// Font display value.
register_post_meta(
GeneratePress_Pro_Font_Library::FONT_LIBRARY_CPT,
'gp_font_display',
array(
'type' => 'string',
'show_in_rest' => false,
)
);
// Font source.
register_post_meta(
GeneratePress_Pro_Font_Library::FONT_LIBRARY_CPT,
'gp_font_source',
array(
'type' => 'string',
'show_in_rest' => false,
)
);
// Font family fallback.
register_post_meta(
GeneratePress_Pro_Font_Library::FONT_LIBRARY_CPT,
'gp_font_fallback',
array(
'type' => 'string',
'show_in_rest' => false,
)
);
// Font family preview.
register_post_meta(
GeneratePress_Pro_Font_Library::FONT_LIBRARY_CPT,
'gp_font_preview',
array(
'type' => 'string',
'show_in_rest' => false,
)
);
// Font family variable.
register_post_meta(
GeneratePress_Pro_Font_Library::FONT_LIBRARY_CPT,
'gp_font_variable',
array(
'type' => 'string',
'show_in_rest' => false,
)
);
}
}
GeneratePress_Pro_Font_Library_CPT::get_instance()->init();

View File

@ -0,0 +1,252 @@
<?php
/**
* Font Optimizations
*
* @since 2.5.0
*
* @package GP Premium
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // No direct access, please.
}
/**
* Font library class.
*/
class GeneratePress_Pro_Font_Library_Optimize extends GeneratePress_Pro_Singleton {
/**
* User Agent to be used to make requests to the Google Fonts API.
*/
const USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0';
/**
* Get the optimized font variants for download.
*
* @param array $font Array of data for the font to optimize.
* @param array $variants The variants to optimize.
*
* @return array The fonts object.
*/
public static function get_variants( $font, $variants ) {
$font_family = $font['name'] ?? '';
$slug = $font['slug'] ?? '';
$variants = wp_list_sort(
wp_list_sort( $variants, 'fontWeight', 'ASC' ),
'fontStyle',
'DESC'
);
$fonts_object = self::convert_to_fonts_object(
self::fetch_stylesheet(
$font_family,
$variants
)
);
return $fonts_object[ $slug ]['variants'] ?? $fonts_object;
}
/**
* Get the URL for the google fonts stylesheet to fetch.
*
* @param string $font_family The font-family name with no fallback.
* @param array $variants The list of variants to include.
* @return string
*/
private static function get_google_css_url( $font_family, $variants ) {
$encoded_name = str_replace( ' ', '+', $font_family );
$weights = wp_list_pluck( $variants, 'fontWeight' );
$styles = wp_list_pluck( $variants, 'fontStyle' );
$has_italics = in_array( 'italic', $styles, true );
// Build the URL.
$url = 'https://fonts.googleapis.com/css2?family=' . $encoded_name;
// If there's only one variant and it's regular, return the URL immediately.
$only_regular = count( $variants ) === 1 && 'normal' === $styles[0];
if ( $only_regular ) {
return $url;
}
if ( $has_italics ) {
$url .= ':ital,wght@';
} else {
$url .= ':wght@' . implode( ';', $weights );
return $url;
}
// If some variants are italic, build the weight string.
foreach ( $variants as $variant ) {
$is_italic = 'italic' === $variant['fontStyle'];
$first_value = $is_italic ? 1 : 0;
$url .= "{$first_value},{$variant['fontWeight']};";
}
return rtrim( $url, ';' );
}
/**
* Fetch Stylesheet.
*
* @param string $font_family The font-family name.
* @param array $variants The variants to optimize.
*
* @return string
*/
private static function fetch_stylesheet( $font_family, $variants ) {
$url = self::get_google_css_url( $font_family, $variants );
// Get the remote stylesheet.
$response = wp_remote_get(
$url,
array(
'user-agent' => self::USER_AGENT,
)
);
$code = wp_remote_retrieve_response_code( $response );
if ( 200 !== $code ) {
return '';
}
return wp_remote_retrieve_body( $response );
}
/**
* Parse the stylesheet and build it into a font object which OMGF can understand.
*
* @param string $stylesheet A valid CSS stylesheet.
*
* @return array
*/
private static function convert_to_fonts_object( $stylesheet ) {
preg_match_all( '/font-family:\s\'(.*?)\';/', $stylesheet, $font_families );
if ( empty( $font_families[1] ) ) {
return array();
}
$font_families = array_unique( $font_families[1] );
$object = array();
foreach ( $font_families as $font_family ) {
$slug = sanitize_title( $font_family );
$object[ $slug ] = array(
'slug' => $slug,
'fontFamily' => $font_family,
'variants' => self::parse_variants( $stylesheet, $font_family ),
'subsets' => self::parse_subsets( $stylesheet, $font_family ),
);
}
return $object;
}
/**
* Parse a stylesheet from Google Fonts' API into a valid Font Object.
*
* @param string $stylesheet The stylesheet to parse.
* @param string $font_family The font family used in the parse.
*
* @return array
*/
private static function parse_variants( $stylesheet, $font_family ) {
/**
* This also captures the commented Subset name.
*/
preg_match_all( '/\/\*\s.*?}/s', $stylesheet, $font_faces );
if ( empty( $font_faces[0] ) ) {
return array();
}
$font_object = array();
foreach ( $font_faces[0] as $font_face ) {
// Check for exact match of font-family.
if ( ! preg_match( '/font-family:[\s\'"]*?' . $font_family . '[\'"]?;/', $font_face ) ) {
continue;
}
preg_match( '/font-style:\s(normal|italic);/', $font_face, $font_style );
preg_match( '/font-weight:\s([0-9]+);/', $font_face, $font_weight );
preg_match( '/src:\surl\((.*?woff2)\)/', $font_face, $font_src );
preg_match( '/\/\*\s([a-z\-0-9\[\]]+?)\s\*\//', $font_face, $subset );
preg_match( '/unicode-range:\s(.*?);/', $font_face, $range );
$subset = ! empty( $subset[1] ) ? trim( $subset[1], '[]' ) : '';
/**
* Remove variants that have subset the user doesn't need.
*/
$allowed_subsets = apply_filters(
'generatepress_google_font_subsets',
GeneratePress_Pro_Font_Library::get_settings( 'preferred_subset' )
);
if ( empty( $allowed_subsets ) ) {
$allowed_subsets = array( 'latin' );
}
if ( ! empty( $subset ) && ! in_array( $subset, $allowed_subsets, true ) && ! is_numeric( $subset ) ) {
continue;
}
/**
* If $subset is empty, assume it's a logographic (Chinese, Japanese, etc.) character set.
*
* @TODO: Apply subset setting here.
*/
if ( is_numeric( $subset ) ) {
$subset = 'logogram-' . $subset;
}
$key = $subset . '-' . $font_weight[1] . ( 'normal' === $font_style[1] ? '' : '-' . $font_style[1] );
// Setup font object data.
$font_object[ $key ] = array(
'fontFamily' => $font_family,
'fontStyle' => $font_style[1],
'fontWeight' => $font_weight[1],
'src' => $font_src[1],
);
if ( ! empty( $subset ) ) {
$font_object[ $key ]['subset'] = $subset;
}
if ( ! empty( $range ) && isset( $range[1] ) ) {
$font_object[ $key ]['range'] = $range[1];
}
}
return $font_object;
}
/**
* Parse stylesheets for subsets, which in Google Fonts stylesheets are always
* included, commented above each @font-face statements, e.g. /* latin-ext
*
* @param string $stylesheet The stylesheet to parse.
* @param string $font_family The font family used in the parse.
*
* @return array
*/
private static function parse_subsets( $stylesheet, $font_family ) {
preg_match_all( '/\/\*\s([a-z\-]+?)\s\*\//', $stylesheet, $subsets );
if ( empty( $subsets[1] ) ) {
return array();
}
$subsets = array_unique( $subsets[1] );
return $subsets;
}
}

View File

@ -0,0 +1,579 @@
<?php
/**
* Rest API functions.
*
* @since 2.5.0
*
* @package GP Premium
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // No direct access, please.
}
/**
* Font library REST API endpoints class.
*/
class GeneratePress_Pro_Font_Library_Rest extends WP_REST_Controller {
/**
* Instance.
*
* @access private
* @var object Instance
*/
private static $instance;
/**
* Namespace.
*
* @var string
*/
protected $namespace = 'generatepress-font-library/v';
/**
* Version.
*
* @var string
*/
protected $version = '1';
/**
* Initiator.
*
* @return object initialized object of class.
*/
public static function get_instance() {
if ( ! isset( self::$instance ) ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* GenerateBlocks_Rest constructor.
*/
public function __construct() {
add_action( 'rest_api_init', array( $this, 'register_routes' ) );
}
/**
* Register rest routes.
*/
public function register_routes() {
$namespace = $this->namespace . $this->version;
// Get fonts from CPT.
register_rest_route(
$namespace,
'/get-fonts/',
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_fonts' ),
'permission_callback' => array( $this, 'edit_posts_permission' ),
)
);
// Download a Google font.
register_rest_route(
$namespace,
'/download-google-font/',
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'download_google_font' ),
'permission_callback' => array( $this, 'edit_posts_permission' ),
)
);
// Upload a font.
register_rest_route(
$namespace,
'/upload-fonts/',
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'upload_fonts' ),
'permission_callback' => array( $this, 'edit_posts_permission' ),
)
);
// Delete a font family.
register_rest_route(
$namespace,
'/delete-font/',
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'delete_font' ),
'permission_callback' => array( $this, 'edit_posts_permission' ),
)
);
// Get font library settings.
register_rest_route(
$namespace,
'/get-settings/',
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_settings' ),
'permission_callback' => array( $this, 'edit_posts_permission' ),
)
);
// Set font library settings.
register_rest_route(
$namespace,
'/set-settings/',
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'set_settings' ),
'permission_callback' => array( $this, 'edit_posts_permission' ),
)
);
register_rest_route(
$namespace,
'/optimize-google-fonts/',
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'optimize_google_fonts' ),
'permission_callback' => array( $this, 'edit_posts_permission' ),
)
);
register_rest_route(
$namespace,
'/update-font-post/',
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'update_font_post' ),
'permission_callback' => array( $this, 'edit_posts_permission' ),
)
);
}
/**
* Get font posts.
*
* @param WP_REST_Request $request The request object.
*
* @return array The response.
*/
public function get_fonts( WP_REST_Request $request ) {
$name = $request->get_param( 'name' );
$response = GeneratePress_Pro_Font_Library::get_fonts( $name );
return $this->success( $response );
}
/**
* Generate font CSS.
*
* @return mixed
*/
public function build_css_file() {
$result = GeneratePress_Pro_Font_Library::build_css_file();
if ( is_wp_error( $result ) ) {
return $this->error( 'font_css_generation_failed', __( 'Failed to generate font CSS.', 'gp-premium' ) );
}
return $this->success( $result );
}
/**
* Delete a specific font from the library and the associated CPT post.
*
* @param WP_REST_Request $request request object.
*
* @return mixed
*/
public function delete_font( WP_REST_Request $request ) {
$font_id = $request->get_param( 'fontId' );
$slug = get_post_field( 'post_name', $font_id );
$upload_dir = wp_get_upload_dir();
$font_base_path = trailingslashit( $upload_dir['basedir'] ) . 'generatepress/fonts/' . $slug . '/';
// Delete the font post.
$success = wp_delete_post( $font_id, true );
if ( ! $success ) {
return $this->error(
'font_post_delete_failed',
__( 'Failed to delete font post.', 'gp-premium' )
);
}
// Delete the font sub folder if it exists.
if ( file_exists( $font_base_path ) ) {
GeneratePress_Pro_Font_Library::delete_directory( $font_base_path );
}
// Regenerate the font CSS.
$this->build_css_file();
// Return success.
return $this->success( __( 'Font successfully deleted!', 'gp-premium' ) );
}
/**
* Download a specific Google font and update the CPT.
*
* @param WP_REST_Request $request request object.
*
* @return mixed
*/
public function optimize_google_fonts( WP_REST_Request $request ) {
$font = $request->get_param( 'font' ) ?? array();
$variants = $request->get_param( 'variants' ) ?? array();
if ( ! $font || ! $variants ) {
return $this->failed( 'No font or variants provided' );
}
$optimized_variants = GeneratePress_Pro_Font_Library_Optimize::get_variants( $font, $variants );
if ( $optimized_variants ) {
foreach ( $optimized_variants as $key => $optimized_variant ) {
foreach ( $variants as &$variant ) {
$style_match = $variant['fontStyle'] === $optimized_variant['fontStyle'];
$weight_match = $variant['fontWeight'] === $optimized_variant['fontWeight'];
if ( $style_match && $weight_match ) {
$variant['src'] = $optimized_variant['src'];
break;
}
}
}
}
return $this->success( $variants );
}
/**
* Check if a font post exists by slug and create it if it doesn't exist.
*
* @param array $variant The font variant to check.
* @param array $slug The font slug.
* @return mixed
*/
public static function get_font_post( $variant, $slug ) {
global $wpdb;
$font_post = $wpdb->get_row(
$wpdb->prepare(
"SELECT ID FROM $wpdb->posts WHERE post_name = %s AND post_type = %s",
$slug,
GeneratePress_Pro_Font_Library::FONT_LIBRARY_CPT
)
);
if ( $font_post ) {
return $font_post->ID;
}
$font_post = wp_insert_post(
array(
'post_title' => $variant['fontFamily'],
'post_name' => $slug,
'post_type' => GeneratePress_Pro_Font_Library::FONT_LIBRARY_CPT,
'post_status' => 'publish',
'wp_error' => true,
'meta_input' => array(
'gp_font_family_alias' => '',
'gp_font_variants' => array(),
'gp_font_display' => 'auto',
'gp_font_fallback' => '',
'gp_font_variable' => GeneratePress_Pro_Font_Library::CSS_VAR_PREFIX . $slug,
),
)
);
return $font_post;
}
/**
* Upload a specific custom font and update the CPT.
*
* @param WP_REST_Request $request request object.
*
* @return mixed
*/
public function upload_fonts( WP_REST_Request $request ) {
$font = $request->get_param( 'font' ) ?? array();
$variants = $request->get_param( 'variants' );
$source = $request->get_param( 'source' );
$slug = $request->get_param( 'slug' ) ?? $font['slug'] ?? '';
$results = array(
'ID' => null,
'variants' => array(),
);
// Tweaks variants based on the source if needed.
if ( 'custom' === $source ) {
// Decode the FormData sent via POST.
$variants = json_decode( $variants, true );
}
foreach ( $variants as $variant ) {
// Move the uploaded font asset from the temp folder to the fonts directory.
if ( ! function_exists( 'wp_handle_upload' ) ) {
require_once ABSPATH . 'wp-admin/includes/file.php';
}
$file = $variant['src'];
// If custom assume the font is being uploaded.
if ( 'custom' === $source ) {
$file_params = $request->get_file_params();
$file = $file_params[ $variant['src'] ] ?? $variant['src'];
}
$font_file = GeneratePress_Pro_Font_Library::handle_font_file_upload( $variant, $slug, $file );
if ( is_wp_error( $font_file ) ) {
$results['error'][] = array(
'font' => $variant,
'message' => $font_file->get_error_message(),
);
continue;
}
// Get the font post for this variant.
$font_post = self::get_font_post( $variant, $slug );
if ( is_wp_error( $font_post ) ) {
return $this->error( 500, __( 'Failed to create font post.', 'gp-premium' ) );
}
if ( 'google' === $source ) {
$font_family = explode( ', ', $font['fontFamily'] ?? '' );
// Remove the main font-family.
array_shift( $font_family );
// Set the fallback if we can infer one.
if ( $font_family ) {
$fallback = implode( ', ', $font_family );
update_post_meta( $font_post, 'gp_font_fallback', $fallback );
}
}
$existing_variants = get_post_meta( $font_post, 'gp_font_variants', true );
if ( ! is_array( $variants ) ) {
$existing_variants = array();
}
$checked_variants = GeneratePress_Pro_Font_Library::check_variants(
$existing_variants,
array(
'src' => $font_file['url'],
'fontFamily' => $variant['fontFamily'],
'fontStyle' => $variant['fontStyle'],
'fontWeight' => $variant['fontWeight'],
'name' => GeneratePress_Pro_Font_Library::get_variant_name( $variant ),
'isVariable' => $variant['isVariable'] ?? false,
'source' => 'custom',
'disabled' => false,
'preview' => '',
)
);
// Update the font post meta with merged variants.
update_post_meta( $font_post, 'gp_font_variants', $checked_variants );
// Generate the font CSS.
$generate_css = $this->build_css_file();
if ( false === $generate_css->success ) {
return $this->error( 500, __( 'CSS Generation failed', 'gp-premium' ) );
}
$results['ID'] = $font_post;
$results['variants'] = $checked_variants;
}
return $this->success( $results );
}
/**
* Get font library settings.
*
* @return mixed
*/
public function get_settings() {
return $this->success( get_option( 'gp_font_library_settings', array() ) );
}
/**
* Update font library settings.
*
* @param WP_REST_Request $request request object.
*
* @return mixed
*/
public function set_settings( WP_REST_Request $request ) {
$settings = $request->get_param( 'settings' );
$sanitized_settings = array();
foreach ( $settings as $setting => $value ) {
if ( 'google_gdpr' === $setting ) {
$sanitized_settings[ $setting ] = (bool) $value;
} elseif ( 'preferred_subset' === $setting ) {
// Stored as an array to support multiple preferred subsets in the future.
$sanitized_settings[ $setting ] = array( sanitize_text_field( $value ) );
} else {
$sanitized_settings[ $setting ] = sanitize_text_field( $value );
}
}
$updated = update_option(
GeneratePress_Pro_Font_Library::SETTINGS_OPTION,
$sanitized_settings,
false
);
if ( $updated ) {
// Return success.
return $this->success(
array(
'message' => __( 'Font settings successfully updated!', 'gp-premium' ),
'response' => $updated,
'settings' => $sanitized_settings,
)
);
}
return $this->failed(
array(
'message' => __( 'Failed to update font settings.', 'gp-premium' ),
'settings' => $sanitized_settings,
)
);
}
/**
* Update a font post.
*
* @param WP_REST_Request $request request object.
*
* @return mixed
*/
public function update_font_post( WP_REST_Request $request ) {
$font_id = $request->get_param( 'id' );
$status = $request->get_param( 'status' );
$font_family_alias = $request->get_param( 'alias' );
$new_variants = $request->get_param( 'newVariants' );
$delete_variants = $request->get_param( 'deleteVariants' );
$font_display = $request->get_param( 'fontDisplay' );
$fallback = $request->get_param( 'fallback' );
$css_variable = $request->get_param( 'cssVariable' );
$slug = get_post_field( 'post_name', $font_id );
// Update the font post.
wp_update_post(
array(
'ID' => $font_id,
'post_status' => $status,
'meta_input' => array(
'gp_font_family_alias' => $font_family_alias,
'gp_font_variants' => $new_variants,
'gp_font_display' => $font_display,
'gp_font_fallback' => $fallback,
'gp_font_variable' => $css_variable,
),
)
);
$upload_dir = wp_get_upload_dir();
$base_path = trailingslashit( $upload_dir['basedir'] ) . 'generatepress/fonts/' . $slug . '/';
foreach ( $delete_variants as $variant ) {
if ( isset( $variant['deleteStatus'] ) && $variant['deleteStatus'] ) {
$file_path = $base_path . basename( $variant['src'] );
if ( file_exists( $file_path ) ) {
unlink( $file_path );
}
}
}
// Regenerate the font CSS.
$this->build_css_file();
// Return success.
return $this->success( __( 'Font post successfully updated!', 'gp-premium' ) );
}
/**
* Get edit options permissions.
*
* @return bool
*/
public function update_settings_permission() {
return current_user_can( 'manage_options' );
}
/**
* Get edit posts permissions.
*
* @return bool
*/
public function edit_posts_permission() {
return current_user_can( 'edit_posts' );
}
/**
* Success rest.
*
* @param mixed $response response data.
* @param mixed $data data.
* @return mixed
*/
public function success( $response, $data = null ) {
return new WP_REST_Response(
array(
'success' => true,
'response' => $response,
'data' => $data,
),
200
);
}
/**
* Failed rest.
*
* @param mixed $response response data.
* @return mixed
*/
public function failed( $response ) {
return new WP_REST_Response(
array(
'success' => false,
'response' => $response,
),
200
);
}
/**
* Error rest.
*
* @param mixed $code error code.
* @param mixed $response response data.
* @return mixed
*/
public function error( $code, $response ) {
return new WP_REST_Response(
array(
'error' => true,
'success' => false,
'error_code' => $code,
'response' => $response,
),
500
);
}
}
GeneratePress_Pro_Font_Library_Rest::get_instance();

View File

@ -0,0 +1,840 @@
<?php
/**
* This file handles the Font Library.
*
* @since 2.5.0
*
* @package GP Premium
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // No direct access, please.
}
/**
* Font library class.
*/
class GeneratePress_Pro_Font_Library extends GeneratePress_Pro_Singleton {
const FONT_LIBRARY_CPT = 'gp_font';
const FONTS_MAX_QUERY = 100;
const CSS_VAR_PREFIX = '--gp-font--';
const SETTINGS_OPTION = 'gp_font_library_settings';
/**
* Constructor.
*/
public function init() {
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_font_css' ), 1 );
if ( ! is_admin() ) {
return;
}
add_filter( 'block_editor_settings_all', array( $this, 'add_fonts_to_editor' ) );
add_filter( 'generate_dashboard_tabs', array( $this, 'add_dashboard_tab' ) );
add_action( 'admin_menu', array( $this, 'add_menu' ), 100 );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
add_filter( 'generate_dashboard_screens', array( $this, 'add_dashboard_screen' ) );
add_action( 'admin_head', array( $this, 'add_head_tags' ), 0 );
add_action( 'import_post_meta', array( $this, 'update_post_meta' ), 100, 3 );
add_action( 'wp_import_existing_post', array( $this, 'maybe_font_exists' ), 10, 2 );
add_action( 'save_post_' . self::FONT_LIBRARY_CPT, array( $this, 'build_css_file' ), 100, 3 );
}
/**
* Add the Font Library tab to our Dashboard tabs.
*
* @param array $tabs Existing tabs.
* @return array New tabs.
*/
public function add_dashboard_tab( $tabs ) {
$screen = get_current_screen();
$tabs['Fonts'] = array(
'name' => __( 'Font Library', 'gp-premium' ),
'url' => self::get_font_library_uri(),
'class' => 'appearance_page_generatepress-font-library' === $screen->id ? 'active' : '',
'id' => 'gp-font-library-tab',
);
return $tabs;
}
/**
* Add our menu item.
*/
public function add_menu() {
add_submenu_page(
'themes.php',
__( 'Font Library', 'gp-premium' ),
__( 'Font Library', 'gp-premium' ),
'manage_options',
'generatepress-font-library',
array( $this, 'library_page' )
);
}
/**
* Add our page.
*/
public function library_page() {
echo '<div id="gp-font-library" class="gp-font-library gp-premium"></div>';
}
/**
* Add tags to the head element for the font library page.
*/
public function add_head_tags() {
$screen = get_current_screen();
$user_id = get_current_user_id();
$google_gdpr = (bool) self::get_settings( 'google_gdpr' );
// Stop if we're not on the right page or the user hasn't opted in to google fonts.
if ( 'appearance_page_generatepress-font-library' !== $screen->id || ! $google_gdpr ) {
return;
}
echo '
<link href="https://fonts.gstatic.com" rel="preconnect" crossorigin="anonymous" id="gp-preconnect-gstatic">
<link href="https://fonts.googleapis.com" rel="preconnect" id="gp-preconnect-google-api">
<link href="https://s.w.org" rel="preconnect" id="gp-preconnect-wp-cdn">
';
}
/**
* Add our scripts.
*/
public function enqueue_scripts() {
$screen = get_current_screen();
if ( 'appearance_page_generatepress-font-library' === $screen->id ) {
$assets = generate_premium_get_enqueue_assets( 'font-library' );
$upload_dir = wp_get_upload_dir();
wp_enqueue_script(
'generatepress-pro-font-library',
GP_PREMIUM_DIR_URL . 'dist/font-library.js',
$assets['dependencies'],
$assets['version'],
true
);
if ( function_exists( 'wp_set_script_translations' ) ) {
wp_set_script_translations( 'generatepress-pro-font-library', 'gp-premium', GP_PREMIUM_DIR_PATH . 'langs' );
}
wp_localize_script(
'generatepress-pro-font-library',
'gppFontLibrary',
array(
'uploadsUrl' => $upload_dir['baseurl'],
)
);
wp_enqueue_style(
'generatepress-pro-font-library',
GP_PREMIUM_DIR_URL . 'dist/font-library.css',
array( 'wp-components' ),
GP_PREMIUM_VERSION
);
}
}
/**
* Tell GeneratePress this is an admin page.
*
* @param array $screens Existing screens.
*/
public function add_dashboard_screen( $screens ) {
$screens[] = 'appearance_page_generatepress-font-library';
return $screens;
}
/**
* Get font posts.
*
* @param string $name font name.
*
* @return mixed
*/
public static function get_fonts( $name = null ) {
$args = array(
'post_type' => self::FONT_LIBRARY_CPT,
'post_status' => 'any',
'numberposts' => GeneratePress_Pro_Font_Library::FONTS_MAX_QUERY, // phpcs:ignore
'fields' => 'ids',
'no_found_rows' => true,
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
'order' => 'ASC',
);
if ( $name ) {
$args['name'] = $name;
}
$all_fonts = get_posts( $args );
$response = array();
if ( is_array( $all_fonts ) ) {
foreach ( $all_fonts as $font_post ) {
$font_name = get_the_title( $font_post );
$alias = get_post_meta( $font_post, 'gp_font_family_alias', true );
$slug = get_post_field( 'post_name', $font_post );
$status = get_post_status( $font_post );
$fallback = get_post_meta( $font_post, 'gp_font_fallback', true );
$preview = get_post_meta( $font_post, 'gp_font_preview', true );
$font_family = empty( $alias ) ? $font_name : $alias;
$font_family = "\"$font_family\"";
if ( $fallback ) {
$font_family .= ", $fallback";
}
// Setup the font data.
$response[] = array(
'id' => $font_post,
'name' => $font_name,
'fontFamily' => $font_family,
'disabled' => 'publish' !== $status,
'slug' => get_post_field( 'post_name', $font_post ),
'alias' => get_post_meta( $font_post, 'gp_font_family_alias', true ),
'variants' => get_post_meta( $font_post, 'gp_font_variants', true ),
'source' => get_post_meta( $font_post, 'gp_font_source', true ),
'fallback' => $fallback,
'fontDisplay' => get_post_meta( $font_post, 'gp_font_display', true ),
'preview' => empty( $preview ) ? '' : $preview,
'cssVariable' => get_post_meta( $font_post, 'gp_font_variable', true ),
);
}
return $response;
} else {
return array();
}
}
/**
* Get the font library URI.
*
* @return string
*/
public static function get_font_library_uri() {
return admin_url( 'themes.php?page=generatepress-font-library' );
}
/**
* Font format mappings.
*
* @param array $font Array of font data.
* @return string
*/
public static function get_font_face_rule( $font ) {
$css = '';
if ( ! empty( $font['variants'] ) ) {
$font_family = $font['alias'] ? $font['alias'] : $font['name'];
foreach ( $font['variants'] as $variant ) {
$is_disabled = $variant['disabled'] ?? false;
if ( $is_disabled ) {
continue;
}
$format = self::get_font_format( $variant['src'] );
$css .= "@font-face {
font-display: {$font['fontDisplay']};
font-family: \"$font_family\";
font-style: {$variant['fontStyle']};
font-weight: {$variant['fontWeight']};
src: url('{$variant['src']}')$format;
}\n";
}
}
return $css;
}
/**
* Font format mappings.
*
* @param string $font_url File extension.
* @return string|null
*/
private static function get_font_format( $font_url ) {
$extension = pathinfo( $font_url, PATHINFO_EXTENSION );
$format_map = array(
'woff' => 'woff',
'woff2' => 'woff2',
'ttf' => 'truetype',
'otf' => 'opentype',
);
$format_string = isset( $format_map[ $extension ] ) ? $format_map[ $extension ] : null;
return $format_string ? " format('$format_string')" : '';
}
/**
* Parses a font variant string to determine weight and style.
* Returns an array with 'weight', 'style'.
*
* @param string $variant Font variant string.
* @return array
*/
private static function parse_font_variant( $variant ) {
$weight = '400';
$style = 'normal';
if ( 'regular' === $variant ) {
return array(
'weight' => $weight,
'style' => $style,
);
}
if ( 'italic' === $variant || strpos( $variant, 'italic' ) !== false ) {
$style = 'italic';
if ( strpos( $variant, 'italic' ) !== false ) {
$variant = str_replace( 'italic', '', $variant );
}
}
return array(
'weight' => empty( $variant ) ? $weight : $variant,
'style' => $style,
);
}
/**
* Checks if the existing font variant exists.
*
* Overwrite it if it exists, and delete associated font file if different.
* Otherwise, add new variant if not found in the list.
*
* @param array $variants Font variants.
* @param int $new_variant New variant to be added.
* @param string $base_path Base path.
*
* @return array The resolved list of variants.
*/
public static function check_variants( $variants, $new_variant ) {
$checked_variants = $variants;
if ( empty( $variants ) ) {
return array( $new_variant );
}
$found = false;
foreach ( $variants as $key => $variant ) {
if ( $variant['name'] === $new_variant['name'] ) {
$checked_variants[ $key ] = $new_variant;
$found = true;
break;
}
}
if ( ! $found ) {
$checked_variants[] = $new_variant;
}
return $checked_variants;
}
/**
* Format uploaded font variant.
*
* @param array $variant Font variant.
* @return string The formatted variant name.
*/
public static function get_variant_name( $variant ) {
// Force variant to array-like structure.
$is_italic = 'italic' === $variant['fontStyle'];
$labels = array(
'100' => 'Thin 100',
'200' => 'ExtraLight 200',
'250' => 'ExtraLight 250',
'300' => 'Light 300',
'400' => 'Regular 400',
'regular' => 'Regular 400',
'500' => 'Medium 500',
'600' => 'SemiBold 600',
'700' => 'Bold 700',
'800' => 'ExtraBold 800',
'900' => 'Black 900',
);
$resolved_label = $labels[ $variant['fontWeight'] ];
if ( $resolved_label ) {
return $resolved_label . ( $is_italic ? ' Italic' : '' );
}
return str_replace( ' ', '-', $variant['fontWeight'] ) . ' ' . __( '(Variable)', 'gp-premium' );
}
/**
* Format a font file name to remove spaces, commas.
*
* @param string $name Font name.
* @return string
*/
public static function format_font_filename( $name ) {
// Replace spaces and commas in file name with hyphen.
$name = preg_replace( '/[ ,]/', '-', $name );
return $name;
}
/**
* Returns the expected mime-type values for font files, depending on PHP version.
*
* This is needed because font mime types vary by PHP version, so checking the PHP version
* is necessary until a list of valid mime-types for each file extension can be provided to
* the 'upload_mimes' filter.
*
* @return array A collection of mime types keyed by file extension.
*/
public static function get_allowed_font_mime_types() {
$php_7_ttf_mime_type = PHP_VERSION_ID >= 70300 ? 'application/font-sfnt' : 'application/x-font-ttf';
return array(
'otf' => 'application/vnd.ms-opentype',
'ttf' => PHP_VERSION_ID >= 70400 ? 'font/sfnt' : $php_7_ttf_mime_type,
'woff' => PHP_VERSION_ID >= 80112 ? 'font/woff' : 'application/font-woff',
'woff2' => PHP_VERSION_ID >= 80112 ? 'font/woff2' : 'application/font-woff2',
);
}
/**
* Get the font CSS file.
*
* @param string $type Type of path to return. Can return the `path` or `url` to the file.
* @return string
*/
public static function get_font_css_file( $type ) {
$upload_dir = wp_get_upload_dir();
$file_path = 'generatepress/fonts/fonts.css';
$base = '';
if ( 'url' === $type ) {
$base = $upload_dir['baseurl'];
} elseif ( 'path' === $type ) {
$base = $upload_dir['basedir'];
}
return $base ? trailingslashit( $base ) . $file_path : '';
}
/**
* Get the font CSS file URL.
*
* @return string
*/
public static function get_font_css_file_url() {
$css_file_url = self::get_font_css_file( 'url' );
$css_file_dir = self::get_font_css_file( 'path' );
return file_exists( $css_file_dir )
? $css_file_url
: '';
}
/**
* Add our font CSS.
*/
public function enqueue_font_css() {
$font_file_url = self::get_font_css_file_url();
// Enqueue the custom fonts CSS if the file exists.
if ( $font_file_url ) {
$version = filemtime( self::get_font_css_file( 'path' ) ) ?? GP_PREMIUM_VERSION;
wp_enqueue_style( 'generatepress-fonts', $font_file_url, array(), $version );
}
}
/**
* Add a font to the uploads directory either from $_FILES or a remote URL.
*
* @param array $variant Font variant object.
* @param string $slug Font slug.
* @param array|null $file Single file item from $_FILES or null.
* @return array|WP_Error Array containing uploaded file attributes on success, or WP_Error object on failure.
*/
public static function handle_font_file_upload( $variant, $slug, $file ) {
if ( ! $slug ) {
$slug = $variant['slug'] ?? '';
}
$upload_dir = wp_get_upload_dir();
$base_path = trailingslashit( $upload_dir['basedir'] ) . 'generatepress/fonts/' . $slug . '/';
// Ensure the directory exists.
if ( ! file_exists( $base_path ) ) {
wp_mkdir_p( $base_path );
}
/**
* If $file is an array, assume it's a param from $_FILES.
*/
if ( is_array( $file ) ) {
$file_name = basename( $file['name'] );
$file_path = $base_path . $file_name;
// Check if the font file exists and delete it if so.
if ( file_exists( $file_path ) ) {
unlink( $file_path );
}
$set_upload_dir = function ( $font_dir ) use ( $base_path, $slug ) {
$font_dir['path'] = $base_path;
$font_dir['url'] = untrailingslashit(
content_url( 'uploads/generatepress/fonts/' . $slug )
);
$font_dir['subdir'] = '';
return $font_dir;
};
add_filter( 'upload_mimes', array( __CLASS__, 'get_allowed_font_mime_types' ) );
add_filter( 'upload_dir', $set_upload_dir );
$overrides = array(
'upload_error_handler' => array( __CLASS__, 'handle_font_file_upload_error' ),
// Not testing a form submission.
'test_form' => false,
// Only allow uploading font files for this request.
'mimes' => self::get_allowed_font_mime_types(),
);
$uploaded_file = wp_handle_upload( $file, $overrides );
remove_filter( 'upload_dir', $set_upload_dir );
remove_filter( 'upload_mimes', array( __CLASS__, 'get_allowed_font_mime_types' ) );
return $uploaded_file;
}
$file_name = basename( $variant['src'] );
$file_path = $base_path . $file_name;
$response = wp_remote_get( $variant['src'] );
if ( is_wp_error( $response ) ) {
$error_message = $response->get_error_message();
return new WP_Error( 500, "Failed to download {$variant['fontFamily']} from {$variant['src']}: $error_message" );
}
// Save the file.
$filesystem = generate_premium_get_wp_filesystem();
if ( ! $filesystem ) {
return new WP_Error( 500, 'Error setting up the file system object.' );
}
$file_contents = wp_remote_retrieve_body( $response );
if ( ! $file_contents ) {
return new WP_Error( 500, "Failed to download $variant from {$variant['src']}: Empty body" );
}
// Assuming $filesystem is already set up correctly.
$chmod_file = defined( 'FS_CHMOD_FILE' ) ? FS_CHMOD_FILE : 0644;
if ( is_writable( $file_path ) || is_writable( dirname( $file_path ) ) ) {
if ( $filesystem->put_contents( $file_path, $file_contents, $chmod_file ) ) {
return array(
'file' => $file_path,
'url' => trailingslashit( $upload_dir['baseurl'] ) . 'generatepress/fonts/' . $slug . '/' . $file_name,
);
} else {
return new WP_Error( 500, "Failed to download $variant from {$variant['src']}." );
}
}
return new WP_Error( 500, 'Unable to write to file path.' );
}
/**
* Handles file upload error.
*
* @param array $file File upload data.
* @param string $message Error message from wp_handle_upload().
* @return WP_Error WP_Error object.
*/
public static function handle_font_file_upload_error( $file, $message ) {
$status = 500;
$code = 'rest_font_upload_unknown_error';
// Note: The absence of a text domain is intentional here as it's checking against a WP core string.
if ( __( 'Sorry, you are not allowed to upload this file type.' ) === $message ) {
$status = 400;
$code = 'rest_font_upload_invalid_file_type';
}
return new WP_Error( $code, $message, array( 'status' => $status ) );
}
/**
* Runs on wp_after_insert_post to download remote font files.
*
* @param int $post_id Post ID.
* @param string $key The meta key that was imported.
* @param mixed $value The meta value that was imported.
* @return void
*/
public function update_post_meta( $post_id, $key, $value ) {
$upload_dir = wp_get_upload_dir();
// Bail if we're not working with a font library post variant meta value.
if ( get_post_type( $post_id ) !== self::FONT_LIBRARY_CPT || 'gp_font_variants' !== $key ) {
return;
}
// Check the src of each variant and if the URL is remote, download the file.
$variants = $value;
// Stop here if variants aren't found.
if ( ! $variants ) {
return;
}
foreach ( $variants as &$variant ) {
$site_hostname = wp_parse_url( site_url(), PHP_URL_HOST );
// Bail if the variant src is already on this site.
if ( strpos( $variant['src'], $site_hostname ) !== false ) {
continue;
}
$font_slug = get_post_field( 'post_name', $post_id );
$font_dir = trailingslashit( $upload_dir['basedir'] ) . 'generatepress/fonts/' . $font_slug . '/';
$font_base_url = trailingslashit( $upload_dir['baseurl'] ) . 'generatepress/fonts/' . $font_slug . '/';
$response = wp_remote_get( $variant['src'] );
$response_code = (int) wp_remote_retrieve_response_code( $response );
if ( is_wp_error( $response ) || 200 !== $response_code ) {
continue;
}
$file_name = basename( $variant['src'] );
$file_path = $font_dir . $file_name;
// If the directory exists, remove it and it's contents.
if ( ! file_exists( $font_dir ) ) {
wp_mkdir_p( $font_dir );
}
// Setup filesystem.
$filesystem = generate_premium_get_wp_filesystem();
// Bail here if the filesystem can't initialize.
if ( ! $filesystem ) {
continue;
}
$file_contents = wp_remote_retrieve_body( $response );
// Bail if file contents are empty or not found.
if ( ! $file_contents ) {
continue;
}
$chmod_file = defined( 'FS_CHMOD_FILE' ) ? FS_CHMOD_FILE : 0644;
if ( is_writable( $file_path ) || is_writable( dirname( $file_path ) ) ) {
// Bail if the file can't be written.
if ( ! $filesystem->put_contents( $file_path, $file_contents, $chmod_file ) ) {
continue;
}
}
$variant['src'] = $font_base_url . $file_name;
}
// Update the meta value with the new src for each variant.
update_post_meta( $post_id, 'gp_font_variants', $variants );
}
/**
* Recursive function to delete a directory and its contents.
*
* @param string $dir directory path.
* @return bool
*/
public static function delete_directory( $dir ) {
if ( ! file_exists( $dir ) ) {
return true;
}
if ( ! is_dir( $dir ) ) {
return unlink( $dir );
}
foreach ( scandir( $dir ) as $item ) {
if ( '.' === $item || '..' === $item ) {
continue;
}
if ( ! self::delete_directory( $dir . DIRECTORY_SEPARATOR . $item ) ) {
return false;
}
}
return rmdir( $dir );
}
/**
* Check if the post exists by checking the title.
*
* @param bool $post_exists Unused. The default post_exists function value.
* @param array $font The font post array.
* @return int Post ID on success, 0 on failure.
*/
public function maybe_font_exists( $post_exists, $font ) {
/**
* The value of $font here is a post array from the XML import, not our standard
* font array. We need to check if the font exists by title.
*/
return post_exists( $font['post_title'] );
}
/**
* Get the CSS variables and values for each font-family.
*
* @return string The color palette variable CSS declaration.
*/
public static function get_css_variables() {
$fonts = self::get_fonts();
if ( ! $fonts ) {
return '';
}
$variables = ":root {\n";
foreach ( $fonts as $font ) {
if ( isset( $font['disabled'] ) && $font['disabled'] ) {
continue;
}
$variables .= sprintf(
"%s: %s;\n",
$font['cssVariable'],
$font['fontFamily']
);
}
$variables .= "}\n";
return $variables;
}
/**
* Add CSS variable definitions to the block editor.
*
* @param string $css The generated CSS for the stylesheet.
* @return void
**/
public function add_variable_definitions_to_editor( $css ) {
wp_add_inline_style( 'generateblocks-pro', self::get_css_variables() );
}
/**
* Build the font CSS file.
*
* @return string|WP_Error The file path on success, WP_Error on failure.
*/
public static function build_css_file() {
$generated_css = self::generate_font_css();
$upload_dir = wp_get_upload_dir();
// Save the generated font CSS to a file.
$base_path_dir = trailingslashit( $upload_dir['basedir'] ) . 'generatepress/fonts/';
$file_path = $base_path_dir . 'fonts.css';
$filesystem = generate_premium_get_wp_filesystem();
if ( ! $filesystem ) {
return new WP_Error( 500, __( 'Error setting up the file system object.', 'gp-premium' ) );
}
// Assuming $filesystem is already set up correctly.
$chmod_file = defined( 'FS_CHMOD_FILE' ) ? FS_CHMOD_FILE : 0644;
if ( empty( $generated_css ) ) {
if ( file_exists( $file_path ) ) {
$filesystem->delete( $file_path );
}
} else {
if ( is_writable( $file_path ) || is_writable( dirname( $file_path ) ) ) {
if ( ! $filesystem->put_contents( $file_path, $generated_css, $chmod_file ) ) {
return new WP_Error( 500, __( 'Failed to write Google font CSS to file.', 'gp-premium' ) );
}
}
}
return $file_path;
}
/**
* Generate font CSS.
*
* @return mixed
*/
public static function generate_font_css() {
$fonts = self::get_fonts();
$variables = self::get_css_variables();
$css = $variables . "\n";
if ( $fonts ) {
foreach ( $fonts as $font ) {
// Add the generated CSS.
$css .= self::get_font_face_rule( $font );
}
}
return apply_filters( 'generatepress_font_css', $css, $fonts );
}
/**
* Add the font CSS to the block editor.
*
* @param array $settings The block editor settings.
* @return array
*/
public function add_fonts_to_editor( $settings ) {
$font_file_url = self::get_font_css_file_url();
if ( ! $font_file_url ) {
return $settings;
}
$fonts_import = sprintf(
'@import url("%s");',
$font_file_url
);
$settings['styles'][] = array( 'css' => $fonts_import );
return $settings;
}
/**
* Get font library settings. At the moment this is just the Google GDPR setting.
*
* @param string $setting The setting to retrieve.
* @return mixed
*/
public static function get_settings( $setting = null ) {
$settings = get_option( self::SETTINGS_OPTION, array() );
if ( $setting ) {
return $settings[ $setting ] ?? null;
}
return $settings;
}
}
GeneratePress_Pro_Font_Library::get_instance()->init();