Completed
Push — master ( 4647f7...7bc7ee )
by Scott Kingsley
04:50
created

WP_Fields_API_Field::_update_meta()   B

Complexity

Conditions 4
Paths 6

Size

Total Lines 22
Code Lines 10

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 22
rs 8.9197
cc 4
eloc 10
nc 6
nop 3
1
<?php
2
/**
3
 * Fields API Field Class
4
 *
5
 * Handles saving and sanitizing of fields.
6
 *
7
 * @package    WordPress
8
 * @subpackage Fields_API
9
 */
10
class WP_Fields_API_Field {
11
12
	/**
13
	 * @access public
14
	 * @var string
15
	 */
16
	public $id = '';
17
18
	/**
19
	 * Object type.
20
	 *
21
	 * @access public
22
	 * @var string
23
	 */
24
	public $object_type = '';
25
26
	/**
27
	 * Object name (for post types and taxonomies).
28
	 *
29
	 * @access public
30
	 * @var string
31
	 */
32
	public $object_name = '';
33
34
	/**
35
	 * Capability required to edit this field.
36
	 *
37
	 * @var string
38
	 */
39
	public $capability = '';
40
41
	/**
42
	 * Theme feature support for the field.
43
	 *
44
	 * @access public
45
	 * @var string|array
46
	 */
47
	public $theme_supports = '';
48
49
	/**
50
	 * Default value for field
51
	 *
52
	 * @var string
53
	 */
54
	public $default = '';
55
56
	/**
57
	 * Server-side sanitization callback for the field's value.
58
	 *
59
	 * @var callback
60
	 */
61
	public $sanitize_callback    = '';
62
	public $sanitize_js_callback = '';
63
64
	protected $id_data = array();
65
66
	/**
67
	 * Constructor.
68
	 *
69
	 * Parameters are not set to maintain PHP overloading compatibility (strict standards)
70
	 *
71
	 * @return WP_Fields_API_Field $field
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
72
	 */
73
	public function __construct() {
74
75
		$args = func_get_args();
76
77
		call_user_func_array( array( $this, 'init' ), $args );
78
79
	}
80
81
	/**
82
	 * Secondary constructor; Any supplied $args override class property defaults.
83
	 *
84
	 * @param string $object_type   Object type.
85
	 * @param string $id            A specific ID of the field. Can be a
86
	 *                              theme mod or option name.
87
	 * @param array  $args          Field arguments.
88
	 *
89
	 * @return WP_Fields_API_Field $field
90
	 */
91
	public function init( $object_type, $id, $args = array() ) {
92
93
		$this->object_type = $object_type;
94
95
		if ( is_array( $id ) ) {
96
			$args = $id;
97
98
			$id = '';
0 ignored issues
show
Unused Code introduced by
$id is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
99
		} else {
100
			$this->id = $id;
101
		}
102
103
		$keys = array_keys( get_object_vars( $this ) );
104
105
		foreach ( $keys as $key ) {
106
			if ( isset( $args[ $key ] ) ) {
107
				$this->$key = $args[ $key ];
108
			}
109
		}
110
111
		// Parse the ID for array keys.
112
		$this->id_data['keys'] = preg_split( '/\[/', str_replace( ']', '', $this->id ) );
113
		$this->id_data['base'] = array_shift( $this->id_data['keys'] );
114
115
		// Rebuild the ID.
116
		$this->id = $this->id_data['base'];
117
118
		if ( ! empty( $this->id_data['keys'] ) ) {
119
			$this->id .= '[' . implode( '][', $this->id_data['keys'] ) . ']';
120
		}
121
122
		if ( $this->sanitize_callback ) {
123
			add_filter( "fields_sanitize_{$this->object_type}_{$this->object_name}_{$this->id}", $this->sanitize_callback, 10, 2 );
124
		}
125
126
		if ( $this->sanitize_js_callback ) {
127
			add_filter( "fields_sanitize_js_{$this->object_type}_{$this->object_name}_{$this->id}", $this->sanitize_js_callback, 10, 2 );
128
		}
129
130
	}
131
132
	/**
133
	 * Check user capabilities and theme supports, and then save
134
	 * the value of the field.
135
	 *
136
	 * @param mixed $value   The value to save.
0 ignored issues
show
Bug introduced by
There is no parameter named $value. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
137
	 * @param int   $item_id The Item ID.
0 ignored issues
show
Bug introduced by
There is no parameter named $item_id. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
138
	 *
139
	 * @return false|mixed False if cap check fails or value isn't set.
140
	 */
141
	public function save() {
142
143
		$value   = func_get_arg(0);
144
		$item_id = func_get_arg(1);
145
146
		if ( ! $this->check_capabilities() || false === $value ) {
147
			return false;
148
		}
149
150
		/**
151
		 * Fires when the WP_Fields_API_Field::save() method is called.
152
		 *
153
		 * The dynamic portion of the hook name, `$this->id_data['base']` refers to
154
		 * the base slug of the field name.
155
		 *
156
		 * @since 3.4.0
157
		 *
158
		 * @param WP_Fields_API_Field $this {@see WP_Fields_API_Field} instance.
159
		 */
160
		do_action( 'field_save_' . $this->object_type . ' _' . $this->id_data[ 'base' ], $this, $value, $item_id );
161
162
		return $this->update( $value, $item_id );
163
164
	}
165
166
	/**
167
	 * Sanitize an input.
168
	 *
169
	 * @param mixed $value The value to sanitize.
170
	 *
171
	 * @return mixed Null if an input isn't valid, otherwise the sanitized value.
172
	 */
173
	public function sanitize( $value ) {
174
175
		$value = wp_unslash( $value );
176
177
		/**
178
		 * Filter a Customize field value in un-slashed form.
179
		 *
180
		 * @param mixed                $value Value of the field.
181
		 * @param WP_Fields_API_Field $this  WP_Fields_API_Field instance.
182
		 */
183
		return apply_filters( "fields_sanitize_{$this->object_type}_{$this->object_name}_{$this->id}", $value, $this );
184
185
	}
186
187
	/**
188
	 * Save the value of the field, using the related API.
189
	 *
190
	 * @param mixed $value The value to update.
191
	 * @param int $item_id Item ID.
0 ignored issues
show
Bug introduced by
There is no parameter named $item_id. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
192
	 *
193
	 * @return mixed The result of saving the value.
194
	 */
195
	protected function update( $value ) {
196
197
		// @todo Support post / term / user / comment object field updates
198
199
		$item_id = func_get_arg(1);
200
201
		switch ( $this->object_type ) {
202
			case 'customizer' :
203
				return $this->_update_theme_mod( $value );
204
205
			case 'settings' :
206
				return $this->_update_option( $value );
207
208
			case 'post' :
209
			case 'term' :
210
			case 'user' :
211
			case 'comment' :
212
				return $this->_update_meta( $this->object_type, $value, $item_id );
213
214
			default :
215
216
				/**
217
				 * Fires when the {@see WP_Fields_API_Field::update()} method is called for fields
218
				 * not handled as theme_mods or options.
219
				 *
220
				 * The dynamic portion of the hook name, `$this->object_type`, refers to the type of field.
221
				 *
222
				 * @param mixed               $value   Value of the field.
223
				 * @param int                 $item_id Item ID.
224
				 * @param WP_Fields_API_Field $this    WP_Fields_API_Field instance.
225
				 */
226
				do_action( "fields_update_{$this->object_type}", $value, $item_id, $this );
227
		}
228
229
		return null;
230
231
	}
232
233
	/**
234
	 * Update the theme mod from the value of the parameter.
235
	 *
236
	 * @param mixed $value The value to update.
237
	 *
238
	 * @return null
239
	 */
240 View Code Duplication
	protected function _update_theme_mod( $value ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
241
242
		if ( is_null( $value ) ) {
243
			remove_theme_mod( $this->id_data['base'] );
244
		}
245
246
		// Handle non-array theme mod.
247
		if ( empty( $this->id_data['keys'] ) ) {
248
			set_theme_mod( $this->id_data['base'], $value );
249
		} else {
250
			// Handle array-based theme mod.
251
			$mods = get_theme_mod( $this->id_data['base'] );
252
			$mods = $this->multidimensional_replace( $mods, $this->id_data['keys'], $value );
253
254
			if ( isset( $mods ) ) {
255
				set_theme_mod( $this->id_data['base'], $mods );
256
			}
257
		}
258
259
		return null;
260
261
	}
262
263
	/**
264
	 * Update the option from the value of the field.
265
	 *
266
	 * @param mixed $value The value to update.
267
	 *
268
	 * @return bool|null The result of saving the value.
269
	 */
270 View Code Duplication
	protected function _update_option( $value ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
271
272
		if ( is_null( $value ) ) {
273
			delete_option( $this->id_data['base'] );
274
		}
275
276
		// Handle non-array option.
277
		if ( empty( $this->id_data['keys'] ) ) {
278
			return update_option( $this->id_data['base'], $value );
279
		}
280
281
		// Handle array-based options.
282
		$options = get_option( $this->id_data['base'] );
283
		$options = $this->multidimensional_replace( $options, $this->id_data['keys'], $value );
284
285
		if ( isset( $options ) ) {
286
			return update_option( $this->id_data['base'], $options );
287
		}
288
289
		return null;
290
291
	}
292
293
	/**
294
	 * Update the meta from the value of the field.
295
	 *
296
	 * @param string $meta_type The meta type.
297
	 * @param mixed  $value     The value to update.
298
	 * @param int    $item_id   Item ID.
299
	 *
300
	 * @return bool|null The result of saving the value.
301
	 */
302
	protected function _update_meta( $meta_type, $value, $item_id = 0 ) {
303
304
		if ( is_null( $value ) ) {
305
			delete_metadata( $meta_type, $item_id, $this->id_data['base'] );
306
		}
307
308
		// Handle non-array option.
309
		if ( empty( $this->id_data['keys'] ) ) {
310
			return update_metadata( $meta_type, $item_id, $this->id_data['base'], $value );
311
		}
312
313
		// Handle array-based keys.
314
		$keys = get_metadata( $meta_type, 0, $this->id_data['base'] );
315
		$keys = $this->multidimensional_replace( $keys, $this->id_data['keys'], $value );
316
317
		if ( isset( $keys ) ) {
318
			return update_metadata( $meta_type, $item_id, $this->id_data['base'], $keys );
319
		}
320
321
		return null;
322
323
	}
324
325
	/**
326
	 * Fetch the value of the field.
327
	 *
328
	 * @param int $item_id (optional) The Item ID.
0 ignored issues
show
Bug introduced by
There is no parameter named $item_id. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
329
	 *
330
	 * @return mixed The value.
331
	 */
332
	public function value() {
333
334
		$item_id = func_get_arg(0);
335
336
		switch ( $this->object_type ) {
337
			case 'post' :
338
			case 'term' :
339
			case 'user' :
340
			case 'comment' :
341
				$value = $this->get_object_value( $item_id );
342
				$value = $this->multidimensional_get( $value, $this->id_data['keys'], $this->default );
343
				break;
344
345
			case 'customizer' :
346
			case 'settings' :
347
				$value = $this->get_option_value();
348
				$value = $this->multidimensional_get( $value, $this->id_data['keys'], $this->default );
349
				break;
350
351
			default :
352
				/**
353
				 * Filter a field value for a custom object type.
354
				 *
355
				 * The dynamic portion of the hook name, `$this->id_date['base']`, refers to
356
				 * the base slug of the field name.
357
				 *
358
				 * For fields handled as theme_mods, options, or object fields, see those corresponding
359
				 * functions for available hooks.
360
				 *
361
				 * @param mixed $default The field default value. Default empty.
362
				 * @param int   $item_id (optional) The Item ID.
363
				 */
364
				$value = apply_filters( 'fields_value_' . $this->object_type . '_' . $this->object_name . '_' . $this->id_data['base'], $this->default, $item_id );
365
				break;
366
		}
367
368
		return $value;
369
370
	}
371
372
	/**
373
	 * Get value from meta / object
374
	 *
375
	 * @param int $item_id
376
	 *
377
	 * @return mixed|null
378
	 */
379
	public function get_object_value( $item_id ) {
380
381
		$value = null;
0 ignored issues
show
Unused Code introduced by
$value is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
382
		$object = null;
383
384
		$field_key = $this->id_data['base'];
385
386
		switch ( $this->object_type ) {
387
			case 'post' :
388
				$object = get_post( $item_id );
389
				break;
390
391
			case 'term' :
392
				$object = get_term( $item_id );
393
				break;
394
395
			case 'user' :
396
				$object = get_userdata( $item_id );
397
				break;
398
399
			case 'comment' :
400
				$object = get_comment( $item_id );
401
				break;
402
		}
403
404
		if ( $object && ! is_wp_error( $object ) && isset( $object->{$field_key} ) ) {
405
			// Get value from object
406
			$value = $object->{$field_key};
407
		} else {
408
			// Get value from meta
409
			$value = get_metadata( $this->object_type, $item_id, $field_key );
410
		}
411
412
		return $value;
413
414
	}
415
416
	/**
417
	 * Get value from option / theme_mod
418
	 *
419
	 * @return mixed|void
420
	 */
421
	public function get_option_value() {
422
423
		$function = '';
424
		$value = null;
425
426
		switch ( $this->object_type ) {
427
			case 'customizer' :
428
				$function = 'get_theme_mod';
429
				break;
430
431
			case 'settings' :
432
				$function = 'get_option';
433
				break;
434
		}
435
436
		if ( is_callable( $function ) ) {
437
			// Handle non-array value
438
			if ( empty( $this->id_data['keys'] ) ) {
439
				return $function( $this->id_data['base'], $this->default );
440
			}
441
442
			// Handle array-based value
443
			$value = $function( $this->id_data['base'] );
444
		}
445
446
		return $value;
447
448
	}
449
450
	/**
451
	 * Sanitize the field's value for use in JavaScript.
452
	 *
453
	 * @return mixed The requested escaped value.
454
	 */
455
	public function js_value() {
456
457
		$value = $this->value();
458
459
		/**
460
		 * Filter a Customize field value for use in JavaScript.
461
		 *
462
		 * The dynamic portion of the hook name, `$this->id`, refers to the field ID.
463
		 *
464
		 * @param mixed                $value The field value.
465
		 * @param WP_Fields_API_Field $this  {@see WP_Fields_API_Field} instance.
466
		 */
467
		$value = apply_filters( "fields_sanitize_js_{$this->object_type}_{$this->object_name}_{$this->id}", $value, $this );
468
469
		if ( is_string( $value ) ) {
470
			return html_entity_decode( $value, ENT_QUOTES, 'UTF-8' );
471
		}
472
473
		return $value;
474
475
	}
476
477
	/**
478
	 * Validate user capabilities whether the theme supports the field.
479
	 *
480
	 * @return bool False if theme doesn't support the section or user can't change section, otherwise true.
481
	 */
482 View Code Duplication
	public function check_capabilities() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
483
484
		if ( $this->capability && ! call_user_func_array( 'current_user_can', (array) $this->capability ) ) {
485
			return false;
486
		}
487
488
		if ( $this->theme_supports && ! call_user_func_array( 'current_theme_supports', (array) $this->theme_supports ) ) {
489
			return false;
490
		}
491
492
		return true;
493
494
	}
