Completed
Push — milestone/2_0/react-ui ( 6feb20...6ca58c )
by
unknown
05:23
created

Container::set_datastore()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 12
Code Lines 8

Duplication

Lines 12
Ratio 100 %

Code Coverage

Tests 8
CRAP Score 4.0218

Importance

Changes 0
Metric Value
cc 4
eloc 8
c 0
b 0
f 0
nc 3
nop 2
dl 12
loc 12
ccs 8
cts 9
cp 0.8889
crap 4.0218
rs 9.2
1
<?php
2
3
namespace Carbon_Fields\Container;
4
5
use Carbon_Fields\App;
6
use Carbon_Fields\Helper\Helper;
7
use Carbon_Fields\Field\Field;
8
use Carbon_Fields\Field\Group_Field;
9
use Carbon_Fields\Container\Fulfillable\Fulfillable_Collection;
10
use Carbon_Fields\Datastore\Datastore_Interface;
11
use Carbon_Fields\Datastore\Datastore_Holder_Interface;
12
use Carbon_Fields\Exception\Incorrect_Syntax_Exception;
13
14
/**
15
 * Base container class.
16
 * Defines the key container methods and their default implementations.
17
 */
18
abstract class Container implements Datastore_Holder_Interface {
19
	/**
20
	 * Where to put a particular tab -- at the head or the tail. Tail by default
21
	 */
22
	const TABS_TAIL = 1;
23
	const TABS_HEAD = 2;
24
25
	/**
26
	 * Separator signifying field hierarchy relation
27
	 * Used when searching for fields in a specific complex field
28
	 */
29
	const HIERARCHY_FIELD_SEPARATOR = '/';
30
31
	/**
32
	 * Separator signifying complex_field->group relation
33
	 * Used when searching for fields in a specific complex field group
34
	 */
35
	const HIERARCHY_GROUP_SEPARATOR = ':';
36
37
	/**
38
	 * Stores if the container is active on the current page
39
	 *
40
	 * @see activate()
41
	 * @var bool
42
	 */
43
	protected $active = false;
44
45
	/**
46
	 * List of registered unique field names for this container instance
47
	 *
48
	 * @see verify_unique_field_name()
49
	 * @var array
50
	 */
51
	protected $registered_field_names = array();
52
53
	/**
54
	 * Tabs available
55
	 */
56
	protected $tabs = array();
57
58
	/**
59
	 * List of default container settings
60
	 *
61
	 * @see init()
62
	 * @var array
63
	 */
64
	public $settings = array();
65
66
	/**
67
	 * Title of the container
68
	 *
69
	 * @var string
70
	 */
71
	public $title = '';
72
73
	/**
74
	 * List of notification messages to be displayed on the front-end
75
	 *
76
	 * @var array
77
	 */
78
	protected $notifications = array();
79
80
	/**
81
	 * List of error messages to be displayed on the front-end
82
	 *
83
	 * @var array
84
	 */
85
	protected $errors = array();
86
87
	/**
88
	 * List of container fields
89
	 *
90
	 * @see add_fields()
91
	 * @var array
92
	 */
93
	protected $fields = array();
94
95
	/**
96
	 * Array of custom CSS classes.
97
	 *
98
	 * @see add_class()
99
	 * @see get_classes()
100
	 * @var array<string>
101
	 */
102
	protected $classes = array();
103
104
	/**
105
	 * Container datastores. Propagated to all container fields
106
	 *
107
	 * @see set_datastore()
108
	 * @see get_datastore()
109
	 * @var object
110
	 */
111
	protected $datastore;
112
113
	/**
114
	 * Flag whether the datastore is the default one or replaced with a custom one
115
	 *
116
	 * @see set_datastore()
117
	 * @see get_datastore()
118
	 * @var boolean
119
	 */
120
	protected $has_default_datastore = true;
121
122
	/**
123
	 * Fulfillable_Collection to use when checking attachment/saving conditions
124
	 *
125
	 * @var Fulfillable_Collection
126
	 */
127
	protected $condition_collection;
128
129
	/**
130
	 * Get array of all static condition types
131
	 *
132
	 * @param  boolean       $static
133
	 * @return array<string>
134
	 */
135
	protected function get_condition_types( $static ) {
136
		$group = $static ? 'static' : 'dynamic';
137
		$container_type = Helper::class_to_type( get_class( $this ), '_Container' );
138
139
		$condition_types = array();
140
		$condition_types = apply_filters( 'carbon_fields_' . $container_type . '_container_' . $group . '_condition_types', $condition_types, $container_type, $this );
141
		$condition_types = apply_filters( 'carbon_fields_container_' . $group . '_condition_types', $condition_types, $container_type, $this );
142
143
		return $condition_types;
144
	}
145
146
	/**
147
	 * Create a new container of type $type and name $name.
148
	 *
149
	 * @param string $type
150
	 * @param string $name Human-readable name of the container
151
	 * @return object $container
152
	 */
153 8
	public static function factory( $type, $name ) {
154 8
		$normalized_type = Helper::normalize_type( $type );
155 8
		$class = Helper::type_to_class( $normalized_type, __NAMESPACE__, '_Container' );
156
157 8 View Code Duplication
		if ( ! class_exists( $class ) ) {
158 3
			Incorrect_Syntax_Exception::raise( 'Unknown container "' . $type . '".' );
159 1
			$class = __NAMESPACE__ . '\\Broken_Container';
160 1
		}
161
162 6
		$repository = App::resolve( 'container_repository' );
163 6
		$unique_id = $repository->get_unique_panel_id( $name );
164 6
		$container = new $class( $unique_id, $name, $normalized_type );
165 6
		$repository->register_container( $container );
166
167 6
		return $container;
168
	}
169
170
	/**
171
	 * An alias of factory().
172
	 *
173
	 * @see Container::factory()
174
	 */
175
	public static function make( $type, $name ) {
176
		return static::factory( $type, $name );
177
	}
178
179
	/**
180
	 * Create a new container
181
	 *
182
	 * @param string $unique_id Unique id of the container
183
	 * @param string $title title of the container
184
	 * @param string $type Type of the container
185
	 */
186 2
	public function __construct( $unique_id, $title, $type ) {
187 2
		App::verify_boot();
188
189 2
		if ( empty( $title ) ) {
190 1
			Incorrect_Syntax_Exception::raise( 'Empty container title is not supported' );
191
		}
192
193 1
		$this->id = $unique_id;
194 1
		$this->title = $title;
195 1
		$this->type = $type;
196 1
		$this->condition_collection = App::resolve( 'container_condition_fulfillable_collection' );
197 1
		$this->condition_collection->set_condition_type_list(
198 1
			array_merge( $this->get_condition_types( true ), $this->get_condition_types( false ) ),
199
			true
200 1
		);
201 1
	}
202
203
	/**
204
	 * Return whether the container is active
205
	 */
206
	public function active() {
207
		return $this->active;
208
	}
209
210
	/**
211
	 * Activate the container and trigger an action
212
	 */
213
	protected function activate() {
214
		$this->active = true;
215
		$this->boot();
216
		do_action( 'crb_container_activated', $this );
217
218
		$fields = $this->get_fields();
219
		foreach ( $fields as $field ) {
220
			$field->activate();
221
		}
222
	}
223
224
	/**
225
	 * Perform instance initialization
226
	 */
227
	abstract public function init();
228
229
	/**
230
	 * Boot the container once it's attached.
231
	 */
232
	protected function boot() {
233
		add_action( 'admin_footer', array( get_class(), 'admin_hook_styles' ), 5 );
234
	}
235
236
	/**
237
	 * Load the value for each field in the container.
238
	 * Could be used internally during container rendering
239
	 */
240
	public function load() {
241
		foreach ( $this->fields as $field ) {
242
			$field->load();
243
		}
244
	}
245
246
	/**
247
	 * Called first as part of the container save procedure.
248
	 * Responsible for checking the request validity and
249
	 * calling the container-specific save() method
250
	 *
251
	 * @see save()
252
	 * @see is_valid_save()
253
	 */
254
	public function _save() {
255
		$param = func_get_args();
256
		if ( call_user_func_array( array( $this, '_is_valid_save' ), $param ) ) {
257
			call_user_func_array( array( $this, 'save' ), $param );
258
		}
259
	}
260
261
	/**
262
	 * Load submitted data and save each field in the container
263
	 *
264
	 * @see is_valid_save()
265
	 */
266
	public function save( $data = null ) {
267
		foreach ( $this->fields as $field ) {
268
			$field->set_value_from_input( stripslashes_deep( $_POST ) );
1 ignored issue
show
introduced by
Detected access of super global var $_POST, probably need manual inspection.
Loading history...
269
			$field->save();
270
		}
271
	}
272
273
	/**
274
	 * Checks whether the current save request is valid
275
	 *
276
	 * @return bool
277
	 **/
278
	final protected function _is_valid_save() {
279
		$params = func_get_args();
280
		$is_valid_save = call_user_func_array( array( $this, 'is_valid_save' ), $params );
281
		return apply_filters( 'carbon_fields_container_is_valid_save', $is_valid_save, $this );
282
	}
283
284
	/**
285
	 * Checks whether the current save request is valid
286
	 *
287
	 * @return bool
288
	 **/
289
	abstract protected function is_valid_save();
290
291
	/**
292
	 * Called first as part of the container attachment procedure.
293
	 * Responsible for checking it's OK to attach the container
294
	 * and if it is, calling the container-specific attach() method
295
	 *
296
	 * @see attach()
297
	 * @see is_valid_attach()
298
	 */
299
	public function _attach() {
300
		$param = func_get_args();
301
		if ( $this->is_valid_attach() ) {
302
			call_user_func_array( array( $this, 'attach' ), $param );
303
304
			// Allow containers to activate but not load (useful in cases such as theme options)
305
			if ( $this->should_activate() ) {
306
				$this->activate();
307
			}
308
		}
309
	}
310
311
	/**
312
	 * Attach the container rendering and helping methods
313
	 * to concrete WordPress Action hooks
314
	 */
315
	public function attach() {}
316
317
	/**
318
	 * Perform checks whether the container should be attached during the current request
319
	 *
320
	 * @return bool True if the container is allowed to be attached
321
	 */
322
	final public function is_valid_attach() {
323
		$is_valid_attach = $this->is_valid_attach_for_request();
324
		return apply_filters( 'carbon_fields_container_is_valid_attach', $is_valid_attach, $this );
325
	}
326
327
	/**
328
	 * Get environment array for page request (in admin)
329
	 *
330
	 * @return array
331
	 */
332
	abstract protected function get_environment_for_request();
333
334
	/**
335
	 * Check container attachment rules against current page request (in admin)
336
	 *
337
	 * @return bool
338
	 */
339
	abstract protected function is_valid_attach_for_request();
340
341
	/**
342
	 * Check if conditions pass for request
343
	 *
344
	 * @return bool
345
	 */
346
	protected function static_conditions_pass() {
347
		$environment = $this->get_environment_for_request();
348
		$static_condition_collection = $this->condition_collection->evaluate(
349
			$this->get_condition_types( false ),
350
			true
351
		);
352
		return $static_condition_collection->is_fulfilled( $environment );
353
	}
354
355
	/**
356
	 * Get environment array for object id
357
	 *
358
	 * @param integer $object_id
359
	 * @return array
360
	 */
361
	abstract protected function get_environment_for_object( $object_id );
362
363
	/**
364
	 * Check container attachment rules against object id
365
	 *
366
	 * @param int $object_id
367
	 * @return bool
368
	 */
369
	abstract public function is_valid_attach_for_object( $object_id );
370
371
	/**
372
	 * Check if all conditions pass for object
373
	 *
374
	 * @return bool
375
	 */
376
	protected function all_conditions_pass( $object_id ) {
377
		$environment = $this->get_environment_for_object( $object_id );
378
		return $this->condition_collection->is_fulfilled( $environment );
379
	}
380
381
	/**
382
	 * Whether this container is currently viewed.
383
	 */
384
	public function should_activate() {
385
		return $this->is_valid_attach();
386
	}
387
388
	/**
389
	 * Perform a check whether the current container has fields
390
	 *
391
	 * @return bool
392
	 */
393
	public function has_fields() {
394
		return (bool) $this->fields;
395
	}
396
397
	/**
398
	 * Returns the private container array of fields.
399
	 * Use only if you are completely aware of what you are doing.
400
	 *
401
	 * @return array
402
	 */
403
	public function get_fields() {
404
		return $this->fields;
405
	}
406
407
	/**
408
	 * Return root field from container with specified name
409
	 *
410
	 * @example crb_complex
411
	 *
412
	 * @param string $field_name
413
	 * @return Field
414
	 */
415
	public function get_root_field_by_name( $field_name ) {
416
		$fields = $this->get_fields();
417
		foreach ( $fields as $field ) {
418
			if ( $field->get_base_name() === $field_name ) {
419
				return $field;
420
			}
421
		}
422
		return null;
423
	}
424
425
	/**
426
	 * Get a regex to match field name patterns used to fetch specific fields
427
	 *
428
	 * @return string
429
	 */
430
	protected function get_field_pattern_regex() {
431
		// matches:
432
		// field_name
433
		// field_name[0]
434
		// field_name[0]:group_name
435
		// field_name:group_name
436
		$regex = '/
437
			\A
438
			(?P<field_name>[a-z0-9_]+)
439
			(?:\[(?P<group_index>\d+)\])?
440
			(?:' .  preg_quote( static::HIERARCHY_GROUP_SEPARATOR, '/' ). '(?P<group_name>[a-z0-9_]+))?
441
			\z
442
		/x';
443
		return $regex;
444
	}
445
446
	/**
447
	 * Return field from container with specified name
448
	 *
449
	 * @example crb_complex/text_field
450
	 * @example crb_complex/complex_2
451
	 * @example crb_complex/complex_2:text_group/text_field
452
	 *
453
	 * @param string $field_name Can specify a field inside a complex with a / (slash) separator
454
	 * @return Field
455
	 */
456
	public function get_field_by_name( $field_name ) {
457
		$hierarchy = array_filter( explode( static::HIERARCHY_FIELD_SEPARATOR, $field_name ) );
458
		$field = null;
459
460
		$field_group = $this->get_fields();
461
		$hierarchy_left = $hierarchy;
462
		$field_pattern_regex = $this->get_field_pattern_regex();
463
		$hierarchy_index = array();
464
465
		while ( ! empty( $hierarchy_left ) ) {
466
			$segment = array_shift( $hierarchy_left );
467
			$segment_pieces = array();
468
			if ( ! preg_match( $field_pattern_regex, $segment, $segment_pieces ) ) {
469
				Incorrect_Syntax_Exception::raise( 'Invalid field name pattern used: ' . $field_name );
470
			}
471
472
			$segment_field_name = $segment_pieces['field_name'];
473
			$segment_group_index = isset( $segment_pieces['group_index'] ) ? $segment_pieces['group_index'] : 0;
474
			$segment_group_name = isset( $segment_pieces['group_name'] ) ? $segment_pieces['group_name'] : Group_Field::DEFAULT_GROUP_NAME;
475
476
			foreach ( $field_group as $f ) {
477
				if ( $f->get_base_name() === $segment_field_name ) {
478
					if ( empty( $hierarchy_left ) ) {
479
						$field = clone $f;
480
						$field->set_hierarchy_index( $hierarchy_index );
481
					} else {
482
						if ( is_a( $f, 'Carbon_Fields\\Field\\Complex_Field' ) ) {
483
							$group = $f->get_group_by_name( $segment_group_name );
484
							if ( ! $group ) {
485
								Incorrect_Syntax_Exception::raise( 'Unknown group name specified when fetching a value inside a complex field: "' . $segment_group_name . '".' );
486
							}
487
							$field_group = $group->get_fields();
488
							$hierarchy_index[] = $segment_group_index;
489
						} else {
490
							Incorrect_Syntax_Exception::raise( 'Attempted to look for a nested field inside a non-complex field.' );
491
						}
492
					}
493
					break;
494
				}
495
			}
496
		}
497
498
		return $field;
499
	}
500
501
	/**
502
	 * Perform checks whether there is a field registered with the name $name.
503
	 * If not, the field name is recorded.
504
	 *
505
	 * @param string $name
506
	 */
507 View Code Duplication
	public function verify_unique_field_name( $name ) {
508
		if ( in_array( $name, $this->registered_field_names ) ) {
509
			Incorrect_Syntax_Exception::raise( 'Field name "' . $name . '" already registered' );
510
		}
511
512
		$this->registered_field_names[] = $name;
513
	}
514
515
	/**
516
	 * Remove field name $name from the list of unique field names
517
	 *
518
	 * @param string $name
519
	 */
520
	public function drop_unique_field_name( $name ) {
521
		$index = array_search( $name, $this->registered_field_names );
522
523
		if ( $index !== false ) {
524
			unset( $this->registered_field_names[ $index ] );
525
		}
526
	}
527
528
	/**
529
	 * Return whether the datastore instance is the default one or has been overriden
530
	 *
531
	 * @return boolean
532
	 */
533 6
	public function has_default_datastore() {
534 6
		return $this->has_default_datastore;
535
	}
536
537
	/**
538
	 * Set datastore instance
539
	 *
540
	 * @param Datastore_Interface $datastore
541
	 * @return object $this
542
	 */
543 6 View Code Duplication
	public function set_datastore( Datastore_Interface $datastore, $set_as_default = false ) {
544 6
		if ( $set_as_default && ! $this->has_default_datastore() ) {
545 1
			return $this; // datastore has been overriden with a custom one - abort changing to a default one
546
		}
547 6
		$this->datastore = $datastore;
548 6
		$this->has_default_datastore = $set_as_default;
549
550 6
		foreach ( $this->fields as $field ) {
551
			$field->set_datastore( $this->get_datastore(), true );
552 6
		}
553 6
		return $this;
554
	}
555
556
	/**
557
	 * Get the DataStore instance
558
	 *
559
	 * @return Datastore_Interface $datastore
560
	 */
561 6
	public function get_datastore() {
562 6
		return $this->datastore;
563
	}
564
565
	/**
566
	 * Return WordPress nonce name used to identify the current container instance
567
	 *
568
	 * @return string
569
	 */
570
	public function get_nonce_name() {
571
		return 'carbon_panel_' . $this->id . '_nonce';
572
	}
573
574
	/**
575
	 * Return WordPress nonce name used to identify the current container instance
576
	 *
577
	 * @return string
578
	 */
579
	public function get_nonce_value() {
580
		return wp_create_nonce( $this->get_nonce_name() );
581
	}
582
583
	/**
584
	 * Check if the nonce is present in the request and that it is verified
585
	 *
586
	 * @return bool
587
	 */
588
	protected function verified_nonce_in_request() {
589
		$nonce_name = $this->get_nonce_name();
590
		$nonce_value = isset( $_REQUEST[ $nonce_name ] ) ? $_REQUEST[ $nonce_name ] : '';
0 ignored issues
show
introduced by
Detected access of super global var $_REQUEST, probably need manual inspection.
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_REQUEST
Loading history...
591
		return wp_verify_nonce( $nonce_value, $nonce_name );
592
	}
593
594
	/**
595
	 * Internal function that creates the tab and associates it with particular field set
596
	 *
597
	 * @param string $tab_name
598
	 * @param array $fields
599
	 * @param int $queue_end
600
	 * @return object $this
601
	 */
602
	private function create_tab( $tab_name, $fields, $queue_end = self::TABS_TAIL ) {
603
		if ( isset( $this->tabs[ $tab_name ] ) ) {
604
			Incorrect_Syntax_Exception::raise( "Tab name duplication for $tab_name" );
605
		}
606
607
		if ( $queue_end === static::TABS_TAIL ) {
608
			$this->tabs[ $tab_name ] = array();
609
		} else if ( $queue_end === static::TABS_HEAD ) {
610
			$this->tabs = array_merge(
611
				array( $tab_name => array() ),
612
				$this->tabs
613
			);
614
		}
615
616
		foreach ( $fields as $field ) {
617
			$field_name = $field->get_name();
618
			$this->tabs[ $tab_name ][ $field_name ] = $field;
619
		}
620
621
		$this->settings['tabs'] = $this->get_tabs_json();
622
	}
623
624
	/**
625
	 * Whether the container is tabbed or not
626
	 *
627
	 * @return bool
628
	 */
629
	public function is_tabbed() {
630
		return (bool) $this->tabs;
631
	}
632
633
	/**
634
	 * Retrieve all fields that are not defined under a specific tab
635
	 *
636
	 * @return array
637
	 */
638
	protected function get_untabbed_fields() {
639
		$tabbed_fields_names = array();
640
		foreach ( $this->tabs as $tab_fields ) {
641
			$tabbed_fields_names = array_merge( $tabbed_fields_names, array_keys( $tab_fields ) );
642
		}
643
644
		$all_fields_names = array();
645
		foreach ( $this->fields as $field ) {
646
			$all_fields_names[] = $field->get_name();
647
		}
648
649
		$fields_not_in_tabs = array_diff( $all_fields_names, $tabbed_fields_names );
650
651
		$untabbed_fields = array();
652
		foreach ( $this->fields as $field ) {
653
			if ( in_array( $field->get_name(), $fields_not_in_tabs ) ) {
654
				$untabbed_fields[] = $field;
655
			}
656
		}
657
658
		return $untabbed_fields;
659
	}
660
661
	/**
662
	 * Retrieve all tabs.
663
	 * Create a default tab if there are any untabbed fields.
664
	 *
665
	 * @return array
666
	 */
667
	protected function get_tabs() {
668
		$untabbed_fields = $this->get_untabbed_fields();
669
670
		if ( ! empty( $untabbed_fields ) ) {
671
			$this->create_tab( __( 'General', 'carbon-fields' ), $untabbed_fields, static::TABS_HEAD );
672
		}
673
674
		return $this->tabs;
675
	}
676
677
	/**
678
	 * Build the tabs JSON
679
	 *
680
	 * @return array
681
	 */
682
	protected function get_tabs_json() {
683
		$tabs_json = array();
684
		$tabs = $this->get_tabs();
685
686
		foreach ( $tabs as $tab_name => $fields ) {
687
			foreach ( $fields as $field_name => $field ) {
688
				$tabs_json[ $tab_name ][] = $field_name;
689
			}
690
		}
691
692
		return $tabs_json;
693
	}
694
695
	/**
696
	 * Get custom CSS classes.
697
	 *
698
	 * @return array<string>
699
	 */
700
	public function get_classes() {
701
		return $this->classes;
702
	}
703
704
	/**
705
	 * Set CSS classes that the container should use.
706
	 *
707
	 * @param string|array $classes
708
	 * @return object $this
709
	 */
710
	public function set_classes( $classes ) {
711
		$this->classes = Helper::sanitize_classes( $classes );
0 ignored issues
show
Bug introduced by
It seems like $classes defined by parameter $classes on line 710 can also be of type array; however, Carbon_Fields\Helper\Helper::sanitize_classes() does only seem to accept string|array<integer,string>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
712
		return $this;
713
	}
714
715
	/**
716
	 * Returns an array that holds the container data, suitable for JSON representation.
717
	 *
718
	 * @param bool $load  Should the value be loaded from the database or use the value from the current instance.
719
	 * @return array
720
	 */
721
	public function to_json( $load ) {
722
		$array_translator = App::resolve( 'container_condition_translator_array' );
723
		$conditions = $this->condition_collection->evaluate( $this->get_condition_types( true ), $this->get_environment_for_request(), array( 'CUSTOM' ) );
724
		$conditions = $array_translator->fulfillable_to_foreign( $conditions );
725
		$conditions = $array_translator->foreign_to_json( $conditions );
726
727
		$container_data = array(
728
			'id' => $this->id,
729
			'type' => $this->type,
730
			'title' => $this->title,
731
			'classes' => $this->get_classes(),
732
			'settings' => $this->settings,
733
			'conditions' => $conditions,
734
			'fields' => array(),
735
			'nonce' => array(
736
				'name' => $this->get_nonce_name(),
737
				'value' => $this->get_nonce_value(),
738
			),
739
		);
740
741
		$fields = $this->get_fields();
742
		foreach ( $fields as $field ) {
743
			$field_data = $field->to_json( $load );
744
			$container_data['fields'][] = $field_data;
745
		}
746
747
		return $container_data;
748
	}
749
750
	/**
751
	 * Enqueue admin styles
752
	 */
753
	public static function admin_hook_styles() {
754
		wp_enqueue_style( 'carbon-main', \Carbon_Fields\URL . '/assets/bundle.css', array(), \Carbon_Fields\VERSION );
755
	}
756
757
	/**
758
	 * COMMON USAGE METHODS
759
	 */
760
761
	/**
762
	 * Append array of fields to the current fields set. All items of the array
763
	 * must be instances of Field and their names should be unique for all
764
	 * Carbon containers.
765
	 * If a field does not have DataStore already, the container datastore is
766
	 * assigned to them instead.
767
	 *
768
	 * @param array $fields
769
	 * @return object $this
770
	 */
771
	public function add_fields( $fields ) {
772
		foreach ( $fields as $field ) {
773
			if ( ! is_a( $field, 'Carbon_Fields\\Field\\Field' ) ) {
774
				Incorrect_Syntax_Exception::raise( 'Object must be of type Carbon_Fields\\Field\\Field' );
775
			}
776
777
			$this->verify_unique_field_name( $field->get_name() );
778
779
			$field->set_context( $this->type );
780
			if ( ! $field->get_datastore() ) {
781
				$field->set_datastore( $this->get_datastore(), $this->has_default_datastore() );
782
			}
783
		}
784
785
		$this->fields = array_merge( $this->fields, $fields );
786
787
		return $this;
788
	}
789
790
	/**
791
	 * Configuration function for adding tab with fields
792
	 *
793
	 * @param string $tab_name
794
	 * @param array $fields
795
	 * @return object $this
796
	 */
797
	public function add_tab( $tab_name, $fields ) {
798
		$this->add_fields( $fields );
799
		$this->create_tab( $tab_name, $fields );
800
		return $this;
801
	}
802
803
	/**
804
	 * Proxy function to set attachment conditions
805
	 *
806
	 * @see    Fulfillable_Collection::when()
807
	 * @return Container $this
808
	 */
809
	public function when() {
810
		call_user_func_array( array( $this->condition_collection, 'when' ), func_get_args() );
811
		return $this;
812
	}
813
814
	/**
815
	 * Proxy function to set attachment conditions
816
	 *
817
	 * @see    Fulfillable_Collection::and_when()
818
	 * @return Container $this
819
	 */
820
	public function and_when() {
821
		call_user_func_array( array( $this->condition_collection, 'and_when' ), func_get_args() );
822
		return $this;
823
	}
824
825
	/**
826
	 * Proxy function to set attachment conditions
827
	 *
828
	 * @see    Fulfillable_Collection::or_when()
829
	 * @return Container $this
830
	 */
831
	public function or_when() {
832
		call_user_func_array( array( $this->condition_collection, 'or_when' ), func_get_args() );
833
		return $this;
834
	}
835
}
836