Completed
Push — milestone/2.0 ( 631282...ba0b77 )
by
unknown
02:33
created

Container   D

Complexity

Total Complexity 84

Size/Duplication

Total Lines 767
Duplicated Lines 3 %

Coupling/Cohesion

Components 2
Dependencies 2

Test Coverage

Coverage 10.61%

Importance

Changes 0
Metric Value
dl 23
loc 767
rs 4.4444
c 0
b 0
f 0
ccs 28
cts 264
cp 0.1061
wmc 84
lcom 2
cbo 2

46 Methods

Rating   Name   Duplication   Size   Complexity  
A normalize_container_type() 0 9 2
A container_type_to_class() 4 8 2
A factory() 0 11 1
A make() 0 3 1
A __construct() 0 11 2
A active() 0 3 1
A activate() 0 5 1
A activate_field() 0 13 3
init() 0 1 ?
B template() 0 27 1
A boot() 0 6 1
A _attach() 0 15 4
A attach() 0 1 1
A is_valid_attach() 0 4 1
is_valid_attach_for_request() 0 1 ?
A get_templates() 0 3 1
A load() 0 5 2
A _save() 0 6 2
A save() 0 6 2
A is_valid_save() 0 3 1
is_valid_attach_for_object() 0 1 ?
A is_active() 0 3 1
A add_template() 0 3 1
A has_fields() 0 3 1
A get_fields() 0 3 1
A get_root_field_by_name() 0 9 3
C get_field_by_name() 0 35 8
A verify_unique_field_name() 7 7 2
A drop_unique_field_name() 0 7 2
A has_default_datastore() 0 3 1
A set_datastore() 12 12 4
A get_datastore() 0 3 1
A get_nonce_name() 0 3 1
A get_nonce_field() 0 3 1
A verified_nonce_in_request() 0 5 2
B create_tab() 0 21 5
A is_tabbed() 0 3 1
B get_untabbed_fields() 0 22 5
A get_tabs() 0 9 2
A get_tabs_json() 0 12 3
A template_tabs() 0 19 1
A to_json() 0 17 2
A admin_hook_scripts() 0 10 1
A admin_hook_styles() 0 3 1
A add_fields() 0 18 4
A add_tab() 0 8 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Container often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Container, and based on these observations, apply Extract Interface, too.

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
	 * Stores if the container is active on the current page
25
	 *
26
	 * @see activate()
27
	 * @var bool
28
	 */
29
	protected $active = false;
30
31
	/**
32
	 * List of registered unique field names for this container instance
33
	 *
34
	 * @see verify_unique_field_name()
35
	 * @var array
36
	 */
37
	protected $registered_field_names = array();
38
39
	/**
40
	 * Stores all the container Backbone templates
41
	 *
42
	 * @see factory()
43
	 * @see add_template()
44
	 * @var array
45
	 */
46
	protected $templates = array();
47
48
	/**
49
	 * Tabs available
50
	 */
51
	protected $tabs = array();
52
53
	/**
54
	 * List of default container settings
55
	 *
56
	 * @see init()
57
	 * @var array
58
	 */
59
	public $settings = array();
60
61
	/**
62
	 * Title of the container
63
	 *
64
	 * @var string
65
	 */
66
	public $title = '';
67
68
	/**
69
	 * List of notification messages to be displayed on the front-end
70
	 *
71
	 * @var array
72
	 */
73
	protected $notifications = array();
74
75
	/**
76
	 * List of error messages to be displayed on the front-end
77
	 *
78
	 * @var array
79
	 */
80
	protected $errors = array();
81
82
	/**
83
	 * List of container fields
84
	 *
85
	 * @see add_fields()
86
	 * @var array
87
	 */
88
	protected $fields = array();
89
90
	/**
91
	 * Container datastores. Propagated to all container fields
92
	 *
93
	 * @see set_datastore()
94
	 * @see get_datastore()
95
	 * @var object
96
	 */
97
	protected $datastore;
98
99
	/**
100
	 * Flag whether the datastore is the default one or replaced with a custom one
101
	 *
102
	 * @see set_datastore()
103
	 * @see get_datastore()
104
	 * @var boolean
105
	 */
106
	protected $has_default_datastore = true;
107
108
	/**
109
	 * Normalizes a container type string to an expected format
110
	 *
111
	 * @param string $type
112
	 * @return string $normalized_type
113
	 **/
