Completed
Push — milestone/2_0/react-ui ( 80ec67...0e2508 )
by
unknown
02:43
created

Field::factory()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 29
Code Lines 18

Duplication

Lines 4
Ratio 13.79 %

Code Coverage

Tests 13
CRAP Score 4.5039

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 4
eloc 18
c 1
b 0
f 1
nc 4
nop 3
dl 4
loc 29
ccs 13
cts 19
cp 0.6842
crap 4.5039
rs 8.5806
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 14
	public static function factory( $raw_type, $name, $label = null ) {
226 14
		$type = Helper::normalize_type( $raw_type );
227
228
		// stop hidden symbol support when the end user is creating fields ]-[
229
		// @see Field::set_name()
1 ignored issue
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
230 14
		$regex = '/\A[a-z0-9_]+\z/';
231 14
		if ( ! preg_match( $regex, $name ) ) {
232 4
			Incorrect_Syntax_Exception::raise( 'Field name can only contain lowercase alphanumeric characters and underscores ("' . $name . '" passed).' );
233
			return;
234
		}
235
236 10
		if ( Carbon_Fields::has( $type, 'fields' ) ) {
237
			return Carbon_Fields::resolve_with_arguments( $type, array(
238
				'type' => $type,
239
				'name' => $name,
240
				'label' => $label,
241
			), 'fields' );
242
		}
243
244
		// Fallback to class name-based resolution
245 10
		$class = Helper::type_to_class( $type, __NAMESPACE__, '_Field' );
246 10 View Code Duplication
		if ( ! class_exists( $class ) ) {
247 4
			Incorrect_Syntax_Exception::raise( 'Unknown field "' . $raw_type . '".' );
248 1
			$class = __NAMESPACE__ . '\\Broken_Field';
249 1
		}
250
251 7
		$field = new $class( $type, $name, $label );
252 7
		return $field;
253
	}
254
255
	/**
256
	 * An alias of factory().
257
	 *
258
	 * @see Field::factory()
259
	 * @return Field
260
	 */
261 14
	public static function make( $type, $name, $label = null ) {
262 14
		return static::factory( $type, $name, $label );
263
	}
264
265
	/**
266
	 * Create a field from a certain type with the specified label.
267
	 * 
268
	 * @param string $type  Field type
269
	 * @param string $name  Field name
270
	 * @param string $label Field label
271
	 */
272 7
	public function __construct( $type, $name, $label ) {
273 7
		Carbon_Fields::verify_boot();
274
		
275 7
		$this->type = $type;
276 7
		$this->set_base_name( $name );
277 7
		$this->set_name( $name );
278 7
		$this->set_label( $label );
279
280
		// Pick random ID
281 7
		$random_string = md5( mt_rand() . $this->get_name() . $this->get_label() );
282 7
		$random_string = substr( $random_string, 0, 5 ); // 5 chars should be enough
283 7
		$this->id = 'carbon-' . $random_string;
284
285 7
		$this->init();
286 7
	}
287
288
	/**
289
	 * Returns the type of the field based on the class.
290
	 * The class is stripped by the "CarbonFields" prefix.
291
	 * Also the "Field" suffix is removed.
292
	 * Then underscores and backslashes are removed.
293
	 *
294
	 * @return string
295
	 */
296
	public function get_type() {
297
		return Helper::class_to_type( get_class( $this ), '_Field' );
298
	}
299
300
	/**
301
	 * Activate the field once the container is attached.
302
	 */
303
	public function activate() {
304
		$this->admin_init();
305
306
		add_action( 'admin_print_footer_scripts', array( get_class(), 'admin_hook_scripts' ), 5 );
307
		add_action( 'admin_print_footer_scripts', array( get_class(), 'admin_hook_styles' ), 5 );
308
		static::activate_field_type( get_class( $this ) );
309
310
		do_action( 'carbon_fields_field_activated', $this );
311
	}
312
313
	/**
314
	 * Activate a field type
315
	 * 
316
	 * @param string $class_name
317
	 */
