felixarntz/wp-site-identity

View on GitHub
src/class-wp-site-identity-bootstrap-customizer.php

Summary

Maintainability
D
2 days
Test Coverage
<?php
/**
 * WP_Site_Identity_Bootstrap_Customizer class
 *
 * @package WPSiteIdentity
 * @since 1.0.0
 */

/**
 * Bootstrap class to register the plugin's Customizer content.
 *
 * @since 1.0.0
 */
final class WP_Site_Identity_Bootstrap_Customizer {

    /**
     * Plugin bootstrap instance.
     *
     * @since 1.0.0
     * @var WP_Site_Identity_Bootstrap
     */
    private $bootstrap;

    /**
     * Plugin instance.
     *
     * @since 1.0.0
     * @var WP_Site_Identity
     */
    private $plugin;

    /**
     * Constructor.
     *
     * @since 1.0.0
     *
     * @param WP_Site_Identity_Bootstrap $bootstrap Plugin bootstrap instance.
     * @param WP_Site_Identity           $plugin    Plugin instance.
     */
    public function __construct( WP_Site_Identity_Bootstrap $bootstrap, WP_Site_Identity $plugin ) {
        $this->bootstrap = $bootstrap;
        $this->plugin    = $plugin;
    }

    /**
     * Action to register the plugin's Customizer content.
     *
     * @since 1.0.0
     *
     * @param WP_Customize_Manager $wp_customize Customize manager instance.
     */
    public function action_customize_register( $wp_customize ) {
        $setting_registry = $this->plugin->services()->get( 'setting_registry' );
        $panel_slug       = 'site_identity';

        $wp_customize->add_panel( $setting_registry->prefix( $panel_slug ), array(
            'title'    => __( 'Site Identity', 'wp-site-identity' ),
            'priority' => 20,
        ) );

        $title_tagline_section = $wp_customize->get_section( 'title_tagline' );
        if ( $title_tagline_section ) {
            if ( current_theme_supports( 'custom-logo' ) ) {
                $title_tagline_section->title = __( 'Title and Logo', 'wp-site-identity' );
            } else {
                $title_tagline_section->title = __( 'Title and Tagline', 'wp-site-identity' );
            }

            $title_tagline_section->panel = $setting_registry->prefix( $panel_slug );
        }

        $this->add_owner_data_content( $wp_customize, $setting_registry, $panel_slug );
        $this->add_brand_data_content( $wp_customize, $setting_registry, $panel_slug );

        /**
         * Fires when additional Customizer content for the plugin can be registered.
         *
         * @since 1.0.0
         *
         * @param WP_Customize_Manager $wp_customize Customize manager instance.
         */
        do_action( 'wp_site_identity_register_customizer_content', $wp_customize );
    }

    /**
     * Action to enqueue the plugin's Customizer controls script.
     *
     * @since 1.0.0
     */
    public function action_customize_controls_enqueue_scripts() {
        $min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';

        wp_enqueue_script( 'wpsi-customize-controls', $this->plugin->url( "assets/dist/js/customize-controls{$min}.js" ), array( 'customize-controls' ), $this->plugin->version(), true );
        wp_localize_script( 'wpsi-customize-controls', 'wpsiCustomizeControls', array(
            'typeDependencies' => $this->bootstrap->get_type_dependencies(),
        ) );
    }

    /**
     * Action to enqueue the plugin's Customizer preview script.
     *
     * @since 1.0.0
     */
    public function action_customize_preview_init() {
        $min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';

        wp_enqueue_script( 'wpsi-customize-preview', $this->plugin->url( "assets/dist/js/customize-preview{$min}.js" ), array( 'customize-preview', 'customize-selective-refresh' ), $this->plugin->version(), true );
        wp_localize_script( 'wpsi-customize-preview', 'wpsiCustomizePreview', array(
            'selectorTemplate' => '.' . $this->plugin->owner_data()->get_css_class( '%name%' ),
            'liveSettings'     => array(
                'first_name',
                'last_name',
                'organization_name',
                'organization_legal_name',
                'address_line_1',
                'address_line_2',
                'address_city',
                'address_zip',
                'address_state',
                'address_state_abbrev',
                'address_country',
                'address_country_abbrev',
                'email',
                'website',
                'phone',
                'phone_human',
            ),
            'cssProperties'    => $this->plugin->get_theme_support( 'css_properties' ),
        ) );
    }

