felixarntz/theme-boilerplate

View on GitHub
inc/library/fonts/class-fonts.php

Summary

Maintainability
C
1 day
Test Coverage
<?php
/**
 * Super_Awesome_Theme_Fonts class
 *
 * @package Super_Awesome_Theme
 * @license GPL-2.0-or-later
 * @link    https://super-awesome-author.org/themes/super-awesome-theme/
 */

/**
 * Class managing theme font settings.
 *
 * @since 1.0.0
 */
final class Super_Awesome_Theme_Fonts extends Super_Awesome_Theme_Theme_Component_Base {

    /**
     * Utility methods instance.
     *
     * @since 1.0.0
     * @var Super_Awesome_Theme_Font_Util
     */
    private $util;

    /**
     * Registered theme font groups as $identifier => $title pairs.
     *
     * @since 1.0.0
     * @var array
     */
    private $groups = array();

    /**
     * Registered theme fonts.
     *
     * @since 1.0.0
     * @var array
     */
    private $fonts = array();

    /**
     * Constructor.
     *
     * Sets the required dependencies.
     *
     * @since 1.0.0
     */
    public function __construct() {
        $this->util = new Super_Awesome_Theme_Font_Util();

        $this->require_dependency_class( 'Super_Awesome_Theme_Settings' );
        $this->require_dependency_class( 'Super_Awesome_Theme_Assets' );
        $this->require_dependency_class( 'Super_Awesome_Theme_Customizer' );
        $this->require_dependency_class( 'Super_Awesome_Theme_Font_Families' );
    }

    /**
     * Gets the value for a theme font setting.
     *
     * @since 1.0.0
     *
     * @param string $id Unique string identifier for this font setting.
     * @return array Value for the font setting, or empty string if font is not registered.
     */
    public function get( $id ) {
        if ( ! isset( $this->fonts[ $id ] ) ) {
            return array(
                'family'       => '',
                'weight'       => '400',
                'size'         => 1.0,
                'family_stack' => '',
            );
        }

        return $this->fonts[ $id ]->get_value();
    }

    /**
     * Registers a theme font.
     *
     * @since 1.0.0
     *
     * @param Super_Awesome_Theme_Font $font Font to register.
     * @return bool True on success, false on failure.
     */
    public function register_font( Super_Awesome_Theme_Font $font ) {
        $id = $font->get_prop( Super_Awesome_Theme_Font::PROP_ID );

        if ( isset( $this->fonts[ $id ] ) ) {
            return false;
        }

        $this->fonts[ $id ] = $font;

        $settings = $this->get_dependency( 'settings' );
        $settings->register_setting( $font->get_setting() );

        return true;
    }

    /**
     * Registers a theme font group.
     *
     * @since 1.0.0
     *
     * @param string $id    Unique string identifier for this font group.
     * @param string $title Title for the font group.
     * @return bool True on success, false on failure.
     */
    public function register_group( $id, $title ) {
        if ( isset( $this->groups[ $id ] ) ) {
            return false;
        }

        $this->groups[ $id ] = $title;

        return true;
    }

    /**
     * Registers a callback that should print CSS rules for font style.
     *
     * The callback receives the `Super_Awesome_Theme_Fonts` instance
     * as sole parameter.
     *
     * @since 1.0.0
     *
     * @param callable $callback Font style callback.
     */
    public function register_font_style_callback( $callback ) {
        add_action( 'super_awesome_theme_font_style', $callback, 10, 1 );
    }

    /**
     * Returns the font utility methods instance.
     *
     * @since 1.0.0
     *
     * @return Super_Awesome_Theme_Font_Util Utility methods instance.
     */
    public function util() {
        return $this->util;
    }

    /**
     * Magic call method.
     *
     * Handles the Customizer registration action hook callbacks.
     *
     * @since 1.0.0
     *
     * @param string $method Method name.
     * @param array  $args   Method arguments.
     *
     * @throws BadMethodCallException Thrown when method name is invalid.
     */
    public function __call( $method, $args ) {
        switch ( $method ) {
            case 'load_webfonts':
            case 'print_font_style':
            case 'add_block_editor_font_style':
            case 'print_font_style_css':
            case 'register_font_customize_control':
            case 'register_customize_partial':
            case 'register_customize_controls_js':
            case 'register_base_fonts_general':
            case 'print_base_font_style_general':
                return call_user_func_array( array( $this, $method ), $args );
            default:
                $font_families = $this->get_dependency( 'font_families' );
                foreach ( $font_families->get_webfont_apis() as $api ) {
                    $slug = $api->get_slug();

                    if ( 'partial_webfont_loader_' . $slug === $method ) {
                        $webfonts = $this->group_webfonts( $this->fonts );
                        $fonts    = isset( $webfonts[ $slug ] ) ? $webfonts[ $slug ] : array();

                        $this->load_webfonts_for_api( $api, $fonts );
                        return;
                    }
                }

                /* translators: %s: method name */
                throw new BadMethodCallException( sprintf( __( 'Call to undefined method %s', 'super-awesome-theme' ), __CLASS__ . '::' . $method . '()' ) );
        }
    }

