Initial commit

This commit is contained in:
Alexander Agnarson 2019-02-10 20:21:07 +01:00
commit ee50200fe7
353 changed files with 78977 additions and 0 deletions

View file

@ -0,0 +1,155 @@
<?php
/**
* Handles the CSS-variables of fields.
*
* @package Kirki
* @category Modules
* @author Aristeides Stathopoulos
* @copyright Copyright (c) 2017, Aristeides Stathopoulos
* @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 );
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,143 @@
<?php
/**
* Writes compiled CSS to a file.
*
* @package Kirki
* @subpackage CSS Module
* @copyright Copyright (c) 2017, Aristeides Stathopoulos
* @license https://opensource.org/licenses/MIT
* @since 3.0.0
*/
/**
* Handles writing CSS to a file.
*/
class Kirki_CSS_To_File {
/**
* Fallback to inline CSS?
*
* @access protected
* @since 3.0.0
* @var bool
*/
protected $fallback = false;
/**
* Constructor.
*
* @access public
* @since 3.0.0
*/
public function __construct() {
// If the file doesn't exist, create it.
if ( ! file_exists( $this->get_path( 'file' ) ) ) {
// If the file-write fails, fallback to inline
// and cache the failure so we don't try again immediately.
$this->write_file();
}
add_action( 'customize_save_after', array( $this, 'write_file' ) );
}
/**
* Gets the path of the CSS file and folder in the filesystem.
*
* @access protected
* @since 3.0.0
* @param string $context Can be "file" or "folder". If empty, returns both as array.
* @return string|array
*/
protected function get_path( $context = '' ) {
$upload_dir = wp_upload_dir();
$paths = array(
'file' => wp_normalize_path( $upload_dir['basedir'] . '/kirki-css/styles.css' ),
'folder' => wp_normalize_path( $upload_dir['basedir'] . '/kirki-css' ),
);
if ( 'file' === $context ) {
return $paths['file'];
}
if ( 'folder' === $context ) {
return $paths['folder'];
}
return $paths;
}
/**
* Gets the URL of the CSS file in the filesystem.
*
* @access public
* @since 3.0.0
* @return string
*/
public function get_url() {
$upload_dir = wp_upload_dir();
return esc_url_raw( $upload_dir['baseurl'] . '/kirki-css/styles.css' );
}
/**
* Gets the timestamp of the file.
* This will be used as "version" for cache-busting purposes.
*
* @access public
* @since 3.0.0
* @return integer|false
*/
public function get_timestamp() {
if ( file_exists( $this->get_path( 'file' ) ) ) {
return filemtime( $this->get_path( 'file' ) );
}
return false;
}
/**
* Writes the file to disk.
*
* @access public
* @since 3.0.0
* @return bool
*/
public function write_file() {
$css = array();
$configs = Kirki::$config;
foreach ( $configs as $config_id => $args ) {
// Get the CSS we want to write.
$css[ $config_id ] = apply_filters( "kirki_{$config_id}_dynamic_css", Kirki_Modules_CSS::loop_controls( $config_id ) );
}
$css = implode( $css, '' );
// If the folder doesn't exist, create it.
if ( ! file_exists( $this->get_path( 'folder' ) ) ) {
wp_mkdir_p( $this->get_path( 'folder' ) );
}
$filesystem = $this->get_filesystem();
$write_file = (bool) $filesystem->put_contents( $this->get_path( 'file' ), $css );
if ( ! $write_file ) {
$this->fallback = true;
set_transient( 'kirki_css_write_to_file_failed', true, HOUR_IN_SECONDS );
}
return $write_file;
}
/**
* Gets the WP_Filesystem object.
*
* @access protected
* @since 3.0.0
* @return object
*/
protected function get_filesystem() {
// The WordPress filesystem.
global $wp_filesystem;
if ( empty( $wp_filesystem ) ) {
require_once wp_normalize_path( ABSPATH . '/wp-admin/includes/file.php' );
WP_Filesystem();
}
return $wp_filesystem;
}
}

View file