318
	public static function activate_field_type( $class_name ) {
319
		if ( in_array( $class_name, static::$activated_field_types ) ) {
320
			return;
321
		}
322
323
		add_action( 'admin_print_footer_scripts', array( $class_name, 'admin_enqueue_scripts' ), 5 );
324
		call_user_func( array( $class_name, 'field_type_activated' ) );
325
326
		static::$activated_field_types[] = $class_name;
327
	}
328
329
	/**
330
	 * Prepare the field type for use
331
	 * Called once per field type when activated
332
	 */
333
	public static function field_type_activated() {}
334
335
	/**
336
	 * Get array of hierarchy field names
337
	 *
338
	 * @return array
339
	 */
340
	public function get_hierarchy() {
341
		return $this->hierarchy;
342
	}
343
344
	/**
345
	 * Set array of hierarchy field names
346
	 */
347
	public function set_hierarchy( $hierarchy ) {
348
		$this->hierarchy = $hierarchy;
349
	}
350
351
	/**
352
	 * Get array of hierarchy indexes
353
	 *
354
	 * @return array
355
	 */
356
	public function get_hierarchy_index() {
357
		return $this->hierarchy_index;
358
	}
359
360
	/**
361
	 * Set array of hierarchy indexes
362
	 */
363
	public function set_hierarchy_index( $hierarchy_index ) {
364
		$hierarchy_index = ( ! empty( $hierarchy_index ) ) ? $hierarchy_index : array();
365
		$this->hierarchy_index = $hierarchy_index;
366
	}
367
368
	/**
369
	 * Return whether the field is a root field and holds a single value
370
	 *
371
	 * @return bool
372
	 */
373 3
	public function is_simple_root_field() {
374 3
		$hierarchy = $this->get_hierarchy();
375
		return (
376 3
			empty( $hierarchy )
377 3
			&&
378 2
			in_array( $this->get_value_set()->get_type(), array( Value_Set::TYPE_SINGLE_VALUE, Value_Set::TYPE_MULTIPLE_PROPERTIES ) )
379 3
		);
380
	}
381
382
	/**
383
	 * Perform instance initialization
384
	 */
385
	public function init() {}
386
387
	/**
388
	 * Instance initialization when in the admin area
389
	 * Called during field boot
390
	 */
391
	public function admin_init() {}
392
393
	/**
394
	 * Enqueue scripts and styles in admin
395
	 * Called once per field type
396
	 */
397
	public static function admin_enqueue_scripts() {}
398
399
	/**
400
	 * Get value from datastore
401
	 *
402
	 * @param bool $fallback_to_default
403
	 * @return mixed
404
	 */
405
	protected function get_value_from_datastore( $fallback_to_default = true ) {
406
		$value = $this->get_datastore()->load( $this );
407
408
		if ( $value === null && $fallback_to_default ) {
409
			$value = $this->get_default_value();
410
		}
411
412
		return $value;
413
	}
414
415
	/**
416
	 * Load value from datastore
417
	 */
418 2
	public function load() {
419 2
		$this->set_value( $this->get_value_from_datastore() );
420 2
	}
421
422
	/**
423
	 * Save value to storage
424
	 */
425 5
	public function save() {
426 5
		$delete_on_save = ! in_array( $this->get_value_set()->get_type(), array( Value_Set::TYPE_SINGLE_VALUE, Value_Set::TYPE_MULTIPLE_PROPERTIES ) );
427 5
		$delete_on_save = apply_filters( 'carbon_fields_should_delete_field_value_on_save', $delete_on_save, $this );
428 5
		if ( $delete_on_save ) {
429 2
			$this->delete();
430 2
		}
431
432 5
		$save = apply_filters( 'carbon_fields_should_save_field_value', true, $this->get_value(), $this );
433 5
		if ( $save ) {
434 5
			$this->get_datastore()->save( $this );
435 5
		}
436 5
	}
437
438
	/**
439
	 * Delete value from storage
440
	 */
441 1
	public function delete() {
442 1
		$this->get_datastore()->delete( $this );
443 1
	}
444
445
	/**
446
	 * Load the field value from an input array based on it's name
447
	 *
448
	 * @param array $input Array of field names and values.
449
	 */
