Completed
Push — milestone/2.0 ( 830ade...fb6d4d )
by
unknown
17:30 queued 10:30
created

Container::add_tab()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 5
nc 1
nop 2
dl 0
loc 8
ccs 0
cts 0
cp 0
crap 2
rs 9.4285
c 0
b 0
f 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 save request is valid
304
	 *
305
	 * @return bool
306
	 **/
307
	final protected function _is_valid_save() {
308
		$param = func_get_args();
309
		$is_valid_save = call_user_func_array( array( $this, 'is_valid_save' ), $param );
310
		return apply_filters( 'carbon_fields_container_is_valid_save', $is_valid_save, $this );
311
	}
312
313
	/**
314
	 * Checks whether the current save request is valid
315
	 *
316
	 * @return bool
317
	 **/
318
	abstract protected function is_valid_save();
319
320
	/**
321
	 * Called first as part of the container attachment procedure.
322
	 * Responsible for checking it's OK to attach the container
323
	 * and if it is, calling the container-specific attach() method
324
	 *
325
	 * @see attach()
326
	 * @see is_valid_attach()
327
	 **/
328
	public function _attach() {
329
		$param = func_get_args();
330
		if ( $this->is_valid_attach() ) {
331
			call_user_func_array( array( $this, 'attach' ), $param );
332
333
			// Allow containers to activate but not load (useful in cases such as theme options)
334
			if ( $this->should_activate() ) {
335
				$this->activate();
336
			}
337
		}
338
	}
339
340
	/**
341
	 * Attach the container rendering and helping methods
342
	 * to concrete WordPress Action hooks
343
	 **/
344
	public function attach() {}
345
346
	/**
347
	 * Perform checks whether the container should be attached during the current request
348
	 *
349
	 * @return bool True if the container is allowed to be attached
350
	 **/
351
	final public function is_valid_attach() {
352
		$is_valid_attach = $this->is_valid_attach_for_request();
353
		return apply_filters( 'carbon_fields_container_is_valid_attach', $is_valid_attach, $this );
354
	}
355
356
	/**
357
	 * Check container attachment rules against current page request (in admin)
358
	 *
359
	 * @return bool
360
	 **/
361
	abstract protected function is_valid_attach_for_request();
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 = null );
370
371
	/**
372
	 * Whether this container is currently viewed.
373
	 **/
374
	public function should_activate() {
375
		return $this->is_valid_attach();
376
	}
377
378
	/**
379
	 * Returns all the Backbone templates
380
	 *
381
	 * @return array
382
	 **/
383
	public function get_templates() {
384
		return $this->templates;
385
	}
386
387
	/**
388
	 * Adds a new Backbone template
389
	 **/
390
	protected function add_template( $name, $callback ) {
391
		$this->templates[ $name ] = $callback;
392
	}
393
394
	/**
395
	 * Perform a check whether the current container has fields
396
	 *
397
	 * @return bool
398
	 **/
399
	public function has_fields() {
400
		return (bool) $this->fields;
401
	}
402
403
	/**
404
	 * Returns the private container array of fields.
405
	 * Use only if you are completely aware of what you are doing.
406
	 *
407
	 * @return array
408
	 **/
409
	public function get_fields() {
410
		return $this->fields;
411
	}
412
413
	/**
414
	 * Return root field from container with specified name
415
	 * 
416
	 * @example crb_complex
417
	 * 
418
	 * @param string $field_name
419
	 * @return Field
420
	 **/
421
	public function get_root_field_by_name( $field_name ) {
422
		$fields = $this->get_fields();
423
		foreach ( $fields as $field ) {
424
			if ( $field->get_base_name() === $field_name ) {
425
				return $field;
426
			}
427
		}
428
		return null;
429
	}
430
431
	/**
432
	 * Get a regex to match field name patterns used to fetch specific fields
433
	 * 
434
	 * @return string
435
	 */
436
	protected function get_field_pattern_regex() {
437
		// matches:
438
		// field_name
439
		// field_name[0]
440
		// field_name[0]:group_name
441
		// field_name:group_name
442
		$regex = '/
443
			\A
444
			(?P<field_name>[a-z0-9_]+)
445
			(?:\[(?P<group_index>\d+)\])?
446
			(?:' .  preg_quote( static::HIERARCHY_GROUP_SEPARATOR, '/' ). '(?P<group_name>[a-z0-9_]+))?
447
			\z
448
		/x';
449
		return $regex;
450
	}