    /**
     * Loads the web fonts needed.
     *
     * @since 1.0.0
     */
    protected function load_webfonts() {
        $font_families = $this->get_dependency( 'font_families' );

        $webfonts = $this->group_webfonts( $this->fonts );

        foreach ( $font_families->get_webfont_apis() as $api ) {
            $slug  = $api->get_slug();
            $fonts = isset( $webfonts[ $slug ] ) ? $webfonts[ $slug ] : array();

            $this->load_webfonts_for_api( $api, $fonts );
        }
    }

    /**
     * Loads given web fonts for a given API.
     *
     * @since 1.0.0
     *
     * @param Super_Awesome_Theme_Webfont_API $api   Web font API instance.
     * @param array                           $fonts List of fonts to load.
     */
    protected function load_webfonts_for_api( $api, array $fonts ) {
        $id_attr = 'super-awesome-theme-' . $api->get_slug() . '-fonts-css';

        $api->load_fonts( $id_attr, $fonts );
    }

    /**
     * Groups font values by their web font API.
     *
     * Fonts that aren't web fonts are excluded.
     *
     * @since 1.0.0
     *
     * @param array $fonts List of Super_Awesome_Theme_Font instances.
     * @return array Associative array of $api_slug => $values pairs, where $values is a list of
     *               associative arrays containing a `family` key with the font family instance,
     *               and a `weight` key with the desired font weight.
     */
    protected function group_webfonts( $fonts ) {
        $font_families = $this->get_dependency( 'font_families' );

        $webfonts = array();

        foreach ( $fonts as $font ) {
            $value = $font->get_value();

            if ( ! strpos( $value['family'], ':' ) ) {
                continue;
            }

            list( $api_slug, $family ) = explode( ':', $value['family'], 2 );

            if ( ! isset( $webfonts[ $api_slug ] ) ) {
                $webfonts[ $api_slug ] = array();
            }

            $webfonts[ $api_slug ][] = array(
                'family' => $font_families->get_registered_family( $value['family'] ),
                'weight' => $value['weight'],
            );
        }

        return $webfonts;
    }

    /**
     * Prints the font style tag.
     *
     * @since 1.0.0
     */
    protected function print_font_style() {
        ?>
        <style id="super-awesome-theme-font-style" type="text/css">

            <?php $this->print_font_style_css(); ?>

        </style>
        <?php
    }

    /**
     * Adds the font style CSS definitions to the block editor as inline CSS.
     *
     * @since 1.0.0
     */
    protected function add_block_editor_font_style() {
        $assets     = $this->get_dependency( 'assets' );
        $stylesheet = $assets->get_registered_asset( 'super-awesome-theme-block-editor-style' );

        ob_start();
        $this->print_font_style_css();
        $style = ob_get_clean();

        $style = preg_replace( '/^(\s*)([a-z0-9\-\.\#\[\]"=: >+]+)(,| \{)$/mi', '$1.edit-post-visual-editor $2$3', $style );

        $stylesheet->add_inline_style( $style, 'after' );
    }

    /**
     * Prints the font style CSS definitions.
     *
     * @since 1.0.0
     */
    protected function print_font_style_css() {

        /**
         * Fires when the custom font styles are printed inside a style tag.
         *
         * @since 1.0.0
         *
         * @param Super_Awesome_Theme_Fonts $fonts The theme fonts instance.
         */
        do_action( 'super_awesome_theme_font_style', $this );
    }

    /**
     * Registers the font Customize control type.
     *
     * @since 1.0.0
     *
     * @param WP_Customize_Manager $wp_customize Customize manager instance.
     */
    protected function register_font_customize_control( $wp_customize ) {
        require get_template_directory() . '/inc/library/fonts/class-font-customize-control.php';

        $wp_customize->register_control_type( 'Super_Awesome_Theme_Font_Customize_Control' );
    }