450 2
	public function set_value_from_input( $input ) {
451 2
		if ( isset( $input[ $this->get_name() ] ) ) {
452 1
			$this->set_value( $input[ $this->get_name() ] );
453 1
		} else {
454 1
			$this->clear_value();
455
		}
456 2
	}
457
458
	/**
459
	 * Return whether the datastore instance is the default one or has been overriden
460
	 *
461
	 * @return boolean
462
	 */
463
	public function has_default_datastore() {
464
		return $this->has_default_datastore;
465
	}
466
467
	/**
468
	 * Get the DataStore instance
469
	 *
470
	 * @return Datastore_Interface $datastore
471
	 */
472 1
	public function get_datastore() {
473 1
		return $this->datastore;
474
	}
475
476
	/**
477
	 * Set datastore instance
478
	 *
479
	 * @param  Datastore_Interface $datastore
480
	 * @param  boolean             $set_as_default
481
	 * @return object              $this
482
	 */
483 1
	public function set_datastore( Datastore_Interface $datastore, $set_as_default = false ) {
484 1
		if ( $set_as_default && ! $this->has_default_datastore() ) {
485
			return $this; // datastore has been overriden with a custom one - abort changing to a default one
486
		}
487 1
		$this->datastore = $datastore;
488 1
		$this->has_default_datastore = $set_as_default;
489 1
		return $this;
490
	}
491
492
	/**
493
	 * Return the type of the container this field is in
494
	 *
495
	 * @return string
496
	 */
497
	public function get_context() {
498
		return $this->context;
499
	}
500
501
	/**
502
	 * Assign the type of the container this field is in
503
	 *
504
	 * @param string
505
	 * @return object $this
506
	 */
507
	public function set_context( $context ) {
508
		$this->context = $context;
509
		return $this;
510
	}
511
512
	/**
513
	 * Get the Value_Set object
514
	 *
515
	 * @return Value_Set
516
	 */
517 2
	public function get_value_set() {
518 2
		if ( $this->value_set === null ) {
519 1
			$this->set_value_set( new Value_Set() );
520 1
		}
521 2
		return $this->value_set;
522
	}
523
524
	/**
525
	 * Set the Value_Set object
526
	 *
527
	 * @param Value_Set $value_set
528
	 */
529 1
	public function set_value_set( $value_set ) {
530 1
		$this->value_set = $value_set;
531 1
	}
532
533
	/**
534
	 * Alias for $this->get_value_set()->get(); with fallback to default value
535
	 *
536
	 * @return mixed
537
	 */
538 3
	public function get_value() {
539 3
		if ( $this->get_value_set()->get() === null ) {
540 1
			$this->set_value( $this->get_default_value() );
541 1
		}
542 3
		return $this->get_value_set()->get();
543
	}
544
545
	/**
546
	 * Alias for $this->get_value_set()->get_set(); with fallback to default value
547
	 *
548
	 * @return array<array>
549
	 */
550
	public function get_full_value() {
551
		if ( $this->get_value_set()->get_set() === null ) {
552
			$this->set_value( $this->get_default_value() );
553
		}
554
		return $this->get_value_set()->get_set();
555
	}
556
557
	/**
558
	 * Return a differently formatted value for end-users
559
	 *
560
	 * @return mixed
561
	 */
562 2
	public function get_formatted_value() {
563 2
		return $this->get_value();
564
	}
565
566
	/**
567
	 * Alias for $this->get_value_set()->set( $value );
568
	 */
569 1
	public function set_value( $value ) {
570 1
		$this->get_value_set()->set( $value );
571 1
	}
572
573
	/**
574
	 * Clear the field value to a blank one (but not the default one)
575
	 */
576
	public function clear_value() {
577
		$this->get_value_set()->set( array() );
578
	}
579
580
	/**
581
	 * Get default field value
582
	 *
583
	 * @return mixed
584
	 */
585 1
	public function get_default_value() {
586 1
		return $this->default_value;
587
	}
588
589
	/**
590
	 * Set default field value
591
	 *
592
	 * @param mixed $default_value
593
	 */
594 1
	public function set_default_value( $default_value ) {
595 1
		$this->default_value = $default_value;
596 1
		return $this;
597
	}
598
599
	/**
600
	 * Return the field base name.
601
	 *
602
	 * @return string
603
	 */
