89 * @param array $args Setting arguments. |
161 * @param array $args Setting arguments. |
90 */ |
162 */ |
91 public function __construct( $manager, $id, $args = array() ) { |
163 public function __construct( $manager, $id, $args = array() ) { |
92 $keys = array_keys( get_object_vars( $this ) ); |
164 $keys = array_keys( get_object_vars( $this ) ); |
93 foreach ( $keys as $key ) { |
165 foreach ( $keys as $key ) { |
94 if ( isset( $args[ $key ] ) ) |
166 if ( isset( $args[ $key ] ) ) { |
95 $this->$key = $args[ $key ]; |
167 $this->$key = $args[ $key ]; |
|
168 } |
96 } |
169 } |
97 |
170 |
98 $this->manager = $manager; |
171 $this->manager = $manager; |
99 $this->id = $id; |
172 $this->id = $id; |
100 |
173 |
101 // Parse the ID for array keys. |
174 // Parse the ID for array keys. |
102 $this->id_data[ 'keys' ] = preg_split( '/\[/', str_replace( ']', '', $this->id ) ); |
175 $this->id_data['keys'] = preg_split( '/\[/', str_replace( ']', '', $this->id ) ); |
103 $this->id_data[ 'base' ] = array_shift( $this->id_data[ 'keys' ] ); |
176 $this->id_data['base'] = array_shift( $this->id_data['keys'] ); |
104 |
177 |
105 // Rebuild the ID. |
178 // Rebuild the ID. |
106 $this->id = $this->id_data[ 'base' ]; |
179 $this->id = $this->id_data[ 'base' ]; |
107 if ( ! empty( $this->id_data[ 'keys' ] ) ) |
180 if ( ! empty( $this->id_data[ 'keys' ] ) ) { |
108 $this->id .= '[' . implode( '][', $this->id_data[ 'keys' ] ) . ']'; |
181 $this->id .= '[' . implode( '][', $this->id_data['keys'] ) . ']'; |
109 |
182 } |
110 if ( $this->sanitize_callback ) |
183 |
|
184 if ( $this->validate_callback ) { |
|
185 add_filter( "customize_validate_{$this->id}", $this->validate_callback, 10, 3 ); |
|
186 } |
|
187 if ( $this->sanitize_callback ) { |
111 add_filter( "customize_sanitize_{$this->id}", $this->sanitize_callback, 10, 2 ); |
188 add_filter( "customize_sanitize_{$this->id}", $this->sanitize_callback, 10, 2 ); |
112 |
189 } |
113 if ( $this->sanitize_js_callback ) |
190 if ( $this->sanitize_js_callback ) { |
114 add_filter( "customize_sanitize_js_{$this->id}", $this->sanitize_js_callback, 10, 2 ); |
191 add_filter( "customize_sanitize_js_{$this->id}", $this->sanitize_js_callback, 10, 2 ); |
115 } |
192 } |
116 |
193 |
117 /** |
194 if ( 'option' === $this->type || 'theme_mod' === $this->type ) { |
118 * The ID for the current blog when the preview() method was called. |
195 // Other setting types can opt-in to aggregate multidimensional explicitly. |
|
196 $this->aggregate_multidimensional(); |
|
197 |
|
198 // Allow option settings to indicate whether they should be autoloaded. |
|
199 if ( 'option' === $this->type && isset( $args['autoload'] ) ) { |
|
200 self::$aggregated_multidimensionals[ $this->type ][ $this->id_data['base'] ]['autoload'] = $args['autoload']; |
|
201 } |
|
202 } |
|
203 } |
|
204 |
|
205 /** |
|
206 * Get parsed ID data for multidimensional setting. |
|
207 * |
|
208 * @since 4.4.0 |
|
209 * |
|
210 * @return array { |
|
211 * ID data for multidimensional setting. |
|
212 * |
|
213 * @type string $base ID base |
|
214 * @type array $keys Keys for multidimensional array. |
|
215 * } |
|
216 */ |
|
217 final public function id_data() { |
|
218 return $this->id_data; |
|
219 } |
|
220 |
|
221 /** |
|
222 * Set up the setting for aggregated multidimensional values. |
|
223 * |
|
224 * When a multidimensional setting gets aggregated, all of its preview and update |
|
225 * calls get combined into one call, greatly improving performance. |
|
226 * |
|
227 * @since 4.4.0 |
|
228 */ |
|
229 protected function aggregate_multidimensional() { |
|
230 $id_base = $this->id_data['base']; |
|
231 if ( ! isset( self::$aggregated_multidimensionals[ $this->type ] ) ) { |
|
232 self::$aggregated_multidimensionals[ $this->type ] = array(); |
|
233 } |
|
234 if ( ! isset( self::$aggregated_multidimensionals[ $this->type ][ $id_base ] ) ) { |
|
235 self::$aggregated_multidimensionals[ $this->type ][ $id_base ] = array( |
|
236 'previewed_instances' => array(), // Calling preview() will add the $setting to the array. |
|
237 'preview_applied_instances' => array(), // Flags for which settings have had their values applied. |
|
238 'root_value' => $this->get_root_value( array() ), // Root value for initial state, manipulated by preview and update calls. |
|
239 ); |
|
240 } |
|
241 |
|
242 if ( ! empty( $this->id_data['keys'] ) ) { |
|
243 // Note the preview-applied flag is cleared at priority 9 to ensure it is cleared before a deferred-preview runs. |
|
244 add_action( "customize_post_value_set_{$this->id}", array( $this, '_clear_aggregated_multidimensional_preview_applied_flag' ), 9 ); |
|
245 $this->is_multidimensional_aggregated = true; |
|
246 } |
|
247 } |
|
248 |
|
249 /** |
|
250 * Reset `$aggregated_multidimensionals` static variable. |
|
251 * |
|
252 * This is intended only for use by unit tests. |
|
253 * |
|
254 * @since 4.5.0 |
|
255 * @ignore |
|
256 */ |
|
257 static public function reset_aggregated_multidimensionals() { |
|
258 self::$aggregated_multidimensionals = array(); |
|
259 } |
|
260 |
|
261 /** |
|
262 * The ID for the current site when the preview() method was called. |
119 * |
263 * |
120 * @since 4.2.0 |
264 * @since 4.2.0 |
121 * @access protected |
|
122 * @var int |
265 * @var int |
123 */ |
266 */ |
124 protected $_previewed_blog_id; |
267 protected $_previewed_blog_id; |
125 |
268 |
126 /** |
269 /** |
127 * Return true if the current blog is not the same as the previewed blog. |
270 * Return true if the current site is not the same as the previewed site. |
128 * |
271 * |
129 * @since 4.2.0 |
272 * @since 4.2.0 |
130 * @access public |
273 * |
131 * |
274 * @return bool If preview() has been called. |
132 * @return bool|null Returns null if preview() has not been called yet. |
|
133 */ |
275 */ |
134 public function is_current_blog_previewed() { |
276 public function is_current_blog_previewed() { |
135 if ( ! isset( $this->_previewed_blog_id ) ) { |
277 if ( ! isset( $this->_previewed_blog_id ) ) { |
136 return null; |
278 return false; |
137 } |
279 } |
138 return ( get_current_blog_id() === $this->_previewed_blog_id ); |
280 return ( get_current_blog_id() === $this->_previewed_blog_id ); |
139 } |
281 } |
140 |
282 |
141 /** |
283 /** |
146 * @var mixed |
288 * @var mixed |
147 */ |
289 */ |
148 protected $_original_value; |
290 protected $_original_value; |
149 |
291 |
150 /** |
292 /** |
151 * Handle previewing the setting. |
293 * Add filters to supply the setting's value when accessed. |
152 * |
294 * |
153 * @since 3.4.0 |
295 * If the setting already has a pre-existing value and there is no incoming |
|
296 * post value for the setting, then this method will short-circuit since |
|
297 * there is no change to preview. |
|
298 * |
|
299 * @since 3.4.0 |
|
300 * @since 4.4.0 Added boolean return value. |
|
301 * |
|
302 * @return bool False when preview short-circuits due no change needing to be previewed. |
154 */ |
303 */ |
155 public function preview() { |
304 public function preview() { |
156 if ( ! isset( $this->_original_value ) ) { |
|
157 $this->_original_value = $this->value(); |
|
158 } |
|
159 if ( ! isset( $this->_previewed_blog_id ) ) { |
305 if ( ! isset( $this->_previewed_blog_id ) ) { |
160 $this->_previewed_blog_id = get_current_blog_id(); |
306 $this->_previewed_blog_id = get_current_blog_id(); |
161 } |
307 } |
162 |
308 |
163 switch( $this->type ) { |
309 // Prevent re-previewing an already-previewed setting. |
|
310 if ( $this->is_previewed ) { |
|
311 return true; |
|
312 } |
|
313 |
|
314 $id_base = $this->id_data['base']; |
|
315 $is_multidimensional = ! empty( $this->id_data['keys'] ); |
|
316 $multidimensional_filter = array( $this, '_multidimensional_preview_filter' ); |
|
317 |
|
318 /* |
|
319 * 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, |
|
321 * then the preview short-circuits because there is nothing that needs |
|
322 * to be previewed. |
|
323 */ |
|
324 $undefined = new stdClass(); |
|
325 $needs_preview = ( $undefined !== $this->post_value( $undefined ) ); |
|
326 $value = null; |
|
327 |
|
328 // Since no post value was defined, check if we have an initial value set. |
|
329 if ( ! $needs_preview ) { |
|
330 if ( $this->is_multidimensional_aggregated ) { |
|
331 $root = self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value']; |
|
332 $value = $this->multidimensional_get( $root, $this->id_data['keys'], $undefined ); |
|
333 } else { |
|
334 $default = $this->default; |
|
335 $this->default = $undefined; // Temporarily set default to undefined so we can detect if existing value is set. |
|
336 $value = $this->value(); |
|
337 $this->default = $default; |
|
338 } |
|
339 $needs_preview = ( $undefined === $value ); // Because the default needs to be supplied. |
|
340 } |
|
341 |
|
342 // If the setting does not need previewing now, defer to when it has a value to preview. |
|
343 if ( ! $needs_preview ) { |
|
344 if ( ! has_action( "customize_post_value_set_{$this->id}", array( $this, 'preview' ) ) ) { |
|
345 add_action( "customize_post_value_set_{$this->id}", array( $this, 'preview' ) ); |
|
346 } |
|
347 return false; |
|
348 } |
|
349 |
|
350 switch ( $this->type ) { |
164 case 'theme_mod' : |
351 case 'theme_mod' : |
165 add_filter( 'theme_mod_' . $this->id_data[ 'base' ], array( $this, '_preview_filter' ) ); |
352 if ( ! $is_multidimensional ) { |
|
353 add_filter( "theme_mod_{$id_base}", array( $this, '_preview_filter' ) ); |
|
354 } else { |
|
355 if ( empty( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'] ) ) { |
|
356 // Only add this filter once for this ID base. |
|
357 add_filter( "theme_mod_{$id_base}", $multidimensional_filter ); |
|
358 } |
|
359 self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'][ $this->id ] = $this; |
|
360 } |
166 break; |
361 break; |
167 case 'option' : |
362 case 'option' : |
168 if ( empty( $this->id_data[ 'keys' ] ) ) |
363 if ( ! $is_multidimensional ) { |
169 add_filter( 'pre_option_' . $this->id_data[ 'base' ], array( $this, '_preview_filter' ) ); |
364 add_filter( "pre_option_{$id_base}", array( $this, '_preview_filter' ) ); |
170 else { |
365 } else { |
171 add_filter( 'option_' . $this->id_data[ 'base' ], array( $this, '_preview_filter' ) ); |
366 if ( empty( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'] ) ) { |
172 add_filter( 'default_option_' . $this->id_data[ 'base' ], array( $this, '_preview_filter' ) ); |
367 // Only add these filters once for this ID base. |
|
368 add_filter( "option_{$id_base}", $multidimensional_filter ); |
|
369 add_filter( "default_option_{$id_base}", $multidimensional_filter ); |
|
370 } |
|
371 self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'][ $this->id ] = $this; |
173 } |
372 } |
174 break; |
373 break; |
175 default : |
374 default : |
176 |
375 |
177 /** |
376 /** |
178 * Fires when the {@see WP_Customize_Setting::preview()} method is called for settings |
377 * Fires when the WP_Customize_Setting::preview() method is called for settings |
179 * not handled as theme_mods or options. |
378 * not handled as theme_mods or options. |
180 * |
379 * |
181 * The dynamic portion of the hook name, `$this->id`, refers to the setting ID. |
380 * The dynamic portion of the hook name, `$this->id`, refers to the setting ID. |
182 * |
381 * |
183 * @since 3.4.0 |
382 * @since 3.4.0 |
184 * |
383 * |
185 * @param WP_Customize_Setting $this {@see WP_Customize_Setting} instance. |
384 * @param WP_Customize_Setting $this WP_Customize_Setting instance. |
186 */ |
385 */ |
187 do_action( "customize_preview_{$this->id}", $this ); |
386 do_action( "customize_preview_{$this->id}", $this ); |
188 |
387 |
189 /** |
388 /** |
190 * Fires when the {@see WP_Customize_Setting::preview()} method is called for settings |
389 * Fires when the WP_Customize_Setting::preview() method is called for settings |
191 * not handled as theme_mods or options. |
390 * not handled as theme_mods or options. |
192 * |
391 * |
193 * The dynamic portion of the hook name, `$this->type`, refers to the setting type. |
392 * The dynamic portion of the hook name, `$this->type`, refers to the setting type. |
194 * |
393 * |
195 * @since 4.1.0 |
394 * @since 4.1.0 |
196 * |
395 * |
197 * @param WP_Customize_Setting $this {@see WP_Customize_Setting} instance. |
396 * @param WP_Customize_Setting $this WP_Customize_Setting instance. |
198 */ |
397 */ |
199 do_action( "customize_preview_{$this->type}", $this ); |
398 do_action( "customize_preview_{$this->type}", $this ); |
200 } |
399 } |
201 } |
400 |
202 |
401 $this->is_previewed = true; |
203 /** |
402 |
204 * Callback function to filter the theme mods and options. |
403 return true; |
|
404 } |
|
405 |
|
406 /** |
|
407 * Clear out the previewed-applied flag for a multidimensional-aggregated value whenever its post value is updated. |
|
408 * |
|
409 * This ensures that the new value will get sanitized and used the next time |
|
410 * that `WP_Customize_Setting::_multidimensional_preview_filter()` |
|
411 * is called for this setting. |
|
412 * |
|
413 * @since 4.4.0 |
|
414 * |
|
415 * @see WP_Customize_Manager::set_post_value() |
|
416 * @see WP_Customize_Setting::_multidimensional_preview_filter() |
|
417 */ |
|
418 final public function _clear_aggregated_multidimensional_preview_applied_flag() { |
|
419 unset( self::$aggregated_multidimensionals[ $this->type ][ $this->id_data['base'] ]['preview_applied_instances'][ $this->id ] ); |
|
420 } |
|
421 |
|
422 /** |
|
423 * Callback function to filter non-multidimensional theme mods and options. |
205 * |
424 * |
206 * If switch_to_blog() was called after the preview() method, and the current |
425 * If switch_to_blog() was called after the preview() method, and the current |
207 * blog is now not the same blog, then this method does a no-op and returns |
426 * site is now not the same site, then this method does a no-op and returns |
208 * the original value. |
427 * the original value. |
209 * |
428 * |
210 * @since 3.4.0 |
429 * @since 3.4.0 |
211 * @uses WP_Customize_Setting::multidimensional_replace() |
|
212 * |
430 * |
213 * @param mixed $original Old value. |
431 * @param mixed $original Old value. |
214 * @return mixed New or old value. |
432 * @return mixed New or old value. |
215 */ |
433 */ |
216 public function _preview_filter( $original ) { |
434 public function _preview_filter( $original ) { |
217 if ( ! $this->is_current_blog_previewed() ) { |
435 if ( ! $this->is_current_blog_previewed() ) { |
218 return $original; |
436 return $original; |
219 } |
437 } |
220 |
438 |
221 $undefined = new stdClass(); // symbol hack |
439 $undefined = new stdClass(); // Symbol hack. |
222 $post_value = $this->post_value( $undefined ); |
440 $post_value = $this->post_value( $undefined ); |
223 if ( $undefined === $post_value ) { |
441 if ( $undefined !== $post_value ) { |
224 $value = $this->_original_value; |
442 $value = $post_value; |
225 } else { |
443 } else { |
226 $value = $post_value; |
444 /* |
227 } |
445 * Note that we don't use $original here because preview() will |
228 |
446 * not add the filter in the first place if it has an initial value |
229 return $this->multidimensional_replace( $original, $this->id_data['keys'], $value ); |
447 * and there is no post value. |
230 } |
448 */ |
231 |
449 $value = $this->default; |
232 /** |
450 } |
233 * Check user capabilities and theme supports, and then save |
451 return $value; |
|
452 } |
|
453 |
|
454 /** |
|
455 * Callback function to filter multidimensional theme mods and options. |
|
456 * |
|
457 * For all multidimensional settings of a given type, the preview filter for |
|
458 * the first setting previewed will be used to apply the values for the others. |
|
459 * |
|
460 * @since 4.4.0 |
|
461 * |
|
462 * @see WP_Customize_Setting::$aggregated_multidimensionals |
|
463 * @param mixed $original Original root value. |
|
464 * @return mixed New or old value. |
|
465 */ |
|
466 final public function _multidimensional_preview_filter( $original ) { |
|
467 if ( ! $this->is_current_blog_previewed() ) { |
|
468 return $original; |
|
469 } |
|
470 |
|
471 $id_base = $this->id_data['base']; |
|
472 |
|
473 // If no settings have been previewed yet (which should not be the case, since $this is), just pass through the original value. |
|
474 if ( empty( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'] ) ) { |
|
475 return $original; |
|
476 } |
|
477 |
|
478 foreach ( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'] as $previewed_setting ) { |
|
479 // Skip applying previewed value for any settings that have already been applied. |
|
480 if ( ! empty( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['preview_applied_instances'][ $previewed_setting->id ] ) ) { |
|
481 continue; |
|
482 } |
|
483 |
|
484 // Do the replacements of the posted/default sub value into the root value. |
|
485 $value = $previewed_setting->post_value( $previewed_setting->default ); |
|
486 $root = self::$aggregated_multidimensionals[ $previewed_setting->type ][ $id_base ]['root_value']; |
|
487 $root = $previewed_setting->multidimensional_replace( $root, $previewed_setting->id_data['keys'], $value ); |
|
488 self::$aggregated_multidimensionals[ $previewed_setting->type ][ $id_base ]['root_value'] = $root; |
|
489 |
|
490 // 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; |
|
492 } |
|
493 |
|
494 return self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value']; |
|
495 } |
|
496 |
|
497 /** |
|
498 * Checks user capabilities and theme supports, and then saves |
234 * the value of the setting. |
499 * the value of the setting. |
235 * |
500 * |
236 * @since 3.4.0 |
501 * @since 3.4.0 |
237 * |
502 * |
238 * @return false|null False if cap check fails or value isn't set. |
503 * @return false|void False if cap check fails or value isn't set or is invalid. |
239 */ |
504 */ |
240 final public function save() { |
505 final public function save() { |
241 $value = $this->post_value(); |
506 $value = $this->post_value(); |
242 |
507 |
243 if ( ! $this->check_capabilities() || ! isset( $value ) ) |
508 if ( ! $this->check_capabilities() || ! isset( $value ) ) { |
244 return false; |
509 return false; |
|
510 } |
|
511 |
|
512 $id_base = $this->id_data['base']; |
245 |
513 |
246 /** |
514 /** |
247 * Fires when the WP_Customize_Setting::save() method is called. |
515 * Fires when the WP_Customize_Setting::save() method is called. |
248 * |
516 * |
249 * The dynamic portion of the hook name, `$this->id_data['base']` refers to |
517 * The dynamic portion of the hook name, `$id_base` refers to |
250 * the base slug of the setting name. |
518 * the base slug of the setting name. |
251 * |
519 * |
252 * @since 3.4.0 |
520 * @since 3.4.0 |
253 * |
521 * |
254 * @param WP_Customize_Setting $this {@see WP_Customize_Setting} instance. |
522 * @param WP_Customize_Setting $this WP_Customize_Setting instance. |
255 */ |
523 */ |
256 do_action( 'customize_save_' . $this->id_data[ 'base' ], $this ); |
524 do_action( "customize_save_{$id_base}", $this ); |
257 |
525 |
258 $this->update( $value ); |
526 $this->update( $value ); |
259 } |
527 } |
260 |
528 |
261 /** |
529 /** |
262 * Fetch and sanitize the $_POST value for the setting. |
530 * Fetch and sanitize the $_POST value for the setting. |
263 * |
531 * |
|
532 * During a save request prior to save, post_value() provides the new value while value() does not. |
|
533 * |
264 * @since 3.4.0 |
534 * @since 3.4.0 |
265 * |
535 * |
266 * @param mixed $default A default value which is used as a fallback. Default is null. |
536 * @param mixed $default A default value which is used as a fallback. Default is null. |
267 * @return mixed The default value on failure, otherwise the sanitized value. |
537 * @return mixed The default value on failure, otherwise the sanitized and validated value. |
268 */ |
538 */ |
269 final public function post_value( $default = null ) { |
539 final public function post_value( $default = null ) { |
270 return $this->manager->post_value( $this, $default ); |
540 return $this->manager->post_value( $this, $default ); |
271 } |
541 } |
272 |
542 |
273 /** |
543 /** |
274 * Sanitize an input. |
544 * Sanitize an input. |
275 * |
545 * |
276 * @since 3.4.0 |
546 * @since 3.4.0 |
277 * |
547 * |
278 * @param mixed $value The value to sanitize. |
548 * @param string|array $value The value to sanitize. |
279 * @return mixed Null if an input isn't valid, otherwise the sanitized value. |
549 * @return string|array|null|WP_Error Sanitized value, or `null`/`WP_Error` if invalid. |
280 */ |
550 */ |
281 public function sanitize( $value ) { |
551 public function sanitize( $value ) { |
282 $value = wp_unslash( $value ); |
|
283 |
552 |
284 /** |
553 /** |
285 * Filter a Customize setting value in un-slashed form. |
554 * Filters a Customize setting value in un-slashed form. |
286 * |
555 * |
287 * @since 3.4.0 |
556 * @since 3.4.0 |
288 * |
557 * |
289 * @param mixed $value Value of the setting. |
558 * @param mixed $value Value of the setting. |
290 * @param WP_Customize_Setting $this WP_Customize_Setting instance. |
559 * @param WP_Customize_Setting $this WP_Customize_Setting instance. |
291 */ |
560 */ |
292 return apply_filters( "customize_sanitize_{$this->id}", $value, $this ); |
561 return apply_filters( "customize_sanitize_{$this->id}", $value, $this ); |
293 } |
562 } |
294 |
563 |
295 /** |
564 /** |
|
565 * Validates an input. |
|
566 * |
|
567 * @since 4.6.0 |
|
568 * |
|
569 * @see WP_REST_Request::has_valid_params() |
|
570 * |
|
571 * @param mixed $value Value to validate. |
|
572 * @return true|WP_Error True if the input was validated, otherwise WP_Error. |
|
573 */ |
|
574 public function validate( $value ) { |
|
575 if ( is_wp_error( $value ) ) { |
|
576 return $value; |
|
577 } |
|
578 if ( is_null( $value ) ) { |
|
579 return new WP_Error( 'invalid_value', __( 'Invalid value.' ) ); |
|
580 } |
|
581 |
|
582 $validity = new WP_Error(); |
|
583 |
|
584 /** |
|
585 * Validates a Customize setting value. |
|
586 * |
|
587 * Plugins should amend the `$validity` object via its `WP_Error::add()` method. |
|
588 * |
|
589 * The dynamic portion of the hook name, `$this->ID`, refers to the setting ID. |
|
590 * |
|
591 * @since 4.6.0 |
|
592 * |
|
593 * @param WP_Error $validity Filtered from `true` to `WP_Error` when invalid. |
|
594 * @param mixed $value Value of the setting. |
|
595 * @param WP_Customize_Setting $this WP_Customize_Setting instance. |
|
596 */ |
|
597 $validity = apply_filters( "customize_validate_{$this->id}", $validity, $value, $this ); |
|
598 |
|
599 if ( is_wp_error( $validity ) && empty( $validity->errors ) ) { |
|
600 $validity = true; |
|
601 } |
|
602 return $validity; |
|
603 } |
|
604 |
|
605 /** |
|
606 * Get the root value for a setting, especially for multidimensional ones. |
|
607 * |
|
608 * @since 4.4.0 |
|
609 * |
|
610 * @param mixed $default Value to return if root does not exist. |
|
611 * @return mixed |
|
612 */ |
|
613 protected function get_root_value( $default = null ) { |
|
614 $id_base = $this->id_data['base']; |
|
615 if ( 'option' === $this->type ) { |
|
616 return get_option( $id_base, $default ); |
|
617 } elseif ( 'theme_mod' === $this->type ) { |
|
618 return get_theme_mod( $id_base, $default ); |
|
619 } else { |
|
620 /* |
|
621 * Any WP_Customize_Setting subclass implementing aggregate multidimensional |
|
622 * will need to override this method to obtain the data from the appropriate |
|
623 * location. |
|
624 */ |
|
625 return $default; |
|
626 } |
|
627 } |
|
628 |
|
629 /** |
|
630 * Set the root value for a setting, especially for multidimensional ones. |
|
631 * |
|
632 * @since 4.4.0 |
|
633 * |
|
634 * @param mixed $value Value to set as root of multidimensional setting. |
|
635 * @return bool Whether the multidimensional root was updated successfully. |
|
636 */ |
|
637 protected function set_root_value( $value ) { |
|
638 $id_base = $this->id_data['base']; |
|
639 if ( 'option' === $this->type ) { |
|
640 $autoload = true; |
|
641 if ( isset( self::$aggregated_multidimensionals[ $this->type ][ $this->id_data['base'] ]['autoload'] ) ) { |
|
642 $autoload = self::$aggregated_multidimensionals[ $this->type ][ $this->id_data['base'] ]['autoload']; |
|
643 } |
|
644 return update_option( $id_base, $value, $autoload ); |
|
645 } elseif ( 'theme_mod' === $this->type ) { |
|
646 set_theme_mod( $id_base, $value ); |
|
647 return true; |
|
648 } else { |
|
649 /* |
|
650 * Any WP_Customize_Setting subclass implementing aggregate multidimensional |
|
651 * will need to override this method to obtain the data from the appropriate |
|
652 * location. |
|
653 */ |
|
654 return false; |
|
655 } |
|
656 } |
|
657 |
|
658 /** |
296 * Save the value of the setting, using the related API. |
659 * Save the value of the setting, using the related API. |
297 * |
660 * |
298 * @since 3.4.0 |
661 * @since 3.4.0 |
299 * |
662 * |
300 * @param mixed $value The value to update. |
663 * @param mixed $value The value to update. |
301 * @return mixed The result of saving the value. |
664 * @return bool The result of saving the value. |
302 */ |
665 */ |
303 protected function update( $value ) { |
666 protected function update( $value ) { |
304 switch( $this->type ) { |
667 $id_base = $this->id_data['base']; |
305 case 'theme_mod' : |
668 if ( 'option' === $this->type || 'theme_mod' === $this->type ) { |
306 return $this->_update_theme_mod( $value ); |
669 if ( ! $this->is_multidimensional_aggregated ) { |
307 |
670 return $this->set_root_value( $value ); |
308 case 'option' : |
671 } else { |
309 return $this->_update_option( $value ); |
672 $root = self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value']; |
310 |
673 $root = $this->multidimensional_replace( $root, $this->id_data['keys'], $value ); |
311 default : |
674 self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value'] = $root; |
312 |
675 return $this->set_root_value( $root ); |
313 /** |
676 } |
314 * Fires when the {@see WP_Customize_Setting::update()} method is called for settings |
677 } else { |
315 * not handled as theme_mods or options. |
678 /** |
316 * |
679 * Fires when the WP_Customize_Setting::update() method is called for settings |
317 * The dynamic portion of the hook name, `$this->type`, refers to the type of setting. |
680 * not handled as theme_mods or options. |
318 * |
681 * |
319 * @since 3.4.0 |
682 * The dynamic portion of the hook name, `$this->type`, refers to the type of setting. |
320 * |
683 * |
321 * @param mixed $value Value of the setting. |
684 * @since 3.4.0 |
322 * @param WP_Customize_Setting $this WP_Customize_Setting instance. |
685 * |
323 */ |
686 * @param mixed $value Value of the setting. |
324 return do_action( 'customize_update_' . $this->type, $value, $this ); |
687 * @param WP_Customize_Setting $this WP_Customize_Setting instance. |
325 } |
688 */ |
326 } |
689 do_action( "customize_update_{$this->type}", $value, $this ); |
327 |
690 |
328 /** |
691 return has_action( "customize_update_{$this->type}" ); |
329 * Update the theme mod from the value of the parameter. |
692 } |
330 * |
693 } |
331 * @since 3.4.0 |
694 |
332 * |
695 /** |
333 * @param mixed $value The value to update. |
696 * Deprecated method. |
334 * @return mixed The result of saving the value. |
697 * |
335 */ |
698 * @since 3.4.0 |
336 protected function _update_theme_mod( $value ) { |
699 * @deprecated 4.4.0 Deprecated in favor of update() method. |
337 // Handle non-array theme mod. |
700 */ |
338 if ( empty( $this->id_data[ 'keys' ] ) ) |
701 protected function _update_theme_mod() { |
339 return set_theme_mod( $this->id_data[ 'base' ], $value ); |
702 _deprecated_function( __METHOD__, '4.4.0', __CLASS__ . '::update()' ); |
340 |
703 } |
341 // Handle array-based theme mod. |
704 |
342 $mods = get_theme_mod( $this->id_data[ 'base' ] ); |
705 /** |
343 $mods = $this->multidimensional_replace( $mods, $this->id_data[ 'keys' ], $value ); |
706 * Deprecated method. |
344 if ( isset( $mods ) ) |
707 * |
345 return set_theme_mod( $this->id_data[ 'base' ], $mods ); |
708 * @since 3.4.0 |
346 } |
709 * @deprecated 4.4.0 Deprecated in favor of update() method. |
347 |
710 */ |
348 /** |
711 protected function _update_option() { |
349 * Update the option from the value of the setting. |
712 _deprecated_function( __METHOD__, '4.4.0', __CLASS__ . '::update()' ); |
350 * |
|
351 * @since 3.4.0 |
|
352 * |
|
353 * @param mixed $value The value to update. |
|
354 * @return bool|null The result of saving the value. |
|
355 */ |
|
356 protected function _update_option( $value ) { |
|
357 // Handle non-array option. |
|
358 if ( empty( $this->id_data[ 'keys' ] ) ) |
|
359 return update_option( $this->id_data[ 'base' ], $value ); |
|
360 |
|
361 // Handle array-based options. |
|
362 $options = get_option( $this->id_data[ 'base' ] ); |
|
363 $options = $this->multidimensional_replace( $options, $this->id_data[ 'keys' ], $value ); |
|
364 if ( isset( $options ) ) |
|
365 return update_option( $this->id_data[ 'base' ], $options ); |
|
366 } |
713 } |
367 |
714 |
368 /** |
715 /** |
369 * Fetch the value of the setting. |
716 * Fetch the value of the setting. |
370 * |
717 * |
371 * @since 3.4.0 |
718 * @since 3.4.0 |
372 * |
719 * |
373 * @return mixed The value. |
720 * @return mixed The value. |
374 */ |
721 */ |
375 public function value() { |
722 public function value() { |
376 // Get the callback that corresponds to the setting type. |
723 $id_base = $this->id_data['base']; |
377 switch( $this->type ) { |
724 $is_core_type = ( 'option' === $this->type || 'theme_mod' === $this->type ); |
378 case 'theme_mod' : |
725 |
379 $function = 'get_theme_mod'; |
726 if ( ! $is_core_type && ! $this->is_multidimensional_aggregated ) { |
380 break; |
727 |
381 case 'option' : |
728 // Use post value if previewed and a post value is present. |
382 $function = 'get_option'; |
729 if ( $this->is_previewed ) { |
383 break; |
730 $value = $this->post_value( null ); |
384 default : |
731 if ( null !== $value ) { |
385 |
732 return $value; |
386 /** |
733 } |
387 * Filter a Customize setting value not handled as a theme_mod or option. |
734 } |
388 * |
735 |
389 * The dynamic portion of the hook name, `$this->id_date['base']`, refers to |
736 $value = $this->get_root_value( $this->default ); |
390 * the base slug of the setting name. |
737 |
391 * |
738 /** |
392 * For settings handled as theme_mods or options, see those corresponding |
739 * Filters a Customize setting value not handled as a theme_mod or option. |
393 * functions for available hooks. |
740 * |
394 * |
741 * The dynamic portion of the hook name, `$id_base`, refers to |
395 * @since 3.4.0 |
742 * the base slug of the setting name, initialized from `$this->id_data['base']`. |
396 * |
743 * |
397 * @param mixed $default The setting default value. Default empty. |
744 * For settings handled as theme_mods or options, see those corresponding |
398 */ |
745 * functions for available hooks. |
399 return apply_filters( 'customize_value_' . $this->id_data[ 'base' ], $this->default ); |
746 * |
400 } |
747 * @since 3.4.0 |
401 |
748 * @since 4.6.0 Added the `$this` setting instance as the second parameter. |
402 // Handle non-array value |
749 * |
403 if ( empty( $this->id_data[ 'keys' ] ) ) |
750 * @param mixed $default The setting default value. Default empty. |
404 return $function( $this->id_data[ 'base' ], $this->default ); |
751 * @param WP_Customize_Setting $this The setting instance. |
405 |
752 */ |
406 // Handle array-based value |
753 $value = apply_filters( "customize_value_{$id_base}", $value, $this ); |
407 $values = $function( $this->id_data[ 'base' ] ); |
754 } elseif ( $this->is_multidimensional_aggregated ) { |
408 return $this->multidimensional_get( $values, $this->id_data[ 'keys' ], $this->default ); |
755 $root_value = self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value']; |
|
756 $value = $this->multidimensional_get( $root_value, $this->id_data['keys'], $this->default ); |
|
757 |
|
758 // 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 ) { |
|
760 $value = $this->post_value( $value ); |
|
761 } |
|
762 } else { |
|
763 $value = $this->get_root_value( $this->default ); |
|
764 } |
|
765 return $value; |
409 } |
766 } |
410 |
767 |
411 /** |
768 /** |
412 * Sanitize the setting's value for use in JavaScript. |
769 * Sanitize the setting's value for use in JavaScript. |
413 * |
770 * |