Completed
Pull Request — development (#720)
by
unknown
02:19
created

Field::admin_enqueue_scripts()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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