Completed
Push — master ( e9be7b...697ab3 )
by Atanas
13:19 queued 05:24
created

Field::get_attribute()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

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