Completed
Push — master ( c1979b...86c6fc )
by Atanas
07:35 queued 03:56
created

Field::to_json()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 25
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 20
nc 2
nop 1
dl 0
loc 25
ccs 0
cts 21
cp 0
crap 6
rs 8.8571
c 0
b 0
f 0
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();
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
		$this->set_datastore( clone $this->get_datastore(), $this->has_default_datastore() );
216
	}
217
218
	/**
219
	 * Create a new field of type $raw_type and name $name and label $label.
220
	 *
221
	 * @param string $raw_type
222
	 * @param string $name lower case and underscore-delimited
223
	 * @param string $label (optional) Automatically generated from $name if not present
224
	 * @return Field
225
	 */
226 15
	public static function factory( $raw_type, $name, $label = null ) {
227 15
		$type = Helper::normalize_type( $raw_type );
228
229
		// stop hidden symbol support when the end user is creating fields ][
230
		// @see Field::set_name()
231 15
		if ( ! Helper::is_valid_entity_id( $name ) ) {
232 5
			Incorrect_Syntax_Exception::raise( 'Field names can only contain lowercase alphanumeric characters, dashes and underscores ("' . $name . '" passed).' );
233
			return null;
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 type "' . $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 15
	public static function make() {
262 15
		return call_user_func_array( array( get_class(), 'factory' ), func_get_args() );
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
	 * @return self  $this
348
	 */
349
	public function set_hierarchy( $hierarchy ) {
350
		$this->hierarchy = $hierarchy;
351
		return $this;
352
	}
353
354
	/**
355
	 * Get array of hierarchy indexes
356
	 *
357
	 * @return array
358
	 */
359
	public function get_hierarchy_index() {
360
		return $this->hierarchy_index;
361
	}
362
363
	/**
364
	 * Set array of hierarchy indexes
365
	 *
366
	 * @return self  $this
367
	 */
368
	public function set_hierarchy_index( $hierarchy_index ) {
369
		$hierarchy_index = ( ! empty( $hierarchy_index ) ) ? $hierarchy_index : array();
370
		$this->hierarchy_index = $hierarchy_index;
371
		return $this;
372
	}
373
374
	/**
375
	 * Return whether the field is a root field and holds a single value
376
	 *
377
	 * @return bool
378
	 */
379 3
	public function is_simple_root_field() {
380 3
		$hierarchy = $this->get_hierarchy();
381
		return (
382 3
			empty( $hierarchy )
383 3
			&&
384 2
			in_array( $this->get_value_set()->get_type(), array( Value_Set::TYPE_SINGLE_VALUE, Value_Set::TYPE_MULTIPLE_PROPERTIES ) )
385 3
		);
386
	}
387
388
	/**
389
	 * Perform instance initialization
390
	 */
391
	public function init() {}
392
393
	/**
394
	 * Instance initialization when in the admin area
395
	 * Called during field boot
396
	 */
397
	public function admin_init() {}
398
399
	/**
400
	 * Enqueue scripts and styles in admin
401
	 * Called once per field type
402
	 */
403
	public static function admin_enqueue_scripts() {}
404
405
	/**
406
	 * Get value from datastore
407
	 *
408
	 * @param bool $fallback_to_default
409
	 * @return mixed
410
	 */
411
	protected function get_value_from_datastore( $fallback_to_default = true ) {
412
		$value = $this->get_datastore()->load( $this );
413
414
		if ( $value === null && $fallback_to_default ) {
415
			$value = $this->get_default_value();
416
		}
417
418
		return $value;
419
	}
420
421
	/**
422
	 * Load value from datastore
423
	 */
424 2
	public function load() {
425 2
		$this->set_value( $this->get_value_from_datastore() );
426 2
	}
427
428
	/**
429
	 * Save value to storage
430
	 */
431 1
	public function save() {
432 1
		$delete_on_save = apply_filters( 'carbon_fields_should_delete_field_value_on_save', true, $this );
433 1
		if ( $delete_on_save ) {
434 1
			$this->delete();
435 1
		}
436
437 1
		$save = apply_filters( 'carbon_fields_should_save_field_value', true, $this->get_value(), $this );
438 1
		if ( $save ) {
439 1
			$this->get_datastore()->save( $this );
440 1
		}
441 1
	}
442
443
	/**
444
	 * Delete value from storage
445
	 */
446 1
	public function delete() {
447 1
		$this->get_datastore()->delete( $this );
448 1
	}
449
450
	/**
451
	 * Load the field value from an input array based on its name
452
	 *
453
	 * @param  array $input Array of field names and values.
454
	 * @return self  $this
455
	 */
456 2
	public function set_value_from_input( $input ) {
457 2
		if ( isset( $input[ $this->get_name() ] ) ) {
458 1
			$this->set_value( $input[ $this->get_name() ] );
459 1
		} else {
460 1
			$this->clear_value();
461
		}
462 2
		return $this;
463
	}
464
465
	/**
466
	 * Return whether the datastore instance is the default one or has been overriden
467
	 *
468
	 * @return boolean
469
	 */
470
	public function has_default_datastore() {
471
		return $this->has_default_datastore;
472
	}
473
474
	/**
475
	 * Get the DataStore instance
476
	 *
477
	 * @return Datastore_Interface $datastore
478
	 */
479 1
	public function get_datastore() {
480 1
		return $this->datastore;
481
	}
482
483
	/**
484
	 * Set datastore instance
485
	 *
486
	 * @param  Datastore_Interface $datastore
487
	 * @param  boolean             $set_as_default
488
	 * @return self                $this
489
	 */
490 1
	public function set_datastore( Datastore_Interface $datastore, $set_as_default = false ) {
491 1
		if ( $set_as_default && ! $this->has_default_datastore() ) {
492
			return $this; // datastore has been overriden with a custom one - abort changing to a default one
493
		}
494 1
		$this->datastore = $datastore;
495 1
		$this->has_default_datastore = $set_as_default;
496 1
		return $this;
497
	}
498
499
	/**
500
	 * Return the type of the container this field is in
501
	 *
502
	 * @return string
503
	 */
504
	public function get_context() {
505
		return $this->context;
506
	}
507
508
	/**
509
	 * Assign the type of the container this field is in
510
	 *
511
	 * @param  string $context
512
	 * @return self   $this
513
	 */
514
	public function set_context( $context ) {
515
		$this->context = $context;
516
		return $this;
517
	}
518
519
	/**
520
	 * Get the Value_Set object
521
	 *
522
	 * @return Value_Set
523
	 */
524 2
	public function get_value_set() {
525 2
		if ( $this->value_set === null ) {
526 1
			$this->set_value_set( new Value_Set() );
527 1
		}
528 2
		return $this->value_set;
529
	}
530
531
	/**
532
	 * Set the Value_Set object
533
	 *
534
	 * @param  Value_Set $value_set
535
	 * @return self      $this
536
	 */
537 1
	public function set_value_set( $value_set ) {
538 1
		$this->value_set = $value_set;
539 1
		return $this;
540
	}
541
542
	/**
543
	 * Alias for $this->get_value_set()->get(); with fallback to default value
544
	 *
545
	 * @return mixed
546
	 */
547 3
	public function get_value() {
548 3
		if ( $this->get_value_set()->get() === null ) {
549 1
			$this->set_value( $this->get_default_value() );
550 1
		}
551 3
		return $this->get_value_set()->get();
552
	}
553
554
	/**
555
	 * Alias for $this->get_value_set()->get_set(); with fallback to default value
556
	 *
557
	 * @return array<array>
558
	 */
559
	public function get_full_value() {
560
		if ( $this->get_value_set()->get_set() === null ) {
561
			$this->set_value( $this->get_default_value() );
562
		}
563
		return $this->get_value_set()->get_set();
564
	}
565
566
	/**
567
	 * Return a differently formatted value for end-users
568
	 *
569
	 * @return mixed
570
	 */
571 2
	public function get_formatted_value() {
572 2
		return $this->get_value();
573
	}
574
575
	/**
576
	 * Alias for $this->get_value_set()->set( $value );
577
	 */
578 1
	public function set_value( $value ) {
579 1
		$this->get_value_set()->set( $value );
580 1
		return $this;
581
	}
582
583
	/**
584
	 * Clear the field value to a blank one (but not the default one)
585
	 */
586
	public function clear_value() {
587
		$this->get_value_set()->clear();
588
	}
589
590
	/**
591
	 * Get default field value
592
	 *
593
	 * @return mixed
594
	 */
595 1
	public function get_default_value() {
596 1
		return $this->default_value;
597
	}
598
599
	/**
600
	 * Set default field value
601
	 *
602
	 * @param  mixed $default_value
603
	 * @return self  $this
604
	 */
605 1
	public function set_default_value( $default_value ) {
606 1
		$this->default_value = $default_value;
607 1
		return $this;
608
	}
609
610
	/**
611
	 * Return the field base name.
612
	 *
613
	 * @return string
614
	 */
615
	public function get_base_name() {
616
		return $this->base_name;
617
	}
618
619
	/**
620
	 * Set field base name as defined in the container.
621
	 *
622
	 * @return self  $this
623
	 */
624
	public function set_base_name( $name ) {
625
		$this->base_name = $name;
626
		return $this;
627
	}
628
629
	/**
630
	 * Return the field name
631
	 *
632
	 * @return string
633
	 */
634 2
	public function get_name() {
635 2
		return $this->name;
636
	}
637
638
	/**
639
	 * Set field name.
640
	 * Use only if you are completely aware of what you are doing.
641
	 *
642
	 * @param  string $name Field name, either sanitized or not
643
	 * @return self   $this
644
	 */
645 2
	public function set_name( $name ) {
646 2
		if ( empty( $name ) ) {
647
			Incorrect_Syntax_Exception::raise( 'Field name can\'t be empty' );
648
			return $this;
649
		}
650
651
		// symbols ][ are supported in a hidden way - required for widgets to work (WP imposes dashes and square brackets on field names)
652 2
		$field_name_characters = Helper::get_field_name_characters_pattern();
653 2
		$regex = '/\A[' . $field_name_characters . '\[\]]+\z/';
654 2
		if ( ! preg_match( $regex, $name ) ) {
655
			Incorrect_Syntax_Exception::raise( 'Field names  can only contain lowercase alphanumeric characters, dashes and underscores ("' . $name . '" passed).' );
656
			return $this;
657
		}
658
659 2
		$name_prefix = $this->get_name_prefix();
660 2
		$name = ( substr( $name, 0, strlen( $name_prefix ) ) !== $name_prefix ? $name_prefix . $name : $name );
661
662 2
		$this->name = $name;
663 2
		return $this;
664
	}
665
666
	/**
667
	 * Return the field name prefix
668
	 *
669
	 * @return string
670
	 */
671 3
	public function get_name_prefix() {
672 3
		return $this->name_prefix;
673
	}
674
675
	/**
676
	 * Set field name prefix
677
	 * Use only if you are completely aware of what you are doing.
678
	 *
679
	 * @param  string $name_prefix
680
	 * @return self   $this
681
	 */
682 3
	public function set_name_prefix( $name_prefix ) {
683 3
		$name_prefix = strval( $name_prefix );
684 3
		$old_prefix_length = strlen( $this->name_prefix );
685 3
		$this->name_prefix = '';
686 3
		$this->set_name( substr( $this->get_name(), $old_prefix_length ) );
687
688 3
		$this->name_prefix = $name_prefix;
689 3
		$this->set_name( $this->name_prefix . $this->get_name() );
690 3
		return $this;
691
	}
692
693
	/**
694
	 * Return field label.
695
	 *
696
	 * @return string
697
	 */
698
	public function get_label() {
699
		return $this->label;
700
	}
701
702
	/**
703
	 * Set field label.
704
	 *
705
	 * @param  string $label If null, the label will be generated from the field name
706
	 * @return self   $this
707
	 */
708 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...
709
		if ( is_null( $label ) ) {
710
			// Try to guess field label from its name
711
			$label = Helper::normalize_label( $this->get_name() );
712
		}
713
714
		$this->label = $label;
715
		return $this;
716
	}
717
718
	/**
719
	 * Get a key-value array of attributes
720
	 *
721
	 * @return array
722
	 */
723
	public function get_attributes() {
724
		return $this->attributes;
725
	}
726
727
	/**
728
	 * Get an attribute value
729
	 *
730
	 * @param  string $name
731
	 * @return string
732
	 */
733
	public function get_attribute( $name ) {
734
		return isset( $this->attributes[ $name ] ) ? $this->attributes[ $name ] : '';
735
	}
736
737
	/**
738
	 * Set an attribute and its value
739
	 *
740
	 * @param  string $name
741
	 * @param  string $value
742
	 * @return self   $this
743
	 */
744
	public function set_attribute( $name, $value = '' ) {
745
		$is_data_attribute = substr( strtolower( $name ), 0, 5 ) === 'data-';
746
		if ( $is_data_attribute ) {
747
			$name = strtolower( $name );
748
			$name = preg_replace( '/[^a-z\-]/', '-', $name );
749
			$name = preg_replace( '/\-{2,}/', '-', $name );
750
			$name = preg_replace( '/^\-+|\-+$/', '', $name );
751
		}
752
753
		if ( ! $is_data_attribute && ! in_array( $name, $this->allowed_attributes ) ) {
754
			Incorrect_Syntax_Exception::raise( 'Only the following attributes are allowed: ' . implode( ', ', array_merge( $this->allowed_attributes, array( 'data-*' ) ) ) );
755
			return $this;
756
		}
757
758
		$this->attributes[ $name ] = $value;
759
		return $this;
760
	}
761
762
	/**
763
	 * Set a key=>value array of attributes
764
	 *
765
	 * @param  array $attributes
766
	 * @return self  $this
767
	 */
768
	public function set_attributes( $attributes ) {
769
		if ( ! is_array( $attributes ) ) {
770
			Incorrect_Syntax_Exception::raise( 'An array must be passed for the $attributes parameter of Field::set_attributes().' );
771
			return $this;
772
		}
773
774
		foreach ( $attributes as $name => $value ) {
775
			$this->set_attribute( $name, $value );
776
		}
777
778
		return $this;
779
	}
780
781
	/**
782
	 * Return the field help text
783
	 *
784
	 * @return object $this
785
	 */
786
	public function get_help_text() {
787
		return $this->help_text;
788
	}
789
790
	/**
791
	 * Set additional text to be displayed during field render,
792
	 * containing information and guidance for the user
793
	 *
794
	 * @return self  $this
795
	 */
796
	public function set_help_text( $help_text ) {
797
		$this->help_text = $help_text;
798
		return $this;
799
	}
800
801
	/**
802
	 * Alias for set_help_text()
803
	 *
804
	 * @see set_help_text()
805
	 * @return object $this
806
	 */
807
	public function help_text( $help_text ) {
808
		return $this->set_help_text( $help_text );
809
	}
810
811
	/**
812
	 * Return whether or not this value should be auto loaded.
813
	 *
814
	 * @return bool
815
	 */
816
	public function get_autoload() {
817
		return $this->autoload;
818
	}
819
820
	/**
821
	 * Whether or not this value should be auto loaded. Applicable to theme options only.
822
	 *
823
	 * @param  bool  $autoload
824
	 * @return self  $this
825
	 */
826
	public function set_autoload( $autoload ) {
827
		$this->autoload = $autoload;
828
		return $this;
829
	}
830
831
	/**
832
	 * Return whether or not this field should be lazyloaded.
833
	 *
834
	 * @return bool
835
	 */
836
	public function get_lazyload() {
837
		return $this->lazyload;
838
	}
839
840
	/**
841
	 * Whether or not this field will be initialized when the field is in the viewport (visible).
842
	 *
843
	 * @param  bool  $lazyload
844
	 * @return self  $this
845
	 */
846
	public function set_lazyload( $lazyload ) {
847
		$this->lazyload = $lazyload;
848
		return $this;
849
	}
850
851
	/**
852
	 * Get the field width.
853
	 *
854
	 * @return int $width
855
	 */
856
	public function get_width() {
857
		return $this->width;
858
	}
859
860
	/**
861
	 * Set the field width.
862
	 *
863
	 * @param  int   $width
864
	 * @return self  $this
865
	 */
866
	public function set_width( $width ) {
867
		$this->width = (int) $width;
868
		return $this;
869
	}
870
871
	/**
872
	 * Get custom CSS classes.
873
	 *
874
	 * @return array<string>
875
	 */
876
	public function get_classes() {
877
		return $this->classes;
878
	}
879
880
	/**
881
	 * Set CSS classes that the container should use.
882
	 *
883
	 * @param  string|array<string> $classes
884
	 * @return self                 $this
885
	 */
886
	public function set_classes( $classes ) {
887
		$this->classes = Helper::sanitize_classes( $classes );
888
		return $this;
889
	}
890
891
	/**
892
	 * Whether this field is mandatory for the user
893
	 *
894
	 * @param  bool  $required
895
	 * @return self  $this
896
	 */
897
	public function set_required( $required = true ) {
898
		$this->required = $required;
899
		return $this;
900
	}
901
902
	/**
903
	 * Return whether this field is mandatory for the user
904
	 *
905
	 * @return bool
906
	 */
907
	public function is_required() {
908
		return $this->required;
909
	}
910
911
	/**
912
	 * HTML id attribute getter.
913
	 * @return string
914
	 */
915 1
	public function get_id() {
916 1
		return $this->id;
917
	}
918
919
	/**
920
	 * HTML id attribute setter
921
	 *
922
	 * @param  string $id
923
	 * @return self   $this
924
	 */
925 1
	public function set_id( $id ) {
926 1
		$this->id = $id;
927 1
		return $this;
928
	}
929
930
	/**
931
	 * Set the field visibility conditional logic.
932
	 *
933
	 * @param  array
934
	 * @return self  $this
935
	 */
936 8
	public function set_conditional_logic( $rules ) {
937 8
		$this->conditional_logic = $this->parse_conditional_rules( $rules );
938 3
		return $this;
939
	}
940
941
	/**
942
	 * Get the conditional logic rules
943
	 *
944
	 * @return array
945
	 */
946 3
	public function get_conditional_logic() {
947 3
		return $this->conditional_logic;
948
	}
949
950
	/**
951
	 * Validate and parse a conditional logic rule.
952
	 *
953
	 * @param  array $rule
954
	 * @return array
955
	 */
956
	protected function parse_conditional_rule( $rule ) {
957
		$allowed_operators = array( '=', '!=', '>', '>=', '<', '<=', 'IN', 'NOT IN', 'INCLUDES', 'EXCLUDES' );
958
		$array_operators = array( 'IN', 'NOT IN' );
959
960
		// Check if the rule is valid
961
		if ( ! is_array( $rule ) || empty( $rule['field'] ) ) {
962
			Incorrect_Syntax_Exception::raise( 'Invalid conditional logic rule format. The rule should be an array with the "field" key set.' );
963
			return null;
964
		}
965
966
		// Fill in optional keys with defaults
967
		$rule = array_merge( array(
968
			'compare' => '=',
969
			'value' => '',
970
		), $rule );
971
972 View Code Duplication
		if ( ! in_array( $rule['compare'], $allowed_operators ) ) {
973
			Incorrect_Syntax_Exception::raise( 'Invalid conditional logic compare operator: <code>' . $rule['compare'] . '</code><br>Allowed operators are: <code>' .
974
			implode( ', ', $allowed_operators ) . '</code>' );
975
			return null;
976
		}
977
978
		if ( in_array( $rule['compare'], $array_operators ) && ! is_array( $rule['value'] ) ) {
979
			Incorrect_Syntax_Exception::raise( 'Invalid conditional logic value format. An array is expected, when using the "' . $rule['compare'] . '" operator.' );
980
			return null;
981
		}
982
983
		return $rule;
984
	}
985
986
	/**
987
	 * Validate and parse conditional logic rules.
988
	 *
989
	 * @param  array $rules
990
	 * @return array
991
	 */
992
	protected function parse_conditional_rules( $rules ) {
993
		if ( ! is_array( $rules ) ) {
994
			Incorrect_Syntax_Exception::raise( 'Conditional logic rules argument should be an array.' );
995
			return array();
996
		}
997
998
		$parsed_rules = array(
999
			'relation' => Helper::get_relation_type_from_array( $rules ),
1000
			'rules' => array(),
1001
		);
1002
1003
		$rules_only = $rules;
1004
		unset( $rules_only['relation'] ); // Skip the relation key as it is already handled above
1005
1006
		foreach ( $rules_only as $key => $rule ) {
1007
			$rule = $this->parse_conditional_rule( $rule );
1008
1009
			if ( $rule === null ) {
1010
				return array();
1011
			}
1012
1013
			$parsed_rules['rules'][] = $rule;
1014
		}
1015
1016
		return $parsed_rules;
1017
	}
1018
1019
	/**
1020
	 * Set the REST visibility of the field
1021
	 *
1022
	 * @param  bool  $visible
1023
	 * @return self  $this
1024
	 */
1025
	public function set_visible_in_rest_api( $visible = true ) {
1026
		$this->visible_in_rest_api = $visible;
1027
		return $this;
1028
	}
1029
1030
	/**
1031
	 * Get the REST visibility of the field
1032
	 *
1033
	 * @return bool
1034
	 */
1035
	public function get_visible_in_rest_api() {
1036
		return $this->visible_in_rest_api;
1037
	}
1038
1039
	/**
1040
	 * Returns an array that holds the field data, suitable for JSON representation.
1041
	 *
1042
	 * @param bool $load  Should the value be loaded from the database or use the value from the current instance.
1043
	 * @return array
1044
	 */
1045
	public function to_json( $load ) {
1046
		if ( $load ) {
1047
			$this->load();
1048
		}
1049
1050
		$field_data = array(
1051
			'id' => $this->get_id(),
1052
			'type' => $this->get_type(),
1053
			'label' => $this->get_label(),
1054
			'name' => $this->get_name(),
1055
			'base_name' => $this->get_base_name(),
1056
			'value' => $this->get_formatted_value(),
1057
			'default_value' => $this->get_default_value(),
1058
			'attributes' => (object) $this->get_attributes(),
1059
			'help_text' => $this->get_help_text(),
1060
			'context' => $this->get_context(),
1061
			'required' => $this->is_required(),
1062
			'lazyload' => $this->get_lazyload(),
1063
			'width' => $this->get_width(),
1064
			'classes' => $this->get_classes(),
1065
			'conditional_logic' => $this->get_conditional_logic(),
1066
		);
1067
1068
		return $field_data;
1069
	}
1070
1071
	/**
1072
	 * Hook administration scripts.
1073
	 */
1074
	public static function admin_hook_scripts() {
1075
		wp_enqueue_media();
1076
		wp_enqueue_script( 'thickbox' );
1077
		wp_enqueue_script( 'media-upload' );
1078
	}
1079
1080
	/**
1081
	 * Hook administration styles.
1082
	 */
1083
	public static function admin_hook_styles() {
1084
		wp_enqueue_style( 'thickbox' );
1085
	}
1086
}
1087