Initial commit

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

View file

@ -0,0 +1,177 @@
<?php
/**
* Processes configurations.
*
* @package Kirki
* @category Core
* @author Ari Stathopoulos (@aristath)
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
*/
/**
* The Kirki_Config object
*/
final class Kirki_Config {
/**
* Each instance is stored separately in this array.
*
* @static
* @access private
* @var array
*/
private static $instances = array();
/**
* The finalized configuration array.
*
* @access protected
* @var array
*/
protected $config_final = array();
/**
* The configuration ID.
*
* @access public
* @var string
*/
public $id = 'global';
/**
* Capability (fields will inherit this).
*
* @access protected
* @var string
*/
protected $capability = 'edit_theme_options';
/**
* The data-type we'll be using.
*
* @access protected
* @var string
*/
protected $option_type = 'theme_mod';
/**
* If we're using serialized options, then this is the global option name.
*
* @access protected
* @var string
*/
protected $option_name = '';
/**
* The compiler.
*
* @access protected
* @var array
*/
protected $compiler = array();
/**
* Set to true if you want to completely disable any Kirki-generated CSS.
*
* @access protected
* @var bool
*/
protected $disable_output = false;
/**
* The class constructor.
* Use the get_instance() static method to get the instance you need.
*
* @access private
* @param string $config_id @see Kirki_Config::get_instance().
* @param array $args @see Kirki_Config::get_instance().
*/
private function __construct( $config_id = 'global', $args = array() ) {
// Get defaults from the class.
$defaults = get_class_vars( __CLASS__ );
// Skip what we don't need in this context.
unset( $defaults['config_final'] );
unset( $defaults['instances'] );
// Apply any kirki_config global filters.
$defaults = apply_filters( 'kirki_config', $defaults );
// Merge our args with the defaults.
$args = wp_parse_args( $args, $defaults );
// Modify default values with the defined ones.
foreach ( $args as $key => $value ) {
// Is this property whitelisted?
if ( property_exists( $this, $key ) ) {
$args[ $key ] = $value;
}
}
$this->id = $config_id;
$this->config_final = wp_parse_args(
array(
'id' => $config_id,
),
$args
);
}
/**
* Use this method to get an instance of your config.
* Each config has its own instance of this object.
*
* @static
* @access public
* @param string $id Config ID.
* @param array $args {
* Optional. Arguments to override config defaults.
*
* @type string $capability @see https://codex.wordpress.org/Roles_and_Capabilities
* @type string $option_type theme_mod or option.
* @type string $option_name If we want to used serialized options,
* this is where we'll be adding the option name.
* All fields using this config will be items in that array.
* @type array $compiler Not yet fully implemented
* @type bool $disable_output If set to true, no CSS will be generated
* from fields using this configuration.
* }
*
* @return Kirki_Config
*/
public static function get_instance( $id = 'global', $args = array() ) {
if ( empty( $id ) ) {
$id = 'global';
}
$id_md5 = md5( $id );
if ( ! isset( self::$instances[ $id_md5 ] ) ) {
self::$instances[ $id_md5 ] = new self( $id, $args );
}
return self::$instances[ $id_md5 ];
}
/**
* Get the IDs of all current configs.
*
* @static
* @access public
* @since 3.0.22
* @return array
*/
public static function get_config_ids() {
$configs = array();
foreach ( self::$instances as $instance ) {
$configs[] = $instance->id;
}
return array_unique( $configs );
}
/**
* Returns the $config_final property
*
* @access public
* @return array
*/
public function get_config() {
return $this->config_final;
}
}

View file

@ -0,0 +1,117 @@
<?php
/**
* Controls handler
*
* @package Kirki
* @category Core
* @author Ari Stathopoulos (@aristath)
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
*/
/**
* Our main Kirki_Control object
*/
class Kirki_Control {
/**
* The $wp_customize WordPress global.
*
* @access protected
* @var WP_Customize_Manager
*/
protected $wp_customize;
/**
* An array of all available control types.
*
* @access protected
* @var array
*/
protected static $control_types = array();
/**
* The class constructor.
* Creates the actual controls in the customizer.
*
* @access public
* @param array $args The field definition as sanitized in Kirki_Field.
*/
public function __construct( $args ) {
// Set the $wp_customize property.
global $wp_customize;
if ( ! $wp_customize ) {
return;
}
$this->wp_customize = $wp_customize;
// Set the control types.
$this->set_control_types();
// Add the control.
$this->add_control( $args );
}
/**
* Get the class name of the class needed to create tis control.
*
* @access private
* @param array $args The field definition as sanitized in Kirki_Field.
*
* @return string the name of the class that will be used to create this control.
*/
final private function get_control_class_name( $args ) {
// Set a default class name.
$class_name = 'WP_Customize_Control';
// Get the classname from the array of control classnames.
if ( array_key_exists( $args['type'], self::$control_types ) ) {
$class_name = self::$control_types[ $args['type'] ];
}
return $class_name;
}
/**
* Adds the control.
*
* @access protected
* @param array $args The field definition as sanitized in Kirki_Field.
*/
final protected function add_control( $args ) {
// Get the name of the class we're going to use.
$class_name = $this->get_control_class_name( $args );
// Add the control.
$this->wp_customize->add_control( new $class_name( $this->wp_customize, $args['settings'], $args ) );
}
/**
* Sets the $control_types property.
* Makes sure the kirki_control_types filter is applied
* and that the defined classes actually exist.
* If a defined class does not exist, it is removed.
*
* @access private
*/
final private function set_control_types() {
// Early exit if this has already run.
if ( ! empty( self::$control_types ) ) {
return;
}
self::$control_types = apply_filters( 'kirki_control_types', array() );
// Make sure the defined classes actually exist.
foreach ( self::$control_types as $key => $classname ) {
if ( ! class_exists( $classname ) ) {
unset( self::$control_types[ $key ] );
}
}
}
}

View file