@ -0,0 +1,278 @@
<?php
/**
* Generates the styles for the frontend.
* Handles the 'output' argument of fields
*
* @package Kirki
* @category Core
* @author Aristeides Stathopoulos
* @copyright Copyright (c) 2017, Aristeides Stathopoulos
* @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;
/**
* Standard Fonts
*
* @static
* @access public
* @var array
*/
public static $backup_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();
}
if ( is_null( self::$backup_fonts ) ) {
self::$backup_fonts = Kirki_Fonts::get_backup_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';
$field_output_classes = apply_filters(
"kirki_{$field['kirki_config']}_output_control_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',
)
);
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,334 @@
<?php
/**
* Handles the CSS Output of fields.
*
* @package Kirki
* @category Modules
* @author Aristeides Stathopoulos
* @copyright Copyright (c) 2017, Aristeides Stathopoulos
* @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;
/**
* Whether we've already processed this or not.
*
* @access public
* @var bool
*/
public $processed = false;
/**
* The CSS array
*
* @access public
* @var array
*/
public static $css_array = array();
/**
* Set to true if you want to use the AJAX method.
*
* @access public
* @var bool
*/
public static $ajax = false;
/**
* The Kirki_CSS_To_File object.
*
* @access protected
* @since 3.0.0
* @var object
*/
protected $css_to_file;
/**
* Should we enqueue font-awesome?
*
* @static
* @access protected
* @since 3.0.26
* @var bool
*/
protected static $enqueue_fa = false;
/**
* Constructor
*
* @access protected
*/
protected function __construct() {
$class_files = array(
'Kirki_CSS_To_File' => '/class-kirki-css-to-file.php',
'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 );
}
}
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() {
global $wp_customize;
Kirki_Modules_Webfonts::get_instance();
$config = apply_filters( 'kirki_config', array() );
$priority = 999;
if ( isset( $config['styles_priority'] ) ) {
$priority = absint( $config['styles_priority'] );
}
// Allow completely disabling Kirki CSS output.
if ( ( defined( 'KIRKI_NO_OUTPUT' ) && true === KIRKI_NO_OUTPUT ) || ( isset( $config['disable_output'] ) && true === $config['disable_output'] ) ) {
return;
}
$method = apply_filters( 'kirki_dynamic_css_method', 'inline' );
if ( $wp_customize ) {
// If we're in the customizer, load inline no matter what.
add_action( 'wp_enqueue_scripts', array( $this, 'inline_dynamic_css' ), $priority );
add_action( 'enqueue_block_editor_assets', array( $this, 'inline_dynamic_css' ), $priority );
// If we're using file method, on save write the new styles.
if ( 'file' === $method ) {
$this->css_to_file = new Kirki_CSS_To_File();
add_action( 'customize_save_after', array( $this->css_to_file, 'write_file' ) );
}
return;
}
if ( 'file' === $method ) {
// Attempt to write the CSS to file.
$this->css_to_file = new Kirki_CSS_To_File();
// If we succesd, load this file.
$failed = get_transient( 'kirki_css_write_to_file_failed' );
// If writing CSS to file hasn't failed, just enqueue this file.
if ( ! $failed ) {
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_compiled_file' ), $priority );
return;
}
}
// If we are in the customizer, load CSS using inline-styles.
// If we are in the frontend AND self::$ajax is true, then load dynamic CSS using AJAX.
if ( ( true === self::$ajax ) || ( isset( $config['inline_css'] ) && false === $config['inline_css'] ) ) {
add_action( 'wp_enqueue_scripts', array( $this, 'frontend_styles' ), $priority );
add_action( 'wp_ajax_kirki_dynamic_css', array( $this, 'ajax_dynamic_css' ) );
add_action( 'wp_ajax_nopriv_kirki_dynamic_css', array( $this, 'ajax_dynamic_css' ) );
return;
}
// If we got this far then add styles inline.
add_action( 'wp_enqueue_scripts', array( $this, 'inline_dynamic_css' ), $priority );
// Admin styles, adds Gutenberg compatibility.
add_action( 'admin_enqueue_scripts', array( $this, 'inline_dynamic_css' ), $priority );
}
/**
* Enqueues compiled CSS file.
*
* @access public
* @since 3.0.0
*/
public function enqueue_compiled_file() {
wp_enqueue_style( 'kirki-styles', $this->css_to_file->get_url(), array(), $this->css_to_file->get_timestamp() );
}
/**
* Adds inline styles.
*
* @access public
*/
public function inline_dynamic_css() {
$configs = Kirki::$config;
if ( ! $this->processed ) {
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 ) ) {
$stylesheet = apply_filters( "kirki_{$config_id}_stylesheet", false );
if ( $stylesheet ) {
wp_add_inline_style( $stylesheet, $styles );
continue;
}
wp_enqueue_style( 'kirki-styles-' . $config_id, trailingslashit( Kirki::$url ) . 'assets/css/kirki-styles.css', array(), KIRKI_VERSION );
wp_add_inline_style( 'kirki-styles-' . $config_id, $styles );
}
}
$this->processed = true;
}
if ( self::$enqueue_fa && apply_filters( 'kirki_load_fontawesome', true ) ) {
wp_enqueue_script( 'kirki-fontawesome-font', 'https://use.fontawesome.com/30858dc40a.js', array(), '4.0.7', true );
}
}
/**
* Get the dynamic-css.php file
*
* @access public
*/
public function ajax_dynamic_css() {
require wp_normalize_path( Kirki::$path . '/modules/css/dynamic-css.php' );
exit;
}
/**
* Enqueues the ajax stylesheet.
*
* @access public
*/
public function frontend_styles() {
wp_enqueue_style( 'kirki-styles-php', admin_url( 'admin-ajax.php' ) . '?action=kirki_dynamic_css', array(), KIRKI_VERSION );
}
/**
* 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 ) );
}
}
/**
* Runs when we're adding a font-awesome field to allow enqueueing the
* fontawesome script on the frontend.
*
* @static
* @since 3.0.26
* @access public
* @return void
*/
public static function add_fontawesome_script() {
self::$enqueue_fa = true;
}
/**
* Check if FontAwesome should be loaded.
*
* @static
* @since 3.0.35
* @access public
* @return void
*/
public static function get_enqueue_fa() {
return self::$enqueue_fa;
}
}

View file