    /**
     * Action to add inline stylesheets if the theme supports them.
     *
     * If the theme supports custom CSS properties (via 'css_properties'), a custom style tag is printed
     * which sets these custom properties on the :root element.
     *
     * If the theme includes a CSS callback (via 'css_callback'), that callback is called and receives the ID attribute
     * to use and the array of $color_slug => $hex_code pairs as parameter.
     *
     * @since 1.0.0
     */
    public function action_wp_head() {
        $colors         = $this->plugin->brand_data()->get( 'colors' );
        $css_properties = $this->plugin->get_theme_support( 'css_properties' );
        $css_callback   = $this->plugin->get_theme_support( 'css_callback' );

        if ( $css_properties ) {
            $custom_css_properties = array_filter( array_combine( $css_properties, $colors ) );

            ?>
            <style id="wpsi-custom-css-properties" type="text/css">
                :root {
                    <?php foreach ( $custom_css_properties as $property_name => $color ) : ?>
                        --<?php echo esc_attr( $property_name ); ?>: <?php echo esc_attr( $color ); ?>;
                    <?php endforeach; ?>
                }
            </style>
            <?php
        }

        if ( $css_callback ) {
            call_user_func( $css_callback, 'wpsi-custom-css-rules', $colors );
        }
    }

    /**
     * Magic call method.
     *
     * Acts as a dynamic shortcode render callback.
     *
     * @since 1.0.0
     *
     * @param string $method Method name.
     * @param array  $args   Method arguments.
     * @return mixed Method results.
     */
    public function __call( $method, $args ) {
        $is_callback = preg_match( '/^(validate|sanitize|partial)_callback_([a-z0-9_]+)_setting_([a-z0-9_]+)$/U', $method, $matches );

        if ( ! $is_callback ) {
            return;
        }

        $callback_type          = $matches[1];
        $aggregate_setting_name = $matches[2];
        $setting_name           = $matches[3];

        switch ( $callback_type ) {
            case 'validate':
                return $this->validate_setting( $args[0], $args[1], $aggregate_setting_name, $setting_name );
            case 'sanitize':
                return $this->sanitize_setting( $args[0], $aggregate_setting_name, $setting_name );
            case 'partial':
                $this->print_partial( $aggregate_setting_name, $setting_name );
                break;
        }
    }