@ -0,0 +1,661 @@
<?php
/**
* Creates and validates field parameters.
*
* @package Kirki
* @category Core
* @author Ari Stathopoulos (@aristath)
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 1.0
*/
/**
* Please do not use this class directly.
* You should instead extend it per-field-type.
*/
class Kirki_Field {
/**
* An array of the field arguments.
*
* @access protected
* @var array
*/
protected $args = array();
/**
* The ID of the kirki_config we're using.
*
* @see Kirki_Config
* @access protected
* @var string
*/
protected $kirki_config = 'global';
/**
* Thje capability required so that users can edit this field.
*
* @access protected
* @var string
*/
protected $capability = 'edit_theme_options';
/**
* If we're using options instead of theme_mods
* and we want them serialized, this is the option that
* will saved in the db.
*
* @access protected
* @var string
*/
protected $option_name = '';
/**
* Custom input attributes (defined as an array).
*
* @access protected
* @var array
*/
protected $input_attrs = array();
/**
* Preset choices.
*
* @access protected
* @var array
*/
protected $preset = array();
/**
* CSS Variables.
*
* @access protected
* @var array
*/
protected $css_vars = array();
/**
* Use "theme_mod" or "option".
*
* @access protected
* @var string
*/
protected $option_type = 'theme_mod';
/**
* The name of this setting (id for the db).
*
* @access protected
* @var string|array
*/
protected $settings = '';
/**
* Set to true if you want to disable all CSS output for this field.
*
* @access protected
* @var bool
*/
protected $disable_output = false;
/**
* The field type.
*
* @access protected
* @var string
*/
protected $type = 'kirki-generic';
/**
* Some fields require options to be set.
* We're whitelisting the property here
* and suggest you validate this in a child class.
*
* @access protected
* @var array
*/
protected $choices = array();
/**
* Assign this field to a section.
* Fields not assigned to a section will not be displayed in the customizer.
*
* @access protected
* @var string
*/
protected $section = '';
/**
* The default value for this field.
*
* @access protected
* @var string|array|bool
*/
protected $default = '';
/**
* Priority determines the position of a control inside a section.
* Lower priority numbers move the control to the top.
*
* @access protected
* @var int
*/
protected $priority = 10;
/**
* Unique ID for this field.
* This is auto-calculated from the $settings argument.
*
* @access protected
* @var string
*/
protected $id = '';
/**
* Use if you want to automatically generate CSS from this field's value.
*
* @see https://kirki.org/docs/arguments/output
* @access protected
* @var array
*/
protected $output = array();
/**
* Use to automatically generate postMessage scripts.
* Not necessary to use if you use 'transport' => 'auto'
* and have already set an array for the 'output' argument.
*
* @see https://kirki.org/docs/arguments/js_vars
* @access protected
* @var array
*/
protected $js_vars = array();
/**
* If you want to use a CSS compiler, then use this to set the variable names.
*
* @see https://kirki.org/docs/arguments/variables
* @access protected
* @var array
*/
protected $variables = array();
/**
* Text that will be used in a tooltip to provide extra info for this field.
*
* @access protected
* @var string
*/
protected $tooltip = '';
/**
* A custom callback to determine if the field should be visible or not.
*
* @access protected
* @var string|array
*/
protected $active_callback = '__return_true';
/**
* A custom sanitize callback that will be used to properly save the values.
*
* @access protected
* @var string|array
*/
protected $sanitize_callback = '';
/**
* Use 'refresh', 'postMessage' or 'auto'.
* 'auto' will automatically geberate any 'js_vars' from the 'output' argument.
*
* @access protected
* @var string
*/
protected $transport = 'refresh';
/**
* Define dependencies to show/hide this field based on the values of other fields.
*
* @access protected
* @var array
*/
protected $required = array();
/**
* Partial Refreshes array.
*
* @access protected
* @var array
*/
protected $partial_refresh = array();
/**
* The class constructor.
* Parses and sanitizes all field arguments.
* Then it adds the field to Kirki::$fields.
*
* @access public
* @param string $config_id The ID of the config we want to use.
* Defaults to "global".
* Configs are handled by the Kirki_Config class.
* @param array $args The arguments of the field.
*/
public function __construct( $config_id = 'global', $args = array() ) {
if ( isset( $args['setting'] ) && ! empty( $args['setting'] ) && ( ! isset( $args['settings'] ) || empty( $args['settings'] ) ) ) {
/* translators: %s represents the field ID where the error occurs. */
_doing_it_wrong( __METHOD__, sprintf( esc_html__( 'Typo found in field %s - setting instead of settings.', 'kirki' ), esc_html( $args['settings'] ) ), '3.0.10' );
$args['settings'] = $args['setting'];
unset( $args['setting'] );
}
// In case the user only provides 1 argument,
// assume that the provided argument is $args and set $config_id = 'global'.
if ( is_array( $config_id ) && empty( $args ) ) {
/* translators: %1$s represents the field ID where the error occurs. %2$s is the URL in the documentation site. */
_doing_it_wrong( __METHOD__, sprintf( esc_html__( 'Config not defined for field %1$s - See %2$s for details on how to properly add fields.', 'kirki' ), esc_html( $args['settings'] ), 'https://kirki.org/docs/getting-started/fields.html' ), '3.0.10' );
$args = $config_id;
$config_id = 'global';
}
$args['kirki_config'] = $config_id;
$this->kirki_config = $config_id;
if ( '' === $config_id ) {
/* translators: %1$s represents the field ID where the error occurs. %2$s is the URL in the documentation site. */
_doing_it_wrong( __METHOD__, sprintf( esc_html__( 'Config not defined for field %1$s - See %2$s for details on how to properly add fields.', 'kirki' ), esc_html( $args['settings'] ), 'https://kirki.org/docs/getting-started/fields.html' ), '3.0.10' );
$this->kirki_config = 'global';
}
// Get defaults from the class.
$defaults = get_class_vars( __CLASS__ );
// Get the config arguments, and merge them with the defaults.
$config_defaults = ( isset( Kirki::$config['global'] ) ) ? Kirki::$config['global'] : array();
if ( 'global' !== $this->kirki_config && isset( Kirki::$config[ $this->kirki_config ] ) ) {
$config_defaults = Kirki::$config[ $this->kirki_config ];
}
$config_defaults = ( is_array( $config_defaults ) ) ? $config_defaults : array();
foreach ( $config_defaults as $key => $value ) {
if ( isset( $defaults[ $key ] ) && ! empty( $value ) && $value !== $defaults[ $key ] ) {
$defaults[ $key ] = $value;
}
}
// Merge our args with the defaults.
$args = wp_parse_args( $args, $defaults );
// Set the class properties using the parsed args.
foreach ( $args as $key => $value ) {
$this->$key = $value;
}
$this->args = $args;
$this->set_field();
}
/**
* Processes the field arguments
*
* @access protected
*/
protected function set_field() {
$properties = get_class_vars( __CLASS__ );
// Some things must run before the others.
$this->set_option_type();
$this->set_settings();
// Sanitize the properties, skipping the ones that have already run above.
foreach ( $properties as $property => $value ) {
if ( in_array( $property, array( 'option_name', 'option_type', 'settings' ), true ) ) {
continue;
}
if ( method_exists( $this, 'set_' . $property ) ) {
$method_name = 'set_' . $property;
$this->$method_name();
}
}
// Get all arguments with their values.
$args = get_object_vars( $this );
foreach ( array_keys( $args ) as $key ) {
$args[ $key ] = $this->$key;
}
// Add the field to the static $fields variable properly indexed.
Kirki::$fields[ $this->settings ] = $args;
}
/**
* Escape the $section.
*
* @access protected
*/
protected function set_input_attrs() {
$this->input_attrs = (array) $this->input_attrs;
}
/**
* Make sure we're using the correct option_type
*
* @access protected
*/
protected function set_option_type() {
// Take care of common typos.
if ( 'options' === $this->option_type ) {
$this->option_type = 'option';
}
// Take care of common typos.
if ( 'theme_mods' === $this->option_type ) {
/* translators: %1$s represents the field ID where the error occurs. */
_doing_it_wrong( __METHOD__, sprintf( esc_html( 'Typo found in field %s - "theme_mods" vs "theme_mod"', 'kirki' ), esc_html( $this->settings ) ), '3.0.10' );
$this->option_type = 'theme_mod';
}
}
/**
* Modifications for partial refreshes.
*
* @access protected
*/
protected function set_partial_refresh() {
if ( ! is_array( $this->partial_refresh ) ) {
$this->partial_refresh = array();
}
foreach ( $this->partial_refresh as $id => $args ) {
if ( ! is_array( $args ) || ! isset( $args['selector'] ) || ! isset( $args['render_callback'] ) || ! is_callable( $args['render_callback'] ) ) {
/* translators: %1$s represents the field ID where the error occurs. */
_doing_it_wrong( __METHOD__, sprintf( esc_html__( '"partial_refresh" invalid entry in field %s', 'kirki' ), esc_html( $this->settings ) ), '3.0.10' );
unset( $this->partial_refresh[ $id ] );
continue;
}
}
if ( ! empty( $this->partial_refresh ) ) {
$this->transport = 'postMessage';
}
}
/**
* Sets the settings.
* If we're using serialized options it makes sure that settings are properly formatted.
* We'll also be escaping all setting names here for consistency.
*
* @access protected
*/
protected function set_settings() {
// If settings is not an array, temporarily convert it to an array.
// This is just to allow us to process everything the same way and avoid code duplication.
// if settings is not an array then it will not be set as an array in the end.
if ( ! is_array( $this->settings ) ) {
$this->settings = array(
'kirki_placeholder_setting' => $this->settings,
);
}
$settings = array();
foreach ( $this->settings as $setting_key => $setting_value ) {
$settings[ $setting_key ] = $setting_value;
// If we're using serialized options then we need to spice this up.
if ( 'option' === $this->option_type && '' !== $this->option_name && ( false === strpos( $setting_key, '[' ) ) ) {
$settings[ $setting_key ] = "{$this->option_name}[{$setting_value}]";
}
}
$this->settings = $settings;
if ( isset( $this->settings['kirki_placeholder_setting'] ) ) {
$this->settings = $this->settings['kirki_placeholder_setting'];
}
}
/**
* Sets the active_callback
* If we're using the $required argument,
* Then this is where the switch is made to our evaluation method.
*
* @access protected
*/
protected function set_active_callback() {
if ( is_array( $this->active_callback ) ) {
if ( ! is_callable( $this->active_callback ) ) {
// Bugfix for https://github.com/aristath/kirki/issues/1961.
foreach ( $this->active_callback as $key => $val ) {
if ( is_callable( $val ) ) {
unset( $this->active_callback[ $key ] );
}
}
if ( isset( $this->active_callback[0] ) ) {
$this->required = $this->active_callback;
}
}
}
if ( ! empty( $this->required ) ) {
$this->active_callback = '__return_true';
return;
}
// No need to proceed any further if we're using the default value.
if ( '__return_true' === $this->active_callback ) {
return;
}
// Make sure the function is callable, otherwise fallback to __return_true.
if ( ! is_callable( $this->active_callback ) ) {
$this->active_callback = '__return_true';
}
}
/**
* Sets the $id.
* Setting the ID should happen after the 'settings' sanitization.
* This way we can also properly handle cases where the option_type is set to 'option'
* and we're using an array instead of individual options.
*
* @access protected
*/
protected function set_id() {
$this->id = sanitize_key( str_replace( '[', '-', str_replace( ']', '', $this->settings ) ) );
}
/**
* Sets the $choices.
*
* @access protected
*/
protected function set_choices() {
if ( ! is_array( $this->choices ) ) {
$this->choices = array();
}
}
/**
* Escapes the $disable_output.
*
* @access protected
*/
protected function set_disable_output() {
$this->disable_output = (bool) $this->disable_output;
}
/**
* Sets the $sanitize_callback
*
* @access protected
*/
protected function set_output() {
if ( empty( $this->output ) ) {
return;
}
if ( ! is_array( $this->output ) ) {
/* translators: The field ID where the error occurs. */
_doing_it_wrong( __METHOD__, sprintf( esc_html__( '"output" invalid format in field %s. The "output" argument should be defined as an array of arrays.', 'kirki' ), esc_html( $this->settings ) ), '3.0.10' );
$this->output = array(
array(
'element' => $this->output,
),
);
}
// Convert to array of arrays if needed.
if ( isset( $this->output['element'] ) ) {
/* translators: The field ID where the error occurs. */
_doing_it_wrong( __METHOD__, sprintf( esc_html__( '"output" invalid format in field %s. The "output" argument should be defined as an array of arrays.', 'kirki' ), esc_html( $this->settings ) ), '3.0.10' );
$this->output = array( $this->output );
}
foreach ( $this->output as $key => $output ) {
if ( empty( $output ) || ! isset( $output['element'] ) ) {
unset( $this->output[ $key ] );
continue;
}
if ( ! isset( $output['sanitize_callback'] ) && isset( $output['callback'] ) ) {
$this->output[ $key ]['sanitize_callback'] = $output['callback'];
}
// Convert element arrays to strings.
if ( isset( $output['element'] ) && is_array( $output['element'] ) ) {
$this->output[ $key ]['element'] = array_unique( $this->output[ $key ]['element'] );
sort( $this->output[ $key ]['element'] );
// Trim each element in the array.
foreach ( $this->output[ $key ]['element'] as $index => $element ) {
$this->output[ $key ]['element'][ $index ] = trim( $element );
}
$this->output[ $key ]['element'] = implode( ',', $this->output[ $key ]['element'] );
}
// Fix for https://github.com/aristath/kirki/issues/1659#issuecomment-346229751.
$this->output[ $key ]['element'] = str_replace( array( "\t", "\n", "\r", "\0", "\x0B" ), ' ', $this->output[ $key ]['element'] );
$this->output[ $key ]['element'] = trim( preg_replace( '/\s+/', ' ', $this->output[ $key ]['element'] ) );
}
}
/**
* Sets the $js_vars
*
* @access protected
*/
protected function set_js_vars() {
if ( ! is_array( $this->js_vars ) ) {
$this->js_vars = array();
}
// Check if transport is set to auto.
// If not, then skip the auto-calculations and exit early.
if ( 'auto' !== $this->transport ) {
return;
}
// Set transport to refresh initially.
// Serves as a fallback in case we failt to auto-calculate js_vars.
$this->transport = 'refresh';
$js_vars = array();
// Try to auto-generate js_vars.
// First we need to check if js_vars are empty, and that output is not empty.
if ( empty( $this->js_vars ) && ! empty( $this->output ) ) {
// Start going through each item in the $output array.
foreach ( $this->output as $output ) {
$output['function'] = ( isset( $output['function'] ) ) ? $output['function'] : 'style';
// If 'element' or 'property' are not defined, skip this.
if ( ! isset( $output['element'] ) || ! isset( $output['property'] ) ) {
continue;
}
if ( is_array( $output['element'] ) ) {
$output['element'] = implode( ',', $output['element'] );
}
// If there's a sanitize_callback defined skip this, unless we also have a js_callback defined.
if ( isset( $output['sanitize_callback'] ) && ! empty( $output['sanitize_callback'] ) && ! isset( $output['js_callback'] ) ) {
continue;
}
// If we got this far, it's safe to add this.
$js_vars[] = $output;
}
// Did we manage to get all the items from 'output'?
// If not, then we're missing something so don't add this.
if ( count( $js_vars ) !== count( $this->output ) ) {
return;
}
$this->js_vars = $js_vars;
$this->transport = 'postMessage';
}
}
/**
* Sets the $variables
*
* @access protected
*/
protected function set_variables() {
if ( ! is_array( $this->variables ) ) {
$variable = ( is_string( $this->variables ) && ! empty( $this->variables ) ) ? $this->variables : false;
$this->variables = array();
if ( $variable && empty( $this->variables ) ) {
$this->variables[0]['name'] = $variable;
}
}
}
/**
* Sets the $transport
*
* @access protected
*/
protected function set_transport() {
if ( 'postmessage' === trim( strtolower( $this->transport ) ) ) {
$this->transport = 'postMessage';
}
}
/**
* Sets the $required
*
* @access protected
*/
protected function set_required() {
if ( ! is_array( $this->required ) ) {
$this->required = array();
}
}
/**
* Sets the $priority
*
* @access protected
*/
protected function set_priority() {
$this->priority = absint( $this->priority );
}
/**
* Sets the $css_vars
*
* @access protected
*/
protected function set_css_vars() {
if ( is_string( $this->css_vars ) ) {
$this->css_vars = array( $this->css_vars );
}
if ( isset( $this->css_vars[0] ) && is_string( $this->css_vars[0] ) ) {
$this->css_vars = array( $this->css_vars );
}
foreach ( $this->css_vars as $key => $val ) {
if ( ! isset( $val[1] ) ) {
$this->css_vars[ $key ][1] = '$';
}
}
}
}

View file

