Field   F
last analyzed

Complexity

Total Complexity 104

Size/Duplication

Total Lines 1047
Duplicated Lines 2.1 %

Coupling/Cohesion

Components 4
Dependencies 5

Test Coverage

Coverage 37.01%

Importance

Changes 0
Metric Value
dl 22
loc 1047
ccs 114
cts 308
cp 0.3701
rs 1.412
c 0
b 0
f 0
wmc 104
lcom 4
cbo 5

69 Methods

Rating   Name   Duplication   Size   Complexity  
A set_hierarchy() 0 4 1
A get_hierarchy_index() 0 3 1
A set_hierarchy_index() 0 5 2
A init() 0 1 1
A admin_init() 0 1 1
A admin_enqueue_scripts() 0 1 1
A load() 0 3 1
A delete() 0 3 1
A has_default_datastore() 0 3 1
A get_datastore() 0 3 1
A set_datastore() 0 8 3
A get_context() 0 3 1
A set_context() 0 4 1
A set_value_set() 0 4 1
A get_formatted_value() 0 3 1
A __clone() 0 3 1
A make() 0 3 1
A __construct() 0 15 1
A get_type() 0 3 1
A activate() 0 9 1
A activate_field_type() 0 10 2
A field_type_activated() 0 1 1
A get_hierarchy() 0 3 1
A set_value() 0 4 1
A clear_value() 0 3 1
A get_default_value() 0 3 1
A set_default_value() 0 4 1
A get_base_name() 0 3 1
A set_base_name() 0 4 1
A get_name() 0 3 1
A set_name() 0 20 4
A get_name_prefix() 0 3 1
A set_name_prefix() 0 10 1
A get_label() 0 3 1
A get_attributes() 0 3 1
A get_attribute() 0 3 2
A get_help_text() 0 3 1
A set_help_text() 0 4 1
A help_text() 0 3 1
A get_autoload() 0 3 1
A set_autoload() 0 4 1
A get_width() 0 3 1
A set_width() 0 4 1
A get_classes() 0 3 1
A set_classes() 0 4 1
A set_required() 0 4 1
A is_required() 0 3 1
A get_id() 0 3 1
A set_id() 0 4 1
A set_conditional_logic() 0 4 1
A get_conditional_logic() 0 3 1
A set_visible_in_rest_api() 0 4 1
A get_visible_in_rest_api() 0 3 1
A admin_hook_scripts() 0 5 1
A admin_hook_styles() 0 3 1
A factory() 4 28 4
A is_simple_root_field() 0 8 2
A get_value_from_datastore() 0 9 3
A save() 0 11 3
A set_value_from_input() 0 8 2
A get_value_set() 0 6 2
A get_value() 0 6 2
A get_full_value() 0 6 2
A set_label() 9 9 2
A set_attribute() 0 17 4
A set_attributes() 0 12 3
B parse_conditional_rule() 9 29 6
A parse_conditional_rules() 0 26 4
A to_json() 0 24 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Field often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Field, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Carbon_Fields\Field;
4
5
use Carbon_Fields\Carbon_Fields;
6
use Carbon_Fields\Datastore\Datastore_Interface;
7
use Carbon_Fields\Datastore\Datastore_Holder_Interface;
8
use Carbon_Fields\Value_Set\Value_Set;
9
use Carbon_Fields\Helper\Helper;
10
use Carbon_Fields\Exception\Incorrect_Syntax_Exception;
11
12
/**
13
 * Base field class.
14
 * Defines the key container methods and their default implementations.
15
 * Implements factory design pattern.
16
 */