    /**
     * Adds owner data content to the Customizer.
     *
     * @since 1.0.0
     *
     * @param WP_Customize_Manager              $wp_customize     Customize manager instance.
     * @param WP_Site_Identity_Setting_Registry $setting_registry Setting registry instance.
     * @param string                            $panel_slug       Slug of the plugin's Customizer panel.
     */
    private function add_owner_data_content( $wp_customize, $setting_registry, $panel_slug ) {
        $registry     = $setting_registry->get_setting( 'owner_data' );
        $data         = $this->plugin->owner_data();
        $section_slug = 'owner_data';

        $wp_customize->add_section( $setting_registry->prefix( $section_slug ), array(
            'title'      => __( 'Owner Data', 'wp-site-identity' ),
            'capability' => 'manage_options',
            'panel'      => $setting_registry->prefix( $panel_slug ),
            'priority'   => 30,
        ) );

        $address_fields = array(
            'address_line_1'         => '',
            'address_line_2'         => '',
            'address_city'           => '',
            'address_zip'            => '',
            'address_state'          => '',
            'address_state_abbrev'   => '',
            'address_country'        => '',
            'address_country_abbrev' => '',
            'address_format_single'  => '',
            'address_format_multi'   => '',
        );

        $sections = $this->bootstrap->get_owner_data_sections();

        $priority = 0;

        foreach ( $sections as $section ) {
            $priority += 10;

            foreach ( $section['fields'] as $field ) {
                $setting          = $registry->get_setting( $field );
                $setting_basename = $setting->get_name();

                $setting_name = $registry->prefix( $registry->get_name() ) . '[' . $setting_basename . ']';

                $validate_callback = 'validate_callback_' . $registry->get_name() . '_setting_' . $setting_basename;
                $sanitize_callback = 'sanitize_callback_' . $registry->get_name() . '_setting_' . $setting_basename;
                $partial_callback  = 'partial_callback_' . $registry->get_name() . '_setting_' . $setting_basename;

                $setting_args = array(
                    'type'              => 'option',
                    'capability'        => 'manage_options',
                    'default'           => $setting->get_default(),
                    'transport'         => 'postMessage',
                    'validate_callback' => array( $this, $validate_callback ),
                    'sanitize_callback' => array( $this, $sanitize_callback ),
                );

                $wp_customize->add_setting( $setting_name, $setting_args );

                $control_args = array(
                    'label'    => $setting->get_title(),
                    'section'  => $setting_registry->prefix( $section_slug ),
                    'priority' => $priority,
                );

                $setting_description = $setting->get_description();
                if ( ! empty( $setting_description ) ) {
                    $control_args['description'] = $setting_description;
                }

                $control_args = $this->set_default_control_args_for_setting( $control_args, $setting );

                if ( isset( $address_fields[ $setting_basename ] ) ) {
                    $address_fields[ $setting_basename ] = $setting_name;
                }

                $control = new WP_Customize_Control( $wp_customize, $setting_name, $control_args );

                $wp_customize->add_control( $control );

                $wp_customize->selective_refresh->add_partial( $registry->prefix( $setting_basename ), array(
                    'settings'            => array( $setting_name ),
                    'selector'            => '.' . $data->get_css_class( $setting_basename ),
                    'render_callback'     => array( $this, $partial_callback ),
                    'container_inclusive' => true,
                    'fallback_refresh'    => false,
                    'type'                => 'WPSiteIdentityPartial',
                ) );
            }
        }

        foreach ( array( 'phone', 'email', 'website' ) as $field ) {
            $partial_settings = array( $registry->prefix( $registry->get_name() ) . '[' . $field . ']' );
            if ( 'phone' === $field ) {
                $partial_settings[] = $registry->prefix( $registry->get_name() ) . '[phone_human]';
            }

            $wp_customize->selective_refresh->add_partial( $registry->prefix( $field . '_link' ), array(
                'settings'            => $partial_settings,
                'selector'            => '.' . $data->get_css_class( $field . '_link' ),
                'render_callback'     => array( $this, 'partial_callback_' . $registry->get_name() . '_setting_' . $field . '_link' ),
                'container_inclusive' => true,
                'fallback_refresh'    => false,
                'type'                => 'WPSiteIdentityPartial',
            ) );
        }

        $address_single_settings = $address_fields;
        $address_multi_settings  = $address_fields;

        unset( $address_single_settings['address_format_multi'] );
        unset( $address_multi_settings['address_format_single'] );

        $address_single_settings = array_values( $address_single_settings );
        $address_multi_settings  = array_values( $address_multi_settings );

        $wp_customize->selective_refresh->add_partial( $registry->prefix( 'address_single' ), array(
            'settings'            => $address_single_settings,
            'selector'            => '.' . $data->get_css_class( 'address_single' ),
            'render_callback'     => array( $this, 'partial_callback_' . $registry->get_name() . '_setting_address_single' ),
            'container_inclusive' => true,
            'fallback_refresh'    => false,
            'type'                => 'WPSiteIdentityPartial',
        ) );
        $wp_customize->selective_refresh->add_partial( $registry->prefix( 'address_multi' ), array(
            'settings'            => $address_multi_settings,
            'selector'            => '.' . $data->get_css_class( 'address_multi' ),
            'render_callback'     => array( $this, 'partial_callback_' . $registry->get_name() . '_setting_address_multi' ),
            'container_inclusive' => true,
            'fallback_refresh'    => false,
            'type'                => 'WPSiteIdentityPartial',
        ) );
    }