@ -0,0 +1,456 @@
<?php
/**
* Helper methods
*
* @package Kirki
* @category Core
* @author Ari Stathopoulos (@aristath)
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 1.0
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* A simple object containing static methods.
*/
class Kirki_Helper {
/**
* Recursive replace in arrays.
*
* @static
* @access public
* @param array $array The first array.
* @param array $array1 The second array.
* @return mixed
*/
public static function array_replace_recursive( $array, $array1 ) {
if ( function_exists( 'array_replace_recursive' ) ) {
return array_replace_recursive( $array, $array1 );
}
/**
* Handle the arguments, merge one by one.
*
* In PHP 7 func_get_args() changed the way it behaves but this doesn't mean anything in this case
* sinc ethis method is only used when the array_replace_recursive() function doesn't exist
* and that was introduced in PHP v5.3.
*
* Once WordPress-Core raises its minimum requirements we''' be able to remove this fallback completely.
*/
$args = func_get_args(); // phpcs:ignore PHPCompatibility.FunctionUse.ArgumentFunctionsReportCurrentValue
$array = $args[0];
if ( ! is_array( $array ) ) {
return $array;
}
$count = count( $args );
for ( $i = 1; $i < $count; $i++ ) {
if ( is_array( $args[ $i ] ) ) {
$array = self::recurse( $array, $args[ $i ] );
}
}
return $array;
}
/**
* Helper method to be used from the array_replace_recursive method.
*
* @static
* @access public
* @param array $array The first array.
* @param array $array1 The second array.
* @return array
*/
public static function recurse( $array, $array1 ) {
foreach ( $array1 as $key => $value ) {
// Create new key in $array, if it is empty or not an array.
if ( ! isset( $array[ $key ] ) || ( isset( $array[ $key ] ) && ! is_array( $array[ $key ] ) ) ) {
$array[ $key ] = array();
}
// Overwrite the value in the base array.
if ( is_array( $value ) ) {
$value = self::recurse( $array[ $key ], $value );
}
$array[ $key ] = $value;
}
return $array;
}
/**
* Initialize the WP_Filesystem
*
* @static
* @access public
* @return object WP_Filesystem
*/
public static function init_filesystem() {
$credentials = array();
if ( ! defined( 'FS_METHOD' ) ) {
define( 'FS_METHOD', 'direct' );
}
$method = defined( 'FS_METHOD' ) ? FS_METHOD : false;
if ( 'ftpext' === $method ) {
// If defined, set it to that, Else, set to NULL.
$credentials['hostname'] = defined( 'FTP_HOST' ) ? preg_replace( '|\w+://|', '', FTP_HOST ) : null;
$credentials['username'] = defined( 'FTP_USER' ) ? FTP_USER : null;
$credentials['password'] = defined( 'FTP_PASS' ) ? FTP_PASS : null;
// Set FTP port.
if ( strpos( $credentials['hostname'], ':' ) && null !== $credentials['hostname'] ) {
list( $credentials['hostname'], $credentials['port'] ) = explode( ':', $credentials['hostname'], 2 );
if ( ! is_numeric( $credentials['port'] ) ) {
unset( $credentials['port'] );
}
} else {
unset( $credentials['port'] );
}
// Set connection type.
if ( ( defined( 'FTP_SSL' ) && FTP_SSL ) && 'ftpext' === $method ) {
$credentials['connection_type'] = 'ftps';
} elseif ( ! array_filter( $credentials ) ) {
$credentials['connection_type'] = null;
} else {
$credentials['connection_type'] = 'ftp';
}
}
// The WordPress filesystem.
global $wp_filesystem;
if ( empty( $wp_filesystem ) ) {
require_once wp_normalize_path( ABSPATH . '/wp-admin/includes/file.php' ); // phpcs:ignore WPThemeReview.CoreFunctionality.FileInclude
WP_Filesystem( $credentials );
}
return $wp_filesystem;
}
/**
* Returns the attachment object
*
* @static
* @access public
* @see https://pippinsplugins.com/retrieve-attachment-id-from-image-url/
* @param string $url URL to the image.
* @return int|string Numeric ID of the attachement.
*/
public static function get_image_id( $url ) {
global $wpdb;
if ( empty( $url ) ) {
return 0;
}
$attachment = wp_cache_get( 'kirki_image_id_' . md5( $url ), null );
if ( false === $attachment ) {
$attachment = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE guid = %s;", $url ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
wp_cache_add( 'kirki_image_id_' . md5( $url ), $attachment, null );
}
if ( ! empty( $attachment ) ) {
return $attachment[0];
}
return 0;
}
/**
* Returns an array of the attachment's properties.
*
* @param string $url URL to the image.
* @return array
*/
public static function get_image_from_url( $url ) {
$image_id = self::get_image_id( $url );
$image = wp_get_attachment_image_src( $image_id, 'full' );
return array(
'url' => $image[0],
'width' => $image[1],
'height' => $image[2],
'thumbnail' => $image[3],
);
}
/**
* Get an array of posts.
*
* @static
* @access public
* @param array $args Define arguments for the get_posts function.
* @return array
*/
public static function get_posts( $args ) {
if ( is_string( $args ) ) {
$args = add_query_arg(
array(
'suppress_filters' => false,
)
);
} elseif ( is_array( $args ) && ! isset( $args['suppress_filters'] ) ) {
$args['suppress_filters'] = false;
}
// Get the posts.
// TODO: WordPress.VIP.RestrictedFunctions.get_posts_get_posts.
$posts = get_posts( $args );
// Properly format the array.
$items = array();
foreach ( $posts as $post ) {
$items[ $post->ID ] = $post->post_title;
}
wp_reset_postdata();
return $items;
}
/**
* Get an array of publicly-querable taxonomies.
*
* @static
* @access public
* @return array
*/
public static function get_taxonomies() {
$items = array();
// Get the taxonomies.
$taxonomies = get_taxonomies(
array(
'public' => true,
)
);
// Build the array.
foreach ( $taxonomies as $taxonomy ) {
$id = $taxonomy;
$taxonomy = get_taxonomy( $taxonomy );
$items[ $id ] = $taxonomy->labels->name;
}
return $items;
}
/**
* Get an array of publicly-querable post-types.
*
* @static
* @access public
* @return array
*/
public static function get_post_types() {
$items = array();
// Get the post types.
$post_types = get_post_types(
array(
'public' => true,
),
'objects'
);
// Build the array.
foreach ( $post_types as $post_type ) {
$items[ $post_type->name ] = $post_type->labels->name;
}
return $items;
}
/**
* Get an array of terms from a taxonomy
*
* @static
* @access public
* @param string|array $taxonomies See https://developer.wordpress.org/reference/functions/get_terms/ for details.
* @return array
*/
public static function get_terms( $taxonomies ) {
$items = array();
// Get the post types.
$terms = get_terms( $taxonomies );
// Build the array.
foreach ( $terms as $term ) {
$items[ $term->term_id ] = $term->name;
}
return $items;
}
/**
* Gets an array of material-design colors.
*
* @static
* @access public
* @param string $context Allows us to get subsets of the palette.
* @return array
*/
public static function get_material_design_colors( $context = 'primary' ) {
$colors = array(
'primary' => array( '#FFFFFF', '#000000', '#F44336', '#E91E63', '#9C27B0', '#673AB7', '#3F51B5', '#2196F3', '#03A9F4', '#00BCD4', '#009688', '#4CAF50', '#8BC34A', '#CDDC39', '#FFEB3B', '#FFC107', '#FF9800', '#FF5722', '#795548', '#9E9E9E', '#607D8B' ),
'red' => array( '#FFEBEE', '#FFCDD2', '#EF9A9A', '#E57373', '#EF5350', '#F44336', '#E53935', '#D32F2F', '#C62828', '#B71C1C', '#FF8A80', '#FF5252', '#FF1744', '#D50000' ),
'pink' => array( '#FCE4EC', '#F8BBD0', '#F48FB1', '#F06292', '#EC407A', '#E91E63', '#D81B60', '#C2185B', '#AD1457', '#880E4F', '#FF80AB', '#FF4081', '#F50057', '#C51162' ),
'purple' => array( '#F3E5F5', '#E1BEE7', '#CE93D8', '#BA68C8', '#AB47BC', '#9C27B0', '#8E24AA', '#7B1FA2', '#6A1B9A', '#4A148C', '#EA80FC', '#E040FB', '#D500F9', '#AA00FF' ),
'deep-purple' => array( '#EDE7F6', '#D1C4E9', '#B39DDB', '#9575CD', '#7E57C2', '#673AB7', '#5E35B1', '#512DA8', '#4527A0', '#311B92', '#B388FF', '#7C4DFF', '#651FFF', '#6200EA' ),
'indigo' => array( '#E8EAF6', '#C5CAE9', '#9FA8DA', '#7986CB', '#5C6BC0', '#3F51B5', '#3949AB', '#303F9F', '#283593', '#1A237E', '#8C9EFF', '#536DFE', '#3D5AFE', '#304FFE' ),
'blue' => array( '#E3F2FD', '#BBDEFB', '#90CAF9', '#64B5F6', '#42A5F5', '#2196F3', '#1E88E5', '#1976D2', '#1565C0', '#0D47A1', '#82B1FF', '#448AFF', '#2979FF', '#2962FF' ),
'light-blue' => array( '#E1F5FE', '#B3E5FC', '#81D4fA', '#4fC3F7', '#29B6FC', '#03A9F4', '#039BE5', '#0288D1', '#0277BD', '#01579B', '#80D8FF', '#40C4FF', '#00B0FF', '#0091EA' ),
'cyan' => array( '#E0F7FA', '#B2EBF2', '#80DEEA', '#4DD0E1', '#26C6DA', '#00BCD4', '#00ACC1', '#0097A7', '#00838F', '#006064', '#84FFFF', '#18FFFF', '#00E5FF', '#00B8D4' ),
'teal' => array( '#E0F2F1', '#B2DFDB', '#80CBC4', '#4DB6AC', '#26A69A', '#009688', '#00897B', '#00796B', '#00695C', '#004D40', '#A7FFEB', '#64FFDA', '#1DE9B6', '#00BFA5' ),
'green' => array( '#E8F5E9', '#C8E6C9', '#A5D6A7', '#81C784', '#66BB6A', '#4CAF50', '#43A047', '#388E3C', '#2E7D32', '#1B5E20', '#B9F6CA', '#69F0AE', '#00E676', '#00C853' ),
'light-green' => array( '#F1F8E9', '#DCEDC8', '#C5E1A5', '#AED581', '#9CCC65', '#8BC34A', '#7CB342', '#689F38', '#558B2F', '#33691E', '#CCFF90', '#B2FF59', '#76FF03', '#64DD17' ),
'lime' => array( '#F9FBE7', '#F0F4C3', '#E6EE9C', '#DCE775', '#D4E157', '#CDDC39', '#C0CA33', '#A4B42B', '#9E9D24', '#827717', '#F4FF81', '#EEFF41', '#C6FF00', '#AEEA00' ),
'yellow' => array( '#FFFDE7', '#FFF9C4', '#FFF590', '#FFF176', '#FFEE58', '#FFEB3B', '#FDD835', '#FBC02D', '#F9A825', '#F57F17', '#FFFF82', '#FFFF00', '#FFEA00', '#FFD600' ),
'amber' => array( '#FFF8E1', '#FFECB3', '#FFE082', '#FFD54F', '#FFCA28', '#FFC107', '#FFB300', '#FFA000', '#FF8F00', '#FF6F00', '#FFE57F', '#FFD740', '#FFC400', '#FFAB00' ),
'orange' => array( '#FFF3E0', '#FFE0B2', '#FFCC80', '#FFB74D', '#FFA726', '#FF9800', '#FB8C00', '#F57C00', '#EF6C00', '#E65100', '#FFD180', '#FFAB40', '#FF9100', '#FF6D00' ),
'deep-orange' => array( '#FBE9A7', '#FFCCBC', '#FFAB91', '#FF8A65', '#FF7043', '#FF5722', '#F4511E', '#E64A19', '#D84315', '#BF360C', '#FF9E80', '#FF6E40', '#FF3D00', '#DD2600' ),
'brown' => array( '#EFEBE9', '#D7CCC8', '#BCAAA4', '#A1887F', '#8D6E63', '#795548', '#6D4C41', '#5D4037', '#4E342E', '#3E2723' ),
'grey' => array( '#FAFAFA', '#F5F5F5', '#EEEEEE', '#E0E0E0', '#BDBDBD', '#9E9E9E', '#757575', '#616161', '#424242', '#212121', '#000000', '#ffffff' ),
'blue-grey' => array( '#ECEFF1', '#CFD8DC', '#B0BBC5', '#90A4AE', '#78909C', '#607D8B', '#546E7A', '#455A64', '#37474F', '#263238' ),
);
switch ( $context ) {
case '50':
case '100':
case '200':
case '300':
case '400':
case '500':
case '600':
case '700':
case '800':
case '900':
case 'A100':
case 'A200':
case 'A400':
case 'A700':
$key = absint( $context ) / 100;
if ( 'A100' === $context ) {
$key = 10;
unset( $colors['grey'] );
} elseif ( 'A200' === $context ) {
$key = 11;
unset( $colors['grey'] );
} elseif ( 'A400' === $context ) {
$key = 12;
unset( $colors['grey'] );
} elseif ( 'A700' === $context ) {
$key = 13;
unset( $colors['grey'] );
}
unset( $colors['primary'] );
$position_colors = array();
foreach ( $colors as $color_family ) {
if ( isset( $color_family[ $key ] ) ) {
$position_colors[] = $color_family[ $key ];
}
}
return $position_colors;
case 'all':
unset( $colors['primary'] );
$all_colors = array();
foreach ( $colors as $color_family ) {
foreach ( $color_family as $color ) {
$all_colors[] = $color;
}
}
return $all_colors;
case 'primary':
return $colors['primary'];
default:
if ( isset( $colors[ $context ] ) ) {
return $colors[ $context ];
}
return $colors['primary'];
}
}
/**
* Get an array of all available dashicons.
*
* @static
* @access public
* @return array
*/
public static function get_dashicons() {
return array(
'admin-menu' => array( 'menu', 'admin-site', 'dashboard', 'admin-post', 'admin-media', 'admin-links', 'admin-page', 'admin-comments', 'admin-appearance', 'admin-plugins', 'admin-users', 'admin-tools', 'admin-settings', 'admin-network', 'admin-home', 'admin-generic', 'admin-collapse', 'filter', 'admin-customizer', 'admin-multisite' ),
'welcome-screen' => array( 'welcome-write-blog', 'welcome-add-page', 'welcome-view-site', 'welcome-widgets-menus', 'welcome-comments', 'welcome-learn-more' ),
'post-formats' => array( 'format-aside', 'format-image', 'format-gallery', 'format-video', 'format-status', 'format-quote', 'format-chat', 'format-audio', 'camera', 'images-alt', 'images-alt2', 'video-alt', 'video-alt2', 'video-alt3' ),
'media' => array( 'media-archive', 'media-audio', 'media-code', 'media-default', 'media-document', 'media-interactive', 'media-spreadsheet', 'media-text', 'media-video', 'playlist-audio', 'playlist-video', 'controls-play', 'controls-pause', 'controls-forward', 'controls-skipforward', 'controls-back', 'controls-skipback', 'controls-repeat', 'controls-volumeon', 'controls-volumeoff' ),
'image-editing' => array( 'image-crop', 'image-rotate', 'image-rotate-left', 'image-rotate-right', 'image-flip-vertical', 'image-flip-horizontal', 'image-filter', 'undo', 'redo' ),
'tinymce' => array( 'editor-bold', 'editor-italic', 'editor-ul', 'editor-ol', 'editor-quote', 'editor-alignleft', 'editor-aligncenter', 'editor-alignright', 'editor-insertmore', 'editor-spellcheck', 'editor-expand', 'editor-contract', 'editor-kitchensink', 'editor-underline', 'editor-justify', 'editor-textcolor', 'editor-paste-word', 'editor-paste-text', 'editor-removeformatting', 'editor-video', 'editor-customchar', 'editor-outdent', 'editor-indent', 'editor-help', 'editor-strikethrough', 'editor-unlink', 'editor-rtl', 'editor-break', 'editor-code', 'editor-paragraph', 'editor-table' ),
'posts' => array( 'align-left', 'align-right', 'align-center', 'align-none', 'lock', 'unlock', 'calendar', 'calendar-alt', 'visibility', 'hidden', 'post-status', 'edit', 'trash', 'sticky' ),
'sorting' => array( 'external', 'arrow-up', 'arrow-down', 'arrow-right', 'arrow-left', 'arrow-up-alt', 'arrow-down-alt', 'arrow-right-alt', 'arrow-left-alt', 'arrow-up-alt2', 'arrow-down-alt2', 'arrow-right-alt2', 'arrow-left-alt2', 'sort', 'leftright', 'randomize', 'list-view', 'exerpt-view', 'grid-view' ),
'social' => array( 'share', 'share-alt', 'share-alt2', 'twitter', 'rss', 'email', 'email-alt', 'facebook', 'facebook-alt', 'googleplus', 'networking' ),
'wordpress_org' => array( 'hammer', 'art', 'migrate', 'performance', 'universal-access', 'universal-access-alt', 'tickets', 'nametag', 'clipboard', 'heart', 'megaphone', 'schedule' ),
'products' => array( 'wordpress', 'wordpress-alt', 'pressthis', 'update', 'screenoptions', 'info', 'cart', 'feedback', 'cloud', 'translation' ),
'taxonomies' => array( 'tag', 'category' ),
'widgets' => array( 'archive', 'tagcloud', 'text' ),
'notifications' => array( 'yes', 'no', 'no-alt', 'plus', 'plus-alt', 'minus', 'dismiss', 'marker', 'star-filled', 'star-half', 'star-empty', 'flag', 'warning' ),
'misc' => array( 'location', 'location-alt', 'vault', 'shield', 'shield-alt', 'sos', 'search', 'slides', 'analytics', 'chart-pie', 'chart-bar', 'chart-line', 'chart-area', 'groups', 'businessman', 'id', 'id-alt', 'products', 'awards', 'forms', 'testimonial', 'portfolio', 'book', 'book-alt', 'download', 'upload', 'backup', 'clock', 'lightbulb', 'microphone', 'desktop', 'tablet', 'smartphone', 'phone', 'index-card', 'carrot', 'building', 'store', 'album', 'palmtree', 'tickets-alt', 'money', 'smiley', 'thumbs-up', 'thumbs-down', 'layout' ),
);
}
/**
* Compares the 2 values given the condition
*
* @param mixed $value1 The 1st value in the comparison.
* @param mixed $value2 The 2nd value in the comparison.
* @param string $operator The operator we'll use for the comparison.
* @return boolean whether The comparison has succeded (true) or failed (false).
*/
public static function compare_values( $value1, $value2, $operator ) {
if ( '===' === $operator ) {
return $value1 === $value2;
}
if ( '!==' === $operator ) {
return $value1 !== $value2;
}
if ( ( '!=' === $operator || 'not equal' === $operator ) ) {
return $value1 != $value2; // phpcs:ignore WordPress.PHP.StrictComparisons
}
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 ( is_array( $value1 ) && is_array( $value2 ) ) {
foreach ( $value2 as $val ) {
if ( in_array( $val, $value1 ) ) { // phpcs:ignore WordPress.PHP.StrictInArray
return true;
}
}
return false;
}
if ( is_array( $value1 ) && ! is_array( $value2 ) ) {
return in_array( $value2, $value1 ); // phpcs:ignore WordPress.PHP.StrictInArray
}
if ( is_array( $value2 ) && ! is_array( $value1 ) ) {
return in_array( $value1, $value2 ); // phpcs:ignore WordPress.PHP.StrictInArray
}
return ( false !== strrpos( $value1, $value2 ) || false !== strpos( $value2, $value1 ) );
}
return $value1 == $value2; // phpcs:ignore WordPress.PHP.StrictComparisons
}
}