@ -0,0 +1,345 @@
<?php
/**
* Handles CSS output for fields.
*
* @package Kirki
* @subpackage Controls
* @copyright Copyright (c) 2017, Aristeides Stathopoulos
* @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_string( $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 );
}
$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() ) {
// Check if this is an admin style.
if ( ! isset( $output['context'] ) || ! in_array( 'editor', $output['context'] ) ) {
continue;
}
} elseif ( isset( $output['context'] ) && ! in_array( 'front', $output['context'] ) ) {
// 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) 2017, Aristeides Stathopoulos
* @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,55 @@
<?php
/**
* Handles CSS output for dimensions fields.
*
* @package Kirki
* @subpackage Controls
* @copyright Copyright (c) 2017, Aristeides Stathopoulos
* @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,49 @@
<?php
/**
* Handles CSS output for image fields.
*
* @package Kirki
* @subpackage Controls
* @copyright Copyright (c) 2017, Aristeides Stathopoulos
* @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) 2017, Aristeides Stathopoulos
* @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,95 @@
<?php
/**
* Handles CSS output for typography fields.
*
* @package Kirki
* @subpackage Controls
* @copyright Copyright (c) 2017, Aristeides Stathopoulos
* @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) 2017, Aristeides Stathopoulos
* @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) 2017, Aristeides Stathopoulos
* @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,64 @@
<?php
/**
* Handles CSS output for font-family.
*
* @package Kirki
* @subpackage Controls
* @copyright Copyright (c) 2017, Aristeides Stathopoulos
* @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();
$backup_fonts = Kirki_Fonts::get_backup_fonts();
$family = $this->value;
$backup = '';
if ( is_array( $this->value ) && isset( $this->value[0] ) && isset( $this->value[1] ) ) {
$family = $this->value[0];
$backup = $this->value[1];
}
// 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 backup font.
if ( Kirki_Fonts::is_google_font( $family ) ) {
if ( '' === $backup && isset( $google_fonts_array[ $family ] ) && isset( $backup_fonts[ $google_fonts_array[ $family ]['category'] ] ) ) {
$backup = $backup_fonts[ $google_fonts_array[ $family ]['category'] ];
}
// Add double quotes if needed.
if ( false !== strpos( $family, ' ' ) && false === strpos( $family, '"' ) ) {
$this->value = '"' . $family . '", ' . $backup;
return;
}
$this->value = $family . ', ' . $backup;
return;
}
// 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) 2017, Aristeides Stathopoulos
* @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,158 @@
<?php
/**
* Custom Sections.
*
* @package Kirki
* @category Modules
* @subpackage Custom Sections Module
* @author Aristeides Stathopoulos
* @copyright Copyright (c) 2017, Aristeides Stathopoulos
* @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',
);
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;
continue;
}
$path = str_replace( 'class-kirki-sections-kirki-', 'class-kirki-sections-', $path );
if ( file_exists( $path ) ) {
include_once $path;
}
}
}
// 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;
continue;
}
$path = str_replace( 'class-kirki-panels-kirki-', 'class-kirki-panels-', $path );
if ( file_exists( $path ) ) {
include_once $path;
}
}
}
}
/**
* 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,60 @@
<?php
/**
* Nested section.
*
* @package Kirki
* @subpackage Custom Sections Module
* @copyright Copyright (c) 2017, Aristeides Stathopoulos
* @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,26 @@
#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 .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,256 @@
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://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) 2017, Aristeides Stathopoulos
* @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) 2017, Aristeides Stathopoulos
* @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,67 @@
<?php
/**
* Nested section.
*
* @package Kirki
* @subpackage Custom Sections Module
* @copyright Copyright (c) 2017, Aristeides Stathopoulos
* @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 Aristeides Stathopoulos
* @copyright Copyright (c) 2017, Aristeides Stathopoulos
* @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 Aristeides Stathopoulos
* @copyright Copyright (c) 2017, Aristeides Stathopoulos
* @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 Aristeides Stathopoulos
* @copyright Copyright (c) 2017, Aristeides Stathopoulos
* @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,355 @@
<?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) 2017, Aristeides Stathopoulos
* @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;
/**
* Webfonts Loader Module reference.
*
* @access public
* @since 3.0.35
* @var object $modules_webfonts
*/
private $modules_webfont_loader;
/**
* 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->set_modules_webfont_loader();
$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_action( 'enqueue_block_editor_assets', array( $this, 'load_fonts' ) );
add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_fontawesome' ) );
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
* @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 );
$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
* @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;
}
/**
* Load Fonts in Gutenberg Editor.
*
* @access public
* @since 3.0.35
*/
public function load_fonts() {
$modules_webfonts = $this->modules_webfonts;
$google_fonts = $this->google_fonts;
foreach ( $this->configs as $config_id => $args ) {
if ( $this->is_disabled( $args ) ) {
continue;
}
$this->modules_webfont_loader->set_load( true );
$this->modules_webfont_loader->enqueue_scripts();
$async = new Kirki_Modules_Webfonts_Async(
$config_id,
$modules_webfonts::get_instance(),
$google_fonts::get_instance()
);
$async->webfont_loader();
$async->webfont_loader_script();
$local_fonts = new Kirki_Modules_Webfonts_Local(
$modules_webfonts::get_instance(),
$google_fonts::get_instance()
);
$local_fonts->add_styles();
return;
}
}
/**
* Enqueue fontawesome in Gutenberg Editor.
*
* @access public
* @since 3.0.35
*/
public function enqueue_fontawesome() {
foreach ( $this->configs as $config_id => $args ) {
if ( $this->is_disabled( $args ) ) {
continue;
}
$modules_css = $this->modules_css;
if ( $modules_css::get_enqueue_fa() && apply_filters( 'kirki_load_fontawesome', true ) ) {
wp_enqueue_script( 'kirki-fontawesome-font', 'https://use.fontawesome.com/30858dc40a.js', array(), '4.0.7', true );
}
return;
}
}
/**
* Set class property for $configs.
*
* @access private
* @since 3.0.35
*/
private function set_configs() {
return $this->configs = Kirki::$config;
}
/**
* 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();
}
/**
* Set class property for $modules_webfont_loader.
*
* @access private
* @since 3.0.35
*/
private function set_modules_webfont_loader() {
$this->modules_webfont_loader = Kirki_Modules_Webfont_Loader::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 Aristeides Stathopoulos
* @copyright Copyright (c) 2017, Aristeides Stathopoulos
* @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) 2017, Aristeides Stathopoulos
* @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,515 @@
<?php
/**
* Automatic postMessage scripts calculation for Kirki controls.
*
* @package Kirki
* @category Modules
* @author Aristeides Stathopoulos
* @copyright Copyright (c) 2017, Aristeides Stathopoulos
* @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;
/**
* The script.
*
* @access protected
* @since 3.0.0
* @var string
*/
protected $script = '';
/**
* 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;
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'] ) ) {
$this->script .= $this->script( $field );
}
}
$this->script = apply_filters( 'kirki_postmessage_script', $this->script );
wp_add_inline_script( 'kirki_auto_postmessage', $this->script, 'after' );
}
/**
* Generates script for a single field.
*
* @access protected
* @since 3.0.0
* @param array $args The arguments.
*/
protected function script( $args ) {
$script = 'wp.customize(\'' . $args['settings'] . '\',function(value){value.bind(function(newval){';
$add_css = false;
foreach ( $args['js_vars'] as $js_var ) {
if ( ! isset( $js_var['function'] ) || 'html' !== $js_var['function'] ) {
$add_css = true;
}
}
if ( $add_css ) {
// append unique style tag if not exist
// The style ID.
$style_id = 'kirki-postmessage-' . str_replace( array( '[', ']' ), '', $args['settings'] );
$script .= 'if(null===document.getElementById(\'' . $style_id . '\')||\'undefined\'===typeof document.getElementById(\'' . $style_id . '\')){jQuery(\'head\').append(\'<style id="' . $style_id . '"></style>\');}';
}
// Add anything we need before the main script.
$script .= $this->before_script( $args );
$field = array(
'scripts' => array(),
);
// Loop through the js_vars and generate the script.
foreach ( $args['js_vars'] as $key => $js_var ) {
// Skip styles if "exclude" is defined and value is excluded.
if ( isset( $js_var['exclude'] ) ) {
$js_var['exclude'] = (array) $js_var['exclude'];
$script .= 'exclude=false;';
foreach ( $js_var['exclude'] as $exclussion ) {
$script .= "if(newval=='{$exclussion}'||(''==='{$exclussion}'&&_.isObject(newval)&&_.isEmpty(newval))){exclude=true;}";
}
}
if ( isset( $js_var['element'] ) ) {
// Array to string.
if ( is_array( $js_var['element'] ) ) {
$js_var['element'] = implode( ',', $js_var['element'] );
}
// Replace single quotes with double quotes to avoid issues with the compiled JS.
$js_var['element'] = str_replace( '\'', '"', $js_var['element'] );
}
if ( isset( $js_var['function'] ) && 'html' === $js_var['function'] ) {
$script .= $this->script_html_var( $js_var );
continue;
}
$js_var['index_key'] = $key;
$callback = $this->get_callback( $args );
if ( is_callable( $callback ) ) {
$field['scripts'][ $key ] = call_user_func_array( $callback, array( $js_var, $args ) );
continue;
}
$field['scripts'][ $key ] = $this->script_var( $js_var );
}
$combo_extra_script = '';
$combo_css_script = '';
foreach ( $field['scripts'] as $script_array ) {
$combo_extra_script .= $script_array['script'];
$combo_css_script .= ( 'css' !== $combo_css_script ) ? $script_array['css'] : '';
}
$text = ( 'css' === $combo_css_script ) ? 'css' : '\'' . $combo_css_script . '\'';
$script .= $combo_extra_script . "var cssContent={$text};";
if ( isset( $js_var['exclude'] ) ) {
$script .= 'if(true===exclude){cssContent="";}';
}
if ( $add_css ) {
$script .= "jQuery('#{$style_id}').text(cssContent);jQuery('#{$style_id}').appendTo('head');";
}
$script .= '});});';
return $script;
}
/**
* Generates script for a single js_var when using "html" as function.
*
* @access protected
* @since 3.0.0
* @param array $args The arguments for this js_var.
*/
protected function script_html_var( $args ) {
$script = ( isset( $args['choice'] ) ) ? "newval=newval['{$args['choice']}'];" : '';
// Apply the value_pattern.
if ( isset( $args['value_pattern'] ) && '' !== $args['value_pattern'] ) {
$script .= $this->value_pattern_replacements( 'newval', $args );
}
if ( isset( $args['attr'] ) ) {
$script .= "jQuery('{$args['element']}').attr('{$args['attr']}',newval);";
return $script;
}
$script .= "jQuery('{$args['element']}').html(newval);";
return $script;
}
/**
* Generates script for a single js_var.
*
* @access protected
* @since 3.0.0
* @param array $args The arguments for this js_var.
*/
protected function script_var( $args ) {
$script = '';
$property_script = '';
$value_key = 'newval' . $args['index_key'];
$property_script .= $value_key . '=newval;';
$args = $this->get_args( $args );
// Apply callback to the value if a callback is defined.
if ( ! empty( $args['js_callback'] ) && is_array( $args['js_callback'] ) && isset( $args['js_callback'][0] ) && ! empty( $args['js_callback'][0] ) ) {
$script .= $value_key . '=' . $args['js_callback'][0] . '(' . $value_key . ',' . $args['js_callback'][1] . ');';
}
// Apply the value_pattern.
if ( '' !== $args['value_pattern'] ) {
$script .= $this->value_pattern_replacements( $value_key, $args );
}
// Tweak to add url() for background-images.
if ( 'background-image' === $args['property'] && ( ! isset( $args['value_pattern'] ) || false === strpos( $args['value_pattern'], 'gradient' ) ) ) {
$script .= 'if(-1===' . $value_key . '.indexOf(\'url(\')){' . $value_key . '=\'url("\'+' . $value_key . '+\'")\';}';
}
// Apply prefix.
$value = $value_key;
if ( '' !== $args['prefix'] ) {
$value = "'" . $args['prefix'] . "'+" . $value_key;
}
$css = $args['element'] . '{' . $args['property'] . ':\'+' . $value . '+\'' . $args['units'] . $args['suffix'] . ';}';
if ( isset( $args['media_query'] ) ) {
$css = $args['media_query'] . '{' . $css . '}';
}
return array(
'script' => $property_script . $script,
'css' => $css,
);
}
/**
* Processes script generation for fields that save an array.
*
* @access protected
* @since 3.0.0
* @param array $args The arguments for this js_var.
*/
protected function script_var_array( $args ) {
$script = ( 0 === $args['index_key'] ) ? 'css=\'\';' : '';
$property_script = '';
// Define choice.
$choice = ( isset( $args['choice'] ) && '' !== $args['choice'] ) ? $args['choice'] : '';
$value_key = 'newval' . $args['index_key'];
$property_script .= $value_key . '=newval;';
$args = $this->get_args( $args );
// Apply callback to the value if a callback is defined.
if ( ! empty( $args['js_callback'] ) && is_array( $args['js_callback'] ) && isset( $args['js_callback'][0] ) && ! empty( $args['js_callback'][0] ) ) {
$script .= $value_key . '=' . $args['js_callback'][0] . '(' . $value_key . ',' . $args['js_callback'][1] . ');';
}
$script .= '_.each(' . $value_key . ', function(subValue,subKey){';
// Apply the value_pattern.
if ( '' !== $args['value_pattern'] ) {
$script .= $this->value_pattern_replacements( 'subValue', $args );
}
// Tweak to add url() for background-images.
if ( '' === $choice || 'background-image' === $choice ) {
$script .= 'if(\'background-image\'===\'' . $args['property'] . '\'||\'background-image\'===subKey){if(-1===subValue.indexOf(\'url(\')){subValue=\'url("\'+subValue+\'")\';}}';
}
// Apply prefix.
$value = $value_key;
if ( '' !== $args['prefix'] ) {
$value = '\'' . $args['prefix'] . '\'+subValue';
}
// Mostly used for padding, margin & position properties.
$direction_script = 'if(_.contains([\'top\',\'bottom\',\'left\',\'right\'],subKey)){';
$direction_script .= 'css+=\'' . $args['element'] . '{' . $args['property'] . '-\'+subKey+\':\'+subValue+\'' . $args['units'] . $args['suffix'] . ';}\';}';
// Allows us to apply this just for a specific choice in the array of the values.
if ( '' !== $choice ) {
$choice_is_direction = ( false !== strpos( $choice, 'top' ) || false !== strpos( $choice, 'bottom' ) || false !== strpos( $choice, 'left' ) || false !== strpos( $choice, 'right' ) );
// The script.
$script .= 'if(\'' . $choice . '\'===subKey){';
$script .= ( $choice_is_direction ) ? $direction_script . 'else{' : '';
$script .= 'css+=\'' . $args['element'] . '{' . $args['property'] . ':\'+subValue+\';}\';';
$script .= ( $choice_is_direction ) ? '}' : '';
$script .= '}';
} else {
// This is where most object-based fields will go.
$script .= $direction_script . 'else{css+=\'' . $args['element'] . '{\'+subKey+\':\'+subValue+\'' . $args['units'] . $args['suffix'] . ';}\';}';
}
$script .= '});';
if ( isset( $args['media_query'] ) ) {
$script .= 'css=\'' . $args['media_query'] . '{\'+css+\'}\';';
}
return array(
'script' => $property_script . $script,
'css' => 'css',
);
}
/**
* Processes script generation for typography fields.
*
* @access protected
* @since 3.0.0
* @param array $args The arguments for this js_var.
* @param array $field The field arguments.
*/
protected function script_var_typography( $args, $field ) {
$args = $this->get_args( $args );
$script = '';
$css = '';
// Load the font using WenFontloader.
// This is a bit ugly because wp_add_inline_script doesn't allow adding <script> directly.
$webfont_loader = 'sc=\'a\';jQuery(\'head\').append(sc.replace(\'a\',\'<\')+\'script>if(!_.isUndefined(WebFont)&&fontFamily){WebFont.load({google:{families:["\'+fontFamily.replace( /\"/g, \'&quot;\' )+\':\'+variant+\':cyrillic,cyrillic-ext,devanagari,greek,greek-ext,khmer,latin,latin-ext,vietnamese,hebrew,arabic,bengali,gujarati,tamil,telugu,thai"]}});}\'+sc.replace(\'a\',\'<\')+\'/script>\');';
// Add the css.
$css_build_array = array(
'font-family' => 'fontFamily',
'font-size' => 'fontSize',
'line-height' => 'lineHeight',
'letter-spacing' => 'letterSpacing',
'word-spacing' => 'wordSpacing',
'text-align' => 'textAlign',
'text-transform' => 'textTransform',
'text-decoration' => 'textDecoration',
'color' => 'color',
'font-weight' => 'fontWeight',
'font-style' => 'fontStyle',
);
$choice_condition = ( isset( $args['choice'] ) && '' !== $args['choice'] && isset( $css_build_array[ $args['choice'] ] ) );
$script .= ( ! $choice_condition ) ? $webfont_loader : '';
foreach ( $css_build_array as $property => $var ) {
if ( $choice_condition && $property !== $args['choice'] ) {
continue;
}
// Fixes https://github.com/aristath/kirki/issues/1436.
if ( ! isset( $field['default'] ) || (
( 'font-family' === $property && ! isset( $field['default']['font-family'] ) ) ||
( 'font-size' === $property && ! isset( $field['default']['font-size'] ) ) ||
( 'line-height' === $property && ! isset( $field['default']['line-height'] ) ) ||
( 'letter-spacing' === $property && ! isset( $field['default']['letter-spacing'] ) ) ||
( 'word-spacing' === $property && ! isset( $field['default']['word-spacing'] ) ) ||
( 'text-align' === $property && ! isset( $field['default']['text-align'] ) ) ||
( 'text-transform' === $property && ! isset( $field['default']['text-transform'] ) ) ||
( 'text-decoration' === $property && ! isset( $field['default']['text-decoration'] ) ) ||
( 'color' === $property && ! isset( $field['default']['color'] ) ) ||
( 'font-weight' === $property && ! isset( $field['default']['variant'] ) && ! isset( $field['default']['font-weight'] ) ) ||
( 'font-style' === $property && ! isset( $field['default']['variant'] ) && ! isset( $field['default']['font-style'] ) )
) ) {
continue;
}
$script .= ( $choice_condition && 'font-family' === $args['choice'] ) ? $webfont_loader : '';
if ( 'font-family' === $property || ( isset( $args['choice'] ) && 'font-family' === $args['choice'] ) ) {
$css .= 'fontFamilyCSS=fontFamily;if(0<fontFamily.indexOf(\' \')&&-1===fontFamily.indexOf(\'"\')){fontFamilyCSS=\'"\'+fontFamily+\'"\';}';
$var = 'fontFamilyCSS';
}
$var = ( ( empty( $args['prefix'] ) ) ? '' : '\'' . $args['prefix'] . '\'+' ) . $var . ( ( empty( $args['units'] ) ) ? '' : '+\'' . $args['units'] . '\'' ) . ( ( empty( $args['suffix'] ) ) ? '' : '+\'' . $args['suffix'] . '\'' );
$css .= 'css+=(\'\'!==' . $var . ')?\'' . $args['element'] . '\'+\'{' . $property . ':\'+' . $var . '+\';}\':\'\';';
}
$script .= $css;
if ( isset( $args['media_query'] ) ) {
$script .= 'css=\'' . $args['media_query'] . '{\'+css+\'}\';';
}
return array(
'script' => $script,
'css' => 'css',
);
}
/**
* Processes script generation for typography fields.
*
* @access protected
* @since 3.0.0
* @param array $args The arguments for this js_var.
*/
protected function script_var_image( $args ) {
$return = $this->script_var( $args );
return array(
'script' => 'newval=(!_.isUndefined(newval.url))?newval.url:newval;' . $return['script'],
'css' => $return['css'],
);
}
/**
* Adds anything we need before the main script.
*
* @access private
* @since 3.0.0
* @param array $args The field args.
* @return string
*/
private function before_script( $args ) {
$script = '';
if ( isset( $args['type'] ) ) {
switch ( $args['type'] ) {
case 'kirki-typography':
$script .= 'fontFamily=(_.isUndefined(newval[\'font-family\']))?\'\':newval[\'font-family\'];variant=(_.isUndefined(newval.variant))?\'400\':newval.variant;fontSize=(_.isUndefined(newval[\'font-size\']))?\'\':newval[\'font-size\'];lineHeight=(_.isUndefined(newval[\'line-height\']))?\'\':newval[\'line-height\'];letterSpacing=(_.isUndefined(newval[\'letter-spacing\']))?\'\':newval[\'letter-spacing\'];wordSpacing=(_.isUndefined(newval[\'word-spacing\']))?\'\':newval[\'word-spacing\'];textAlign=(_.isUndefined(newval[\'text-align\']))?\'\':newval[\'text-align\'];textTransform=(_.isUndefined(newval[\'text-transform\']))?\'\':newval[\'text-transform\'];textDecoration=(_.isUndefined(newval[\'text-decoration\']))?\'\':newval[\'text-decoration\'];color=(_.isUndefined(newval.color))?\'\':newval.color;fw=(!_.isString(newval.variant))?\'400\':newval.variant.match(/\d/g);fontWeight=(!_.isObject(fw))?400:fw.join(\'\');fontStyle=(variant&&-1!==variant.indexOf(\'italic\'))?\'italic\':\'normal\';css=\'\';';
break;
}
}
return $script;
}
/**
* Sanitizes the arguments and makes sure they are all there.
*
* @access private
* @since 3.0.0
* @param array $args The arguments.
* @return array
*/
private function get_args( $args ) {
// Make sure everything is defined to avoid "undefined index" errors.
$args = wp_parse_args(
$args, array(
'element' => '',
'property' => '',
'prefix' => '',
'suffix' => '',
'units' => '',
'js_callback' => array( '', '' ),
'value_pattern' => '',
)
);
// Element should be a string.
if ( is_array( $args['element'] ) ) {
$args['element'] = implode( ',', $args['element'] );
}
// Make sure arguments that are passed-on to callbacks are strings.
if ( is_array( $args['js_callback'] ) && isset( $args['js_callback'][1] ) && is_array( $args['js_callback'][1] ) ) {
$args['js_callback'][1] = wp_json_encode( $args['js_callback'][1] );
}
if ( ! isset( $args['js_callback'][1] ) ) {
$args['js_callback'][1] = '';
}
return $args;
}
/**
* Returns script for value_pattern & replacements.
*
* @access private
* @since 3.0.0
* @param string $value The value placeholder.
* @param array $js_vars The js_vars argument.
* @return string The script.
*/
private function value_pattern_replacements( $value, $js_vars ) {
$script = '';
$alias = $value;
if ( ! isset( $js_vars['value_pattern'] ) ) {
return $value;
}
$value = $js_vars['value_pattern'];
if ( isset( $js_vars['pattern_replace'] ) ) {
$script .= 'settings=window.wp.customize.get();';
foreach ( $js_vars['pattern_replace'] as $search => $replace ) {
$replace = '\'+settings["' . $replace . '"]+\'';
$value = str_replace( $search, $replace, $js_vars['value_pattern'] );
$value = trim( $value, '+' );
}
}
$value_compiled = str_replace( '$', '\'+' . $alias . '+\'', $value );
$value_compiled = trim( $value_compiled, '+' );
return $script . $alias . '=\'' . $value_compiled . '\';';
}
/**
* Get the callback function/method we're going to use for this field.
*
* @access private
* @since 3.0.0
* @param array $args The field args.
* @return string|array A callable function or method.
*/
protected function get_callback( $args ) {
switch ( $args['type'] ) {
case 'kirki-background':
case 'kirki-dimensions':
case 'kirki-multicolor':
case 'kirki-sortable':
$callback = array( $this, 'script_var_array' );
break;
case 'kirki-typography':
$callback = array( $this, 'script_var_typography' );
break;
case 'kirki-image':
$callback = array( $this, 'script_var_image' );
break;
default:
$callback = array( $this, 'script_var' );
}
return $callback;
}
}