451
452
	/**
453
	 * Return field from container with specified name
454
	 * 
455
	 * @example crb_complex/text_field
456
	 * @example crb_complex/complex_2
457
	 * @example crb_complex/complex_2:text_group/text_field
458
	 * 
459
	 * @param string $field_name Can specify a field inside a complex with a / (slash) separator
460
	 * @return Field
461
	 **/
462
	public function get_field_by_name( $field_name ) {
463
		$hierarchy = array_filter( explode( static::HIERARCHY_FIELD_SEPARATOR, $field_name ) );
464
		$field = null;
465
466
		$field_group = $this->get_fields();
467
		$hierarchy_left = $hierarchy;
468
		$field_pattern_regex = $this->get_field_pattern_regex();
469
		$hierarchy_index = array();
470
471
		while ( ! empty( $hierarchy_left ) ) {
472
			$segment = array_shift( $hierarchy_left );
473
			$segment_pieces = array();
474
			if ( ! preg_match( $field_pattern_regex, $segment, $segment_pieces ) ) {
475
				Incorrect_Syntax_Exception::raise( 'Invalid field name pattern used: ' . $field_name );
476
			}
477
			
478
			$segment_field_name = $segment_pieces['field_name'];
479
			$segment_group_index = isset( $segment_pieces['group_index'] ) ? $segment_pieces['group_index'] : 0;
480
			$segment_group_name = isset( $segment_pieces['group_name'] ) ? $segment_pieces['group_name'] : Group_Field::DEFAULT_GROUP_NAME;
481
482
			foreach ( $field_group as $f ) {
483
				if ( $f->get_base_name() === $segment_field_name ) {
484
					if ( empty( $hierarchy_left ) ) {
485
						$field = clone $f;
486
						$field->set_hierarchy_index( $hierarchy_index );
487
					} else {
488
						if ( is_a( $f, 'Carbon_Fields\\Field\\Complex_Field' ) ) {
489
							$group = $f->get_group_by_name( $segment_group_name );
490
							if ( ! $group ) {
491
								Incorrect_Syntax_Exception::raise( 'Unknown group name specified when fetching a value inside a complex field: "' . $segment_group_name . '".' );
492
							}
493
							$field_group = $group->get_fields();
494
							$hierarchy_index[] = $segment_group_index;
495
						} else {
496
							Incorrect_Syntax_Exception::raise( 'Attempted to look for a nested field inside a non-complex field.' );
497
						}
498
					}
499
					break;
500
				}
501
			}
502
		}
503
504
		return $field;
505
	}
506
507
	/**
508
	 * Perform checks whether there is a field registered with the name $name.
509
	 * If not, the field name is recorded.
510
	 *
511
	 * @param string $name
512
	 **/
513 View Code Duplication
	public function verify_unique_field_name( $name ) {
514
		if ( in_array( $name, $this->registered_field_names ) ) {
515
			Incorrect_Syntax_Exception::raise( 'Field name "' . $name . '" already registered' );
516
		}
517
518
		$this->registered_field_names[] = $name;
519
	}
520
521
	/**
522
	 * Remove field name $name from the list of unique field names
523
	 *
524
	 * @param string $name
525
	 **/
526
	public function drop_unique_field_name( $name ) {
527
		$index = array_search( $name, $this->registered_field_names );
528
529
		if ( $index !== false ) {
530 6
			unset( $this->registered_field_names[ $index ] );
531 6
		}
532
	}
533
534
	/**
535
	 * Return whether the datastore instance is the default one or has been overriden
536
	 *
537
	 * @return boolean
538
	 **/
539
	public function has_default_datastore() {
540 6
		return $this->has_default_datastore;
541 6
	}
542 1
543
	/**
544 6
	 * Set datastore instance
545 6
	 *
546
	 * @param Datastore_Interface $datastore
547 6
	 * @return object $this
548
	 **/
549 6 View Code Duplication
	public function set_datastore( Datastore_Interface $datastore, $set_as_default = false ) {
550 6
		if ( $set_as_default && ! $this->has_default_datastore() ) {
551
			return $this; // datastore has been overriden with a custom one - abort changing to a default one
552
		}
553
		$this->datastore = $datastore;
554
		$this->has_default_datastore = $set_as_default;
555
556
		foreach ( $this->fields as $field ) {
557
			$field->set_datastore( $this->get_datastore(), true );
558 6
		}
559 6
		return $this;
560
	}