604
	public function get_base_name() {
605
		return $this->base_name;
606
	}
607
608
	/**
609
	 * Set field base name as defined in the container.
610
	 */
611
	public function set_base_name( $name ) {
612
		$this->base_name = $name;
613
	}
614
615
	/**
616
	 * Return the field name
617
	 *
618
	 * @return string
619
	 */
620 2
	public function get_name() {
621 2
		return $this->name;
622
	}
623
624
	/**
625
	 * Set field name.
626
	 * Use only if you are completely aware of what you are doing.
627
	 *
628
	 * @param string $name Field name, either sanitized or not
629
	 */
630 2
	public function set_name( $name ) {
631 2
		if ( empty( $name ) ) {
632
			Incorrect_Syntax_Exception::raise( 'Field name can\'t be empty' );
633
			return;
634
		}
635
636
		// symbols ]-[ are supported in a hidden way - required for widgets to work (WP imposes dashes and square brackets on field names)
637 2
		$regex = '/\A[a-z0-9_\-\[\]]+\z/';
638 2
		if ( ! preg_match( $regex, $name ) ) {
639
			Incorrect_Syntax_Exception::raise( 'Field name can only contain lowercase alphanumeric characters and underscores ("' . $name . '" passed).' );
640
			return;
641
		}
642
643 2
		$name_prefix = $this->get_name_prefix();
644 2
		$name = ( substr( $name, 0, strlen( $name_prefix ) ) !== $name_prefix ? $name_prefix . $name : $name );
645
646 2
		$this->name = $name;
647 2
	}
648
649
	/**
650
	 * Return the field name prefix
651
	 *
652
	 * @return string
653
	 */
654 3
	public function get_name_prefix() {
655 3
		return $this->name_prefix;
656
	}
657
658
	/**
659
	 * Set field name prefix
660
	 * Use only if you are completely aware of what you are doing.
661
	 *
662
	 * @param string $name_prefix
663
	 */
664 3
	public function set_name_prefix( $name_prefix ) {
665 3
		$name_prefix = strval( $name_prefix );
666 3
		$old_prefix_length = strlen( $this->name_prefix );
667 3
		$this->name_prefix = '';
668 3
		$this->set_name( substr( $this->get_name(), $old_prefix_length ) );
669
670 3
		$this->name_prefix = $name_prefix;
671 3
		$this->set_name( $this->name_prefix . $this->get_name() );
672 3
	}
673
674
	/**
675
	 * Return field label.
676
	 *
677
	 * @return string
678
	 */
679
	public function get_label() {
680
		return $this->label;
681
	}
682
683
	/**
684
	 * Set field label.
685
	 *
686
	 * @param string $label If null, the label will be generated from the field name
687
	 */
688
	public function set_label( $label ) {
689
		if ( is_null( $label ) ) {
690
			// Try to guess field label from it's name
691
			$label = Helper::normalize_label( $this->get_name() );
692
		}
693
694
		$this->label = $label;
695
	}
696
697
	/**
698
	 * Get a key-value array of attributes
699
	 * 
700
	 * @return array
701
	 */
702
	public function get_attributes() {
703
		return $this->attributes;
704
	}
705
706
	/**
707
	 * Get an attribute value
708
	 * 
709
	 * @param  string $name
710
	 * @return string
711
	 */
712
	public function get_attribute( $name ) {
713
		return isset( $this->attributes[ $name ] ) ? $this->attributes[ $name ] : '';
714
	}
715
716
	/**
717
	 * Set an attribute and it's value
718
	 * 
719
	 * @param  string $name
720
	 * @param  string $value
721
	 * @return Field  $this
722
	 */
723
	public function set_attribute( $name, $value = '' ) {
724
		if ( ! in_array( $name, $this->allowed_attributes ) ) {
725
			Incorrect_Syntax_Exception::raise( 'Only the following attributes are allowed: ' . implode( ', ', $this->allowed_attributes ) . '.' );
726
			return $this;
727
		}
728
		$this->attributes[ $name ] = $value;
729
		return $this;
730
	}
731
732
	/**
733
	 * Return the field help text
734
	 *
735
	 * @return object $this
736
	 */