View file

@ -0,0 +1,4 @@
/**
* This file is empty on purpose.
* Scripts are added dynamically using wp_add_inline_script() on this.
*/

View file

@ -0,0 +1,68 @@
<?php
/**
* Automatic preset scripts calculation for Kirki controls.
*
* @package Kirki
* @category Modules
* @author Aristeides Stathopoulos
* @copyright Copyright (c) 2017, Aristeides Stathopoulos
* @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,88 @@
<?php
/**
* Handles sections created via the Kirki API.
*
* @package Kirki
* @category Modules
* @author Aristeides Stathopoulos
* @copyright Copyright (c) 2017, Aristeides Stathopoulos
* @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,118 @@
<?php
/**
* Injects tooltips to controls when the 'tooltip' argument is used.
*
* @package Kirki
* @category Modules
* @author Aristeides Stathopoulos
* @copyright Copyright (c) 2017, Aristeides Stathopoulos
* @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 Aristeides Stathopoulos
* @copyright Copyright (c) 2017, Aristeides Stathopoulos
* @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,483 @@
<?php
/**
* Handles downloading a font from the google-fonts API locally.
* Solves privacy concerns with Google's CDN
* and their sometimes less-than-transparent policies.
*
* @package Kirki
* @category Core
* @author Aristeides Stathopoulos
* @copyright Copyright (c) 2017, Aristeides Stathopoulos
* @license https://opensource.org/licenses/MIT
* @since 3.0.28
*/
// 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_Google_Local {
/**
* The name of the font-family
*
* @access private
* @since 3.0.28
* @var string
*/
private $family;
/**
* The system path where font-files are stored.
*
* @access private
* @since 3.0.28
* @var string
*/
private $folder_path;
/**
* The URL where files for this font can be found.
*
* @access private
* @since 3.0.28
* @var string
*/
private $folder_url;
/**
* The font-family array from the google-fonts API.
*
* @access private
* @since 3.0.28
* @var array
*/
private $font;
/**
* An array of instances for this object.
*
* @static
* @access private
* @since 3.0.28
* @var array
*/
private static $instances = array();
/**
* Create an instance of this object for a specific font-family.
*
* @static
* @access public
* @since 3.0.28
* @param string $family The font-family name.
* @return Kirki_Fonts_Google_Local
*/
public static function init( $family ) {
$key = sanitize_key( $family );
if ( ! isset( self::$instances[ $key ] ) ) {
self::$instances[ $key ] = new self( $family );
}
return self::$instances[ $key ];
}
/**
* Constructor.
*
* @access private
* @since 3.0.28
* @param string $family The font-family name.
*/
private function __construct( $family ) {
$this->family = $family;
$key = sanitize_key( $this->family );
$this->folder_path = $this->get_root_path() . "/$key";
$this->folder_url = $this->get_root_url() . "/$key";
$this->files = $this->get_font_family();
}
/**
* Gets the @font-face CSS.
*
* @access public
* @since 3.0.28
* @param array $variants The variants we want to get.
* @return string
*/
public function get_css( $variants = array() ) {
if ( ! $this->files ) {
return;
}
$key = md5( wp_json_encode( $this->files ) );
$cached = get_transient( $key );
if ( $cached ) {
return $cached;
}
$css = '';
// If $variants is empty then use all variants available.
if ( empty( $variants ) ) {
$variants = array_keys( $this->files );
}
// Download files.
$this->download_font_family( $variants );
// Create the @font-face CSS.
foreach ( $variants as $variant ) {
$css .= $this->get_variant_fontface_css( $variant );
}
set_transient( $key, $css, DAY_IN_SECONDS );
return $css;
}
/**
* Get the @font-face CSS for a specific variant.
*
* @access public
* @since 3.0.28
* @param string $variant The variant.
* @return string
*/
public function get_variant_fontface_css( $variant ) {
$font_face = "@font-face{font-family:'{$this->family}';";
// Get the font-style.
$font_style = ( false !== strpos( $variant, 'italic' ) ) ? 'italic' : 'normal';
$font_face .= "font-style:{$font_style};";
// Get the font-weight.
$font_weight = '400';
$font_weight = str_replace( 'italic', '', $variant );
$font_weight = ( ! $font_weight || 'regular' === $font_weight ) ? '400' : $font_weight;
$font_face .= "font-weight:{$font_weight};";
// Get the font-names.
$font_name_0 = $this->get_local_font_name( $variant, false );
$font_name_1 = $this->get_local_font_name( $variant, true );
$font_face .= "src:local('{$font_name_0}'),";
if ( $font_name_0 !== $font_name_1 ) {
$font_face .= "local('{$font_name_1}'),";
}
// Get the font-url.
$font_url = $this->get_variant_local_url( $variant );
$paths = $this->get_font_files_paths();
if ( ! file_exists( $paths[ $variant ] ) ) {
$font_url = $this->files[ $variant ];
}
// Get the font-format.
$font_format = ( strpos( $font_url, '.woff2' ) ) ? 'woff2' : 'truetype';
$font_format = ( strpos( $font_url, '.woff' ) && ! strpos( $font_url, '.woff2' ) ) ? 'woff' : $font_format;
$font_face .= "url({$font_url}) format('{$font_format}');}";
return $font_face;
}
/**
* Gets the local URL for a variant.
*
* @access public
* @since 3.0.28
* @param string $variant The variant.
* @return string The URL.
*/
public function get_variant_local_url( $variant ) {
$local_urls = $this->get_font_files_urls_local();
if ( empty( $local_urls ) ) {
return;
}
// Return the specific variant if we can find it.
if ( isset( $local_urls[ $variant ] ) ) {
return $local_urls[ $variant ];
}
// Return regular if the one we want could not be found.
if ( isset( $local_urls['regular'] ) ) {
return $local_urls['regular'];
}
// Return the first available if all else failed.
$vals = array_values( $local_urls );
return $vals[0];
}
/**
* Get the name of the font-family.
* This is used by @font-face in case the user already has the font downloaded locally.
*
* @access public
* @since 3.0.28
* @param string $variant The variant.
* @param bool $compact Whether we want the compact formatting or not.
* @return string
*/
public function get_local_font_name( $variant, $compact = false ) {
$variant_names = array(
'100' => 'Thin',
'100i' => 'Thin Italic',
'100italic' => 'Thin Italic',
'200' => 'Extra-Light',
'200i' => 'Extra-Light Italic',
'200italic' => 'Extra-Light Italic',
'300' => 'Light',
'300i' => 'Light Italic',
'300italic' => 'Light Italic',
'400' => 'Regular',
'regular' => 'Regular',
'400i' => 'Regular Italic',
'italic' => 'Italic',
'400italic' => 'Regular Italic',
'500' => 'Medium',
'500i' => 'Medium Italic',
'500italic' => 'Medium Italic',
'600' => 'Semi-Bold',
'600i' => 'Semi-Bold Italic',
'600italic' => 'Semi-Bold Italic',
'700' => 'Bold',
'700i' => 'Bold Italic',
'700italic' => 'Bold Italic',
'800' => 'Extra-Bold',
'800i' => 'Extra-Bold Italic',
'800italic' => 'Extra-Bold Italic',
'900' => 'Black',
'900i' => 'Black Italic',
'900italic' => 'Black Italic',
);
$variant = (string) $variant;
if ( $compact ) {
if ( isset( $variant_names[ $variant ] ) ) {
return str_replace( array( ' ', '-' ), '', $this->family ) . '-' . str_replace( array( ' ', '-' ), '', $variant_names[ $variant ] );
}
return str_replace( array( ' ', '-' ), '', $this->family );
}
if ( isset( $variant_names[ $variant ] ) ) {
return $this->family . ' ' . $variant_names[ $variant ];
}
return $this->family;
}
/**
* Get an array of font-files.
* Only contains the filenames.
*
* @access public
* @since 3.0.28
* @return array
*/
public function get_font_files() {
$files = array();
foreach ( $this->files as $key => $url ) {
$files[ $key ] = $this->get_filename_from_url( $url );
}
return $files;
}
/**
* Get an array of local file URLs.
*
* @access public
* @since 3.0.28
* @return array
*/
public function get_font_files_urls_local() {
$urls = array();
$files = $this->get_font_files();
foreach ( $files as $key => $file ) {
$urls[ $key ] = $this->folder_url . '/' . $file;
}
return $urls;
}
/**
* Get an array of local file paths.
*
* @access public
* @since 3.0.28
* @return array
*/
public function get_font_files_paths() {
$paths = array();
$files = $this->get_font_files();
foreach ( $files as $key => $file ) {
$paths[ $key ] = $this->folder_path . '/' . $file;
}
return $paths;
}
/**
* Downloads a font-file and saves it locally.
*
* @access private
* @since 3.0.28
* @param string $url The URL of the file we want to get.
* @return bool
*/
private function download_font_file( $url ) {
$contents = $this->get_remote_url_contents( $url );
$path = $this->folder_path . '/' . $this->get_filename_from_url( $url );
// If the folder doesn't exist, create it.
if ( ! file_exists( $this->folder_path ) ) {
wp_mkdir_p( $this->folder_path );
}
// If the file exists no reason to do anything.
if ( file_exists( $path ) ) {
return true;
}
// Write file.
return Kirki_Helper::init_filesystem()->put_contents( $path, $contents, FS_CHMOD_FILE );
}
/**
* Get a font-family from the array of google-fonts.
*
* @access public
* @since 3.0.28
* @return array
*/
public function get_font_family() {
// Get the fonts array.
$fonts = $this->get_fonts();
if ( isset( $fonts[ $this->family ] ) ) {
return $fonts[ $this->family ];
}
return array();
}
/**
* Gets the filename by breaking-down the URL parts.
*
* @access private
* @since 3.0.28
* @param string $url The URL.
* @return string The filename.
*/
private 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;
}
/**
* Get the font defined in the google-fonts API.
*
* @access private
* @since 3.0.28
* @return array
*/
private function get_fonts() {
ob_start();
include wp_normalize_path( dirname( __FILE__ ) . '/webfont-files.json' );
$json = ob_get_clean();
return json_decode( $json, true );
}
/**
* Gets the root fonts folder path.
* Other paths are built based on this.
*
* @since 1.5
* @access public
* @return string
*/
public 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 root folder url.
* Other urls are built based on this.
*
* @since 1.5
* @access public
* @return string
*/
public 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' );
}
/**
* Download font-family files.
*
* @access public
* @since 3.0.28
* @param array $variants An array of variants to download. Leave empty to download all.
* @return void
*/
public function download_font_family( $variants = array() ) {
if ( empty( $variants ) ) {
$variants = array_keys( $this->files );
}
foreach ( $this->files as $variant => $file ) {
if ( in_array( $variant, $variants ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
$this->download_font_file( $file );
}
}
}
/**
* Gets the remote URL contents.
*
* @access private
* @since 3.0.28
* @param string $url The URL we want to get.
* @return string The contents of the remote URL.
*/
public function get_remote_url_contents( $url ) {
$response = wp_remote_get( $url );
if ( is_wp_error( $response ) ) {
return array();
}
$html = wp_remote_retrieve_body( $response );
if ( is_wp_error( $html ) ) {
return;
}
return $html;
}
}