17
class Field implements Datastore_Holder_Interface {
18
19
	/**
20
	 * Array of field class names that have had their activation method called
21
	 *
22
	 * @var array<string>
23
	 */
24
	protected static $activated_field_types = array();
25
26
	/**
27
	 * Globally unique field identificator. Generated randomly
28
	 *
29
	 * @var string
30
	 */
31
	protected $id;
32
33
	/**
34
	 * Stores the initial <kbd>$type</kbd> variable passed to the <code>factory()</code> method
35
	 *
36
	 * @see factory
37
	 * @var string
38
	 */
39
	public $type;
40
41
	/**
42
	 * Array of ancestor field names
43
	 *
44
	 * @var array
45
	 */
46
	protected $hierarchy = array();
47
48
	/**
49
	 * Array of complex entry ids
50
	 *
51
	 * @var array
52
	 */
53
	protected $hierarchy_index = array();
54
55
	/**
56
	 * Field value
57
	 *
58
	 * @var Value_Set
59
	 */
60
	protected $value_set;
61
62
	/**
63
	 * Default field value
64
	 *
65
	 * @var mixed
66
	 */
67
	protected $default_value = '';
68
69
	/**
70
	 * Sanitized field name used as input name attribute during field render
71
	 *
72
	 * @see factory()
73
	 * @see set_name()
74
	 * @var string
75
	 */
76
	protected $name;
77
78
	/**
79
	 * Field name prefix
80
	 *
81
	 * @see set_name()
82
	 * @var string
83
	 */
84
	protected $name_prefix = '_';
85
86
	/**
87
	 * The base field name which is used in the container.
88
	 *
89
	 * @see set_base_name()
90
	 * @var string
91
	 */
92
	protected $base_name;
93
94
	/**
95
	 * Field name used as label during field render
96
	 *
97
	 * @see factory()
98
	 * @see set_label()
99
	 * @var string
100
	 */
101
	protected $label;
102
103
	/**
104
	 * Additional text containing information and guidance for the user
105
	 *
106
	 * @see help_text()
107
	 * @var string
108
	 */
109
	protected $help_text;
110
111
	/**
112
	 * Field DataStore instance to which save, load and delete calls are delegated
113
	 *
114
	 * @see set_datastore()
115
	 * @see get_datastore()
116
	 * @var Datastore_Interface
117
	 */
118
	protected $datastore;
119
120
	/**
121
	 * Flag whether the datastore is the default one or replaced with a custom one
122
	 *
123
	 * @see set_datastore()
124
	 * @see get_datastore()
125
	 * @var boolean
126
	 */
127
	protected $has_default_datastore = true;
128
129
	/**
130
	 * The type of the container this field is in
131
	 *
132
	 * @see get_context()
133
	 * @var string
134
	 */
135
	protected $context;
136
137
	/**
138
	 * Whether or not this value should be auto loaded. Applicable to theme options only.
139
	 *
140
	 * @see set_autoload()
141
	 * @var bool
142
	 */
143
	protected $autoload = false;
144
145
	/**
146
	 * Key-value array of attribtues and their values
147
	 *
148
	 * @var array
149
	 */
150
	protected $attributes = array();
151
152
	/**
153
	 * Array of attributes the user is allowed to change
154
	 *
155
	 * @var array<string>
156
	 */
157
	protected $allowed_attributes = array();
158
159
	/**
160
	 * The width of the field.
161
	 *
162
	 * @see set_width()
163
	 * @var int
164
	 */
165
	protected $width = 0;
166
167
	/**
168
	 * Custom CSS classes.
169
	 *
170
	 * @see set_classes()
171
	 * @var array
172
	 */
173
	protected $classes = array();
174
175
	/**
176
	 * Whether or not this field is required.
177
	 *
178
	 * @see set_required()
179
	 * @var bool
180
	 */
181
	protected $required = false;
182
183
	/**
184
	 * Stores the field conditional logic rules.
185
	 *
186
	 * @var array
187
	 */
188
	protected $conditional_logic = array();
189
190
	/**
191
	 * Whether the field should be included in the response of the requests to the REST API
192
	 *
193
	 * @see  set_visible_in_rest_api
194
	 * @see  get_visible_in_rest_api
195
	 * @var boolean
196
	 */
197
	protected $visible_in_rest_api = false;
198
199
	/**
200
	 * Clone the Value_Set object as well
201
	 *
202
	 * @var array
203
	 */
204
	public function __clone() {
205
		$this->set_value_set( clone $this->get_value_set() );
206
	}
207
208
	/**
209
	 * Create a new field of type $raw_type and name $name and label $label.
210
	 *
211
	 * @param string $raw_type
212
	 * @param string $name lower case and underscore-delimited
213
	 * @param string $label (optional) Automatically generated from $name if not present
214
	 * @return Field
215
	 */
216 15
	public static function factory( $raw_type, $name, $label = null ) {
217 15
		$type = Helper::normalize_type( $raw_type );
218
219
		// stop hidden symbol support when the end user is creating fields ][
220
		// @see Field::set_name()
221 15
		if ( ! Helper::is_valid_entity_id( $name ) ) {
222 5
			Incorrect_Syntax_Exception::raise( 'Field names can only contain lowercase alphanumeric characters, dashes and underscores ("' . $name . '" passed).' );
223
			return null;
224
		}
225
226 10
		if ( Carbon_Fields::has( $type, 'fields' ) ) {
227
			return Carbon_Fields::resolve_with_arguments( $type, array(
228
				'type' => $type,
229
				'name' => $name,
230
				'label' => $label,
231
			), 'fields' );
232
		}
233
234
		// Fallback to class name-based resolution
235 10
		$class = Helper::type_to_class( $type, __NAMESPACE__, '_Field' );
236 10 View Code Duplication
		if ( ! class_exists( $class ) ) {
237 4
			Incorrect_Syntax_Exception::raise( 'Unknown field type "' . $raw_type . '".' );
238 1
			$class = __NAMESPACE__ . '\\Broken_Field';
239 1
		}
240
241 7
		$field = new $class( $type, $name, $label );
242 7
		return $field;
243
	}
244
245
	/**
246
	 * An alias of factory().
247
	 *
248
	 * @see    Field::factory()
249
	 * @return Field
250
	 */
251 15
	public static function make() {
252 15
		return call_user_func_array( array( get_class(), 'factory' ), func_get_args() );
253
	}
254
255
	/**
256
	 * Create a field from a certain type with the specified label.
257
	 *
258
	 * @param string $type  Field type
259
	 * @param string $name  Field name
260
	 * @param string $label Field label
261
	 */
262 7
	public function __construct( $type, $name, $label ) {
263 7
		Carbon_Fields::verify_boot();
264
265 7
		$this->type = $type;
266 7
		$this->set_base_name( $name );
267 7
		$this->set_name( $name );
268 7
		$this->set_label( $label );
269
270
		// Pick random ID
271 7
		$random_string = md5( mt_rand() . $this->get_name() . $this->get_label() );
272 7
		$random_string = substr( $random_string, 0, 5 ); // 5 chars should be enough
273 7
		$this->id = 'carbon-' . $random_string;
274
275 7
		$this->init();
276 7
	}
277
278
	/**
279
	 * Returns the type of the field based on the class.
280
	 * The class is stripped by the "CarbonFields" prefix.
281
	 * Also the "Field" suffix is removed.
282
	 * Then underscores and backslashes are removed.
283
	 *
284
	 * @return string
285
	 */
286
	public function get_type() {
287
		return Helper::class_to_type( get_class( $this ), '_Field' );
288
	}
289
290
	/**
291
	 * Activate the field once the container is attached.
292
	 */
293
	public function activate() {
294
		$this->admin_init();
295
296
		add_action( 'admin_print_footer_scripts', array( get_class(), 'admin_hook_scripts' ), 5 );
297
		add_action( 'admin_print_footer_scripts', array( get_class(), 'admin_hook_styles' ), 5 );
298
		static::activate_field_type( get_class( $this ) );
299
300
		do_action( 'carbon_fields_field_activated', $this );
301
	}
302
303
	/**
304
	 * Activate a field type
305
	 *
306
	 * @param string $class_name
307
	 */
308
	public static function activate_field_type( $class_name ) {
309
		if ( in_array( $class_name, static::$activated_field_types ) ) {
310
			return;
311
		}
312
313
		add_action( 'admin_print_footer_scripts', array( $class_name, 'admin_enqueue_scripts' ), 5 );
314
		call_user_func( array( $class_name, 'field_type_activated' ) );
315
316
		static::$activated_field_types[] = $class_name;
317
	}
318
319
	/**
320
	 * Prepare the field type for use
321
	 * Called once per field type when activated
322
	 */
323
	public static function field_type_activated() {}
324
325
	/**
326
	 * Get array of hierarchy field names
327
	 *
328
	 * @return array
329
	 */
330
	public function get_hierarchy() {
331
		return $this->hierarchy;
332
	}
333
334
	/**
335
	 * Set array of hierarchy field names
336
	 *
337
	 * @param array $hierarchy
338
	 * @return self  $this
339
	 */
340
	public function set_hierarchy( $hierarchy ) {
341
		$this->hierarchy = $hierarchy;
342
		return $this;
343
	}
344
345
	/**
346
	 * Get array of hierarchy indexes
347
	 *
348
	 * @return array
349
	 */
350
	public function get_hierarchy_index() {
351
		return $this->hierarchy_index;
352
	}
353
354
	/**
355
	 * Set array of hierarchy indexes
356
	 *
357
	 * @param array $hierarchy_index
358
	 * @return self  $this
359
	 */
360
	public function set_hierarchy_index( $hierarchy_index ) {
361
		$hierarchy_index = ( ! empty( $hierarchy_index ) ) ? $hierarchy_index : array();
362
		$this->hierarchy_index = $hierarchy_index;
363
		return $this;
364
	}
365
366
	/**
367
	 * Return whether the field is a root field and holds a single value
368
	 *
369
	 * @return bool
370
	 */
371 3
	public function is_simple_root_field() {
372 3
		$hierarchy = $this->get_hierarchy();
373
		return (
374 3
			empty( $hierarchy )
375 3
			&&
376 2
			in_array( $this->get_value_set()->get_type(), array( Value_Set::TYPE_SINGLE_VALUE, Value_Set::TYPE_MULTIPLE_PROPERTIES ) )
377 3
		);
378
	}
379
380
	/**
381
	 * Perform instance initialization
382
	 */
383
	public function init() {}
384
385
	/**
386
	 * Instance initialization when in the admin area
387
	 * Called during field boot
388
	 */
389
	public function admin_init() {}
390
391
	/**
392
	 * Enqueue scripts and styles in admin
393
	 * Called once per field type
394
	 */
395
	public static function admin_enqueue_scripts() {}
396
397
	/**
398
	 * Get value from datastore
399
	 *
400
	 * @param bool $fallback_to_default
401
	 * @return mixed
402
	 */
403
	protected function get_value_from_datastore( $fallback_to_default = true ) {
404
		$value = $this->get_datastore()->load( $this );
405
406
		if ( $value === null && $fallback_to_default ) {
407
			$value = $this->get_default_value();
408
		}
409
410
		return $value;
411
	}
412
413
	/**
414
	 * Load value from datastore
415
	 */
416 2
	public function load() {
417 2
		$this->set_value( $this->get_value_from_datastore() );
418 2
	}
419
420
	/**
421
	 * Save value to storage
422
	 */
423 1
	public function save() {
424 1
		$delete_on_save = apply_filters( 'carbon_fields_should_delete_field_value_on_save', true, $this );
425 1
		if ( $delete_on_save ) {
426 1
			$this->delete();
427 1
		}
428
429 1
		$save = apply_filters( 'carbon_fields_should_save_field_value', true, $this->get_value(), $this );
430 1
		if ( $save ) {
431 1
			$this->get_datastore()->save( apply_filters( 'carbon_fields_before_field_save', $this ) );
432 1
		}
433 1
	}
434
435
	/**
436
	 * Delete value from storage
437
	 */
438 1
	public function delete() {
439 1
		$this->get_datastore()->delete( apply_filters( 'carbon_fields_before_field_delete', $this ) );
440 1
	}
441
442
	/**
443
	 * Load the field value from an input array based on its name
444
	 *
445
	 * @param  array $input Array of field names and values.
446
	 * @return self  $this
447
	 */
448 2
	public function set_value_from_input( $input ) {
449 2
		if ( isset( $input[ $this->get_name() ] ) ) {
450 1
			$this->set_value( $input[ $this->get_name() ] );
451 1
		} else {
452 1
			$this->clear_value();
453
		}
454 2
		return $this;
455
	}
456
457
	/**
458
	 * Return whether the datastore instance is the default one or has been overriden
459
	 *
460
	 * @return boolean
461
	 */
462
	public function has_default_datastore() {
463
		return $this->has_default_datastore;
464
	}
465
466
	/**
467
	 * Get the DataStore instance
468
	 *
469
	 * @return Datastore_Interface $datastore
470
	 */
471 1
	public function get_datastore() {
472 1
		return $this->datastore;
473
	}
474
475
	/**
476
	 * Set datastore instance
477
	 *
478
	 * @param  Datastore_Interface $datastore
479
	 * @param  boolean             $set_as_default
480
	 * @return self                $this
481
	 */
482 1
	public function set_datastore( Datastore_Interface $datastore, $set_as_default = false ) {
483 1
		if ( $set_as_default && ! $this->has_default_datastore() ) {
484
			return $this; // datastore has been overriden with a custom one - abort changing to a default one
485
		}
486 1
		$this->datastore = $datastore;
487 1
		$this->has_default_datastore = $set_as_default;
488 1
		return $this;
489
	}
490
491
	/**
492
	 * Return the type of the container this field is in
493
	 *
494
	 * @return string
495
	 */
496
	public function get_context() {
497
		return $this->context;
498
	}
499
500
	/**
501
	 * Assign the type of the container this field is in
502
	 *
503
	 * @param  string $context
504
	 * @return self   $this
505
	 */
506
	public function set_context( $context ) {
507
		$this->context = $context;
508
		return $this;
509
	}
510
511
	/**
512
	 * Get the Value_Set object
513
	 *
514
	 * @return Value_Set
515
	 */
516 2
	public function get_value_set() {
517 2
		if ( $this->value_set === null ) {
518 1
			$this->set_value_set( new Value_Set() );
519 1
		}
520 2
		return $this->value_set;
521
	}
522
523
	/**
524
	 * Set the Value_Set object
525
	 *
526
	 * @param  Value_Set $value_set
527
	 * @return self      $this
528
	 */
529 1
	public function set_value_set( $value_set ) {
530 1
		$this->value_set = $value_set;
531 1
		return $this;
532
	}
533
534
	/**
535
	 * Alias for $this->get_value_set()->get(); with fallback to default value
536
	 *
537
	 * @return mixed
538
	 */
539 3
	public function get_value() {
540 3
		if ( $this->get_value_set()->get() === null ) {
541 1
			$this->set_value( $this->get_default_value() );
542 1
		}
543 3
		return $this->get_value_set()->get();
544
	}
545
546
	/**
547
	 * Alias for $this->get_value_set()->get_set(); with fallback to default value
548
	 *
549
	 * @return array<array>
550
	 */
551
	public function get_full_value() {
552
		if ( $this->get_value_set()->get_set() === null ) {
553
			$this->set_value( $this->get_default_value() );
554
		}
555
		return $this->get_value_set()->get_set();
556
	}
557
558
	/**
559
	 * Return a differently formatted value for end-users
560
	 *
561
	 * @return mixed
562
	 */
563 2
	public function get_formatted_value() {
564 2
		return $this->get_value();
565
	}
566
567
	/**
568
	 * Alias for $this->get_value_set()->set( $value );
569
	 *
570
	 * @param mixed $value
571
	 * @return self  $this
572
	 */
573 1
	public function set_value( $value ) {
574 1
		$this->get_value_set()->set( $value );
575 1
		return $this;
576
	}
577
578
	/**
579
	 * Clear the field value to a blank one (but not the default one)
580
	 */
581
	public function clear_value() {
582
		$this->get_value_set()->clear();
583
	}
584
585
	/**
586
	 * Get default field value
587
	 *
588
	 * @return mixed
589
	 */
590 1
	public function get_default_value() {
591 1
		return $this->default_value;
592
	}
593
594
	/**
595
	 * Set default field value
596
	 *
597
	 * @param  mixed $default_value
598
	 * @return $this
599
	 */
600 1
	public function set_default_value( $default_value ) {
601 1
		$this->default_value = $default_value;
602 1
		return $this;
603
	}
604
605
	/**
606
	 * Return the field base name.
607
	 *
608
	 * @return string
609
	 */
610
	public function get_base_name() {
611
		return $this->base_name;
612
	}
613
614
	/**
615
	 * Set field base name as defined in the container.
616
	 *
617
	 * @param string $name
618
	 * @return self  $this
619
	 */
620
	public function set_base_name( $name ) {
621
		$this->base_name = $name;
622
		return $this;
623
	}
624
625
	/**
626
	 * Return the field name
627
	 *
628
	 * @return string
629
	 */
630 2
	public function get_name() {
631 2
		return $this->name;
632
	}
633
634
	/**
635
	 * Set field name.
636
	 * Use only if you are completely aware of what you are doing.
637
	 *
638
	 * @param  string $name Field name, either sanitized or not
639
	 * @return self   $this
640
	 */
641 2
	public function set_name( $name ) {
642 2
		if ( empty( $name ) ) {
643
			Incorrect_Syntax_Exception::raise( 'Field name can\'t be empty' );
644
			return $this;
645
		}
646
647
		// symbols ][ are supported in a hidden way - required for widgets to work (WP imposes dashes and square brackets on field names)
648 2
		$field_name_characters = Helper::get_field_name_characters_pattern();
649 2
		$regex = '/\A[' . $field_name_characters . '\[\]]+\z/';
650 2
		if ( ! preg_match( $regex, $name ) ) {
651
			Incorrect_Syntax_Exception::raise( 'Field names  can only contain lowercase alphanumeric characters, dashes and underscores ("' . $name . '" passed).' );
652
			return $this;
653
		}
654
655 2
		$name_prefix = $this->get_name_prefix();
656 2
		$name = ( substr( $name, 0, strlen( $name_prefix ) ) !== $name_prefix ? $name_prefix . $name : $name );
657
658 2
		$this->name = $name;
659 2
		return $this;
660
	}
661
662
	/**
663
	 * Return the field name prefix
664
	 *
665
	 * @return string
666
	 */
667 3
	public function get_name_prefix() {
668 3
		return $this->name_prefix;
669
	}
670
671
	/**
672
	 * Set field name prefix
673
	 * Use only if you are completely aware of what you are doing.
674
	 *
675
	 * @param  string $name_prefix
676
	 * @return self   $this
677
	 */
678 3
	public function set_name_prefix( $name_prefix ) {
679 3
		$name_prefix = strval( $name_prefix );
680 3
		$old_prefix_length = strlen( $this->name_prefix );
681 3
		$this->name_prefix = '';
682 3
		$this->set_name( substr( $this->get_name(), $old_prefix_length ) );
683
684 3
		$this->name_prefix = $name_prefix;
685 3
		$this->set_name( $this->name_prefix . $this->get_name() );
686 3
		return $this;
687
	}
688
689
	/**
690
	 * Return field label.
691
	 *
692
	 * @return string
693
	 */
694
	public function get_label() {
695
		return $this->label;
696
	}
697
698
	/**
699
	 * Set field label.
700
	 *
701
	 * @param  string $label If null, the label will be generated from the field name
702
	 * @return self   $this
703
	 */
704 View Code Duplication
	public function set_label( $label ) {
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...
705
		if ( is_null( $label ) ) {
706
			// Try to guess field label from its name
707
			$label = Helper::normalize_label( $this->get_name() );
708
		}
709
710
		$this->label = $label;
711
		return $this;
712
	}
713
714
	/**
715
	 * Get a key-value array of attributes
716
	 *
717
	 * @return array
718
	 */
719
	public function get_attributes() {
720
		return $this->attributes;
721
	}
722
723
	/**
724
	 * Get an attribute value
725
	 *
726
	 * @param  string $name
727
	 * @return string
728
	 */
729
	public function get_attribute( $name ) {
730
		return isset( $this->attributes[ $name ] ) ? $this->attributes[ $name ] : '';
731
	}
732
733
	/**
734
	 * Set an attribute and its value
735
	 *
736
	 * @param  string $name
737
	 * @param  string $value
738
	 * @return self   $this
739
	 */
740
	public function set_attribute( $name, $value = '' ) {
741
		$is_data_attribute = substr( strtolower( $name ), 0, 5 ) === 'data-';
742
		if ( $is_data_attribute ) {
743
			$name = strtolower( $name );
744
			$name = preg_replace( '/[^a-z\-]/', '-', $name );
745
			$name = preg_replace( '/\-{2,}/', '-', $name );
746
			$name = preg_replace( '/^\-+|\-+$/', '', $name );
747
		}
748
749
		if ( ! $is_data_attribute && ! in_array( $name, $this->allowed_attributes ) ) {
750
			Incorrect_Syntax_Exception::raise( 'Only the following attributes are allowed: ' . implode( ', ', array_merge( $this->allowed_attributes, array( 'data-*' ) ) ) );
751
			return $this;
752
		}
753
754
		$this->attributes[ $name ] = $value;
755
		return $this;
756
	}
757
758
	/**
759
	 * Set a key=>value array of attributes
760
	 *
761
	 * @param  array $attributes
762
	 * @return self  $this
763
	 */
764
	public function set_attributes( $attributes ) {
765
		if ( ! is_array( $attributes ) ) {
766
			Incorrect_Syntax_Exception::raise( 'An array must be passed for the $attributes parameter of Field::set_attributes().' );
767
			return $this;
768
		}
769
770
		foreach ( $attributes as $name => $value ) {
771
			$this->set_attribute( $name, $value );
772
		}
773
774
		return $this;
775
	}
776
777
	/**
778
	 * Return the field help text
779
	 *
780
	 * @return string
781
	 */
782
	public function get_help_text() {
783
		return $this->help_text;
784
	}
785
786
	/**
787
	 * Set additional text to be displayed during field render,
788
	 * containing information and guidance for the user
789
	 *
790
	 * @param string $help_text
791
	 * @return self  $this
792
	 */
793
	public function set_help_text( $help_text ) {
794
		$this->help_text = $help_text;
795
		return $this;
796
	}
797
798
	/**
799
	 * Alias for set_help_text()
800
	 *
801
	 * @see set_help_text()
802
	 * @param string $help_text
803
	 * @return object $this
804
	 */
805
	public function help_text( $help_text ) {
806
		return $this->set_help_text( $help_text );
807
	}
808
809
	/**
810
	 * Return whether or not this value should be auto loaded.
811
	 *
812
	 * @return bool
813
	 */
814
	public function get_autoload() {
815
		return $this->autoload;
816
	}
817
818
	/**
819
	 * Whether or not this value should be auto loaded. Applicable to theme options only.
820
	 *
821
	 * @param  bool  $autoload
822
	 * @return self  $this
823
	 */
824
	public function set_autoload( $autoload ) {
825
		$this->autoload = $autoload;
826
		return $this;
827
	}
828
829
	/**
830
	 * Get the field width.
831
	 *
832
	 * @return int $width
833
	 */
834
	public function get_width() {
835
		return $this->width;
836
	}
837
838
	/**
839
	 * Set the field width.
840
	 *
841
	 * @param  int   $width
842
	 * @return self  $this
843
	 */
844
	public function set_width( $width ) {
845
		$this->width = (int) $width;
846
		return $this;
847
	}
848
849
	/**
850
	 * Get custom CSS classes.
851
	 *
852
	 * @return array<string>
853
	 */
854
	public function get_classes() {
855
		return $this->classes;
856
	}
857
858
	/**
859
	 * Set CSS classes that the container should use.
860
	 *
861
	 * @param  string|array<string> $classes
862
	 * @return self                 $this
863
	 */
864
	public function set_classes( $classes ) {
865
		$this->classes = Helper::sanitize_classes( $classes );
866
		return $this;
867
	}
868
869
	/**
870
	 * Whether this field is mandatory for the user
871
	 *
872
	 * @param  bool  $required
873
	 * @return self  $this
874
	 */
875
	public function set_required( $required = true ) {
876
		$this->required = $required;
877
		return $this;
878
	}
879
880
	/**
881
	 * Return whether this field is mandatory for the user
882
	 *
883
	 * @return bool
884
	 */
885
	public function is_required() {
886
		return $this->required;
887
	}
888
889
	/**
890
	 * HTML id attribute getter.
891
	 * @return string
892
	 */
893 1
	public function get_id() {
894 1
		return $this->id;
895
	}
896
897
	/**
898
	 * HTML id attribute setter
899
	 *
900
	 * @param  string $id
901
	 * @return self   $this
902
	 */
903 1
	public function set_id( $id ) {
904 1
		$this->id = $id;
905 1
		return $this;
906
	}
907
908
	/**
909
	 * Set the field visibility conditional logic.
910
	 *
911
	 * @param  array
912
	 * @return self  $this
913
	 */
914 8
	public function set_conditional_logic( $rules ) {
915 8
		$this->conditional_logic = $this->parse_conditional_rules( $rules );
916 3
		return $this;
917
	}
918
919
	/**
920
	 * Get the conditional logic rules
921
	 *
922
	 * @return array
923
	 */
924 3
	public function get_conditional_logic() {
925 3
		return $this->conditional_logic;
926
	}
927
928
	/**
929
	 * Validate and parse a conditional logic rule.
930
	 *
931
	 * @param  array $rule
932
	 * @return array
933
	 */
934
	protected function parse_conditional_rule( $rule ) {
935
		$allowed_operators = array( '=', '!=', '>', '>=', '<', '<=', 'IN', 'NOT IN', 'INCLUDES', 'EXCLUDES' );
936
		$array_operators = array( 'IN', 'NOT IN' );
937
938
		// Check if the rule is valid
939
		if ( ! is_array( $rule ) || empty( $rule['field'] ) ) {
940
			Incorrect_Syntax_Exception::raise( 'Invalid conditional logic rule format. The rule should be an array with the "field" key set.' );
941
			return null;
942
		}
943
944
		// Fill in optional keys with defaults
945
		$rule = array_merge( array(
946
			'compare' => '=',
947
			'value' => '',
948
		), $rule );
949
950 View Code Duplication
		if ( ! in_array( $rule['compare'], $allowed_operators ) ) {
951
			Incorrect_Syntax_Exception::raise( 'Invalid conditional logic compare operator: <code>' . $rule['compare'] . '</code><br>Allowed operators are: <code>' .
952
			implode( ', ', $allowed_operators ) . '</code>' );
953
			return null;
954
		}
955
956 View Code Duplication
		if ( in_array( $rule['compare'], $array_operators ) && ! is_array( $rule['value'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
957
			Incorrect_Syntax_Exception::raise( 'Invalid conditional logic value format. An array is expected, when using the "' . $rule['compare'] . '" operator.' );
958
			return null;
959
		}
960
961
		return $rule;
962
	}
963
964
	/**
965
	 * Validate and parse conditional logic rules.
966
	 *
967
	 * @param  array $rules
968
	 * @return array
969
	 */
970
	protected function parse_conditional_rules( $rules ) {
971
		if ( ! is_array( $rules ) ) {
972
			Incorrect_Syntax_Exception::raise( 'Conditional logic rules argument should be an array.' );
973
			return array();
974
		}
975
976
		$parsed_rules = array(
977
			'relation' => Helper::get_relation_type_from_array( $rules ),
978
			'rules' => array(),
979
		);
980
981
		$rules_only = $rules;
982
		unset( $rules_only['relation'] ); // Skip the relation key as it is already handled above
983
984
		foreach ( $rules_only as $key => $rule ) {
985
			$rule = $this->parse_conditional_rule( $rule );
986
987
			if ( $rule === null ) {
988
				return array();
989
			}
990
991
			$parsed_rules['rules'][] = $rule;
992
		}
993
994
		return $parsed_rules;
995
	}
996
997
	/**
998
	 * Set the REST visibility of the field
999
	 *
1000
	 * @param  bool  $visible
1001
	 * @return self  $this
1002
	 */
1003
	public function set_visible_in_rest_api( $visible = true ) {
1004
		$this->visible_in_rest_api = $visible;
1005
		return $this;
1006
	}
1007
1008
	/**
1009
	 * Get the REST visibility of the field
1010
	 *
1011
	 * @return bool
1012
	 */
1013
	public function get_visible_in_rest_api() {
1014
		return $this->visible_in_rest_api;
1015
	}
1016
1017
	/**
1018
	 * Returns an array that holds the field data, suitable for JSON representation.
1019
	 *
1020
	 * @param bool $load  Should the value be loaded from the database or use the value from the current instance.
1021
	 * @return array
1022
	 */
1023
	public function to_json( $load ) {
1024
		if ( $load ) {
1025
			$this->load();
1026
		}
1027
1028
		$field_data = array(
1029
			'id' => $this->get_id(),
1030
			'type' => $this->get_type(),
1031
			'label' => $this->get_label(),
1032
			'name' => $this->get_name(),
1033
			'base_name' => $this->get_base_name(),
1034
			'value' => $this->get_formatted_value(),
1035
			'default_value' => $this->get_default_value(),
1036
			'attributes' => (object) $this->get_attributes(),
1037
			'help_text' => $this->get_help_text(),
1038
			'context' => $this->get_context(),
1039
			'required' => $this->is_required(),
1040
			'width' => $this->get_width(),
1041
			'classes' => $this->get_classes(),
1042
			'conditional_logic' => $this->get_conditional_logic(),
1043
		);
1044
1045
		return $field_data;
1046
	}
1047
1048
	/**
1049
	 * Hook administration scripts.
1050
	 */
1051
	public static function admin_hook_scripts() {
1052
		wp_enqueue_media();
1053
		wp_enqueue_script( 'thickbox' );
1054
		wp_enqueue_script( 'media-upload' );
1055
	}
1056
1057
	/**
1058
	 * Hook administration styles.
1059
	 */
1060
	public static function admin_hook_styles() {
1061
		wp_enqueue_style( 'thickbox' );
1062
	}
1063
}
1064