Completed
Push — milestone/2_0/react-ui ( 4cfe8b...e0c885 )
by
unknown
05:22
created

Field   F

Complexity

Total Complexity 101

Size/Duplication

Total Lines 998
Duplicated Lines 0.6 %

Coupling/Cohesion

Components 4
Dependencies 5

Test Coverage

Coverage 39.25%

Importance

Changes 9
Bugs 2 Features 1
Metric Value
c 9
b 2
f 1
dl 6
loc 998
ccs 115
cts 293
cp 0.3925
rs 1.263
wmc 101
lcom 4
cbo 5

69 Methods

Rating   Name   Duplication   Size   Complexity  
A load() 0 3 1
A save() 0 12 3
A delete() 0 3 1
A set_value_from_input() 0 7 2
A has_default_datastore() 0 3 1
A get_datastore() 0 3 1
A get_visible_in_rest_api() 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_hierarchy() 0 3 1
A get_hierarchy_index() 0 3 1
A set_hierarchy_index() 0 4 2
A is_simple_root_field() 0 8 2
A init() 0 1 1
A admin_init() 0 1 1
A admin_enqueue_scripts() 0 1 1
A get_value_from_datastore() 0 9 3
A set_datastore() 0 8 3
A get_context() 0 3 1
A set_context() 0 4 1
A get_value_set() 0 6 2
A set_value_set() 0 3 1
A get_value() 0 6 2
A get_full_value() 0 6 2
A get_formatted_value() 0 3 1
A set_value() 0 3 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 3 1
A get_name() 0 3 1
A get_name_prefix() 0 3 1
A set_name_prefix() 0 9 1
A get_label() 0 3 1
A set_label() 0 8 2
A get_attributes() 0 3 1
A get_attribute() 0 3 2
A set_attribute() 0 8 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_lazyload() 0 3 1
A set_lazyload() 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 3 1
A set_conditional_logic() 0 4 1
A get_conditional_logic() 0 3 1
C parse_conditional_rules() 6 46 9
B factory() 0 28 5
A set_name() 0 18 4
A set_visible_in_rest_api() 0 4 1
B to_json() 0 25 2
A admin_hook_scripts() 0 5 1
A admin_hook_styles() 0 3 1

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