Completed
Pull Request — development (#615)
by
unknown
02:22
created

Field::get_width()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

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