    /**
     * Adds brand data content to the Customizer.
     *
     * @since 1.0.0
     *
     * @param WP_Customize_Manager              $wp_customize     Customize manager instance.
     * @param WP_Site_Identity_Setting_Registry $setting_registry Setting registry instance.
     * @param string                            $panel_slug       Slug of the plugin's Customizer panel.
     */
    private function add_brand_data_content( $wp_customize, $setting_registry, $panel_slug ) {
        $registry     = $setting_registry->get_setting( 'brand_data' );
        $data         = $this->plugin->brand_data();
        $section_slug = 'brand_data';

        $wp_customize->add_section( $setting_registry->prefix( $section_slug ), array(
            'title'      => __( 'Brand Data', 'wp-site-identity' ),
            'capability' => 'manage_options',
            'panel'      => $setting_registry->prefix( $panel_slug ),
            'priority'   => 40,
        ) );

        $image_fields = array(
            'logo' => '',
            'icon' => '',
        );

        $color_fields = array(
            'primary_color'   => '',
            'secondary_color' => '',
            'tertiary_color'  => '',
        );

        $sections = $this->bootstrap->get_brand_data_sections();

        $priority = 0;

        foreach ( $sections as $section ) {
            $priority += 10;

            foreach ( $section['fields'] as $field ) {
                $setting          = $registry->get_setting( $field );
                $setting_basename = $setting->get_name();

                $setting_name = $registry->prefix( $registry->get_name() ) . '[' . $setting_basename . ']';

                $validate_callback = 'validate_callback_' . $registry->get_name() . '_setting_' . $setting_basename;
                $sanitize_callback = 'sanitize_callback_' . $registry->get_name() . '_setting_' . $setting_basename;

                $setting_args = array(
                    'type'              => 'option',
                    'capability'        => 'manage_options',
                    'default'           => $setting->get_default(),
                    'transport'         => 'postMessage',
                    'validate_callback' => array( $this, $validate_callback ),
                    'sanitize_callback' => array( $this, $sanitize_callback ),
                );

                $wp_customize->add_setting( $setting_name, $setting_args );

                $control_args = array(
                    'label'    => $setting->get_title(),
                    'section'  => $setting_registry->prefix( $section_slug ),
                    'priority' => $priority,
                );

                $setting_description = $setting->get_description();
                if ( ! empty( $setting_description ) ) {
                    $control_args['description'] = $setting_description;
                }

                $control_args = $this->set_default_control_args_for_setting( $control_args, $setting );

                if ( isset( $image_fields[ $setting_basename ] ) ) {
                    $image_fields[ $setting_basename ] = $setting_name;

                    $control_args['type']      = 'media';
                    $control_args['mime_type'] = 'image';
                    $control                   = new WP_Customize_Media_Control( $wp_customize, $setting_name, $control_args );
                } elseif ( isset( $color_fields[ $setting_basename ] ) ) {
                    $color_fields[ $setting_basename ] = $setting_name;

                    $control_args['type'] = 'color';
                    $control              = new WP_Customize_Color_Control( $wp_customize, $setting_name, $control_args );
                } else {
                    $control = new WP_Customize_Control( $wp_customize, $setting_name, $control_args );
                }

                $wp_customize->add_control( $control );
            }
        }

        $wp_customize->selective_refresh->add_partial( $registry->prefix( 'colors' ), array(
            'settings'            => array_values( $color_fields ),
            'selector'            => '#wpsi-custom-css-rules',
            'render_callback'     => array( $this, 'print_partial_custom_css_rules' ),
            'container_inclusive' => false,
            'fallback_refresh'    => false,
        ) );
    }

    /**
     * Validates a Customizer setting.
     *
     * @since 1.0.0
     *
     * @param WP_Error $validity               Error object to add validation errors to as necessary.
     * @param mixed    $value                  Value to sanitize.
     * @param string   $aggregate_setting_name Name of the aggregate setting or setting registry.
     * @param string   $setting_name           Name of the setting.
     * @return mixed Sanitized value.
     */
    private function validate_setting( $validity, $value, $aggregate_setting_name, $setting_name ) {
        $setting_registry  = $this->plugin->services()->get( 'setting_registry' );
        $aggregate_setting = $setting_registry->get_setting( $aggregate_setting_name );
        $setting           = $aggregate_setting->get_setting( $setting_name );

        try {
            $validated_value = $setting_registry->validator()->validate( $value, $setting );
        } catch ( WP_Site_Identity_Setting_Validation_Error_Exception $e ) {
            $prefixed = $setting_registry->prefix( $setting_name );

            $validity->add( "valid_{$prefixed}", $e->getMessage() );
        }

        return $validity;
    }