    /**
     * Registers Customizer partial.
     *
     * @since 1.0.0
     *
     * @param Super_Awesome_Theme_Customizer $customizer Customizer instance.
     */
    protected function register_customize_partial( $customizer ) {
        $partial_fonts     = array();
        $has_default_group = false;
        foreach ( $this->fonts as $id => $font ) {
            if ( $font->get_prop( Super_Awesome_Theme_Font::PROP_LIVE_PREVIEW ) ) {
                $partial_fonts[] = $id;
            }
            if ( 'fonts' === $font->get_prop( Super_Awesome_Theme_Font::PROP_GROUP ) ) {
                $has_default_group = true;
            }
        }

        $groups = $this->groups;
        if ( $has_default_group ) {
            $groups['fonts'] = __( 'Other Fonts', 'super-awesome-theme' );
        }

        if ( ! empty( $groups ) ) {
            if ( count( $groups ) === 1 ) {
                $customizer->add_section( key( $groups ), array(
                    Super_Awesome_Theme_Customize_Section::PROP_TITLE    => __( 'Fonts', 'super-awesome-theme' ),
                    Super_Awesome_Theme_Customize_Section::PROP_PRIORITY => 41,
                ) );
            } else {
                $customizer->add_panel( 'fonts', array(
                    Super_Awesome_Theme_Customize_Panel::PROP_TITLE    => __( 'Fonts', 'super-awesome-theme' ),
                    Super_Awesome_Theme_Customize_Panel::PROP_PRIORITY => 41,
                ) );

                foreach ( $groups as $id => $title ) {
                    $customizer->add_section( $id, array(
                        Super_Awesome_Theme_Customize_Section::PROP_PANEL => 'fonts',
                        Super_Awesome_Theme_Customize_Section::PROP_TITLE => $title,
                    ) );
                }
            }
        }

        if ( ! empty( $partial_fonts ) ) {
            $customizer->add_partial( 'super_awesome_theme_font_style', array(
                Super_Awesome_Theme_Customize_Partial::PROP_SETTINGS            => $partial_fonts,
                Super_Awesome_Theme_Customize_Partial::PROP_SELECTOR            => '#super-awesome-theme-font-style',
                Super_Awesome_Theme_Customize_Partial::PROP_RENDER_CALLBACK     => array( $this, 'print_font_style_css' ),
                Super_Awesome_Theme_Customize_Partial::PROP_CONTAINER_INCLUSIVE => false,
                Super_Awesome_Theme_Customize_Partial::PROP_FALLBACK_REFRESH    => false,
            ) );

            $font_families = $this->get_dependency( 'font_families' );
            foreach ( $font_families->get_webfont_apis() as $api ) {
                $slug = $api->get_slug();

                $customizer->add_partial( 'super_awesome_theme_webfont_loader_' . $slug, array(
                    Super_Awesome_Theme_Customize_Partial::PROP_SETTINGS            => $partial_fonts,
                    Super_Awesome_Theme_Customize_Partial::PROP_SELECTOR            => '#super-awesome-theme-' . $slug . '-fonts-css',
                    Super_Awesome_Theme_Customize_Partial::PROP_RENDER_CALLBACK     => array( $this, 'partial_webfont_loader_' . $slug ),
                    Super_Awesome_Theme_Customize_Partial::PROP_CONTAINER_INCLUSIVE => true,
                    Super_Awesome_Theme_Customize_Partial::PROP_FALLBACK_REFRESH    => false,
                ) );
            }
        }
    }

    /**
     * Registers scripts for the Customizer controls.
     *
     * @since 1.0.0
     *
     * @param Super_Awesome_Theme_Assets $assets Assets instance.
     */
    protected function register_customize_controls_js( $assets ) {
        $data = array(
            'groups'           => array(),
            'fonts'            => array(),
            'fontFamilyGroups' => array(),
            'fontFamilies'     => array(),
            'fontWeights'      => array(),
            'apis'             => array(),
        );
        foreach ( $this->groups as $id => $title ) {
            $data['groups'][] = array(
                'id'    => $id,
                'title' => $title,
            );
        }

        foreach ( $this->fonts as $font ) {
            $data['fonts'][] = $font->get_props();
        }

        $font_families = $this->get_dependency( 'font_families' );

        foreach ( $font_families->get_groups() as $id => $label ) {
            $data['fontFamilyGroups'][] = array(
                'id'    => $id,
                'label' => $label,
            );
        }

        foreach ( $font_families->get_registered_families() as $family ) {
            $family_data                = $family->get_props();
            $family_data['stackString'] = $family->get_stack_string();

            $data['fontFamilies'][] = $family_data;
        }

        foreach ( $font_families->get_weight_choices() as $id => $label ) {
            $data['fontWeights'][] = array(
                'id'    => $id,
                'label' => $label,
            );
        }

        foreach ( $font_families->get_webfont_apis() as $api ) {
            $data['apis'][ $api->get_slug() ] = $api->get_title();
        }

        // This workaround ensures the selectWoo assets are properly enqueued.
        $control = new Super_Awesome_Theme_Font_Customize_Control( $GLOBALS['wp_customize'], 'temp', array(
            'settings' => array(),
        ) );
        $control->enqueue();

        $assets->register_asset( new Super_Awesome_Theme_Script(
            'super-awesome-theme-fonts-customize-controls',
            get_theme_file_uri( '/assets/dist/js/fonts.customize-controls.js' ),
            array(
                Super_Awesome_Theme_Script::PROP_DEPENDENCIES => array( 'customize-controls', 'wp-i18n', 'underscore', 'selectWoo' ),
                Super_Awesome_Theme_Script::PROP_VERSION      => SUPER_AWESOME_THEME_VERSION,
                Super_Awesome_Theme_Script::PROP_LOCATION     => Super_Awesome_Theme_Script::LOCATION_CUSTOMIZE_CONTROLS,
                Super_Awesome_Theme_Script::PROP_MIN_URI      => true,
                Super_Awesome_Theme_Script::PROP_DATA_NAME    => 'themeFontsControlsData',
                Super_Awesome_Theme_Script::PROP_DATA         => $data,
            )
        ) );
    }

