wp/wp-includes/class-wp-customize-setting.php
changeset 9 177826044cd9
parent 7 cf61fcea0001
child 16 a86126ab1dd4
equal deleted inserted replaced
8:c7c34916027a 9:177826044cd9
    64 	 * @var string
    64 	 * @var string
    65 	 */
    65 	 */
    66 	public $default = '';
    66 	public $default = '';
    67 
    67 
    68 	/**
    68 	/**
    69 	 * Options for rendering the live preview of changes in Theme Customizer.
    69 	 * Options for rendering the live preview of changes in Customizer.
    70 	 *
    70 	 *
    71 	 * Set this value to 'postMessage' to enable a custom Javascript handler to render changes to this setting
    71 	 * Set this value to 'postMessage' to enable a custom JavaScript handler to render changes to this setting
    72 	 * as opposed to reloading the whole page.
    72 	 * as opposed to reloading the whole page.
    73 	 *
    73 	 *
    74 	 * @link https://developer.wordpress.org/themes/customize-api
    74 	 * @link https://developer.wordpress.org/themes/customize-api
    75 	 *
    75 	 *
    76 	 * @since 3.4.0
    76 	 * @since 3.4.0
   133 
   133 
   134 	/**
   134 	/**
   135 	 * Cache of multidimensional values to improve performance.
   135 	 * Cache of multidimensional values to improve performance.
   136 	 *
   136 	 *
   137 	 * @since 4.4.0
   137 	 * @since 4.4.0
   138 	 * @static
       
   139 	 * @var array
   138 	 * @var array
   140 	 */
   139 	 */
   141 	protected static $aggregated_multidimensionals = array();
   140 	protected static $aggregated_multidimensionals = array();
   142 
   141 
   143 	/**
   142 	/**
   167 				$this->$key = $args[ $key ];
   166 				$this->$key = $args[ $key ];
   168 			}
   167 			}
   169 		}
   168 		}
   170 
   169 
   171 		$this->manager = $manager;
   170 		$this->manager = $manager;
   172 		$this->id = $id;
   171 		$this->id      = $id;
   173 
   172 
   174 		// Parse the ID for array keys.
   173 		// Parse the ID for array keys.
   175 		$this->id_data['keys'] = preg_split( '/\[/', str_replace( ']', '', $this->id ) );
   174 		$this->id_data['keys'] = preg_split( '/\[/', str_replace( ']', '', $this->id ) );
   176 		$this->id_data['base'] = array_shift( $this->id_data['keys'] );
   175 		$this->id_data['base'] = array_shift( $this->id_data['keys'] );
   177 
   176 
   178 		// Rebuild the ID.
   177 		// Rebuild the ID.
   179 		$this->id = $this->id_data[ 'base' ];
   178 		$this->id = $this->id_data['base'];
   180 		if ( ! empty( $this->id_data[ 'keys' ] ) ) {
   179 		if ( ! empty( $this->id_data['keys'] ) ) {
   181 			$this->id .= '[' . implode( '][', $this->id_data['keys'] ) . ']';
   180 			$this->id .= '[' . implode( '][', $this->id_data['keys'] ) . ']';
   182 		}
   181 		}
   183 
   182 
   184 		if ( $this->validate_callback ) {
   183 		if ( $this->validate_callback ) {
   185 			add_filter( "customize_validate_{$this->id}", $this->validate_callback, 10, 3 );
   184 			add_filter( "customize_validate_{$this->id}", $this->validate_callback, 10, 3 );
   309 		// Prevent re-previewing an already-previewed setting.
   308 		// Prevent re-previewing an already-previewed setting.
   310 		if ( $this->is_previewed ) {
   309 		if ( $this->is_previewed ) {
   311 			return true;
   310 			return true;
   312 		}
   311 		}
   313 
   312 
   314 		$id_base = $this->id_data['base'];
   313 		$id_base                 = $this->id_data['base'];
   315 		$is_multidimensional = ! empty( $this->id_data['keys'] );
   314 		$is_multidimensional     = ! empty( $this->id_data['keys'] );
   316 		$multidimensional_filter = array( $this, '_multidimensional_preview_filter' );
   315 		$multidimensional_filter = array( $this, '_multidimensional_preview_filter' );
   317 
   316 
   318 		/*
   317 		/*
   319 		 * Check if the setting has a pre-existing value (an isset check),
   318 		 * Check if the setting has a pre-existing value (an isset check),
   320 		 * and if doesn't have any incoming post value. If both checks are true,
   319 		 * and if doesn't have any incoming post value. If both checks are true,
   321 		 * then the preview short-circuits because there is nothing that needs
   320 		 * then the preview short-circuits because there is nothing that needs
   322 		 * to be previewed.
   321 		 * to be previewed.
   323 		 */
   322 		 */
   324 		$undefined = new stdClass();
   323 		$undefined     = new stdClass();
   325 		$needs_preview = ( $undefined !== $this->post_value( $undefined ) );
   324 		$needs_preview = ( $undefined !== $this->post_value( $undefined ) );
   326 		$value = null;
   325 		$value         = null;
   327 
   326 
   328 		// Since no post value was defined, check if we have an initial value set.
   327 		// Since no post value was defined, check if we have an initial value set.
   329 		if ( ! $needs_preview ) {
   328 		if ( ! $needs_preview ) {
   330 			if ( $this->is_multidimensional_aggregated ) {
   329 			if ( $this->is_multidimensional_aggregated ) {
   331 				$root = self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value'];
   330 				$root  = self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value'];
   332 				$value = $this->multidimensional_get( $root, $this->id_data['keys'], $undefined );
   331 				$value = $this->multidimensional_get( $root, $this->id_data['keys'], $undefined );
   333 			} else {
   332 			} else {
   334 				$default = $this->default;
   333 				$default       = $this->default;
   335 				$this->default = $undefined; // Temporarily set default to undefined so we can detect if existing value is set.
   334 				$this->default = $undefined; // Temporarily set default to undefined so we can detect if existing value is set.
   336 				$value = $this->value();
   335 				$value         = $this->value();
   337 				$this->default = $default;
   336 				$this->default = $default;
   338 			}
   337 			}
   339 			$needs_preview = ( $undefined === $value ); // Because the default needs to be supplied.
   338 			$needs_preview = ( $undefined === $value ); // Because the default needs to be supplied.
   340 		}
   339 		}
   341 
   340 
   346 			}
   345 			}
   347 			return false;
   346 			return false;
   348 		}
   347 		}
   349 
   348 
   350 		switch ( $this->type ) {
   349 		switch ( $this->type ) {
   351 			case 'theme_mod' :
   350 			case 'theme_mod':
   352 				if ( ! $is_multidimensional ) {
   351 				if ( ! $is_multidimensional ) {
   353 					add_filter( "theme_mod_{$id_base}", array( $this, '_preview_filter' ) );
   352 					add_filter( "theme_mod_{$id_base}", array( $this, '_preview_filter' ) );
   354 				} else {
   353 				} else {
   355 					if ( empty( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'] ) ) {
   354 					if ( empty( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'] ) ) {
   356 						// Only add this filter once for this ID base.
   355 						// Only add this filter once for this ID base.
   357 						add_filter( "theme_mod_{$id_base}", $multidimensional_filter );
   356 						add_filter( "theme_mod_{$id_base}", $multidimensional_filter );
   358 					}
   357 					}
   359 					self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'][ $this->id ] = $this;
   358 					self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'][ $this->id ] = $this;
   360 				}
   359 				}
   361 				break;
   360 				break;
   362 			case 'option' :
   361 			case 'option':
   363 				if ( ! $is_multidimensional ) {
   362 				if ( ! $is_multidimensional ) {
   364 					add_filter( "pre_option_{$id_base}", array( $this, '_preview_filter' ) );
   363 					add_filter( "pre_option_{$id_base}", array( $this, '_preview_filter' ) );
   365 				} else {
   364 				} else {
   366 					if ( empty( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'] ) ) {
   365 					if ( empty( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'] ) ) {
   367 						// Only add these filters once for this ID base.
   366 						// Only add these filters once for this ID base.
   369 						add_filter( "default_option_{$id_base}", $multidimensional_filter );
   368 						add_filter( "default_option_{$id_base}", $multidimensional_filter );
   370 					}
   369 					}
   371 					self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'][ $this->id ] = $this;
   370 					self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'][ $this->id ] = $this;
   372 				}
   371 				}
   373 				break;
   372 				break;
   374 			default :
   373 			default:
   375 
       
   376 				/**
   374 				/**
   377 				 * Fires when the WP_Customize_Setting::preview() method is called for settings
   375 				 * Fires when the WP_Customize_Setting::preview() method is called for settings
   378 				 * not handled as theme_mods or options.
   376 				 * not handled as theme_mods or options.
   379 				 *
   377 				 *
   380 				 * The dynamic portion of the hook name, `$this->id`, refers to the setting ID.
   378 				 * The dynamic portion of the hook name, `$this->id`, refers to the setting ID.
   434 	public function _preview_filter( $original ) {
   432 	public function _preview_filter( $original ) {
   435 		if ( ! $this->is_current_blog_previewed() ) {
   433 		if ( ! $this->is_current_blog_previewed() ) {
   436 			return $original;
   434 			return $original;
   437 		}
   435 		}
   438 
   436 
   439 		$undefined = new stdClass(); // Symbol hack.
   437 		$undefined  = new stdClass(); // Symbol hack.
   440 		$post_value = $this->post_value( $undefined );
   438 		$post_value = $this->post_value( $undefined );
   441 		if ( $undefined !== $post_value ) {
   439 		if ( $undefined !== $post_value ) {
   442 			$value = $post_value;
   440 			$value = $post_value;
   443 		} else {
   441 		} else {
   444 			/*
   442 			/*
   481 				continue;
   479 				continue;
   482 			}
   480 			}
   483 
   481 
   484 			// Do the replacements of the posted/default sub value into the root value.
   482 			// Do the replacements of the posted/default sub value into the root value.
   485 			$value = $previewed_setting->post_value( $previewed_setting->default );
   483 			$value = $previewed_setting->post_value( $previewed_setting->default );
   486 			$root = self::$aggregated_multidimensionals[ $previewed_setting->type ][ $id_base ]['root_value'];
   484 			$root  = self::$aggregated_multidimensionals[ $previewed_setting->type ][ $id_base ]['root_value'];
   487 			$root = $previewed_setting->multidimensional_replace( $root, $previewed_setting->id_data['keys'], $value );
   485 			$root  = $previewed_setting->multidimensional_replace( $root, $previewed_setting->id_data['keys'], $value );
   488 			self::$aggregated_multidimensionals[ $previewed_setting->type ][ $id_base ]['root_value'] = $root;
   486 			self::$aggregated_multidimensionals[ $previewed_setting->type ][ $id_base ]['root_value'] = $root;
   489 
   487 
   490 			// Mark this setting having been applied so that it will be skipped when the filter is called again.
   488 			// Mark this setting having been applied so that it will be skipped when the filter is called again.
   491 			self::$aggregated_multidimensionals[ $previewed_setting->type ][ $id_base ]['preview_applied_instances'][ $previewed_setting->id ] = true;
   489 			self::$aggregated_multidimensionals[ $previewed_setting->type ][ $id_base ]['preview_applied_instances'][ $previewed_setting->id ] = true;
   492 		}
   490 		}
   594 		 * @param mixed                $value    Value of the setting.
   592 		 * @param mixed                $value    Value of the setting.
   595 		 * @param WP_Customize_Setting $this     WP_Customize_Setting instance.
   593 		 * @param WP_Customize_Setting $this     WP_Customize_Setting instance.
   596 		 */
   594 		 */
   597 		$validity = apply_filters( "customize_validate_{$this->id}", $validity, $value, $this );
   595 		$validity = apply_filters( "customize_validate_{$this->id}", $validity, $value, $this );
   598 
   596 
   599 		if ( is_wp_error( $validity ) && empty( $validity->errors ) ) {
   597 		if ( is_wp_error( $validity ) && ! $validity->has_errors() ) {
   600 			$validity = true;
   598 			$validity = true;
   601 		}
   599 		}
   602 		return $validity;
   600 		return $validity;
   603 	}
   601 	}
   604 
   602 
   718 	 * @since 3.4.0
   716 	 * @since 3.4.0
   719 	 *
   717 	 *
   720 	 * @return mixed The value.
   718 	 * @return mixed The value.
   721 	 */
   719 	 */
   722 	public function value() {
   720 	public function value() {
   723 		$id_base = $this->id_data['base'];
   721 		$id_base      = $this->id_data['base'];
   724 		$is_core_type = ( 'option' === $this->type || 'theme_mod' === $this->type );
   722 		$is_core_type = ( 'option' === $this->type || 'theme_mod' === $this->type );
   725 
   723 
   726 		if ( ! $is_core_type && ! $this->is_multidimensional_aggregated ) {
   724 		if ( ! $is_core_type && ! $this->is_multidimensional_aggregated ) {
   727 
   725 
   728 			// Use post value if previewed and a post value is present.
   726 			// Use post value if previewed and a post value is present.
   751 			 * @param WP_Customize_Setting $this    The setting instance.
   749 			 * @param WP_Customize_Setting $this    The setting instance.
   752 			 */
   750 			 */
   753 			$value = apply_filters( "customize_value_{$id_base}", $value, $this );
   751 			$value = apply_filters( "customize_value_{$id_base}", $value, $this );
   754 		} elseif ( $this->is_multidimensional_aggregated ) {
   752 		} elseif ( $this->is_multidimensional_aggregated ) {
   755 			$root_value = self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value'];
   753 			$root_value = self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value'];
   756 			$value = $this->multidimensional_get( $root_value, $this->id_data['keys'], $this->default );
   754 			$value      = $this->multidimensional_get( $root_value, $this->id_data['keys'], $this->default );
   757 
   755 
   758 			// Ensure that the post value is used if the setting is previewed, since preview filters aren't applying on cached $root_value.
   756 			// Ensure that the post value is used if the setting is previewed, since preview filters aren't applying on cached $root_value.
   759 			if ( $this->is_previewed ) {
   757 			if ( $this->is_previewed ) {
   760 				$value = $this->post_value( $value );
   758 				$value = $this->post_value( $value );
   761 			}
   759 			}
   784 		 * @param mixed                $value The setting value.
   782 		 * @param mixed                $value The setting value.
   785 		 * @param WP_Customize_Setting $this  WP_Customize_Setting instance.
   783 		 * @param WP_Customize_Setting $this  WP_Customize_Setting instance.
   786 		 */
   784 		 */
   787 		$value = apply_filters( "customize_sanitize_js_{$this->id}", $this->value(), $this );
   785 		$value = apply_filters( "customize_sanitize_js_{$this->id}", $this->value(), $this );
   788 
   786 
   789 		if ( is_string( $value ) )
   787 		if ( is_string( $value ) ) {
   790 			return html_entity_decode( $value, ENT_QUOTES, 'UTF-8');
   788 			return html_entity_decode( $value, ENT_QUOTES, 'UTF-8' );
       
   789 		}
   791 
   790 
   792 		return $value;
   791 		return $value;
   793 	}
   792 	}
   794 
   793 
   795 	/**
   794 	/**
   814 	 * @since 3.4.0
   813 	 * @since 3.4.0
   815 	 *
   814 	 *
   816 	 * @return bool False if theme doesn't support the setting or user can't change setting, otherwise true.
   815 	 * @return bool False if theme doesn't support the setting or user can't change setting, otherwise true.
   817 	 */
   816 	 */
   818 	final public function check_capabilities() {
   817 	final public function check_capabilities() {
   819 		if ( $this->capability && ! call_user_func_array( 'current_user_can', (array) $this->capability ) )
   818 		if ( $this->capability && ! call_user_func_array( 'current_user_can', (array) $this->capability ) ) {
   820 			return false;
   819 			return false;
   821 
   820 		}
   822 		if ( $this->theme_supports && ! call_user_func_array( 'current_theme_supports', (array) $this->theme_supports ) )
   821 
       
   822 		if ( $this->theme_supports && ! call_user_func_array( 'current_theme_supports', (array) $this->theme_supports ) ) {
   823 			return false;
   823 			return false;
       
   824 		}
   824 
   825 
   825 		return true;
   826 		return true;
   826 	}
   827 	}
   827 
   828 
   828 	/**
   829 	/**
   834 	 * @param $keys
   835 	 * @param $keys
   835 	 * @param bool $create Default is false.
   836 	 * @param bool $create Default is false.
   836 	 * @return array|void Keys are 'root', 'node', and 'key'.
   837 	 * @return array|void Keys are 'root', 'node', and 'key'.
   837 	 */
   838 	 */
   838 	final protected function multidimensional( &$root, $keys, $create = false ) {
   839 	final protected function multidimensional( &$root, $keys, $create = false ) {
   839 		if ( $create && empty( $root ) )
   840 		if ( $create && empty( $root ) ) {
   840 			$root = array();
   841 			$root = array();
   841 
   842 		}
   842 		if ( ! isset( $root ) || empty( $keys ) )
   843 
       
   844 		if ( ! isset( $root ) || empty( $keys ) ) {
   843 			return;
   845 			return;
       
   846 		}
   844 
   847 
   845 		$last = array_pop( $keys );
   848 		$last = array_pop( $keys );
   846 		$node = &$root;
   849 		$node = &$root;
   847 
   850 
   848 		foreach ( $keys as $key ) {
   851 		foreach ( $keys as $key ) {
   849 			if ( $create && ! isset( $node[ $key ] ) )
   852 			if ( $create && ! isset( $node[ $key ] ) ) {
   850 				$node[ $key ] = array();
   853 				$node[ $key ] = array();
   851 
   854 			}
   852 			if ( ! is_array( $node ) || ! isset( $node[ $key ] ) )
   855 
       
   856 			if ( ! is_array( $node ) || ! isset( $node[ $key ] ) ) {
   853 				return;
   857 				return;
       
   858 			}
   854 
   859 
   855 			$node = &$node[ $key ];
   860 			$node = &$node[ $key ];
   856 		}
   861 		}
   857 
   862 
   858 		if ( $create ) {
   863 		if ( $create ) {
   863 			if ( ! isset( $node[ $last ] ) ) {
   868 			if ( ! isset( $node[ $last ] ) ) {
   864 				$node[ $last ] = array();
   869 				$node[ $last ] = array();
   865 			}
   870 			}
   866 		}
   871 		}
   867 
   872 
   868 		if ( ! isset( $node[ $last ] ) )
   873 		if ( ! isset( $node[ $last ] ) ) {
   869 			return;
   874 			return;
       
   875 		}
   870 
   876 
   871 		return array(
   877 		return array(
   872 			'root' => &$root,
   878 			'root' => &$root,
   873 			'node' => &$node,
   879 			'node' => &$node,
   874 			'key'  => $last,
   880 			'key'  => $last,
   884 	 * @param $keys
   890 	 * @param $keys
   885 	 * @param mixed $value The value to update.
   891 	 * @param mixed $value The value to update.
   886 	 * @return mixed
   892 	 * @return mixed
   887 	 */
   893 	 */
   888 	final protected function multidimensional_replace( $root, $keys, $value ) {
   894 	final protected function multidimensional_replace( $root, $keys, $value ) {
   889 		if ( ! isset( $value ) )
   895 		if ( ! isset( $value ) ) {
   890 			return $root;
   896 			return $root;
   891 		elseif ( empty( $keys ) ) // If there are no keys, we're replacing the root.
   897 		} elseif ( empty( $keys ) ) { // If there are no keys, we're replacing the root.
   892 			return $value;
   898 			return $value;
       
   899 		}
   893 
   900 
   894 		$result = $this->multidimensional( $root, $keys, true );
   901 		$result = $this->multidimensional( $root, $keys, true );
   895 
   902 
   896 		if ( isset( $result ) )
   903 		if ( isset( $result ) ) {
   897 			$result['node'][ $result['key'] ] = $value;
   904 			$result['node'][ $result['key'] ] = $value;
       
   905 		}
   898 
   906 
   899 		return $root;
   907 		return $root;
   900 	}
   908 	}
   901 
   909 
   902 	/**
   910 	/**
   908 	 * @param $keys
   916 	 * @param $keys
   909 	 * @param mixed $default A default value which is used as a fallback. Default is null.
   917 	 * @param mixed $default A default value which is used as a fallback. Default is null.
   910 	 * @return mixed The requested value or the default value.
   918 	 * @return mixed The requested value or the default value.
   911 	 */
   919 	 */
   912 	final protected function multidimensional_get( $root, $keys, $default = null ) {
   920 	final protected function multidimensional_get( $root, $keys, $default = null ) {
   913 		if ( empty( $keys ) ) // If there are no keys, test the root.
   921 		if ( empty( $keys ) ) { // If there are no keys, test the root.
   914 			return isset( $root ) ? $root : $default;
   922 			return isset( $root ) ? $root : $default;
       
   923 		}
   915 
   924 
   916 		$result = $this->multidimensional( $root, $keys );
   925 		$result = $this->multidimensional( $root, $keys );
   917 		return isset( $result ) ? $result['node'][ $result['key'] ] : $default;
   926 		return isset( $result ) ? $result['node'][ $result['key'] ] : $default;
   918 	}
   927 	}
   919 
   928