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 ) { |
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 |