Completed
Pull Request — master (#25)
by
unknown
03:51
created

Container::get_datastore()   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 2
Bugs 1 Features 0
Metric Value
c 2
b 1
f 0
dl 0
loc 3
rs 10
ccs 0
cts 2
cp 0
cc 1
eloc 2
nc 1
nop 0
crap 2
1
<?php
2
3
namespace Carbon_Fields\Container;
4
5
use Carbon_Fields\Field\Field;
6
use Carbon_Fields\Datastore\Datastore_Interface;
7
use Carbon_Fields\Exception\Incorrect_Syntax_Exception;
8
9
/**
10
 * Base container class.
11
 * Defines the key container methods and their default implementations.
12
 */
13
abstract class Container {
14
	/**
15
	 * Where to put a particular tab -- at the head or the tail. Tail by default
16
	 */
17
	const TABS_TAIL = 1;
18
	const TABS_HEAD = 2;
19
20
	/**
21
	 * List of registered unique panel identificators
22
	 *
23
	 * @see verify_unique_panel_id()
24
	 * @var array
25
	 */
26
	public static $registered_panel_ids = array();
27
28
	/**
29
	 * List of registered unique field names
30
	 *
31
	 * @see verify_unique_field_name()
32
	 * @var array
33
	 */
34
	static protected $registered_field_names = array();
35
36
	/**
37
	 * List of containers created via factory that
38
	 * should be initialized
39
	 *
40
	 * @see verify_unique_field_name()
41
	 * @var array
42
	 */
43
	static protected $init_containers = array();
44
45
	/**
46
	 * List of containers attached to the current page view
47
	 *
48
	 * @see _attach()
49
	 * @var array
50
	 */
51
	public static $active_containers = array();
52
53
	/**
54
	 * List of fields attached to the current page view
55
	 *
56
	 * @see _attach()
57
	 * @var array
58
	 */
59
	static protected $active_fields = array();
60
61
	/**
62
	 * Stores all the container Backbone templates
63
	 *
64
	 * @see factory()
65
	 * @see add_template()
66
	 * @var array
67
	 */
68
	protected $templates = array();
69
70
	/**
71
	 * Tabs available
72
	 */
73
	protected $tabs = array();
74
75
	/**
76
	 * List of default container settings
77
	 *
78
	 * @see init()
79
	 * @var array
80
	 */
81
	public $settings = array();
82
83
	/**
84
	 * Title of the container
85
	 *
86
	 * @var string
87
	 */
88
	public $title = '';
89
90
	/**
91
	 * Whether the container was setup
92
	 *
93
	 * @var bool
94
	 */
95
	public $setup_ready = false;
96
97
	/**
98
	 * List of notification messages to be displayed on the front-end
99
	 *
100
	 * @var array
101
	 */
102
	protected $notifications = array();
103
104
	/**
105
	 * List of error messages to be displayed on the front-end
106
	 *
107
	 * @var array
108
	 */
109
	protected $errors = array();
110
111
	/**
112
	 * List of container fields
113
	 *
114
	 * @see add_fields()
115
	 * @var array
116
	 */
117
	protected $fields = array();
118
119
	/**
120
	 * Container DataStore. Propagated to all container fields
121
	 *
122
	 * @see set_datastore()
123
	 * @see get_datastore()
124
	 * @var object
125
	 */
126
	protected $store;
127
128
	/**
129
	 * Create a new container of type $type and name $name and label $label.
130
	 *
131
	 * @param string $type
132
	 * @param string $id Unique id of the container
133
	 * @param string $name Human-readable name of the container
134
	 * @return object $container
135
	 **/
136 11
	public static function factory( $type, $id, $name = '' ) {
137
		// backward compatibility: post_meta container used to be called custom_fields
138 11
		if ( $type === 'custom_fields' ) {
139 1
			$type = 'post_meta';
140 1
		}
141
142 11
		$type = str_replace( ' ', '_', ucwords( str_replace( '_', ' ', $type ) ) );
143
144 11
		$class = __NAMESPACE__ . '\\' . $type . '_Container';
145
146 11 View Code Duplication
		if ( ! class_exists( $class ) ) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
147 3
			Incorrect_Syntax_Exception::raise( 'Unknown container "' . $type . '".' );
148 1
			$class = __NAMESPACE__ . '\\Broken_Container';
149 1
		}
150
151 9
		$container = new $class( $id, $name );
152 8
		$container->type = $type;
153 8
		$container->add_template( $type, array( $container, 'template' ) );
154
155 8
		self::$init_containers[] = $container;
156
157 8
		return $container;
158
	}
159
160
	/**
161
	 * An alias of factory().
162
	 *
163
	 * @see Container::factory()
164
	 **/
165 11
	public static function make( $type, $id, $name = '' ) {
166 11
		return self::factory( $type, $id, $name );
167
	}
168
169
	/**
170
	 * Initialize containers created via factory
171
	 *
172
	 * @return object
173
	 **/
174
	public static function init_containers() {
175
		while ( ( $container = array_shift( self::$init_containers ) ) ) {
176
			$container->init();
177
		}
178
179
		return $container;
180
	}
181
182
	/**
183
	 * Returns all the active containers created via factory
184
	 *
185
	 * @return array
186
	 **/
187
	public static function get_active_containers() {
188
		return self::$active_containers;
189
	}
190
191
	/**
192
	 * Adds a container to the active containers array and triggers an action
193
	 **/
194
	public static function add_active_container( $container ) {
195
		self::$active_containers[] = $container;
196
197
		do_action( 'crb_container_activated', $container );
198
	}
199
200
	/**
201
	 * Returns all the active fields created via factory
202
	 *
203
	 * @return array
204
	 **/
205
	public static function get_active_fields() {
206
		return self::$active_fields;
207
	}
208
209
	/**
210
	 * Adds a field to the active fields array and triggers an action
211
	 **/
212
	public static function add_active_field( $field ) {
213
		self::$active_fields[] = $field;
214
215
		if ( method_exists( $field, 'get_fields' ) ) {
216
			$fields = $field->get_fields();
217
218
			foreach ( $fields as $inner_field ) {
219
				self::add_active_field( $inner_field );
220
			}
221
		}
222
223
		do_action( 'crb_field_activated', $field );
224
	}
225
226
	/**
227
	 * Perform instance initialization after calling setup()
228
	 **/
229
	abstract public function init();
230
231
	/**
232
	 * Prints the container Underscore template
233
	 **/
234
	public function template() {
235
		?>
236
		<div class="{{{ classes.join(' ') }}}">
237
			<# _.each(fields, function(field) { #>
238
				<div class="{{{ field.classes.join(' ') }}}">
239
					<label for="{{{ field.id }}}">
240
						{{ field.label }}
241
242
						<# if (field.required) { #>
243
							 <span class="carbon-required">*</span>
244
						<# } #>
245
					</label>
246
247
					<div class="field-holder {{{ field.id }}}"></div>
248
249
					<# if (field.help_text) { #>
250
						<em class="help-text">
251
							{{{ field.help_text }}}
252
						</em>
253
					<# } #>
254
255
					<em class="carbon-error"></em>
256
				</div>
257
			<# }); #>
258
		</div>
259
		<?php
260
	}
261
262
	/**
263
	 * Create a new container
264
	 *
265
	 * @param string $id Unique id of the container
266
	 * @param string $title Title of the container
267
	 **/
268 9 View Code Duplication
	public function __construct( $id, $title = '' ) {
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...
269 9
		if ( empty( $id ) ) {
270 1
			Incorrect_Syntax_Exception::raise( 'Empty container id is not supported' );
271
		}
272
273 8
		if ( empty( $title ) ) {
274 8
			$title = $id;
275 8
		}
276
277 8
		$this->title = $title;
278 8
		$this->id = preg_replace( '~\W~u', '', remove_accents( $id ) );
279
280 8
		self::verify_unique_panel_id( $this->id );
281
282 8
		$this->load_scripts_styles();
283 8
	}
284
285
	/**
286
	 * Load the admin scripts and styles.
287
	 **/
288
	public function load_scripts_styles() {
289
		add_action( 'admin_print_scripts', array( $this, 'admin_hook_scripts' ) );
290
		add_action( 'admin_print_styles', array( $this, 'admin_hook_styles' ) );
291
	}
292
293
	/**
294
	 * Update container settings and begin initialization
295
	 *
296
	 * @see init()
297
	 * @param array $settings
298
	 **/
299
	public function setup( $settings = array() ) {
300
		if ( $this->setup_ready ) {
301
			Incorrect_Syntax_Exception::raise( 'Panel "' . $this->title . '" already setup' );
302
		}
303
304
		$this->check_setup_settings( $settings );
305
306
		$this->settings = array_merge( $this->settings, $settings );
307
308
		foreach ( $this->settings as $key => $value ) {
309
			if ( is_null( $value ) ) {
310
				unset( $this->settings[ $key ] );
311
			}
312
		}
313
314
		$this->setup_ready = true;
315
316
		return $this;
317
	}
318
319
	/**
320
	 * Check if all required container settings have been specified
321
	 *
322
	 * @param array $settings Container settings
323
	 **/
324
	public function check_setup_settings( &$settings = array() ) {
325
		$invalid_settings = array_diff_key( $settings, $this->settings );
326 View Code Duplication
		if ( ! empty( $invalid_settings ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
327
			Incorrect_Syntax_Exception::raise( 'Invalid settings supplied to setup(): "' . implode( '", "', array_keys( $invalid_settings ) ) . '"' );
328
		}
329
	}
330
331
	/**
332
	 * Called first as part of the container save procedure.
333
	 * Responsible for checking the request validity and
334
	 * calling the container-specific save() method
335
	 *
336
	 * @see save()
337
	 * @see is_valid_save()
338
	 **/
339
	public function _save() {
340
		$param = func_get_args();
341
		if ( call_user_func_array( array( $this, 'is_valid_save' ), $param ) ) {
342
			call_user_func_array( array( $this, 'save' ), $param );
343
		}
344
	}
345
346
	/**
347
	 * Load submitted data and save each field in the container
348
	 *
349
	 * @see is_valid_save()
350
	 **/
351
	public function save( $user_data ) {
352
		foreach ( $this->fields as $field ) {
353
			$field->set_value_from_input();
354
			$field->save();
355
		}
356
	}
357
358
	/**
359
	 * Checks whether the current request is valid
360
	 *
361
	 * @return bool
362
	 **/
363
	public function is_valid_save() {
364
		return false;
365
	}
366
367
	/**
368
	 * Load the value for each field in the container.
369
	 * Could be used internally during container rendering
370
	 **/
371
	public function load() {
372
		foreach ( $this->fields as $field ) {
373
			$field->load();
374
		}
375
	}
376
377
378
	/**
379
	 * Called first as part of the container attachment procedure.
380
	 * Responsible for checking it's OK to attach the container
381
	 * and if it is, calling the container-specific attach() method
382
	 *
383
	 * @see attach()
384
	 * @see is_valid_attach()
385
	 **/
386
	public function _attach() {
387
		$param = func_get_args();
388
		if ( call_user_func_array( array( $this, 'is_valid_attach' ), $param ) ) {
389
			call_user_func_array( array( $this, 'attach' ), $param );
390
391
			if ( call_user_func_array( array( $this, 'is_active' ), $param ) ) {
392
				self::add_active_container( $this );
393
394
				$fields = $this->get_fields();
395
				foreach ( $fields as $field ) {
396
					self::add_active_field( $field );
397
				}
398
			}
399
		}
400
	}
401
402
	/**
403
	 * Returns all the Backbone templates
404
	 *
405
	 * @return array
406
	 **/
407
	public function get_templates() {
408
		return $this->templates;
409
	}
410
411
	/**
412
	 * Adds a new Backbone template
413
	 **/
414
	public function add_template( $name, $callback ) {
415
		$this->templates[ $name ] = $callback;
416
	}
417
418
	/**
419
	 * Attach the container rendering and helping methods
420
	 * to concrete WordPress Action hooks
421
	 **/
422
	public function attach() {}
423
424
	/**
425
	 * Perform checks whether the container is active for current request
426
	 *
427
	 * @return bool True if the container is active
428
	 **/
429
	public function is_active() {
430
		return $this->is_valid_attach();
431
	}
432
433
	/**
434
	 * Perform checks whether the container should be attached during the current request
435
	 *
436
	 * @return bool True if the container is allowed to be attached
437
	 **/
438
	public function is_valid_attach() {
439
		return true;
440
	}
441
442
	/**
443
	 * Revert the result of attach()
444
	 **/
445
	public function detach() {
446
		self::drop_unique_panel_id( $this->id );
447
448
		// unregister field names
449
		foreach ( $this->fields as $field ) {
450
			$this->drop_unique_field_name( $field->get_name() );
451
		}
452
	}
453
454
	/**
455
	 * Append array of fields to the current fields set. All items of the array
456
	 * must be instances of Field and their names should be unique for all
457
	 * Carbon containers.
458
	 * If a field does not have DataStore already, the container data store is
459
	 * assigned to them instead.
460
	 *
461
	 * @param array $fields
462
	 **/
463
	public function add_fields( $fields ) {
464
		foreach ( $fields as $field ) {
465
			if ( ! is_a( $field, 'Carbon_Fields\\Field\\Field' ) ) {
466
				Incorrect_Syntax_Exception::raise( 'Object must be of type Carbon_Fields\\Field\\Field' );
467
			}
468
469
			$this->verify_unique_field_name( $field->get_name() );
470
471
			$field->set_context( $this->type );
472
			if ( ! $field->get_datastore() ) {
473
				$field->set_datastore( $this->store );
474
			}
475
		}
476
477
		$this->fields = array_merge( $this->fields, $fields );
478
479
		return $this;
480
	}
481
482
	/**
483
	 * Configuration function for adding tab with fields
484
	 */
485
	public function add_tab( $tab_name, $fields ) {
486
		$this->add_template( 'tabs', array( $this, 'template_tabs' ) );
487
488
		$this->add_fields( $fields );
489
		$this->create_tab( $tab_name, $fields );
490
491
		return $this;
492
	}
493
494
	/**
495
	 * Internal function that creates the tab and associates it with particular field set
496
	 */
497
	private function create_tab( $tab_name, $fields, $queue_end = self::TABS_TAIL ) {
498
		if ( isset( $this->tabs[ $tab_name ] ) ) {
499
			Incorrect_Syntax_Exception::raise( "Tab name duplication for $tab_name" );
500
		}
501
502
		if ( $queue_end === self::TABS_TAIL ) {
503
			$this->tabs[ $tab_name ] = array();
504
		} else if ( $queue_end === self::TABS_HEAD ) {
505
			$this->tabs = array_merge(
506
				array( $tab_name => array() ),
507
				$this->tabs
508
			);
509
		}
510
511
		foreach ( $fields as $field ) {
512
			$field_name = $field->get_name();
513
			$this->tabs[ $tab_name ][ $field_name ] = $field;
514
		}
515
516
		$this->settings['tabs'] = $this->get_tabs_json();
517
	}
518
519
	/**
520
	 * Whether the container is tabbed or not
521
	 */
522
	public function is_tabbed() {
523
		return (bool) $this->tabs;
524
	}
525
526
	/**
527
	 * Retrieve all fields that are not defined under a specific tab
528
	 */
529
	public function get_untabbed_fields() {
530
		$tabbed_fields_names = array();
531
		foreach ( $this->tabs as $tab_fields ) {
532
			$tabbed_fields_names = array_merge( $tabbed_fields_names, array_keys( $tab_fields ) );
533
		}
534
535
		$all_fields_names = array();
536
		foreach ( $this->fields as $field ) {
537
			$all_fields_names[] = $field->get_name();
538
		}
539
540
		$fields_not_in_tabs = array_diff( $all_fields_names, $tabbed_fields_names );
541
542
		$untabbed_fields = array();
543
		foreach ( $this->fields as $field ) {
544
			if ( in_array( $field->get_name(), $fields_not_in_tabs ) ) {
545
				$untabbed_fields[] = $field;
546
			}
547
		}
548
549
		return $untabbed_fields;
550
	}
551
552
	/**
553
	 * Retrieve all tabs.
554
	 * Create a default tab if there are any untabbed fields.
555
	 */
556
	public function get_tabs() {
557
		$untabbed_fields = $this->get_untabbed_fields();
558
559
		if ( ! empty( $untabbed_fields ) ) {
560
			$this->create_tab( __( 'General', 'carbon_fields' ), $untabbed_fields, self::TABS_HEAD );
561
		}
562
563
		return $this->tabs;
564
	}
565
566
	/**
567
	 * Build the tabs JSON
568
	 */
569
	public function get_tabs_json() {
570
		$tabs_json = array();
571
		$tabs = $this->get_tabs();
572
573
		foreach ( $tabs as $tab_name => $fields ) {
574
			foreach ( $fields as $field_name => $field ) {
575
				$tabs_json[ $tab_name ][] = $field_name;
576
			}
577
		}
578
579
		return $tabs_json;
580
	}
581
582
	/**
583
	 * Returns the private container array of fields.
584
	 * Use only if you are completely aware of what you are doing.
585
	 *
586
	 * @return array
587
	 **/
588
	public function get_fields() {
589
		return $this->fields;
590
	}
591
592
	/**
593
	 * Perform a check whether the current container has fields
594
	 *
595
	 * @return bool
596
	 **/
597
	public function has_fields() {
598
		return (bool) $this->fields;
599
	}
600
601
	/**
602
	 * Perform checks whether there is a container registered with identificator $id
603
	 */
604
	public static function verify_unique_panel_id( $id ) {
605
		if ( in_array( $id, self::$registered_panel_ids ) ) {
606
			Incorrect_Syntax_Exception::raise( 'Panel ID "' . $id . '" already registered' );
607
		}
608
609
		self::$registered_panel_ids[] = $id;
610
	}
611
612
613
	/**
614
	 * Remove container identificator $id from the list of unique container ids
615
	 *
616
	 * @param string $id
617
	 **/
618
	public static function drop_unique_panel_id( $id ) {
619
		if ( in_array( $id, self::$registered_panel_ids ) ) {
620
			unset( self::$registered_panel_ids[ array_search( $id, self::$registered_panel_ids ) ] );
621
		}
622
	}
623
624
	/**
625
	 * Perform checks whether there is a field registered with the name $name.
626
	 * If not, the field name is recorded.
627
	 *
628
	 * @param string $name
629
	 **/
630
	public function verify_unique_field_name( $name ) {
631
		if ( in_array( $name, self::$registered_field_names ) ) {
632
			Incorrect_Syntax_Exception::raise( 'Field name "' . $name . '" already registered' );
633
		}
634
635
		self::$registered_field_names[] = $name;
636
	}
637
638
	/**
639
	 * Remove field name $name from the list of unique field names
640
	 *
641
	 * @param string $name
642
	 **/
643
	public function drop_unique_field_name( $name ) {
644
		$index = array_search( $name, self::$registered_field_names );
645
		if ( $index !== false ) {
646
			unset( self::$registered_field_names[ $index ] );
647
		}
648
	}
649
650
	/**
651
	 * Assign DataStore instance for use by the container fields
652
	 *
653
	 * @param object $store
654
	 **/
655
	public function set_datastore( $store ) {
656
		$this->store = $store;
657
658
		foreach ( $this->fields as $field ) {
659
			$field->set_datastore( $this->store );
660
		}
661
	}
662
663
	/**
664
	 * Return the DataStore instance used by container fields
665
	 *
666
	 * @return object $store
667
	 **/
668
	public function get_datastore() {
669
		return $this->store;
670
	}
671
672
	/**
673
	 * Return WordPress nonce name used to identify the current container instance
674
	 *
675
	 * @return string
676
	 **/
677
	public function get_nonce_name() {
678
		return 'carbon_panel_' . $this->id . '_nonce';
679
	}
680
681
	/**
682
	 * Return WordPress nonce field
683
	 *
684
	 * @return string
685
	 **/
686
	public function get_nonce_field() {
687
		return wp_nonce_field( $this->get_nonce_name(), $this->get_nonce_name(), /*referer?*/ false, /*echo?*/ false );
688
	}
689
690
	/**
691
	 * Returns an array that holds the container data, suitable for JSON representation.
692
	 * This data will be available in the Underscore template and the Backbone Model.
693
	 *
694
	 * @param bool $load  Should the value be loaded from the database or use the value from the current instance.
695
	 * @return array
696
	 */
697
	public function to_json( $load ) {
698
		$container_data = array(
699
			'id' => $this->id,
700
			'type' => $this->type,
701
			'title' => $this->title,
702
			'settings' => $this->settings,
703
			'fields' => array(),
704
		);
705
706
		$fields = $this->get_fields();
707
		foreach ( $fields as $field ) {
708
			$field_data = $field->to_json( $load );
709
			$container_data['fields'][] = $field_data;
710
		}
711
712
		return $container_data;
713
	}
714
715
	/**
716
	 * Underscore template for tabs
717
	 */
718
	public function template_tabs() {
719
		?>
720
		<div class="carbon-tabs">
721
			<ul class="carbon-tabs-nav">
722
				<# _.each(tabs, function (tab, tabName) { #>
723
					<li><a href="#" data-id="{{{ tab.id }}}">{{{ tabName }}}</a></li>
724
				<# }); #>
725
			</ul> 
726
727
			<div class="carbon-tabs-body">
728
				<# _.each(tabs, function (tab) { #>
729
					<div class="carbon-fields-collection carbon-tab">
730
						{{{ tab.html }}}
731
					</div>
732
				<# }); #>
733
			</div>
734
		</div>
735
		<?php
736
	}
737
738
	/**
739
	 * Enqueue admin scripts
740
	 */
741
	public function admin_hook_scripts() {
742
		wp_enqueue_script( 'carbon-containers', \Carbon_Fields\URL . '/assets/js/containers.js', array( 'carbon-app' ) );
743
744
		wp_localize_script( 'carbon-containers', 'carbon_containers_l10n',
745
			array(
746
				'please_fill_the_required_fields' => __( 'Please fill out all required fields highlighted below.', 'carbon_fields' ),
747
				'changes_made_save_alert' => __( 'The changes you made will be lost if you navigate away from this page.', 'carbon_fields' ),
748
			)
749
		);
750
	}
751
752
	/**
753
	 * Enqueue admin styles
754
	 */
755
	public function admin_hook_styles() {
756
		wp_enqueue_style( 'carbon-main', \Carbon_Fields\URL . '/assets/css/main.css' );
757
	}
758
} // END Container
759
760