737
	public function get_help_text() {
738
		return $this->help_text;
739
	}
740
741
	/**
742
	 * Set additional text to be displayed during field render,
743
	 * containing information and guidance for the user
744
	 *
745
	 * @return object $this
746
	 */
747
	public function set_help_text( $help_text ) {
748
		$this->help_text = $help_text;
749
		return $this;
750
	}
751
752
	/**
753
	 * Alias for set_help_text()
754
	 *
755
	 * @see set_help_text()
756
	 * @return object $this
757
	 */
758
	public function help_text( $help_text ) {
759
		return $this->set_help_text( $help_text );
760
	}
761
762
	/**
763
	 * Return whether or not this value should be auto loaded.
764
	 *
765
	 * @return bool
766
	 */
767
	public function get_autoload() {
768
		return $this->autoload;
769
	}
770
771
	/**
772
	 * Whether or not this value should be auto loaded. Applicable to theme options only.
773
	 *
774
	 * @param bool $autoload
775
	 * @return object $this
776
	 */
777
	public function set_autoload( $autoload ) {
778
		$this->autoload = $autoload;
779
		return $this;
780
	}
781
782
	/**
783
	 * Return whether or not this field should be lazyloaded.
784
	 *
785
	 * @return bool
786
	 */
787
	public function get_lazyload() {
788
		return $this->lazyload;
789
	}
790
791
	/**
792
	 * Whether or not this field will be initialized when the field is in the viewport (visible).
793
	 *
794
	 * @param bool $lazyload
795
	 * @return object $this
796
	 */
797
	public function set_lazyload( $lazyload ) {
798
		$this->lazyload = $lazyload;
799
		return $this;
800
	}
801
802
	/**
803
	 * Get the field width.
804
	 *
805
	 * @return int $width
806
	 */
807
	public function get_width() {
808
		return $this->width;
809
	}
810
811
	/**
812
	 * Set the field width.
813
	 *
814
	 * @param int $width
815
	 * @return object $this
816
	 */
817
	public function set_width( $width ) {
818
		$this->width = (int) $width;
819
		return $this;
820
	}
821
822
	/**
823
	 * Get custom CSS classes.
824
	 *
825
	 * @return array<string>
826
	 */
827
	public function get_classes() {
828
		return $this->classes;
829
	}
830
831
	/**
832
	 * Set CSS classes that the container should use.
833
	 *
834
	 * @param string|array<string> $classes
835
	 * @return object $this
836
	 */
837
	public function set_classes( $classes ) {
838
		$this->classes = Helper::sanitize_classes( $classes );
839
		return $this;
840
	}
841
842
	/**
843
	 * Whether this field is mandatory for the user
844
	 *
845
	 * @param bool $required
846
	 * @return object $this
847
	 */
848
	public function set_required( $required = true ) {
849
		$this->required = $required;
850
		return $this;
851
	}
852
853
	/**
854
	 * Return whether this field is mandatory for the user
855
	 *
856
	 * @return bool
857
	 */
858
	public function is_required() {
859
		return $this->required;
860
	}
861
862
	/**
863
	 * HTML id attribute getter.
864
	 * @return string
865
	 */
866 1
	public function get_id() {
867 1
		return $this->id;
868
	}
869
870
	/**
871
	 * HTML id attribute setter
872
	 * @param string $id
873
	 */
874 1
	public function set_id( $id ) {
875 1
		$this->id = $id;
876 1
	}
877
878
	/**
879
	 * Set the field visibility conditional logic.
880
	 *
881
	 * @param array
882
	 */
883 8
	public function set_conditional_logic( $rules ) {
884 8
		$this->conditional_logic = $this->parse_conditional_rules( $rules );
885 3
		return $this;
886
	}
887
888
	/**
889
	 * Get the conditional logic rules
890
	 *
891
	 * @return array
892
	 */
893 3
	public function get_conditional_logic() {
894 3
		return $this->conditional_logic;
895
	}
896
897
	/**
898
	 * Validate and parse the conditional logic rules.
899
	 *
900
	 * @param array $rules
901
	 * @return array
902
	 */
