Completed
Push — milestone/2_0/react-ui ( 321798...c1e1b2 )
by
unknown
14:33 queued 10:17
created

Container   C

Complexity

Total Complexity 79

Size/Duplication

Total Lines 703
Duplicated Lines 3.27 %

Coupling/Cohesion

Components 2
Dependencies 2

Test Coverage

Coverage 32.42%

Importance

Changes 0
Metric Value
dl 23
loc 703
ccs 12
cts 37
cp 0.3242
rs 5
c 0
b 0
f 0
wmc 79
lcom 2
cbo 2

42 Methods

Rating   Name   Duplication   Size   Complexity  
init() 0 1 ?
is_valid_save() 0 1 ?
A is_valid_attach() 0 4 1
is_valid_attach_for_request() 0 1 ?
is_valid_attach_for_object() 0 1 ?
A get_fields() 0 3 1
A get_datastore() 0 3 1
A verified_nonce_in_request() 0 5 2
A admin_hook_styles() 0 3 1
A add_tab() 0 5 1
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 10 2
A boot() 0 3 1
A load() 0 5 2
A _save() 0 6 2
A save() 0 6 2
A _is_valid_save() 0 5 1
A _attach() 0 11 3
A attach() 0 1 1
A should_activate() 0 3 1
A has_fields() 0 3 1
A get_root_field_by_name() 0 9 3
A get_field_pattern_regex() 0 15 1
D get_field_by_name() 0 44 10
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_nonce_name() 0 3 1
A get_nonce_field() 0 3 1
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 to_json() 0 17 2
A add_fields() 0 18 4

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