114
	protected static function normalize_container_type( $type ) {
115
		// backward compatibility: post_meta container used to be called custom_fields
116
		if ( $type === 'custom_fields' ) {
117
			$type = 'post_meta';
118
		}
119
120
		$normalized_type = str_replace( ' ', '_', ucwords( str_replace( '_', ' ', $type ) ) );
121
		return $normalized_type;
122
	}
123
124
	/**
125
	 * Resolves a string-based type to a fully qualified container class name
126
	 *
127
	 * @param string $type
128
	 * @return string $class_name
129
	 **/
130
	protected static function container_type_to_class( $type ) {
131
		$class = __NAMESPACE__ . '\\' . $type . '_Container';
132 View Code Duplication
		if ( ! class_exists( $class ) ) {
133
			Incorrect_Syntax_Exception::raise( 'Unknown container "' . $type . '".' );
134
			$class = __NAMESPACE__ . '\\Broken_Container';
135
		}
136
		return $class;
137
	}
138
139
	/**
140
	 * Create a new container of type $type and name $name.
141
	 *
142
	 * @param string $type
143
	 * @param string $name Human-readable name of the container
144
	 * @return object $container
145
	 **/
146 9
	public static function factory( $type, $name ) {
147 9
		$repository = App::resolve( 'container_repository' );
148 9
		$unique_id = $repository->get_unique_panel_id( $name );
149
		
150 9
		$normalized_type = static::normalize_container_type( $type );
151 9
		$class = static::container_type_to_class( $normalized_type );
152 7
		$container = new $class( $unique_id, $name, $normalized_type );
153 7
		$repository->register_container( $container );
154
155 7
		return $container;
156
	}
157
158
	/**
159
	 * An alias of factory().
160
	 *
161
	 * @see Container::factory()
162
	 **/
163
	public static function make( $type, $name ) {
164
		return static::factory( $type, $name );
165
	}
166
167
	/**
168
	 * Create a new container
169
	 *
170
	 * @param string $unique_id Unique id of the container
171
	 * @param string $title title of the container
172
	 * @param string $type Type of the container
173
	 **/
174 2
	public function __construct( $unique_id, $title, $type ) {
175 2
		\Carbon_Fields\App::verify_boot();
176
177 2
		if ( empty( $title ) ) {
178 1
			Incorrect_Syntax_Exception::raise( 'Empty container title is not supported' );
179
		}
180
181 1
		$this->id = $unique_id;
182 1
		$this->title = $title;
183 1
		$this->type = $type;
184 1
	}
185
186
	/**
187
	 * Return whether the container is active
188
	 **/
189
	public function active() {
190
		return $this->active;
191
	}
192
193
	/**
194
	 * Activate the container and trigger an action
195
	 **/
196
	protected function activate() {
197
		$this->active = true;
198
		$this->boot();
199
		do_action( 'crb_container_activated', $this );
200
	}
201
202
	/**
203
	 * Activates and boots a field recursively
204
	 **/
205
	protected function activate_field( $field ) {
206
		if ( method_exists( $field, 'get_fields' ) ) {
207
			$fields = $field->get_fields();
208
209
			foreach ( $fields as $inner_field ) {
210
				$this->activate_field( $inner_field );
211
			}
212
		}
213
214
		$field->boot();
215
216
		do_action( 'crb_field_activated', $field );
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 ) {
296
		foreach ( $this->fields as $field ) {
297
			$field->set_value_from_input();
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
			if ( call_user_func_array( array( $this, 'is_active' ), $param ) ) {
325
				$this->activate();
326
327
				$fields = $this->get_fields();
328
				foreach ( $fields as $field ) {
329
					$this->activate_field( $field );
330
				}
331
			}
332
		}
333
	}
334
335
	/**
336
	 * Attach the container rendering and helping methods
337
	 * to concrete WordPress Action hooks
338
	 **/
339
	public function attach() {}
340
341
	/**
342
	 * Perform checks whether the container should be attached during the current request
343
	 *
344
	 * @return bool True if the container is allowed to be attached
345
	 **/
346
	final public function is_valid_attach() {
347
		$is_valid_attach = $this->is_valid_attach_for_request();
348
		return apply_filters( 'carbon_fields_container_is_valid_attach', $is_valid_attach, $this );
349
	}
350
351
	/**
352
	 * Check container attachment rules against current page request (in admin)
353
	 *
354
	 * @return bool
355
	 **/
