Initial commit

This commit is contained in:
Alexander Agnarson 2020-03-11 14:44:42 +01:00
commit b0607606ae
369 changed files with 85494 additions and 0 deletions

View file

@ -0,0 +1,160 @@
<?php
/**
* Handles the CSS-variables of fields.
*
* @package Kirki
* @category Modules
* @author Ari Stathopoulos (@aristath)
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 3.0.28
*/
/**
* The Kirki_Modules_CSS_Vars object.
*
* @since 3.0.28
*/
class Kirki_Modules_CSS_Vars {
/**
* The object instance.
*
* @static
* @access private
* @since 3.0.28
* @var object
*/
private static $instance;
/**
* Fields with variables.
*
* @access private
* @since 3.0.28
* @var array
*/
private $fields = array();
/**
* CSS-variables array [var=>val].
*
* @access private
* @since 3.0.35
* @var array
*/
private $vars = array();
/**
* Constructor
*
* @access protected
* @since 3.0.28
*/
protected function __construct() {
add_action( 'init', array( $this, 'populate_vars' ) );
add_action( 'wp_head', array( $this, 'the_style' ), 999 );
add_action( 'admin_head', array( $this, 'the_style' ), 999 );
add_action( 'customize_preview_init', array( $this, 'postmessage' ) );
}
/**
* Gets an instance of this object.
* Prevents duplicate instances which avoid artefacts and improves performance.
*
* @static
* @access public
* @since 3.0.28
* @return object
*/
public static function get_instance() {
if ( ! self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Populates the $vars property of this object.
*
* @access public
* @since 3.0.35
* @return void
*/
public function populate_vars() {
// Get an array of all fields.
$fields = Kirki::$fields;
foreach ( $fields as $id => $args ) {
if ( ! isset( $args['css_vars'] ) || empty( $args['css_vars'] ) ) {
continue;
}
$val = Kirki_Values::get_value( $args['kirki_config'], $id );
if ( isset( $args['type'] ) && in_array( $args['type'], array( 'typography', 'kirki-typography' ), true ) ) {
if ( isset( $val['font-weight'] ) && 'regular' === $val['font-weight'] ) {
$val['font-weight'] = '400';
}
}
foreach ( $args['css_vars'] as $css_var ) {
if ( isset( $css_var[2] ) && is_array( $val ) && isset( $val[ $css_var[2] ] ) ) {
$this->vars[ $css_var[0] ] = str_replace( '$', $val[ $css_var[2] ], $css_var[1] );
} else {
$this->vars[ $css_var[0] ] = str_replace( '$', $val, $css_var[1] );
}
}
}
}
/**
* Add styles in <head>.
*
* @access public
* @since 3.0.28
* @return void
*/
public function the_style() {
if ( empty( $this->vars ) ) {
return;
}
echo '<style id="kirki-css-vars">';
echo ':root{';
foreach ( $this->vars as $var => $val ) {
echo esc_html( $var ) . ':' . esc_html( $val ) . ';';
}
echo '}';
echo '</style>';
}
/**
* Get an array of all the variables.
*
* @access public
* @since 3.0.35
* @return array
*/
public function get_vars() {
return $this->vars;
}
/**
* Enqueues the script that handles postMessage
* and adds variables to it using the wp_localize_script function.
* The rest is handled via JS.
*
* @access public
* @since 3.0.28
* @return void
*/
public function postmessage() {
wp_enqueue_script( 'kirki_auto_css_vars', trailingslashit( Kirki::$url ) . 'modules/css-vars/script.js', array( 'jquery', 'customize-preview' ), KIRKI_VERSION, true );
$fields = Kirki::$fields;
$data = array();
foreach ( $fields as $field ) {
if ( isset( $field['transport'] ) && 'postMessage' === $field['transport'] && isset( $field['css_vars'] ) && ! empty( $field['css_vars'] ) ) {
$data[] = $field;
}
}
wp_localize_script( 'kirki_auto_css_vars', 'kirkiCssVarFields', $data );
}
}

View file

@ -0,0 +1,82 @@
/* global kirkiCssVarFields */
var kirkiCssVars = {
/**
* Get styles.
*
* @since 3.0.28
* @returns {Object}
*/
getStyles: function() {
var style = jQuery( '#kirki-css-vars' ),
styles = style.html().replace( ':root{', '' ).replace( '}', '' ).split( ';' ),
stylesObj = {};
// Format styles as a object we can then tweak.
_.each( styles, function( style ) {
style = style.split( ':' );
if ( style[0] && style[1] ) {
stylesObj[ style[0] ] = style[1];
}
} );
return stylesObj;
},
/**
* Builds the styles from an object.
*
* @since 3.0.28
* @param {Object} vars - The vars.
* @returns {string}
*/
buildStyle: function( vars ) {
var style = '';
_.each( vars, function( val, name ) {
style += name + ':' + val + ';';
} );
return ':root{' + style + '}';
}
};
jQuery( document ).ready( function() {
_.each( kirkiCssVarFields, function( field ) {
wp.customize( field.settings, function( value ) {
value.bind( function( newVal ) {
var styles = kirkiCssVars.getStyles();
_.each( field.css_vars, function( cssVar ) {
if ( 'object' === typeof newVal ) {
if ( cssVar[2] && newVal[ cssVar[2] ] ) {
styles[ cssVar[0] ] = cssVar[1].replace( '$', newVal[ cssVar[2] ] );
}
} else {
styles[ cssVar[0] ] = cssVar[1].replace( '$', newVal );
}
} );
jQuery( '#kirki-css-vars' ).html( kirkiCssVars.buildStyle( styles ) );
} );
} );
} );
} );
wp.customize.bind( 'preview-ready', function() {
wp.customize.preview.bind( 'active', function() {
_.each( kirkiCssVarFields, function( field ) {
wp.customize( field.settings, function( value ) {
var styles = kirkiCssVars.getStyles(),
newVal = window.parent.wp.customize( value.id ).get();
_.each( field.css_vars, function( cssVar ) {
if ( 'object' === typeof newVal ) {
if ( cssVar[2] && newVal[ cssVar[2] ] ) {
styles[ cssVar[0] ] = cssVar[1].replace( '$', newVal[ cssVar[2] ] );
}
} else {
styles[ cssVar[0] ] = cssVar[1].replace( '$', newVal );
}
} );
jQuery( '#kirki-css-vars' ).html( kirkiCssVars.buildStyle( styles ) );
} );
} );
} );
} );

View file

@ -0,0 +1,270 @@
<?php
/**
* Generates the styles for the frontend.
* Handles the 'output' argument of fields
*
* @package Kirki
* @category Core
* @author Ari Stathopoulos (@aristath)
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 1.0
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Handles CSS output.
*/
final class Kirki_Modules_CSS_Generator {
/**
* The instance of this class (singleton pattern).
*
* @static
* @access public
* @var null|object
*/
public static $instance = null;
/**
* Settings.
*
* @static
* @access public
* @var null|string|array
*/
public static $settings = null;
/**
* Output.
*
* @static
* @access public
* @var array
*/
public static $output = array();
/**
* Callback.
*
* @static
* @access public
* @var null|string|array
*/
public static $callback = null;
/**
* Option Name.
*
* @static
* @access public
* @var null|string
*/
public static $option_name = null;
/**
* Field Type.
*
* @static
* @access public
* @var string
*/
public static $field_type = null;
/**
* Google Fonts
*
* @static
* @access public
* @var array
*/
public static $google_fonts = null;
/**
* CSS
*
* @static
* @access public
* @var string
*/
public static $css;
/**
* Value
*
* @static
* @access public
* @var mixed
*/
public static $value = null;
/**
* The class constructor.
*/
private function __construct() {
if ( is_null( self::$google_fonts ) ) {
self::$google_fonts = Kirki_Fonts::get_google_fonts();
}
}
/**
* Get a single instance of this class
*
* @return object
*/
public static function get_instance() {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Get the CSS for a field.
*
* @static
* @access public
* @param array $field The field.
* @return array
*/
public static function css( $field ) {
// Set class vars.
self::$settings = $field['settings'];
self::$callback = $field['sanitize_callback'];
self::$field_type = $field['type'];
self::$output = $field['output'];
if ( ! is_array( self::$output ) ) {
self::$output = array(
array(
'element' => self::$output,
'sanitize_callback' => null,
),
);
}
// Get the value of this field.
self::$value = Kirki_Values::get_sanitized_field_value( $field );
// Find the class that will handle the outpout for this field.
$classname = 'Kirki_Output';
$default_classnames = array(
'kirki-background' => 'Kirki_Output_Field_Background',
'kirki-dimensions' => 'Kirki_Output_Field_Dimensions',
'kirki-image' => 'Kirki_Output_Field_Image',
'kirki-typography' => 'Kirki_Output_Field_Typography',
'kirki-multicolor' => 'Kirki_Output_Field_Multicolor',
);
$field_output_classes = apply_filters( 'kirki_output_control_classnames', $default_classnames );
$field_output_classes = apply_filters( "kirki_{$field['kirki_config']}_output_control_classnames", $field_output_classes );
if ( array_key_exists( self::$field_type, $field_output_classes ) ) {
$classname = $field_output_classes[ self::$field_type ];
}
$obj = new $classname( $field['kirki_config'], self::$output, self::$value, $field );
return $obj->get_styles();
}
/**
* Gets the array of generated styles and creates the minimized, inline CSS.
*
* @static
* @access public
* @param array $css The CSS definitions array.
* @return string The generated CSS.
*/
public static function styles_parse( $css = array() ) {
// Pass our styles from the kirki_styles_array filter.
$css = apply_filters( 'kirki_styles_array', $css );
// Process the array of CSS properties and produce the final CSS.
$final_css = '';
if ( ! is_array( $css ) || empty( $css ) ) {
return '';
}
foreach ( $css as $media_query => $styles ) {
$final_css .= ( 'global' !== $media_query ) ? $media_query . '{' : '';
foreach ( $styles as $style => $style_array ) {
$css_for_style = '';
foreach ( $style_array as $property => $value ) {
if ( is_string( $value ) && '' !== $value ) {
$css_for_style .= $property . ':' . $value . ';';
} elseif ( is_array( $value ) ) {
foreach ( $value as $subvalue ) {
if ( is_string( $subvalue ) && '' !== $subvalue ) {
$css_for_style .= $property . ':' . $subvalue . ';';
}
}
}
$value = ( is_string( $value ) ) ? $value : '';
}
if ( '' !== $css_for_style ) {
$final_css .= $style . '{' . $css_for_style . '}';
}
}
$final_css .= ( 'global' !== $media_query ) ? '}' : '';
}
return $final_css;
}
/**
* Add prefixes if necessary.
*
* @param array $css The CSS definitions array.
* @return array
*/
public static function add_prefixes( $css ) {
if ( is_array( $css ) ) {
foreach ( $css as $media_query => $elements ) {
foreach ( $elements as $element => $style_array ) {
foreach ( $style_array as $property => $value ) {
// Add -webkit-* and -moz-*.
if ( is_string( $property ) && in_array(
$property,
array(
'border-radius',
'box-shadow',
'box-sizing',
'text-shadow',
'transform',
'background-size',
'transition',
'transition-property',
),
true
) ) {
unset( $css[ $media_query ][ $element ][ $property ] );
$css[ $media_query ][ $element ][ '-webkit-' . $property ] = $value;
$css[ $media_query ][ $element ][ '-moz-' . $property ] = $value;
$css[ $media_query ][ $element ][ $property ] = $value;
}
// Add -ms-* and -o-*.
if ( is_string( $property ) && in_array(
$property,
array(
'transform',
'background-size',
'transition',
'transition-property',
),
true
) ) {
unset( $css[ $media_query ][ $element ][ $property ] );
$css[ $media_query ][ $element ][ '-ms-' . $property ] = $value;
$css[ $media_query ][ $element ][ '-o-' . $property ] = $value;
$css[ $media_query ][ $element ][ $property ] = $value;
}
}
}
}
}
return $css;
}
}

View file

@ -0,0 +1,296 @@
<?php
/**
* Handles the CSS Output of fields.
*
* @package Kirki
* @category Modules
* @author Ari Stathopoulos (@aristath)
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 3.0.0
*/
/**
* The Kirki_Modules_CSS object.
*/
class Kirki_Modules_CSS {
/**
* The object instance.
*
* @static
* @access private
* @since 3.0.0
* @var object
*/
private static $instance;
/**
* The CSS array
*
* @access public
* @var array
*/
public static $css_array = array();
/**
* Constructor
*
* @access protected
*/
protected function __construct() {
$class_files = array(
'Kirki_Modules_CSS_Generator' => '/class-kirki-modules-css-generator.php',
'Kirki_Output' => '/class-kirki-output.php',
'Kirki_Output_Field_Background' => '/field/class-kirki-output-field-background.php',
'Kirki_Output_Field_Image' => '/field/class-kirki-output-field-image.php',
'Kirki_Output_Field_Multicolor' => '/field/class-kirki-output-field-multicolor.php',
'Kirki_Output_Field_Dimensions' => '/field/class-kirki-output-field-dimensions.php',
'Kirki_Output_Field_Typography' => '/field/class-kirki-output-field-typography.php',
'Kirki_Output_Property' => '/property/class-kirki-output-property.php',
'Kirki_Output_Property_Background_Image' => '/property/class-kirki-output-property-background-image.php',
'Kirki_Output_Property_Background_Position' => '/property/class-kirki-output-property-background-position.php',
'Kirki_Output_Property_Font_Family' => '/property/class-kirki-output-property-font-family.php',
);
foreach ( $class_files as $class_name => $file ) {
if ( ! class_exists( $class_name ) ) {
include_once wp_normalize_path( dirname( __FILE__ ) . $file ); // phpcs:ignore WPThemeReview.CoreFunctionality.FileInclude
}
}
add_action( 'init', array( $this, 'init' ) );
}
/**
* Gets an instance of this object.
* Prevents duplicate instances which avoid artefacts and improves performance.
*
* @static
* @access public
* @since 3.0.0
* @return object
*/
public static function get_instance() {
if ( ! self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Init.
*
* @access public
*/
public function init() {
Kirki_Modules_Webfonts::get_instance();
// Allow completely disabling Kirki CSS output.
if ( ( defined( 'KIRKI_NO_OUTPUT' ) && true === KIRKI_NO_OUTPUT ) || ( isset( $config['disable_output'] ) && true === $config['disable_output'] ) ) {
return;
}
// Admin styles, adds compatibility with the new WordPress editor (Gutenberg).
add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_styles' ), 100 );
add_action( 'wp', array( $this, 'print_styles_action' ) );
if ( ! apply_filters( 'kirki_output_inline_styles', true ) ) {
$config = apply_filters( 'kirki_config', array() );
$priority = 999;
if ( isset( $config['styles_priority'] ) ) {
$priority = absint( $config['styles_priority'] );
}
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_styles' ), $priority );
} else {
add_action( 'wp_head', array( $this, 'print_styles_inline' ), 999 );
}
}
/**
* Print styles inline.
*
* @access public
* @since 3.0.36
* @return void
*/
public function print_styles_inline() {
echo '<style id="kirki-inline-styles">';
$this->print_styles();
echo '</style>';
}
/**
* Enqueue the styles.
*
* @access public
* @since 3.0.36
* @return void
*/
public function enqueue_styles() {
$args = array(
'action' => apply_filters( 'kirki_styles_action_handle', 'kirki-styles' ),
);
if ( is_admin() && ! is_customize_preview() ) {
$args['editor'] = '1';
}
// Enqueue the dynamic stylesheet.
wp_enqueue_style(
'kirki-styles',
add_query_arg( $args, site_url() ),
array(),
KIRKI_VERSION
);
}
/**
* Prints the styles as an enqueued file.
*
* @access public
* @since 3.0.36
* @return void
*/
public function print_styles_action() {
/**
* Note to code reviewers:
* There is no need for a nonce check here, we're only checking if this is a valid request or not.
*/
if ( empty( $_GET['action'] ) || apply_filters( 'kirki_styles_action_handle', 'kirki-styles' ) !== $_GET['action'] ) { // phpcs:ignore WordPress.Security.NonceVerification
return;
}
// This is a stylesheet.
header( 'Content-type: text/css' );
$this->print_styles();
exit;
}
/**
* Prints the styles.
*
* @access public
*/
public function print_styles() {
// Go through all configs.
$configs = Kirki::$config;
foreach ( $configs as $config_id => $args ) {
if ( isset( $args['disable_output'] ) && true === $args['disable_output'] ) {
continue;
}
$styles = self::loop_controls( $config_id );
$styles = apply_filters( "kirki_{$config_id}_dynamic_css", $styles );
if ( ! empty( $styles ) ) {
/**
* Note to code reviewers:
*
* Though all output should be run through an escaping function, this is pure CSS.
*
* When used in the print_styles_action() method the PHP header() call makes the browser interpret it as such.
* No code, script or anything else can be executed from inside a stylesheet.
*
* When using in the print_styles_inline() method the wp_strip_all_tags call we use below
* strips anything that has the possibility to be malicious, and since this is inslide a <style> tag
* it can only be interpreted by the browser as such.
* wp_strip_all_tags() excludes the possibility of someone closing the <style> tag and then opening something else.
*/
echo wp_strip_all_tags( $styles ); // phpcs:ignore WordPress.Security.EscapeOutput
}
}
do_action( 'kirki_dynamic_css' );
}
/**
* Loop through all fields and create an array of style definitions.
*
* @static
* @access public
* @param string $config_id The configuration ID.
*/
public static function loop_controls( $config_id ) {
// Get an instance of the Kirki_Modules_CSS_Generator class.
// This will make sure google fonts and backup fonts are loaded.
Kirki_Modules_CSS_Generator::get_instance();
$fields = Kirki::$fields;
$css = array();
// Early exit if no fields are found.
if ( empty( $fields ) ) {
return;
}
foreach ( $fields as $field ) {
// Only process fields that belong to $config_id.
if ( $config_id !== $field['kirki_config'] ) {
continue;
}
if ( true === apply_filters( "kirki_{$config_id}_css_skip_hidden", true ) ) {
// Only continue if field dependencies are met.
if ( ! empty( $field['required'] ) ) {
$valid = true;
foreach ( $field['required'] as $requirement ) {
if ( isset( $requirement['setting'] ) && isset( $requirement['value'] ) && isset( $requirement['operator'] ) ) {
$controller_value = Kirki_Values::get_value( $config_id, $requirement['setting'] );
if ( ! Kirki_Helper::compare_values( $controller_value, $requirement['value'], $requirement['operator'] ) ) {
$valid = false;
}
}
}
if ( ! $valid ) {
continue;
}
}
}
// Only continue if $field['output'] is set.
if ( isset( $field['output'] ) && ! empty( $field['output'] ) ) {
$css = Kirki_Helper::array_replace_recursive( $css, Kirki_Modules_CSS_Generator::css( $field ) );
// Add the globals.
if ( isset( self::$css_array[ $config_id ] ) && ! empty( self::$css_array[ $config_id ] ) ) {
Kirki_Helper::array_replace_recursive( $css, self::$css_array[ $config_id ] );
}
}
}
$css = apply_filters( "kirki_{$config_id}_styles", $css );
if ( is_array( $css ) ) {
return Kirki_Modules_CSS_Generator::styles_parse( Kirki_Modules_CSS_Generator::add_prefixes( $css ) );
}
}
/**
* The FA field got deprecated in v3.0.42.
* This is here for backwards-compatibility in case a theme was calling it directly.
*
* @static
* @since 3.0.26
* @access public
* @return void
*/
public static function add_fontawesome_script() {}
/**
* The FA field got deprecated in v3.0.42.
* This is here for backwards-compatibility in case a theme was calling it directly.
*
* @static
* @since 3.0.35
* @access public
* @return false
*/
public static function get_enqueue_fa() {
return false;
}
}

View file

@ -0,0 +1,349 @@
<?php
/**
* Handles CSS output for fields.
*
* @package Kirki
* @subpackage Controls
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 2.2.0
*/
/**
* Handles field CSS output.
*/
class Kirki_Output {
/**
* The Kirki configuration used in the field.
*
* @access protected
* @var string
*/
protected $config_id = 'global';
/**
* The field's `output` argument.
*
* @access protected
* @var array
*/
protected $output = array();
/**
* An array of the generated styles.
*
* @access protected
* @var array
*/
protected $styles = array();
/**
* The field.
*
* @access protected
* @var array
*/
protected $field = array();
/**
* The value.
*
* @access protected
* @var string|array
*/
protected $value;
/**
* The class constructor.
*
* @access public
* @param string $config_id The config ID.
* @param array $output The output argument.
* @param string|array $value The value.
* @param array $field The field.
*/
public function __construct( $config_id, $output, $value, $field ) {
$this->config_id = $config_id;
$this->value = $value;
$this->output = $output;
$this->field = $field;
$this->parse_output();
}
/**
* If we have a sanitize_callback defined, apply it to the value.
*
* @param array $output The output args.
* @param string|array $value The value.
*
* @return string|array
*/
protected function apply_sanitize_callback( $output, $value ) {
if ( isset( $output['sanitize_callback'] ) && null !== $output['sanitize_callback'] ) {
// If the sanitize_callback is invalid, return the value.
if ( ! is_callable( $output['sanitize_callback'] ) ) {
return $value;
}
return call_user_func( $output['sanitize_callback'], $this->value );
}
return $value;
}
/**
* If we have a value_pattern defined, apply it to the value.
*
* @param array $output The output args.
* @param string|array $value The value.
* @return string|array
*/
protected function apply_value_pattern( $output, $value ) {
if ( isset( $output['value_pattern'] ) && ! empty( $output['value_pattern'] ) && is_string( $output['value_pattern'] ) ) {
if ( ! is_array( $value ) ) {
$value = str_replace( '$', $value, $output['value_pattern'] );
}
if ( is_array( $value ) ) {
foreach ( array_keys( $value ) as $value_k ) {
if ( is_array( $value[ $value_k ] ) ) {
continue;
}
if ( isset( $output['choice'] ) ) {
if ( $output['choice'] === $value_k ) {
$value[ $output['choice'] ] = str_replace( '$', $value[ $output['choice'] ], $output['value_pattern'] );
}
continue;
}
$value[ $value_k ] = str_replace( '$', $value[ $value_k ], $output['value_pattern'] );
}
}
$value = $this->apply_pattern_replace( $output, $value );
}
return $value;
}
/**
* If we have a value_pattern defined, apply it to the value.
*
* @param array $output The output args.
* @param string|array $value The value.
* @return string|array
*/
protected function apply_pattern_replace( $output, $value ) {
if ( isset( $output['pattern_replace'] ) && is_array( $output['pattern_replace'] ) ) {
$option_type = ( '' !== Kirki::get_config_param( $this->config_id, 'option_type' ) ) ? Kirki::get_config_param( $this->config_id, 'option_type' ) : 'theme_mod';
$option_name = Kirki::get_config_param( $this->config_id, 'option_name' );
$options = array();
if ( $option_name ) {
$options = ( 'site_option' === $option_type ) ? get_site_option( $option_name ) : get_option( $option_name );
}
foreach ( $output['pattern_replace'] as $search => $replace ) {
$replacement = '';
switch ( $option_type ) {
case 'option':
if ( is_array( $options ) ) {
if ( $option_name ) {
$subkey = str_replace( array( $option_name, '[', ']' ), '', $replace );
$replacement = ( isset( $options[ $subkey ] ) ) ? $options[ $subkey ] : '';
break;
}
$replacement = ( isset( $options[ $replace ] ) ) ? $options[ $replace ] : '';
break;
}
$replacement = get_option( $replace );
break;
case 'site_option':
$replacement = ( is_array( $options ) && isset( $options[ $replace ] ) ) ? $options[ $replace ] : get_site_option( $replace );
break;
case 'user_meta':
$user_id = get_current_user_id();
if ( $user_id ) {
$replacement = get_user_meta( $user_id, $replace, true );
}
break;
default:
$replacement = get_theme_mod( $replace );
if ( ! $replacement ) {
$replacement = Kirki_Values::get_value( $this->field['kirki_config'], $replace );
}
}
$replacement = ( false === $replacement ) ? '' : $replacement;
if ( is_array( $value ) ) {
foreach ( $value as $k => $v ) {
$_val = ( isset( $value[ $v ] ) ) ? $value[ $v ] : $v;
$value[ $k ] = str_replace( $search, $replacement, $_val );
}
return $value;
}
$value = str_replace( $search, $replacement, $value );
}
}
return $value;
}
/**
* Parses the output arguments.
* Calls the process_output method for each of them.
*
* @access protected
*/
protected function parse_output() {
foreach ( $this->output as $output ) {
$skip = false;
// Apply any sanitization callbacks defined.
$value = $this->apply_sanitize_callback( $output, $this->value );
// Skip if value is empty.
if ( '' === $this->value ) {
$skip = true;
}
// No need to proceed this if the current value is the same as in the "exclude" value.
if ( isset( $output['exclude'] ) && is_array( $output['exclude'] ) ) {
foreach ( $output['exclude'] as $exclude ) {
if ( is_array( $value ) ) {
if ( is_array( $exclude ) ) {
$diff1 = array_diff( $value, $exclude );
$diff2 = array_diff( $exclude, $value );
if ( empty( $diff1 ) && empty( $diff2 ) ) {
$skip = true;
}
}
// If 'choice' is defined check for sub-values too.
// Fixes https://github.com/aristath/kirki/issues/1416.
if ( isset( $output['choice'] ) && isset( $value[ $output['choice'] ] ) && $exclude == $value[ $output['choice'] ] ) { // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
$skip = true;
}
}
if ( $skip ) {
continue;
}
// Skip if value is defined as excluded.
if ( $exclude === $value || ( '' === $exclude && empty( $value ) ) ) {
$skip = true;
}
}
}
if ( $skip ) {
continue;
}
// Apply any value patterns defined.
$value = $this->apply_value_pattern( $output, $value );
if ( isset( $output['element'] ) && is_array( $output['element'] ) ) {
$output['element'] = array_unique( $output['element'] );
sort( $output['element'] );
$output['element'] = implode( ',', $output['element'] );
}
$value = $this->process_value( $value, $output );
if ( ( is_admin() && ! is_customize_preview() ) || ( isset( $_GET['editor'] ) && '1' === $_GET['editor'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
// Check if this is an admin style.
if ( ! isset( $output['context'] ) || ! in_array( 'editor', $output['context'], true ) ) {
continue;
}
} elseif ( isset( $output['context'] ) && ! in_array( 'front', $output['context'], true ) ) {
// Check if this is a frontend style.
continue;
}
$this->process_output( $output, $value );
}
}
/**
* Parses an output and creates the styles array for it.
*
* @access protected
* @param array $output The field output.
* @param string|array $value The value.
*
* @return null
*/
protected function process_output( $output, $value ) {
if ( ! isset( $output['element'] ) || ! isset( $output['property'] ) ) {
return;
}
$output['media_query'] = ( isset( $output['media_query'] ) ) ? $output['media_query'] : 'global';
$output['prefix'] = ( isset( $output['prefix'] ) ) ? $output['prefix'] : '';
$output['units'] = ( isset( $output['units'] ) ) ? $output['units'] : '';
$output['suffix'] = ( isset( $output['suffix'] ) ) ? $output['suffix'] : '';
// Properties that can accept multiple values.
// Useful for example for gradients where all browsers use the "background-image" property
// and the browser prefixes go in the value_pattern arg.
$accepts_multiple = array(
'background-image',
'background',
);
if ( in_array( $output['property'], $accepts_multiple, true ) ) {
if ( isset( $this->styles[ $output['media_query'] ][ $output['element'] ][ $output['property'] ] ) && ! is_array( $this->styles[ $output['media_query'] ][ $output['element'] ][ $output['property'] ] ) ) {
$this->styles[ $output['media_query'] ][ $output['element'] ][ $output['property'] ] = (array) $this->styles[ $output['media_query'] ][ $output['element'] ][ $output['property'] ];
}
$this->styles[ $output['media_query'] ][ $output['element'] ][ $output['property'] ][] = $output['prefix'] . $value . $output['units'] . $output['suffix'];
return;
}
if ( is_string( $value ) || is_numeric( $value ) ) {
$this->styles[ $output['media_query'] ][ $output['element'] ][ $output['property'] ] = $output['prefix'] . $this->process_property_value( $output['property'], $value ) . $output['units'] . $output['suffix'];
}
}
/**
* Some CSS properties are unique.
* We need to tweak the value to make everything works as expected.
*
* @access protected
* @param string $property The CSS property.
* @param string|array $value The value.
*
* @return array
*/
protected function process_property_value( $property, $value ) {
$properties = apply_filters(
"kirki_{$this->config_id}_output_property_classnames",
array(
'font-family' => 'Kirki_Output_Property_Font_Family',
'background-image' => 'Kirki_Output_Property_Background_Image',
'background-position' => 'Kirki_Output_Property_Background_Position',
)
);
if ( array_key_exists( $property, $properties ) ) {
$classname = $properties[ $property ];
$obj = new $classname( $property, $value );
return $obj->get_value();
}
return $value;
}
/**
* Returns the value.
*
* @access protected
* @param string|array $value The value.
* @param array $output The field "output".
* @return string|array
*/
protected function process_value( $value, $output ) {
if ( isset( $output['property'] ) ) {
return $this->process_property_value( $output['property'], $value );
}
return $value;
}
/**
* Exploses the private $styles property to the world
*
* @access protected
* @return array
*/
public function get_styles() {
return $this->styles;
}
}

View file

@ -0,0 +1,47 @@
<?php
/**
* Handles CSS output for background fields.
*
* @package Kirki
* @subpackage Controls
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 3.0.0
*/
/**
* Output overrides.
*/
class Kirki_Output_Field_Background extends Kirki_Output {
/**
* Processes a single item from the `output` array.
*
* @access protected
* @param array $output The `output` item.
* @param array $value The field's value.
*/
protected function process_output( $output, $value ) {
$output = wp_parse_args(
$output,
array(
'media_query' => 'global',
'element' => 'body',
'prefix' => '',
'suffix' => '',
)
);
foreach ( array( 'background-image', 'background-color', 'background-repeat', 'background-position', 'background-size', 'background-attachment' ) as $property ) {
// See https://github.com/aristath/kirki/issues/1808.
if ( 'background-color' === $property && isset( $value['background-color'] ) && $value['background-color'] && ( ! isset( $value['background-image'] ) || empty( $value['background-image'] ) ) ) {
$this->styles[ $output['media_query'] ][ $output['element'] ]['background'] = $output['prefix'] . $this->process_property_value( $property, $value[ $property ] ) . $output['suffix'];
}
if ( isset( $value[ $property ] ) && ! empty( $value[ $property ] ) ) {
$this->styles[ $output['media_query'] ][ $output['element'] ][ $property ] = $output['prefix'] . $this->process_property_value( $property, $value[ $property ] ) . $output['suffix'];
}
}
}
}

View file

@ -0,0 +1,56 @@
<?php
/**
* Handles CSS output for dimensions fields.
*
* @package Kirki
* @subpackage Controls
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 2.2.0
*/
/**
* Output overrides.
*/
class Kirki_Output_Field_Dimensions extends Kirki_Output {
/**
* Processes a single item from the `output` array.
*
* @access protected
* @param array $output The `output` item.
* @param array $value The field's value.
*/
protected function process_output( $output, $value ) {
$output = wp_parse_args(
$output,
array(
'element' => '',
'property' => '',
'media_query' => 'global',
'prefix' => '',
'suffix' => '',
)
);
if ( ! is_array( $value ) ) {
return;
}
foreach ( array_keys( $value ) as $key ) {
$property = ( empty( $output['property'] ) ) ? $key : $output['property'] . '-' . $key;
if ( isset( $output['choice'] ) && $output['property'] ) {
if ( $key === $output['choice'] ) {
$property = $output['property'];
} else {
continue;
}
}
if ( false !== strpos( $output['property'], '%%' ) ) {
$property = str_replace( '%%', $key, $output['property'] );
}
$this->styles[ $output['media_query'] ][ $output['element'] ][ $property ] = $output['prefix'] . $this->process_property_value( $property, $value[ $key ] ) . $output['suffix'];
}
}
}

View file

@ -0,0 +1,50 @@
<?php
/**
* Handles CSS output for image fields.
*
* @package Kirki
* @subpackage Controls
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 3.0.10
*/
/**
* Output overrides.
*/
class Kirki_Output_Field_Image extends Kirki_Output {
/**
* Processes a single item from the `output` array.
*
* @access protected
* @param array $output The `output` item.
* @param array $value The field's value.
*/
protected function process_output( $output, $value ) {
if ( ! isset( $output['element'] ) || ! isset( $output['property'] ) ) {
return;
}
$output = wp_parse_args(
$output,
array(
'media_query' => 'global',
'prefix' => '',
'units' => '',
'suffix' => '',
)
);
if ( is_array( $value ) ) {
if ( isset( $output['choice'] ) && $output['choice'] ) {
$this->styles[ $output['media_query'] ][ $output['element'] ][ $output['property'] ] = $output['prefix'] . $this->process_property_value( $output['property'], $value[ $output['choice'] ] ) . $output['units'] . $output['suffix'];
return;
}
if ( isset( $value['url'] ) ) {
$this->styles[ $output['media_query'] ][ $output['element'] ][ $output['property'] ] = $output['prefix'] . $this->process_property_value( $output['property'], $value['url'] ) . $output['units'] . $output['suffix'];
return;
}
return;
}
$this->styles[ $output['media_query'] ][ $output['element'] ][ $output['property'] ] = $output['prefix'] . $this->process_property_value( $output['property'], $value ) . $output['units'] . $output['suffix'];
}
}

View file

@ -0,0 +1,53 @@
<?php
/**
* Handles CSS output for multicolor fields.
*
* @package Kirki
* @subpackage Controls
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 2.2.0
*/
/**
* Output overrides.
*/
class Kirki_Output_Field_Multicolor extends Kirki_Output {
/**
* Processes a single item from the `output` array.
*
* @access protected
* @param array $output The `output` item.
* @param array $value The field's value.
*/
protected function process_output( $output, $value ) {
foreach ( $value as $key => $sub_value ) {
// If "element" is not defined, there's no reason to continue.
if ( ! isset( $output['element'] ) ) {
continue;
}
// If the "choice" is not the same as the $key in our loop, there's no reason to proceed.
if ( isset( $output['choice'] ) && $key !== $output['choice'] ) {
continue;
}
// If "property" is not defined, fallback to the $key.
$property = ( ! isset( $output['property'] ) || empty( $output['property'] ) ) ? $key : $output['property'];
// If "media_query" is not defined, use "global".
if ( ! isset( $output['media_query'] ) || empty( $output['media_query'] ) ) {
$output['media_query'] = 'global';
}
// If "suffix" is defined, add it to the value.
$output['suffix'] = ( isset( $output['suffix'] ) ) ? $output['suffix'] : '';
// Create the styles.
$this->styles[ $output['media_query'] ][ $output['element'] ][ $property ] = $sub_value . $output['suffix'];
}
}
}

View file

@ -0,0 +1,96 @@
<?php
/**
* Handles CSS output for typography fields.
*
* @package Kirki
* @subpackage Controls
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 2.2.0
*/
/**
* Output overrides.
*/
class Kirki_Output_Field_Typography extends Kirki_Output {
/**
* Processes a single item from the `output` array.
*
* @access protected
* @param array $output The `output` item.
* @param array $value The field's value.
*/
protected function process_output( $output, $value ) {
$output['media_query'] = ( isset( $output['media_query'] ) ) ? $output['media_query'] : 'global';
$output['element'] = ( isset( $output['element'] ) ) ? $output['element'] : 'body';
$output['prefix'] = ( isset( $output['prefix'] ) ) ? $output['prefix'] : '';
$output['suffix'] = ( isset( $output['suffix'] ) ) ? $output['suffix'] : '';
$value = Kirki_Field_Typography::sanitize( $value );
$properties = array(
'font-family',
'font-size',
'variant',
'font-weight',
'font-style',
'letter-spacing',
'word-spacing',
'line-height',
'text-align',
'text-transform',
'text-decoration',
'color',
);
foreach ( $properties as $property ) {
// Early exit if the value is not in the defaults.
if ( ! isset( $this->field['default'][ $property ] ) ) {
continue;
}
// Early exit if the value is not saved in the values.
if ( ! isset( $value[ $property ] ) || ! $value[ $property ] ) {
continue;
}
// Early exit if we use "choice" but not for this property.
if ( isset( $output['choice'] ) && $output['choice'] !== $property ) {
continue;
}
// Take care of variants.
if ( 'variant' === $property && isset( $value['variant'] ) && ! empty( $value['variant'] ) ) {
// Get the font_weight.
$font_weight = str_replace( 'italic', '', $value['variant'] );
$font_weight = ( in_array( $font_weight, array( '', 'regular' ), true ) ) ? '400' : $font_weight;
// Is this italic?
$is_italic = ( false !== strpos( $value['variant'], 'italic' ) );
$this->styles[ $output['media_query'] ][ $output['element'] ]['font-weight'] = $font_weight;
if ( $is_italic ) {
$this->styles[ $output['media_query'] ][ $output['element'] ]['font-style'] = 'italic';
}
continue;
}
$property_value = $this->process_property_value( $property, $value[ $property ] );
if ( 'font-family' === $property ) {
$value['font-backup'] = ( isset( $value['font-backup'] ) ) ? $value['font-backup'] : '';
$property_value = $this->process_property_value(
$property,
array(
$value['font-family'],
$value['font-backup'],
)
);
}
$property = ( isset( $output['choice'] ) && isset( $output['property'] ) ) ? $output['property'] : $property;
$property_value = ( is_array( $property_value ) && isset( $property_value[0] ) ) ? $property_value[0] : $property_value;
$this->styles[ $output['media_query'] ][ $output['element'] ][ $property ] = $output['prefix'] . $property_value . $output['suffix'];
}
}
}

View file

@ -0,0 +1,37 @@
<?php
/**
* Handles CSS output for background-image.
*
* @package Kirki
* @subpackage Controls
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 2.2.0
*/
/**
* Output overrides.
*/
class Kirki_Output_Property_Background_Image extends Kirki_Output_Property {
/**
* Modifies the value.
*
* @access protected
*/
protected function process_value() {
if ( is_array( $this->value ) && isset( $this->value['url'] ) ) {
$this->value = $this->value['url'];
}
if ( false === strpos( $this->value, 'gradient' ) && false === strpos( $this->value, 'url(' ) ) {
if ( empty( $this->value ) ) {
return;
}
if ( preg_match( '/^\d+$/', $this->value ) ) {
$this->value = 'url("' . set_url_scheme( wp_get_attachment_url( $this->value ) ) . '")';
} else {
$this->value = 'url("' . set_url_scheme( $this->value ) . '")';
}
}
}
}

View file

@ -0,0 +1,71 @@
<?php
/**
* Handles CSS output for background-position.
*
* @package Kirki
* @subpackage Controls
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 2.2.0
*/
/**
* Output overrides.
*/
class Kirki_Output_Property_Background_Position extends Kirki_Output_Property {
/**
* Modifies the value.
*
* @access protected
*/
protected function process_value() {
$this->value = trim( $this->value );
// If you use calc() there, I suppose you know what you're doing.
// No need to process this any further, just exit.
if ( false !== strpos( $this->value, 'calc' ) ) {
return;
}
// If the value is initial or inherit, we don't need to do anything.
// Just exit.
if ( 'initial' === $this->value || 'inherit' === $this->value ) {
return;
}
$x_dimensions = array( 'left', 'center', 'right' );
$y_dimensions = array( 'top', 'center', 'bottom' );
// If there's a space, we have an X and a Y value.
if ( false !== strpos( $this->value, ' ' ) ) {
$xy = explode( ' ', $this->value );
$x = trim( $xy[0] );
$y = trim( $xy[1] );
// If x is not left/center/right, we need to sanitize it.
if ( ! in_array( $x, $x_dimensions, true ) ) {
$x = sanitize_text_field( $x );
}
if ( ! in_array( $y, $y_dimensions, true ) ) {
$y = sanitize_text_field( $y );
}
$this->value = $x . ' ' . $y;
return;
}
$x = 'center';
foreach ( $x_dimensions as $x_dimension ) {
if ( false !== strpos( $this->value, $x_dimension ) ) {
$x = $x_dimension;
}
}
$y = 'center';
foreach ( $y_dimensions as $y_dimension ) {
if ( false !== strpos( $this->value, $y_dimension ) ) {
$y = $y_dimension;
}
}
$this->value = $x . ' ' . $y;
}
}

View file

@ -0,0 +1,45 @@
<?php
/**
* Handles CSS output for font-family.
*
* @package Kirki
* @subpackage Controls
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 2.2.0
*/
/**
* Output overrides.
*/
class Kirki_Output_Property_Font_Family extends Kirki_Output_Property {
/**
* Modifies the value.
*
* @access protected
*/
protected function process_value() {
$google_fonts_array = Kirki_Fonts::get_google_fonts();
$family = $this->value;
if ( is_array( $this->value ) && isset( $this->value[0] ) && isset( $this->value[1] ) ) {
$family = $this->value[0];
}
// Make sure the value is a string.
// If not, then early exit.
if ( ! is_string( $family ) ) {
return;
}
// Hack for standard fonts.
$family = str_replace( '&quot;', '"', $family );
// Add double quotes if needed.
if ( false !== strpos( $family, ' ' ) && false === strpos( $family, '"' ) ) {
$this->value = '"' . $family . '"';
}
$this->value = html_entity_decode( $family, ENT_QUOTES );
}
}

View file

@ -0,0 +1,64 @@
<?php
/**
* Handles CSS properties.
* Extend this class in order to handle exceptions.
*
* @package Kirki
* @subpackage Controls
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 2.2.0
*/
/**
* Output for CSS properties.
*/
class Kirki_Output_Property {
/**
* The property we're modifying.
*
* @access protected
* @var string
*/
protected $property;
/**
* The value
*
* @access protected
* @var string|array
*/
protected $value;
/**
* Constructor.
*
* @access public
* @param string $property The CSS property we're modifying.
* @param mixed $value The value.
*/
public function __construct( $property, $value ) {
$this->property = $property;
$this->value = $value;
$this->process_value();
}
/**
* Modifies the value.
*
* @access protected
*/
protected function process_value() {
}
/**
* Gets the value.
*
* @access protected
*/
public function get_value() {
return $this->value;
}
}

View file

@ -0,0 +1,159 @@
<?php
/**
* Custom Sections.
*
* @package Kirki
* @category Modules
* @subpackage Custom Sections Module
* @author Ari Stathopoulos (@aristath)
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 3.0.0
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Adds styles to the customizer.
*/
class Kirki_Modules_Custom_Sections {
/**
* The object instance.
*
* @static
* @access private
* @since 3.0.0
* @var object
*/
private static $instance;
/**
* Constructor.
*
* @access protected
* @since 3.0.0
*/
protected function __construct() {
// Register the new section types.
add_filter( 'kirki_section_types', array( $this, 'set_section_types' ) );
// Register the new panel types.
add_filter( 'kirki_panel_types', array( $this, 'set_panel_types' ) );
// Include the section-type files.
add_action( 'customize_register', array( $this, 'include_sections_and_panels' ) );
// Enqueue styles & scripts.
add_action( 'customize_controls_enqueue_scripts', array( $this, 'enqueue_scrips' ), 999 );
}
/**
* Gets an instance of this object.
* Prevents duplicate instances which avoid artefacts and improves performance.
*
* @static
* @access public
* @since 3.0.0
* @return object
*/
public static function get_instance() {
if ( ! self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Add the custom section types.
*
* @access public
* @since 3.0.0
* @param array $section_types The registered section-types.
* @return array
*/
public function set_section_types( $section_types ) {
$new_types = array(
'kirki-default' => 'Kirki_Sections_Default_Section',
'kirki-expanded' => 'Kirki_Sections_Expanded_Section',
'kirki-nested' => 'Kirki_Sections_Nested_Section',
'kirki-link' => 'Kirki_Sections_Link_Section',
);
return array_merge( $section_types, $new_types );
}
/**
* Add the custom panel types.
*
* @access public
* @since 3.0.0
* @param array $panel_types The registered section-types.
* @return array
*/
public function set_panel_types( $panel_types ) {
$new_types = array(
'kirki-nested' => 'Kirki_Panels_Nested_Panel',
);
return array_merge( $panel_types, $new_types );
}
/**
* Include the custom-section classes.
*
* @access public
* @since 3.0.0
*/
public function include_sections_and_panels() {
// Sections.
$folder_path = dirname( __FILE__ ) . '/sections/';
$section_types = apply_filters( 'kirki_section_types', array() );
foreach ( $section_types as $id => $class ) {
if ( ! class_exists( $class ) ) {
$path = wp_normalize_path( $folder_path . 'class-kirki-sections-' . $id . '-section.php' );
if ( file_exists( $path ) ) {
include_once $path; // phpcs:ignore WPThemeReview.CoreFunctionality.FileInclude
continue;
}
$path = str_replace( 'class-kirki-sections-kirki-', 'class-kirki-sections-', $path );
if ( file_exists( $path ) ) {
include_once $path; // phpcs:ignore WPThemeReview.CoreFunctionality.FileInclude
}
}
}
// Panels.
$folder_path = dirname( __FILE__ ) . '/panels/';
$panel_types = apply_filters( 'kirki_panel_types', array() );
foreach ( $panel_types as $id => $class ) {
if ( ! class_exists( $class ) ) {
$path = wp_normalize_path( $folder_path . 'class-kirki-panels-' . $id . '-panel.php' );
if ( file_exists( $path ) ) {
include_once $path; // phpcs:ignore WPThemeReview.CoreFunctionality.FileInclude
continue;
}
$path = str_replace( 'class-kirki-panels-kirki-', 'class-kirki-panels-', $path );
if ( file_exists( $path ) ) {
include_once $path; // phpcs:ignore WPThemeReview.CoreFunctionality.FileInclude
}
}
}
}
/**
* Enqueues any necessary scripts and styles.
*
* @access public
* @since 3.0.0
*/
public function enqueue_scrips() {
wp_enqueue_style( 'kirki-custom-sections', trailingslashit( Kirki::$url ) . 'modules/custom-sections/sections.css', array(), KIRKI_VERSION );
wp_enqueue_script( 'kirki-custom-sections', trailingslashit( Kirki::$url ) . 'modules/custom-sections/sections.js', array( 'jquery', 'customize-base', 'customize-controls' ), KIRKI_VERSION, false );
}
}

View file

@ -0,0 +1,61 @@
<?php
/**
* Nested section.
*
* @package Kirki
* @subpackage Custom Sections Module
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 3.0.0
*/
/**
* Nested panel.
*/
class Kirki_Panels_Nested_Panel extends WP_Customize_Panel {
/**
* The parent panel.
*
* @access public
* @since 3.0.0
* @var string
*/
public $panel;
/**
* Type of this panel.
*
* @access public
* @since 3.0.0
* @var string
*/
public $type = 'kirki-nested';
/**
* Gather the parameters passed to client JavaScript via JSON.
*
* @access public
* @since 3.0.0
* @return array The array to be exported to the client as JSON.
*/
public function json() {
$array = wp_array_slice_assoc(
(array) $this,
array(
'id',
'description',
'priority',
'type',
'panel',
)
);
$array['title'] = html_entity_decode( $this->title, ENT_QUOTES, get_bloginfo( 'charset' ) );
$array['content'] = $this->get_content();
$array['active'] = $this->active();
$array['instanceNumber'] = $this->instance_number;
return $array;
}
}

View file

@ -0,0 +1,29 @@
#customize-theme-controls .control-section-kirki-expanded .accordion-section-title {
display: none;
}
#customize-theme-controls .control-section-kirki-expanded .customize-section-back {
display: none;
}
#customize-theme-controls .customize-pane-child.control-section-kirki-expanded {
position: relative;
visibility: visible;
height: auto;
margin-left: -100%;
}
#customize-theme-controls .customize-pane-child.control-section-kirki-expanded h3 .customize-action {
display: none;
}
#customize-theme-controls .control-section-kirki-link .button {
margin-top: -3px;
}
#customize-theme-controls .customize-pane-child.current-section-parent,
.in-sub-panel #customize-theme-controls .customize-pane-child.current-panel-parent {
transform: translateX(-100%);
}
.control-section-kirki-nested {
margin: 0 -12px;
}
/*# sourceMappingURL=sections.css.map */

View file

@ -0,0 +1,267 @@
jQuery( document ).ready( function() {
wp.customize.section.each( function( section ) {
// Get the pane element.
var pane = jQuery( '#sub-accordion-section-' + section.id ),
sectionLi = jQuery( '#accordion-section-' + section.id );
// Check if the section is expanded.
if ( sectionLi.hasClass( 'control-section-kirki-expanded' ) ) {
// Move element.
pane.appendTo( sectionLi );
}
} );
} );
/**
* See https://github.com/justintadlock/trt-customizer-pro
*/
( function() {
wp.customize.sectionConstructor['kirki-link'] = wp.customize.Section.extend( {
attachEvents: function() {},
isContextuallyActive: function() {
return true;
}
} );
} () );
/**
* @see https://wordpress.stackexchange.com/a/256103/17078
*/
( function() {
var _panelEmbed,
_panelIsContextuallyActive,
_panelAttachEvents,
_sectionEmbed,
_sectionIsContextuallyActive,
_sectionAttachEvents;
wp.customize.bind( 'pane-contents-reflowed', function() {
var panels = [],
sections = [];
// Reflow Sections.
wp.customize.section.each( function( section ) {
if ( 'kirki-nested' !== section.params.type || _.isUndefined( section.params.section ) ) {
return;
}
sections.push( section );
} );
sections.sort( wp.customize.utils.prioritySort ).reverse();
jQuery.each( sections, function( i, section ) {
var parentContainer = jQuery( '#sub-accordion-section-' + section.params.section );
parentContainer.children( '.section-meta' ).after( section.headContainer );
} );
// Reflow Panels.
wp.customize.panel.each( function( panel ) {
if ( 'kirki-nested' !== panel.params.type || _.isUndefined( panel.params.panel ) ) {
return;
}
panels.push( panel );
} );
panels.sort( wp.customize.utils.prioritySort ).reverse();
jQuery.each( panels, function( i, panel ) {
var parentContainer = jQuery( '#sub-accordion-panel-' + panel.params.panel );
parentContainer.children( '.panel-meta' ).after( panel.headContainer );
} );
} );
// Extend Panel.
_panelEmbed = wp.customize.Panel.prototype.embed;
_panelIsContextuallyActive = wp.customize.Panel.prototype.isContextuallyActive;
_panelAttachEvents = wp.customize.Panel.prototype.attachEvents;
wp.customize.Panel = wp.customize.Panel.extend( {
attachEvents: function() {
var panel;
if ( 'kirki-nested' !== this.params.type || _.isUndefined( this.params.panel ) ) {
_panelAttachEvents.call( this );
return;
}
_panelAttachEvents.call( this );
panel = this;
panel.expanded.bind( function( expanded ) {
var parent = wp.customize.panel( panel.params.panel );
if ( expanded ) {
parent.contentContainer.addClass( 'current-panel-parent' );
} else {
parent.contentContainer.removeClass( 'current-panel-parent' );
}
} );
panel.container.find( '.customize-panel-back' ).off( 'click keydown' ).on( 'click keydown', function( event ) {
if ( wp.customize.utils.isKeydownButNotEnterEvent( event ) ) {
return;
}
event.preventDefault(); // Keep this AFTER the key filter above
if ( panel.expanded() ) {
wp.customize.panel( panel.params.panel ).expand();
}
} );
},
embed: function() {
var panel = this,
parentContainer;
if ( 'kirki-nested' !== this.params.type || _.isUndefined( this.params.panel ) ) {
_panelEmbed.call( this );
return;
}
_panelEmbed.call( this );
parentContainer = jQuery( '#sub-accordion-panel-' + this.params.panel );
parentContainer.append( panel.headContainer );
},
isContextuallyActive: function() {
var panel = this,
children,
activeCount = 0;
if ( 'kirki-nested' !== this.params.type ) {
return _panelIsContextuallyActive.call( this );
}
children = this._children( 'panel', 'section' );
wp.customize.panel.each( function( child ) {
if ( ! child.params.panel ) {
return;
}
if ( child.params.panel !== panel.id ) {
return;
}
children.push( child );
} );
children.sort( wp.customize.utils.prioritySort );
_( children ).each( function( child ) {
if ( child.active() && child.isContextuallyActive() ) {
activeCount += 1;
}
} );
return ( 0 !== activeCount );
}
} );
// Extend Section.
_sectionEmbed = wp.customize.Section.prototype.embed;
_sectionIsContextuallyActive = wp.customize.Section.prototype.isContextuallyActive;
_sectionAttachEvents = wp.customize.Section.prototype.attachEvents;
wp.customize.Section = wp.customize.Section.extend( {
attachEvents: function() {
var section = this;
if ( 'kirki-nested' !== this.params.type || _.isUndefined( this.params.section ) ) {
_sectionAttachEvents.call( section );
return;
}
_sectionAttachEvents.call( section );
section.expanded.bind( function( expanded ) {
var parent = wp.customize.section( section.params.section );
if ( expanded ) {
parent.contentContainer.addClass( 'current-section-parent' );
} else {
parent.contentContainer.removeClass( 'current-section-parent' );
}
} );
section.container.find( '.customize-section-back' ).off( 'click keydown' ).on( 'click keydown', function( event ) {
if ( wp.customize.utils.isKeydownButNotEnterEvent( event ) ) {
return;
}
event.preventDefault(); // Keep this AFTER the key filter above
if ( section.expanded() ) {
wp.customize.section( section.params.section ).expand();
}
} );
},
embed: function() {
var section = this,
parentContainer;
if ( 'kirki-nested' !== this.params.type || _.isUndefined( this.params.section ) ) {
_sectionEmbed.call( section );
return;
}
_sectionEmbed.call( section );
parentContainer = jQuery( '#sub-accordion-section-' + this.params.section );
parentContainer.append( section.headContainer );
},
isContextuallyActive: function() {
var section = this,
children,
activeCount = 0;
if ( 'kirki-nested' !== this.params.type ) {
return _sectionIsContextuallyActive.call( this );
}
children = this._children( 'section', 'control' );
wp.customize.section.each( function( child ) {
if ( ! child.params.section ) {
return;
}
if ( child.params.section !== section.id ) {
return;
}
children.push( child );
} );
children.sort( wp.customize.utils.prioritySort );
_( children ).each( function( child ) {
if ( 'undefined' !== typeof child.isContextuallyActive ) {
if ( child.active() && child.isContextuallyActive() ) {
activeCount += 1;
}
} else {
if ( child.active() ) {
activeCount += 1;
}
}
} );
return ( 0 !== activeCount );
}
} );
}( jQuery ) );

View file

@ -0,0 +1,25 @@
<?php
/**
* The default section.
*
* @package Kirki
* @subpackage Custom Sections Module
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 2.2.0
*/
/**
* Default Section.
*/
class Kirki_Sections_Default_Section extends WP_Customize_Section {
/**
* The section type.
*
* @access public
* @var string
*/
public $type = 'kirki-default';
}

View file

@ -0,0 +1,24 @@
<?php
/**
* An expanded section.
*
* @package Kirki
* @subpackage Custom Sections Module
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 2.2.0
*/
/**
* Expanded Section.
*/
class Kirki_Sections_Expanded_Section extends WP_Customize_Section {
/**
* The section type.
*
* @access public
* @var string
*/
public $type = 'kirki-expanded';
}

View file

@ -0,0 +1,78 @@
<?php
/**
* The default section.
* Inspired from https://github.com/justintadlock/trt-customizer-pro
*
* @package Kirki
* @subpackage Custom Sections Module
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 3.0.36
*/
/**
* Link Section.
*/
class Kirki_Sections_Link_Section extends WP_Customize_Section {
/**
* The section type.
*
* @since 3.0.36
* @access public
* @var string
*/
public $type = 'kirki-link';
/**
* Button Text
*
* @since 3.0.36
* @access public
* @var string
*/
public $button_text = '';
/**
* Button URL.
*
* @since 3.0.36
* @access public
* @var string
*/
public $button_url = '';
/**
* Gather the parameters passed to client JavaScript via JSON.
*
* @access public
* @since 3.0.36
* @return array The array to be exported to the client as JSON.
*/
public function json() {
$json = parent::json();
$json['button_text'] = $this->button_text;
$json['button_url'] = esc_url( $this->button_url );
return $json;
}
/**
* Outputs the Underscore.js template.
*
* @since 3.0.36
* @access public
* @return void
*/
protected function render_template() {
?>
<li id="accordion-section-{{ data.id }}" class="accordion-section control-section control-section-{{ data.type }} cannot-expand">
<h3 class="accordion-section-title">
{{ data.title }}
<a href="{{ data.button_url }}" class="button alignright" target="_blank" rel="nofollow">{{ data.button_text }}</a>
</h3>
</li>
<?php
}
}

View file

@ -0,0 +1,68 @@
<?php
/**
* Nested section.
*
* @package Kirki
* @subpackage Custom Sections Module
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 2.2.0
*/
/**
* Nested section.
*/
class Kirki_Sections_Nested_Section extends WP_Customize_Section {
/**
* The parent section.
*
* @access public
* @since 3.0.0
* @var string
*/
public $section;
/**
* The section type.
*
* @access public
* @since 3.0.0
* @var string
*/
public $type = 'kirki-nested';
/**
* Gather the parameters passed to client JavaScript via JSON.
*
* @access public
* @since 3.0.0
* @return array The array to be exported to the client as JSON.
*/
public function json() {
$array = wp_array_slice_assoc(
(array) $this,
array(
'id',
'description',
'priority',
'panel',
'type',
'description_hidden',
'section',
)
);
$array['title'] = html_entity_decode( $this->title, ENT_QUOTES, get_bloginfo( 'charset' ) );
$array['content'] = $this->get_content();
$array['active'] = $this->active();
$array['instanceNumber'] = $this->instance_number;
$array['customizeAction'] = esc_html__( 'Customizing', 'kirki' );
if ( $this->panel ) {
/* translators: The title. */
$array['customizeAction'] = sprintf( esc_html__( 'Customizing &#9656; %s', 'kirki' ), esc_html( $this->manager->get_panel( $this->panel )->title ) );
}
return $array;
}
}

View file

@ -0,0 +1,14 @@
/* global kirkiBranding */
jQuery( document ).ready( function() {
'use strict';
if ( '' !== kirkiBranding.logoImage ) {
jQuery( 'div#customize-info .preview-notice' ).replaceWith( '<img src="' + kirkiBranding.logoImage + '">' );
}
if ( '' !== kirkiBranding.description ) {
jQuery( 'div#customize-info > .customize-panel-description' ).replaceWith( '<div class="customize-panel-description">' + kirkiBranding.description + '</div>' );
}
} );

View file

@ -0,0 +1,88 @@
<?php
/**
* Changes the customizer's branding.
* For documentation please see
* https://github.com/aristath/kirki/wiki/Styling-the-Customizer
*
* @package Kirki
* @category Modules
* @author Ari Stathopoulos (@aristath)
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 3.0.0
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Adds styles to the customizer.
*/
class Kirki_Modules_Customizer_Branding {
/**
* The object instance.
*
* @static
* @access private
* @since 3.0.0
* @var object
*/
private static $instance;
/**
* Constructor.
*
* @access protected
* @since 3.0.0
*/
protected function __construct() {
add_action( 'customize_controls_print_scripts', array( $this, 'customize_controls_print_scripts' ) );
}
/**
* Gets an instance of this object.
* Prevents duplicate instances which avoid artefacts and improves performance.
*
* @static
* @access public
* @since 3.0.0
* @return object
*/
public static function get_instance() {
if ( ! self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Enqueues the script responsible for branding the customizer
* and also adds variables to it using the wp_localize_script function.
* The actual branding is handled via JS.
*
* @access public
* @since 3.0.0
*/
public function customize_controls_print_scripts() {
$config = apply_filters( 'kirki_config', array() );
$vars = array(
'logoImage' => '',
'description' => '',
);
if ( isset( $config['logo_image'] ) && '' !== $config['logo_image'] ) {
$vars['logoImage'] = esc_url_raw( $config['logo_image'] );
}
if ( isset( $config['description'] ) && '' !== $config['description'] ) {
$vars['description'] = esc_textarea( $config['description'] );
}
if ( ! empty( $vars['logoImage'] ) || ! empty( $vars['description'] ) ) {
wp_register_script( 'kirki-branding', Kirki::$url . '/modules/customizer-branding/branding.js', array(), KIRKI_VERSION, false );
wp_localize_script( 'kirki-branding', 'kirkiBranding', $vars );
wp_enqueue_script( 'kirki-branding' );
}
}
}

View file

@ -0,0 +1,422 @@
<?php
/**
* Changes the styling of the customizer
* based on the settings set using the kirki_config filter.
* For documentation please see
* https://github.com/aristath/kirki/wiki/Styling-the-Customizer
*
* @package Kirki
* @category Modules
* @author Ari Stathopoulos (@aristath)
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 3.0.0
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Adds styles to the customizer.
*/
class Kirki_Modules_Customizer_Styling {
/**
* The object instance.
*
* @static
* @access private
* @since 3.0.0
* @var object
*/
private static $instance;
/**
* Constructor.
*
* @access protected
*/
protected function __construct() {
add_action( 'customize_controls_print_styles', array( $this, 'custom_css' ), 99 );
}
/**
* Gets an instance of this object.
* Prevents duplicate instances which avoid artefacts and improves performance.
*
* @static
* @access public
* @since 3.0.0
* @return object
*/
public static function get_instance() {
if ( ! self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Add custom CSS rules to the head, applying our custom styles.
*
* @access public
*/
public function custom_css() {
$config = apply_filters( 'kirki_config', array() );
if ( ! isset( $config['color_accent'] ) && ! isset( $config['color_back'] ) ) {
return;
}
$back = isset( $config['color_back'] ) ? $config['color_back'] : false;
$text_on_back = '';
$border_on_back = '';
$back_on_back = '';
$hover_on_back = '';
$arrows_on_back = '';
$text_on_accent = '';
$border_on_accent = '';
$accent_disabled_obj = '';
$accent_disabled = '';
$text_on_accent_disabled = '';
$border_on_accent_disabled = '';
if ( $back ) {
$back_obj = ariColor::newColor( $back );
$text_on_back = ( 60 > $back_obj->lightness ) ? $back_obj->getNew( 'lightness', $back_obj->lightness + 60 )->toCSS( $back_obj->mode ) : $back_obj->getNew( 'lightness', $back_obj->lightness - 60 )->toCSS( $back_obj->mode );
$border_on_back = ( 80 < $back_obj->lightness ) ? $back_obj->getNew( 'lightness', $back_obj->lightness - 13 )->toCSS( $back_obj->mode ) : $back_obj->getNew( 'lightness', $back_obj->lightness + 13 )->toCSS( $back_obj->mode );
$back_on_back = ( 90 < $back_obj->lightness ) ? $back_obj->getNew( 'lightness', $back_obj->lightness - 6 )->toCSS( $back_obj->mode ) : $back_obj->getNew( 'lightness', $back_obj->lightness + 11 )->toCSS( $back_obj->mode );
$hover_on_back = ( 90 < $back_obj->lightness ) ? $back_obj->getNew( 'lightness', $back_obj->lightness - 3 )->toCSS( $back_obj->mode ) : $back_obj->getNew( 'lightness', $back_obj->lightness + 3 )->toCSS( $back_obj->mode );
$arrows_on_back = ( 50 > $back_obj->lightness ) ? $back_obj->getNew( 'lightness', $back_obj->lightness + 30 )->toCSS( $back_obj->mode ) : $back_obj->getNew( 'lightness', $back_obj->lightness - 30 )->toCSS( $back_obj->mode );
}
$accent = ( isset( $config['color_accent'] ) ) ? $config['color_accent'] : false;
if ( $accent ) {
$accent_obj = ariColor::newColor( $accent );
$text_on_accent = ( 60 > $accent_obj->lightness ) ? $accent_obj->getNew( 'lightness', $accent_obj->lightness + 60 )->toCSS( $accent_obj->mode ) : $accent_obj->getNew( 'lightness', $accent_obj->lightness - 60 )->toCSS( $accent_obj->mode );
$border_on_accent = ( 50 < $accent_obj->lightness ) ? $accent_obj->getNew( 'lightness', $accent_obj->lightness - 4 )->toCSS( $accent_obj->mode ) : $accent_obj->getNew( 'lightness', $accent_obj->lightness + 4 )->toCSS( $accent_obj->mode );
$accent_disabled_obj = ( 35 < $accent_obj->lightness ) ? $accent_obj->getNew( 'lightness', $accent_obj->lightness - 30 ) : $accent_obj->getNew( 'lightness', $accent_obj->lightness + 30 );
$accent_disabled = $accent_disabled_obj->toCSS( $accent_disabled_obj->mode );
$text_on_accent_disabled = ( 60 > $accent_disabled_obj->lightness ) ? $accent_disabled_obj->getNew( 'lightness', $accent_disabled_obj->lightness + 60 )->toCSS( $accent_disabled_obj->mode ) : $accent_disabled_obj->getNew( 'lightness', $accent_disabled_obj->lightness - 60 )->toCSS( $accent_disabled_obj->mode );
$border_on_accent_disabled = ( 50 < $accent_disabled_obj->lightness ) ? $accent_disabled_obj->getNew( 'lightness', $accent_disabled_obj->lightness - 4 )->toCSS( $accent_disabled_obj->mode ) : $accent_disabled_obj->getNew( 'lightness', $accent_disabled_obj->lightness + 4 )->toCSS( $accent_disabled_obj->mode );
}
?>
<style>
.wp-full-overlay-sidebar,
#customize-controls .customize-info .accordion-section-title,
#customize-controls .panel-meta.customize-info .accordion-section-title:hover,
#customize-theme-controls .accordion-section-title,
.customize-section-title,
#customize-theme-controls .control-section-themes .accordion-section-title,
#customize-theme-controls .control-section-themes .accordion-section-title,
#customize-theme-controls .control-section-themes .accordion-section-title:hover,
.outer-section-open #customize-controls .wp-full-overlay-sidebar-content,
#customize-sidebar-outer-content,
#customize-control-changeset_status .customize-inside-control-row,
#customize-control-changeset_preview_link input,
#customize-control-changeset_scheduled_date,
.wp-core-ui .wp-full-overlay .collapse-sidebar {
background: <?php echo esc_html( $back ); ?>;
background-color: <?php echo esc_html( $back ); ?>;
color: <?php echo esc_html( $text_on_back ); ?>;
}
<?php if ( $back ) : ?>
.media-widget-preview.media_image, .media-widget-preview.media_audio, .attachment-media-view {
background: none;
}
.wp-core-ui .button-link-delete {
color: <?php echo ( 90 > $back_obj->lightness ) ? '#FF8A80' : '#a00'; ?>;
}
.button.wp-color-result {
text-shadow: none !important;
}
<?php endif; ?>
#customize-sidebar-outer-content {
border-left-color: <?php echo esc_html( $border_on_back ); ?>;
border-right-color: <?php echo esc_html( $border_on_back ); ?>;
}
#customize-controls .customize-info .panel-title,
#customize-controls .customize-pane-child .customize-section-title h3,
#customize-controls .customize-pane-child h3.customize-section-title,
.customize-control,
#customize-controls .description {
color: <?php echo esc_html( $text_on_back ); ?>;
}
#customize-controls .customize-info,
#customize-header-actions,
.customize-section-title {
border-bottom-color: <?php echo esc_html( $border_on_back ); ?>;
}
.wp-full-overlay-sidebar .wp-full-overlay-header,
.customize-controls-close,
.expanded .wp-full-overlay-footer {
color: <?php echo esc_html( $text_on_back ); ?>;
background-color: <?php echo esc_html( $back_on_back ); ?>;
border-color: <?php echo esc_html( $border_on_back ); ?>;
}
.accordion-section,
#customize-theme-controls .customize-pane-child.accordion-section-content {
background: <?php echo esc_html( $back_on_back ); ?>;
}
#accordion-section-themes+.control-section,
#customize-theme-controls .control-section:last-of-type.open,
#customize-theme-controls .control-section:last-of-type > .accordion-section-title,
#customize-theme-controls .control-section.open {
border-bottom-color: <?php echo esc_html( $border_on_back ); ?>;
border-top-color: <?php echo esc_html( $border_on_back ); ?>;
}
#customize-theme-controls .accordion-section-title {
border-bottom-color: <?php echo esc_html( $border_on_back ); ?>;
border-left-color: <?php echo esc_html( $border_on_back ); ?>;
}
#customize-theme-controls .control-section-themes .accordion-section-title,
#customize-theme-controls .control-section-themes .accordion-section-title:hover {
border-bottom-color: <?php echo esc_html( $border_on_back ); ?>;
border-top-color: <?php echo esc_html( $border_on_back ); ?>;
border-bottom-color: <?php echo esc_html( $border_on_back ); ?>;
}
#customize-theme-controls .accordion-section-title:after {
color: <?php echo esc_html( $arrows_on_back ); ?>;
}
.wp-core-ui .button,
.wp-core-ui .button-secondary {
background-color: <?php echo esc_html( $back ); ?>;
border-color: <?php echo esc_html( $border_on_back ); ?>;
box-shadow: 0 1px 0 <?php echo esc_html( $border_on_back ); ?>;
-webkit-box-shadow: 0 1px 0 <?php echo esc_html( $border_on_back ); ?>;
text-shadow: 0 -1px 1px <?php echo esc_html( $border_on_back ); ?>, 1px 0 1px <?php echo esc_html( $border_on_back ); ?>, 0 1px 1px <?php echo esc_html( $border_on_back ); ?>, -1px 0 1px <?php echo esc_html( $border_on_back ); ?>;
color: <?php echo esc_html( $text_on_back ); ?>;
}
@media screen and (max-width: 640px) {
.customize-controls-preview-toggle{
background-color: <?php echo esc_html( $back ); ?>;
border-color: <?php echo esc_html( $border_on_back ); ?>;
box-shadow:0 1px 0 <?php echo esc_html( $border_on_back ); ?>;
-webkit-box-shadow:0 1px 0 <?php echo esc_html( $border_on_back ); ?>;
text-shadow:0 -1px 1px <?php echo esc_html( $border_on_back ); ?>, 1px 0 1px <?php echo esc_html( $border_on_back ); ?>, 0 1px 1px <?php echo esc_html( $border_on_back ); ?>, -1px 0 1px <?php echo esc_html( $border_on_back ); ?>;
color: <?php echo esc_html( $text_on_back ); ?>;
}
}
.wp-core-ui .button.focus,
.wp-core-ui .button.hover,
.wp-core-ui .button:focus,
.wp-core-ui .button:hover,
.wp-core-ui .button-secondary.focus,
.wp-core-ui .button-secondary.hover,
.wp-core-ui .button-secondary:focus,
.wp-core-ui .button-secondary:hover,
.customize-panel-back,
.customize-section-back {
background-color: <?php echo esc_html( $back_on_back ); ?>;
border-color: <?php echo esc_html( $border_on_back ); ?>;
box-shadow: 0 1px 0 <?php echo esc_html( $border_on_back ); ?>;
-webkit-box-shadow: 0 1px 0 <?php echo esc_html( $border_on_back ); ?>;
text-shadow: 0 -1px 1px <?php echo esc_html( $border_on_back ); ?>, 1px 0 1px <?php echo esc_html( $border_on_back ); ?>, 0 1px 1px <?php echo esc_html( $border_on_back ); ?>, -1px 0 1px <?php echo esc_html( $border_on_back ); ?>;
color: <?php echo esc_html( $text_on_back ); ?>;
}
@media screen and (max-width: 640px) {
.customize-controls-preview-toggle.focus,
.customize-controls-preview-toggle.hover,
.customize-controls-preview-toggle:focus,
.customize-controls-preview-toggle:hover {
background-color: <?php echo esc_html( $back_on_back ); ?>;
border-color: <?php echo esc_html( $border_on_back ); ?>;
box-shadow: 0 1px 0 <?php echo esc_html( $border_on_back ); ?>;
-webkit-box-shadow: 0 1px 0 <?php echo esc_html( $border_on_back ); ?>;
text-shadow: 0 -1px 1px <?php echo esc_html( $border_on_back ); ?>, 1px 0 1px <?php echo esc_html( $border_on_back ); ?>, 0 1px 1px <?php echo esc_html( $border_on_back ); ?>, -1px 0 1px <?php echo esc_html( $border_on_back ); ?>;
color:<?php echo esc_html( $text_on_back ); ?>;
}
}
.customize-control-kirki-background .background-attachment .buttonset .switch-label,
.customize-control-kirki-background .background-size .buttonset .switch-label,
.customize-control-kirki-radio-buttonset .buttonset .switch-label {
color: <?php echo esc_html( $text_on_back ); ?>;
}
.wp-color-result {
border-color: <?php echo esc_html( $border_on_back ); ?>;
-webkit-box-shadow: 0 1px 0 <?php echo esc_html( $border_on_back ); ?>;
box-shadow: 0 1px 0 <?php echo esc_html( $border_on_back ); ?>;
}
.wp-color-result:focus,
.wp-color-result:hover {
border-color: <?php echo esc_html( $border_on_back ); ?>;
background: <?php echo esc_html( $back_on_back ); ?>;
}
.wp-color-result:after {
border-color: <?php echo esc_html( $border_on_back ); ?>;
background: <?php echo esc_html( $back ); ?>;
color: <?php echo esc_html( $text_on_back ); ?>;
}
.wp-color-result:focus:after,
.wp-color-result:hover:after {
color: <?php echo esc_html( $text_on_back ); ?>;
}
.customize-control input[type=tel],
.customize-control input[type=url],
.customize-control input[type=text],
.customize-control input[type=password],
.customize-control input[type=email],
.customize-control input[type=number],
.customize-control input[type=search],
.customize-control input[type=radio],
.customize-control input[type=checkbox],
.customize-control select,
.select2-container--default .select2-selection--single,
.select2-container--default .select2-selection--multiple {
background: <?php echo esc_html( $back ); ?>;
border-color: <?php echo esc_html( $border_on_back ); ?>;
color: <?php echo esc_html( $text_on_back ); ?>;
}
.customize-control-kirki-slider input[type=range]::-webkit-slider-thumb {
background-color:<?php echo esc_html( $accent ); ?>;
}
.customize-control-kirki-slider input[type=range]::-moz-range-thumb {
background-color: <?php echo esc_html( $accent ); ?>;
}
.customize-control-kirki-slider input[type=range]::-ms-thumb {
background-color: <?php echo esc_html( $accent ); ?>;
}
.customize-control-kirki-slider input[type=range] {
background: <?php echo esc_html( $border_on_back ); ?>;
}
.select2-container--default .select2-selection--single .select2-selection__rendered {
color:<?php echo esc_html( $text_on_back ); ?>;
}
.wp-full-overlay-footer .devices {
background: none;
background: transparent;
box-shadow: none;
-webkit-box-shadow: none;
}
.kirki-reset-section .dashicons {
color: <?php echo esc_html( $back_on_back ); ?>;
}
#customize-controls .control-section .accordion-section-title:focus,
#customize-controls .control-section .accordion-section-title:hover,
#customize-controls .control-section.open .accordion-section-title,
#customize-controls .control-section:hover > .accordion-section-title,
.customize-panel-back:focus,
.customize-panel-back:hover,
.customize-section-back:focus,
.customize-section-back:hover {
background: <?php echo esc_html( $hover_on_back ); ?>;
color: <?php echo esc_html( $accent ); ?>;
border-left-color: <?php echo esc_html( $accent ); ?>;
}
.customize-controls-close:hover {
background-color: <?php echo esc_html( $back ); ?>;
color: <?php echo esc_html( $accent ); ?>;
border-color: <?php echo esc_html( $accent ); ?>;
}
#customize-theme-controls .control-section .accordion-section-title:focus:after,
#customize-theme-controls .control-section .accordion-section-title:hover:after,
#customize-theme-controls .control-section.open .accordion-section-title:after,
#customize-theme-controls .control-section:hover>.accordion-section-title:after {
color: <?php echo esc_html( $accent ); ?>;
}
.wp-core-ui .button.button-primary {
background-color: <?php echo esc_html( $accent ); ?>;
border-color: <?php echo esc_html( $border_on_accent ); ?>;
box-shadow: 0 1px 0 <?php echo esc_html( $border_on_accent ); ?>;
-webkit-box-shadow: 0 1px 0 <?php echo esc_html( $border_on_accent ); ?>;
text-shadow: 0 -1px 1px <?php echo esc_html( $border_on_accent ); ?>, 1px 0 1px <?php echo esc_html( $border_on_accent ); ?>, 0 1px 1px <?php echo esc_html( $border_on_accent ); ?>, -1px 0 1px <?php echo esc_html( $border_on_accent ); ?>;
color: <?php echo esc_html( $text_on_accent ); ?>;
}
.wp-core-ui .button.button-primary.focus,
.wp-core-ui .button.button-primary.hover,
.wp-core-ui .button.button-primary:focus,
.wp-core-ui .button.button-primary:hover {
background-color: <?php echo esc_html( $accent ); ?>;
border-color: <?php echo esc_html( $border_on_accent ); ?>;
box-shadow: 0 1px 0 <?php echo esc_html( $border_on_accent ); ?>;
-webkit-box-shadow: 0 1px 0 <?php echo esc_html( $border_on_accent ); ?>;
text-shadow: 0 -1px 1px <?php echo esc_html( $border_on_accent ); ?>, 1px 0 1px <?php echo esc_html( $border_on_accent ); ?>, 0 1px 1px <?php echo esc_html( $border_on_accent ); ?>, -1px 0 1px <?php echo esc_html( $border_on_accent ); ?>;
color: <?php echo esc_html( $text_on_accent ); ?>;
}
.wp-core-ui .button.button-primary-disabled,
.wp-core-ui .button.button-primary.disabled,
.wp-core-ui .button.button-primary:disabled,
.wp-core-ui .button.button-primary[disabled] {
background-color: <?php echo esc_html( $accent_disabled ); ?> !important;
border-color: <?php echo esc_html( $border_on_accent_disabled ); ?> !important;
box-shadow: 0 1px 0 <?php echo esc_html( $border_on_accent_disabled ); ?> !important;
-webkit-box-shadow: 0 1px 0 <?php echo esc_html( $border_on_accent_disabled ); ?> !important;
text-shadow: 0 -1px 1px <?php echo esc_html( $border_on_accent_disabled ); ?>, 1px 0 1px <?php echo esc_html( $border_on_accent_disabled ); ?>, 0 1px 1px <?php echo esc_html( $border_on_accent_disabled ); ?>, -1px 0 1px <?php echo esc_html( $border_on_accent_disabled ); ?> !important;
color: <?php echo esc_html( $text_on_accent_disabled ); ?> !important;
}
input[type=checkbox]:checked:before {
color: <?php echo esc_html( $accent ); ?>;
}
.select2-container--default .select2-results__option--highlighted[aria-selected] {
background-color: <?php echo esc_html( $accent ); ?>;
color: <?php echo esc_html( $text_on_accent ); ?>;
}
.customize-control-kirki-radio-buttonset .buttonset .switch-input:checked + .switch-label,
.customize-control-kirki-background .background-attachment .buttonset .switch-input:checked + .switch-label,
.customize-control-kirki-background .background-size .buttonset .switch-input:checked + .switch-label {
background-color: <?php echo esc_html( $accent ); ?>;
border-color: <?php echo esc_html( $border_on_accent ); ?>;
color: <?php echo esc_html( $text_on_accent ); ?>;
}
.notice,
div.updated,
div.error {
color: #444 !important;
}
<?php if ( isset( $config['width'] ) ) : ?>
.wp-full-overlay-sidebar {
width: <?php echo esc_html( $config['width'] ); ?>;
}
.expanded .wp-full-overlay-footer {
width: <?php echo esc_html( $config['width'] ); ?>;
}
.wp-full-overlay.expanded {
margin-left: <?php echo esc_html( $config['width'] ); ?>;
}
.wp-full-overlay.collapsed .wp-full-overlay-sidebar {
margin-left: -<?php echo esc_html( $config['width'] ); ?>;
}
<?php endif; ?>
</style>
<?php
}
}