495
496
	/**
497
	 * Multidimensional helper function.
498
	 *
499
	 * @param      $root
500
	 * @param      $keys
501
	 * @param bool $create Default is false.
502
	 *
503
	 * @return null|array Keys are 'root', 'node', and 'key'.
504
	 */
505
	final protected function multidimensional( &$root, $keys, $create = false ) {
506
507
		if ( $create && empty( $root ) ) {
508
			$root = array();
509
		}
510
511
		if ( ! isset( $root ) || empty( $keys ) ) {
512
			return null;
513
		}
514
515
		$last = array_pop( $keys );
516
		$node = &$root;
517
518
		foreach ( $keys as $key ) {
519
			if ( $create && ! isset( $node[ $key ] ) ) {
520
				$node[ $key ] = array();
521
			}
522
523
			if ( ! is_array( $node ) || ! isset( $node[ $key ] ) ) {
524
				return null;
525
			}
526
527
			$node = &$node[ $key ];
528
		}
529
530
		if ( $create ) {
531
			if ( ! is_array( $node ) ) {
532
				// account for an array overriding a string or object value
533
				$node = array();
534
			}
535
			if ( ! isset( $node[ $last ] ) ) {
536
				$node[ $last ] = array();
537
			}
538
		}
539
540
		if ( ! isset( $node[ $last ] ) ) {
541
			return null;
542
		}
543
544
		return array(
545
			'root' => &$root,
546
			'node' => &$node,
547
			'key'  => $last,
548
		);
549
550
	}