    /**
     * Sanitizes a Customizer setting.
     *
     * @since 1.0.0
     *
     * @param mixed  $value                  Value to sanitize.
     * @param string $aggregate_setting_name Name of the aggregate setting or setting registry.
     * @param string $setting_name           Name of the setting.
     * @return mixed Sanitized value.
     */
    private function sanitize_setting( $value, $aggregate_setting_name, $setting_name ) {
        $setting_registry  = $this->plugin->services()->get( 'setting_registry' );
        $aggregate_setting = $setting_registry->get_setting( $aggregate_setting_name );
        $setting           = $aggregate_setting->get_setting( $setting_name );

        try {
            $validated_value = $setting_registry->validator()->validate( $value, $setting );
        } catch ( WP_Site_Identity_Setting_Validation_Error_Exception $e ) {
            $validated_value = $setting_registry->get_value_from_wp( $setting );
        }

        return $setting_registry->sanitizer()->sanitize( $validated_value, $setting );
    }

    /**
     * Prints a partial for a Customizer setting.
     *
     * @since 1.0.0
     *
     * @param string $aggregate_setting_name Name of the aggregate setting or setting registry.
     * @param string $setting_name           Name of the setting.
     */
    private function print_partial( $aggregate_setting_name, $setting_name ) {
        $data = call_user_func( array( $this->plugin, $aggregate_setting_name ) );

        echo $data->get_as_html( $setting_name ); // WPCS: XSS OK.
    }

    /**
     * Prints the CSS rules partial.
     *
     * This is a special partial since it is actually an inline stylesheet.
     *
     * @since 1.0.0
     */
    public function print_partial_custom_css_rules() {
        $colors       = $this->plugin->brand_data()->get( 'colors' );
        $css_callback = $this->plugin->get_theme_support( 'css_callback' );

        if ( $css_callback ) {
            ob_start();
            call_user_func( $css_callback, 'wpsi-custom-css-rules', $colors );
            $output = ob_get_clean();

            echo preg_replace( '#<style[^>]*>(.*)</style>#is', '$1', $output ); // WPCS: XSS OK.
        }
    }

    /**
     * Gets the default Customizer control arguments depending on settings field arguments.
     *
     * @since 1.0.0
     *
     * @param array                    $control_args Arguments for the Customizer control. See {@see WP_Customize_Control::__construct}
     *                                               for a list of supported arguments.
     * @param WP_Site_Identity_Setting $setting      Setting the Customizer control is for.
     * @return array Arguments including a type and further details.
     */
    private function set_default_control_args_for_setting( array $control_args, WP_Site_Identity_Setting $setting ) {
        $type = $setting->get_type();

        switch ( $type ) {
            case 'boolean':
                $control_args['type'] = 'checkbox';
                break;
            case 'number':
            case 'integer':
                $control_args['type']        = 'number';
                $control_args['input_attrs'] = array();

                $min = $setting->get_min();
                $max = $setting->get_max();

                if ( is_float( $min ) || is_int( $min ) ) {
                    if ( 'integer' === $type ) {
                        $control_args['input_attrs']['min'] = (int) $min;
                    } else {
                        $control_args['input_attrs']['min'] = $min;
                    }
                }

                if ( is_float( $max ) || is_int( $max ) ) {
                    if ( 'integer' === $type ) {
                        $control_args['input_attrs']['max'] = (int) $max;
                    } else {
                        $control_args['input_attrs']['max'] = $max;
                    }
                }

                if ( 'integer' === $type ) {
                    $control_args['input_attrs']['step'] = 1;
                } else {
                    $control_args['input_attrs']['step'] = 0.01;
                }
                break;
            case 'string':
                $choices = $setting->get_choices();
                if ( ! empty( $choices ) ) {
                    if ( count( $choices ) <= 5 ) {
                        $control_args['type'] = 'radio';
                    } else {
                        $control_args['type'] = 'select';
                    }
                    $control_args['choices'] = $choices;
                } else {
                    $default = $setting->get_default();
                    $format  = $setting->get_format();

                    if ( 'email' === $format ) {
                        $control_args['type'] = 'email';
                    } elseif ( 'uri' === $format ) {
                        $control_args['type'] = 'url';
                    } else {
                        if ( is_string( $default ) && false !== strpos( $default, "\n" ) ) {
                            $control_args['type'] = 'textarea';
                        } else {
                            $control_args['type'] = 'text';
                        }
                    }
                }
                break;
        }

        return $control_args;
    }
}