Completed
Push — milestone/2.0 ( 8ed296...84f65d )
by
unknown
03:34
created

Container::get_untabbed_fields()   B

Complexity

Conditions 5
Paths 12

Size

Total Lines 22
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
cc 5
eloc 13
nc 12
nop 0
dl 0
loc 22
rs 8.6737
c 0
b 0
f 0
ccs 0
cts 17
cp 0
crap 30
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 ) ) {
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...
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 ) );
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
			unset( $this->registered_field_names[ $index ] );
531
		}
532
	}
533
534
	/**
535
	 * Return whether the datastore instance is the default one or has been overriden
536
	 *
537
	 * @return boolean
538
	 **/
539 6
	public function has_default_datastore() {
540 6
		return $this->has_default_datastore;
541
	}
542
543
	/**
544
	 * Set datastore instance
545
	 *
546
	 * @param Datastore_Interface $datastore
547
	 * @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 1
			return $this; // datastore has been overriden with a custom one - abort changing to a default one
552
		}
553 6
		$this->datastore = $datastore;
554 6
		$this->has_default_datastore = $set_as_default;
555
556 6
		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 6
	public function get_datastore() {
568 6
		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