551
552
	/**
553
	 * Will attempt to replace a specific value in a multidimensional array.
554
	 *
555
	 * @param       $root
556
	 * @param       $keys
557
	 * @param mixed $value The value to update.
558
	 *
559
	 * @return
560
	 */
561 View Code Duplication
	final protected function multidimensional_replace( $root, $keys, $value ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
562
563
		if ( ! isset( $value ) ) {
564
			return $root;
565
		} elseif ( empty( $keys ) ) {
566
			// If there are no keys, we're replacing the root.
567
			return $value;
568
		}
569
570
		$result = $this->multidimensional( $root, $keys, true );
571
572
		if ( isset( $result ) ) {
573
			$result['node'][ $result['key'] ] = $value;
574
		}
575
576
		return $root;
577
578
	}
579
580
	/**
581
	 * Will attempt to fetch a specific value from a multidimensional array.
582
	 *
583
	 * @param       $root
584
	 * @param       $keys
585
	 * @param mixed $default A default value which is used as a fallback. Default is null.
586
	 *
587
	 * @return mixed The requested value or the default value.
588
	 */
589 View Code Duplication
	final protected function multidimensional_get( $root, $keys, $default = null ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
590
591
		// If there are no keys, test the root.
592
		if ( empty( $keys ) ) {
593
			if ( isset( $root ) ) {
594
				return $root;
595
			}
596
		} else {
597
			$result = $this->multidimensional( $root, $keys );
598
599
			if ( isset( $result ) ) {
600
				return $result['node'][ $result['key'] ];
601
			}
602
		}
603
604
		return $default;
605
606
	}
607
608
	/**
609
	 * Will attempt to check if a specific value in a multidimensional array is set.
610
	 *
611
	 * @param $root
612
	 * @param $keys
613
	 *
614
	 * @return bool True if value is set, false if not.
615
	 */
616
	final protected function multidimensional_isset( $root, $keys ) {
617
618
		$result = $this->multidimensional_get( $root, $keys );
619
620
		return isset( $result );
621
622
	}
623
}
624
625
/**
626
 * A field that is used to filter a value, but will not save the results.
627
 *
628
 * Results should be properly handled using another field or callback.
629
 *
630
 * @package    WordPress
631
 * @subpackage Fields_API
632
 */
633
class WP_Fields_API_Filter_Field extends WP_Fields_API_Field {
634
635
	/**
636
	 * Save the value of the field, using the related API.
637
	 *
638
	 * @param mixed $value The value to update.
639
	 *
640
	 * @return mixed The result of saving the value.
641
	 */
642
	public function update( $value ) {
643
644
		return null;
645
646
	}
647
}