Completed
Push — milestone/2_0/container-condit... ( af96ef...c41f81 )
by
unknown
03:01
created

Container::factory()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 1

Importance

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