356
	abstract protected function is_valid_attach_for_request();
357
358
	/**
359
	 * Check container attachment rules against object id
360
	 *
361
	 * @return bool
362
	 **/
363
	abstract public function is_valid_attach_for_object( $object_id = null );
364
365
	/**
366
	 * Whether this container is currently viewed.
367
	 **/
368
	public function is_active() {
369
		return $this->is_valid_attach();
370
	}
371
372
	/**
373
	 * Returns all the Backbone templates
374
	 *
375
	 * @return array
376
	 **/
377
	public function get_templates() {
378
		return $this->templates;
379
	}
380
381
	/**
382
	 * Adds a new Backbone template
383
	 **/
384
	protected function add_template( $name, $callback ) {
385
		$this->templates[ $name ] = $callback;
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
	 * Return field from container with specified name
427
	 * 
428
	 * @example crb_complex/text_field
429
	 * @example crb_complex/complex_2
430
	 * @example crb_complex/complex_2:text_group/text_field
431
	 * 
432
	 * @param string $field_name Can specify a field inside a complex with a / (slash) separator
433
	 * @return Field
434
	 **/
435
	public function get_field_by_name( $field_name ) {
436
		$hierarchy = array_filter( explode( '/', $field_name ) );
437
		$field = null;
438
439
		$field_group = $this->get_fields();
440
		$hierarchy_left = $hierarchy;
441
442
		while ( ! empty( $hierarchy_left ) ) {
443
			$segment = array_shift( $hierarchy_left );
444
			$segment_pieces = explode( ':', $segment, 2 );
445
			$field_name = $segment_pieces[0];
446
			$group_name = isset( $segment_pieces[1] ) ? $segment_pieces[1] : Group_Field::DEFAULT_GROUP_NAME;
447
448
			foreach ( $field_group as $f ) {
449
				if ( $f->get_base_name() === $field_name ) {
450
					if ( empty( $hierarchy_left ) ) {
451
						$field = $f;
452
					} else {
453
						if ( is_a( $f, '\\Carbon_Fields\\Field\\Complex_Field' ) ) {
454
							$group = $f->get_group_by_name( $group_name );
455
							if ( ! $group ) {
456
								Incorrect_Syntax_Exception::raise( 'Unknown group name specified when fetching a value inside a complex field: "' . $group_name . '".' );
457
							}
458
							$field_group = $group->get_fields();
459
						} else {
460
							Incorrect_Syntax_Exception::raise( 'Attempted to look for a nested field inside a non-complex field.' );
461
						}
462
					}
463
					break;
464
				}
465
			}
466
		}
467
468
		return $field;
469
	}
470
471
	/**
472
	 * Perform checks whether there is a field registered with the name $name.
473
	 * If not, the field name is recorded.
474
	 *
475
	 * @param string $name
476
	 **/
477 View Code Duplication
	public function verify_unique_field_name( $name ) {
478
		if ( in_array( $name, $this->registered_field_names ) ) {
479
			Incorrect_Syntax_Exception::raise( 'Field name "' . $name . '" already registered' );
480
		}
481
482
		$this->registered_field_names[] = $name;
483
	}
484
485
	/**
486
	 * Remove field name $name from the list of unique field names
487
	 *
488
	 * @param string $name
489
	 **/
490
	public function drop_unique_field_name( $name ) {
491
		$index = array_search( $name, $this->registered_field_names );
492
493
		if ( $index !== false ) {
494
			unset( $this->registered_field_names[ $index ] );
495
		}
496
	}
497
498
	/**
499
	 * Return whether the datastore instance is the default one or has been overriden
500
	 *
501
	 * @return boolean
502
	 **/
503 6
	public function has_default_datastore() {
504 6
		return $this->has_default_datastore;
505
	}
506
507
	/**
508
	 * Set datastore instance
509
	 *
510
	 * @param Datastore_Interface $datastore
511
	 * @return object $this
512
	 **/
513 6 View Code Duplication
	public function set_datastore( Datastore_Interface $datastore, $set_as_default = false ) {
514 6
		if ( $set_as_default && ! $this->has_default_datastore() ) {
515 1
			return $this; // datastore has been overriden with a custom one - abort changing to a default one
516
		}
517 6
		$this->datastore = $datastore;
518 6
		$this->has_default_datastore = $set_as_default;
519
520 6
		foreach ( $this->fields as $field ) {
521
			$field->set_datastore( $this->get_datastore(), true );
522 6
		}
523 6
		return $this;
524
	}
525
526
	/**
527
	 * Get the DataStore instance
528
	 *
529
	 * @return Datastore_Interface $datastore
530
	 **/
531 6
	public function get_datastore() {
532 6
		return $this->datastore;
533
	}
534
535
	/**
536
	 * Return WordPress nonce name used to identify the current container instance
537
	 *
538
	 * @return string
539
	 **/
540
	public function get_nonce_name() {
541
		return 'carbon_panel_' . $this->id . '_nonce';
542
	}
543
544
	/**
545
	 * Return WordPress nonce field
546
	 *
547
	 * @return string
548
	 **/
549
	public function get_nonce_field() {
550
		return wp_nonce_field( $this->get_nonce_name(), $this->get_nonce_name(), /*referer?*/ false, /*echo?*/ false );
551
	}
552
553
	/**
554
	 * Check if the nonce is present in the request and that it is verified
555
	 *
556
	 * @return bool
557
	 **/
558
	protected function verified_nonce_in_request() {
559
		$nonce_name = $this->get_nonce_name();
560
		$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...
561
		return wp_verify_nonce( $nonce_value, $nonce_name );
562
	}
563
564
	/**
565
	 * Internal function that creates the tab and associates it with particular field set
566
	 *
567
	 * @param string $tab_name
568
	 * @param array $fields
569
	 * @param int $queue_end
570
	 * @return object $this
571
	 */
572
	private function create_tab( $tab_name, $fields, $queue_end = self::TABS_TAIL ) {
573
		if ( isset( $this->tabs[ $tab_name ] ) ) {
574
			Incorrect_Syntax_Exception::raise( "Tab name duplication for $tab_name" );
575
		}
576
577
		if ( $queue_end === static::TABS_TAIL ) {
578
			$this->tabs[ $tab_name ] = array();
579
		} else if ( $queue_end === static::TABS_HEAD ) {
580
			$this->tabs = array_merge(
581
				array( $tab_name => array() ),
582
				$this->tabs
583
			);
584
		}
585
586
		foreach ( $fields as $field ) {
587
			$field_name = $field->get_name();
588
			$this->tabs[ $tab_name ][ $field_name ] = $field;
589
		}
590
591
		$this->settings['tabs'] = $this->get_tabs_json();
592
	}
593
594
	/**
595
	 * Whether the container is tabbed or not
596
	 *
597
	 * @return bool
598
	 */
599
	public function is_tabbed() {
600
		return (bool) $this->tabs;
601
	}
602
603
	/**
604
	 * Retrieve all fields that are not defined under a specific tab
605
	 *
606
	 * @return array
607
	 */
608
	protected function get_untabbed_fields() {
609
		$tabbed_fields_names = array();
610
		foreach ( $this->tabs as $tab_fields ) {
611
			$tabbed_fields_names = array_merge( $tabbed_fields_names, array_keys( $tab_fields ) );
612
		}
613
614
		$all_fields_names = array();
615
		foreach ( $this->fields as $field ) {
616
			$all_fields_names[] = $field->get_name();
617
		}
618
619
		$fields_not_in_tabs = array_diff( $all_fields_names, $tabbed_fields_names );
620
621
		$untabbed_fields = array();
622
		foreach ( $this->fields as $field ) {
623
			if ( in_array( $field->get_name(), $fields_not_in_tabs ) ) {
624
				$untabbed_fields[] = $field;
625
			}
626
		}
627
628
		return $untabbed_fields;
629
	}
630
631
	/**
632
	 * Retrieve all tabs.
633
	 * Create a default tab if there are any untabbed fields.
634
	 *
635
	 * @return array
636
	 */
637
	protected function get_tabs() {
638
		$untabbed_fields = $this->get_untabbed_fields();
639
640
		if ( ! empty( $untabbed_fields ) ) {
641
			$this->create_tab( __( 'General', \Carbon_Fields\TEXT_DOMAIN ), $untabbed_fields, static::TABS_HEAD );
642
		}
643
644
		return $this->tabs;
645
	}
646
647
	/**
648
	 * Build the tabs JSON
649
	 *
650
	 * @return array
651
	 */
652
	protected function get_tabs_json() {
653
		$tabs_json = array();
654
		$tabs = $this->get_tabs();
655
656
		foreach ( $tabs as $tab_name => $fields ) {
657
			foreach ( $fields as $field_name => $field ) {
658
				$tabs_json[ $tab_name ][] = $field_name;
659
			}
660
		}
661
662
		return $tabs_json;
663
	}
664
665
	/**
666
	 * Underscore template for tabs
667
	 */
668
	public function template_tabs() {
669
		?>
670
		<div class="carbon-tabs">
671
			<ul class="carbon-tabs-nav">
672
				<# _.each(tabs, function (tab, tabName) { #>
673
					<li><a href="#" data-id="{{{ tab.id }}}">{{{ tabName }}}</a></li>
674
				<# }); #>
675
			</ul>
676
677
			<div class="carbon-tabs-body">
678
				<# _.each(tabs, function (tab) { #>
679
					<div class="carbon-fields-collection carbon-tab">
680
						{{{ tab.html }}}
681
					</div>
682
				<# }); #>
683
			</div>
684
		</div>
685
		<?php
686
	}
687
688
	/**
689
	 * Returns an array that holds the container data, suitable for JSON representation.
690
	 * This data will be available in the Underscore template and the Backbone Model.
691
	 *
692
	 * @param bool $load  Should the value be loaded from the database or use the value from the current instance.
693
	 * @return array
694
	 */
695
	public function to_json( $load ) {
696
		$container_data = array(
697
			'id' => $this->id,
698
			'type' => $this->type,
699
			'title' => $this->title,
700
			'settings' => $this->settings,
701
			'fields' => array(),
702
		);
703
704
		$fields = $this->get_fields();
705
		foreach ( $fields as $field ) {
706
			$field_data = $field->to_json( $load );
707
			$container_data['fields'][] = $field_data;
708
		}
709
710
		return $container_data;
711
	}
712
713
	/**
714
	 * Enqueue admin scripts
715
	 */
716
	public static function admin_hook_scripts() {
717
		wp_enqueue_script( 'carbon-containers', \Carbon_Fields\URL . '/assets/js/containers.js', array( 'carbon-app' ), \Carbon_Fields\VERSION );
718
719
		wp_localize_script( 'carbon-containers', 'carbon_containers_l10n',
720
			array(
721
				'please_fill_the_required_fields' => __( 'Please fill out all required fields highlighted below.', \Carbon_Fields\TEXT_DOMAIN ),
722
				'changes_made_save_alert' => __( 'The changes you made will be lost if you navigate away from this page.', \Carbon_Fields\TEXT_DOMAIN ),
723
			)
724
		);
725
	}
726
727
	/**
728
	 * Enqueue admin styles
729
	 */
730
	public static function admin_hook_styles() {
731
		wp_enqueue_style( 'carbon-main', \Carbon_Fields\URL . '/assets/bundle.css', array(), \Carbon_Fields\VERSION );
732
	}
733
734
	/**
735
	 * COMMON USAGE METHODS
736
	 */
737
738
	/**
739
	 * Append array of fields to the current fields set. All items of the array
740
	 * must be instances of Field and their names should be unique for all
741
	 * Carbon containers.
742
	 * If a field does not have DataStore already, the container datastore is
743
	 * assigned to them instead.
744
	 *
745
	 * @param array $fields
746
	 * @return object $this
747
	 **/
748
	public function add_fields( $fields ) {
749
		foreach ( $fields as $field ) {
750
			if ( ! is_a( $field, 'Carbon_Fields\\Field\\Field' ) ) {
751
				Incorrect_Syntax_Exception::raise( 'Object must be of type Carbon_Fields\\Field\\Field' );
752
			}
753
754
			$this->verify_unique_field_name( $field->get_name() );
755
756
			$field->set_context( $this->type );
757
			if ( ! $field->get_datastore() ) {
758
				$field->set_datastore( $this->get_datastore(), $this->has_default_datastore() );
759
			}
760
		}
761
762
		$this->fields = array_merge( $this->fields, $fields );
763
764
		return $this;
765
	}
766
767
	/**
768
	 * Configuration function for adding tab with fields
769
	 *
770
	 * @param string $tab_name
771
	 * @param array $fields
772
	 * @return object $this
773
	 */
774
	public function add_tab( $tab_name, $fields ) {
775
		$this->add_template( 'tabs', array( $this, 'template_tabs' ) );
776
777
		$this->add_fields( $fields );
778
		$this->create_tab( $tab_name, $fields );
779
780
		return $this;
781
	}
782
}
783