903
	protected function parse_conditional_rules( $rules ) {
904
		if ( ! is_array( $rules ) ) {
905
			Incorrect_Syntax_Exception::raise( 'Conditional logic rules argument should be an array.' );
906
			return array();
907
		}
908
909
		$allowed_operators = array( '=', '!=', '>', '>=', '<', '<=', 'IN', 'NOT IN', 'INCLUDES', 'EXCLUDES' );
910
911
		$parsed_rules = array(
912
			'relation' => Helper::get_relation_type_from_array( $rules ),
913
			'rules' => array(),
914
		);
915
916
		foreach ( $rules as $key => $rule ) {
917
			if ( $key === 'relation' ) {
918
				continue; // Skip the relation key as it is already handled above
919
			}
920
921
			// Check if the rule is valid
922
			if ( ! is_array( $rule ) || empty( $rule['field'] ) ) {
923
				Incorrect_Syntax_Exception::raise( 'Invalid conditional logic rule format. The rule should be an array with the "field" key set.' );
924
				return array();
925
			}
926
927
			// Fill in optional keys with defaults
928
			$rule = array_merge( array(
929
				'compare' => '=',
930
				'value' => '',
931
			), $rule );
932
933 View Code Duplication
			if ( ! in_array( $rule['compare'], $allowed_operators ) ) {
934
				Incorrect_Syntax_Exception::raise( 'Invalid conditional logic compare operator: <code>' . $rule['compare'] . '</code><br>Allowed operators are: <code>' .
935
				implode( ', ', $allowed_operators ) . '</code>' );
936
				return array();
937
			}
938
939
			if ( in_array( $rule['compare'], array( 'IN', 'NOT IN' ) ) && ! is_array( $rule['value'] ) ) {
940
				Incorrect_Syntax_Exception::raise( 'Invalid conditional logic value format. An array is expected, when using the "' . $rule['compare'] . '" operator.' );
941
				return array();
942
			}
943
944
			$parsed_rules['rules'][] = $rule;
945
		}
946
947
		return $parsed_rules;
948
	}
949
950
	/**
951
	 * Set the REST visibility of the field
952
	 * 
953
	 * @param bool $visible
954
	 */
955
	public function set_visible_in_rest_api( $visible ) {
956
		$this->visible_in_rest_api = $visible;
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
	 * Configuration function for setting the field visibility in the response of the requests to the REST API
970
	 * 
971
	 * @param bool $visible
972
	 * @return Field $this
973
	 */
974
	public function show_in_rest( $visible = true ) {
975
		$this->set_visible_in_rest_api( $visible );
976
		return $this;
977
	}
978
979
	/**
980
	 * Returns an array that holds the field data, suitable for JSON representation.
981
	 *
982
	 * @param bool $load  Should the value be loaded from the database or use the value from the current instance.
983
	 * @return array
984
	 */
985
	public function to_json( $load ) {
986
		if ( $load ) {
987
			$this->load();
988
		}
989
990
		$field_data = array(
991
			'id' => $this->get_id(),
992
			'type' => $this->get_type(),
993
			'label' => $this->get_label(),
994
			'name' => $this->get_name(),
995
			'base_name' => $this->get_base_name(),
996
			'value' => $this->get_formatted_value(),
997
			'default_value' => $this->get_default_value(),
998
			'attributes' => (object) $this->get_attributes(),
999
			'help_text' => $this->get_help_text(),
1000
			'context' => $this->get_context(),
1001
			'required' => $this->is_required(),
1002
			'lazyload' => $this->get_lazyload(),
1003
			'width' => $this->get_width(),
1004
			'classes' => $this->get_classes(),
1005
			'conditional_logic' => $this->get_conditional_logic(),
1006
		);
1007
1008
		return $field_data;
1009
	}
1010
1011
	/**
1012
	 * Hook administration scripts.
1013
	 */
1014
	public static function admin_hook_scripts() {
1015
		wp_enqueue_media();
1016
	}
1017
1018
	/**
1019
	 * Hook administration styles.
1020
	 */
1021
	public static function admin_hook_styles() {
1022
		wp_enqueue_style( 'thickbox' );
1023
	}
1024
}
1025