View file

@ -0,0 +1,262 @@
<?php
/**
* Processes typography-related fields
* and generates the google-font link.
*
* @package Kirki
* @category Core
* @author Aristeides Stathopoulos
* @copyright Copyright (c) 2017, Aristeides Stathopoulos
* @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 ) {
global $wp_customize;
// Process typography fields.
if ( isset( $args['type'] ) && 'kirki-typography' === $args['type'] ) {
// Get the value.
$value = Kirki_Values::get_sanitized_field_value( $args );
if ( isset( $value['downloadFont'] ) && $value['downloadFont'] ) {
$this->hosted_fonts[] = $value['font-family'];
}
// If we don't have a font-family then we can skip this.
if ( ! $wp_customize && ( ! 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( $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' );
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();
}
/**
* Gets $this->hosted_fonts.
*
* @access public
* @since 3.0.32
* @return array
*/
public function get_hosted_fonts() {
return $this->hosted_fonts;
}
}

View file

@ -0,0 +1,262 @@
<?php
/**
* A simple object containing properties for fonts.
*
* @package Kirki
* @category Core
* @author Aristeides Stathopoulos
* @copyright Copyright (c) 2017, Aristeides Stathopoulos
* @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 backup fonts based on the font-category
*
* @return array
*/
public static function get_backup_fonts() {
$backup_fonts = array(
'sans-serif' => 'Helvetica, Arial, sans-serif',
'serif' => 'Georgia, serif',
'display' => '"Comic Sans MS", cursive, sans-serif',
'handwriting' => '"Comic Sans MS", cursive, sans-serif',
'monospace' => '"Lucida Console", Monaco, monospace',
);
return apply_filters( 'kirki_fonts_backup_fonts', $backup_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' );
// If we're debugging, don't use cached.
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
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' );
$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', HOUR_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 Aristeides Stathopoulos
* @copyright Copyright (c) 2017, Aristeides Stathopoulos
* @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,105 @@
<?php
/**
* Handles adding to the footer the @font-face CSS for locally-hosted google-fonts.
* Solves privacy concerns with Google's CDN and their sometimes less-than-transparent policies.
*
* @package Kirki
* @category Core
* @author Aristeides Stathopoulos
* @copyright Copyright (c) 2017, Aristeides Stathopoulos
* @license https://opensource.org/licenses/MIT
* @since 3.0.28
*/
/**
* Manages the way Google Fonts are enqueued.
*/
final class Kirki_Modules_Webfonts_Local {
/**
* The config ID.
*
* @access protected
* @since 3.0.28
* @var string
*/
protected $config_id;
/**
* The Kirki_Modules_Webfonts object.
*
* @access protected
* @since 3.0.28
* @var object
*/
protected $webfonts;
/**
* The Kirki_Fonts_Google object.
*
* @access protected
* @since 3.0.28
* @var object
*/
protected $googlefonts;
/**
* Fonts to load.
*
* @access protected
* @since 3.0.28
* @var array
*/
protected $fonts_to_load = array();
/**
* Constructor.
*
* @access public
* @since 3..28
* @param object $webfonts The Kirki_Modules_Webfonts object.
* @param object $googlefonts The Kirki_Fonts_Google object.
*/
public function __construct( $webfonts, $googlefonts ) {
$this->webfonts = $webfonts;
$this->googlefonts = $googlefonts;
add_action( 'wp_footer', array( $this, 'add_styles' ) );
add_action( 'admin_footer', array( $this, 'add_styles' ) );
}
/**
* Webfont Loader for Google Fonts.
*
* @access public
* @since 3.0.28
*/
public function add_styles() {
// Go through our fields and populate $this->fonts.
$this->webfonts->loop_fields( $this->config_id );
$this->googlefonts->process_fonts();
$hosted_fonts = $this->googlefonts->get_hosted_fonts();
// Early exit if we don't need to add any fonts.
if ( empty( $hosted_fonts ) ) {
return;
}
// Make sure we only do this once per font-family.
$hosted_fonts = array_unique( $hosted_fonts );
// Start CSS.
$css = '';
foreach ( $hosted_fonts as $family ) {
// Add the @font-face CSS for this font-family.
$css .= Kirki_Fonts_Google_Local::init( $family )->get_css();
}
// If we've got CSS, add to the footer.
if ( $css ) {
echo '<style id="kirki-local-webfonts-' . esc_attr( sanitize_key( $this->config_id ) ) . '">' . $css . '</style>'; // WPCS: XSS ok.
}
}
}

View file

@ -0,0 +1,161 @@
<?php
/**
* Handles webfonts.
*
* @package Kirki
* @category Modules
* @author Aristeides Stathopoulos
* @copyright Copyright (c) 2017, Aristeides Stathopoulos
* @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.php' );
include_once wp_normalize_path( dirname( __FILE__ ) . '/class-kirki-fonts-google.php' );
include_once wp_normalize_path( dirname( __FILE__ ) . '/class-kirki-fonts-google-local.php' );
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 ) {
$method = $this->get_method( $config_id );
$classname = 'Kirki_Modules_Webfonts_' . ucfirst( $method );
new $classname( $config_id, $this, $this->fonts_google );
}
new Kirki_Modules_Webfonts_Local( $this, $this->fonts_google );
}
/**
* Get the method we're going to use.
*
* @access public
* @since 3.0.0
* @return string
*/
public function get_method() {
// Figure out which method to use.
$method = apply_filters( 'kirki_googlefonts_load_method', 'async' );
// Fallback to 'async' if value is invalid.
if ( 'async' !== $method && 'embed' !== $method && 'link' !== $method ) {
$method = 'async';
}
$classname = 'Kirki_Modules_Webfonts_' . ucfirst( $method );
if ( ! class_exists( $classname ) ) {
$method = 'async';
}
// Force using the JS method while in the customizer.
// This will help us work-out the live-previews for typography fields.
// If we're not in the customizer use the defined method.
return ( is_customize_preview() ) ? 'async' : $method;
}
/**
* 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