561
562
	/**
563
	 * Get the DataStore instance
564
	 *
565
	 * @return Datastore_Interface $datastore
566
	 **/
567
	public function get_datastore() {
568
		return $this->datastore;
569
	}
570
571
	/**
572
	 * Return WordPress nonce name used to identify the current container instance
573
	 *
574
	 * @return string
575
	 **/
576
	public function get_nonce_name() {
577
		return 'carbon_panel_' . $this->id . '_nonce';
578
	}
579
580
	/**
581
	 * Return WordPress nonce field
582
	 *
583
	 * @return string
584
	 **/
585
	public function get_nonce_field() {
586
		return wp_nonce_field( $this->get_nonce_name(), $this->get_nonce_name(), /*referer?*/ false, /*echo?*/ false );
587
	}
588
589
	/**
590
	 * Check if the nonce is present in the request and that it is verified
591
	 *
592
	 * @return bool
593
	 **/
594
	protected function verified_nonce_in_request() {
595
		$nonce_name = $this->get_nonce_name();
596
		$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...
597
		return wp_verify_nonce( $nonce_value, $nonce_name );
598
	}
599
600
	/**
601
	 * Internal function that creates the tab and associates it with particular field set
602
	 *
603
	 * @param string $tab_name
604
	 * @param array $fields
605
	 * @param int $queue_end
606
	 * @return object $this
607
	 */
608
	private function create_tab( $tab_name, $fields, $queue_end = self::TABS_TAIL ) {
609
		if ( isset( $this->tabs[ $tab_name ] ) ) {
610
			Incorrect_Syntax_Exception::raise( "Tab name duplication for $tab_name" );
611
		}
612
613
		if ( $queue_end === static::TABS_TAIL ) {
614
			$this->tabs[ $tab_name ] = array();
615
		} else if ( $queue_end === static::TABS_HEAD ) {
616
			$this->tabs = array_merge(
617
				array( $tab_name => array() ),
618
				$this->tabs
619
			);
620
		}
621
622
		foreach ( $fields as $field ) {
623
			$field_name = $field->get_name();
624
			$this->tabs[ $tab_name ][ $field_name ] = $field;
625
		}
626
627
		$this->settings['tabs'] = $this->get_tabs_json();
628
	}
629
630
	/**
631
	 * Whether the container is tabbed or not
632
	 *
633
	 * @return bool
634
	 */
635
	public function is_tabbed() {
636
		return (bool) $this->tabs;
637
	}
638
639
	/**
640
	 * Retrieve all fields that are not defined under a specific tab
641
	 *
642
	 * @return array
643
	 */
644
	protected function get_untabbed_fields() {
645
		$tabbed_fields_names = array();
646
		foreach ( $this->tabs as $tab_fields ) {
647
			$tabbed_fields_names = array_merge( $tabbed_fields_names, array_keys( $tab_fields ) );
648
		}
649
650
		$all_fields_names = array();
651
		foreach ( $this->fields as $field ) {
652
			$all_fields_names[] = $field->get_name();
653
		}
654
655
		$fields_not_in_tabs = array_diff( $all_fields_names, $tabbed_fields_names );
656
657
		$untabbed_fields = array();
658
		foreach ( $this->fields as $field ) {
659
			if ( in_array( $field->get_name(), $fields_not_in_tabs ) ) {
660
				$untabbed_fields[] = $field;
661
			}
662
		}
663
664
		return $untabbed_fields;
665
	}
666
667
	/**
668
	 * Retrieve all tabs.
669
	 * Create a default tab if there are any untabbed fields.
670
	 *
671
	 * @return array
672
	 */
673
	protected function get_tabs() {
674
		$untabbed_fields = $this->get_untabbed_fields();
675
676
		if ( ! empty( $untabbed_fields ) ) {
677
			$this->create_tab( __( 'General', \Carbon_Fields\TEXT_DOMAIN ), $untabbed_fields, static::TABS_HEAD );
678
		}
679
680
		return $this->tabs;
681
	}
682
683
	/**
684
	 * Build the tabs JSON
685
	 *
686
	 * @return array
687
	 */
688
	protected function get_tabs_json() {
689
		$tabs_json = array();
690
		$tabs = $this->get_tabs();
691
692
		foreach ( $tabs as $tab_name => $fields ) {
693
			foreach ( $fields as $field_name => $field ) {
694
				$tabs_json[ $tab_name ][] = $field_name;
695
			}
696
		}
697
698
		return $tabs_json;
699
	}