View file

@ -0,0 +1,418 @@
<?php
/**
* Initializes Kirki
*
* @package Kirki
* @category Core
* @author Ari Stathopoulos (@aristath)
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 1.0
*/
/**
* Initialize Kirki
*/
class Kirki_Init {
/**
* Control types.
*
* @access private
* @since 3.0.0
* @var array
*/
private $control_types = array();
/**
* Should we show a nag for the deprecated fontawesome field?
*
* @static
* @access private
* @since 3.0.42
* @var bool
*/
private static $show_fa_nag = false;
/**
* The class constructor.
*/
public function __construct() {
self::set_url();
add_action( 'after_setup_theme', array( $this, 'set_url' ) );
add_action( 'wp_loaded', array( $this, 'add_to_customizer' ), 1 );
add_filter( 'kirki_control_types', array( $this, 'default_control_types' ) );
add_action( 'customize_register', array( $this, 'remove_panels' ), 99999 );
add_action( 'customize_register', array( $this, 'remove_sections' ), 99999 );
add_action( 'customize_register', array( $this, 'remove_controls' ), 99999 );
add_action( 'admin_notices', array( $this, 'admin_notices' ) );
add_action( 'admin_init', array( $this, 'dismiss_nag' ) );
new Kirki_Values();
new Kirki_Sections();
}
/**
* Properly set the Kirki URL for assets.
*
* @static
* @access public
*/
public static function set_url() {
if ( Kirki_Util::is_plugin() ) {
return;
}
// Get correct URL and path to wp-content.
$content_url = untrailingslashit( dirname( dirname( get_stylesheet_directory_uri() ) ) );
$content_dir = wp_normalize_path( untrailingslashit( WP_CONTENT_DIR ) );
Kirki::$url = str_replace( $content_dir, $content_url, wp_normalize_path( Kirki::$path ) );
// Apply the kirki_config filter.
$config = apply_filters( 'kirki_config', array() );
if ( isset( $config['url_path'] ) ) {
Kirki::$url = $config['url_path'];
}
// Make sure the right protocol is used.
Kirki::$url = set_url_scheme( Kirki::$url );
}
/**
* Add the default Kirki control types.
*
* @access public
* @since 3.0.0
* @param array $control_types The control types array.
* @return array
*/
public function default_control_types( $control_types = array() ) {
$this->control_types = array(
'checkbox' => 'Kirki_Control_Checkbox',
'kirki-background' => 'Kirki_Control_Background',
'code_editor' => 'Kirki_Control_Code',
'kirki-color' => 'Kirki_Control_Color',
'kirki-color-palette' => 'Kirki_Control_Color_Palette',
'kirki-custom' => 'Kirki_Control_Custom',
'kirki-date' => 'Kirki_Control_Date',
'kirki-dashicons' => 'Kirki_Control_Dashicons',
'kirki-dimension' => 'Kirki_Control_Dimension',
'kirki-dimensions' => 'Kirki_Control_Dimensions',
'kirki-editor' => 'Kirki_Control_Editor',
'kirki-image' => 'Kirki_Control_Image',
'kirki-multicolor' => 'Kirki_Control_Multicolor',
'kirki-multicheck' => 'Kirki_Control_MultiCheck',
'kirki-number' => 'Kirki_Control_Number',
'kirki-palette' => 'Kirki_Control_Palette',
'kirki-radio' => 'Kirki_Control_Radio',
'kirki-radio-buttonset' => 'Kirki_Control_Radio_ButtonSet',
'kirki-radio-image' => 'Kirki_Control_Radio_Image',
'repeater' => 'Kirki_Control_Repeater',
'kirki-select' => 'Kirki_Control_Select',
'kirki-slider' => 'Kirki_Control_Slider',
'kirki-sortable' => 'Kirki_Control_Sortable',
'kirki-spacing' => 'Kirki_Control_Dimensions',
'kirki-switch' => 'Kirki_Control_Switch',
'kirki-generic' => 'Kirki_Control_Generic',
'kirki-toggle' => 'Kirki_Control_Toggle',
'kirki-typography' => 'Kirki_Control_Typography',
'image' => 'Kirki_Control_Image',
'cropped_image' => 'Kirki_Control_Cropped_Image',
'upload' => 'Kirki_Control_Upload',
);
return array_merge( $this->control_types, $control_types );
}
/**
* Helper function that adds the fields, sections and panels to the customizer.
*/
public function add_to_customizer() {
$this->fields_from_filters();
add_action( 'customize_register', array( $this, 'register_control_types' ) );
add_action( 'customize_register', array( $this, 'add_panels' ), 97 );
add_action( 'customize_register', array( $this, 'add_sections' ), 98 );
add_action( 'customize_register', array( $this, 'add_fields' ), 99 );
}
/**
* Register control types
*/
public function register_control_types() {
global $wp_customize;
$section_types = apply_filters( 'kirki_section_types', array() );
foreach ( $section_types as $section_type ) {
$wp_customize->register_section_type( $section_type );
}
$this->control_types = $this->default_control_types();
if ( ! class_exists( 'WP_Customize_Code_Editor_Control' ) ) {
unset( $this->control_types['code_editor'] );
}
foreach ( $this->control_types as $key => $classname ) {
if ( ! class_exists( $classname ) ) {
unset( $this->control_types[ $key ] );
}
}
$skip_control_types = apply_filters(
'kirki_control_types_exclude',
array(
'Kirki_Control_Repeater',
'WP_Customize_Control',
)
);
foreach ( $this->control_types as $control_type ) {
if ( ! in_array( $control_type, $skip_control_types, true ) && class_exists( $control_type ) ) {
$wp_customize->register_control_type( $control_type );
}
}
}
/**
* Register our panels to the WordPress Customizer.
*
* @access public
*/
public function add_panels() {
if ( ! empty( Kirki::$panels ) ) {
foreach ( Kirki::$panels as $panel_args ) {
// Extra checks for nested panels.
if ( isset( $panel_args['panel'] ) ) {
if ( isset( Kirki::$panels[ $panel_args['panel'] ] ) ) {
// Set the type to nested.
$panel_args['type'] = 'kirki-nested';
}
}
new Kirki_Panel( $panel_args );
}
}
}
/**
* Register our sections to the WordPress Customizer.
*
* @var object The WordPress Customizer object
*/
public function add_sections() {
if ( ! empty( Kirki::$sections ) ) {
foreach ( Kirki::$sections as $section_args ) {
// Extra checks for nested sections.
if ( isset( $section_args['section'] ) ) {
if ( isset( Kirki::$sections[ $section_args['section'] ] ) ) {
// Set the type to nested.
$section_args['type'] = 'kirki-nested';
// We need to check if the parent section is nested inside a panel.
$parent_section = Kirki::$sections[ $section_args['section'] ];
if ( isset( $parent_section['panel'] ) ) {
$section_args['panel'] = $parent_section['panel'];
}
}
}
new Kirki_Section( $section_args );
}
}
}
/**
* Create the settings and controls from the $fields array and register them.
*
* @var object The WordPress Customizer object.
*/
public function add_fields() {
global $wp_customize;
foreach ( Kirki::$fields as $args ) {
// Create the settings.
new Kirki_Settings( $args );
// Check if we're on the customizer.
// If we are, then we will create the controls, add the scripts needed for the customizer
// and any other tweaks that this field may require.
if ( $wp_customize ) {
// Create the control.
new Kirki_Control( $args );
}
}
}
/**
* Process fields added using the 'kirki_fields' and 'kirki_controls' filter.
* These filters are no longer used, this is simply for backwards-compatibility.
*
* @access private
* @since 2.0.0
*/
private function fields_from_filters() {
$fields = apply_filters( 'kirki_controls', array() );
$fields = apply_filters( 'kirki_fields', $fields );
if ( ! empty( $fields ) ) {
foreach ( $fields as $field ) {
Kirki::add_field( 'global', $field );
}
}
}
/**
* Alias for the is_plugin static method in the Kirki_Util class.
* This is here for backwards-compatibility purposes.
*
* @static
* @access public
* @since 3.0.0
* @return bool
*/
public static function is_plugin() {
return Kirki_Util::is_plugin();
}
/**
* Alias for the get_variables static method in the Kirki_Util class.
* This is here for backwards-compatibility purposes.
*
* @static
* @access public
* @since 2.0.0
* @return array Formatted as array( 'variable-name' => value ).
*/
public static function get_variables() {
// Log error for developers.
_doing_it_wrong( __METHOD__, esc_html__( 'We detected you\'re using Kirki_Init::get_variables(). Please use Kirki_Util::get_variables() instead.', 'kirki' ), '3.0.10' );
return Kirki_Util::get_variables();
}
/**
* Remove panels.
*
* @since 3.0.17
* @param object $wp_customize The customizer object.
* @return void
*/
public function remove_panels( $wp_customize ) {
foreach ( Kirki::$panels_to_remove as $panel ) {
$wp_customize->remove_panel( $panel );
}
}
/**
* Remove sections.
*
* @since 3.0.17
* @param object $wp_customize The customizer object.
* @return void
*/
public function remove_sections( $wp_customize ) {
foreach ( Kirki::$sections_to_remove as $section ) {
$wp_customize->remove_section( $section );
}
}
/**
* Remove controls.
*
* @since 3.0.17
* @param object $wp_customize The customizer object.
* @return void
*/
public function remove_controls( $wp_customize ) {
foreach ( Kirki::$controls_to_remove as $control ) {
$wp_customize->remove_control( $control );
}
}
/**
* Shows an admin notice.
*
* @access public
* @since 3.0.42
* @return void
*/
public function admin_notices() {
// No need for a nag if we don't need to recommend installing the FA plugin.
if ( ! self::$show_fa_nag ) {
return;
}
// No need for a nag if FA plugin is already installed.
if ( defined( 'FONTAWESOME_DIR_PATH' ) ) {
return;
}
// No need for a nag if current user can't install plugins.
if ( ! current_user_can( 'install_plugins' ) ) {
return;
}
// No need for a nag if user has dismissed it.
$dismissed = get_user_meta( get_current_user_id(), 'kirki_fa_nag_dismissed', true );
if ( true === $dismissed || 1 === $dismissed || '1' === $dismissed ) {
return;
}
?>
<div class="notice notice-info is-dismissible">
<p>
<?php esc_html_e( 'Your theme uses a Font Awesome field for icons. To avoid issues with missing icons on your frontend we recommend you install the official Font Awesome plugin.', 'kirki' ); ?>
</p>
<p>
<a class="button button-primary" href="<?php echo esc_url( admin_url( 'plugin-install.php?tab=plugin-information&plugin=font-awesome&TB_iframe=true&width=600&height=550' ) ); ?>"><?php esc_html_e( 'Install Plugin', 'kirki' ); ?></a>
<a class="button button-secondary" href="<?php echo esc_url( wp_nonce_url( admin_url( '?dismiss-nag=font-awesome-kirki' ), 'kirki-dismiss-nag', 'nonce' ) ); ?>"><?php esc_html_e( 'Don\'t show this again', 'kirki' ); ?></a>
</p>
</div>
<?php
}
/**
* Dismisses the nag.
*
* @access public
* @since 3.0.42
* @return void
*/
public function dismiss_nag() {
if ( isset( $_GET['nonce'] ) && wp_verify_nonce( $_GET['nonce'], 'kirki-dismiss-nag' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
if ( get_current_user_id() && isset( $_GET['dismiss-nag'] ) && 'font-awesome-kirki' === $_GET['dismiss-nag'] ) {
update_user_meta( get_current_user_id(), 'kirki_fa_nag_dismissed', true );
}
}
}
/**
* Handles showing a nag if the theme is using the deprecated fontawesome field
*
* @static
* @access protected
* @since 3.0.42
* @param array $args The field arguments.
* @return void
*/
protected static function maybe_show_fontawesome_nag( $args ) {
// If we already know we want it, skip check.
if ( self::$show_fa_nag ) {
return;
}
// Check if the field is fontawesome.
if ( isset( $args['type'] ) && in_array( $args['type'], array( 'fontawesome', 'kirki-fontawesome' ), true ) ) {
// Skip check if theme has disabled FA enqueueing via a filter.
if ( ! apply_filters( 'kirki_load_fontawesome', true ) ) {
return;
}
// If we got this far, we need to show the nag.
self::$show_fa_nag = true;
}
}
}

View file

@ -0,0 +1,144 @@
<?php
/**
* Internationalization helper.
*
* @package Kirki
* @category Core
* @author Ari Stathopoulos (@aristath)
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 1.0
*/
/**
* Handles translations
*/
class Kirki_L10n {
/**
* The plugin textdomain
*
* @access private
* @var string
*/
private $textdomain = 'kirki';
/**
* The theme textdomain
*
* @access private
* @var string
*/
private $theme_textdomain = '';
/**
* The class constructor.
* Adds actions & filters to handle the rest of the methods.
*
* @access public
*/
public function __construct() {
// If Kirki is installed as a plugin, load the texdomain.
if ( Kirki_Util::is_plugin() ) {
add_action( 'plugins_loaded', array( $this, 'load_textdomain' ) );
return;
}
// If we got this far, then Kirki is embedded in a plugin.
// We want the theme's textdomain to handle translations.
add_filter( 'override_load_textdomain', array( $this, 'override_load_textdomain' ), 5, 3 );
}
/**
* Load the plugin textdomain
*
* @access public
*/
public function load_textdomain() {
if ( null !== $this->get_path() ) {
load_textdomain( $this->textdomain, $this->get_path() );
}
load_plugin_textdomain( $this->textdomain, false, Kirki::$path . '/languages' );
}
/**
* Gets the path to a translation file.
*
* @access protected
* @return string Absolute path to the translation file.
*/
protected function get_path() {
$path_found = false;
$found_path = null;
foreach ( $this->get_paths() as $path ) {
if ( $path_found ) {
continue;
}
$path = wp_normalize_path( $path );
if ( file_exists( $path ) ) {
$path_found = true;
$found_path = $path;
}
}
return $found_path;
}
/**
* Returns an array of paths where translation files may be located.
*
* @access protected
* @return array
*/
protected function get_paths() {
return array(
WP_LANG_DIR . '/' . $this->textdomain . '-' . get_locale() . '.mo',
Kirki::$path . '/languages/' . $this->textdomain . '-' . get_locale() . '.mo',
);
}
/**
* Allows overriding the "kirki" textdomain from a theme.
*
* @since 3.0.12
* @access public
* @param bool $override Whether to override the .mo file loading. Default false.
* @param string $domain Text domain. Unique identifier for retrieving translated strings.
* @param string $mofile Path to the MO file.
* @return bool
*/
public function override_load_textdomain( $override, $domain, $mofile ) {
global $l10n;
if ( isset( $l10n[ $this->get_theme_textdomain() ] ) ) {
$l10n['kirki'] = $l10n[ $this->get_theme_textdomain() ]; // phpcs:ignore WordPress.WP.GlobalVariablesOverride
}
// Check if the domain is "kirki".
if ( 'kirki' === $domain ) {
return true;
}
return $override;
}
/**
* Get the theme's textdomain.
*
* @since 3.0.12
* @access private
* @return string
*/
private function get_theme_textdomain() {
if ( '' === $this->theme_textdomain ) {
// Get the textdomain.
$theme = wp_get_theme();
$this->theme_textdomain = $theme->get( 'TextDomain' );
// If no texdomain was found, use the template folder name.
if ( ! $this->theme_textdomain ) {
$this->theme_textdomain = get_template();
}
}
return $this->theme_textdomain;
}
}

View file

@ -0,0 +1,151 @@
<?php
/**
* Handles modules loading.
*
* @package Kirki
* @category Core
* @author Ari Stathopoulos (@aristath)
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 3.0.0
*/
/**
* The Kirki_Modules class.
*/
class Kirki_Modules {
/**
* An array of available modules.
*
* @static
* @access private
* @since 3.0.0
* @var array
*/
private static $modules = array();
/**
* An array of active modules (objects).
*
* @static
* @access private
* @since 3.0.0
* @var array
*/
private static $active_modules = array();
/**
* Constructor.
*
* @access public
* @since 3.0.0
*/
public function __construct() {
add_action( 'after_setup_theme', array( $this, 'setup_default_modules' ), 10 );
add_action( 'after_setup_theme', array( $this, 'init' ), 11 );
}
/**
* Set the default modules and apply the 'kirki_modules' filter.
* In v3.0.35 this method was renamed from default_modules to setup_default_modules,
* and its visibility changed from private to public to fix https://github.com/aristath/kirki/issues/2023
*
* @access public
* @since 3.0.0
*/
public function setup_default_modules() {
self::$modules = apply_filters(
'kirki_modules',
array(
'css' => 'Kirki_Modules_CSS',
'css-vars' => 'Kirki_Modules_CSS_Vars',
'customizer-styling' => 'Kirki_Modules_Customizer_Styling',
'icons' => 'Kirki_Modules_Icons',
'loading' => 'Kirki_Modules_Loading',
'tooltips' => 'Kirki_Modules_Tooltips',
'branding' => 'Kirki_Modules_Customizer_Branding',
'postMessage' => 'Kirki_Modules_PostMessage',
'selective-refresh' => 'Kirki_Modules_Selective_Refresh',
'field-dependencies' => 'Kirki_Modules_Field_Dependencies',
'custom-sections' => 'Kirki_Modules_Custom_Sections',
'webfonts' => 'Kirki_Modules_Webfonts',
'webfont-loader' => 'Kirki_Modules_Webfont_Loader',
'preset' => 'Kirki_Modules_Preset',
'gutenberg' => 'Kirki_Modules_Gutenberg',
'telemetry' => 'Kirki_Modules_Telemetry',
)
);
}
/**
* Instantiates the modules.
* In v3.0.35 the visibility for this method was changed
* from private to public to fix https://github.com/aristath/kirki/issues/2023
*
* @access public
* @since 3.0.0
*/
public function init() {
foreach ( self::$modules as $key => $module_class ) {
if ( class_exists( $module_class ) ) {
// Use this syntax instead of $module_class::get_instance()
// for PHP 5.2 compatibility.
self::$active_modules[ $key ] = call_user_func( array( $module_class, 'get_instance' ) );
}
}
}
/**
* Add a module.
*
* @static
* @access public
* @param string $module The classname of the module to add.
* @since 3.0.0
*/
public static function add_module( $module ) {
if ( ! in_array( $module, self::$modules, true ) ) {
self::$modules[] = $module;
}
}
/**
* Remove a module.
*
* @static
* @access public
* @param string $module The classname of the module to add.
* @since 3.0.0
*/
public static function remove_module( $module ) {
$key = array_search( $module, self::$modules, true );
if ( false !== $key ) {
unset( self::$modules[ $key ] );
}
}
/**
* Get the modules array.
*
* @static
* @access public
* @since 3.0.0
* @return array
*/
public static function get_modules() {
return self::$modules;
}
/**
* Get the array of active modules (objects).
*
* @static
* @access public
* @since 3.0.0
* @return array
*/
public static function get_active_modules() {
return self::$active_modules;
}
}

View file

@ -0,0 +1,54 @@
<?php
/**
* Handles panels added via the Kirki API.
*
* @package Kirki
* @category Core
* @author Ari Stathopoulos (@aristath)
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 1.0
*/
/**
* Each panel is a separate instance of the Kirki_Panel object.
*/
class Kirki_Panel {
/**
* An array of our panel types.
*
* @access private
* @var array
*/
private $panel_types = array(
'default' => 'WP_Customize_Panel',
);
/**
* The class constructor.
*
* @access public
* @param array $args The panel arguments.
*/
public function __construct( $args ) {
$this->panel_types = apply_filters( 'kirki_panel_types', $this->panel_types );
$this->add_panel( $args );
}
/**
* Add the panel using the Customizer API.
*
* @param array $args The panel arguments.
*/
public function add_panel( $args ) {
global $wp_customize;
if ( ! isset( $args['type'] ) || ! array_key_exists( $args['type'], $this->panel_types ) ) {
$args['type'] = 'default';
}
$panel_classname = $this->panel_types[ $args['type'] ];
$wp_customize->add_panel( new $panel_classname( $wp_customize, $args['id'], $args ) );
}
}

View file

@ -0,0 +1,203 @@
<?php
/**
* Additional sanitization methods for controls.
* These are used in the field's 'sanitize_callback' argument.
*
* @package Kirki
* @category Core
* @author Ari Stathopoulos (@aristath)
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 1.0
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* A simple wrapper class for static methods.
*/
class Kirki_Sanitize_Values {
/**
* Checkbox sanitization callback.
*
* Sanitization callback for 'checkbox' type controls.
* This callback sanitizes `$value` as a boolean value, either TRUE or FALSE.
*
* Deprecated. Use Kirki_Field_Checkbox::sanitize() instead.
*
* @static
* @access public
* @see Kirki_Field_Checkbox::sanitize()
* @param bool|string $value Whether the checkbox is checked.
* @return bool Whether the checkbox is checked.
*/
public static function checkbox( $value ) {
$obj = new Kirki_Field_Checkbox();
return (bool) $obj->sanitize( $value );
}
/**
* Sanitize number options.
*
* @static
* @access public
* @since 0.5
* @param int|float|double|string $value The value to be sanitized.
* @return integer|double|string
*/
public static function number( $value ) {
return ( is_numeric( $value ) ) ? $value : intval( $value );
}
/**
* Drop-down Pages sanitization callback.
*
* - Sanitization: dropdown-pages
* - Control: dropdown-pages
*
* Sanitization callback for 'dropdown-pages' type controls. This callback sanitizes `$page_id`
* as an absolute integer, and then validates that $input is the ID of a published page.
*
* @see absint() https://developer.wordpress.org/reference/functions/absint/
* @see get_post_status() https://developer.wordpress.org/reference/functions/get_post_status/
*
* @param int $page_id Page ID.
* @param WP_Customize_Setting $setting Setting instance.
* @return int|string Page ID if the page is published; otherwise, the setting default.
*/
public static function dropdown_pages( $page_id, $setting ) {
// Ensure $input is an absolute integer.
$page_id = absint( $page_id );
// If $page_id is an ID of a published page, return it; otherwise, return the default.
return ( 'publish' === get_post_status( $page_id ) ? $page_id : $setting->default );
}
/**
* Sanitizes css dimensions.
*
* @static
* @access public
* @since 2.2.0
* @param string $value The value to be sanitized.
* @return string
*/
public static function css_dimension( $value ) {
// Trim it.
$value = trim( $value );
// If the value is round, then return 50%.
if ( 'round' === $value ) {
$value = '50%';
}
// If the value is empty, return empty.
if ( '' === $value ) {
return '';
}
// If auto, inherit or initial, return the value.
if ( 'auto' === $value || 'initial' === $value || 'inherit' === $value || 'normal' === $value ) {
return $value;
}
// Return empty if there are no numbers in the value.
if ( ! preg_match( '#[0-9]#', $value ) ) {
return '';
}
// If we're using calc() then return the value.
if ( false !== strpos( $value, 'calc(' ) ) {
return $value;
}
// The raw value without the units.
$raw_value = self::filter_number( $value );
$unit_used = '';
// An array of all valid CSS units. Their order was carefully chosen for this evaluation, don't mix it up!!!
$units = array( 'fr', 'rem', 'em', 'ex', '%', 'px', 'cm', 'mm', 'in', 'pt', 'pc', 'ch', 'vh', 'vw', 'vmin', 'vmax' );
foreach ( $units as $unit ) {
if ( false !== strpos( $value, $unit ) ) {
$unit_used = $unit;
}
}
// Hack for rem values.
if ( 'em' === $unit_used && false !== strpos( $value, 'rem' ) ) {
$unit_used = 'rem';
}
return $raw_value . $unit_used;
}
/**
* Filters numeric values.
*
* @static
* @access public
* @param string $value The value to be sanitized.
* @return int|float
*/
public static function filter_number( $value ) {
return filter_var( $value, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION );
}
/**
* Sanitize RGBA colors
*
* @static
* @since 0.8.5
* @param string $value The value to be sanitized.
* @return string
*/
public static function rgba( $value ) {
$color = ariColor::newColor( $value );
return $color->toCSS( 'rgba' );
}
/**
* Sanitize colors.
*
* @static
* @since 0.8.5
* @param string $value The value to be sanitized.
* @return string
*/
public static function color( $value ) {
// If the value is empty, then return empty.
if ( '' === $value ) {
return '';
}
// If transparent, then return 'transparent'.
if ( is_string( $value ) && 'transparent' === trim( $value ) ) {
return 'transparent';
}
// Instantiate the object.
$color = ariColor::newColor( $value );
// Return a CSS value, using the auto-detected mode.
return $color->toCSS( $color->mode );
}
/**
* DOES NOT SANITIZE ANYTHING.
*
* @static
* @since 0.5
* @param int|string|array $value The value to be sanitized.
* @return int|string|array
*/
public static function unfiltered( $value ) {
return $value;
}
}

View file

@ -0,0 +1,60 @@
<?php
/**
* Handles sections created via the Kirki API.
*
* @package Kirki
* @category Core
* @author Ari Stathopoulos (@aristath)
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 1.0
*/
/**
* Each section is a separate instrance of the Kirki_Section object.
*/
class Kirki_Section {
/**
* An array of our section types.
*
* @access private
* @var array
*/
private $section_types = array();
/**
* The object constructor.
*
* @access public
* @param array $args The section parameters.
*/
public function __construct( $args ) {
$this->section_types = apply_filters( 'kirki_section_types', $this->section_types );
$this->add_section( $args );
}
/**
* Adds the section using the WordPress Customizer API.
*
* @access public
* @param array $args The section parameters.
*/
public function add_section( $args ) {
global $wp_customize;
// The default class to be used when creating a section.
$section_classname = 'WP_Customize_Section';
if ( isset( $args['type'] ) && array_key_exists( $args['type'], $this->section_types ) ) {
$section_classname = $this->section_types[ $args['type'] ];
}
if ( isset( $args['type'] ) && 'kirki-outer' === $args['type'] ) {
$args['type'] = 'outer';
$section_classname = 'WP_Customize_Section';
}
// Add the section.
$wp_customize->add_section( new $section_classname( $wp_customize, $args['id'], $args ) );
}
}

View file

@ -0,0 +1,47 @@
<?php
/**
* Additional tweaks for sections.
*
* @package Kirki
* @category Core
* @author Ari Stathopoulos (@aristath)
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 3.0.17
*/
/**
* Additional tweaks for sections.
*/
class Kirki_Sections {
/**
* The object constructor.
*
* @access public
* @since 3.0.17
*/
public function __construct() {
add_action( 'customize_controls_print_footer_scripts', array( $this, 'outer_sections_css' ) );
}
/**
* Generate CSS for the outer sections.
* These are by default hidden, we need to expose them.
*
* @since 3.0.17
* @return void
*/
public function outer_sections_css() {
echo '<style>';
$css = '';
if ( ! empty( Kirki::$sections ) ) {
foreach ( Kirki::$sections as $section_args ) {
if ( isset( $section_args['id'] ) && isset( $section_args['type'] ) && 'outer' === $section_args['type'] || 'kirki-outer' === $section_args['type'] ) {
echo '#customize-theme-controls li#accordion-section-' . esc_html( $section_args['id'] ) . '{display:list-item!important;}';
}
}
}
echo '</style>';
}
}

View file

@ -0,0 +1,73 @@
<?php
/**
* WordPress Customize Setting classes
*
* @package Kirki
* @subpackage Modules
* @since 3.0.0
*/
/**
* Handles saving and sanitizing of user-meta.
*
* @since 3.0.0
* @see WP_Customize_Setting
*/
class Kirki_Setting_Site_Option extends WP_Customize_Setting {
/**
* Type of customize settings.
*
* @access public
* @since 3.0.0
* @var string
*/
public $type = 'site_option';
/**
* Get the root value for a setting, especially for multidimensional ones.
*
* @access protected
* @since 3.0.0
* @param mixed $default Value to return if root does not exist.
* @return mixed
*/
protected function get_root_value( $default = null ) {
return get_site_option( $this->id_data['base'], $default );
}
/**
* Set the root value for a setting, especially for multidimensional ones.
*
* @access protected
* @since 3.0.0
* @param mixed $value Value to set as root of multidimensional setting.
* @return bool Whether the multidimensional root was updated successfully.
*/
protected function set_root_value( $value ) {
return update_site_option( $this->id_data['base'], $value );
}
/**
* Save the value of the setting, using the related API.
*
* @access protected
* @since 3.0.0
* @param mixed $value The value to update.
* @return bool The result of saving the value.
*/
protected function update( $value ) {
return $this->set_root_value( $value );
}
/**
* Fetch the value of the setting.
*
* @access protected
* @since 3.0.0
* @return mixed The value.
*/
public function value() {
return $this->get_root_value( $this->default );
}
}

View file

@ -0,0 +1,93 @@
<?php
/**
* WordPress Customize Setting classes
*
* @package Kirki
* @subpackage Modules
* @since 3.0.0
*/
/**
* Handles saving and sanitizing of user-meta.
*
* @since 3.0.0
* @see WP_Customize_Setting
*/
class Kirki_Setting_User_Meta extends WP_Customize_Setting {
/**
* Type of customize settings.
*
* @access public
* @since 3.0.0
* @var string
*/
public $type = 'user_meta';
/**
* Get the root value for a setting, especially for multidimensional ones.
*
* @access protected
* @since 3.0.0
* @param mixed $default Value to return if root does not exist.
* @return mixed
*/
protected function get_root_value( $default = null ) {
$id_base = $this->id_data['base'];
// Get all user-meta.
// We'll use this to check if the value is set or not,
// in order to figure out if we need to return the default value.
$user_meta = get_user_meta( get_current_user_id() );
// Get the single meta.
$single_meta = get_user_meta( get_current_user_id(), $id_base, true );
if ( isset( $user_meta[ $id_base ] ) ) {
return $single_meta;
}
return $default;
}
/**
* Set the root value for a setting, especially for multidimensional ones.
*
* @access protected
* @since 3.0.0
* @param mixed $value Value to set as root of multidimensional setting.
* @return bool Whether the multidimensional root was updated successfully.
*/
protected function set_root_value( $value ) {
$id_base = $this->id_data['base'];
// First delete the current user-meta.
// We're doing this to avoid duplicate entries.
delete_user_meta( get_current_user_id(), $id_base );
// Update the user-meta.
return update_user_meta( get_current_user_id(), $id_base, $value );
}
/**
* Save the value of the setting, using the related API.
*
* @access protected
* @since 3.0.0
* @param mixed $value The value to update.
* @return bool The result of saving the value.
*/
protected function update( $value ) {
return $this->set_root_value( $value );
}
/**
* Fetch the value of the setting.
*
* @access protected
* @since 3.0.0
* @return mixed The value.
*/
public function value() {
return $this->get_root_value( $this->default );
}
}

View file

@ -0,0 +1,154 @@
<?php
/**
* Handles sections created via the Kirki API.
*
* @package Kirki
* @category Core
* @author Ari Stathopoulos (@aristath)
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 1.0
*/
/**
* Each setting is a separate instance
*/
class Kirki_Settings {
/**
* TYhe global $wp_customize object.
*
* @access protected
* @var WP_Customize_Manager
*/
protected $wp_customize;
/**
* The setting-stypes we're using.
*
* @access protected
* @var array
*/
protected $setting_types = array();
/**
* Creates a new Kirki_Settings object.
* Class constructor.
*
* @access public
* @param array $args The field definition as sanitized in Kirki_Field.
*/
public function __construct( $args = array() ) {
// Set the $wp_customize property.
global $wp_customize;
if ( ! $wp_customize ) {
return;
}
$this->wp_customize = $wp_customize;
// Set the setting_types.
$this->set_setting_types();
// Add the settings.
$this->add_settings( $args );
}
/**
* Adds the settings for this field.
* If settings are defined as an array, then it goes through them
* and calls the add_setting method.
* If not an array, then it just calls add_setting
*
* @access private
* @param array $args The field definition as sanitized in Kirki_Field.
*/
final private function add_settings( $args = array() ) {
// Get the classname we'll be using to create our setting(s).
$classname = false;
if ( isset( $args['option_type'] ) && array_key_exists( $args['option_type'], $this->setting_types ) ) {
$classname = $this->setting_types[ $args['option_type'] ];
}
if ( ! isset( $args['type'] ) || ! array_key_exists( $args['type'], $this->setting_types ) ) {
$args['type'] = 'default';
}
$classname = ! $classname ? $this->setting_types[ $args['type'] ] : $classname;
// If settings are defined as an array, then we need to go through them
// and call add_setting for each one of them separately.
if ( isset( $args['settings'] ) && is_array( $args['settings'] ) ) {
// Make sure defaults have been defined.
if ( ! isset( $args['default'] ) || ! is_array( $args['default'] ) ) {
$args['default'] = array();
}
foreach ( $args['settings'] as $key => $value ) {
$default = ( isset( $defaults[ $key ] ) ) ? $defaults[ $key ] : '';
$this->add_setting( $classname, $value, $default, $args['option_type'], $args['capability'], $args['transport'], $args['sanitize_callback'] );
}
}
$this->add_setting( $classname, $args['settings'], $args['default'], $args['option_type'], $args['capability'], $args['transport'], $args['sanitize_callback'] );
}
/**
* This is where we're finally adding the setting to the Customizer.
*
* @access private
* @param string $classname The name of the class that will be used to create this setting.
* We're getting this from $this->setting_types.
* @param string $setting The setting-name.
* If settings is an array, then this method is called per-setting.
* @param string|array $default Default value for this setting.
* @param string $type The data type we're using. Valid options: theme_mod|option.
* @param string $capability @see https://codex.wordpress.org/Roles_and_Capabilities.
* @param string $transport Use refresh|postMessage.
* @param string|array $sanitize_callback A callable sanitization function or method.
*/
final private function add_setting( $classname, $setting, $default, $type, $capability, $transport, $sanitize_callback ) {
$this->wp_customize->add_setting(
new $classname(
$this->wp_customize,
$setting,
array(
'default' => $default,
'type' => $type,
'capability' => $capability,
'transport' => $transport,
'sanitize_callback' => $sanitize_callback,
)
)
);
}
/**
* Sets the $this->setting_types property.
* Makes sure the kirki_setting_types filter is applied
* and that the defined classes actually exist.
* If a defined class does not exist, it is removed.
*/
final private function set_setting_types() {
// Apply the kirki_setting_types filter.
$this->setting_types = apply_filters(
'kirki_setting_types',
array(
'default' => 'WP_Customize_Setting',
'repeater' => 'Kirki_Settings_Repeater_Setting',
'user_meta' => 'Kirki_Setting_User_Meta',
'site_option' => 'Kirki_Setting_Site_Option',
)
);
// Make sure the defined classes actually exist.
foreach ( $this->setting_types as $key => $classname ) {
if ( ! class_exists( $classname ) ) {
unset( $this->setting_types[ $key ] );
}
}
}
}

View file

@ -0,0 +1,45 @@
<?php
/**
* The main Kirki object
*
* @package Kirki
* @category Core
* @author Ari Stathopoulos (@aristath)
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 1.0
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Singleton class
*/
final class Kirki_Toolkit {
/**
* Holds the one, true instance of this object.
*
* @static
* @access protected
* @var object
*/
protected static $instance = null;
/**
* Access the single instance of this class.
*
* @static
* @access public
* @return object Kirki_Toolkit.
*/
public static function get_instance() {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
}

View file

@ -0,0 +1,205 @@
<?php
/**
* A utility class for Kirki.
*
* @package Kirki
* @category Core
* @author Ari Stathopoulos (@aristath)
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 3.0.9
*/
/**
* Utility class.
*/
class Kirki_Util {
/**
* Constructor.
*
* @since 3.0.9
* @access public
*/
public function __construct() {
add_filter( 'http_request_args', array( $this, 'http_request' ), 10, 2 );
}
/**
* Determine if Kirki is installed as a plugin.
*
* @static
* @access public
* @since 3.0.0
* @return bool
*/
public static function is_plugin() {
$is_plugin = false;
if ( ! function_exists( 'get_plugins' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php'; // phpcs:ignore WPThemeReview.CoreFunctionality.FileInclude
}
// Get all plugins.
$plugins = get_plugins();
$_plugin = '';
foreach ( $plugins as $plugin => $args ) {
if ( ! $is_plugin && isset( $args['Name'] ) && ( 'Kirki' === $args['Name'] || 'Kirki Toolkit' === $args['Name'] ) ) {
$is_plugin = true;
$_plugin = $plugin;
}
}
// No need to proceed any further if Kirki wasn't found in the list of plugins.
if ( ! $is_plugin ) {
return false;
}
// Make sure the is_plugins_loaded function is loaded.
include_once ABSPATH . 'wp-admin/includes/plugin.php'; // phpcs:ignore WPThemeReview.CoreFunctionality.FileInclude
// Extra logic in case the plugin is installed but not activated.
if ( $_plugin && is_plugin_inactive( $_plugin ) ) {
return false;
}
return $is_plugin;
}
/**
* Build the variables.
*
* @static
* @access public
* @since 3.0.9
* @return array Formatted as array( 'variable-name' => value ).
*/
public static function get_variables() {
$variables = array();
// Loop through all fields.
foreach ( Kirki::$fields as $field ) {
// Check if we have variables for this field.
if ( isset( $field['variables'] ) && $field['variables'] && ! empty( $field['variables'] ) ) {
// Loop through the array of variables.
foreach ( $field['variables'] as $field_variable ) {
// Is the variable ['name'] defined? If yes, then we can proceed.
if ( isset( $field_variable['name'] ) ) {
// Do we have a callback function defined? If not then set $variable_callback to false.
$variable_callback = ( isset( $field_variable['callback'] ) && is_callable( $field_variable['callback'] ) ) ? $field_variable['callback'] : false;
// If we have a variable_callback defined then get the value of the option
// and run it through the callback function.
// If no callback is defined (false) then just get the value.
$variables[ $field_variable['name'] ] = Kirki_Values::get_value( $field['settings'] );
if ( $variable_callback ) {
$variables[ $field_variable['name'] ] = call_user_func( $field_variable['callback'], Kirki_Values::get_value( $field['settings'] ) );
}
}
}
}
}
// Pass the variables through a filter ('kirki_variable') and return the array of variables.
return apply_filters( 'kirki_variable', $variables );
}
/**
* HTTP Request injection.
*
* @access public
* @since 3.0.0
* @param array $request The request params.
* @param string $url The request URL.
* @return array
*/
public function http_request( $request = array(), $url = '' ) {
// Early exit if installed as a plugin or not a request to wordpress.org,
// or finally if we don't have everything we need.
if (
self::is_plugin() ||
false === strpos( $url, 'wordpress.org' ) || (
! isset( $request['body'] ) ||
! isset( $request['body']['plugins'] ) ||
! isset( $request['body']['translations'] ) ||
! isset( $request['body']['locale'] ) ||
! isset( $request['body']['all'] )
)
) {
return $request;
}
$plugins = json_decode( $request['body']['plugins'], true );
if ( ! isset( $plugins['plugins'] ) ) {
return $request;
}
$exists = false;
foreach ( $plugins['plugins'] as $plugin ) {
if ( isset( $plugin['Name'] ) && 'Kirki Toolkit' === $plugin['Name'] ) {
$exists = true;
}
}
// Inject data.
if ( ! $exists && defined( 'KIRKI_PLUGIN_FILE' ) ) {
$plugins['plugins']['kirki/kirki.php'] = get_plugin_data( KIRKI_PLUGIN_FILE );
}
$request['body']['plugins'] = wp_json_encode( $plugins );
return $request;
}
/**
* Returns the $wp_version.
*
* @static
* @access public
* @since 3.0.12
* @param string $context Use 'minor' or 'major'.
* @return int|string Returns integer when getting the 'major' version.
* Returns string when getting the 'minor' version.
*/
public static function get_wp_version( $context = 'minor' ) {
global $wp_version;
// We only need the major version.
if ( 'major' === $context ) {
$version_parts = explode( '.', $wp_version );
return $version_parts[0];
}
return $wp_version;
}
/**
* Returns the $wp_version, only numeric value.
*
* @static
* @access public
* @since 3.0.12
* @param string $context Use 'minor' or 'major'.
* @param bool $only_numeric Whether we wwant to return numeric value or include beta/alpha etc.
* @return int|float Returns integer when getting the 'major' version.
* Returns float when getting the 'minor' version.
*/
public static function get_wp_version_numeric( $context = 'minor', $only_numeric = true ) {
global $wp_version;
// We only need the major version.
if ( 'major' === $context ) {
$version_parts = explode( '.', $wp_version );
return absint( $version_parts[0] );
}
// If we got this far, we want the full monty.
// Get the numeric part of the version without any beta, alpha etc parts.
if ( false !== strpos( $wp_version, '-' ) ) {
// We're on a dev version.
$version_parts = explode( '-', $wp_version );
return floatval( $version_parts[0] );
}
return floatval( $wp_version );
}
}

View file

@ -0,0 +1,155 @@
<?php
/**
* Helpers to get the values of a field.
* WARNING: PLEASE DO NOT USE THESE.
* we only have these for backwards-compatibility purposes.
* please use get_option() & get_theme_mod() instead.
*
* @package Kirki
* @category Core
* @author Ari Stathopoulos (@aristath)
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 1.0
*/
/**
* Wrapper class for static methods.
*/
class Kirki_Values {
/**
* Constructor.
*
* @access public
* @since 3.0.10
*/
public function __construct() {
add_filter( 'kirki_values_get_value', array( $this, 'typography_field_tweaks' ), 10, 2 );
}
/**
* Tweaks for typography fields.
*
* @access public
* @since 3.0.10
* @param string|array $value The value.
* @param string $field_id The field-ID.
* @return array
*/
public function typography_field_tweaks( $value, $field_id ) {
if ( isset( Kirki::$fields[ $field_id ] ) && isset( Kirki::$fields[ $field_id ]['type'] ) ) {
if ( 'kirki-typography' === Kirki::$fields[ $field_id ]['type'] ) {
// Sanitize the value.
// This also adds font-weight if it doesn't already exist.
$value = Kirki_Field_Typography::sanitize( $value );
// Combine font-family and font-backup.
if ( isset( $value['font-family'] ) && isset( $value['font-backup'] ) ) {
$backup = trim( $value['font-backup'] );
if ( ! empty( $backup ) ) {
$value['font-family'] .= ', ' . $backup;
}
unset( $value['font-backup'] );
}
}
}
return $value;
}
/**
* Get the value of a field.
*
* @static
* @access public
* @param string $config_id The configuration ID. @see Kirki_Config.
* @param string $field_id The field ID.
* @return string|array
*/
public static function get_value( $config_id = '', $field_id = '' ) {
// Make sure value is defined.
$value = '';
// This allows us to skip the $config_id argument.
// If we skip adding a $config_id, use the 'global' configuration.
if ( ( '' === $field_id ) && '' !== $config_id ) {
$field_id = $config_id;
$config_id = 'global';
}
// If $config_id is empty, set it to 'global'.
$config_id = ( '' === $config_id ) ? 'global' : $config_id;
// Fallback to 'global' if $config_id is not found.
if ( ! isset( Kirki::$config[ $config_id ] ) ) {
$config_id = 'global';
}
if ( 'theme_mod' === Kirki::$config[ $config_id ]['option_type'] ) {
// We're using theme_mods so just get the value using get_theme_mod.
$default_value = null;
if ( isset( Kirki::$fields[ $field_id ] ) && isset( Kirki::$fields[ $field_id ]['default'] ) ) {
$default_value = Kirki::$fields[ $field_id ]['default'];
}
$value = get_theme_mod( $field_id, $default_value );
return apply_filters( 'kirki_values_get_value', $value, $field_id );
}
if ( 'option' === Kirki::$config[ $config_id ]['option_type'] ) {
// We're using options.
if ( '' !== Kirki::$config[ $config_id ]['option_name'] ) {
// Options are serialized as a single option in the db.
// We'll have to get the option and then get the item from the array.
$options = get_option( Kirki::$config[ $config_id ]['option_name'] );
if ( ! isset( Kirki::$fields[ $field_id ] ) && isset( Kirki::$fields[ Kirki::$config[ $config_id ]['option_name'] . '[' . $field_id . ']' ] ) ) {
$field_id = Kirki::$config[ $config_id ]['option_name'] . '[' . $field_id . ']';
}
$setting_modified = str_replace( ']', '', str_replace( Kirki::$config[ $config_id ]['option_name'] . '[', '', $field_id ) );
$default_value = ( isset( Kirki::$fields[ $field_id ] ) && isset( Kirki::$fields[ $field_id ]['default'] ) ) ? Kirki::$fields[ $field_id ]['default'] : '';
$value = ( isset( $options[ $setting_modified ] ) ) ? $options[ $setting_modified ] : $default_value;
$value = maybe_unserialize( $value );
return apply_filters( 'kirki_values_get_value', $value, $field_id );
}
// Each option separately saved in the db.
$value = get_option( $field_id, Kirki::$fields[ $field_id ]['default'] );
return apply_filters( 'kirki_values_get_value', $value, $field_id );
}
return apply_filters( 'kirki_values_get_value', $value, $field_id );
}
/**
* Gets the value or fallsback to default.
*
* @static
* @access public
* @param array $field The field aruments.
* @return string|array
*/
public static function get_sanitized_field_value( $field ) {
$value = $field['default'];
if ( isset( $field['option_type'] ) && 'theme_mod' === $field['option_type'] ) {
$value = get_theme_mod( $field['settings'], $field['default'] );
} elseif ( isset( $field['option_type'] ) && 'option' === $field['option_type'] ) {
if ( isset( $field['option_name'] ) && '' !== $field['option_name'] ) {
$all_values = get_option( $field['option_name'], array() );
$sub_setting_id = str_replace( array( ']', $field['option_name'] . '[' ), '', $field['settings'] );
if ( isset( $all_values[ $sub_setting_id ] ) ) {
$value = $all_values[ $sub_setting_id ];
}
} else {
$value = get_option( $field['settings'], $field['default'] );
}
}
return $value;
}
}

View file

@ -0,0 +1,293 @@
<?php
/**
* The Kirki API class.
* Takes care of adding panels, sections & fields to the customizer.
* For documentation please see https://github.com/aristath/kirki/wiki
*
* @package Kirki
* @category Core
* @author Ari Stathopoulos (@aristath)
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
* @license https://opensource.org/licenses/MIT
* @since 1.0
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* This class acts as an interface.
* Developers may use this object to add configurations, fields, panels and sections.
* You can also access all available configurations, fields, panels and sections
* by accessing the object's static properties.
*/
class Kirki extends Kirki_Init {
/**
* Absolute path to the Kirki folder.
*
* @static
* @access public
* @var string
*/
public static $path;
/**
* URL to the Kirki folder.
*
* @static
* @access public
* @var string
*/
public static $url;
/**
* An array containing all configurations.
*
* @static
* @access public
* @var array
*/
public static $config = array();
/**
* An array containing all fields.
*
* @static
* @access public
* @var array
*/
public static $fields = array();
/**
* An array containing all panels.
*
* @static
* @access public
* @var array
*/
public static $panels = array();
/**
* An array containing all sections.
*
* @static
* @access public
* @var array
*/
public static $sections = array();
/**
* An array containing all panels to be removed.
*
* @static
* @access public
* @since 3.0.17
* @var array
*/
public static $panels_to_remove = array();
/**
* An array containing all sections to be removed.
*
* @static
* @access public
* @since 3.0.17
* @var array
*/
public static $sections_to_remove = array();
/**
* An array containing all controls to be removed.
*
* @static
* @access public
* @since 3.0.17
* @var array
*/
public static $controls_to_remove = array();
/**
* Modules object.
*
* @access public
* @since 3.0.0
* @var object
*/
public $modules;
/**
* Get the value of an option from the db.
*
* @static
* @access public
* @param string $config_id The ID of the configuration corresponding to this field.
* @param string $field_id The field_id (defined as 'settings' in the field arguments).
* @return mixed The saved value of the field.
*/
public static function get_option( $config_id = '', $field_id = '' ) {
return Kirki_Values::get_value( $config_id, $field_id );
}
/**
* Sets the configuration options.
*
* @static
* @access public
* @param string $config_id The configuration ID.
* @param array $args The configuration options.
*/
public static function add_config( $config_id, $args = array() ) {
$config = Kirki_Config::get_instance( $config_id, $args );
$config_args = $config->get_config();
self::$config[ $config_args['id'] ] = $config_args;
}
/**
* Create a new panel.
*
* @static
* @access public
* @param string $id The ID for this panel.
* @param array $args The panel arguments.
*/
public static function add_panel( $id = '', $args = array() ) {
$args['id'] = $id;
if ( ! isset( $args['description'] ) ) {
$args['description'] = '';
}
if ( ! isset( $args['priority'] ) ) {
$args['priority'] = 10;
}
if ( ! isset( $args['type'] ) ) {
$args['type'] = 'default';
}
if ( false === strpos( $args['type'], 'kirki-' ) ) {
$args['type'] = 'kirki-' . $args['type'];
}
self::$panels[ $id ] = $args;
}
/**
* Remove a panel.
*
* @static
* @access public
* @since 3.0.17
* @param string $id The ID for this panel.
*/
public static function remove_panel( $id = '' ) {
if ( ! in_array( $id, self::$panels_to_remove, true ) ) {
self::$panels_to_remove[] = $id;
}
}
/**
* Create a new section.
*
* @static
* @access public
* @param string $id The ID for this section.
* @param array $args The section arguments.
*/
public static function add_section( $id, $args ) {
$args['id'] = $id;
if ( ! isset( $args['description'] ) ) {
$args['description'] = '';
}
if ( ! isset( $args['priority'] ) ) {
$args['priority'] = 10;
}
if ( ! isset( $args['type'] ) ) {
$args['type'] = 'default';
}
if ( false === strpos( $args['type'], 'kirki-' ) ) {
$args['type'] = 'kirki-' . $args['type'];
}
self::$sections[ $id ] = $args;
}
/**
* Remove a section.
*
* @static
* @access public
* @since 3.0.17
* @param string $id The ID for this panel.
*/
public static function remove_section( $id = '' ) {
if ( ! in_array( $id, self::$sections_to_remove, true ) ) {
self::$sections_to_remove[] = $id;
}
}
/**
* Create a new field.
*
* @static
* @access public
* @param string $config_id The configuration ID for this field.
* @param array $args The field arguments.
*/
public static function add_field( $config_id, $args ) {
if ( doing_action( 'customize_register' ) ) {
_doing_it_wrong( __METHOD__, esc_html__( 'Kirki fields should not be added on customize_register. Please add them directly, or on init.', 'kirki' ), '3.0.10' );
}
parent::maybe_show_fontawesome_nag( $args );
// Early exit if 'type' is not defined.
if ( ! isset( $args['type'] ) ) {
return;
}
$str = str_replace( array( '-', '_' ), ' ', $args['type'] );
$classname = 'Kirki_Field_' . str_replace( ' ', '_', ucwords( $str ) );
if ( class_exists( $classname ) ) {
new $classname( $config_id, $args );
return;
}
if ( false !== strpos( $classname, 'Kirki_Field_Kirki_' ) ) {
$classname = str_replace( 'Kirki_Field_Kirki_', 'Kirki_Field_', $classname );
if ( class_exists( $classname ) ) {
new $classname( $config_id, $args );
return;
}
}
new Kirki_Field( $config_id, $args );
}
/**
* Remove a control.
*
* @static
* @access public
* @since 3.0.17
* @param string $id The field ID.
*/
public static function remove_control( $id ) {
if ( ! in_array( $id, self::$controls_to_remove, true ) ) {
self::$controls_to_remove[] = $id;
}
}
/**
* Gets a parameter for a config-id.
*
* @static
* @access public
* @since 3.0.10
* @param string $id The config-ID.
* @param string $param The parameter we want.
* @return string
*/
public static function get_config_param( $id, $param ) {
if ( ! isset( self::$config[ $id ] ) || ! isset( self::$config[ $id ][ $param ] ) ) {
return '';
}
return self::$config[ $id ][ $param ];
}
}