    /**
     * Registers the general base fonts.
     *
     * @since 1.0.0
     */
    protected function register_base_fonts_general() {
        $this->register_group( 'general_fonts', __( 'General Fonts', 'super-awesome-theme' ) );

        $this->register_font( new Super_Awesome_Theme_Font( 'base_font', array(
            Super_Awesome_Theme_Font::PROP_GROUP   => 'general_fonts',
            Super_Awesome_Theme_Font::PROP_TITLE   => __( 'Base Font', 'super-awesome-theme' ),
            Super_Awesome_Theme_Font::PROP_DEFAULT => array(
                'family' => 'libre_franklin',
                'weight' => '400',
                'size'   => 1.0,
            ),
        ) ) );

        $this->register_font( new Super_Awesome_Theme_Font( 'heading_font', array(
            Super_Awesome_Theme_Font::PROP_GROUP   => 'general_fonts',
            Super_Awesome_Theme_Font::PROP_TITLE   => __( 'Heading Font', 'super-awesome-theme' ),
            Super_Awesome_Theme_Font::PROP_DEFAULT => array(
                'family' => 'libre_franklin',
                'weight' => '700',
                'size'   => 1.25,
            ),
        ) ) );

        $this->register_font_style_callback( array( $this, 'print_base_font_style_general' ) );
    }

    /**
     * Prints general font styles.
     *
     * @since 1.0.0
     */
    protected function print_base_font_style_general() {
        $base_font    = $this->get( 'base_font' );
        $heading_font = $this->get( 'heading_font' );

        if ( ! empty( $base_font['family_stack'] ) ) {
            ?>
            body,
            button,
            input,
            select,
            textarea {
                font-family: <?php echo str_replace( '&quot;', '"', esc_attr( $base_font['family_stack'] ) ); /* phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped */ ?>;
                font-weight: <?php echo esc_attr( $base_font['weight'] ); ?>;
                font-size: <?php echo esc_attr( '' . $base_font['size'] . 'rem' ); ?>;
            }
            <?php
        }

        if ( ! empty( $heading_font['family_stack'] ) ) {
            ?>
            h1,
            h2,
            h3,
            h4,
            h5,
            h6 {
                font-family: <?php echo str_replace( '&quot;', '"', esc_attr( $heading_font['family_stack'] ) ); /* phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped */ ?>;
                font-weight: <?php echo esc_attr( $heading_font['weight'] ); ?>;
            }

            <?php
            $sizes = array(
                'h1' => '' . ( 1.2 * $heading_font['size'] ) . 'rem',
                'h2' => '' . ( 1.0 * $heading_font['size'] ) . 'rem',
                'h3' => '' . ( 0.9 * $heading_font['size'] ) . 'rem',
                'h4' => '' . ( 0.8 * $heading_font['size'] ) . 'rem',
                'h5' => '' . ( 0.7 * $heading_font['size'] ) . 'rem',
                'h6' => '' . ( 0.6 * $heading_font['size'] ) . 'rem',
            );
            foreach ( $sizes as $heading_level => $size ) {
                ?>
                <?php echo esc_attr( $heading_level ); ?> {
                    font-size: <?php echo esc_attr( $size ); ?>;
                }
                <?php
            }
        }
    }

    /**
     * Adds hooks and runs other processes required to initialize the component.
     *
     * @since 1.0.0
     */
    protected function run_initialization() {
        add_action( 'after_setup_theme', array( $this, 'register_base_fonts_general' ), 5, 0 );
        add_action( 'wp_head', array( $this, 'load_webfonts' ), 0, 0 );
        add_action( 'wp_head', array( $this, 'print_font_style' ), 10, 0 );
        add_action( 'enqueue_block_editor_assets', array( $this, 'add_block_editor_font_style' ), 0, 0 );
        add_action( 'customize_register', array( $this, 'register_font_customize_control' ), 0 );

        $customizer = $this->get_dependency( 'customizer' );
        $customizer->on_init( array( $this, 'register_customize_partial' ) );
        $customizer->on_js_controls_init( array( $this, 'register_customize_controls_js' ) );
    }
}