700
701
	/**
702
	 * Underscore template for tabs
703
	 */
704
	public function template_tabs() {
705
		?>
706
		<div class="carbon-tabs">
707
			<ul class="carbon-tabs-nav">
708
				<# _.each(tabs, function (tab, tabName) { #>
709
					<li><a href="#" data-id="{{{ tab.id }}}">{{{ tabName }}}</a></li>
710
				<# }); #>
711
			</ul>
712
713
			<div class="carbon-tabs-body">
714
				<# _.each(tabs, function (tab) { #>
715
					<div class="carbon-fields-collection carbon-tab">
716
						{{{ tab.html }}}
717
					</div>
718
				<# }); #>
719
			</div>
720
		</div>
721
		<?php
722
	}
723
724
	/**
725
	 * Returns an array that holds the container data, suitable for JSON representation.
726
	 * This data will be available in the Underscore template and the Backbone Model.
727
	 *
728
	 * @param bool $load  Should the value be loaded from the database or use the value from the current instance.
729
	 * @return array
730
	 */
731
	public function to_json( $load ) {
732
		$container_data = array(
733
			'id' => $this->id,
734
			'type' => $this->type,
735
			'title' => $this->title,
736
			'settings' => $this->settings,
737
			'fields' => array(),
738
		);
739
740
		$fields = $this->get_fields();
741
		foreach ( $fields as $field ) {
742
			$field_data = $field->to_json( $load );
743
			$container_data['fields'][] = $field_data;
744
		}
745
746
		return $container_data;
747
	}
748
749
	/**
750
	 * Enqueue admin scripts
751
	 */
752
	public static function admin_hook_scripts() {
753
		wp_enqueue_script( 'carbon-containers', \Carbon_Fields\URL . '/assets/js/containers.js', array( 'carbon-app' ), \Carbon_Fields\VERSION );
754
755
		wp_localize_script( 'carbon-containers', 'carbon_containers_l10n',
756
			array(
757
				'please_fill_the_required_fields' => __( 'Please fill out all required fields highlighted below.', \Carbon_Fields\TEXT_DOMAIN ),
758
				'changes_made_save_alert' => __( 'The changes you made will be lost if you navigate away from this page.', \Carbon_Fields\TEXT_DOMAIN ),
759
			)
760
		);
761
	}
762
763
	/**
764
	 * Enqueue admin styles
765
	 */
766
	public static function admin_hook_styles() {
767
		wp_enqueue_style( 'carbon-main', \Carbon_Fields\URL . '/assets/bundle.css', array(), \Carbon_Fields\VERSION );
768
	}
769
770
	/**
771
	 * COMMON USAGE METHODS
772
	 */
773
774
	/**
775
	 * Append array of fields to the current fields set. All items of the array
776
	 * must be instances of Field and their names should be unique for all
777
	 * Carbon containers.
778
	 * If a field does not have DataStore already, the container datastore is
779
	 * assigned to them instead.
780
	 *
781
	 * @param array $fields
782
	 * @return object $this
783
	 **/
784
	public function add_fields( $fields ) {
785
		foreach ( $fields as $field ) {
786
			if ( ! is_a( $field, 'Carbon_Fields\\Field\\Field' ) ) {
787
				Incorrect_Syntax_Exception::raise( 'Object must be of type Carbon_Fields\\Field\\Field' );
788
			}
789
790
			$this->verify_unique_field_name( $field->get_name() );
791
792
			$field->set_context( $this->type );
793
			if ( ! $field->get_datastore() ) {
794
				$field->set_datastore( $this->get_datastore(), $this->has_default_datastore() );
795
			}
796
		}
797
798
		$this->fields = array_merge( $this->fields, $fields );
799
800
		return $this;
801
	}
802
803
	/**
804
	 * Configuration function for adding tab with fields
805
	 *
806
	 * @param string $tab_name
807
	 * @param array $fields
808
	 * @return object $this
809
	 */
810
	public function add_tab( $tab_name, $fields ) {
811
		$this->add_template( 'tabs', array( $this, 'template_tabs' ) );
812
813
		$this->add_fields( $fields );
814
		$this->create_tab( $tab_name, $fields );
815
816
		return $this;
817
	}
818
}
819