Completed
Push — milestone/2_0/react-ui ( a147b7...7d0076 )
by
unknown
03:05
created

Container::make()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 2
c 0
b 0
f 0
nc 1
nop 2
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 2
rs 10
1
<?php
2
3
namespace Carbon_Fields\Container;
4
5
use Carbon_Fields\Carbon_Fields;
6
use Carbon_Fields\Helper\Helper;
7
use Carbon_Fields\Field\Field;
8
use Carbon_Fields\Field\Group_Field;
9
use Carbon_Fields\Container\Fulfillable\Fulfillable_Collection;
10
use Carbon_Fields\Datastore\Datastore_Interface;
11
use Carbon_Fields\Datastore\Datastore_Holder_Interface;
12
use Carbon_Fields\Exception\Incorrect_Syntax_Exception;
13
14
/**
15
 * Base container class.
16
 * Defines the key container methods and their default implementations.
17
 */
18
abstract class Container implements Datastore_Holder_Interface {
19
	/**
20
	 * Where to put a particular tab -- at the head or the tail. Tail by default
21
	 */
22
	const TABS_TAIL = 1;
23
	const TABS_HEAD = 2;
24
25
	/**
26
	 * Separator signifying field hierarchy relation
27
	 * Used when searching for fields in a specific complex field
28
	 */
29
	const HIERARCHY_FIELD_SEPARATOR = '/';
30
31
	/**
32
	 * Separator signifying complex_field->group relation
33
	 * Used when searching for fields in a specific complex field group
34
	 */
35
	const HIERARCHY_GROUP_SEPARATOR = ':';
36
37
	/**
38
	 * Stores if the container is active on the current page
39
	 *
40
	 * @see activate()
41
	 * @var bool
42
	 */
43
	protected $active = false;
44
45
	/**
46
	 * List of registered unique field names for this container instance
47
	 *
48
	 * @see register_field_name()
49
	 * @var array
50
	 */
51
	protected $registered_field_names = array();
52
53
	/**
54
	 * Tabs available
55
	 */
56
	protected $tabs = array();
57
58
	/**
59
	 * List of default container settings
60
	 *
61
	 * @see init()
62
	 * @var array
63
	 */
64
	public $settings = array();
65
66
	/**
67
	 * Title of the container
68
	 *
69
	 * @var string
70
	 */
71
	public $title = '';
72
73
	/**
74
	 * List of notification messages to be displayed on the front-end
75
	 *
76
	 * @var array
77
	 */
78
	protected $notifications = array();
79
80
	/**
81
	 * List of error messages to be displayed on the front-end
82
	 *
83
	 * @var array
84
	 */
85
	protected $errors = array();
86
87
	/**
88
	 * List of container fields
89
	 *
90
	 * @see add_fields()
91
	 * @var array
92
	 */
93
	protected $fields = array();
94
95
	/**
96
	 * Array of custom CSS classes.
97
	 *
98
	 * @see set_classes()
99
	 * @see get_classes()
100
	 * @var array<string>
101
	 */
102
	protected $classes = array();
103
104
	/**
105
	 * Container datastores. Propagated to all container fields
106
	 *
107
	 * @see set_datastore()
108
	 * @see get_datastore()
109
	 * @var object
110
	 */
111
	protected $datastore;
112
113
	/**
114
	 * Flag whether the datastore is the default one or replaced with a custom one
115
	 *
116
	 * @see set_datastore()
117
	 * @see get_datastore()
118
	 * @var boolean
119
	 */
120
	protected $has_default_datastore = true;
121
122
	/**
123
	 * Fulfillable_Collection to use when checking attachment/saving conditions
124
	 *
125
	 * @var Fulfillable_Collection
126
	 */
127
	protected $condition_collection;
128
129
	/**
130
	 * Translator to use when translating conditions to json
131
	 *
132
	 * @var Carbon_Fields\Container\Fulfillable\Translator\Translator
133
	 */
134
	protected $condition_translator;
135
136
	/**
137
	 * Create a new container of type $type and name $name.
138
	 *
139
	 * @param  string $raw_type
140
	 * @param  string $name      Human-readable name of the container
141
	 * @return object $container
142
	 */
143 8
	public static function factory( $raw_type, $name ) {
144 8
		$type = Helper::normalize_type( $raw_type );
145 8
		$repository = Carbon_Fields::resolve( 'container_repository' );
146 8
		$unique_id = $repository->get_unique_panel_id( $name );
147 8
		$container = null;
148
149 8
		if ( Carbon_Fields::has( $type, 'containers' ) ) {
150
			$container = Carbon_Fields::resolve_with_arguments( $type, array(
151
				'unique_id' => $unique_id,
152
				'name' => $name,
153
				'type' => $type,
154
			), 'containers' );
155
		} else {
156
			// Fallback to class name-based resolution
157 8
			$class = Helper::type_to_class( $type, __NAMESPACE__, '_Container' );
158
159 8
			if ( ! class_exists( $class ) ) {
160 3
				Incorrect_Syntax_Exception::raise( 'Unknown container "' . $raw_type . '".' );
161 1
				$class = __NAMESPACE__ . '\\Broken_Container';
162 1
			}
163
164 6
			$fulfillable_collection = Carbon_Fields::resolve( 'container_condition_fulfillable_collection' );
165 6
			$condition_translator = Carbon_Fields::resolve( 'container_condition_translator_json' );
166 6
			$container = new $class( $unique_id, $name, $type, $fulfillable_collection, $condition_translator );
167
		}
168
169 6
		$repository->register_container( $container );
170
171 6
		return $container;
172
	}
173
174
	/**
175
	 * An alias of factory().
176
	 *
177
	 * @see Container::factory()
178
	 */
179
	public static function make( $type, $name ) {
180
		return static::factory( $type, $name );
181
	}
182
183
	/**
184
	 * Create a new container
185
	 *
186
	 * @param string                 $unique_id            Unique id of the container
187
	 * @param string                 $title                title of the container
188
	 * @param string                 $type                 Type of the container
189
	 * @param Fulfillable_Collection $condition_collection
190
	 * @param Carbon_Fields\Container\Fulfillable\Translator\Translator $condition_translator
191
	 */
192 2
	public function __construct( $unique_id, $title, $type, $condition_collection, $condition_translator ) {
193 2
		Carbon_Fields::verify_boot();
194
195 2
		if ( empty( $title ) ) {
196 1
			Incorrect_Syntax_Exception::raise( 'Empty container title is not supported' );
197
		}
198
199 1
		$this->id = $unique_id;
200 1
		$this->title = $title;
201 1
		$this->type = $type;
202 1
		$this->condition_collection = $condition_collection;
203 1
		$this->condition_collection->set_condition_type_list(
204 1
			array_merge( $this->get_condition_types( true ), $this->get_condition_types( false ) ),
205
			true
206 1
		);
207 1
		$this->condition_translator = $condition_translator;
208 1
	}
209
210
	/**
211
	 * Get array of all static condition types
212
	 *
213
	 * @param  boolean       $static
214
	 * @return array<string>
215
	 */
216
	protected function get_condition_types( $static ) {
217
		$group = $static ? 'static' : 'dynamic';
218
		$container_type = Helper::class_to_type( get_class( $this ), '_Container' );
219
220
		$condition_types = array();
221
		$condition_types = apply_filters( 'carbon_fields_' . $container_type . '_container_' . $group . '_condition_types', $condition_types, $container_type, $this );
222
		$condition_types = apply_filters( 'carbon_fields_container_' . $group . '_condition_types', $condition_types, $container_type, $this );
223
224
		return $condition_types;
225
	}
226
227
	/**
228
	 * Return whether the container is active
229
	 */
230
	public function is_active() {
231
		return $this->active;
232
	}
233
234
	/**
235
	 * Activate the container and trigger an action
236
	 */
237
	protected function activate() {
238
		$this->active = true;
239
		$this->boot();
240
		do_action( 'carbon_fields_container_activated', $this );
241
242
		$fields = $this->get_fields();
243
		foreach ( $fields as $field ) {
244
			$field->activate();
245
		}
246
	}
247
248
	/**
249
	 * Perform instance initialization
250
	 */
251
	abstract public function init();
252
253
	/**
254
	 * Boot the container once it's attached.
255
	 */
256
	protected function boot() {
257
		
258
	}
259
260
	/**
261
	 * Load the value for each field in the container.
262
	 * Could be used internally during container rendering
263
	 */
264
	public function load() {
265
		foreach ( $this->fields as $field ) {
266
			$field->load();
267
		}
268
	}
269
270
	/**
271
	 * Called first as part of the container save procedure.
272
	 * Responsible for checking the request validity and
273
	 * calling the container-specific save() method
274
	 *
275
	 * @see save()
276
	 * @see is_valid_save()
277
	 */
278
	public function _save() {
279
		$param = func_get_args();
280
		if ( call_user_func_array( array( $this, '_is_valid_save' ), $param ) ) {
281
			call_user_func_array( array( $this, 'save' ), $param );
282
		}
283
	}
284
285
	/**
286
	 * Load submitted data and save each field in the container
287
	 *
288
	 * @see is_valid_save()
289
	 */
290
	public function save( $data = null ) {
291
		foreach ( $this->fields as $field ) {
292
			$field->set_value_from_input( stripslashes_deep( $_POST ) );
293
			$field->save();
294
		}
295
	}
296
297
	/**
298
	 * Checks whether the current save request is valid
299
	 *
300
	 * @return bool
301
	 */
302
	final protected function _is_valid_save() {
303
		$params = func_get_args();
304
		$is_valid_save = call_user_func_array( array( $this, 'is_valid_save' ), $params );
305
		return apply_filters( 'carbon_fields_container_is_valid_save', $is_valid_save, $this );
306
	}
307
308
	/**
309
	 * Checks whether the current save request is valid
310
	 *
311
	 * @return bool
312
	 */
313
	abstract protected function is_valid_save();
314
315
	/**
316
	 * Called first as part of the container attachment procedure.
317
	 * Responsible for checking it's OK to attach the container
318
	 * and if it is, calling the container-specific attach() method
319
	 *
320
	 * @see attach()
321
	 * @see is_valid_attach()
322
	 */
323
	public function _attach() {
324
		if ( ! $this->is_valid_attach() ) {
325
			return;
326
		}
327
		
328
		$param = func_get_args();
329
		call_user_func_array( array( $this, 'attach' ), $param );
330
331
		// Allow containers to activate but not load (useful in cases such as theme options)
332
		if ( $this->should_activate() ) {
333
			$this->activate();
334
		}
335
	}
336
337
	/**
338
	 * Attach the container rendering and helping methods
339
	 * to concrete WordPress Action hooks
340
	 */
341
	public function attach() {}
342
343
	/**
344
	 * Perform checks whether the container should be attached during the current request
345
	 *
346
	 * @return bool True if the container is allowed to be attached
347
	 */
348
	final public function is_valid_attach() {
349
		$is_valid_attach = $this->is_valid_attach_for_request();
350
		return apply_filters( 'carbon_fields_container_is_valid_attach', $is_valid_attach, $this );
351
	}
352
353
	/**
354
	 * Get environment array for page request (in admin)
355
	 *
356
	 * @return array
357
	 */
358
	abstract protected function get_environment_for_request();
359
360
	/**
361
	 * Check container attachment rules against current page request (in admin)
362
	 *
363
	 * @return bool
364
	 */
365
	abstract protected function is_valid_attach_for_request();
366
367
	/**
368
	 * Check if conditions pass for request
369
	 *
370
	 * @return bool
371
	 */
372
	protected function static_conditions_pass() {
373
		$environment = $this->get_environment_for_request();
374
		$static_condition_collection = $this->condition_collection->evaluate(
375
			$this->get_condition_types( false ),
376
			true
377
		);
378
		return $static_condition_collection->is_fulfilled( $environment );
379
	}
380
381
	/**
382
	 * Get environment array for object id
383
	 *
384
	 * @param integer $object_id
385
	 * @return array
386
	 */
387
	abstract protected function get_environment_for_object( $object_id );
388
389
	/**
390
	 * Check container attachment rules against object id
391
	 *
392
	 * @param int $object_id
393
	 * @return bool
394
	 */
395
	abstract public function is_valid_attach_for_object( $object_id );
396
397
	/**
398
	 * Check if all conditions pass for object
399
	 *
400
	 * @return bool
401
	 */
402
	protected function all_conditions_pass( $object_id ) {
403
		$environment = $this->get_environment_for_object( $object_id );
404
		return $this->condition_collection->is_fulfilled( $environment );
405
	}
406
407
	/**
408
	 * Whether this container is currently viewed.
409
	 */
410
	public function should_activate() {
411
		return $this->is_valid_attach();
412
	}
413
414
	/**
415
	 * Perform a check whether the current container has fields
416
	 *
417
	 * @return bool
418
	 */
419
	public function has_fields() {
420
		return (bool) $this->fields;
421
	}
422
423
	/**
424
	 * Returns the private container array of fields.
425
	 * Use only if you are completely aware of what you are doing.
426
	 *
427
	 * @return array
428
	 */
429
	public function get_fields() {
430
		return $this->fields;
431
	}
432
433
	/**
434
	 * Return root field from container with specified name
435
	 *
436
	 * @example crb_complex
437
	 *
438
	 * @param string $field_name
439
	 * @return Field
440
	 */
441
	public function get_root_field_by_name( $field_name ) {
442
		$fields = $this->get_fields();
443
		foreach ( $fields as $field ) {
444
			if ( $field->get_base_name() === $field_name ) {
445
				return $field;
446
			}
447
		}
448
		return null;
449
	}
450
451
	/**
452
	 * Get a regex to match field name patterns used to fetch specific fields
453
	 *
454
	 * @return string
455
	 */
456
	protected function get_field_pattern_regex() {
457
		// matches:
458
		// field_name
459
		// field_name[0]
460
		// field_name[0]:group_name
461
		// field_name:group_name
462
		$regex = '/
463
			\A
464
			(?P<field_name>[a-z0-9_]+)
465
			(?:\[(?P<group_index>\d+)\])?
466
			(?:' .  preg_quote( static::HIERARCHY_GROUP_SEPARATOR, '/' ). '(?P<group_name>[a-z0-9_]+))?
467
			\z
468
		/x';
469
		return $regex;
470
	}
471
472
	/**
473
	 * Return field from container with specified name
474
	 *
475
	 * @example $field_name = 'crb_complex/text_field'
476
	 * @example $field_name = 'crb_complex/complex_2'
477
	 * @example $field_name = 'crb_complex/complex_2:text_group/text_field'
478
	 * @example $field_name = 'crb_complex[3]/complex_2[1]:text_group/text_field'
479
	 *
480
	 * @param string $field_name
481
	 * @return Field
482
	 */
483
	public function get_field_by_name( $field_name ) {
484
		$hierarchy = array_filter( explode( static::HIERARCHY_FIELD_SEPARATOR, $field_name ) );
485
		$field = null;
486
487
		$field_group = $this->get_fields();
488
		$hierarchy_left = $hierarchy;
489
		$field_pattern_regex = $this->get_field_pattern_regex();
490
		$hierarchy_index = array();
491
492
		while ( ! empty( $hierarchy_left ) ) {
493
			$segment = array_shift( $hierarchy_left );
494
			$segment_pieces = array();
495
			if ( ! preg_match( $field_pattern_regex, $segment, $segment_pieces ) ) {
496
				return null;
497
			}
498
499
			$segment_field_name = $segment_pieces['field_name'];
500
			$segment_group_index = isset( $segment_pieces['group_index'] ) ? $segment_pieces['group_index'] : 0;
501
			$segment_group_name = isset( $segment_pieces['group_name'] ) ? $segment_pieces['group_name'] : Group_Field::DEFAULT_GROUP_NAME;
502
503
			foreach ( $field_group as $f ) {
504
				if ( $f->get_base_name() !== $segment_field_name ) {
505
					continue;
506
				}
507
508
				if ( empty( $hierarchy_left ) ) {
509
					$field = clone $f;
510
					$field->set_hierarchy_index( $hierarchy_index );
511
				} else {
512
					if ( ! is_a( $f, 'Carbon_Fields\\Field\\Complex_Field' ) ) {
513
						return null;
514
					}
515
516
					$group = $f->get_group_by_name( $segment_group_name );
517
					if ( ! $group ) {
518
						return null;
519
					}
520
					$field_group = $group->get_fields();
521
					$hierarchy_index[] = $segment_group_index;
522
				}
523
				break;
524
			}
525
		}
526
527
		return $field;
528
	}
529
530
	/**
531
	 * Perform checks whether there is a field registered with the name $name.
532
	 * If not, the field name is recorded.
533
	 *
534
	 * @param string $name
535
	 * @return boolean
536
	 */
537 View Code Duplication
	protected function register_field_name( $name ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
538
		if ( in_array( $name, $this->registered_field_names ) ) {
539
			Incorrect_Syntax_Exception::raise( 'Field name "' . $name . '" already registered' );
540
			return false;
541
		}
542
543
		$this->registered_field_names[] = $name;
544
		return true;
545
	}
546
547
	/**
548
	 * Return whether the datastore instance is the default one or has been overriden
549
	 *
550
	 * @return boolean
551
	 */
552 6
	public function has_default_datastore() {
553 6
		return $this->has_default_datastore;
554
	}
555
556
	/**
557
	 * Set datastore instance
558
	 *
559
	 * @param Datastore_Interface $datastore
560
	 * @return object $this
561
	 */
562 6 View Code Duplication
	public function set_datastore( Datastore_Interface $datastore, $set_as_default = false ) {
563 6
		if ( $set_as_default && ! $this->has_default_datastore() ) {
564 1
			return $this; // datastore has been overriden with a custom one - abort changing to a default one
565
		}
566 6
		$this->datastore = $datastore;
567 6
		$this->has_default_datastore = $set_as_default;
568
569 6
		foreach ( $this->fields as $field ) {
570
			$field->set_datastore( $this->get_datastore(), true );
571 6
		}
572 6
		return $this;
573
	}
574
575
	/**
576
	 * Get the DataStore instance
577
	 *
578
	 * @return Datastore_Interface $datastore
579
	 */
580 6
	public function get_datastore() {
581 6
		return $this->datastore;
582
	}
583
584
	/**
585
	 * Return WordPress nonce name used to identify the current container instance
586
	 *
587
	 * @return string
588
	 */
589
	protected function get_nonce_name() {
590
		return 'carbon_fields_container_' . $this->id . '_nonce';
591
	}
592
593
	/**
594
	 * Return WordPress nonce name used to identify the current container instance
595
	 *
596
	 * @return string
597
	 */
598
	protected function get_nonce_value() {
599
		return wp_create_nonce( $this->get_nonce_name() );
600
	}
601
602
	/**
603
	 * Check if the nonce is present in the request and that it is verified
604
	 *
605
	 * @return bool
606
	 */
607
	protected function verified_nonce_in_request() {
608
		$input = stripslashes_deep( $_REQUEST );
609
		$nonce_name = $this->get_nonce_name();
610
		$nonce_value = isset( $input[ $nonce_name ] ) ? $input[ $nonce_name ] : '';
611
		return wp_verify_nonce( $nonce_value, $nonce_name );
612
	}
613
614
	/**
615
	 * Internal function that creates the tab and associates it with particular field set
616
	 *
617
	 * @param string $tab_name
618
	 * @param array $fields
619
	 * @param int $queue_end
620
	 * @return object $this
621
	 */
622
	private function create_tab( $tab_name, $fields, $queue_end = self::TABS_TAIL ) {
623
		if ( isset( $this->tabs[ $tab_name ] ) ) {
624
			Incorrect_Syntax_Exception::raise( "Tab name duplication for $tab_name" );
625
		}
626
627
		if ( $queue_end === static::TABS_TAIL ) {
628
			$this->tabs[ $tab_name ] = array();
629
		} else if ( $queue_end === static::TABS_HEAD ) {
630
			$this->tabs = array_merge(
631
				array( $tab_name => array() ),
632
				$this->tabs
633
			);
634
		}
635
636
		foreach ( $fields as $field ) {
637
			$field_name = $field->get_name();
638
			$this->tabs[ $tab_name ][ $field_name ] = $field;
639
		}
640
641
		$this->settings['tabs'] = $this->get_tabs_json();
642
	}
643
644
	/**
645
	 * Whether the container is tabbed or not
646
	 *
647
	 * @return bool
648
	 */
649
	public function is_tabbed() {
650
		return (bool) $this->tabs;
651
	}
652
653
	/**
654
	 * Retrieve all fields that are not defined under a specific tab
655
	 *
656
	 * @return array
657
	 */
658
	protected function get_untabbed_fields() {
659
		$tabbed_fields_names = array();
660
		foreach ( $this->tabs as $tab_fields ) {
661
			$tabbed_fields_names = array_merge( $tabbed_fields_names, array_keys( $tab_fields ) );
662
		}
663
664
		$untabbed_fields = array_filter( $this->fields, function( $field ) use ( $tabbed_fields_names ) {
665
			return ! in_array( $field->get_name(), $tabbed_fields_names );
666
		} );
667
668
		return $untabbed_fields;
669
	}
670
671
	/**
672
	 * Retrieve all tabs.
673
	 * Create a default tab if there are any untabbed fields.
674
	 *
675
	 * @return array
676
	 */
677
	protected function get_tabs() {
678
		$untabbed_fields = $this->get_untabbed_fields();
679
680
		if ( ! empty( $untabbed_fields ) ) {
681
			$this->create_tab( __( 'General', 'carbon-fields' ), $untabbed_fields, static::TABS_HEAD );
682
		}
683
684
		return $this->tabs;
685
	}
686
687
	/**
688
	 * Build the tabs JSON
689
	 *
690
	 * @return array
691
	 */
692
	protected function get_tabs_json() {
693
		$tabs_json = array();
694
		$tabs = $this->get_tabs();
695
696
		foreach ( $tabs as $tab_name => $fields ) {
697
			foreach ( $fields as $field_name => $field ) {
698
				$tabs_json[ $tab_name ][] = $field_name;
699
			}
700
		}
701
702
		return $tabs_json;
703
	}
704
705
	/**
706
	 * Get custom CSS classes.
707
	 *
708
	 * @return array<string>
709
	 */
710
	public function get_classes() {
711
		return $this->classes;
712
	}
713
714
	/**
715
	 * Set CSS classes that the container should use.
716
	 *
717
	 * @param string|array<string> $classes
718
	 * @return object $this
719
	 */
720
	public function set_classes( $classes ) {
721
		$this->classes = Helper::sanitize_classes( $classes );
722
		return $this;
723
	}
724
725
	/**
726
	 * Returns an array that holds the container data, suitable for JSON representation.
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
		$conditions = $this->condition_collection->evaluate( $this->get_condition_types( true ), $this->get_environment_for_request(), array( 'CUSTOM' ) );
733
		$conditions = $this->condition_translator->fulfillable_to_foreign( $conditions );
734
735
		$container_data = array(
736
			'id' => $this->id,
737
			'type' => $this->type,
738
			'title' => $this->title,
739
			'classes' => $this->get_classes(),
740
			'settings' => $this->settings,
741
			'conditions' => $conditions,
742
			'fields' => array(),
743
			'nonce' => array(
744
				'name' => $this->get_nonce_name(),
745
				'value' => $this->get_nonce_value(),
746
			),
747
		);
748
749
		$fields = $this->get_fields();
750
		foreach ( $fields as $field ) {
751
			$field_data = $field->to_json( $load );
752
			$container_data['fields'][] = $field_data;
753
		}
754
755
		return $container_data;
756
	}
757
758
	/**
759
	 * COMMON USAGE METHODS
760
	 */
761
762
	/**
763
	 * Append array of fields to the current fields set. All items of the array
764
	 * must be instances of Field and their names should be unique for all
765
	 * Carbon containers.
766
	 * If a field does not have DataStore already, the container datastore is
767
	 * assigned to them instead.
768
	 *
769
	 * @param array $fields
770
	 * @return object $this
771
	 */
772
	public function add_fields( $fields ) {
773
		foreach ( $fields as $field ) {
774
			if ( ! is_a( $field, 'Carbon_Fields\\Field\\Field' ) ) {
775
				Incorrect_Syntax_Exception::raise( 'Object must be of type Carbon_Fields\\Field\\Field' );
776
				return $this;
777
			}
778
779
			$unique = $this->register_field_name( $field->get_name() );
780
			if ( ! $unique ) {
781
				return $this;
782
			}
783
784
			$field->set_context( $this->type );
785
			if ( ! $field->get_datastore() ) {
786
				$field->set_datastore( $this->get_datastore(), $this->has_default_datastore() );
787
			}
788
		}
789
790
		$this->fields = array_merge( $this->fields, $fields );
791
792
		return $this;
793
	}
794
795
	/**
796
	 * Configuration function for adding tab with fields
797
	 *
798
	 * @param string $tab_name
799
	 * @param array $fields
800
	 * @return object $this
801
	 */
802
	public function add_tab( $tab_name, $fields ) {
803
		$this->add_fields( $fields );
804
		$this->create_tab( $tab_name, $fields );
805
		return $this;
806
	}
807
808
	/**
809
	 * Proxy function to set attachment conditions
810
	 *
811
	 * @see    Fulfillable_Collection::where()
812
	 * @return Container $this
813
	 */
814
	public function where() {
815
		call_user_func_array( array( $this->condition_collection, 'where' ), func_get_args() );
816
		return $this;
817
	}
818
819
	/**
820
	 * Proxy function to set attachment conditions
821
	 *
822
	 * @see    Fulfillable_Collection::or_where()
823
	 * @return Container $this
824
	 */
825
	public function or_where() {
826
		call_user_func_array( array( $this->condition_collection, 'or_where' ), func_get_args() );
827
		return $this;
828
	}
829
}
830