Completed
Push — develop ( 98d5c5...e59683 )
by Aristeides
07:08 queued 04:29
created

Kirki_Field::set_disable_output()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Creates and validates field parameters.
4
 *
5
 * @package     Kirki
6
 * @category    Core
7
 * @author      Aristeides Stathopoulos
8
 * @copyright   Copyright (c) 2017, Aristeides Stathopoulos
9
 * @license     http://opensource.org/licenses/https://opensource.org/licenses/MIT
10
 * @since       1.0
11
 */
12
13
/**
14
 * Please do not use this class directly.
15
 * You should instead extend it per-field-type.
16
 */
17
class Kirki_Field {
18
19
	/**
20
	 * An array of the field arguments.
21
	 *
22
	 * @access protected
23
	 * @var array
24
	 */
25
	protected $args = array();
26
27
	/**
28
	 * The ID of the kirki_config we're using.
29
	 *
30
	 * @see Kirki_Config
31
	 * @access protected
32
	 * @var string
33
	 */
34
	protected $kirki_config = 'global';
35
36
	/**
37
	 * Thje capability required so that users can edit this field.
38
	 *
39
	 * @access protected
40
	 * @var string
41
	 */
42
	protected $capability = 'edit_theme_options';
43
44
	/**
45
	 * If we're using options instead of theme_mods
46
	 * and we want them serialized, this is the option that
47
	 * will saved in the db.
48
	 *
49
	 * @access protected
50
	 * @var string
51
	 */
52
	protected $option_name = '';
53
54
	/**
55
	 * Custom input attributes (defined as an array).
56
	 *
57
	 * @access protected
58
	 * @var array
59
	 */
60
	protected $input_attrs = array();
61
62
	/**
63
	 * Use "theme_mod" or "option".
64
	 *
65
	 * @access protected
66
	 * @var string
67
	 */
68
	protected $option_type = 'theme_mod';
69
70
	/**
71
	 * The name of this setting (id for the db).
72
	 *
73
	 * @access protected
74
	 * @var string|array
75
	 */
76
	protected $settings = '';
77
78
	/**
79
	 * Set to true if you want to disable all CSS output for this field.
80
	 *
81
	 * @access protected
82
	 * @var bool
83
	 */
84
	protected $disable_output = false;
85
86
	/**
87
	 * The field type.
88
	 *
89
	 * @access protected
90
	 * @var string
91
	 */
92
	protected $type = 'kirki-generic';
93
94
	/**
95
	 * Some fields require options to be set.
96
	 * We're whitelisting the property here
97
	 * and suggest you validate this in a child class.
98
	 *
99
	 * @access protected
100
	 * @var array
101
	 */
102
	protected $choices = array();
103
104
	/**
105
	 * Assign this field to a section.
106
	 * Fields not assigned to a section will not be displayed in the customizer.
107
	 *
108
	 * @access protected
109
	 * @var string
110
	 */
111
	protected $section = '';
112
113
	/**
114
	 * The default value for this field.
115
	 *
116
	 * @access protected
117
	 * @var string|array|bool
118
	 */
119
	protected $default = '';
120
121
	/**
122
	 * Priority determines the position of a control inside a section.
123
	 * Lower priority numbers move the control to the top.
124
	 *
125
	 * @access protected
126
	 * @var int
127
	 */
128
	protected $priority = 10;
129
130
	/**
131
	 * Unique ID for this field.
132
	 * This is auto-calculated from the $settings argument.
133
	 *
134
	 * @access protected
135
	 * @var string
136
	 */
137
	protected $id = '';
138
139
	/**
140
	 * Use if you want to automatically generate CSS from this field's value.
141
	 *
142
	 * @see https://kirki.org/docs/arguments/output
143
	 * @access protected
144
	 * @var array
145
	 */
146
	protected $output = array();
147
148
	/**
149
	 * Use to automatically generate postMessage scripts.
150
	 * Not necessary to use if you use 'transport' => 'auto'
151
	 * and have already set an array for the 'output' argument.
152
	 *
153
	 * @see https://kirki.org/docs/arguments/js_vars
154
	 * @access protected
155
	 * @var array
156
	 */
157
	protected $js_vars = array();
158
159
	/**
160
	 * If you want to use a CSS compiler, then use this to set the variable names.
161
	 *
162
	 * @see https://kirki.org/docs/arguments/variables
163
	 * @access protected
164
	 * @var array
165
	 */
166
	protected $variables = array();
167
168
	/**
169
	 * Text that will be used in a tooltip to provide extra info for this field.
170
	 *
171
	 * @access protected
172
	 * @var string
173
	 */
174
	protected $tooltip = '';
175
176
	/**
177
	 * A custom callback to determine if the field should be visible or not.
178
	 *
179
	 * @access protected
180
	 * @var string|array
181
	 */
182
	protected $active_callback = '__return_true';
183
184
	/**
185
	 * A custom sanitize callback that will be used to properly save the values.
186
	 *
187
	 * @access protected
188
	 * @var string|array
189
	 */
190
	protected $sanitize_callback = '';
191
192
	/**
193
	 * Use 'refresh', 'postMessage' or 'auto'.
194
	 * 'auto' will automatically geberate any 'js_vars' from the 'output' argument.
195
	 *
196
	 * @access protected
197
	 * @var string
198
	 */
199
	protected $transport = 'refresh';
200
201
	/**
202
	 * Define dependencies to show/hide this field based on the values of other fields.
203
	 *
204
	 * @access protected
205
	 * @var array
206
	 */
207
	protected $required = array();
208
209
	/**
210
	 * Partial Refreshes array.
211
	 *
212
	 * @access protected
213
	 * @var array
214
	 */
215
	protected $partial_refresh = array();
216
217
	/**
218
	 * The class constructor.
219
	 * Parses and sanitizes all field arguments.
220
	 * Then it adds the field to Kirki::$fields.
221
	 *
222
	 * @access public
223
	 * @param string $config_id    The ID of the config we want to use.
224
	 *                             Defaults to "global".
225
	 *                             Configs are handled by the Kirki_Config class.
226
	 * @param array  $args         The arguments of the field.
227
	 */
228
	public function __construct( $config_id = 'global', $args = array() ) {
229
230
		if ( isset( $args['setting'] ) && ! empty( $args['setting'] ) && ( ! isset( $args['settings'] ) || empty( $args['settings'] ) ) ) {
231
			/* translators: %s represents the field ID where the error occurs. */
232
			_doing_it_wrong( __METHOD__, sprintf( esc_attr__( 'Typo found in field %s - setting instead of settings.', 'kirki' ), esc_attr( $args['settings'] ) ), '3.0.10' );
233
			$args['settings'] = $args['setting'];
234
			unset( $args['setting'] );
235
		}
236
237
		// In case the user only provides 1 argument,
238
		// assume that the provided argument is $args and set $config_id = 'global'.
239
		if ( is_array( $config_id ) && empty( $args ) ) {
240
			/* translators: %1$s represents the field ID where the error occurs. %2$s is the URL in the documentation site. */
241
			_doing_it_wrong( __METHOD__, sprintf( esc_attr__( 'Config not defined for field %1$s - See %2$s for details on how to properly add fields.', 'kirki' ), esc_attr( $args['settings'] ), 'https://aristath.github.io/kirki/docs/getting-started/fields.html' ), '3.0.10' );
242
			$args = $config_id;
243
			$config_id = 'global';
244
		}
245
246
		$args['kirki_config'] = $config_id;
247
248
		$this->kirki_config = trim( esc_attr( $config_id ) );
249
		if ( '' === $config_id ) {
250
			/* translators: %1$s represents the field ID where the error occurs. %2$s is the URL in the documentation site. */
251
			_doing_it_wrong( __METHOD__, sprintf( esc_attr__( 'Config not defined for field %1$s - See %2$s for details on how to properly add fields.', 'kirki' ), esc_attr( $args['settings'] ), 'https://aristath.github.io/kirki/docs/getting-started/fields.html' ), '3.0.10' );
252
			$this->kirki_config = 'global';
253
		}
254
255
		// Get defaults from the class.
256
		$defaults = get_class_vars( __CLASS__ );
257
258
		// Get the config arguments, and merge them with the defaults.
259
		$config_defaults = ( isset( Kirki::$config['global'] ) ) ? Kirki::$config['global'] : array();
260
		if ( 'global' !== $this->kirki_config && isset( Kirki::$config[ $this->kirki_config ] ) ) {
261
			$config_defaults = Kirki::$config[ $this->kirki_config ];
262
		}
263
		$config_defaults = ( is_array( $config_defaults ) ) ? $config_defaults : array();
264
		foreach ( $config_defaults as $key => $value ) {
265
			if ( isset( $defaults[ $key ] ) && ! empty( $value ) && $value != $defaults[ $key ] ) {
266
				$defaults[ $key ] = $value;
267
			}
268
		}
269
270
		// Merge our args with the defaults.
271
		$args = wp_parse_args( $args, $defaults );
272
273
		// Set the class properties using the parsed args.
274
		foreach ( $args as $key => $value ) {
275
			$this->$key = $value;
276
		}
277
278
		$this->args = $args;
279
280
		$this->set_field();
281
	}
282
283
	/**
284
	 * Processes the field arguments
285
	 *
286
	 * @access protected
287
	 */
288
	protected function set_field() {
289
290
		$properties = get_class_vars( __CLASS__ );
291
292
		// Some things must run before the others.
293
		$this->set_option_name();
294
		$this->set_option_type();
295
		$this->set_settings();
296
297
		// Sanitize the properties, skipping the ones that have already run above.
298
		foreach ( $properties as $property => $value ) {
299
			if ( in_array( $property, array( 'option_name', 'option_type', 'settings' ), true ) ) {
300
				continue;
301
			}
302
			$property_class_name = $this->get_property_classname( $property );
303
			if ( class_exists( $property_class_name ) ) {
304
				$property_obj   = new $property_class_name( $this->args );
305
				$this->property = $property_obj->get_property();
0 ignored issues
show
Bug introduced by
The property property does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
306
			}
307
			if ( method_exists( $this, 'set_' . $property ) ) {
308
				$method_name = 'set_' . $property;
309
				$this->$method_name();
310
			}
311
		}
312
313
		// Get all arguments with their values.
314
		$args = get_object_vars( $this );
315
		foreach ( $args as $key => $default_value ) {
316
			$args[ $key ] = $this->$key;
317
		}
318
319
		// Add the field to the static $fields variable properly indexed.
320
		Kirki::$fields[ $this->settings ] = $args;
321
322
	}
323
324
	/**
325
	 * Gets the classname from a property.
326
	 *
327
	 * @access private
328
	 * @since 3.0.10
329
	 * @param string $property The property.
330
	 * @return string          A classname derived from the property.
331
	 */
332
	private function get_property_classname( $property ) {
333
		$property_parts = (array) $property;
334
		if ( false !== strpos( $property, '-' ) ) {
335
			$property_parts = explode( '-', $property );
336
		}
337
		if ( false !== strpos( $property, '_' ) ) {
338
			$property_parts = explode( '_', $property );
339
		}
340
		foreach ( $property_parts as $property_part_k => $property_part_v ) {
341
			$property_parts[ $property_part_k ] = ucfirst( $property_part_v );
342
		}
343
		return 'Kirki_Field_Property_' . implode( '_', $property_parts );
344
	}
345
346
	/**
347
	 * Escape $kirki_config.
348
	 *
349
	 * @access protected
350
	 */
351
	protected function set_kirki_config() {
352
353
		$this->kirki_config = esc_attr( $this->kirki_config );
354
	}
355
356
	/**
357
	 * Escape $option_name.
358
	 *
359
	 * @access protected
360
	 */
361
	protected function set_option_name() {
362
363
		$this->option_name = esc_attr( $this->option_name );
364
	}
365
366
	/**
367
	 * Escape the $section.
368
	 *
369
	 * @access protected
370
	 */
371
	protected function set_section() {
372
373
		$this->section = sanitize_key( $this->section );
374
	}
375
376
	/**
377
	 * Escape the $section.
378
	 *
379
	 * @access protected
380
	 */
381
	protected function set_input_attrs() {
382
383
		if ( ! is_array( $this->input_attrs ) ) {
384
			$this->input_attrs = array();
385
		}
386
	}
387
388
	/**
389
	 * Checks the capability chosen is valid.
390
	 * If not, then falls back to 'edit_theme_options'
391
	 *
392
	 * @access protected
393
	 */
394
	protected function set_capability() {
395
396
		// Early exit if we're using 'edit_theme_options'.
397
		if ( 'edit_theme_options' === $this->capability ) {
398
			return;
399
		}
400
		// Escape & trim the capability.
401
		$this->capability = trim( esc_attr( $this->capability ) );
402
	}
403
404
	/**
405
	 * Make sure we're using the correct option_type
406
	 *
407
	 * @access protected
408
	 */
409
	protected function set_option_type() {
410
411
		// Take care of common typos.
412
		if ( 'options' === $this->option_type ) {
413
			$this->option_type = 'option';
414
		}
415
		// Take care of common typos.
416
		if ( 'theme_mods' === $this->option_type ) {
417
			/* translators: %1$s represents the field ID where the error occurs. */
418
			_doing_it_wrong( __METHOD__, sprintf( esc_attr__( 'Typo found in field %s - "theme_mods" vs "theme_mod"', 'kirki' ), esc_attr( $this->settings ) ), '3.0.10' );
419
			$this->option_type = 'theme_mod';
420
		}
421
	}
422
423
	/**
424
	 * Modifications for partial refreshes.
425
	 *
426
	 * @access protected
427
	 */
428
	protected function set_partial_refresh() {
429
430
		if ( ! is_array( $this->partial_refresh ) ) {
431
			$this->partial_refresh = array();
432
		}
433
		foreach ( $this->partial_refresh as $id => $args ) {
434
			if ( ! is_array( $args ) || ! isset( $args['selector'] ) || ! isset( $args['render_callback'] ) || ! is_callable( $args['render_callback'] ) ) {
435
				/* translators: %1$s represents the field ID where the error occurs. */
436
				_doing_it_wrong( __METHOD__, sprintf( esc_attr__( '"partial_refresh" invalid entry in field %s', 'kirki' ), esc_attr( $this->settings ) ), '3.0.10' );
437
				unset( $this->partial_refresh[ $id ] );
438
				continue;
439
			}
440
		}
441
		if ( ! empty( $this->partial_refresh ) ) {
442
			$this->transport = 'postMessage';
443
		}
444
	}
445
446
	/**
447
	 * Sets the settings.
448
	 * If we're using serialized options it makes sure that settings are properly formatted.
449
	 * We'll also be escaping all setting names here for consistency.
450
	 *
451
	 * @access protected
452
	 */
453
	protected function set_settings() {
454
455
		// If settings is not an array, temporarily convert it to an array.
456
		// This is just to allow us to process everything the same way and avoid code duplication.
457
		// if settings is not an array then it will not be set as an array in the end.
458
		if ( ! is_array( $this->settings ) ) {
459
			$this->settings = array(
460
				'kirki_placeholder_setting' => $this->settings,
461
			);
462
		}
463
		$settings = array();
464
		foreach ( $this->settings as $setting_key => $setting_value ) {
465
			$settings[ sanitize_key( $setting_key ) ] = esc_attr( $setting_value );
466
			// If we're using serialized options then we need to spice this up.
467
			if ( 'option' === $this->option_type && '' !== $this->option_name && ( false === strpos( $setting_key, '[' ) ) ) {
468
				$settings[ sanitize_key( $setting_key ) ] = esc_attr( $this->option_name ) . '[' . esc_attr( $setting_value ) . ']';
469
			}
470
		}
471
		$this->settings = $settings;
472
		if ( isset( $this->settings['kirki_placeholder_setting'] ) ) {
473
			$this->settings = $this->settings['kirki_placeholder_setting'];
474
		}
475
	}
476
477
	/**
478
	 * Escapes the tooltip messages.
479
	 *
480
	 * @access protected
481
	 */
482
	protected function set_tooltip() {
483
484
		if ( '' !== $this->tooltip ) {
485
			$this->tooltip = wp_strip_all_tags( $this->tooltip );
486
			return;
487
		}
488
	}
489
490
	/**
491
	 * Sets the active_callback
492
	 * If we're using the $required argument,
493
	 * Then this is where the switch is made to our evaluation method.
494
	 *
495
	 * @access protected
496
	 */
497
	protected function set_active_callback() {
498
499
		if ( is_array( $this->active_callback ) && ! is_callable( $this->active_callback ) ) {
500
			if ( isset( $this->active_callback[0] ) ) {
501
				$this->required = $this->active_callback;
502
			}
503
		}
504
505
		if ( ! empty( $this->required ) ) {
506
			$this->active_callback = array( 'Kirki_Active_Callback', 'evaluate' );
507
			return;
508
		}
509
		// No need to proceed any further if we're using the default value.
510
		if ( '__return_true' === $this->active_callback ) {
511
			return;
512
		}
513
		// Make sure the function is callable, otherwise fallback to __return_true.
514
		if ( ! is_callable( $this->active_callback ) ) {
515
			$this->active_callback = '__return_true';
516
		}
517
	}
518
519
	/**
520
	 * Sets the control type.
521
	 *
522
	 * @access protected
523
	 */
524
	protected function set_type() {
525
526
		// Escape the control type (it doesn't hurt to be sure).
527
		$this->type = esc_attr( $this->type );
528
	}
529
530
	/**
531
	 * Sets the $id.
532
	 * Setting the ID should happen after the 'settings' sanitization.
533
	 * This way we can also properly handle cases where the option_type is set to 'option'
534
	 * and we're using an array instead of individual options.
535
	 *
536
	 * @access protected
537
	 */
538
	protected function set_id() {
539
540
		$this->id = sanitize_key( str_replace( '[', '-', str_replace( ']', '', $this->settings ) ) );
541
	}
542
543
	/**
544
	 * Sets the $choices.
545
	 *
546
	 * @access protected
547
	 */
548
	protected function set_choices() {
549
550
		if ( ! is_array( $this->choices ) ) {
551
			$this->choices = array();
552
		}
553
	}
554
555
	/**
556
	 * Escapes the $disable_output.
557
	 *
558
	 * @access protected
559
	 */
560
	protected function set_disable_output() {
561
562
		$this->disable_output = (bool) $this->disable_output;
563
	}
564
565
	/**
566
	 * Sets the $js_vars
567
	 *
568
	 * @access protected
569
	 */
570
	protected function set_js_vars() {
571
572
		if ( ! is_array( $this->js_vars ) ) {
573
			$this->js_vars = array();
574
		}
575
576
		// Check if transport is set to auto.
577
		// If not, then skip the auto-calculations and exit early.
578
		if ( 'auto' !== $this->transport ) {
579
			return;
580
		}
581
582
		// Set transport to refresh initially.
583
		// Serves as a fallback in case we failt to auto-calculate js_vars.
584
		$this->transport = 'refresh';
585
586
		$js_vars = array();
587
588
		// Try to auto-generate js_vars.
589
		// First we need to check if js_vars are empty, and that output is not empty.
590
		if ( empty( $this->js_vars ) && ! empty( $this->output ) ) {
591
592
			// Start going through each item in the $output array.
593
			foreach ( $this->output as $output ) {
594
				$output['function'] = ( isset( $output['function'] ) ) ? $output['function'] : 'style';
595
596
				// If 'element' or 'property' are not defined, skip this.
597
				if ( ! isset( $output['element'] ) || ! isset( $output['property'] ) ) {
598
					continue;
599
				}
600
				if ( is_array( $output['element'] ) ) {
601
					$output['element'] = implode( ',', $output['element'] );
602
				}
603
604
				// If there's a sanitize_callback defined skip this, unless we also have a js_callback defined.
605
				if ( isset( $output['sanitize_callback'] ) && ! empty( $output['sanitize_callback'] ) && ! isset( $output['js_callback'] ) ) {
606
					continue;
607
				}
608
609
				// If we got this far, it's safe to add this.
610
				$js_vars[] = $output;
611
			}
612
613
			// Did we manage to get all the items from 'output'?
614
			// If not, then we're missing something so don't add this.
615
			if ( count( $js_vars ) !== count( $this->output ) ) {
616
				return;
617
			}
618
			$this->js_vars   = $js_vars;
619
			$this->transport = 'postMessage';
620
621
		}
622
	}
623
624
	/**
625
	 * Sets the $variables
626
	 *
627
	 * @access protected
628
	 */
629
	protected function set_variables() {
630
631
		if ( ! is_array( $this->variables ) ) {
632
			$variable = ( is_string( $this->variables ) && ! empty( $this->variables ) ) ? $this->variables : false;
633
			$this->variables = array();
634
			if ( $variable && empty( $this->variables ) ) {
635
				$this->variables[0]['name'] = $variable;
636
			}
637
		}
638
	}
639
640
	/**
641
	 * Sets the $transport
642
	 *
643
	 * @access protected
644
	 */
645
	protected function set_transport() {
646
647
		if ( 'postmessage' === trim( strtolower( $this->transport ) ) ) {
648
			$this->transport = 'postMessage';
649
		}
650
	}
651
652
	/**
653
	 * Sets the $required
654
	 *
655
	 * @access protected
656
	 */
657
	protected function set_required() {
658
659
		if ( ! is_array( $this->required ) ) {
660
			$this->required = array();
661
		}
662
	}
663
664
	/**
665
	 * Sets the $priority
666
	 *
667
	 * @access protected
668
	 */
669
	protected function set_priority() {
670
671
		$this->priority = absint( $this->priority );
672
	}
673
}
674