View file

@ -0,0 +1,70 @@
<?php
/**
* Automatic field-dependencies scripts calculation for Kirki controls.
*
* @package Kirki
* @category Modules
* @author Ari Stathopoulos (@aristath)
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 3.0.0
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Adds styles to the customizer.
*/
class Kirki_Modules_Field_Dependencies {
/**
* The object instance.
*
* @static
* @access private
* @since 3.0.0
* @var object
*/
private static $instance;
/**
* Constructor.
*
* @access protected
* @since 3.0.0
*/
protected function __construct() {
add_action( 'customize_controls_enqueue_scripts', array( $this, 'field_dependencies' ) );
}
/**
* Gets an instance of this object.
* Prevents duplicate instances which avoid artefacts and improves performance.
*
* @static
* @access public
* @since 3.0.0
* @return object
*/
public static function get_instance() {
if ( ! self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Enqueues the field-dependencies script
* and adds variables to it using the wp_localize_script function.
* The rest is handled via JS.
*
* @access public
* @return void
*/
public function field_dependencies() {
wp_enqueue_script( 'kirki_field_dependencies', trailingslashit( Kirki::$url ) . 'modules/field-dependencies/field-dependencies.js', array( 'jquery', 'customize-base', 'customize-controls' ), KIRKI_VERSION, true );
}
}

View file

@ -0,0 +1,210 @@
var kirkiDependencies = {
listenTo: {},
init: function() {
var self = this;
wp.customize.control.each( function( control ) {
self.showKirkiControl( control );
} );
_.each( self.listenTo, function( slaves, master ) {
_.each( slaves, function( slave ) {
wp.customize( master, function( setting ) {
var setupControl = function( control ) {
var setActiveState,
isDisplayed;
isDisplayed = function() {
return self.showKirkiControl( wp.customize.control( slave ) );
};
setActiveState = function() {
control.active.set( isDisplayed() );
};
setActiveState();
setting.bind( setActiveState );
control.active.validate = isDisplayed;
};
wp.customize.control( slave, setupControl );
} );
} );
} );
},
/**
* Should we show the control?
*
* @since 3.0.17
* @param {string|object} control - The control-id or the control object.
* @returns {bool}
*/
showKirkiControl: function( control ) {
var self = this,
show = true,
isOption = (
control.params && // Check if control.params exists.
control.params.kirkiOptionType && // Check if option_type exists.
'option' === control.params.kirkiOptionType && // We're using options.
control.params.kirkiOptionName && // Check if option_name exists.
! _.isEmpty( control.params.kirkiOptionName ) // Check if option_name is not empty.
),
i;
if ( _.isString( control ) ) {
control = wp.customize.control( control );
}
// Exit early if control not found or if "required" argument is not defined.
if ( 'undefined' === typeof control || ( control.params && _.isEmpty( control.params.required ) ) ) {
return true;
}
// Loop control requirements.
for ( i = 0; i < control.params.required.length; i++ ) {
if ( ! self.checkCondition( control.params.required[ i ], control, isOption, 'AND' ) ) {
show = false;
}
}
return show;
},
/**
* Check a condition.
*
* @param {Object} requirement - The requirement, inherited from showKirkiControl.
* @param {Object} control - The control object.
* @param {bool} isOption - Whether it's an option or not.
* @param {string} relation - Can be one of 'AND' or 'OR'.
*/
checkCondition: function( requirement, control, isOption, relation ) {
var self = this,
childRelation = ( 'AND' === relation ) ? 'OR' : 'AND',
nestedItems,
i;
// Tweak for using active callbacks with serialized options instead of theme_mods.
if ( isOption && requirement.setting ) {
// Make sure we don't already have the option_name in there.
if ( -1 === requirement.setting.indexOf( control.params.kirkiOptionName + '[' ) ) {
requirement.setting = control.params.kirkiOptionName + '[' + requirement.setting + ']';
}
}
// If an array of other requirements nested, we need to process them separately.
if ( 'undefined' !== typeof requirement[0] && 'undefined' === typeof requirement.setting ) {
nestedItems = [];
// Loop sub-requirements.
for ( i = 0; i < requirement.length; i++ ) {
nestedItems.push( self.checkCondition( requirement[ i ], control, isOption, childRelation ) );
}
// OR relation. Check that true is part of the array.
if ( 'OR' === childRelation ) {
return ( -1 !== nestedItems.indexOf( true ) );
}
// AND relation. Check that false is not part of the array.
return ( -1 === nestedItems.indexOf( false ) );
}
// Early exit if setting is not defined.
if ( 'undefined' === typeof wp.customize.control( requirement.setting ) ) {
return true;
}
self.listenTo[ requirement.setting ] = self.listenTo[ requirement.setting ] || [];
if ( -1 === self.listenTo[ requirement.setting ].indexOf( control.id ) ) {
self.listenTo[ requirement.setting ].push( control.id );
}
return self.evaluate(
requirement.value,
wp.customize.control( requirement.setting ).setting._value,
requirement.operator
);
},
/**
* Figure out if the 2 values have the relation we want.
*
* @since 3.0.17
* @param {mixed} value1 - The 1st value.
* @param {mixed} value2 - The 2nd value.
* @param {string} operator - The comparison to use.
* @returns {bool}
*/
evaluate: function( value1, value2, operator ) {
var found = false;
if ( '===' === operator ) {
return value1 === value2;
}
if ( '==' === operator || '=' === operator || 'equals' === operator || 'equal' === operator ) {
return value1 == value2;
}
if ( '!==' === operator ) {
return value1 !== value2;
}
if ( '!=' === operator || 'not equal' === operator ) {
return value1 != value2;
}
if ( '>=' === operator || 'greater or equal' === operator || 'equal or greater' === operator ) {
return value2 >= value1;
}
if ( '<=' === operator || 'smaller or equal' === operator || 'equal or smaller' === operator ) {
return value2 <= value1;
}
if ( '>' === operator || 'greater' === operator ) {
return value2 > value1;
}
if ( '<' === operator || 'smaller' === operator ) {
return value2 < value1;
}
if ( 'contains' === operator || 'in' === operator ) {
if ( _.isArray( value1 ) && _.isArray( value2 ) ) {
_.each( value2, function( value ) {
if ( value1.includes( value ) ) {
found = true;
return false;
}
} );
return found;
}
if ( _.isArray( value2 ) ) {
_.each( value2, function( value ) {
if ( value == value1 ) { // jshint ignore:line
found = true;
}
} );
return found;
}
if ( _.isObject( value2 ) ) {
if ( ! _.isUndefined( value2[ value1 ] ) ) {
found = true;
}
_.each( value2, function( subValue ) {
if ( value1 === subValue ) {
found = true;
}
} );
return found;
}
if ( _.isString( value2 ) ) {
if ( _.isString( value1 ) ) {
return ( -1 < value1.indexOf( value2 ) && -1 < value2.indexOf( value1 ) );
}
return -1 < value1.indexOf( value2 );
}
}
return value1 == value2;
}
};
jQuery( document ).ready( function() {
kirkiDependencies.init();
} );

View file

@ -0,0 +1,276 @@
<?php
/**
* Gutenberg integration for Kirki.
*
* This class contains methods for integrating Kirki with
* the new WordPress core editor, Gutenberg. It provides
* fonts and styles to be output by the theme.
*
* @package Kirki
* @category Core
* @author Tim Elsass
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 3.0.35
*/
/**
* Wrapper class for static methods.
*
* @since 3.0.35
*/
class Kirki_Modules_Gutenberg {
/**
* The object instance.
*
* @static
* @access private
* @since 3.0.35
* @var object
*/
private static $instance;
/**
* Configuration reference.
*
* @access public
* @since 3.0.35
* @var object $configs
*/
private $configs;
/**
* Whether feature is enabled.
*
* @access public
* @since 3.0.35
* @var bool $enabled
*/
public $enabled;
/**
* CSS Module reference.
*
* @access public
* @since 3.0.35
* @var object $modules_css
*/
private $modules_css;
/**
* Webfonts Module reference.
*
* @access public
* @since 3.0.35
* @var object $modules_webfonts
*/
private $modules_webfonts;
/**
* Google Fonts reference.
*
* @access public
* @since 3.0.35
* @var object $google_fonts
*/
private $google_fonts;
/**
* Constructor.
*
* @access protected
* @since 3.0.0
*/
protected function __construct() {
add_action( 'admin_init', array( $this, 'init' ) );
}
/**
* Initialize Module.
*
* Sets class properties and add necessary hooks.
*
* @since 3.0.35
*/
public function init() {
$this->set_configs();
$this->set_enabled();
$this->set_modules_css();
$this->set_google_fonts();
$this->set_modules_webfonts();
$this->add_hooks();
}
/**
* Gets an instance of this object.
* Prevents duplicate instances which avoid artefacts and improves performance.
*
* @static
* @access public
* @since 3.0.0
* @return object
*/
public static function get_instance() {
if ( ! self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Add hooks for Gutenberg editor integration.
*
* @access protected
* @since 3.0.35
*/
protected function add_hooks() {
if ( ! $this->is_disabled() ) {
add_action( 'after_setup_theme', array( $this, 'add_theme_support' ), 999 );
add_filter( 'block_editor_settings', array( $this, 'enqueue' ) );
}
}
/**
* Add theme support for editor styles.
*
* This checks if theme has declared editor-styles support
* already, and if not present, declares it. Hooked late.
*
* @access public
* @since 3.0.35
*/
public function add_theme_support() {
if ( true !== get_theme_support( 'editor-styles' ) ) {
add_theme_support( 'editor-styles' );
}
}
/**
* Enqueue styles to Gutenberg Editor.
*
* @access public
* @param array $settings The settings for styles.
* @since 3.0.35
*/
public function enqueue( $settings ) {
$styles = $this->get_styles();
if ( ! empty( $styles ) ) {
$settings['styles'][] = array( 'css' => $styles );
}
return $settings;
}
/**
* Gets the styles to add to Gutenberg Editor.
*
* @access public
* @since 3.0.35
*
* @return string $styles String containing inline styles to add to Gutenberg.
*/
public function get_styles() {
$styles = null;
foreach ( $this->configs as $config_id => $args ) {
if ( true === $this->is_disabled( $args ) ) {
continue;
}
$modules_css = $this->modules_css;
$styles = $modules_css::loop_controls( $config_id ); // phpcs:ignore PHPCompatibility.Syntax.NewDynamicAccessToStatic
$styles = apply_filters( "kirki_gutenberg_{$config_id}_dynamic_css", $styles );
if ( empty( $styles ) ) {
continue;
}
}
return $styles;
}
/**
* Helper method to check if feature is disabled.
*
* Feature can be disabled by KIRKI_NO_OUTPUT constant,
* gutenbeg_support argument, and disabled output argument.
*
* @access public
* @param array $args An array of arguments.
* @since 3.0.35
*
* @return bool $disabled Is gutenberg integration feature disabled?
*/
private function is_disabled( $args = array() ) {
if ( defined( 'KIRKI_NO_OUTPUT' ) && true === KIRKI_NO_OUTPUT ) {
return true;
}
if ( ! empty( $args ) ) {
if ( isset( $args['disable_output'] ) && true === $args['disable_output'] ) {
return true;
}
if ( ! isset( $args['gutenberg_support'] ) || true !== $args['gutenberg_support'] ) {
return true;
}
}
return false;
}
/**
* Set class property for $configs.
*
* @access private
* @since 3.0.35
*/
private function set_configs() {
$this->configs = Kirki::$config;
return $this->configs;
}
/**
* Set class property for $enabled.
*
* @access private
* @since 3.0.35
*/
private function set_enabled() {
$this->enabled = ! $this->is_disabled();
}
/**
* Set class property for $modules_css.
*
* @access private
* @since 3.0.35
*/
private function set_modules_css() {
$this->modules_css = Kirki_Modules_CSS::get_instance();
}
/**
* Set class property for $google_fonts.
*
* @access private
* @since 3.0.35
*/
private function set_google_fonts() {
$this->google_fonts = Kirki_Fonts_Google::get_instance();
}
/**
* Set class property for $modules_webfonts.
*
* @access private
* @since 3.0.35
*/
private function set_modules_webfonts() {
$this->modules_webfonts = Kirki_Modules_Webfonts::get_instance();
}
}

View file

@ -0,0 +1,104 @@
<?php
/**
* Try to automatically generate the script necessary for adding icons to panels & section
*
* @package Kirki
* @category Core
* @author Ari Stathopoulos (@aristath)
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 3.0.0
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Adds scripts for icons in sections & panels.
*/
class Kirki_Modules_Icons {
/**
* The object instance.
*
* @static
* @access private
* @since 3.0.0
* @var object
*/
private static $instance;
/**
* An array of panels and sections with icons.
*
* @static
* @access private
* @var string
*/
private static $icons = array();
/**
* The class constructor.
*
* @access protected
*/
protected function __construct() {
add_action( 'customize_controls_enqueue_scripts', array( $this, 'customize_controls_enqueue_scripts' ), 99 );
}
/**
* Gets an instance of this object.
* Prevents duplicate instances which avoid artefacts and improves performance.
*
* @static
* @access public
* @since 3.0.0
* @return object
*/
public static function get_instance() {
if ( ! self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Adds icon for a section/panel.
*
* @access public
* @since 3.0.0
* @param string $id The panel or section ID.
* @param string $icon The icon to add.
* @param string $context Lowercase 'section' or 'panel'.
*/
public function add_icon( $id, $icon, $context = 'section' ) {
self::$icons[ $context ][ $id ] = trim( $icon );
}
/**
* Format the script in a way that will be compatible with WordPress.
*/
public function customize_controls_enqueue_scripts() {
$sections = Kirki::$sections;
$panels = Kirki::$panels;
// Parse sections and find ones with icons.
foreach ( $sections as $section ) {
if ( isset( $section['icon'] ) ) {
$this->add_icon( $section['id'], $section['icon'], 'section' );
}
}
// Parse panels and find ones with icons.
foreach ( $panels as $panel ) {
if ( isset( $panel['icon'] ) ) {
$this->add_icon( $panel['id'], $panel['icon'], 'panel' );
}
}
wp_enqueue_script( 'kirki_panel_and_section_icons', trailingslashit( Kirki::$url ) . 'modules/icons/icons.js', array( 'jquery', 'customize-base', 'customize-controls' ), KIRKI_VERSION, true );
wp_localize_script( 'kirki_panel_and_section_icons', 'kirkiIcons', self::$icons );
}
}

View file

@ -0,0 +1,30 @@
/* global kirkiIcons */
jQuery( document ).ready( function() {
'use strict';
if ( ! _.isUndefined( kirkiIcons.section ) ) {
// Parse sections and add icons.
_.each( kirkiIcons.section, function( icon, sectionID ) {
// Add icons in list.
jQuery( '#accordion-section-' + sectionID + ' > h3' ).addClass( 'dashicons-before ' + icon );
// Add icons on titles when a section is open.
jQuery( '#sub-accordion-section-' + sectionID + ' .customize-section-title > h3' ).append( '<span class="dashicons ' + icon + '" style="float:left;padding-right:.1em;padding-top:2px;"></span>' );
} );
}
if ( ! _.isUndefined( kirkiIcons.panel ) ) {
_.each( kirkiIcons.panel, function( icon, panelID ) {
// Add icons in lists & headers.
jQuery( '#accordion-panel-' + panelID + ' > h3, #sub-accordion-panel-' + panelID + ' .panel-title' ).addClass( 'dashicons-before ' + icon );
} );
}
} );

View file

@ -0,0 +1,167 @@
<?php
/**
* Adds a custom loading icon when the previewer refreshes.
*
* @package Kirki
* @subpackage Modules
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 3.0.0
*/
/**
* Modifies the loading overlay.
*/
class Kirki_Modules_Loading {
/**
* The object instance.
*
* @static
* @access private
* @since 3.0.0
* @var object
*/
private static $instance;
/**
* Constructor.
*
* @access protected
*/
protected function __construct() {
add_action( 'init', array( $this, 'init' ) );
}
/**
* Gets an instance of this object.
* Prevents duplicate instances which avoid artefacts and improves performance.
*
* @static
* @access public
* @since 3.0.0
* @return object
*/
public static function get_instance() {
if ( ! self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Runs on init.
*
* @access public
* @since 3.0.0
*/
public function init() {
global $wp_customize;
if ( ! $wp_customize ) {
return;
}
// Allow disabling the custom loader using the kirki_config filter.
$config = apply_filters( 'kirki_config', array() );
if ( isset( $config['disable_loader'] ) && true === $config['disable_loader'] ) {
return;
}
// Add the "loading" icon.
add_action( 'wp_footer', array( $this, 'add_loader_to_footer' ) );
add_action( 'wp_head', array( $this, 'add_loader_styles_to_header' ), 99 );
$this->remove_default_loading_styles();
}
/**
* Adds a custom "loading" div $ its styles when changes are made to the customizer.
*
* @access public
*/
public function add_loader_to_footer() {
?>
<div class="kirki-customizer-loading-wrapper">
<span class="kirki-customizer-loading"></span>
</div>
<?php
}
/**
* Adds the loader CSS to our `<head>`.
*
* @access public
*/
public function add_loader_styles_to_header() {
?>
<style>
body.wp-customizer-unloading {
opacity: 1;
cursor: progress !important;
-webkit-transition: none;
transition: none;
}
body.wp-customizer-unloading * {
pointer-events: none !important;
}
.kirki-customizer-loading-wrapper {
width: 100%;
height: 100%;
position: fixed;
top: 0;
left: 0;
background: rgba(255,255,255,0.83);
z-index: 999999;
display: none;
opacity: 0;
-webkit-transition: opacity 0.5s;
transition: opacity 0.5s;
background-image: url("<?php echo esc_url_raw( Kirki::$url ); ?>/assets/images/kirki-logo.svg");
background-repeat: no-repeat;
background-position: center center;
}
body.wp-customizer-unloading .kirki-customizer-loading-wrapper {
display: block;
opacity: 1;
}
.kirki-customizer-loading-wrapper .kirki-customizer-loading {
position: absolute;
width: 60px;
height: 60px;
top: 50%;
left: 50%;
margin: -30px;
background-color: rgba(0,0,0,.83);
border-radius: 100%;
-webkit-animation: sk-scaleout 1.0s infinite ease-in-out;
animation: sk-scaleout 1.0s infinite ease-in-out;
}
@-webkit-keyframes sk-scaleout {
0% { -webkit-transform: scale(0) }
100% {
-webkit-transform: scale(1.0);
opacity: 0;
}
}
@keyframes sk-scaleout {
0% {
-webkit-transform: scale(0);
transform: scale(0);
}
100% {
-webkit-transform: scale(1.0);
transform: scale(1.0);
opacity: 0;
}
}
</style>
<?php
}
/**
* Removes the default loader styles from WP Core.
*
* @access public
*/
public function remove_default_loading_styles() {
global $wp_customize;
remove_action( 'wp_head', array( $wp_customize, 'customize_preview_loading_style' ) );
}
}

View file

@ -0,0 +1,87 @@
<?php
/**
* Customize_Queried_Post_Info class.
*
* @package CustomizeQueriedPostInfo
*/
/**
* Class Customize_Queried_Post_Info.
*/
class Kirki_Modules_Post_Meta {
/**
* The object instance.
*
* @static
* @access private
* @since 3.0.0
* @var object
*/
private static $instance;
/**
* Gets an instance of this object.
* Prevents duplicate instances which avoid artefacts and improves performance.
*
* @static
* @access public
* @since 3.0.0
* @return object
*/
public static function get_instance() {
if ( ! self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Constructor.
*
* @access protected
* @since 3.1.0
*/
protected function __construct() {
add_action( 'customize_preview_init', array( $this, 'customize_preview_init' ) );
add_action( 'customize_controls_enqueue_scripts', array( $this, 'enqueue_control_scripts' ) );
}
/**
* Enqueue Customizer control scripts.
*
* @access public
* @since 3.1.0
*/
public function enqueue_control_scripts() {
wp_enqueue_script( 'kirki_post_meta_previewed_controls', trailingslashit( Kirki::$url ) . 'modules/post-meta/customize-controls.js', array( 'jquery', 'customize-controls' ), KIRKI_VERSION, true );
}
/**
* Initialize Customizer preview.
*
* @access public
* @since 3.1.0
*/
public function customize_preview_init() {
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_preview_scripts' ) );
}
/**
* Enqueue script for Customizer preview.
*
* @access public
* @since 3.1.0
*/
public function enqueue_preview_scripts() {
wp_enqueue_script( 'kirki_post_meta_previewed_preview', trailingslashit( Kirki::$url ) . 'modules/post-meta/customize-preview.js', array( 'jquery', 'customize-preview' ), KIRKI_VERSION, true );
$wp_scripts = wp_scripts();
$queried_post = null;
if ( is_singular() && get_queried_object() ) {
$queried_post = get_queried_object();
$queried_post->meta = get_post_custom( $queried_post->id );
}
$wp_scripts->add_data( 'kirki_post_meta_previewed_preview', 'data', sprintf( 'var _customizePostPreviewedQueriedObject = %s;', wp_json_encode( $queried_post ) ) );
}
}

View file

@ -0,0 +1,23 @@
jQuery( document ).ready( function() {
var self;
self = {
queriedPost: new wp.customize.Value()
};
// Listen for queried-post messages from the preview.
wp.customize.bind( 'ready', function() {
wp.customize.previewer.bind( 'queried-post', function( queriedPost ) {
self.queriedPost.set( queriedPost || false );
} );
} );
// Listen for post
self.queriedPost.bind( function( newPost, oldPost ) {
window.kirkiPost = false;
if ( newPost || oldPost ) {
window.kirkiPost = ( newPost ) ? newPost : oldPost;
}
} );
} );

View file

@ -0,0 +1,14 @@
/* global wp, _customizePostPreviewedQueriedObject */
jQuery( document ).ready( function() {
var self = {
queriedPost: ( ! _.isUndefined( _customizePostPreviewedQueriedObject ) ) ? _customizePostPreviewedQueriedObject : null
};
// Send the queried post object to the Customizer pane when ready.
wp.customize.bind( 'preview-ready', function() {
wp.customize.preview.bind( 'active', function() {
wp.customize.preview.send( 'queried-post', self.queriedPost );
} );
} );
} );

View file

@ -0,0 +1,79 @@
<?php
/**
* Automatic postMessage scripts calculation for Kirki controls.
*
* @package Kirki
* @category Modules
* @author Ari Stathopoulos (@aristath)
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 3.0.0
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Adds styles to the customizer.
*/
class Kirki_Modules_PostMessage {
/**
* The object instance.
*
* @static
* @access private
* @since 3.0.0
* @var object
*/
private static $instance;
/**
* Constructor.
*
* @access protected
* @since 3.0.0
*/
protected function __construct() {
add_action( 'customize_preview_init', array( $this, 'postmessage' ) );
}
/**
* Gets an instance of this object.
* Prevents duplicate instances which avoid artefacts and improves performance.
*
* @static
* @access public
* @since 3.0.0
* @return object
*/
public static function get_instance() {
if ( ! self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Enqueues the postMessage script
* and adds variables to it using the wp_localize_script function.
* The rest is handled via JS.
*/
public function postmessage() {
wp_enqueue_script( 'kirki_auto_postmessage', trailingslashit( Kirki::$url ) . 'modules/postmessage/postmessage.js', array( 'jquery', 'customize-preview' ), KIRKI_VERSION, true );
$fields = Kirki::$fields;
$data = array();
foreach ( $fields as $field ) {
if ( isset( $field['transport'] ) && 'postMessage' === $field['transport'] && isset( $field['js_vars'] ) && ! empty( $field['js_vars'] ) && is_array( $field['js_vars'] ) && isset( $field['settings'] ) ) {
$data[] = $field;
}
}
wp_localize_script( 'kirki_auto_postmessage', 'kirkiPostMessageFields', $data );
$extras = apply_filters( 'kirki_postmessage_script', false );
if ( $extras ) {
wp_add_inline_script( 'kirki_auto_postmessage', $extras, 'after' );
}
}
}

View file

@ -0,0 +1,344 @@
/* global kirkiPostMessageFields, WebFont */
var kirkiPostMessage = {
/**
* The fields.
*
* @since 3.0.26
*/
fields: {},
/**
* A collection of methods for the <style> tags.
*
* @since 3.0.26
*/
styleTag: {
/**
* Add a <style> tag in <head> if it doesn't already exist.
*
* @since 3.0.26
* @param {string} id - The field-ID.
* @returns {void}
*/
add: function( id ) {
if ( null === document.getElementById( 'kirki-postmessage-' + id ) || 'undefined' === typeof document.getElementById( 'kirki-postmessage-' + id ) ) {
jQuery( 'head' ).append( '<style id="kirki-postmessage-' + id + '"></style>' );
}
},
/**
* Add a <style> tag in <head> if it doesn't already exist,
* by calling the this.add method, and then add styles inside it.
*
* @since 3.0.26
* @param {string} id - The field-ID.
* @param {string} styles - The styles to add.
* @returns {void}
*/
addData: function( id, styles ) {
kirkiPostMessage.styleTag.add( id );
jQuery( '#kirki-postmessage-' + id ).text( styles );
}
},
/**
* Common utilities.
*
* @since 3.0.26
*/
util: {
/**
* Processes the value and applies any replacements and/or additions.
*
* @since 3.0.26
* @param {Object} output - The output (js_vars) argument.
* @param {mixed} value - The value.
* @param {string} controlType - The control-type.
* @returns {string|false} - Returns false if value is excluded, otherwise a string.
*/
processValue: function( output, value ) {
var self = this,
settings = window.parent.wp.customize.get(),
excluded = false;
if ( 'object' === typeof value ) {
_.each( value, function( subValue, key ) {
value[ key ] = self.processValue( output, subValue );
} );
return value;
}
output = _.defaults( output, {
prefix: '',
units: '',
suffix: '',
value_pattern: '$',
pattern_replace: {},
exclude: []
} );
if ( 1 <= output.exclude.length ) {
_.each( output.exclude, function( exclusion ) {
if ( value == exclusion ) {
excluded = true;
}
} );
}
if ( excluded ) {
return false;
}
value = output.value_pattern.replace( new RegExp( '\\$', 'g' ), value );
_.each( output.pattern_replace, function( id, placeholder ) {
if ( ! _.isUndefined( settings[ id ] ) ) {
value = value.replace( placeholder, settings[ id ] );
}
} );
return output.prefix + value + output.units + output.suffix;
},
/**
* Make sure urls are properly formatted for background-image properties.
*
* @since 3.0.26
* @param {string} url - The URL.
* @returns {string}
*/
backgroundImageValue: function( url ) {
return ( -1 === url.indexOf( 'url(' ) ) ? 'url(' + url + ')' : url;
}
},
/**
* A collection of utilities for CSS generation.
*
* @since 3.0.26
*/
css: {
/**
* Generates the CSS from the output (js_vars) parameter.
*
* @since 3.0.26
* @param {Object} output - The output (js_vars) argument.
* @param {mixed} value - The value.
* @param {string} controlType - The control-type.
* @returns {string}
*/
fromOutput: function( output, value, controlType ) {
var styles = '',
kirkiParent = window.parent.kirki,
googleFont = '',
mediaQuery = false,
processedValue;
if ( output.js_callback && 'function' === typeof window[ output.js_callback ] ) {
value = window[ output.js_callback[0] ]( value, output.js_callback[1] );
}
switch ( controlType ) {
case 'kirki-typography':
styles += output.element + '{';
_.each( value, function( val, key ) {
if ( output.choice && key !== output.choice ) {
return;
}
processedValue = kirkiPostMessage.util.processValue( output, val );
if ( false !== processedValue ) {
styles += key + ':' + processedValue + ';';
}
} );
styles += '}';
// Check if this is a googlefont so that we may load it.
if ( ! _.isUndefined( WebFont ) && value['font-family'] && 'google' === kirkiParent.util.webfonts.getFontType( value['font-family'] ) ) {
// Calculate the googlefont params.
googleFont = value['font-family'].replace( /\"/g, '&quot;' );
if ( value.variant ) {
if ( 'regular' === value.variant ) {
googleFont += ':400';
} else if ( 'italic' === value.variant ) {
googleFont += ':400i';
} else {
googleFont += ':' + value.variant;
}
}
googleFont += ':cyrillic,cyrillic-ext,devanagari,greek,greek-ext,khmer,latin,latin-ext,vietnamese,hebrew,arabic,bengali,gujarati,tamil,telugu,thai';
WebFont.load( {
google: {
families: [ googleFont ]
}
} );
}
break;
case 'kirki-background':
case 'kirki-dimensions':
case 'kirki-multicolor':
case 'kirki-sortable':
styles += output.element + '{';
_.each( value, function( val, key ) {
if ( output.choice && key !== output.choice ) {
return;
}
if ( 'background-image' === key ) {
val = kirkiPostMessage.util.backgroundImageValue( val );
}
processedValue = kirkiPostMessage.util.processValue( output, val );
if ( false !== processedValue ) {
// Mostly used for padding, margin & position properties.
if ( output.property ) {
styles += output.property;
if ( '' !== output.property && ( 'top' === key || 'bottom' === key || 'left' === key || 'right' === key ) ) {
styles += '-' + key;
}
styles += ':' + processedValue + ';';
} else {
styles += key + ':' + processedValue + ';';
}
}
} );
styles += '}';
break;
default:
if ( 'kirki-image' === controlType ) {
value = ( ! _.isUndefined( value.url ) ) ? kirkiPostMessage.util.backgroundImageValue( value.url ) : kirkiPostMessage.util.backgroundImageValue( value );
}
if ( _.isObject( value ) ) {
styles += output.element + '{';
_.each( value, function( val, key ) {
if ( output.choice && key !== output.choice ) {
return;
}
processedValue = kirkiPostMessage.util.processValue( output, val );
if ( ! output.property ) {
output.property = key;
}
if ( false !== processedValue ) {
styles += output.property + ':' + processedValue + ';';
}
} );
styles += '}';
} else {
processedValue = kirkiPostMessage.util.processValue( output, value );
if ( false !== processedValue ) {
styles += output.element + '{' + output.property + ':' + processedValue + ';}';
}
}
break;
}
// Get the media-query.
if ( output.media_query && 'string' === typeof output.media_query && ! _.isEmpty( output.media_query ) ) {
mediaQuery = output.media_query;
if ( -1 === mediaQuery.indexOf( '@media' ) ) {
mediaQuery = '@media ' + mediaQuery;
}
}
// If we have a media-query, add it and return.
if ( mediaQuery ) {
return mediaQuery + '{' + styles + '}';
}
// Return the styles.
return styles;
}
},
/**
* A collection of utilities to change the HTML in the document.
*
* @since 3.0.26
*/
html: {
/**
* Modifies the HTML from the output (js_vars) parameter.
*
* @since 3.0.26
* @param {Object} output - The output (js_vars) argument.
* @param {mixed} value - The value.
* @returns {string}
*/
fromOutput: function( output, value ) {
if ( output.js_callback && 'function' === typeof window[ output.js_callback ] ) {
value = window[ output.js_callback[0] ]( value, output.js_callback[1] );
}
if ( _.isObject( value ) || _.isArray( value ) ) {
if ( ! output.choice ) {
return;
}
_.each( value, function( val, key ) {
if ( output.choice && key !== output.choice ) {
return;
}
value = val;
} );
}
value = kirkiPostMessage.util.processValue( output, value );
if ( output.attr ) {
jQuery( output.element ).attr( output.attr, value );
} else {
jQuery( output.element ).html( value );
}
}
},
/**
* A collection of utilities to allow toggling a CSS class.
*
* @since 3.0.26
*/
toggleClass: {
/**
* Toggles a CSS class from the output (js_vars) parameter.
*
* @since 3.0.21
* @param {Object} output - The output (js_vars) argument.
* @param {mixed} value - The value.
* @returns {string}
*/
fromOutput: function( output, value ) {
if ( 'undefined' === typeof output.class || 'undefined' === typeof output.value ) {
return;
}
if ( value === output.value && ! jQuery( output.element ).hasClass( output.class ) ) {
jQuery( output.element ).addClass( output.class );
} else {
jQuery( output.element ).removeClass( output.class );
}
}
}
};
jQuery( document ).ready( function() {
_.each( kirkiPostMessageFields, function( field ) {
wp.customize( field.settings, function( value ) {
value.bind( function( newVal ) {
var styles = '';
_.each( field.js_vars, function( output ) {
if ( ! output.function || 'undefined' === typeof kirkiPostMessage[ output.function ] ) {
output.function = 'css';
}
if ( 'css' === output.function ) {
styles += kirkiPostMessage.css.fromOutput( output, newVal, field.type );
} else {
kirkiPostMessage[ output.function ].fromOutput( output, newVal, field.type );
}
} );
kirkiPostMessage.styleTag.addData( field.settings, styles );
} );
} );
} );
} );

View file

@ -0,0 +1,68 @@
<?php
/**
* Automatic preset scripts calculation for Kirki controls.
*
* @package Kirki
* @category Modules
* @author Ari Stathopoulos (@aristath)
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 3.0.26
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Adds styles to the customizer.
*/
class Kirki_Modules_Preset {
/**
* The object instance.
*
* @static
* @access private
* @since 3.0.26
* @var object
*/
private static $instance;
/**
* Constructor.
*
* @access protected
* @since 3.0.26
*/
protected function __construct() {
add_action( 'customize_controls_print_footer_scripts', array( $this, 'customize_controls_print_footer_scripts' ) );
}
/**
* Gets an instance of this object.
* Prevents duplicate instances which avoid artefacts and improves performance.
*
* @static
* @access public
* @since 3.0.26
* @return object
*/
public static function get_instance() {
if ( ! self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Enqueue scripts.
*
* @access public
* @since 3.0.26
*/
public function customize_controls_print_footer_scripts() {
wp_enqueue_script( 'kirki-preset', trailingslashit( Kirki::$url ) . 'modules/preset/preset.js', array( 'jquery' ), KIRKI_VERSION, false );
}
}

View file

@ -0,0 +1,32 @@
/* global kirkiSetSettingValue */
jQuery( document ).ready( function() {
// Loop Controls.
wp.customize.control.each( function( control ) {
// Check if we have a preset defined.
if ( control.params && control.params.preset && ! _.isEmpty( control.params.preset ) ) {
wp.customize( control.id, function( value ) {
// Listen to value changes.
value.bind( function( to ) {
// Loop preset definitions.
_.each( control.params.preset, function( preset, valueToListen ) {
// Check if the value set want is the same as the one we're looking for.
if ( valueToListen === to ) {
// Loop settings defined inside the preset.
_.each( preset.settings, function( controlValue, controlID ) {
// Set the value.
kirkiSetSettingValue.set( controlID, controlValue );
} );
}
} );
} );
} );
}
} );
} );

View file

@ -0,0 +1,89 @@
<?php
/**
* Handles sections created via the Kirki API.
*
* @package Kirki
* @category Modules
* @author Ari Stathopoulos (@aristath)
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 3.0.0
*/
/**
* Handle selective refreshes introduced in WordPress 4.5.
*/
class Kirki_Modules_Selective_Refresh {
/**
* The object instance.
*
* @static
* @access private
* @since 3.0.0
* @var object
*/
private static $instance;
/**
* Adds any necessary actions & filters.
*
* @access protected
*/
protected function __construct() {
add_action( 'customize_register', array( $this, 'register_partials' ), 99 );
}
/**
* Gets an instance of this object.
* Prevents duplicate instances which avoid artefacts and improves performance.
*
* @static
* @access public
* @since 3.0.0
* @return object
*/
public static function get_instance() {
if ( ! self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Parses all fields and searches for the "partial_refresh" argument inside them.
* If that argument is found, then it starts parsing the array of arguments.
* Registers a selective_refresh in the customizer for each one of them.
*
* @param object $wp_customize WP_Customize_Manager.
*/
public function register_partials( $wp_customize ) {
// Abort if selective refresh is not available.
if ( ! isset( $wp_customize->selective_refresh ) ) {
return;
}
// Get an array of all fields.
$fields = Kirki::$fields;
// Start parsing the fields.
foreach ( $fields as $field ) {
if ( isset( $field['partial_refresh'] ) && ! empty( $field['partial_refresh'] ) ) {
// Start going through each item in the array of partial refreshes.
foreach ( $field['partial_refresh'] as $partial_refresh => $partial_refresh_args ) {
// If we have all we need, create the selective refresh call.
if ( isset( $partial_refresh_args['render_callback'] ) && isset( $partial_refresh_args['selector'] ) ) {
$partial_refresh_args = wp_parse_args(
$partial_refresh_args,
array(
'settings' => $field['settings'],
)
);
$wp_customize->selective_refresh->add_partial( $partial_refresh, $partial_refresh_args );
}
}
}
}
}
}

View file

@ -0,0 +1,327 @@
<?php
/**
* Telemetry implementation for Kirki.
*
* @package Kirki
* @category Core
* @author Ari Stathopoulos (@aristath)
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license http://opensource.org/licenses/https://opensource.org/licenses/MIT
* @since 3.0.36
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Telemetry implementation.
*/
final class Kirki_Modules_Telemetry {
/**
* The object instance.
*
* @static
* @access private
* @since 3.0.36
* @var object
*/
private static $instance;
/**
* Constructor.
*
* @access protected
* @since 3.0.36
*/
protected function __construct() {
// Early exit if telemetry is disabled.
if ( ! apply_filters( 'kirki_telemetry', true ) ) {
return;
}
add_action( 'init', array( $this, 'init' ) );
add_action( 'admin_notices', array( $this, 'admin_notice' ) );
}
/**
* Gets an instance of this object.
* Prevents duplicate instances which avoid artefacts and improves performance.
*
* @static
* @access public
* @since 3.0.36
* @return object
*/
public static function get_instance() {
if ( ! self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Additional actions that run on init.
*
* @access public
* @since 3.0.36
* @return void
*/
public function init() {
$this->dismiss_notice();
$this->consent();
// This is the last thing to run. No impact on performance or anything else.
add_action( 'wp_footer', array( $this, 'maybe_send_data' ), 99999 );
}
/**
* Maybe send data.
*
* @access public
* @since 3.0.36
* @return void
*/
public function maybe_send_data() {
// Check if the user has consented to the data sending.
if ( ! get_option( 'kirki_telemetry_optin' ) ) {
return;
}
// Only send data once/month. We use an option instead of a transient
// because transients in some managed hosting environments don't properly update
// due to their caching implementations.
$sent = get_option( 'kirki_telemetry_sent' );
if ( ! $sent || $sent < time() - MONTH_IN_SECONDS ) {
$this->send_data();
update_option( 'kirki_telemetry_sent', time() );
}
}
/**
* Sends data.
*
* @access private
* @since 3.0.36
* @return void
*/
private function send_data() {
// Ping remote server.
wp_remote_post(
'https://wplemon.com/?action=kirki-stats',
array(
'method' => 'POST',
'blocking' => false,
'body' => array_merge(
array(
'action' => 'kirki-stats',
),
$this->get_data()
),
)
);
}
/**
* The admin-notice.
*
* @access private
* @since 3.0.36
* @return void
*/
public function admin_notice() {
// Early exit if the user has dismissed the consent, or if they have opted-in.
if ( get_option( 'kirki_telemetry_no_consent' ) || get_option( 'kirki_telemetry_optin' ) ) {
return;
}
$data = $this->get_data();
?>
<div class="notice notice-info kirki-telemetry">
<h3><strong><?php esc_html_e( 'Help us improve Kirki.', 'kirki' ); ?></strong></h3>
<p style="max-width: 76em;"><?php _e( 'Help us begin a dialogue with theme developers, collaborate and improve both the theme you are using and the Kirki framework by agreeing to send anonymous data. <strong>The data is completely anonymous and we will never collect any identifyable information about you or your website.</strong>', 'kirki' ); // phpcs:ignore WordPress.Security.EscapeOutput ?></p>
<table class="data-to-send hidden">
<thead>
<tr>
<th colspan="2"><?php esc_html_e( 'Data that will be sent', 'kirki' ); ?></th>
</tr>
</thead>
<tbody>
<tr>
<td style="min-width: 200px;"><?php esc_html_e( 'PHP Version', 'kirki' ); ?></td>
<td><code><?php echo esc_html( $data['phpVer'] ); ?></code></td>
</tr>
<tr>
<td><?php esc_html_e( 'Theme Name', 'kirki' ); ?></td>
<td><code><?php echo esc_html( $data['themeName'] ); ?></code></td>
</tr>
<tr>
<td><?php esc_html_e( 'Theme Author', 'kirki' ); ?></td>
<td><code><?php echo esc_html( $data['themeAuthor'] ); ?></code></td>
</tr>
<tr>
<td><?php esc_html_e( 'Theme URI', 'kirki' ); ?></td>
<td><code><?php echo esc_html( $data['themeURI'] ); ?></code></td>
</tr>
<tr>
<td><?php esc_html_e( 'Field Types Used', 'kirki' ); ?></td>
<td><code><?php echo esc_html( implode( ',', $data['fieldTypes'] ) ); ?></code></td>
</tr>
</tbody>
<tfoot>
<tr>
<th colspan="2">
<?php
printf(
/* translators: %1$s: URL to the server plugin code. %2$s: URL to the stats page. */
__( 'We believe in complete transparency. You can see the code used on our server <a href="%1$s" rel="nofollow">here</a>, and the results of the statistics we\'re gathering on <a href="%2$s" rel="nofollow">this page</a>.', 'kirki' ), // phpcs:ignore WordPress.Security.EscapeOutput
'https://github.com/aristath/kirki-telemetry-server',
'https://wplemon.com/kirki-telemetry-statistics/'
);
?>
</th>
</tr>
</tfoot>
</table>
<p class="actions">
<a href="<?php echo esc_url( wp_nonce_url( add_query_arg( 'kirki-consent-notice', 'telemetry' ) ) ); ?>" class="button button-primary consent"><?php esc_html_e( 'I agree', 'kirki' ); ?></a>
<a href="<?php echo esc_url( wp_nonce_url( add_query_arg( 'kirki-hide-notice', 'telemetry' ) ) ); ?>" class="button button-secondary dismiss"><?php esc_html_e( 'No thanks', 'kirki' ); ?></a>
<a class="button button-link details details-show"><?php esc_html_e( 'Show me the data', 'kirki' ); ?></a>
<a class="button button-link details details-hide hidden"><?php esc_html_e( 'Collapse data', 'kirki' ); ?></a>
</p>
<script>
jQuery( '.kirki-telemetry a.details' ).on( 'click', function() {
jQuery( '.kirki-telemetry .data-to-send' ).toggleClass( 'hidden' );
jQuery( '.kirki-telemetry a.details-show' ).toggleClass( 'hidden' );
jQuery( '.kirki-telemetry a.details-hide' ).toggleClass( 'hidden' );
});
</script>
</div>
<?php
$this->table_styles();
}
/**
* Builds and returns the data or uses cached if data already exists.
*
* @access private
* @since 3.0.36
* @return array
*/
private function get_data() {
// Get the theme.
$theme = wp_get_theme();
// Format the PHP version.
$php_version = phpversion( 'tidy' );
if ( ! $php_version ) {
$php_version = array_merge( explode( '.', phpversion() ), array( 0, 0 ) );
$php_version = "{$php_version[0]}.{$php_version[1]}";
}
// Build data and return the array.
return array(
'phpVer' => $php_version,
'themeName' => $theme->get( 'Name' ),
'themeAuthor' => $theme->get( 'Author' ),
'themeURI' => $theme->get( 'ThemeURI' ),
'fieldTypes' => $this->get_field_types(),
);
}
/**
* Get the field-types used.
*
* @access private
* @since 3.0.36
* @return array
*/
public function get_field_types() {
$types = array();
foreach ( Kirki::$fields as $field ) {
if ( isset( $field['type'] ) ) {
$types[] = $field['type'];
}
}
return $types;
}
/**
* Dismisses the notice.
*
* @access private
* @since 3.0.36
* @return void
*/
private function dismiss_notice() {
// Check if this is the request we want.
if ( isset( $_GET['_wpnonce'] ) && isset( $_GET['kirki-hide-notice'] ) ) {
if ( 'telemetry' === sanitize_text_field( wp_unslash( $_GET['kirki-hide-notice'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification
// Check the wp-nonce.
if ( wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ) ) ) {
// All good, we can save the option to dismiss this notice.
update_option( 'kirki_telemetry_no_consent', true );
}
}
}
}
/**
* Dismisses the notice.
*
* @access private
* @since 3.0.36
* @return void
*/
private function consent() {
// Check if this is the request we want.
if ( isset( $_GET['_wpnonce'] ) && isset( $_GET['kirki-consent-notice'] ) ) {
if ( 'telemetry' === sanitize_text_field( wp_unslash( $_GET['kirki-consent-notice'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification
// Check the wp-nonce.
if ( wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ) ) ) {
// All good, we can save the option to dismiss this notice.
update_option( 'kirki_telemetry_optin', true );
}
}
}
}
/**
* Prints the table styles.
*
* Normally we'd just use the .widefat CSS class for the table,
* however apparently there's an obscure bug in WP causing this: https://github.com/aristath/kirki/issues/2067
* This CSS is a copy of some styles from common.css in wp-core.
*
* @access private
* @since 3.0.37
* @return void
*/
private function table_styles() {
?>
<style>
/* .widefat - main style for tables */
.data-to-send { border-spacing: 0; width: 100%; clear: both; }
.data-to-send * { word-wrap: break-word; }
.data-to-send a, .data-to-send button.button-link { text-decoration: none; }
.data-to-send td, .data-to-send th { padding: 8px 10px; }
.data-to-send thead th, .data-to-send thead td { border-bottom: 1px solid #e1e1e1; }
.data-to-send tfoot th, .data-to-send tfoot td { border-top: 1px solid #e1e1e1; border-bottom: none; }
.data-to-send .no-items td { border-bottom-width: 0; }
.data-to-send td { vertical-align: top; }
.data-to-send td, .data-to-send td p, .data-to-send td ol, .data-to-send td ul { font-size: 13px; line-height: 1.5em; }
.data-to-send th, .data-to-send thead td, .data-to-send tfoot td { text-align: left; line-height: 1.3em; font-size: 14px; }
.data-to-send th input, .updates-table td input, .data-to-send thead td input, .data-to-send tfoot td input { margin: 0 0 0 8px; padding: 0; vertical-align: text-top; }
</style>
<?php
}
}

View file

@ -0,0 +1,118 @@
<?php
/**
* Injects tooltips to controls when the 'tooltip' argument is used.
*
* @package Kirki
* @category Modules
* @author Ari Stathopoulos (@aristath)
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 3.0.0
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Adds script for tooltips.
*/
class Kirki_Modules_Tooltips {
/**
* The object instance.
*
* @static
* @access private
* @since 3.0.0
* @var object
*/
private static $instance;
/**
* An array containing field identifieds and their tooltips.
*
* @access private
* @since 3.0.0
* @var array
*/
private $tooltips_content = array();
/**
* The class constructor
*
* @access protected
* @since 3.0.0
*/
protected function __construct() {
add_action( 'customize_controls_print_footer_scripts', array( $this, 'customize_controls_print_footer_scripts' ) );
}
/**
* Gets an instance of this object.
* Prevents duplicate instances which avoid artefacts and improves performance.
*
* @static
* @access public
* @since 3.0.0
* @return object
*/
public static function get_instance() {
if ( ! self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Parses fields and if any tooltips are found, they are added to the
* object's $tooltips_content property.
*
* @access private
* @since 3.0.0
*/
private function parse_fields() {
$fields = Kirki::$fields;
foreach ( $fields as $field ) {
if ( isset( $field['tooltip'] ) && ! empty( $field['tooltip'] ) ) {
// Get the control ID and properly format it for the tooltips.
$id = str_replace( '[', '-', str_replace( ']', '', $field['settings'] ) );
// Add the tooltips content.
$this->tooltips_content[ $id ] = array(
'id' => $id,
'content' => $field['tooltip'],
);
}
}
}
/**
* Allows us to add a tooltip to any control.
*
* @access public
* @since 4.2.0
* @param string $field_id The field-ID.
* @param string $tooltip The tooltip content.
*/
public function add_tooltip( $field_id, $tooltip ) {
$this->tooltips_content[ $field_id ] = array(
'id' => sanitize_key( $field_id ),
'content' => wp_kses_post( $tooltip ),
);
}
/**
* Enqueue scripts.
*
* @access public
* @since 3.0.0
*/
public function customize_controls_print_footer_scripts() {
$this->parse_fields();
wp_enqueue_script( 'kirki-tooltip', trailingslashit( Kirki::$url ) . 'modules/tooltips/tooltip.js', array( 'jquery' ), KIRKI_VERSION, false );
wp_localize_script( 'kirki-tooltip', 'kirkiTooltips', $this->tooltips_content );
wp_enqueue_style( 'kirki-tooltip', trailingslashit( Kirki::$url ) . 'modules/tooltips/tooltip.css', array(), KIRKI_VERSION );
}
}

View file

@ -0,0 +1,36 @@
@charset "UTF-8";
.tooltip-wrapper {
float: right;
position: relative;
}
.tooltip-wrapper .tooltip-trigger {
text-decoration: none;
cursor: help;
}
.tooltip-wrapper .tooltip-content {
position: absolute;
width: 200px;
height: auto;
top: -10px;
left: -225px;
background: #FFC107;
color: #000;
padding: 10px;
z-index: 99999;
border-radius: 3px;
line-height: 1.4em;
}
.tooltip-wrapper .tooltip-content a {
color: #000;
}
.tooltip-wrapper .tooltip-content:after {
content: "";
font-family: dashicons;
position: absolute;
right: -12px;
top: 11px;
color: #FFC107;
font-size: 20px;
}
/*# sourceMappingURL=tooltip.css.map */

View file

@ -0,0 +1,55 @@
/* global kirkiTooltips */
jQuery( document ).ready( function() {
function kirkiTooltipAdd( control ) {
_.each( kirkiTooltips, function( tooltip ) {
let trigger,
controlID,
content;
if ( tooltip.id !== control.id ) {
return;
}
if ( control.container.find( '.tooltip-content' ).length ) {
return;
}
trigger = '<span class="tooltip-trigger" data-setting="' + tooltip.id + '"><span class="dashicons dashicons-editor-help"></span></span>';
controlID = '#customize-control-' + tooltip.id;
content = '<div class="tooltip-content hidden" data-setting="' + tooltip.id + '">' + tooltip.content + '</div>';
// Add the trigger & content.
jQuery( '<div class="tooltip-wrapper">' + trigger + content + '</div>' ).prependTo( controlID );
// Handle onclick events.
jQuery( '.tooltip-trigger[data-setting="' + tooltip.id + '"]' ).on( 'click', function() {
jQuery( '.tooltip-content[data-setting="' + tooltip.id + '"]' ).toggleClass( 'hidden' );
} );
} );
// Close tooltips if we click anywhere else.
jQuery( document ).mouseup( function( e ) {
if ( ! jQuery( '.tooltip-content' ).is( e.target ) ) {
if ( ! jQuery( '.tooltip-content' ).hasClass( 'hidden' ) ) {
jQuery( '.tooltip-content' ).addClass( 'hidden' );
}
}
} );
}
wp.customize.control.each( function( control ) {
wp.customize.section( control.section(), function( section ) {
if ( section.expanded() || wp.customize.settings.autofocus.control === control.id ) {
kirkiTooltipAdd( control );
} else {
section.expanded.bind( function( expanded ) {
if ( expanded ) {
kirkiTooltipAdd( control );
}
} );
}
} );
} );
} );

View file

@ -0,0 +1,96 @@
<?php
/**
* WebFont-Loader Module.
*
* @see https://github.com/typekit/webfontloader
* @package Kirki
* @category Modules
* @author Ari Stathopoulos (@aristath)
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 3.0.26
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Adds script for tooltips.
*/
class Kirki_Modules_Webfont_Loader {
/**
* The object instance.
*
* @static
* @access private
* @since 3.0.26
* @var object
*/
private static $instance;
/**
* Only load the webfont script if this is true.
*
* @static
* @access public
* @since 3.0.26
* @var bool
*/
public static $load = false;
/**
* The class constructor
*
* @access protected
* @since 3.0.26
*/
protected function __construct() {
add_action( 'wp_head', array( $this, 'enqueue_scripts' ), 20 );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ), 20 );
}
/**
* Gets an instance of this object.
* Prevents duplicate instances which avoid artefacts and improves performance.
*
* @static
* @access public
* @since 3.0.26
* @return object
*/
public static function get_instance() {
if ( ! self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Enqueue scripts.
*
* @access public
* @since 3.0.26
* @return void
*/
public function enqueue_scripts() {
global $wp_customize;
if ( self::$load || $wp_customize || is_customize_preview() ) {
wp_enqueue_script( 'webfont-loader', trailingslashit( Kirki::$url ) . 'modules/webfont-loader/vendor-typekit/webfontloader.js', array(), '3.0.28', true );
}
}
/**
* Set the $load property of this object.
*
* @access public
* @since 3.0.35
* @param bool $load Set to false to disable loading.
* @return void
*/
public function set_load( $load ) {
self::$load = $load;
}
}

View file

@ -0,0 +1,2 @@
/* Web Font Loader v{{version}} - (c) Adobe Systems, Google. License: Apache 2.0 */
(function(){{{source}}}());

View file

@ -0,0 +1,46 @@
goog.provide('webfont.CssClassName');
/**
* Handles sanitization and construction of css class names.
* @param {string=} opt_joinChar The character to join parts of the name on.
* Defaults to '-'.
* @constructor
*/
webfont.CssClassName = function(opt_joinChar) {
/** @type {string} */
this.joinChar_ = opt_joinChar || webfont.CssClassName.DEFAULT_JOIN_CHAR;
};
/**
* @const
* @type {string}
*/
webfont.CssClassName.DEFAULT_JOIN_CHAR = '-';
goog.scope(function () {
var CssClassName = webfont.CssClassName;
/**
* Sanitizes a string for use as a css class name. Removes non-word and
* underscore characters.
* @param {string} name The string.
* @return {string} The sanitized string.
*/
CssClassName.prototype.sanitize = function(name) {
return name.replace(/[\W_]+/g, '').toLowerCase();
};
/**
* Builds a complete css class name given a variable number of parts.
* Sanitizes, then joins the parts together.
* @param {...string} var_args The parts to join.
* @return {string} The sanitized and joined string.
*/
CssClassName.prototype.build = function(var_args) {
var parts = []
for (var i = 0; i < arguments.length; i++) {
parts.push(this.sanitize(arguments[i]));
}
return parts.join(this.joinChar_);
};
});

View file

@ -0,0 +1,405 @@
goog.provide('webfont.DomHelper');
/**
* Handles common DOM manipulation tasks. The aim of this library is to cover
* the needs of typical font loading. Not more, not less.
* @param {Window} mainWindow The main window webfontloader.js is loaded in.
* @param {Window=} opt_loadWindow The window we'll load the font into. By
* default, the main window is used.
* @constructor
*/
webfont.DomHelper = function(mainWindow, opt_loadWindow) {
this.mainWindow_ = mainWindow;
this.loadWindow_ = opt_loadWindow || mainWindow;
/** @type {string} */
this.protocol_;
/** @type {Document} */
this.document_ = this.loadWindow_.document;
};
goog.scope(function () {
var DomHelper = webfont.DomHelper;
/**
* The NativeFontWatchRunnner depends on the correct and reliable
* |onload| event, and browsers with the native font loading API
* have reliable @onload support as far as we know. So we use the
* event for such a case and unconditionally invokes the callback
* otherwise.
*
* @const
* @type {boolean}
*/
DomHelper.CAN_WAIT_STYLESHEET = !!window['FontFace'];
/**
* Creates an element.
* @param {string} elem The element type.
* @param {Object=} opt_attr A hash of attribute key/value pairs.
* @param {string=} opt_innerHtml Contents of the element.
* @return {Element} the new element.
*/
DomHelper.prototype.createElement = function(elem, opt_attr,
opt_innerHtml) {
var domElement = this.document_.createElement(elem);
if (opt_attr) {
for (var attr in opt_attr) {
// protect against native prototype augmentations
if (opt_attr.hasOwnProperty(attr)) {
if (attr == "style") {
this.setStyle(domElement, opt_attr[attr]);
} else {
domElement.setAttribute(attr, opt_attr[attr]);
}
}
}
}
if (opt_innerHtml) {
domElement.appendChild(this.document_.createTextNode(opt_innerHtml));
}
return domElement;
};
/**
* Inserts an element into the document. This is intended for unambiguous
* elements such as html, body, head.
* @param {string} tagName The element name.
* @param {Element} e The element to append.
* @return {boolean} True if the element was inserted.
*/
DomHelper.prototype.insertInto = function(tagName, e) {
var t = this.document_.getElementsByTagName(tagName)[0];
if (!t) { // opera allows documents without a head
t = document.documentElement;
}
// This is safer than appendChild in IE. appendChild causes random
// JS errors in IE. Sometimes errors in other JS exectution, sometimes
// complete 'This page cannot be displayed' errors. For our purposes,
// it's equivalent because we don't need to insert at any specific
// location.
t.insertBefore(e, t.lastChild);
return true;
};
/**
* Calls a function when the body tag exists.
* @param {function()} callback The function to call.
*/
DomHelper.prototype.whenBodyExists = function(callback) {
var that = this;
if (that.document_.body) {
callback();
} else {
if (that.document_.addEventListener) {
that.document_.addEventListener('DOMContentLoaded', callback);
} else {
that.document_.attachEvent('onreadystatechange', function () {
if (that.document_.readyState == 'interactive' || that.document_.readyState == 'complete') {
callback();
}
});
}
}
};
/**
* Removes an element from the DOM.
* @param {Element} node The element to remove.
* @return {boolean} True if the element was removed.
*/
DomHelper.prototype.removeElement = function(node) {
if (node.parentNode) {
node.parentNode.removeChild(node);
return true;
}
return false;
};
/**
* @deprecated Use updateClassName().
*
* Appends a name to an element's class attribute.
* @param {Element} e The element.
* @param {string} name The class name to add.
*/
DomHelper.prototype.appendClassName = function(e, name) {
this.updateClassName(e, [name]);
};
/**
* @deprecated Use updateClassName().
*
* Removes a name to an element's class attribute.
* @param {Element} e The element.
* @param {string} name The class name to remove.
*/
DomHelper.prototype.removeClassName = function(e, name) {
this.updateClassName(e, null, [name]);
};
/**
* Updates an element's class attribute in a single change. This
* allows multiple updates in a single class name change so there
* is no chance for a browser to relayout in between changes.
*
* @param {Element} e The element.
* @param {Array.<string>=} opt_add List of class names to add.
* @param {Array.<string>=} opt_remove List of class names to remove.
*/
DomHelper.prototype.updateClassName = function (e, opt_add, opt_remove) {
var add = opt_add || [],
remove = opt_remove || [];
var classes = e.className.split(/\s+/);
for (var i = 0; i < add.length; i += 1) {
var found = false;
for (var j = 0; j < classes.length; j += 1) {
if (add[i] === classes[j]) {
found = true;
break;
}
}
if (!found) {
classes.push(add[i]);
}
}
var remainingClasses = [];
for (var i = 0; i < classes.length; i += 1) {
var found = false;
for (var j = 0; j < remove.length; j += 1) {
if (classes[i] === remove[j]) {
found = true;
break;
}
}
if (!found) {
remainingClasses.push(classes[i]);
}
}
e.className = remainingClasses.join(' ')
.replace(/\s+/g, ' ')
.replace(/^\s+|\s+$/, '');
};
/**
* Returns true if an element has a given class name and false otherwise.
* @param {Element} e The element.
* @param {string} name The class name to check for.
* @return {boolean} Whether or not the element has this class name.
*/
DomHelper.prototype.hasClassName = function(e, name) {
var classes = e.className.split(/\s+/);
for (var i = 0, len = classes.length; i < len; i++) {
if (classes[i] == name) {
return true;
}
}
return false;
};
/**
* Sets the style attribute on an element.
* @param {Element} e The element.
* @param {string} styleString The style string.
*/
DomHelper.prototype.setStyle = function(e, styleString) {
e.style.cssText = styleString;
};
/**
* @return {Window} The main window webfontloader.js is loaded in (for config).
*/
DomHelper.prototype.getMainWindow = function() {
return this.mainWindow_;
};
/**
* @return {Window} The window that we're loading the font(s) into.
*/
DomHelper.prototype.getLoadWindow = function() {
return this.loadWindow_;
};
/**
* Returns the hostname of the current document.
* @return {string} hostname.
*/
DomHelper.prototype.getHostName = function() {
return this.getLoadWindow().location.hostname || this.getMainWindow().location.hostname;
};
/**
* Creates a style element.
* @param {string} css Contents of the style element.
* @return {Element} a DOM element.
*/
DomHelper.prototype.createStyle = function(css) {
var e = this.createElement('style');
e.setAttribute('type', 'text/css');
if (e.styleSheet) { // IE
e.styleSheet.cssText = css;
} else {
e.appendChild(document.createTextNode(css));
}
return e;
};
/**
* Loads an external stylesheet.
*
* @param {string} href the URL of the stylesheet
* @param {function(Error)=} opt_callback Called when the stylesheet has loaded or failed to
* load. Note that the callback is *NOT* guaranteed to be called in all browsers. The first
* argument to the callback is an error object that is falsy when there are no errors and
* truthy when there are.
* @param {boolean=} opt_async True if the stylesheet should be loaded asynchronously. Defaults to false.
* @return {Element} The link element
*/
DomHelper.prototype.loadStylesheet = function (href, opt_callback, opt_async) {
var link = this.createElement('link', {
'rel': 'stylesheet',
'href': href,
'media': (opt_async ? 'only x' : 'all')
});
var sheets = this.document_.styleSheets,
eventFired = false,
asyncResolved = !opt_async,
callbackArg = null,
callback = opt_callback || null;
function mayInvokeCallback() {
if (callback && eventFired && asyncResolved) {
callback(callbackArg);
callback = null;
}
}
if (DomHelper.CAN_WAIT_STYLESHEET) {
link.onload = function () {
eventFired = true;
mayInvokeCallback();
};
link.onerror = function () {
eventFired = true;
callbackArg = new Error('Stylesheet failed to load');
mayInvokeCallback();
};
} else {
// Some callers expect opt_callback being called asynchronously.
setTimeout(function () {
eventFired = true;
mayInvokeCallback();
}, 0);
}
function onStylesheetAvailable(callback) {
for (var i = 0; i < sheets.length; i++) {
if (sheets[i].href && sheets[i].href.indexOf(href) !== -1) {
return callback();
}
}
setTimeout(function () {
onStylesheetAvailable(callback);
}, 0);
}
function onMediaAvailable(callback) {
for (var i = 0; i < sheets.length; i++) {
if (sheets[i].href && sheets[i].href.indexOf(href) !== -1 && sheets[i].media) {
/**
* @type {string|MediaList|null}
*/
var media = sheets[i].media;
if (media === "all" || (media.mediaText && media.mediaText === "all")) {
return callback();
}
}
}
setTimeout(function () {
onMediaAvailable(callback);
}, 0);
}
this.insertInto('head', link);
if (opt_async) {
onStylesheetAvailable(function () {
link.media = "all";
// The media type change doesn't take effect immediately on Chrome, so
// we'll query the media attribute on the stylesheet until it changes
// to "all".
onMediaAvailable(function () {
asyncResolved = true;
mayInvokeCallback();
});
});
}
return link;
};
/**
* Loads an external script file.
* @param {string} src URL of the script.
* @param {function(Error)=} opt_callback callback when the script has loaded. The first argument to
* the callback is an error object that is falsy when there are no errors and truthy when there are.
* @param {number=} opt_timeout The number of milliseconds after which the callback will be called
* with a timeout error. Defaults to 5 seconds.
* @return {Element} The script element
*/
DomHelper.prototype.loadScript = function(src, opt_callback, opt_timeout) {
var head = this.document_.getElementsByTagName('head')[0];
if (head) {
var script = this.createElement('script', {
'src': src
});
var done = false;
script.onload = script.onreadystatechange = function() {
if (!done && (!this.readyState || this.readyState == 'loaded' || this.readyState == 'complete')) {
done = true;
if (opt_callback) {
opt_callback(null);
}
script.onload = script.onreadystatechange = null;
// Avoid a bizarre issue with unclosed <base> tag in IE6 - http://blog.dotsmart.net/2008/04/
if (script.parentNode.tagName == 'HEAD') head.removeChild(script);
}
};
head.appendChild(script);
setTimeout(function () {
if (!done) {
done = true;
if (opt_callback) {
opt_callback(new Error('Script load timeout'));
}
}
}, opt_timeout || 5000);
return script;
}
return null;
};
});

View file

@ -0,0 +1,195 @@
goog.provide('webfont.EventDispatcher');
goog.require('webfont.CssClassName');
/**
* A class to dispatch events and manage the event class names on an html
* element that represent the current state of fonts on the page. Active class
* names always overwrite inactive class names of the same type, while loading
* class names may be present whenever a font is loading (regardless of if an
* associated active or inactive class name is also present).
*
* @param {webfont.DomHelper} domHelper
* @param {Object} config
* @constructor
*/
webfont.EventDispatcher = function(domHelper, config) {
this.domHelper_ = domHelper;
this.htmlElement_ = domHelper.getLoadWindow().document.documentElement;
this.callbacks_ = config;
this.namespace_ = webfont.EventDispatcher.DEFAULT_NAMESPACE;
this.cssClassName_ = new webfont.CssClassName('-');
this.dispatchEvents_ = config['events'] !== false;
this.setClasses_ = config['classes'] !== false;
};
/**
* @const
* @type {string}
*/
webfont.EventDispatcher.DEFAULT_NAMESPACE = 'wf';
/**
* @const
* @type {string}
*/
webfont.EventDispatcher.LOADING = 'loading';
/**
* @const
* @type {string}
*/
webfont.EventDispatcher.ACTIVE = 'active';
/**
* @const
* @type {string}
*/
webfont.EventDispatcher.INACTIVE = 'inactive';
/**
* @const
* @type {string}
*/
webfont.EventDispatcher.FONT = 'font';
goog.scope(function () {
var EventDispatcher = webfont.EventDispatcher;
/**
* Dispatch the loading event and append the loading class name.
*/
EventDispatcher.prototype.dispatchLoading = function() {
if (this.setClasses_) {
this.domHelper_.updateClassName(this.htmlElement_,
[
this.cssClassName_.build(this.namespace_, webfont.EventDispatcher.LOADING)
]
);
}
this.dispatch_(webfont.EventDispatcher.LOADING);
};
/**
* Dispatch the font loading event and append the font loading class name.
* @param {webfont.Font} font
*/
EventDispatcher.prototype.dispatchFontLoading = function(font) {
if (this.setClasses_) {
this.domHelper_.updateClassName(this.htmlElement_,
[
this.cssClassName_.build(this.namespace_, font.getName(), font.getVariation().toString(), webfont.EventDispatcher.LOADING)
]
);
}
this.dispatch_(webfont.EventDispatcher.FONT + webfont.EventDispatcher.LOADING, font);
};
/**
* Dispatch the font active event, remove the font loading class name, remove
* the font inactive class name, and append the font active class name.
* @param {webfont.Font} font
*/
EventDispatcher.prototype.dispatchFontActive = function(font) {
if (this.setClasses_) {
this.domHelper_.updateClassName(
this.htmlElement_,
[
this.cssClassName_.build(this.namespace_, font.getName(), font.getVariation().toString(), webfont.EventDispatcher.ACTIVE)
],
[
this.cssClassName_.build(this.namespace_, font.getName(), font.getVariation().toString(), webfont.EventDispatcher.LOADING),
this.cssClassName_.build(this.namespace_, font.getName(), font.getVariation().toString(), webfont.EventDispatcher.INACTIVE)
]
);
}
this.dispatch_(webfont.EventDispatcher.FONT + webfont.EventDispatcher.ACTIVE, font);
};
/**
* Dispatch the font inactive event, remove the font loading class name, and
* append the font inactive class name (unless the font active class name is
* already present).
* @param {webfont.Font} font
*/
EventDispatcher.prototype.dispatchFontInactive = function(font) {
if (this.setClasses_) {
var hasFontActive = this.domHelper_.hasClassName(this.htmlElement_,
this.cssClassName_.build(this.namespace_, font.getName(), font.getVariation().toString(), webfont.EventDispatcher.ACTIVE)
),
add = [],
remove = [
this.cssClassName_.build(this.namespace_, font.getName(), font.getVariation().toString(), webfont.EventDispatcher.LOADING)
];
if (!hasFontActive) {
add.push(this.cssClassName_.build(this.namespace_, font.getName(), font.getVariation().toString(), webfont.EventDispatcher.INACTIVE));
}
this.domHelper_.updateClassName(this.htmlElement_, add, remove);
}
this.dispatch_(webfont.EventDispatcher.FONT + webfont.EventDispatcher.INACTIVE, font);
};
/**
* Dispatch the inactive event, remove the loading class name, and append the
* inactive class name (unless the active class name is already present).
*/
EventDispatcher.prototype.dispatchInactive = function() {
if (this.setClasses_) {
var hasActive = this.domHelper_.hasClassName(this.htmlElement_,
this.cssClassName_.build(this.namespace_, webfont.EventDispatcher.ACTIVE)
),
add = [],
remove = [
this.cssClassName_.build(this.namespace_, webfont.EventDispatcher.LOADING)
];
if (!hasActive) {
add.push(this.cssClassName_.build(this.namespace_, webfont.EventDispatcher.INACTIVE));
}
this.domHelper_.updateClassName(this.htmlElement_, add, remove);
}
this.dispatch_(webfont.EventDispatcher.INACTIVE);
};
/**
* Dispatch the active event, remove the loading class name, remove the inactive
* class name, and append the active class name.
*/
EventDispatcher.prototype.dispatchActive = function() {
if (this.setClasses_) {
this.domHelper_.updateClassName(this.htmlElement_,
[
this.cssClassName_.build(this.namespace_, webfont.EventDispatcher.ACTIVE)
],
[
this.cssClassName_.build(this.namespace_, webfont.EventDispatcher.LOADING),
this.cssClassName_.build(this.namespace_, webfont.EventDispatcher.INACTIVE)
]
);
}
this.dispatch_(webfont.EventDispatcher.ACTIVE);
};
/**
* @param {string} event
* @param {webfont.Font=} opt_font
*/
EventDispatcher.prototype.dispatch_ = function(event, opt_font) {
if (this.dispatchEvents_ && this.callbacks_[event]) {
if (opt_font) {
this.callbacks_[event](opt_font.getName(), opt_font.getVariation());
} else {
this.callbacks_[event]();
}
}
};
});

View file

@ -0,0 +1,140 @@
goog.provide('webfont.Font');
/**
* This class is an abstraction for a single font or typeface.
* It contains the font name and the variation (i.e. style
* and weight.) A collection Font instances can represent a
* font family.
*
* @constructor
* @param {string} name The font family name
* @param {string=} opt_variation A font variation description
*/
webfont.Font = function (name, opt_variation) {
this.name_ = name;
this.weight_ = 4;
this.style_ = 'n'
var variation = opt_variation || 'n4',
match = variation.match(/^([nio])([1-9])$/i);
if (match) {
this.style_ = match[1];
this.weight_ = parseInt(match[2], 10);
}
};
goog.scope(function () {
var Font = webfont.Font;
/**
* @return {string}
*/
Font.prototype.getName = function () {
return this.name_;
};
/**
* @return {string}
*/
Font.prototype.getCssName = function () {
return this.quote_(this.name_);
};
/**
* Returns a CSS string representation of the font that
* can be used as the CSS font property shorthand.
*
* @return {string}
*/
Font.prototype.toCssString = function () {
return this.getCssStyle() + ' ' + this.getCssWeight() + ' 300px ' + this.getCssName();
};
/**
* @private
* @param {string} name
* @return {string}
*/
Font.prototype.quote_ = function (name) {
var quoted = [];
var split = name.split(/,\s*/);
for (var i = 0; i < split.length; i++) {
var part = split[i].replace(/['"]/g, '');
if (part.indexOf(' ') == -1 && !(/^\d/.test(part))) {
quoted.push(part);
} else {
quoted.push("'" + part + "'");
}
}
return quoted.join(',');
};
/**
* @return {string}
*/
Font.prototype.getVariation = function () {
return this.style_ + this.weight_;
};
/**
* @return {string}
*/
Font.prototype.getCssVariation = function () {
return 'font-style:' + this.getCssStyle() + ';font-weight:' + this.getCssWeight() + ';';
};
/**
* @return {string}
*/
Font.prototype.getCssWeight = function () {
return this.weight_ + '00';
};
/**
* @return {string}
*/
Font.prototype.getCssStyle = function () {
var style = 'normal';
if (this.style_ === 'o') {
style = 'oblique';
} else if (this.style_ === 'i') {
style = 'italic';
}
return style;
};
/**
* Parses a CSS font declaration and returns a font
* variation description.
*
* @param {string} css
* @return {string}
*/
Font.parseCssVariation = function (css) {
var weight = 4,
style = 'n',
m = null;
if (css) {
m = css.match(/(normal|oblique|italic)/i);
if (m && m[1]) {
style = m[1].substr(0, 1).toLowerCase();
}
m = css.match(/([1-9]00|normal|bold)/i);
if (m && m[1]) {
if (/bold/i.test(m[1])) {
weight = 7;
} else if (/[1-9]00/.test(m[1])) {
weight = parseInt(m[1].substr(0, 1), 10);
}
}
}
return style + weight;
}
});

View file

@ -0,0 +1,16 @@
goog.provide('webfont.FontModule');
/**
* @interface
*/
webfont.FontModule = function () {};
goog.scope(function () {
var FontModule = webfont.FontModule;
/**
* @param {function(Array.<webfont.Font>, webfont.FontTestStrings=, Object.<string, boolean>=)} onReady
*/
FontModule.prototype.load = function (onReady) {};
});

View file

@ -0,0 +1,47 @@
goog.provide('webfont.FontModuleLoader');
goog.provide('webfont.FontModuleFactory');
/** @typedef {function(Object, webfont.DomHelper): webfont.FontModule} */
webfont.FontModuleFactory;
/**
* @constructor
*/
webfont.FontModuleLoader = function() {
/**
* @type {Object.<string, webfont.FontModuleFactory>}
*/
this.modules_ = {};
};
goog.scope(function () {
var FontModuleLoader = webfont.FontModuleLoader;
/**
* @param {string} name
* @param {webfont.FontModuleFactory} factory
*/
FontModuleLoader.prototype.addModuleFactory = function(name, factory) {
this.modules_[name] = factory;
};
/**
* @param {Object} configuration
* @param {webfont.DomHelper} domHelper
* @return {Array.<webfont.FontModule>}
*/
FontModuleLoader.prototype.getModules = function(configuration, domHelper) {
var modules = [];
for (var key in configuration) {
if (configuration.hasOwnProperty(key)) {
var moduleFactory = this.modules_[key];
if (moduleFactory) {
modules.push(moduleFactory(configuration[key], domHelper));
}
}
}
return modules;
};
});

View file

@ -0,0 +1,60 @@
goog.provide('webfont.FontRuler');
/**
* An element that can be used to measure the metrics
* of a given font and string.
* @constructor
* @param {webfont.DomHelper} domHelper
* @param {string} fontTestString
*/
webfont.FontRuler = function (domHelper, fontTestString) {
this.domHelper_ = domHelper;
this.fontTestString_ = fontTestString;
this.el_ = this.domHelper_.createElement('span', {
"aria-hidden": "true"
}, this.fontTestString_);
};
goog.scope(function () {
var FontRuler = webfont.FontRuler;
/**
* @param {webfont.Font} font
*/
FontRuler.prototype.setFont = function(font) {
this.domHelper_.setStyle(this.el_, this.computeStyleString_(font));
};
/**
* Inserts the ruler into the DOM.
*/
FontRuler.prototype.insert = function() {
this.domHelper_.insertInto('body', this.el_);
};
/**
* @private
* @param {webfont.Font} font
* @return {string}
*/
FontRuler.prototype.computeStyleString_ = function(font) {
return "display:block;position:absolute;top:-9999px;left:-9999px;" +
"font-size:300px;width:auto;height:auto;line-height:normal;margin:0;" +
"padding:0;font-variant:normal;white-space:nowrap;font-family:" +
font.getCssName() + ";" + font.getCssVariation();
};
/**
* @return {number}
*/
FontRuler.prototype.getWidth = function() {
return this.el_.offsetWidth;
};
/**
* Removes the ruler element from the DOM.
*/
FontRuler.prototype.remove = function() {
this.domHelper_.removeElement(this.el_);
};
});

View file

@ -0,0 +1,171 @@
goog.provide('webfont.FontWatcher');
goog.require('webfont.FontWatchRunner');
goog.require('webfont.NativeFontWatchRunner');
/**
* @typedef {Object.<string, Array.<string>>}
*/
webfont.FontTestStrings;
/**
* @constructor
* @param {webfont.DomHelper} domHelper
* @param {webfont.EventDispatcher} eventDispatcher
* @param {number=} opt_timeout
*/
webfont.FontWatcher = function(domHelper, eventDispatcher, opt_timeout) {
this.domHelper_ = domHelper;
this.eventDispatcher_ = eventDispatcher;
this.currentlyWatched_ = 0;
this.last_ = false;
this.success_ = false;
this.timeout_ = opt_timeout;
};
goog.scope(function () {
var FontWatcher = webfont.FontWatcher,
FontWatchRunner = webfont.FontWatchRunner,
NativeFontWatchRunner = webfont.NativeFontWatchRunner;
/**
* @type {null|boolean}
*/
FontWatcher.SHOULD_USE_NATIVE_LOADER = null;
/**
* @return {string}
*/
FontWatcher.getUserAgent = function () {
return window.navigator.userAgent;
};
/**
* @return {string}
*/
FontWatcher.getVendor = function () {
return window.navigator.vendor;
};
/**
* Returns true if this browser has support for
* the CSS font loading API.
*
* @return {boolean}
*/
FontWatcher.shouldUseNativeLoader = function () {
if (FontWatcher.SHOULD_USE_NATIVE_LOADER === null) {
if (!!window.FontFace) {
var match = /Gecko.*Firefox\/(\d+)/.exec(FontWatcher.getUserAgent());
var safari10Match = /OS X.*Version\/10\..*Safari/.exec(FontWatcher.getUserAgent()) && /Apple/.exec(FontWatcher.getVendor());
if (match) {
FontWatcher.SHOULD_USE_NATIVE_LOADER = parseInt(match[1], 10) > 42;
} else if (safari10Match) {
FontWatcher.SHOULD_USE_NATIVE_LOADER = false;
} else {
FontWatcher.SHOULD_USE_NATIVE_LOADER = true;
}
} else {
FontWatcher.SHOULD_USE_NATIVE_LOADER = false;
}
}
return FontWatcher.SHOULD_USE_NATIVE_LOADER;
};
/**
* Watches a set of font families.
* @param {Array.<webfont.Font>} fonts The fonts to watch.
* @param {webfont.FontTestStrings} fontTestStrings The font test strings for
* each family.
* @param {Object.<String, boolean>} metricCompatibleFonts
* @param {boolean} last True if this is the last set of fonts to watch.
*/
FontWatcher.prototype.watchFonts = function(fonts,
fontTestStrings, metricCompatibleFonts, last) {
var length = fonts.length,
testStrings = fontTestStrings || {};
if (length === 0 && last) {
this.eventDispatcher_.dispatchInactive();
return;
}
this.currentlyWatched_ += fonts.length;
if (last) {
this.last_ = last;
}
var i, fontWatchRunners = [];
for (i = 0; i < fonts.length; i++) {
var font = fonts[i],
testString = testStrings[font.getName()];
this.eventDispatcher_.dispatchFontLoading(font);
var fontWatchRunner = null;
if (FontWatcher.shouldUseNativeLoader()) {
fontWatchRunner = new NativeFontWatchRunner(
goog.bind(this.fontActive_, this),
goog.bind(this.fontInactive_, this),
this.domHelper_,
font,
this.timeout_,
testString
);
} else {
fontWatchRunner = new FontWatchRunner(
goog.bind(this.fontActive_, this),
goog.bind(this.fontInactive_, this),
this.domHelper_,
font,
this.timeout_,
metricCompatibleFonts,
testString
);
}
fontWatchRunners.push(fontWatchRunner);
}
for (i = 0; i < fontWatchRunners.length; i++) {
fontWatchRunners[i].start();
}
};
/**
* Called by a FontWatchRunner when a font has been detected as active.
* @param {webfont.Font} font
* @private
*/
FontWatcher.prototype.fontActive_ = function(font) {
this.eventDispatcher_.dispatchFontActive(font);
this.success_ = true;
this.decreaseCurrentlyWatched_();
};
/**
* Called by a FontWatchRunner when a font has been detected as inactive.
* @param {webfont.Font} font
* @private
*/
FontWatcher.prototype.fontInactive_ = function(font) {
this.eventDispatcher_.dispatchFontInactive(font);
this.decreaseCurrentlyWatched_();
};
/**
* @private
*/
FontWatcher.prototype.decreaseCurrentlyWatched_ = function() {
if (--this.currentlyWatched_ == 0 && this.last_) {
if (this.success_) {
this.eventDispatcher_.dispatchActive();
} else {
this.eventDispatcher_.dispatchInactive();
}
}
};
});

View file

@ -0,0 +1,249 @@
goog.provide('webfont.FontWatchRunner');
goog.require('webfont.Font');
goog.require('webfont.FontRuler');
/**
* @constructor
* @param {function(webfont.Font)} activeCallback
* @param {function(webfont.Font)} inactiveCallback
* @param {webfont.DomHelper} domHelper
* @param {webfont.Font} font
* @param {number=} opt_timeout
* @param {Object.<string, boolean>=} opt_metricCompatibleFonts
* @param {string=} opt_fontTestString
*/
webfont.FontWatchRunner = function(activeCallback, inactiveCallback, domHelper,
font, opt_timeout, opt_metricCompatibleFonts, opt_fontTestString) {
this.activeCallback_ = activeCallback;
this.inactiveCallback_ = inactiveCallback;
this.domHelper_ = domHelper;
this.font_ = font;
this.fontTestString_ = opt_fontTestString || webfont.FontWatchRunner.DEFAULT_TEST_STRING;
this.lastResortWidths_ = {};
this.timeout_ = opt_timeout || 3000;
this.metricCompatibleFonts_ = opt_metricCompatibleFonts || null;
this.fontRulerA_ = null;
this.fontRulerB_ = null;
this.lastResortRulerA_ = null;
this.lastResortRulerB_ = null;
this.setupRulers_();
};
/**
* @enum {string}
* @const
*/
webfont.FontWatchRunner.LastResortFonts = {
SERIF: 'serif',
SANS_SERIF: 'sans-serif'
};
/**
* Default test string. Characters are chosen so that their widths vary a lot
* between the fonts in the default stacks. We want each fallback stack
* to always start out at a different width than the other.
* @type {string}
* @const
*/
webfont.FontWatchRunner.DEFAULT_TEST_STRING = 'BESbswy';
goog.scope(function () {
var FontWatchRunner = webfont.FontWatchRunner,
Font = webfont.Font,
FontRuler = webfont.FontRuler;
/**
* @type {null|boolean}
*/
FontWatchRunner.HAS_WEBKIT_FALLBACK_BUG = null;
/**
* @return {string}
*/
FontWatchRunner.getUserAgent = function () {
return window.navigator.userAgent;
};
/**
* Returns true if this browser is WebKit and it has the fallback bug
* which is present in WebKit 536.11 and earlier.
*
* @return {boolean}
*/
FontWatchRunner.hasWebKitFallbackBug = function () {
if (FontWatchRunner.HAS_WEBKIT_FALLBACK_BUG === null) {
var match = /AppleWebKit\/([0-9]+)(?:\.([0-9]+))/.exec(FontWatchRunner.getUserAgent());
FontWatchRunner.HAS_WEBKIT_FALLBACK_BUG = !!match &&
(parseInt(match[1], 10) < 536 ||
(parseInt(match[1], 10) === 536 &&
parseInt(match[2], 10) <= 11));
}
return FontWatchRunner.HAS_WEBKIT_FALLBACK_BUG;
};
/**
* @private
*/
FontWatchRunner.prototype.setupRulers_ = function() {
this.fontRulerA_ = new FontRuler(this.domHelper_, this.fontTestString_);
this.fontRulerB_ = new FontRuler(this.domHelper_, this.fontTestString_);
this.lastResortRulerA_ = new FontRuler(this.domHelper_, this.fontTestString_);
this.lastResortRulerB_ = new FontRuler(this.domHelper_, this.fontTestString_);
this.fontRulerA_.setFont(new Font(this.font_.getName() + ',' + FontWatchRunner.LastResortFonts.SERIF, this.font_.getVariation()));
this.fontRulerB_.setFont(new Font(this.font_.getName() + ',' + FontWatchRunner.LastResortFonts.SANS_SERIF, this.font_.getVariation()));
this.lastResortRulerA_.setFont(new Font(FontWatchRunner.LastResortFonts.SERIF, this.font_.getVariation()));
this.lastResortRulerB_.setFont(new Font(FontWatchRunner.LastResortFonts.SANS_SERIF, this.font_.getVariation()));
this.fontRulerA_.insert();
this.fontRulerB_.insert();
this.lastResortRulerA_.insert();
this.lastResortRulerB_.insert();
};
FontWatchRunner.prototype.start = function() {
this.lastResortWidths_[FontWatchRunner.LastResortFonts.SERIF] = this.lastResortRulerA_.getWidth();
this.lastResortWidths_[FontWatchRunner.LastResortFonts.SANS_SERIF] = this.lastResortRulerB_.getWidth();
this.started_ = goog.now();
this.check_();
};
/**
* Returns true if the given width matches the generic font family width.
*
* @private
* @param {number} width
* @param {string} lastResortFont
* @return {boolean}
*/
FontWatchRunner.prototype.widthMatches_ = function(width, lastResortFont) {
return width === this.lastResortWidths_[lastResortFont];
};
/**
* Return true if the given widths match any of the generic font family
* widths.
*
* @private
* @param {number} a
* @param {number} b
* @return {boolean}
*/
FontWatchRunner.prototype.widthsMatchLastResortWidths_ = function(a, b) {
for (var font in FontWatchRunner.LastResortFonts) {
if (FontWatchRunner.LastResortFonts.hasOwnProperty(font)) {
if (this.widthMatches_(a, FontWatchRunner.LastResortFonts[font]) &&
this.widthMatches_(b, FontWatchRunner.LastResortFonts[font])) {
return true;
}
}
}
return false;
};
/**
* @private
* Returns true if the loading has timed out.
* @return {boolean}
*/
FontWatchRunner.prototype.hasTimedOut_ = function() {
return goog.now() - this.started_ >= this.timeout_;
};
/**
* Returns true if both fonts match the normal fallback fonts.
*
* @private
* @param {number} a
* @param {number} b
* @return {boolean}
*/
FontWatchRunner.prototype.isFallbackFont_ = function (a, b) {
return this.widthMatches_(a, FontWatchRunner.LastResortFonts.SERIF) &&
this.widthMatches_(b, FontWatchRunner.LastResortFonts.SANS_SERIF);
};
/**
* Returns true if the WebKit bug is present and both widths match a last resort font.
*
* @private
* @param {number} a
* @param {number} b
* @return {boolean}
*/
FontWatchRunner.prototype.isLastResortFont_ = function (a, b) {
return FontWatchRunner.hasWebKitFallbackBug() && this.widthsMatchLastResortWidths_(a, b);
};
/**
* Returns true if the current font is metric compatible. Also returns true
* if we do not have a list of metric compatible fonts.
*
* @private
* @return {boolean}
*/
FontWatchRunner.prototype.isMetricCompatibleFont_ = function () {
return this.metricCompatibleFonts_ === null || this.metricCompatibleFonts_.hasOwnProperty(this.font_.getName());
};
/**
* Checks the width of the two spans against their original widths during each
* async loop. If the width of one of the spans is different than the original
* width, then we know that the font is rendering and finish with the active
* callback. If we wait more than 5 seconds and nothing has changed, we finish
* with the inactive callback.
*
* @private
*/
FontWatchRunner.prototype.check_ = function() {
var widthA = this.fontRulerA_.getWidth();
var widthB = this.fontRulerB_.getWidth();
if (this.isFallbackFont_(widthA, widthB) || this.isLastResortFont_(widthA, widthB)) {
if (this.hasTimedOut_()) {
if (this.isLastResortFont_(widthA, widthB) && this.isMetricCompatibleFont_()) {
this.finish_(this.activeCallback_);
} else {
this.finish_(this.inactiveCallback_);
}
} else {
this.asyncCheck_();
}
} else {
this.finish_(this.activeCallback_);
}
};
/**
* @private
*/
FontWatchRunner.prototype.asyncCheck_ = function() {
setTimeout(goog.bind(function () {
this.check_();
}, this), 50);
};
/**
* @private
* @param {function(webfont.Font)} callback
*/
FontWatchRunner.prototype.finish_ = function(callback) {
// Remove elements and trigger callback (which adds active/inactive class) asynchronously to avoid reflow chain if
// several fonts are finished loading right after each other
setTimeout(goog.bind(function () {
this.fontRulerA_.remove();
this.fontRulerB_.remove();
this.lastResortRulerA_.remove();
this.lastResortRulerB_.remove();
callback(this.font_);
}, this), 0);
};
});

View file

@ -0,0 +1,97 @@
goog.provide('webfont');
goog.require('webfont.WebFont');
goog.require('webfont.modules.Typekit');
goog.require('webfont.modules.Fontdeck');
goog.require('webfont.modules.Monotype');
goog.require('webfont.modules.Custom');
goog.require('webfont.modules.google.GoogleFontApi');
/**
* @define {boolean}
*/
var INCLUDE_CUSTOM_MODULE = false;
/**
* @define {boolean}
*/
var INCLUDE_FONTDECK_MODULE = false;
/**
* @define {boolean}
*/
var INCLUDE_MONOTYPE_MODULE = false;
/**
* @define {boolean}
*/
var INCLUDE_TYPEKIT_MODULE = false;
/**
* @define {boolean}
*/
var INCLUDE_GOOGLE_MODULE = false;
/**
* @define {string}
*/
var WEBFONT = 'WebFont';
/**
* @define {string}
*/
var WEBFONT_CONFIG = 'WebFontConfig';
/**
* @type {webfont.WebFont}
*/
var webFontLoader = new webfont.WebFont(window);
if (INCLUDE_CUSTOM_MODULE) {
webFontLoader.addModule(webfont.modules.Custom.NAME, function (configuration, domHelper) {
return new webfont.modules.Custom(domHelper, configuration);
});
}
if (INCLUDE_FONTDECK_MODULE) {
webFontLoader.addModule(webfont.modules.Fontdeck.NAME, function (configuration, domHelper) {
return new webfont.modules.Fontdeck(domHelper, configuration);
});
}
if (INCLUDE_MONOTYPE_MODULE) {
webFontLoader.addModule(webfont.modules.Monotype.NAME, function (configuration, domHelper) {
return new webfont.modules.Monotype(domHelper, configuration);
});
}
if (INCLUDE_TYPEKIT_MODULE) {
webFontLoader.addModule(webfont.modules.Typekit.NAME, function (configuration, domHelper) {
return new webfont.modules.Typekit(domHelper, configuration);
});
}
if (INCLUDE_GOOGLE_MODULE) {
webFontLoader.addModule(webfont.modules.google.GoogleFontApi.NAME, function (configuration, domHelper) {
return new webfont.modules.google.GoogleFontApi(domHelper, configuration);
});
}
var exports = {
'load': goog.bind(webFontLoader.load, webFontLoader)
};
if (typeof define === "function" && define.amd) {
define(function () {
return exports;
});
} else if (typeof module !== "undefined" && module.exports) {
module.exports = exports;
} else {
window[WEBFONT] = exports;
if (window[WEBFONT_CONFIG]) {
webFontLoader.load(window[WEBFONT_CONFIG]);
}
}

View file

@ -0,0 +1,69 @@
goog.provide('webfont.NativeFontWatchRunner');
goog.require('webfont.Font');
goog.scope(function () {
/**
* @constructor
* @param {function(webfont.Font)} activeCallback
* @param {function(webfont.Font)} inactiveCallback
* @param {webfont.DomHelper} domHelper
* @param {webfont.Font} font
* @param {number=} opt_timeout
* @param {string=} opt_fontTestString
*/
webfont.NativeFontWatchRunner = function(activeCallback, inactiveCallback, domHelper, font, opt_timeout, opt_fontTestString) {
this.activeCallback_ = activeCallback;
this.inactiveCallback_ = inactiveCallback;
this.font_ = font;
this.domHelper_ = domHelper;
this.timeout_ = opt_timeout || 3000;
this.fontTestString_ = opt_fontTestString || undefined;
};
var NativeFontWatchRunner = webfont.NativeFontWatchRunner;
NativeFontWatchRunner.prototype.start = function () {
var doc = this.domHelper_.getLoadWindow().document,
that = this;
var start = goog.now();
var loader = new Promise(function (resolve, reject) {
var check = function () {
var now = goog.now();
if (now - start >= that.timeout_) {
reject();
} else {
doc.fonts.load(that.font_.toCssString(), that.fontTestString_).then(function (fonts) {
if (fonts.length >= 1) {
resolve();
} else {
setTimeout(check, 25);
}
}, function () {
reject();
});
}
};
check();
});
var timeoutId = null,
timer = new Promise(function (resolve, reject) {
timeoutId = setTimeout(reject, that.timeout_);
});
Promise.race([timer, loader]).then(function () {
if (timeoutId) {
clearTimeout(timeoutId);
timeoutId = null;
}
that.activeCallback_(that.font_);
}, function () {
that.inactiveCallback_(that.font_);
});
};
});

View file

@ -0,0 +1,48 @@
goog.provide('webfont.StyleSheetWaiter');
/**
* A utility class for handling callback from DomHelper.loadStylesheet().
*
* @constructor
*/
webfont.StyleSheetWaiter = function() {
/** @private @type {number} */
this.waitingCount_ = 0;
/** @private @type {Function} */
this.onReady_ = null;
};
goog.scope(function () {
var StyleSheetWaiter = webfont.StyleSheetWaiter;
/**
* @return {function(Error)}
*/
StyleSheetWaiter.prototype.startWaitingLoad = function() {
var self = this;
self.waitingCount_++;
return function(error) {
self.waitingCount_--;
self.fireIfReady_();
};
};
/**
* @param {Function} fn
*/
StyleSheetWaiter.prototype.waitWhileNeededThen = function(fn) {
this.onReady_ = fn;
this.fireIfReady_();
};
/**
* @private
*/
StyleSheetWaiter.prototype.fireIfReady_ = function() {
var isReady = 0 == this.waitingCount_;
if (isReady && this.onReady_) {
this.onReady_();
this.onReady_ = null;
}
};
});

View file

@ -0,0 +1,97 @@
goog.provide('webfont.WebFont');
goog.require('webfont.DomHelper');
goog.require('webfont.EventDispatcher');
goog.require('webfont.FontWatcher');
goog.require('webfont.FontModuleLoader');
/**
* @param {Window} mainWindow The main application window containing
* webfontloader.js.
* @constructor
*/
webfont.WebFont = function(mainWindow) {
this.mainWindow_ = mainWindow;
this.fontModuleLoader_ = new webfont.FontModuleLoader();
this.moduleLoading_ = 0;
this.events_ = true;
this.classes_ = true;
};
goog.scope(function () {
var WebFont = webfont.WebFont,
DomHelper = webfont.DomHelper,
EventDispatcher = webfont.EventDispatcher,
FontWatcher = webfont.FontWatcher;
/**
* @param {string} name
* @param {webfont.FontModuleFactory} factory
*/
WebFont.prototype.addModule = function(name, factory) {
this.fontModuleLoader_.addModuleFactory(name, factory);
};
/**
* @param {Object} configuration
*/
WebFont.prototype.load = function(configuration) {
var context = configuration['context'] || this.mainWindow_;
this.domHelper_ = new DomHelper(this.mainWindow_, context);
this.events_ = configuration['events'] !== false;
this.classes_ = configuration['classes'] !== false;
var eventDispatcher = new EventDispatcher(
this.domHelper_,
configuration
);
this.load_(eventDispatcher, configuration);
};
/**
* @param {webfont.EventDispatcher} eventDispatcher
* @param {webfont.FontWatcher} fontWatcher
* @param {Array.<webfont.Font>} fonts
* @param {webfont.FontTestStrings=} opt_fontTestStrings
* @param {Object.<string, boolean>=} opt_metricCompatibleFonts
*/
WebFont.prototype.onModuleReady_ = function(eventDispatcher, fontWatcher, fonts, opt_fontTestStrings, opt_metricCompatibleFonts) {
var allModulesLoaded = --this.moduleLoading_ == 0;
if (this.classes_ || this.events_) {
setTimeout(function () {
fontWatcher.watchFonts(fonts, opt_fontTestStrings || null, opt_metricCompatibleFonts || null, allModulesLoaded);
}, 0);
}
};
/**
* @param {webfont.EventDispatcher} eventDispatcher
* @param {Object} configuration
*/
WebFont.prototype.load_ = function(eventDispatcher, configuration) {
var modules = [],
timeout = configuration['timeout'],
self = this;
// Immediately dispatch the loading event before initializing the modules
// so we know for sure that the loading event is synchronous.
eventDispatcher.dispatchLoading();
modules = this.fontModuleLoader_.getModules(configuration, this.domHelper_);
var fontWatcher = new webfont.FontWatcher(this.domHelper_, eventDispatcher, timeout);
this.moduleLoading_ = modules.length;
for (var i = 0, len = modules.length; i < len; i++) {
var module = modules[i];
module.load(function (fonts, opt_fontTestStrings, opt_metricCompatibleFonts) {
self.onModuleReady_(eventDispatcher, fontWatcher, fonts, opt_fontTestStrings, opt_metricCompatibleFonts);
});
}
};
});

View file

@ -0,0 +1,34 @@
core:
- ../tools/compiler/base.js
- core/domhelper.js
- core/stylesheetwaiter.js
- core/cssclassname.js
- core/font.js
- core/eventdispatcher.js
- core/fontmodule.js
- core/fontmoduleloader.js
- core/fontruler.js
- core/nativefontwatchrunner.js
- core/fontwatchrunner.js
- core/fontwatcher.js
- core/webfont.js
- core/initialize.js
google:
- modules/google/fontapiurlbuilder.js
- modules/google/fontapiparser.js
- modules/google/googlefontapi.js
fontdeck:
- modules/fontdeck.js
typekit:
- modules/typekit.js
monotype:
- modules/monotype.js
custom:
- modules/custom.js

View file

@ -0,0 +1,63 @@
goog.provide('webfont.modules.Custom');
goog.require('webfont.Font');
goog.require('webfont.StyleSheetWaiter');
/**
*
* WebFont.load({
* custom: {
* families: ['Font1', 'Font2'],
* urls: [ 'https://moo', 'https://meuh' ] }
* });
*
* @constructor
* @implements {webfont.FontModule}
*/
webfont.modules.Custom = function(domHelper, configuration) {
this.domHelper_ = domHelper;
this.configuration_ = configuration;
};
/**
* @const
* @type {string}
*/
webfont.modules.Custom.NAME = 'custom';
goog.scope(function () {
var Custom = webfont.modules.Custom,
Font = webfont.Font,
StyleSheetWaiter = webfont.StyleSheetWaiter;
Custom.prototype.load = function(onReady) {
var i, len;
var urls = this.configuration_['urls'] || [];
var familiesConfiguration = this.configuration_['families'] || [];
var fontTestStrings = this.configuration_['testStrings'] || {};
var waiter = new StyleSheetWaiter();
for (i = 0, len = urls.length; i < len; i++) {
this.domHelper_.loadStylesheet(urls[i], waiter.startWaitingLoad());
}
var fonts = [];
for (i = 0, len = familiesConfiguration.length; i < len; i++) {
var components = familiesConfiguration[i].split(":");
if (components[1]) {
var variations = components[1].split(",");
for (var j = 0; j < variations.length; j += 1) {
fonts.push(new Font(components[0], variations[j]));
}
} else {
fonts.push(new Font(components[0]));
}
}
waiter.waitWhileNeededThen(function() {
onReady(fonts, fontTestStrings);
});
};
});

View file

@ -0,0 +1,66 @@
goog.provide('webfont.modules.Fontdeck');
goog.require('webfont.Font');
/**
* @constructor
* @implements {webfont.FontModule}
*/
webfont.modules.Fontdeck = function(domHelper, configuration) {
this.domHelper_ = domHelper;
this.configuration_ = configuration;
this.fonts_ = [];
};
/**
* @const
* @type {string}
*/
webfont.modules.Fontdeck.NAME = 'fontdeck';
webfont.modules.Fontdeck.HOOK = '__webfontfontdeckmodule__';
webfont.modules.Fontdeck.API = 'https://f.fontdeck.com/s/css/js/';
goog.scope(function () {
var Fontdeck = webfont.modules.Fontdeck,
Font = webfont.Font,
FontVariationDescription = webfont.FontVariationDescription;
Fontdeck.prototype.getScriptSrc = function(projectId) {
// For empty iframes, fall back to main window's hostname.
var hostname = this.domHelper_.getHostName();
var api = this.configuration_['api'] || webfont.modules.Fontdeck.API;
return api + hostname + '/' + projectId + '.js';
};
Fontdeck.prototype.load = function(onReady) {
var projectId = this.configuration_['id'];
var loadWindow = this.domHelper_.getLoadWindow();
var self = this;
if (projectId) {
// Provide data to Fontdeck for processing.
if (!loadWindow[webfont.modules.Fontdeck.HOOK]) {
loadWindow[webfont.modules.Fontdeck.HOOK] = {};
}
// Fontdeck will call this function to indicate support status
// and what fonts are provided.
loadWindow[webfont.modules.Fontdeck.HOOK][projectId] = function(fontdeckSupports, data) {
for (var i = 0, j = data['fonts'].length; i<j; ++i) {
var font = data['fonts'][i];
self.fonts_.push(new Font(font['name'], Font.parseCssVariation('font-weight:' + font['weight'] + ';font-style:' + font['style'])));
}
onReady(self.fonts_);
};
// Call the Fontdeck API.
this.domHelper_.loadScript(this.getScriptSrc(projectId), function (err) {
if (err) {
onReady([]);
}
});
} else {
onReady([]);
}
};
});

View file

@ -0,0 +1,181 @@
goog.provide('webfont.modules.google.FontApiParser');
goog.require('webfont.Font');
/**
* @constructor
*/
webfont.modules.google.FontApiParser = function(fontFamilies) {
this.fontFamilies_ = fontFamilies;
this.parsedFonts_ = [];
this.fontTestStrings_ = {};
};
webfont.modules.google.FontApiParser.INT_FONTS = {
'latin': webfont.FontWatchRunner.DEFAULT_TEST_STRING,
'latin-ext': '\u00E7\u00F6\u00FC\u011F\u015F',
'cyrillic': '\u0439\u044f\u0416',
'greek': '\u03b1\u03b2\u03a3',
'khmer': '\u1780\u1781\u1782',
'Hanuman': '\u1780\u1781\u1782' // For backward compatibility
};
webfont.modules.google.FontApiParser.WEIGHTS = {
'thin': '1',
'extralight': '2',
'extra-light': '2',
'ultralight': '2',
'ultra-light': '2',
'light': '3',
'regular': '4',
'book': '4',
'medium': '5',
'semi-bold': '6',
'semibold': '6',
'demi-bold': '6',
'demibold': '6',
'bold': '7',
'extra-bold': '8',
'extrabold': '8',
'ultra-bold': '8',
'ultrabold': '8',
'black': '9',
'heavy': '9',
'l': '3',
'r': '4',
'b': '7'
};
webfont.modules.google.FontApiParser.STYLES = {
'i': 'i',
'italic': 'i',
'n': 'n',
'normal': 'n'
};
webfont.modules.google.FontApiParser.VARIATION_MATCH =
new RegExp("^(thin|(?:(?:extra|ultra)-?)?light|regular|book|medium|" +
"(?:(?:semi|demi|extra|ultra)-?)?bold|black|heavy|l|r|b|[1-9]00)?(n|i" +
"|normal|italic)?$");
goog.scope(function () {
var FontApiParser = webfont.modules.google.FontApiParser,
Font = webfont.Font;
FontApiParser.prototype.parse = function() {
var length = this.fontFamilies_.length;
for (var i = 0; i < length; i++) {
var elements = this.fontFamilies_[i].split(":");
var fontFamily = elements[0].replace(/\+/g, " ");
var variations = ['n4'];
if (elements.length >= 2) {
var fvds = this.parseVariations_(elements[1]);
if (fvds.length > 0) {
variations = fvds;
}
if (elements.length == 3) {
var subsets = this.parseSubsets_(elements[2]);
if (subsets.length > 0) {
var fontTestString = FontApiParser.INT_FONTS[subsets[0]];
if (fontTestString) {
this.fontTestStrings_[fontFamily] = fontTestString;
}
}
}
}
// For backward compatibility
if (!this.fontTestStrings_[fontFamily]) {
var hanumanTestString = FontApiParser.INT_FONTS[fontFamily];
if (hanumanTestString) {
this.fontTestStrings_[fontFamily] = hanumanTestString;
}
}
for (var j = 0; j < variations.length; j += 1) {
this.parsedFonts_.push(new Font(fontFamily, variations[j]));
}
}
};
FontApiParser.prototype.generateFontVariationDescription_ = function(variation) {
if (!variation.match(/^[\w-]+$/)) {
return '';
}
var normalizedVariation = variation.toLowerCase();
var groups = FontApiParser.VARIATION_MATCH.exec(normalizedVariation);
if (groups == null) {
return '';
}
var styleMatch = this.normalizeStyle_(groups[2]);
var weightMatch = this.normalizeWeight_(groups[1]);
return [styleMatch, weightMatch].join('');
};
FontApiParser.prototype.normalizeStyle_ = function(parsedStyle) {
if (parsedStyle == null || parsedStyle == '') {
return 'n';
}
return FontApiParser.STYLES[parsedStyle];
};
FontApiParser.prototype.normalizeWeight_ = function(parsedWeight) {
if (parsedWeight == null || parsedWeight == '') {
return '4';
}
var weight = FontApiParser.WEIGHTS[parsedWeight];
if (weight) {
return weight;
}
if (isNaN(parsedWeight)) {
return '4';
}
return parsedWeight.substr(0, 1);
};
FontApiParser.prototype.parseVariations_ = function(variations) {
var finalVariations = [];
if (!variations) {
return finalVariations;
}
var providedVariations = variations.split(",");
var length = providedVariations.length;
for (var i = 0; i < length; i++) {
var variation = providedVariations[i];
var fvd = this.generateFontVariationDescription_(variation);
if (fvd) {
finalVariations.push(fvd);
}
}
return finalVariations;
};
FontApiParser.prototype.parseSubsets_ = function(subsets) {
var finalSubsets = [];
if (!subsets) {
return finalSubsets;
}
return subsets.split(",");
};
FontApiParser.prototype.getFonts = function() {
return this.parsedFonts_;
};
FontApiParser.prototype.getFontTestStrings = function() {
return this.fontTestStrings_;
};
});

View file

@ -0,0 +1,77 @@
goog.provide('webfont.modules.google.FontApiUrlBuilder');
/**
* @constructor
*/
webfont.modules.google.FontApiUrlBuilder = function(apiUrl, text) {
if (apiUrl) {
this.apiUrl_ = apiUrl;
} else {
this.apiUrl_ = webfont.modules.google.FontApiUrlBuilder.DEFAULT_API_URL;
}
this.fontFamilies_ = [];
this.subsets_ = [];
this.text_ = text || '';
};
webfont.modules.google.FontApiUrlBuilder.DEFAULT_API_URL = 'https://fonts.googleapis.com/css';
goog.scope(function () {
var FontApiUrlBuilder = webfont.modules.google.FontApiUrlBuilder;
FontApiUrlBuilder.prototype.setFontFamilies = function(fontFamilies) {
this.parseFontFamilies_(fontFamilies);
};
FontApiUrlBuilder.prototype.parseFontFamilies_ =
function(fontFamilies) {
var length = fontFamilies.length;
for (var i = 0; i < length; i++) {
var elements = fontFamilies[i].split(':');
if (elements.length == 3) {
this.subsets_.push(elements.pop());
}
var joinCharacter = '';
if (elements.length == 2 && elements[1] != ''){
joinCharacter = ':';
}
this.fontFamilies_.push(elements.join(joinCharacter));
}
};
FontApiUrlBuilder.prototype.webSafe = function(string) {
return string.replace(/ /g, '+');
};
FontApiUrlBuilder.prototype.build = function() {
if (this.fontFamilies_.length == 0) {
throw new Error('No fonts to load!');
}
if (this.apiUrl_.indexOf("kit=") != -1) {
return this.apiUrl_;
}
var length = this.fontFamilies_.length;
var sb = [];
for (var i = 0; i < length; i++) {
sb.push(this.webSafe(this.fontFamilies_[i]));
}
var url = this.apiUrl_ + '?family=' + sb.join('%7C'); // '|' escaped.
if (this.subsets_.length > 0) {
url += '&subset=' + this.subsets_.join(',');
}
if (this.text_.length > 0) {
url += '&text=' + encodeURIComponent(this.text_);
}
return url;
};
});

View file

@ -0,0 +1,54 @@
goog.provide('webfont.modules.google.GoogleFontApi');
goog.require('webfont.modules.google.FontApiUrlBuilder');
goog.require('webfont.modules.google.FontApiParser');
goog.require('webfont.FontWatchRunner');
goog.require('webfont.StyleSheetWaiter');
/**
* @constructor
* @implements {webfont.FontModule}
*/
webfont.modules.google.GoogleFontApi = function(domHelper, configuration) {
this.domHelper_ = domHelper;
this.configuration_ = configuration;
};
/**
* @const
* @type {string}
*/
webfont.modules.google.GoogleFontApi.NAME = 'google';
goog.scope(function () {
var GoogleFontApi = webfont.modules.google.GoogleFontApi,
FontWatchRunner = webfont.FontWatchRunner,
StyleSheetWaiter = webfont.StyleSheetWaiter,
FontApiUrlBuilder = webfont.modules.google.FontApiUrlBuilder,
FontApiParser = webfont.modules.google.FontApiParser;
GoogleFontApi.METRICS_COMPATIBLE_FONTS = {
"Arimo": true,
"Cousine": true,
"Tinos": true
};
GoogleFontApi.prototype.load = function(onReady) {
var waiter = new StyleSheetWaiter();
var domHelper = this.domHelper_;
var fontApiUrlBuilder = new FontApiUrlBuilder(
this.configuration_['api'],
this.configuration_['text']
);
var fontFamilies = this.configuration_['families'];
fontApiUrlBuilder.setFontFamilies(fontFamilies);
var fontApiParser = new FontApiParser(fontFamilies);
fontApiParser.parse();
domHelper.loadStylesheet(fontApiUrlBuilder.build(), waiter.startWaitingLoad());
waiter.waitWhileNeededThen(function() {
onReady(fontApiParser.getFonts(), fontApiParser.getFontTestStrings(), GoogleFontApi.METRICS_COMPATIBLE_FONTS);
});
};
});

View file

@ -0,0 +1,110 @@
goog.provide('webfont.modules.Monotype');
goog.require('webfont.Font');
/**
webfont.load({
monotype: {
projectId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'//this is your Fonts.com Web fonts projectId
}
});
*/
/**
* @constructor
* @implements {webfont.FontModule}
*/
webfont.modules.Monotype = function(domHelper, configuration) {
this.domHelper_ = domHelper;
this.configuration_ = configuration;
};
/**
* name of the module through which external API is supposed to call the MonotypeFontAPI.
*
* @const
* @type {string}
*/
webfont.modules.Monotype.NAME = 'monotype';
/**
* __mti_fntLst is the name of function that exposes Monotype's font list.
* @const
*/
webfont.modules.Monotype.HOOK = '__mti_fntLst';
/**
* __MonotypeAPIScript__ is the id of script added by google API. Currently 'fonts.com' supports only one script in a page.
* This may require change in future if 'fonts.com' begins supporting multiple scripts per page.
* @const
*/
webfont.modules.Monotype.SCRIPTID = '__MonotypeAPIScript__';
/**
* __MonotypeConfiguration__ is function exposed to fonts.com. fonts.com will use this function to get webfontloader configuration
* @const
*/
webfont.modules.Monotype.CONFIGURATION = '__MonotypeConfiguration__';
goog.scope(function() {
var Monotype = webfont.modules.Monotype,
Font = webfont.Font;
Monotype.prototype.getScriptSrc = function(projectId, version) {
var api = (this.configuration_['api'] || 'https://fast.fonts.net/jsapi')
return api + '/' + projectId + '.js' + (version ? '?v=' + version : '');
};
Monotype.prototype.load = function(onReady) {
var self = this;
var projectId = self.configuration_['projectId'];
var version = self.configuration_['version'];
function checkAndLoadIfDownloaded() {
if (loadWindow[Monotype.HOOK + projectId]) {
var mti_fnts = loadWindow[Monotype.HOOK + projectId](),
fonts = [],
fntVariation;
if (mti_fnts) {
for (var i = 0; i < mti_fnts.length; i++) {
var fnt = mti_fnts[i]["fontfamily"];
//Check if font-style and font-weight is available
if (mti_fnts[i]["fontStyle"] != undefined && mti_fnts[i]["fontWeight"] != undefined) {
fntVariation = mti_fnts[i]["fontStyle"] + mti_fnts[i]["fontWeight"];
fonts.push(new Font(fnt, fntVariation));
} else {
fonts.push(new Font(fnt));
}
}
}
onReady(fonts);
} else {
setTimeout(function() {
checkAndLoadIfDownloaded();
}, 50);
}
}
if (projectId) {
var loadWindow = self.domHelper_.getLoadWindow();
var script = this.domHelper_.loadScript(self.getScriptSrc(projectId, version), function(err) {
if (err) {
onReady([]);
} else {
loadWindow[Monotype.CONFIGURATION+ projectId] = function() {
return self.configuration_;
};
checkAndLoadIfDownloaded();
}
});
script["id"] = Monotype.SCRIPTID + projectId;
} else {
onReady([]);
}
};
});

View file

@ -0,0 +1,73 @@
goog.provide('webfont.modules.Typekit');
goog.require('webfont.Font');
/**
* @constructor
* @implements {webfont.FontModule}
*/
webfont.modules.Typekit = function(domHelper, configuration) {
this.domHelper_ = domHelper;
this.configuration_ = configuration;
};
/**
* @const
* @type {string}
*/
webfont.modules.Typekit.NAME = 'typekit';
goog.scope(function () {
var Typekit = webfont.modules.Typekit,
Font = webfont.Font;
Typekit.prototype.getScriptSrc = function(kitId) {
var api = this.configuration_['api'] || 'https://use.typekit.net';
return api + '/' + kitId + '.js';
};
Typekit.prototype.load = function(onReady) {
var kitId = this.configuration_['id'];
var configuration = this.configuration_;
var loadWindow = this.domHelper_.getLoadWindow();
var that = this;
if (kitId) {
// Load the Typekit script. Once it is done loading we grab its configuration
// and use that to populate the fonts we should watch.
this.domHelper_.loadScript(this.getScriptSrc(kitId), function (err) {
if (err) {
onReady([]);
} else {
if (loadWindow['Typekit'] && loadWindow['Typekit']['config'] && loadWindow['Typekit']['config']['fn']) {
var fn = loadWindow['Typekit']['config']['fn'],
fonts = [];
for (var i = 0; i < fn.length; i += 2) {
var font = fn[i],
variations = fn[i + 1];
for (var j = 0; j < variations.length; j++) {
fonts.push(new Font(font, variations[j]));
}
}
// Kick off font loading but disable font events so
// we don't duplicate font watching.
try {
loadWindow['Typekit']['load']({
'events': false,
'classes': false,
'async': true
});
} catch (e) {}
onReady(fonts);
}
}
}, 2000);
} else {
onReady([]);
}
};
});

View file

@ -0,0 +1,17 @@
/* Web Font Loader v1.6.28 - (c) Adobe Systems, Google. License: Apache 2.0 */(function(){function aa(a,b,c){return a.call.apply(a.bind,arguments)}function ba(a,b,c){if(!a)throw Error();if(2<arguments.length){var d=Array.prototype.slice.call(arguments,2);return function(){var c=Array.prototype.slice.call(arguments);Array.prototype.unshift.apply(c,d);return a.apply(b,c)}}return function(){return a.apply(b,arguments)}}function p(a,b,c){p=Function.prototype.bind&&-1!=Function.prototype.bind.toString().indexOf("native code")?aa:ba;return p.apply(null,arguments)}var q=Date.now||function(){return+new Date};function ca(a,b){this.a=a;this.o=b||a;this.c=this.o.document}var da=!!window.FontFace;function t(a,b,c,d){b=a.c.createElement(b);if(c)for(var e in c)c.hasOwnProperty(e)&&("style"==e?b.style.cssText=c[e]:b.setAttribute(e,c[e]));d&&b.appendChild(a.c.createTextNode(d));return b}function u(a,b,c){a=a.c.getElementsByTagName(b)[0];a||(a=document.documentElement);a.insertBefore(c,a.lastChild)}function v(a){a.parentNode&&a.parentNode.removeChild(a)}
function w(a,b,c){b=b||[];c=c||[];for(var d=a.className.split(/\s+/),e=0;e<b.length;e+=1){for(var f=!1,g=0;g<d.length;g+=1)if(b[e]===d[g]){f=!0;break}f||d.push(b[e])}b=[];for(e=0;e<d.length;e+=1){f=!1;for(g=0;g<c.length;g+=1)if(d[e]===c[g]){f=!0;break}f||b.push(d[e])}a.className=b.join(" ").replace(/\s+/g," ").replace(/^\s+|\s+$/,"")}function y(a,b){for(var c=a.className.split(/\s+/),d=0,e=c.length;d<e;d++)if(c[d]==b)return!0;return!1}
function ea(a){return a.o.location.hostname||a.a.location.hostname}function z(a,b,c){function d(){m&&e&&f&&(m(g),m=null)}b=t(a,"link",{rel:"stylesheet",href:b,media:"all"});var e=!1,f=!0,g=null,m=c||null;da?(b.onload=function(){e=!0;d()},b.onerror=function(){e=!0;g=Error("Stylesheet failed to load");d()}):setTimeout(function(){e=!0;d()},0);u(a,"head",b)}
function A(a,b,c,d){var e=a.c.getElementsByTagName("head")[0];if(e){var f=t(a,"script",{src:b}),g=!1;f.onload=f.onreadystatechange=function(){g||this.readyState&&"loaded"!=this.readyState&&"complete"!=this.readyState||(g=!0,c&&c(null),f.onload=f.onreadystatechange=null,"HEAD"==f.parentNode.tagName&&e.removeChild(f))};e.appendChild(f);setTimeout(function(){g||(g=!0,c&&c(Error("Script load timeout")))},d||5E3);return f}return null};function B(){this.a=0;this.c=null}function C(a){a.a++;return function(){a.a--;D(a)}}function E(a,b){a.c=b;D(a)}function D(a){0==a.a&&a.c&&(a.c(),a.c=null)};function F(a){this.a=a||"-"}F.prototype.c=function(a){for(var b=[],c=0;c<arguments.length;c++)b.push(arguments[c].replace(/[\W_]+/g,"").toLowerCase());return b.join(this.a)};function G(a,b){this.c=a;this.f=4;this.a="n";var c=(b||"n4").match(/^([nio])([1-9])$/i);c&&(this.a=c[1],this.f=parseInt(c[2],10))}function fa(a){return H(a)+" "+(a.f+"00")+" 300px "+I(a.c)}function I(a){var b=[];a=a.split(/,\s*/);for(var c=0;c<a.length;c++){var d=a[c].replace(/['"]/g,"");-1!=d.indexOf(" ")||/^\d/.test(d)?b.push("'"+d+"'"):b.push(d)}return b.join(",")}function J(a){return a.a+a.f}function H(a){var b="normal";"o"===a.a?b="oblique":"i"===a.a&&(b="italic");return b}
function ga(a){var b=4,c="n",d=null;a&&((d=a.match(/(normal|oblique|italic)/i))&&d[1]&&(c=d[1].substr(0,1).toLowerCase()),(d=a.match(/([1-9]00|normal|bold)/i))&&d[1]&&(/bold/i.test(d[1])?b=7:/[1-9]00/.test(d[1])&&(b=parseInt(d[1].substr(0,1),10))));return c+b};function ha(a,b){this.c=a;this.f=a.o.document.documentElement;this.h=b;this.a=new F("-");this.j=!1!==b.events;this.g=!1!==b.classes}function ia(a){a.g&&w(a.f,[a.a.c("wf","loading")]);K(a,"loading")}function L(a){if(a.g){var b=y(a.f,a.a.c("wf","active")),c=[],d=[a.a.c("wf","loading")];b||c.push(a.a.c("wf","inactive"));w(a.f,c,d)}K(a,"inactive")}function K(a,b,c){if(a.j&&a.h[b])if(c)a.h[b](c.c,J(c));else a.h[b]()};function ja(){this.c={}}function ka(a,b,c){var d=[],e;for(e in b)if(b.hasOwnProperty(e)){var f=a.c[e];f&&d.push(f(b[e],c))}return d};function M(a,b){this.c=a;this.f=b;this.a=t(this.c,"span",{"aria-hidden":"true"},this.f)}function N(a){u(a.c,"body",a.a)}function O(a){return"display:block;position:absolute;top:-9999px;left:-9999px;font-size:300px;width:auto;height:auto;line-height:normal;margin:0;padding:0;font-variant:normal;white-space:nowrap;font-family:"+I(a.c)+";"+("font-style:"+H(a)+";font-weight:"+(a.f+"00")+";")};function P(a,b,c,d,e,f){this.g=a;this.j=b;this.a=d;this.c=c;this.f=e||3E3;this.h=f||void 0}P.prototype.start=function(){var a=this.c.o.document,b=this,c=q(),d=new Promise(function(d,e){function f(){q()-c>=b.f?e():a.fonts.load(fa(b.a),b.h).then(function(a){1<=a.length?d():setTimeout(f,25)},function(){e()})}f()}),e=null,f=new Promise(function(a,d){e=setTimeout(d,b.f)});Promise.race([f,d]).then(function(){e&&(clearTimeout(e),e=null);b.g(b.a)},function(){b.j(b.a)})};function Q(a,b,c,d,e,f,g){this.v=a;this.B=b;this.c=c;this.a=d;this.s=g||"BESbswy";this.f={};this.w=e||3E3;this.u=f||null;this.m=this.j=this.h=this.g=null;this.g=new M(this.c,this.s);this.h=new M(this.c,this.s);this.j=new M(this.c,this.s);this.m=new M(this.c,this.s);a=new G(this.a.c+",serif",J(this.a));a=O(a);this.g.a.style.cssText=a;a=new G(this.a.c+",sans-serif",J(this.a));a=O(a);this.h.a.style.cssText=a;a=new G("serif",J(this.a));a=O(a);this.j.a.style.cssText=a;a=new G("sans-serif",J(this.a));a=
O(a);this.m.a.style.cssText=a;N(this.g);N(this.h);N(this.j);N(this.m)}var R={D:"serif",C:"sans-serif"},S=null;function T(){if(null===S){var a=/AppleWebKit\/([0-9]+)(?:\.([0-9]+))/.exec(window.navigator.userAgent);S=!!a&&(536>parseInt(a[1],10)||536===parseInt(a[1],10)&&11>=parseInt(a[2],10))}return S}Q.prototype.start=function(){this.f.serif=this.j.a.offsetWidth;this.f["sans-serif"]=this.m.a.offsetWidth;this.A=q();U(this)};
function la(a,b,c){for(var d in R)if(R.hasOwnProperty(d)&&b===a.f[R[d]]&&c===a.f[R[d]])return!0;return!1}function U(a){var b=a.g.a.offsetWidth,c=a.h.a.offsetWidth,d;(d=b===a.f.serif&&c===a.f["sans-serif"])||(d=T()&&la(a,b,c));d?q()-a.A>=a.w?T()&&la(a,b,c)&&(null===a.u||a.u.hasOwnProperty(a.a.c))?V(a,a.v):V(a,a.B):ma(a):V(a,a.v)}function ma(a){setTimeout(p(function(){U(this)},a),50)}function V(a,b){setTimeout(p(function(){v(this.g.a);v(this.h.a);v(this.j.a);v(this.m.a);b(this.a)},a),0)};function W(a,b,c){this.c=a;this.a=b;this.f=0;this.m=this.j=!1;this.s=c}var X=null;W.prototype.g=function(a){var b=this.a;b.g&&w(b.f,[b.a.c("wf",a.c,J(a).toString(),"active")],[b.a.c("wf",a.c,J(a).toString(),"loading"),b.a.c("wf",a.c,J(a).toString(),"inactive")]);K(b,"fontactive",a);this.m=!0;na(this)};
W.prototype.h=function(a){var b=this.a;if(b.g){var c=y(b.f,b.a.c("wf",a.c,J(a).toString(),"active")),d=[],e=[b.a.c("wf",a.c,J(a).toString(),"loading")];c||d.push(b.a.c("wf",a.c,J(a).toString(),"inactive"));w(b.f,d,e)}K(b,"fontinactive",a);na(this)};function na(a){0==--a.f&&a.j&&(a.m?(a=a.a,a.g&&w(a.f,[a.a.c("wf","active")],[a.a.c("wf","loading"),a.a.c("wf","inactive")]),K(a,"active")):L(a.a))};function oa(a){this.j=a;this.a=new ja;this.h=0;this.f=this.g=!0}oa.prototype.load=function(a){this.c=new ca(this.j,a.context||this.j);this.g=!1!==a.events;this.f=!1!==a.classes;pa(this,new ha(this.c,a),a)};
function qa(a,b,c,d,e){var f=0==--a.h;(a.f||a.g)&&setTimeout(function(){var a=e||null,m=d||null||{};if(0===c.length&&f)L(b.a);else{b.f+=c.length;f&&(b.j=f);var h,l=[];for(h=0;h<c.length;h++){var k=c[h],n=m[k.c],r=b.a,x=k;r.g&&w(r.f,[r.a.c("wf",x.c,J(x).toString(),"loading")]);K(r,"fontloading",x);r=null;if(null===X)if(window.FontFace){var x=/Gecko.*Firefox\/(\d+)/.exec(window.navigator.userAgent),xa=/OS X.*Version\/10\..*Safari/.exec(window.navigator.userAgent)&&/Apple/.exec(window.navigator.vendor);
X=x?42<parseInt(x[1],10):xa?!1:!0}else X=!1;X?r=new P(p(b.g,b),p(b.h,b),b.c,k,b.s,n):r=new Q(p(b.g,b),p(b.h,b),b.c,k,b.s,a,n);l.push(r)}for(h=0;h<l.length;h++)l[h].start()}},0)}function pa(a,b,c){var d=[],e=c.timeout;ia(b);var d=ka(a.a,c,a.c),f=new W(a.c,b,e);a.h=d.length;b=0;for(c=d.length;b<c;b++)d[b].load(function(b,d,c){qa(a,f,b,d,c)})};function ra(a,b){this.c=a;this.a=b}
ra.prototype.load=function(a){function b(){if(f["__mti_fntLst"+d]){var c=f["__mti_fntLst"+d](),e=[],h;if(c)for(var l=0;l<c.length;l++){var k=c[l].fontfamily;void 0!=c[l].fontStyle&&void 0!=c[l].fontWeight?(h=c[l].fontStyle+c[l].fontWeight,e.push(new G(k,h))):e.push(new G(k))}a(e)}else setTimeout(function(){b()},50)}var c=this,d=c.a.projectId,e=c.a.version;if(d){var f=c.c.o;A(this.c,(c.a.api||"https://fast.fonts.net/jsapi")+"/"+d+".js"+(e?"?v="+e:""),function(e){e?a([]):(f["__MonotypeConfiguration__"+
d]=function(){return c.a},b())}).id="__MonotypeAPIScript__"+d}else a([])};function sa(a,b){this.c=a;this.a=b}sa.prototype.load=function(a){var b,c,d=this.a.urls||[],e=this.a.families||[],f=this.a.testStrings||{},g=new B;b=0;for(c=d.length;b<c;b++)z(this.c,d[b],C(g));var m=[];b=0;for(c=e.length;b<c;b++)if(d=e[b].split(":"),d[1])for(var h=d[1].split(","),l=0;l<h.length;l+=1)m.push(new G(d[0],h[l]));else m.push(new G(d[0]));E(g,function(){a(m,f)})};function ta(a,b){a?this.c=a:this.c=ua;this.a=[];this.f=[];this.g=b||""}var ua="https://fonts.googleapis.com/css";function va(a,b){for(var c=b.length,d=0;d<c;d++){var e=b[d].split(":");3==e.length&&a.f.push(e.pop());var f="";2==e.length&&""!=e[1]&&(f=":");a.a.push(e.join(f))}}
function wa(a){if(0==a.a.length)throw Error("No fonts to load!");if(-1!=a.c.indexOf("kit="))return a.c;for(var b=a.a.length,c=[],d=0;d<b;d++)c.push(a.a[d].replace(/ /g,"+"));b=a.c+"?family="+c.join("%7C");0<a.f.length&&(b+="&subset="+a.f.join(","));0<a.g.length&&(b+="&text="+encodeURIComponent(a.g));return b};function ya(a){this.f=a;this.a=[];this.c={}}
var za={latin:"BESbswy","latin-ext":"\u00e7\u00f6\u00fc\u011f\u015f",cyrillic:"\u0439\u044f\u0416",greek:"\u03b1\u03b2\u03a3",khmer:"\u1780\u1781\u1782",Hanuman:"\u1780\u1781\u1782"},Aa={thin:"1",extralight:"2","extra-light":"2",ultralight:"2","ultra-light":"2",light:"3",regular:"4",book:"4",medium:"5","semi-bold":"6",semibold:"6","demi-bold":"6",demibold:"6",bold:"7","extra-bold":"8",extrabold:"8","ultra-bold":"8",ultrabold:"8",black:"9",heavy:"9",l:"3",r:"4",b:"7"},Ba={i:"i",italic:"i",n:"n",normal:"n"},
Ca=/^(thin|(?:(?:extra|ultra)-?)?light|regular|book|medium|(?:(?:semi|demi|extra|ultra)-?)?bold|black|heavy|l|r|b|[1-9]00)?(n|i|normal|italic)?$/;
function Da(a){for(var b=a.f.length,c=0;c<b;c++){var d=a.f[c].split(":"),e=d[0].replace(/\+/g," "),f=["n4"];if(2<=d.length){var g;var m=d[1];g=[];if(m)for(var m=m.split(","),h=m.length,l=0;l<h;l++){var k;k=m[l];if(k.match(/^[\w-]+$/)){var n=Ca.exec(k.toLowerCase());if(null==n)k="";else{k=n[2];k=null==k||""==k?"n":Ba[k];n=n[1];if(null==n||""==n)n="4";else var r=Aa[n],n=r?r:isNaN(n)?"4":n.substr(0,1);k=[k,n].join("")}}else k="";k&&g.push(k)}0<g.length&&(f=g);3==d.length&&(d=d[2],g=[],d=d?d.split(","):
g,0<d.length&&(d=za[d[0]])&&(a.c[e]=d))}a.c[e]||(d=za[e])&&(a.c[e]=d);for(d=0;d<f.length;d+=1)a.a.push(new G(e,f[d]))}};function Ea(a,b){this.c=a;this.a=b}var Fa={Arimo:!0,Cousine:!0,Tinos:!0};Ea.prototype.load=function(a){var b=new B,c=this.c,d=new ta(this.a.api,this.a.text),e=this.a.families;va(d,e);var f=new ya(e);Da(f);z(c,wa(d),C(b));E(b,function(){a(f.a,f.c,Fa)})};function Ga(a,b){this.c=a;this.a=b}Ga.prototype.load=function(a){var b=this.a.id,c=this.c.o;b?A(this.c,(this.a.api||"https://use.typekit.net")+"/"+b+".js",function(b){if(b)a([]);else if(c.Typekit&&c.Typekit.config&&c.Typekit.config.fn){b=c.Typekit.config.fn;for(var e=[],f=0;f<b.length;f+=2)for(var g=b[f],m=b[f+1],h=0;h<m.length;h++)e.push(new G(g,m[h]));try{c.Typekit.load({events:!1,classes:!1,async:!0})}catch(l){}a(e)}},2E3):a([])};function Ha(a,b){this.c=a;this.f=b;this.a=[]}Ha.prototype.load=function(a){var b=this.f.id,c=this.c.o,d=this;b?(c.__webfontfontdeckmodule__||(c.__webfontfontdeckmodule__={}),c.__webfontfontdeckmodule__[b]=function(b,c){for(var g=0,m=c.fonts.length;g<m;++g){var h=c.fonts[g];d.a.push(new G(h.name,ga("font-weight:"+h.weight+";font-style:"+h.style)))}a(d.a)},A(this.c,(this.f.api||"https://f.fontdeck.com/s/css/js/")+ea(this.c)+"/"+b+".js",function(b){b&&a([])})):a([])};var Y=new oa(window);Y.a.c.custom=function(a,b){return new sa(b,a)};Y.a.c.fontdeck=function(a,b){return new Ha(b,a)};Y.a.c.monotype=function(a,b){return new ra(b,a)};Y.a.c.typekit=function(a,b){return new Ga(b,a)};Y.a.c.google=function(a,b){return new Ea(b,a)};var Z={load:p(Y.load,Y)};"function"===typeof define&&define.amd?define(function(){return Z}):"undefined"!==typeof module&&module.exports?module.exports=Z:(window.WebFont=Z,window.WebFontConfig&&Y.load(window.WebFontConfig));}());

View file

@ -0,0 +1,246 @@
<?php
/**
* Processes typography-related fields
* and generates the google-font link.
*
* @package Kirki
* @category Core
* @author Ari Stathopoulos (@aristath)
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 1.0
*/
/**
* Manages the way Google Fonts are enqueued.
*/
final class Kirki_Fonts_Google {
/**
* The Kirki_Fonts_Google instance.
* We use the singleton pattern here to avoid loading the google-font array multiple times.
* This is mostly a performance tweak.
*
* @access private
* @var null|object
*/
private static $instance = null;
/**
* DUMMY. DOESN'T DO ANYTHING, SIMPLY BACKWARDS-COMPATIBILITY.
*
* @static
* @access public
* @var bool
*/
public static $force_load_all_subsets = false;
/**
* If set to true, forces loading ALL variants.
*
* @static
* @access public
* @var bool
*/
public static $force_load_all_variants = false;
/**
* The array of fonts
*
* @access public
* @var array
*/
public $fonts = array();
/**
* An array of all google fonts.
*
* @access private
* @var array
*/
private $google_fonts = array();
/**
* An array of fonts that should be hosted locally instead of served via the google-CDN.
*
* @access protected
* @since 3.0.32
* @var array
*/
protected $hosted_fonts = array();
/**
* The class constructor.
*/
private function __construct() {
$config = apply_filters( 'kirki_config', array() );
// If we have set $config['disable_google_fonts'] to true then do not proceed any further.
if ( isset( $config['disable_google_fonts'] ) && true === $config['disable_google_fonts'] ) {
return;
}
add_action( 'wp_ajax_kirki_fonts_google_all_get', array( $this, 'get_googlefonts_json' ) );
add_action( 'wp_ajax_nopriv_kirki_fonts_google_all_get', array( $this, 'get_googlefonts_json' ) );
add_action( 'wp_ajax_kirki_fonts_standard_all_get', array( $this, 'get_standardfonts_json' ) );
add_action( 'wp_ajax_nopriv_kirki_fonts_standard_all_get', array( $this, 'get_standardfonts_json' ) );
// Populate the array of google fonts.
$this->google_fonts = Kirki_Fonts::get_google_fonts();
}
/**
* Get the one, true instance of this class.
* Prevents performance issues since this is only loaded once.
*
* @return object Kirki_Fonts_Google
*/
public static function get_instance() {
if ( null === self::$instance ) {
self::$instance = new Kirki_Fonts_Google();
}
return self::$instance;
}
/**
* Processes the arguments of a field
* determines if it's a typography field
* and if it is, then takes appropriate actions.
*
* @param array $args The field arguments.
*/
public function generate_google_font( $args ) {
// Process typography fields.
if ( isset( $args['type'] ) && 'kirki-typography' === $args['type'] ) {
// Get the value.
$value = Kirki_Values::get_sanitized_field_value( $args );
// If we don't have a font-family then we can skip this.
if ( ! isset( $value['font-family'] ) || in_array( $value['font-family'], $this->hosted_fonts, true ) ) {
return;
}
// If not a google-font, then we can skip this.
if ( ! isset( $value['font-family'] ) || ! Kirki_Fonts::is_google_font( $value['font-family'] ) ) {
return;
}
// Set a default value for variants.
if ( ! isset( $value['variant'] ) ) {
$value['variant'] = 'regular';
}
// Add the requested google-font.
if ( ! isset( $this->fonts[ $value['font-family'] ] ) ) {
$this->fonts[ $value['font-family'] ] = array();
}
if ( ! in_array( $value['variant'], $this->fonts[ $value['font-family'] ], true ) ) {
$this->fonts[ $value['font-family'] ][] = $value['variant'];
}
// Are we force-loading all variants?
if ( true === self::$force_load_all_variants ) {
$all_variants = Kirki_Fonts::get_all_variants();
$args['choices']['variant'] = array_keys( $all_variants );
}
if ( ! empty( $args['choices']['variant'] ) && is_array( $args['choices']['variant'] ) ) {
foreach ( $args['choices']['variant'] as $extra_variant ) {
$this->fonts[ $value['font-family'] ][] = $extra_variant;
}
}
return;
}
// Process non-typography fields.
if ( isset( $args['output'] ) && is_array( $args['output'] ) ) {
foreach ( $args['output'] as $output ) {
// If we don't have a typography-related output argument we can skip this.
if ( ! isset( $output['property'] ) || ! in_array( $output['property'], array( 'font-family', 'font-weight' ), true ) ) {
continue;
}
// Get the value.
$value = Kirki_Values::get_sanitized_field_value( $args );
if ( is_string( $value ) ) {
if ( 'font-family' === $output['property'] ) {
if ( ! array_key_exists( $value, $this->fonts ) ) {
$this->fonts[ $value ] = array();
}
} elseif ( 'font-weight' === $output['property'] ) {
foreach ( $this->fonts as $font => $variants ) {
if ( ! in_array( $value, $variants, true ) ) {
$this->fonts[ $font ][] = $value;
}
}
}
}
}
}
}
/**
* Determines the vbalidity of the selected font as well as its properties.
* This is vital to make sure that the google-font script that we'll generate later
* does not contain any invalid options.
*/
public function process_fonts() {
// Early exit if font-family is empty.
if ( empty( $this->fonts ) ) {
return;
}
foreach ( $this->fonts as $font => $variants ) {
// Determine if this is indeed a google font or not.
// If it's not, then just remove it from the array.
if ( ! array_key_exists( $font, $this->google_fonts ) ) {
unset( $this->fonts[ $font ] );
continue;
}
// Get all valid font variants for this font.
$font_variants = array();
if ( isset( $this->google_fonts[ $font ]['variants'] ) ) {
$font_variants = $this->google_fonts[ $font ]['variants'];
}
foreach ( $variants as $variant ) {
// If this is not a valid variant for this font-family
// then unset it and move on to the next one.
if ( ! in_array( strval( $variant ), $font_variants, true ) ) {
$variant_key = array_search( $variant, $this->fonts[ $font ], true );
unset( $this->fonts[ $font ][ $variant_key ] );
continue;
}
}
}
}
/**
* Gets the googlefonts JSON file.
*
* @since 3.0.17
* @return void
*/
public function get_googlefonts_json() {
include wp_normalize_path( dirname( __FILE__ ) . '/webfonts.json' ); // phpcs:ignore WPThemeReview.CoreFunctionality.FileInclude
wp_die();
}
/**
* Get the standard fonts JSON.
*
* @since 3.0.17
* @return void
*/
public function get_standardfonts_json() {
echo wp_json_encode( Kirki_Fonts::get_standard_fonts() );
wp_die();
}
}

View file

@ -0,0 +1,172 @@
<?php
/**
* Helper methods for fonts.
*
* @package Kirki
* @category Core
* @author Ari Stathopoulos (@aristath)
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 3.0.36
*/
// Do not allow directly accessing this file.
if ( ! defined( 'ABSPATH' ) ) {
exit( 'Direct script access denied.' );
}
/**
* The Kirki_Fonts object.
*
* @since 3.0.28
*/
final class Kirki_Fonts_Helper {
/**
* Gets the remote URL contents.
*
* @static
* @access public
* @since 3.0.36
* @param string $url The URL we want to get.
* @param array $args An array of arguments for the wp_remote_retrieve_body() function.
* @return string The contents of the remote URL.
*/
public static function get_remote_url_contents( $url, $args = array() ) {
$response = wp_remote_get( $url, $args );
if ( is_wp_error( $response ) ) {
return array();
}
$html = wp_remote_retrieve_body( $response );
if ( is_wp_error( $html ) ) {
return;
}
return $html;
}
/**
* Gets the root fonts folder path.
* Other paths are built based on this.
*
* @static
* @since 3.0.36
* @access public
* @return string
*/
public static function get_root_path() {
// Get the upload directory for this site.
$upload_dir = wp_upload_dir();
$path = untrailingslashit( wp_normalize_path( $upload_dir['basedir'] ) ) . '/webfonts';
// If the folder doesn't exist, create it.
if ( ! file_exists( $path ) ) {
wp_mkdir_p( $path );
}
// Return the path.
return apply_filters( 'kirki_googlefonts_root_path', $path );
}
/**
* Gets the filename by breaking-down the URL parts.
*
* @static
* @access private
* @since 3.0.28
* @param string $url The URL.
* @return string The filename.
*/
private static function get_filename_from_url( $url ) {
$url_parts = explode( '/', $url );
$parts_count = count( $url_parts );
if ( 1 < $parts_count ) {
return $url_parts[ count( $url_parts ) - 1 ];
}
return $url;
}
/**
* Downloads a font-file and saves it locally.
*
* @access public
* @since 3.0.28
* @param string $url The URL of the file we want to get.
* @return bool
*/
public static function download_font_file( $url ) {
$saved_fonts = get_option( 'kirki_font_local_filenames', array() );
if ( isset( $saved_fonts[ $url ] ) && file_exists( $saved_fonts[ $url ]['file'] ) ) {
return $saved_fonts[ $url ]['url'];
}
// Gives us access to the download_url() and wp_handle_sideload() functions.
require_once ABSPATH . 'wp-admin/includes/file.php';
$timeout_seconds = 5;
// Download file to temp dir.
$temp_file = download_url( $url, $timeout_seconds );
if ( is_wp_error( $temp_file ) ) {
return false;
}
// Array based on $_FILE as seen in PHP file uploads.
$file = array(
'name' => basename( $url ),
'type' => 'font/woff',
'tmp_name' => $temp_file,
'error' => 0,
'size' => filesize( $temp_file ),
);
$overrides = array(
'test_type' => false,
'test_form' => false,
'test_size' => true,
);
// Move the temporary file into the uploads directory.
$results = wp_handle_sideload( $file, $overrides );
if ( empty( $results['error'] ) ) {
$saved_fonts[ $url ] = $results;
update_option( 'kirki_font_local_filenames', $saved_fonts );
return $results['url'];
}
return false;
}
/**
* Gets the root folder url.
* Other urls are built based on this.
*
* @static
* @since 3.0.36
* @access public
* @return string
*/
public static function get_root_url() {
// Get the upload directory for this site.
$upload_dir = wp_upload_dir();
// The URL.
$url = trailingslashit( $upload_dir['baseurl'] );
// Take care of domain mapping.
// When using domain mapping we have to make sure that the URL to the file
// does not include the original domain but instead the mapped domain.
if ( defined( 'DOMAIN_MAPPING' ) && DOMAIN_MAPPING ) {
if ( function_exists( 'domain_mapping_siteurl' ) && function_exists( 'get_original_url' ) ) {
$mapped_domain = domain_mapping_siteurl( false );
$original_domain = get_original_url( 'siteurl' );
$url = str_replace( $original_domain, $mapped_domain, $url );
}
}
$url = str_replace( array( 'https://', 'http://' ), '//', $url );
return apply_filters( 'kirki_googlefonts_root_url', untrailingslashit( esc_url_raw( $url ) ) . '/webfonts' );
}
}

View file

@ -0,0 +1,251 @@
<?php
/**
* A simple object containing properties for fonts.
*
* @package Kirki
* @category Core
* @author Ari Stathopoulos (@aristath)
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 1.0
*/
/**
* The Kirki_Fonts object.
*/
final class Kirki_Fonts {
/**
* The mode we'll be using to add google fonts.
* This is a todo item, not yet functional.
*
* @static
* @todo
* @access public
* @var string
*/
public static $mode = 'link';
/**
* Holds a single instance of this object.
*
* @static
* @access private
* @var null|object
*/
private static $instance = null;
/**
* An array of our google fonts.
*
* @static
* @access public
* @var null|object
*/
public static $google_fonts = null;
/**
* The class constructor.
*/
private function __construct() {}
/**
* Get the one, true instance of this class.
* Prevents performance issues since this is only loaded once.
*
* @return object Kirki_Fonts
*/
public static function get_instance() {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Compile font options from different sources.
*
* @return array All available fonts.
*/
public static function get_all_fonts() {
$standard_fonts = self::get_standard_fonts();
$google_fonts = self::get_google_fonts();
return apply_filters( 'kirki_fonts_all', array_merge( $standard_fonts, $google_fonts ) );
}
/**
* Return an array of standard websafe fonts.
*
* @return array Standard websafe fonts.
*/
public static function get_standard_fonts() {
$standard_fonts = array(
'serif' => array(
'label' => 'Serif',
'stack' => 'Georgia,Times,"Times New Roman",serif',
),
'sans-serif' => array(
'label' => 'Sans Serif',
'stack' => '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif',
),
'monospace' => array(
'label' => 'Monospace',
'stack' => 'Monaco,"Lucida Sans Typewriter","Lucida Typewriter","Courier New",Courier,monospace',
),
);
return apply_filters( 'kirki_fonts_standard_fonts', $standard_fonts );
}
/**
* Return an array of all available Google Fonts.
*
* @return array All Google Fonts.
*/
public static function get_google_fonts() {
// Get fonts from cache.
self::$google_fonts = get_site_transient( 'kirki_googlefonts_cache' );
/**
* Reset the cache if we're using action=kirki-reset-cache in the URL.
*
* Note to code reviewers:
* There's no need to check nonces or anything else, this is a simple true/false evaluation.
*/
if ( ! empty( $_GET['action'] ) && 'kirki-reset-cache' === $_GET['action'] ) { // phpcs:ignore WordPress.Security.NonceVerification
self::$google_fonts = false;
}
// If cache is populated, return cached fonts array.
if ( self::$google_fonts ) {
return self::$google_fonts;
}
// If we got this far, cache was empty so we need to get from JSON.
ob_start();
include wp_normalize_path( dirname( __FILE__ ) . '/webfonts.json' ); // phpcs:ignore WPThemeReview.CoreFunctionality.FileInclude
$fonts_json = ob_get_clean();
$fonts = json_decode( $fonts_json, true );
$google_fonts = array();
if ( is_array( $fonts ) ) {
foreach ( $fonts['items'] as $font ) {
$google_fonts[ $font['family'] ] = array(
'label' => $font['family'],
'variants' => $font['variants'],
'category' => $font['category'],
);
}
}
// Apply the 'kirki_fonts_google_fonts' filter.
self::$google_fonts = apply_filters( 'kirki_fonts_google_fonts', $google_fonts );
// Save the array in cache.
$cache_time = apply_filters( 'kirki_googlefonts_transient_time', DAY_IN_SECONDS );
set_site_transient( 'kirki_googlefonts_cache', self::$google_fonts, $cache_time );
return self::$google_fonts;
}
/**
* Returns an array of all available subsets.
*
* @static
* @access public
* @return array
*/
public static function get_google_font_subsets() {
return array(
'cyrillic' => 'Cyrillic',
'cyrillic-ext' => 'Cyrillic Extended',
'devanagari' => 'Devanagari',
'greek' => 'Greek',
'greek-ext' => 'Greek Extended',
'khmer' => 'Khmer',
'latin' => 'Latin',
'latin-ext' => 'Latin Extended',
'vietnamese' => 'Vietnamese',
'hebrew' => 'Hebrew',
'arabic' => 'Arabic',
'bengali' => 'Bengali',
'gujarati' => 'Gujarati',
'tamil' => 'Tamil',
'telugu' => 'Telugu',
'thai' => 'Thai',
);
}
/**
* Dummy function to avoid issues with backwards-compatibility.
* This is not functional, but it will prevent PHP Fatal errors.
*
* @static
* @access public
*/
public static function get_google_font_uri() {}
/**
* Returns an array of all available variants.
*
* @static
* @access public
* @return array
*/
public static function get_all_variants() {
return array(
'100' => esc_html__( 'Ultra-Light 100', 'kirki' ),
'100light' => esc_html__( 'Ultra-Light 100', 'kirki' ),
'100italic' => esc_html__( 'Ultra-Light 100 Italic', 'kirki' ),
'200' => esc_html__( 'Light 200', 'kirki' ),
'200italic' => esc_html__( 'Light 200 Italic', 'kirki' ),
'300' => esc_html__( 'Book 300', 'kirki' ),
'300italic' => esc_html__( 'Book 300 Italic', 'kirki' ),
'400' => esc_html__( 'Normal 400', 'kirki' ),
'regular' => esc_html__( 'Normal 400', 'kirki' ),
'italic' => esc_html__( 'Normal 400 Italic', 'kirki' ),
'500' => esc_html__( 'Medium 500', 'kirki' ),
'500italic' => esc_html__( 'Medium 500 Italic', 'kirki' ),
'600' => esc_html__( 'Semi-Bold 600', 'kirki' ),
'600bold' => esc_html__( 'Semi-Bold 600', 'kirki' ),
'600italic' => esc_html__( 'Semi-Bold 600 Italic', 'kirki' ),
'700' => esc_html__( 'Bold 700', 'kirki' ),
'700italic' => esc_html__( 'Bold 700 Italic', 'kirki' ),
'800' => esc_html__( 'Extra-Bold 800', 'kirki' ),
'800bold' => esc_html__( 'Extra-Bold 800', 'kirki' ),
'800italic' => esc_html__( 'Extra-Bold 800 Italic', 'kirki' ),
'900' => esc_html__( 'Ultra-Bold 900', 'kirki' ),
'900bold' => esc_html__( 'Ultra-Bold 900', 'kirki' ),
'900italic' => esc_html__( 'Ultra-Bold 900 Italic', 'kirki' ),
);
}
/**
* Determine if a font-name is a valid google font or not.
*
* @static
* @access public
* @param string $fontname The name of the font we want to check.
* @return bool
*/
public static function is_google_font( $fontname ) {
return ( array_key_exists( $fontname, self::$google_fonts ) );
}
/**
* Gets available options for a font.
*
* @static
* @access public
* @return array
*/
public static function get_font_choices() {
$fonts = self::get_all_fonts();
$fonts_array = array();
foreach ( $fonts as $key => $args ) {
$fonts_array[ $key ] = $key;
}
return $fonts_array;
}
}

View file

@ -0,0 +1,145 @@
<?php
/**
* Adds the Webfont Loader to load fonts asyncronously.
*
* @package Kirki
* @category Core
* @author Ari Stathopoulos (@aristath)
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 3.0
*/
/**
* Manages the way Google Fonts are enqueued.
*/
final class Kirki_Modules_Webfonts_Async {
/**
* The config ID.
*
* @access protected
* @since 3.0.0
* @var string
*/
protected $config_id;
/**
* The Kirki_Modules_Webfonts object.
*
* @access protected
* @since 3.0.0
* @var object
*/
protected $webfonts;
/**
* The Kirki_Fonts_Google object.
*
* @access protected
* @since 3.0.0
* @var object
*/
protected $googlefonts;
/**
* Fonts to load.
*
* @access protected
* @since 3.0.26
* @var array
*/
protected $fonts_to_load = array();
/**
* Constructor.
*
* @access public
* @since 3.0
* @param string $config_id The config-ID.
* @param object $webfonts The Kirki_Modules_Webfonts object.
* @param object $googlefonts The Kirki_Fonts_Google object.
* @param array $args Extra args we want to pass.
*/
public function __construct( $config_id, $webfonts, $googlefonts, $args = array() ) {
$this->config_id = $config_id;
$this->webfonts = $webfonts;
$this->googlefonts = $googlefonts;
add_action( 'wp_head', array( $this, 'webfont_loader' ) );
add_action( 'wp_head', array( $this, 'webfont_loader_script' ), 30 );
// Add these in the dashboard to support editor-styles.
add_action( 'admin_enqueue_scripts', array( $this, 'webfont_loader' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'webfont_loader_script' ), 30 );
add_filter( 'wp_resource_hints', array( $this, 'resource_hints' ), 10, 2 );
}
/**
* Add preconnect for Google Fonts.
*
* @access public
* @param array $urls URLs to print for resource hints.
* @param string $relation_type The relation type the URLs are printed.
* @return array $urls URLs to print for resource hints.
*/
public function resource_hints( $urls, $relation_type ) {
$fonts_to_load = $this->googlefonts->fonts;
if ( ! empty( $fonts_to_load ) && 'preconnect' === $relation_type ) {
$urls[] = array(
'href' => 'https://fonts.gstatic.com',
'crossorigin',
);
}
return $urls;
}
/**
* Webfont Loader for Google Fonts.
*
* @access public
* @since 3.0.0
*/
public function webfont_loader() {
// Go through our fields and populate $this->fonts.
$this->webfonts->loop_fields( $this->config_id );
$this->googlefonts->fonts = apply_filters( 'kirki_enqueue_google_fonts', $this->googlefonts->fonts );
// Goes through $this->fonts and adds or removes things as needed.
$this->googlefonts->process_fonts();
foreach ( $this->googlefonts->fonts as $font => $weights ) {
foreach ( $weights as $key => $value ) {
if ( 'italic' === $value ) {
$weights[ $key ] = '400i';
} else {
$weights[ $key ] = str_replace( array( 'regular', 'bold', 'italic' ), array( '400', '', 'i' ), $value );
}
}
$this->fonts_to_load[] = $font . ':' . join( ',', $weights ) . ':cyrillic,cyrillic-ext,devanagari,greek,greek-ext,khmer,latin,latin-ext,vietnamese,hebrew,arabic,bengali,gujarati,tamil,telugu,thai';
}
if ( ! empty( $this->fonts_to_load ) ) {
Kirki_Modules_Webfont_Loader::$load = true;
}
}
/**
* Webfont Loader script for Google Fonts.
*
* @access public
* @since 3.0.0
*/
public function webfont_loader_script() {
if ( ! empty( $this->fonts_to_load ) ) {
wp_add_inline_script(
'webfont-loader',
'WebFont.load({google:{families:[\'' . join( '\', \'', $this->fonts_to_load ) . '\']}});',
'after'
);
}
}
}

View file

@ -0,0 +1,249 @@
<?php
/**
* Adds the Webfont Loader to load fonts asyncronously.
*
* @package Kirki
* @category Core
* @author Ari Stathopoulos (@aristath)
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 3.0
*/
/**
* Manages the way Google Fonts are enqueued.
*/
final class Kirki_Modules_Webfonts_Embed {
/**
* The config ID.
*
* @access protected
* @since 3.0.0
* @var string
*/
protected $config_id;
/**
* The Kirki_Modules_Webfonts object.
*
* @access protected
* @since 3.0.0
* @var object
*/
protected $webfonts;
/**
* The Kirki_Fonts_Google object.
*
* @access protected
* @since 3.0.0
* @var object
*/
protected $googlefonts;
/**
* Fonts to load.
*
* @access protected
* @since 3.0.26
* @var array
*/
protected $fonts_to_load = array();
/**
* Constructor.
*
* @access public
* @since 3.0
* @param string $config_id The config-ID.
* @param object $webfonts The Kirki_Modules_Webfonts object.
* @param object $googlefonts The Kirki_Fonts_Google object.
* @param array $args Extra args we want to pass.
*/
public function __construct( $config_id, $webfonts, $googlefonts, $args = array() ) {
$this->config_id = $config_id;
$this->webfonts = $webfonts;
$this->googlefonts = $googlefonts;
add_action( 'wp', array( $this, 'init' ), 9 );
add_filter( 'wp_resource_hints', array( $this, 'resource_hints' ), 10, 2 );
}
/**
* Init.
*
* @access public
* @since 3.0.36
* @return void
*/
public function init() {
$this->populate_fonts();
add_action( 'kirki_dynamic_css', array( $this, 'the_css' ) );
}
/**
* Add preconnect for Google Fonts.
*
* @access public
* @param array $urls URLs to print for resource hints.
* @param string $relation_type The relation type the URLs are printed.
* @return array $urls URLs to print for resource hints.
*/
public function resource_hints( $urls, $relation_type ) {
$fonts_to_load = $this->googlefonts->fonts;
if ( ! empty( $fonts_to_load ) && 'preconnect' === $relation_type ) {
$urls[] = array(
'href' => 'https://fonts.gstatic.com',
'crossorigin',
);
}
return $urls;
}
/**
* Webfont Loader for Google Fonts.
*
* @access public
* @since 3.0.0
*/
public function populate_fonts() {
// Go through our fields and populate $this->fonts.
$this->webfonts->loop_fields( $this->config_id );
$this->googlefonts->fonts = apply_filters( 'kirki_enqueue_google_fonts', $this->googlefonts->fonts );
// Goes through $this->fonts and adds or removes things as needed.
$this->googlefonts->process_fonts();
foreach ( $this->googlefonts->fonts as $font => $weights ) {
foreach ( $weights as $key => $value ) {
if ( 'italic' === $value ) {
$weights[ $key ] = '400i';
} else {
$weights[ $key ] = str_replace( array( 'regular', 'bold', 'italic' ), array( '400', '', 'i' ), $value );
}
}
$this->fonts_to_load[] = array(
'family' => $font,
'weights' => $weights,
);
}
}
/**
* Webfont Loader script for Google Fonts.
*
* @access public
* @since 3.0.0
*/
public function the_css() {
foreach ( $this->fonts_to_load as $font ) {
$family = str_replace( ' ', '+', trim( $font['family'] ) );
$weights = join( ',', $font['weights'] );
$url = "https://fonts.googleapis.com/css?family={$family}:{$weights}&subset=cyrillic,cyrillic-ext,devanagari,greek,greek-ext,khmer,latin,latin-ext,vietnamese,hebrew,arabic,bengali,gujarati,tamil,telugu,thai";
$transient_id = 'kirki_gfonts_' . md5( $url );
$contents = get_transient( $transient_id );
/**
* Reset the cache if we're using action=kirki-reset-cache in the URL.
*
* Note to code reviewers:
* There's no need to check nonces or anything else, this is a simple true/false evaluation.
*/
if ( ! empty( $_GET['action'] ) && 'kirki-reset-cache' === $_GET['action'] ) { // phpcs:ignore WordPress.Security.NonceVerification
$contents = false;
}
if ( ! $contents ) {
// Get the contents of the remote URL.
$contents = Kirki_Fonts_Helper::get_remote_url_contents(
$url,
array(
'headers' => array(
/**
* Set user-agent to firefox so that we get woff files.
* If we want woff2, use this instead: 'Mozilla/5.0 (X11; Linux i686; rv:64.0) Gecko/20100101 Firefox/64.0'
*/
'user-agent' => 'Mozilla/5.0 (X11; Linux i686; rv:21.0) Gecko/20100101 Firefox/21.0',
),
)
);
/**
* Allow filtering the font-display property.
*/
$font_display = apply_filters( 'kirki_googlefonts_font_display', 'swap' );
if ( $contents ) {
// Add font-display:swap to improve rendering speed.
$contents = str_replace( '@font-face {', '@font-face{', $contents );
$contents = str_replace( '@font-face{', '@font-face{font-display:' . $font_display . ';', $contents );
// Remove blank lines and extra spaces.
$contents = str_replace(
array( ': ', '; ', '; ', ' ' ),
array( ':', ';', ';', ' ' ),
preg_replace( "/\r|\n/", '', $contents )
);
// Use local fonts.
if ( apply_filters( 'kirki_use_local_fonts', true ) ) {
$contents = $this->use_local_files( $contents );
}
// Remove protocol to fix http/https issues.
$contents = str_replace(
array( 'http://', 'https://' ),
array( '//', '//' ),
$contents
);
// Set the transient for a day.
set_transient( $transient_id, $contents, DAY_IN_SECONDS );
}
}
if ( $contents ) {
/**
* Note to code reviewers:
*
* Though all output should be run through an escaping function, this is pure CSS
* and it is added on a call that has a PHP `header( 'Content-type: text/css' );`.
* No code, script or anything else can be executed from inside a stylesheet.
* For extra security we're using the wp_strip_all_tags() function here
* just to make sure there's no <script> tags in there or anything else.
*/
echo wp_strip_all_tags( $contents ); // phpcs:ignore WordPress.Security.EscapeOutput
}
}
}
/**
* Downloads font-files locally and uses the local files instead of the ones from Google's servers.
* This addresses any and all GDPR concerns, as well as firewalls that exist in some parts of the world.
*
* @access private
* @since 3.0.36
* @param string $css The CSS with original URLs.
* @return string The CSS with local URLs.
*/
private function use_local_files( $css ) {
preg_match_all( '/https\:.*?\.woff/', $css, $matches );
$matches = array_shift( $matches );
foreach ( $matches as $match ) {
if ( 0 === strpos( $match, 'https://fonts.gstatic.com' ) ) {
$new_url = Kirki_Fonts_Helper::download_font_file( $match );
if ( $new_url ) {
$css = str_replace( $match, $new_url, $css );
}
}
}
return $css;
}
}

View file

@ -0,0 +1,145 @@
<?php
/**
* Handles webfonts.
*
* @package Kirki
* @category Modules
* @author Ari Stathopoulos (@aristath)
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 3.0.0
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Adds script for tooltips.
*/
class Kirki_Modules_Webfonts {
/**
* The object instance.
*
* @static
* @access private
* @since 3.0.0
* @var object
*/
private static $instance;
/**
* The Kirki_Fonts_Google object.
*
* @access protected
* @since 3.0.0
* @var object
*/
protected $fonts_google;
/**
* The class constructor
*
* @access protected
* @since 3.0.0
*/
protected function __construct() {
include_once wp_normalize_path( dirname( __FILE__ ) . '/class-kirki-fonts-helper.php' ); // phpcs:ignore WPThemeReview.CoreFunctionality.FileInclude
include_once wp_normalize_path( dirname( __FILE__ ) . '/class-kirki-fonts.php' ); // phpcs:ignore WPThemeReview.CoreFunctionality.FileInclude
include_once wp_normalize_path( dirname( __FILE__ ) . '/class-kirki-fonts-google.php' ); // phpcs:ignore WPThemeReview.CoreFunctionality.FileInclude
add_action( 'wp_loaded', array( $this, 'run' ) );
}
/**
* Run on after_setup_theme.
*
* @access public
* @since 3.0.0
*/
public function run() {
$this->fonts_google = Kirki_Fonts_Google::get_instance();
$this->init();
}
/**
* Gets an instance of this object.
* Prevents duplicate instances which avoid artefacts and improves performance.
*
* @static
* @access public
* @since 3.0.0
* @return object
*/
public static function get_instance() {
if ( ! self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Init other objects depending on the method we'll be using.
*
* @access protected
* @since 3.0.0
*/
protected function init() {
foreach ( array_keys( Kirki::$config ) as $config_id ) {
if ( 'async' === $this->get_method() ) {
new Kirki_Modules_Webfonts_Async( $config_id, $this, $this->fonts_google );
}
new Kirki_Modules_Webfonts_Embed( $config_id, $this, $this->fonts_google );
}
}
/**
* Get the method we're going to use.
*
* @access public
* @since 3.0.0
* @deprecated in 3.0.36.
* @return string
*/
public function get_method() {
return ( is_customize_preview() || is_admin() ) ? 'async' : 'embed';
}
/**
* Goes through all our fields and then populates the $this->fonts property.
*
* @access public
* @param string $config_id The config-ID.
*/
public function loop_fields( $config_id ) {
foreach ( Kirki::$fields as $field ) {
if ( isset( $field['kirki_config'] ) && $config_id !== $field['kirki_config'] ) {
continue;
}
if ( true === apply_filters( "kirki_{$config_id}_webfonts_skip_hidden", true ) ) {
// Only continue if field dependencies are met.
if ( ! empty( $field['required'] ) ) {
$valid = true;
foreach ( $field['required'] as $requirement ) {
if ( isset( $requirement['setting'] ) && isset( $requirement['value'] ) && isset( $requirement['operator'] ) ) {
$controller_value = Kirki_Values::get_value( $config_id, $requirement['setting'] );
if ( ! Kirki_Helper::compare_values( $controller_value, $requirement['value'], $requirement['operator'] ) ) {
$valid = false;
}
}
}
if ( ! $valid ) {
continue;
}
}
}
$this->fonts_google->generate_google_font( $field );
}
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long