Completed
Push — milestone/2.0 ( 693dca...1de7af )
by
unknown
02:42
created

Container::init()

Size

Total Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
nc 1
dl 0
loc 1
ccs 0
cts 0
cp 0
1
<?php
2
3
namespace Carbon_Fields\Container;
4
5
use Carbon_Fields\App;
6
use Carbon_Fields\Field\Field;
7
use Carbon_Fields\Field\Group_Field;
8
use Carbon_Fields\Datastore\Datastore_Interface;
9
use Carbon_Fields\Datastore\Datastore_Holder_Interface;
10
use Carbon_Fields\Exception\Incorrect_Syntax_Exception;
11
12
/**
13
 * Base container class.
14
 * Defines the key container methods and their default implementations.
15
 */
16
abstract class Container implements Datastore_Holder_Interface {
17
	/**
18
	 * Where to put a particular tab -- at the head or the tail. Tail by default
19
	 */
20
	const TABS_TAIL = 1;
21
	const TABS_HEAD = 2;
22
23
	/**
24
	 * Separator signifying field hierarchy relation
25
	 * Used when searching for fields in a specific complex field
26
	 */
27
	const HIERARCHY_FIELD_SEPARATOR = '/';
28
29
	/**
30
	 * Separator signifying complex_field->group relation
31
	 * Used when searching for fields in a specific complex field group
32
	 */
33
	const HIERARCHY_GROUP_SEPARATOR = ':';
34
35
	/**
36
	 * Stores if the container is active on the current page
37
	 *
38
	 * @see activate()
39
	 * @var bool
40
	 */
41
	protected $active = false;
42
43
	/**
44
	 * List of registered unique field names for this container instance
45
	 *
46
	 * @see verify_unique_field_name()
47
	 * @var array
48
	 */
49
	protected $registered_field_names = array();
50
51
	/**
52
	 * Stores all the container Backbone templates
53
	 *
54
	 * @see factory()
55
	 * @see add_template()
56
	 * @var array
57
	 */
58
	protected $templates = array();
59
60
	/**
61
	 * Tabs available
62
	 */
63
	protected $tabs = array();
64
65
	/**
66
	 * List of default container settings
67
	 *
68
	 * @see init()
69
	 * @var array
70
	 */
71
	public $settings = array();
72
73
	/**
74
	 * Title of the container
75
	 *
76
	 * @var string
77
	 */
78
	public $title = '';
79
80
	/**
81
	 * List of notification messages to be displayed on the front-end
82
	 *
83
	 * @var array
84
	 */
85
	protected $notifications = array();
86
87
	/**
88
	 * List of error messages to be displayed on the front-end
89
	 *
90
	 * @var array
91
	 */
92
	protected $errors = array();
93
94
	/**
95
	 * List of container fields
96
	 *
97
	 * @see add_fields()
98
	 * @var array
99
	 */
100
	protected $fields = array();
101
102
	/**
103
	 * Container datastores. Propagated to all container fields
104
	 *
105
	 * @see set_datastore()
106
	 * @see get_datastore()
107
	 * @var object
108
	 */
109
	protected $datastore;
110
111
	/**
112
	 * Flag whether the datastore is the default one or replaced with a custom one
113
	 *
114
	 * @see set_datastore()
115
	 * @see get_datastore()
116
	 * @var boolean
117
	 */
118
	protected $has_default_datastore = true;
119
120
	/**
121
	 * Normalizes a container type string to an expected format
122
	 *
123
	 * @param string $type
124
	 * @return string $normalized_type
125
	 **/
126
	protected static function normalize_container_type( $type ) {
127
		// backward compatibility: post_meta container used to be called custom_fields
128
		if ( $type === 'custom_fields' ) {
129
			$type = 'post_meta';
130
		}
131
132
		$normalized_type = str_replace( ' ', '_', ucwords( str_replace( '_', ' ', $type ) ) );
133
		return $normalized_type;
134
	}
135
136
	/**
137
	 * Resolves a string-based type to a fully qualified container class name
138
	 *
139
	 * @param string $type
140
	 * @return string $class_name
141
	 **/
142
	protected static function container_type_to_class( $type ) {
143
		$class = __NAMESPACE__ . '\\' . $type . '_Container';
144 View Code Duplication
		if ( ! class_exists( $class ) ) {
145
			Incorrect_Syntax_Exception::raise( 'Unknown container "' . $type . '".' );
146
			$class = __NAMESPACE__ . '\\Broken_Container';
147
		}
148
		return $class;
149
	}
150
151
	/**
152
	 * Create a new container of type $type and name $name.
153
	 *
154
	 * @param string $type
155
	 * @param string $name Human-readable name of the container
156
	 * @return object $container
157
	 **/
158 9
	public static function factory( $type, $name ) {
159 9
		$repository = App::resolve( 'container_repository' );
160 9
		$unique_id = $repository->get_unique_panel_id( $name );
161
		
162 9
		$normalized_type = static::normalize_container_type( $type );
163 9
		$class = static::container_type_to_class( $normalized_type );
164 7
		$container = new $class( $unique_id, $name, $normalized_type );
165 7
		$repository->register_container( $container );
166
167 7
		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
	}
197
198
	/**
199
	 * Return whether the container is active
200
	 **/
201
	public function active() {
202
		return $this->active;
203
	}
204
205
	/**
206
	 * Activate the container and trigger an action
207
	 **/
208
	protected function activate() {
209
		$this->active = true;
210
		$this->boot();
211
		do_action( 'crb_container_activated', $this );
212
213
		$fields = $this->get_fields();
214
		foreach ( $fields as $field ) {
215
			$field->activate();
216
		}
217
	}
218
219
	/**
220
	 * Perform instance initialization
221
	 **/
222
	abstract public function init();
223
224
	/**
225
	 * Prints the container Underscore template
226
	 **/
227
	public function template() {
228
		?>
229
		<div class="{{{ classes.join(' ') }}}">
230
			<# _.each(fields, function(field) { #>
231
				<div class="{{{ field.classes.join(' ') }}}">
232
					<label for="{{{ field.id }}}">
233
						{{ field.label }}
234
235
						<# if (field.required) { #>
236
							 <span class="carbon-required">*</span>
237
						<# } #>
238
					</label>
239
240
					<div class="field-holder {{{ field.id }}}"></div>
241
242
					<# if (field.help_text) { #>
243
						<em class="help-text">
244
							{{{ field.help_text }}}
245
						</em>
246
					<# } #>
247
248
					<em class="carbon-error"></em>
249
				</div>
250
			<# }); #>
251
		</div>
252
		<?php
253
	}
254
255
	/**
256
	 * Boot the container once it's attached.
257
	 **/
258
	protected function boot() {
259
		$this->add_template( $this->type, array( $this, 'template' ) );
260
261
		add_action( 'admin_footer', array( get_class(), 'admin_hook_scripts' ), 5 );
262
		add_action( 'admin_footer', array( get_class(), 'admin_hook_styles' ), 5 );
263
	}
264
265
	/**
266
	 * Load the value for each field in the container.
267
	 * Could be used internally during container rendering
268
	 **/
269
	public function load() {
270
		foreach ( $this->fields as $field ) {
271
			$field->load();
272
		}
273
	}
274
275
	/**
276
	 * Called first as part of the container save procedure.
277
	 * Responsible for checking the request validity and
278
	 * calling the container-specific save() method
279
	 *
280
	 * @see save()
281
	 * @see is_valid_save()
282
	 **/
283
	public function _save() {
284
		$param = func_get_args();
285
		if ( call_user_func_array( array( $this, 'is_valid_save' ), $param ) ) {
286
			call_user_func_array( array( $this, 'save' ), $param );
287
		}
288
	}
289
290
	/**
291
	 * Load submitted data and save each field in the container
292
	 *
293
	 * @see is_valid_save()
294
	 **/
295
	public function save( $data = null ) {
296
		foreach ( $this->fields as $field ) {
297
			$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...
298
			$field->save();
299
		}
300
	}
301
302
	/**
303
	 * Checks whether the current request is valid
304
	 *
305
	 * @return bool
306
	 **/
307
	public function is_valid_save() {
308
		return false;
309
	}
310
311
	/**
312
	 * Called first as part of the container attachment procedure.
313
	 * Responsible for checking it's OK to attach the container
314
	 * and if it is, calling the container-specific attach() method
315
	 *
316
	 * @see attach()
317
	 * @see is_valid_attach()
318
	 **/
319
	public function _attach() {
320
		$param = func_get_args();
321
		if ( call_user_func_array( array( $this, 'is_valid_attach' ), $param ) ) {
322
			call_user_func_array( array( $this, 'attach' ), $param );
323
324
			// Allow containers to activate but not load (useful in cases such as theme options)
325
			if ( $this->should_activate() ) {
326
				$this->activate();
327
			}
328
		}
329
	}
330
331
	/**
332
	 * Attach the container rendering and helping methods
333
	 * to concrete WordPress Action hooks
334
	 **/
335
	public function attach() {}
336
337
	/**
338
	 * Perform checks whether the container should be attached during the current request
339
	 *
340
	 * @return bool True if the container is allowed to be attached
341
	 **/
342
	final public function is_valid_attach() {
343
		$is_valid_attach = $this->is_valid_attach_for_request();
344
		return apply_filters( 'carbon_fields_container_is_valid_attach', $is_valid_attach, $this );
345
	}
346
347
	/**
348
	 * Check container attachment rules against current page request (in admin)
349
	 *
350
	 * @return bool
351
	 **/
352
	abstract protected function is_valid_attach_for_request();
353
354
	/**
355
	 * Check container attachment rules against object id
356
	 *
357
	 * @param int $object_id
358
	 * @return bool
359
	 **/
360
	abstract public function is_valid_attach_for_object( $object_id = null );
361
362
	/**
363
	 * Whether this container is currently viewed.
364
	 **/
365
	public function should_activate() {
366
		return $this->is_valid_attach();
367
	}
368
369
	/**
370
	 * Returns all the Backbone templates
371
	 *
372
	 * @return array
373
	 **/
374
	public function get_templates() {
375
		return $this->templates;
376
	}
377
378
	/**
379
	 * Adds a new Backbone template
380
	 **/
381
	protected function add_template( $name, $callback ) {
382
		$this->templates[ $name ] = $callback;
383
	}
384
385
	/**
386
	 * Perform a check whether the current container has fields
387
	 *
388
	 * @return bool
389
	 **/
390
	public function has_fields() {
391
		return (bool) $this->fields;
392
	}
393
394
	/**
395
	 * Returns the private container array of fields.
396
	 * Use only if you are completely aware of what you are doing.
397
	 *
398
	 * @return array
399
	 **/
400
	public function get_fields() {
401
		return $this->fields;
402
	}
403
404
	/**
405
	 * Return root field from container with specified name
406
	 * 
407
	 * @example crb_complex
408
	 * 
409
	 * @param string $field_name
410
	 * @return Field
411
	 **/
412
	public function get_root_field_by_name( $field_name ) {
413
		$fields = $this->get_fields();
414
		foreach ( $fields as $field ) {
415
			if ( $field->get_base_name() === $field_name ) {
416
				return $field;
417
			}
418
		}
419
		return null;
420
	}
421
422
	/**
423
	 * Get a regex to match field name patterns used to fetch specific fields
424
	 * 
425
	 * @return string
426
	 */
427
	protected function get_field_pattern_regex() {
428
		// matches:
429
		// field_name
430
		// field_name[0]
431
		// field_name[0]:group_name
1 ignored issue
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
432
		// field_name:group_name
433
		$regex = '/
434
			\A
435
			(?P<field_name>[a-z0-9_]+)
436
			(?:\[(?P<entry_index>\d+)\])?
437
			(?:' .  preg_quote( static::HIERARCHY_GROUP_SEPARATOR, '/' ). '(?P<group_name>[a-z0-9_]+))?
438
			\z
439
		/x';
440
		return $regex;
441
	}
442
443
	/**
444
	 * Return field from container with specified name
445
	 * 
446
	 * @example crb_complex/text_field
447
	 * @example crb_complex/complex_2
448
	 * @example crb_complex/complex_2:text_group/text_field
449
	 * 
450
	 * @param string $field_name Can specify a field inside a complex with a / (slash) separator
451
	 * @return Field
452
	 **/
453
	public function get_field_by_name( $field_name ) {
454
		$hierarchy = array_filter( explode( static::HIERARCHY_FIELD_SEPARATOR, $field_name ) );
455
		$field = null;
456
457
		$field_group = $this->get_fields();
458
		$hierarchy_left = $hierarchy;
459
		$field_pattern_regex = $this->get_field_pattern_regex();
460
		$hierarchy_index = array();
461
462
		while ( ! empty( $hierarchy_left ) ) {
463
			$segment = array_shift( $hierarchy_left );
464
			$segment_pieces = array();
465
			if ( ! preg_match( $field_pattern_regex, $segment, $segment_pieces ) ) {
466
				Incorrect_Syntax_Exception::raise( 'Invalid field name pattern used: ' . $field_name );
467
			}
468
			
469
			$segment_field_name = $segment_pieces['field_name'];
470
			$segment_entry_index = isset( $segment_pieces['entry_index'] ) ? $segment_pieces['entry_index'] : 0;
471
			$segment_group_name = isset( $segment_pieces['group_name'] ) ? $segment_pieces['group_name'] : Group_Field::DEFAULT_GROUP_NAME;
472
473
			foreach ( $field_group as $f ) {
474
				if ( $f->get_base_name() === $segment_field_name ) {
475
					if ( empty( $hierarchy_left ) ) {
476
						$field = clone $f;
477
						$field->set_hierarchy_index( $hierarchy_index );
478
					} else {
479
						if ( is_a( $f, 'Carbon_Fields\\Field\\Complex_Field' ) ) {
480
							$group = $f->get_group_by_name( $segment_group_name );
481
							if ( ! $group ) {
482
								Incorrect_Syntax_Exception::raise( 'Unknown group name specified when fetching a value inside a complex field: "' . $segment_group_name . '".' );
483
							}
484
							$field_group = $group->get_fields();
485
							$hierarchy_index[] = $segment_entry_index;
486
						} else {
487
							Incorrect_Syntax_Exception::raise( 'Attempted to look for a nested field inside a non-complex field.' );
488
						}
489
					}
490
					break;
491
				}
492
			}
493
		}
494
495
		return $field;
496
	}
497
498
	/**
499
	 * Perform checks whether there is a field registered with the name $name.
500
	 * If not, the field name is recorded.
501
	 *
502
	 * @param string $name
503
	 **/
504 View Code Duplication
	public function verify_unique_field_name( $name ) {
505
		if ( in_array( $name, $this->registered_field_names ) ) {
506
			Incorrect_Syntax_Exception::raise( 'Field name "' . $name . '" already registered' );
507
		}
508
509
		$this->registered_field_names[] = $name;
510
	}
511
512
	/**
513
	 * Remove field name $name from the list of unique field names
514
	 *
515
	 * @param string $name
516
	 **/
517
	public function drop_unique_field_name( $name ) {
518
		$index = array_search( $name, $this->registered_field_names );
519
520
		if ( $index !== false ) {
521
			unset( $this->registered_field_names[ $index ] );
522
		}
523
	}
524
525
	/**
526
	 * Return whether the datastore instance is the default one or has been overriden
527
	 *
528
	 * @return boolean
529
	 **/
530 6
	public function has_default_datastore() {
531 6
		return $this->has_default_datastore;
532
	}
533
534
	/**
535
	 * Set datastore instance
536
	 *
537
	 * @param Datastore_Interface $datastore
538
	 * @return object $this
539
	 **/
540 6 View Code Duplication
	public function set_datastore( Datastore_Interface $datastore, $set_as_default = false ) {
541 6
		if ( $set_as_default && ! $this->has_default_datastore() ) {
542 1
			return $this; // datastore has been overriden with a custom one - abort changing to a default one
543
		}
544 6
		$this->datastore = $datastore;
545 6
		$this->has_default_datastore = $set_as_default;
546
547 6
		foreach ( $this->fields as $field ) {
548
			$field->set_datastore( $this->get_datastore(), true );
549 6
		}
550 6
		return $this;
551
	}
552
553
	/**
554
	 * Get the DataStore instance
555
	 *
556
	 * @return Datastore_Interface $datastore
557
	 **/
558 6
	public function get_datastore() {
559 6
		return $this->datastore;
560
	}
561
562
	/**
563
	 * Return WordPress nonce name used to identify the current container instance
564
	 *
565
	 * @return string
566
	 **/
567
	public function get_nonce_name() {
568
		return 'carbon_panel_' . $this->id . '_nonce';
569
	}
570
571
	/**
572
	 * Return WordPress nonce field
573
	 *
574
	 * @return string
575
	 **/
576
	public function get_nonce_field() {
577
		return wp_nonce_field( $this->get_nonce_name(), $this->get_nonce_name(), /*referer?*/ false, /*echo?*/ false );
578
	}
579
580
	/**
581
	 * Check if the nonce is present in the request and that it is verified
582
	 *
583
	 * @return bool
584
	 **/
585
	protected function verified_nonce_in_request() {
586
		$nonce_name = $this->get_nonce_name();
587
		$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...
588
		return wp_verify_nonce( $nonce_value, $nonce_name );
589
	}
590
591
	/**
592
	 * Internal function that creates the tab and associates it with particular field set
593
	 *
594
	 * @param string $tab_name
595
	 * @param array $fields
596
	 * @param int $queue_end
597
	 * @return object $this
598
	 */
599
	private function create_tab( $tab_name, $fields, $queue_end = self::TABS_TAIL ) {
600
		if ( isset( $this->tabs[ $tab_name ] ) ) {
601
			Incorrect_Syntax_Exception::raise( "Tab name duplication for $tab_name" );
602
		}
603
604
		if ( $queue_end === static::TABS_TAIL ) {
605
			$this->tabs[ $tab_name ] = array();
606
		} else if ( $queue_end === static::TABS_HEAD ) {
607
			$this->tabs = array_merge(
608
				array( $tab_name => array() ),
609
				$this->tabs
610
			);
611
		}
612
613
		foreach ( $fields as $field ) {
614
			$field_name = $field->get_name();
615
			$this->tabs[ $tab_name ][ $field_name ] = $field;
616
		}
617
618
		$this->settings['tabs'] = $this->get_tabs_json();
619
	}
620
621
	/**
622
	 * Whether the container is tabbed or not
623
	 *
624
	 * @return bool
625
	 */
626
	public function is_tabbed() {
627
		return (bool) $this->tabs;
628
	}
629
630
	/**
631
	 * Retrieve all fields that are not defined under a specific tab
632
	 *
633
	 * @return array
634
	 */
635
	protected function get_untabbed_fields() {
636
		$tabbed_fields_names = array();
637
		foreach ( $this->tabs as $tab_fields ) {
638
			$tabbed_fields_names = array_merge( $tabbed_fields_names, array_keys( $tab_fields ) );
639
		}
640
641
		$all_fields_names = array();
642
		foreach ( $this->fields as $field ) {
643
			$all_fields_names[] = $field->get_name();
644
		}
645
646
		$fields_not_in_tabs = array_diff( $all_fields_names, $tabbed_fields_names );
647
648
		$untabbed_fields = array();
649
		foreach ( $this->fields as $field ) {
650
			if ( in_array( $field->get_name(), $fields_not_in_tabs ) ) {
651
				$untabbed_fields[] = $field;
652
			}
653
		}
654
655
		return $untabbed_fields;
656
	}
657
658
	/**
659
	 * Retrieve all tabs.
660
	 * Create a default tab if there are any untabbed fields.
661
	 *
662
	 * @return array
663
	 */
664
	protected function get_tabs() {
665
		$untabbed_fields = $this->get_untabbed_fields();
666
667
		if ( ! empty( $untabbed_fields ) ) {
668
			$this->create_tab( __( 'General', \Carbon_Fields\TEXT_DOMAIN ), $untabbed_fields, static::TABS_HEAD );
669
		}
670
671
		return $this->tabs;
672
	}
673
674
	/**
675
	 * Build the tabs JSON
676
	 *
677
	 * @return array
678
	 */
679
	protected function get_tabs_json() {
680
		$tabs_json = array();
681
		$tabs = $this->get_tabs();
682
683
		foreach ( $tabs as $tab_name => $fields ) {
684
			foreach ( $fields as $field_name => $field ) {
685
				$tabs_json[ $tab_name ][] = $field_name;
686
			}
687
		}
688
689
		return $tabs_json;
690
	}
691
692
	/**
693
	 * Underscore template for tabs
694
	 */
695
	public function template_tabs() {
696
		?>
697
		<div class="carbon-tabs">
698
			<ul class="carbon-tabs-nav">
699
				<# _.each(tabs, function (tab, tabName) { #>
700
					<li><a href="#" data-id="{{{ tab.id }}}">{{{ tabName }}}</a></li>
701
				<# }); #>
702
			</ul>
703
704
			<div class="carbon-tabs-body">
705
				<# _.each(tabs, function (tab) { #>
706
					<div class="carbon-fields-collection carbon-tab">
707
						{{{ tab.html }}}
708
					</div>
709
				<# }); #>
710
			</div>
711
		</div>
712
		<?php
713
	}
714
715
	/**
716
	 * Returns an array that holds the container data, suitable for JSON representation.
717
	 * This data will be available in the Underscore template and the Backbone Model.
718
	 *
719
	 * @param bool $load  Should the value be loaded from the database or use the value from the current instance.
720
	 * @return array
721
	 */
722
	public function to_json( $load ) {
723
		$container_data = array(
724
			'id' => $this->id,
725
			'type' => $this->type,
726
			'title' => $this->title,
727
			'settings' => $this->settings,
728
			'fields' => array(),
729
		);
730
731
		$fields = $this->get_fields();
732
		foreach ( $fields as $field ) {
733
			$field_data = $field->to_json( $load );
734
			$container_data['fields'][] = $field_data;
735
		}
736
737
		return $container_data;
738
	}
739
740
	/**
741
	 * Enqueue admin scripts
742
	 */
743
	public static function admin_hook_scripts() {
744
		wp_enqueue_script( 'carbon-containers', \Carbon_Fields\URL . '/assets/js/containers.js', array( 'carbon-app' ), \Carbon_Fields\VERSION );
745
746
		wp_localize_script( 'carbon-containers', 'carbon_containers_l10n',
747
			array(
748
				'please_fill_the_required_fields' => __( 'Please fill out all required fields highlighted below.', \Carbon_Fields\TEXT_DOMAIN ),
749
				'changes_made_save_alert' => __( 'The changes you made will be lost if you navigate away from this page.', \Carbon_Fields\TEXT_DOMAIN ),
750
			)
751
		);
752
	}
753
754
	/**
755
	 * Enqueue admin styles
756
	 */
757
	public static function admin_hook_styles() {
758
		wp_enqueue_style( 'carbon-main', \Carbon_Fields\URL . '/assets/bundle.css', array(), \Carbon_Fields\VERSION );
759
	}
760
761
	/**
762
	 * COMMON USAGE METHODS
763
	 */
764
765
	/**
766
	 * Append array of fields to the current fields set. All items of the array
767
	 * must be instances of Field and their names should be unique for all
768
	 * Carbon containers.
769
	 * If a field does not have DataStore already, the container datastore is
770
	 * assigned to them instead.
771
	 *
772
	 * @param array $fields
773
	 * @return object $this
774
	 **/
775
	public function add_fields( $fields ) {
776
		foreach ( $fields as $field ) {
777
			if ( ! is_a( $field, 'Carbon_Fields\\Field\\Field' ) ) {
778
				Incorrect_Syntax_Exception::raise( 'Object must be of type Carbon_Fields\\Field\\Field' );
779
			}
780
781
			$this->verify_unique_field_name( $field->get_name() );
782
783
			$field->set_context( $this->type );
784
			if ( ! $field->get_datastore() ) {
785
				$field->set_datastore( $this->get_datastore(), $this->has_default_datastore() );
786
			}
787
		}
788
789
		$this->fields = array_merge( $this->fields, $fields );
790
791
		return $this;
792
	}
793
794
	/**
795
	 * Configuration function for adding tab with fields
796
	 *
797
	 * @param string $tab_name
798
	 * @param array $fields
799
	 * @return object $this
800
	 */
801
	public function add_tab( $tab_name, $fields ) {
802
		$this->add_template( 'tabs', array( $this, 'template_tabs' ) );
803
804
		$this->add_fields( $fields );
805
		$this->create_tab( $tab_name, $fields );
806
807
		return $this;
808
	}
809
}
810