Completed
Push — milestone/2.0 ( 9cc217...20a31b )
by
unknown
02:47
created

Complex_Field::get_group_by_name()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 6
nc 3
nop 1
dl 0
loc 11
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace Carbon_Fields\Field;
4
5
use Carbon_Fields\Datastore\Datastore_Interface;
6
use Carbon_Fields\Helper\Helper;
7
use Carbon_Fields\Field\Field;
8
use Carbon_Fields\Field\Group_Field;
9
use Carbon_Fields\Value_Set\Value_Set;
10
use Carbon_Fields\Exception\Incorrect_Syntax_Exception;
11
12
/**
13
 * Complex field class.
14
 * Allows nested repeaters with multiple field groups to be created.
15
 */
16
class Complex_Field extends Field {
17
18
	const LAYOUT_GRID = 'grid'; // default
19
20
	const LAYOUT_TABBED_HORIZONTAL = 'tabbed-horizontal';
21
22
	const LAYOUT_TABBED_VERTICAL = 'tabbed-vertical';
23
24
	const GROUP_TYPE_KEY = '_type';
25
26
	/**
27
	 * Default field value
28
	 *
29
	 * @var array
30
	 */
31
	protected $default_value = array();
32
33
	protected $layout = self::LAYOUT_GRID;
34
35
	protected $value_tree = array();
36
37
	protected $fields = array();
38
39
	protected $groups = array();
40
41
	protected $values_min = -1;
42
43
	protected $values_max = -1;
44
45
	protected $collapsed = false;
46
47
	public $labels = array(
48
		'singular_name' => 'Entry',
49
		'plural_name' => 'Entries',
50
	);
51
52
	/**
53
	 * Create a field from a certain type with the specified label.
54
	 * @param string $name  Field name
55
	 * @param string $label Field label
56
	 */
57
	protected function __construct( $name, $label ) {
58
		$this->value = new Value_Set( Value_Set::TYPE_MULTIPLE_VALUES );
59
		parent::__construct( $name, $label );
60
	}
61
62
	/**
63
	 * Initialization tasks.
64
	 */
65
	public function init() {
66
		$this->labels = array(
67
			'singular_name' => __( 'Entry', \Carbon_Fields\TEXT_DOMAIN ),
68
			'plural_name' => __( 'Entries', \Carbon_Fields\TEXT_DOMAIN ),
69
		);
70
71
		// Include the complex group Underscore templates
72
		$this->add_template( 'Complex-Group', array( $this, 'template_group' ) );
73
		$this->add_template( 'Complex-Group-Tab-Item', array( $this, 'template_group_tab_item' ) );
74
75
		parent::init();
76
	}
77
78
	/**
79
	 * Add a set/group of fields.
80
	 *
81
	 * Accepted param variations:
82
	 *   - array<Field> $fields
83
	 *   - string $group_name, array<Field> $fields
84
	 *   - string $group_name, string $group_label, array<Field> $fields
85
	 * 
86
	 * @return $this
87
	 */
88
	public function add_fields() {
89
		$argv = func_get_args();
90
		$argc = count( $argv );
91
		$fields = array();
0 ignored issues
show
Unused Code introduced by
$fields is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
92
		$name = '';
93
		$label = null;
94
95
		if ( $argc >= 2 ) {
96
			$name = $argv[0];
97
		}
98
		
99
		if ( $argc >= 3 ) {
100
			$label = $argv[1];
101
		}
102
103
		$fields = $argv[ $argc - 1 ];
104
		$name = ! empty( $name ) ? $name : Group_Field::DEFAULT_GROUP_NAME;
105
106
		if ( array_key_exists( $name, $this->groups ) ) {
107
			Incorrect_Syntax_Exception::raise( 'Group with name "' . $name . '" in Complex Field "' . $this->get_label() . '" already exists.' );
108
		}
109
110
		foreach ( $fields as $field ) {
111
			if ( $field->get_base_name() === static::GROUP_TYPE_KEY ) {
112
				Incorrect_Syntax_Exception::raise( '"' . static::GROUP_TYPE_KEY . '" is a reserved keyword for Complex fields and cannot be used for a field name.' );
113
			}
114
			$field->set_hierarchy( array_merge( $this->get_hierarchy(), array( $this->get_base_name() ) ) );
115
		}
116
117
		$group = new Group_Field( $name, $label, $fields );
118
119
		$this->groups[ $group->get_name() ] = $group;
120
121
		return $this;
122
	}
123
124
	/**
125
	 * Set the group label Underscore template.
126
	 *
127
	 * @param  string|callable $template
128
	 * @return $this
129
	 */
130
	public function set_header_template( $template ) {
131
		if ( count( $this->groups ) === 0 ) {
132
			Incorrect_Syntax_Exception::raise( "Can't set group label template. There are no present groups for Complex Field " . $this->get_label() . '.' );
133
		}
134
135
		$template = is_callable( $template ) ? call_user_func( $template ) : $template;
136
137
		// Assign the template to the group that was added last
138
		$values = array_values( $this->groups );
139
		$group = end( $values );
140
		$group->set_label_template( $template );
141
142
		// Include the group label Underscore template
143
		$this->add_template( $group->get_group_id(), array( $group, 'template_label' ) );
144
145
		$this->groups[ $group->get_name() ] = $group;
146
147
		return $this;
148
	}
149
150
	/**
151
	 * Retrieve all groups of fields.
152
	 *
153
	 * @return array $fields
154
	 */
155
	public function get_fields() {
156
		$fields = array();
157
158
		foreach ( $this->groups as $group ) {
159
			$group_fields = $group->get_fields();
160
161
			$fields = array_merge( $fields, $group_fields );
162
		}
163
164
		return $fields;
165
	}
166
167
	/**
168
	 * Set the field labels.
169
	 * Currently supported values:
170
	 *  - singular_name - the singular entry label
171
	 *  - plural_name - the plural entries label
172
	 *
173
	 * @param  array $labels Labels
174
	 */
175
	public function setup_labels( $labels ) {
176
		$this->labels = array_merge( $this->labels, $labels );
177
		return $this;
178
	}
179
180
	/**
181
	 * Set the datastore of this field.
182
	 *
183
	 * @param Datastore_Interface $datastore
184
	 */
185 View Code Duplication
	public function set_datastore( Datastore_Interface $datastore, $set_as_default = false ) {
186
		if ( $set_as_default && ! $this->has_default_datastore() ) {
187
			return $this; // datastore has been overriden with a custom one - abort changing to a default one
188
		}
189
		$this->datastore = $datastore;
190
		$this->has_default_datastore = $set_as_default;
191
192
		foreach ( $this->groups as $group ) {
193
			$group->set_datastore( $this->get_datastore(), true );
194
		}
195
		return $this;
196
	}
197
198
	/**
199
	 * Return a clone of a field with hierarchy settings applied
200
	 *
201
	 * @return bool
202
	 **/
203
	public function get_clone_under_field_in_hierarchy( $field, $parent_field, $entry_index = 0 ) {
204
		$clone = clone $field;
205
		$clone->set_hierarchy( array_merge( $parent_field->get_hierarchy(), array( $parent_field->get_base_name() ) ) );
206
		$clone->set_hierarchy_index( array_merge( $parent_field->get_hierarchy_index(), array( $entry_index ) ) );
207
		return $clone;
208
	}
209
210
	/**
211
	 * Load the field value from an input array based on it's name.
212
	 *
213
	 * @param array $input (optional) Array of field names and values. Defaults to $_POST
214
	 **/
215
	public function set_value_from_input( $input = null ) {
216
		$value_tree = array();
217
218
		if ( is_null( $input ) ) {
219
			$input = $_POST;
0 ignored issues
show
introduced by
Detected access of super global var $_POST, probably need manual inspection.
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
220
		}
221
222
		if ( ! isset( $input[ $this->get_name() ] ) ) {
223
			return;
224
		}
225
226
		$input_groups = $input[ $this->get_name() ];
227
		$input_group_index = 0;
228
		foreach ( $input_groups as $values ) {
229
			if ( ! isset( $values[ static::GROUP_TYPE_KEY ] ) || ! isset( $this->groups[ $values[ static::GROUP_TYPE_KEY ] ] ) ) {
230
				continue;
231
			}
232
233
			$group = $this->get_group_by_name( $values[ static::GROUP_TYPE_KEY ] );
234
			$value_group = array( static::GROUP_TYPE_KEY => $values[ static::GROUP_TYPE_KEY ] );
235
			unset( $values[ static::GROUP_TYPE_KEY ] );
236
237
			$group_fields = $group->get_fields();
238
239
			// trim input values to those used by the field
240
			$group_field_names = array_flip( $group->get_field_names() );
241
			$values = array_intersect_key( $values, $group_field_names );
242
			
243
			foreach ( $group_fields as $field ) {
244
				$tmp_field = $this->get_clone_under_field_in_hierarchy( $field, $this, $input_group_index );
245
246
				$tmp_field->set_value_from_input( $values );
247
				if ( is_a( $tmp_field, get_class() ) ) {
248
					$value_group[ $tmp_field->get_base_name() ] = $tmp_field->get_value_tree();
249
				} else {
250
					$value_group[ $tmp_field->get_base_name() ] = array(
251
						'value_set' => $tmp_field->value()->get_set(),
252
					);
253
				}
254
			}
255
256
			$value_tree['groups'][] = $value_group;
257
			$value_tree['value_set'][] = array(
258
				Value_Set::VALUE_PROPERTY => $group->get_name(),
259
			);
260
			$input_group_index++;
261
		}
262
263
		$this->set_value( $value_tree['value_set'] );
264
		$this->set_value_tree( $value_tree );
265
	}
266
267
	protected function get_prefilled_field_groups( $value_tree ) {
268
		$fields = array();
269
270
		if ( empty( $value_tree ) ) {
271
			return $fields;
272
		}
273
274
		foreach ( $value_tree['value_set'] as $entry_index => $value ) {
275
			$group_name = $value[ Value_Set::VALUE_PROPERTY ];
276
			$group = $this->get_group_by_name( $group_name );
277
			$group_fields = $group->get_fields();
278
			$fields[ $entry_index ] = array(
279
				static::GROUP_TYPE_KEY => $group->get_name(),
280
			);
281
			$group_values = array();
282
			if ( isset( $value_tree['groups'][ $entry_index ] ) ) {
283
				$group_values = $value_tree['groups'][ $entry_index ];
284
			}
285
286
			foreach ( $group_fields as $field ) {
287
				$clone = $this->get_clone_under_field_in_hierarchy( $field, $this, $entry_index );
288
				if ( isset( $group_values[ $clone->get_base_name() ] ) ) {
289
					$group_value = $group_values[ $clone->get_base_name() ];
290
					
291
					if ( isset( $group_value['value_set'] ) ) {
292
						$clone->set_value( $group_value['value_set'] );
293
					}
294
295
					if ( is_a( $clone, get_class() ) ) {
296
						$clone->set_value_tree( $group_value );
297
					}
298
				}
299
				$fields[ $entry_index ][] = $clone;
300
			}
301
		}
302
303
		return $fields;
304
	}
305
306
	/**
307
	 * Load all groups of fields and their data.
308
	 */
309
	public function load() {
310
		$raw_value_set_tree = $this->get_datastore()->load( $this );
311
		$value = null;
312
		if ( isset( $raw_value_set_tree[ $this->get_base_name() ] ) ) {
313
			$value = $raw_value_set_tree[ $this->get_base_name() ]['value_set'];
314
		}
315
		$this->set_value( $value );
316
317
		if ( $this->get_value() === null ) {
318
			$this->set_value( $this->get_default_value() );
319
		} else {
320
			$this->set_value_tree( $raw_value_set_tree[ $this->get_base_name() ] );
321
		}
322
	}
323
324
	/**
325
	 * Save all contained groups of fields.
326
	 */
327
	public function save() {
328
		// Only delete root field values as nested field values should be deleted in a cascading manner by the datastore
329
		$hierarchy = $this->get_hierarchy();
330
		if ( empty( $hierarchy ) ) {
331
			$this->delete();
332
		}
333
		
334
		$this->get_datastore()->save( $this );
335
336
		$field_groups = $this->get_prefilled_field_groups( $this->get_value_tree() );
337
338
		foreach ( $field_groups as $entry_index => $fields ) {
339
			foreach ( $fields as $field ) {
340
				if ( ! is_a( $field, __NAMESPACE__ . '\\Field' ) ) {
341
					continue;
342
				}
343
				$field->save();
344
			}
345
		}
346
	}
347
348
	/**
349
	 * Return the full value tree of all groups and their fields
350
	 *
351
	 * @return mixed
352
	 **/
353
	public function get_value_tree() {
354
		return (array) $this->value_tree;
355
	}
356
357
	/**
358
	 * Set the full value tree of all groups and their fields
359
	 *
360
	 * Tree Schema
361
	 * 'value_set' => array(
362
	 * 		array(
363
	 * 			'value' => '_',
364
	 * 		),
365
	 * 		...
366
	 * ),
367
	 * 'groups' => array(
368
	 * 		array(
369
	 * 			'field1' => array(
370
	 * 				'value_set'=>array(
371
	 * 					array(
372
	 * 						'value' => ...,
373
	 * 						...
374
	 * 					),
375
	 * 					...
376
	 * 				),
377
	 * 			),
378
	 * 			...
379
	 * 		),
380
	 * 		...
381
	 * ),
382
	 * 
383
	 * @return mixed
384
	 **/
385
	public function set_value_tree( $value_tree ) {
386
		return $this->value_tree = $value_tree;
387
	}
388
389
	/**
390
	 * Return a differently formatted value for end-users
391
	 *
392
	 * @return mixed
393
	 **/
394
	public function get_formatted_value() {
395
		$field_groups = $this->get_prefilled_field_groups( $this->get_value_tree() );
396
		
397
		$value = array();
398
		foreach ( $field_groups as $entry_index => $field_group ) {
399
			$value[ $entry_index ] = array();
400
			foreach ( $field_group as $key => $field ) {
401
				if ( is_a( $field, __NAMESPACE__ . '\\Field' ) ) {
402
					$value[ $entry_index ][ $field->get_base_name() ] = $field->get_formatted_value(); 
403
				} else {
404
					$value[ $entry_index ][ $key ] = $field;
405
				}
406
			}
407
		}
408
		return $value;
409
	}
410
411
	/**
412
	 * Returns an array that holds the field data, suitable for JSON representation.
413
	 * This data will be available in the Underscore template and the Backbone Model.
414
	 *
415
	 * @param bool $load  Should the value be loaded from the database or use the value from the current instance.
416
	 * @return array
417
	 */
418
	public function to_json( $load ) {
419
		$complex_data = parent::to_json( $load );
420
421
		$groups_data = array();
422
		foreach ( $this->groups as $group ) {
423
			$groups_data[] = $group->to_json( false );
424
		}
425
426
		$field_groups = $this->get_prefilled_field_groups( $this->get_value_tree() );
427
		$value_data = array();
428
		foreach ( $field_groups as $entry_index => $fields ) {
429
			$group = $this->get_group_by_name( $fields[ static::GROUP_TYPE_KEY ] );
430
431
			$data = array(
432
				'name' => $group->get_name(),
433
				'label' => $group->get_label(),
434
				'group_id' => $group->get_group_id(),
435
				'fields' => array(),
436
			);
437
438
			foreach ( $fields as $field ) {
439
				if ( ! is_a( $field, __NAMESPACE__ . '\\Field' ) ) {
440
					continue;
441
				}
442
				$data['fields'][] = $field->to_json( false );
443
			}
444
445
			$value_data[] = $data;
446
		}
447
448
		$complex_data = array_merge( $complex_data, array(
449
			'layout' => $this->layout,
450
			'labels' => $this->labels,
451
			'min' => $this->get_min(),
452
			'max' => $this->get_max(),
453
			'multiple_groups' => count( $groups_data ) > 1,
454
			'groups' => $groups_data,
455
			'value' => $value_data,
456
			'collapsed' => $this->collapsed,
457
		) );
458
		return $complex_data;
459
	}
460
461
	/**
462
	 * The main Underscore template.
463
	 */
464
	public function template() {
465
		?>
466
		<div class="carbon-subcontainer carbon-grid {{ multiple_groups ? 'multiple-groups' : '' }}">
467
			<div class="carbon-empty-row carbon-empty-row-visible">
468
				{{{ crbl10n.complex_no_rows.replace('%s', labels.plural_name) }}}
469
			</div>
470
471
			<div class="groups-wrapper layout-{{ layout }}">
472
				<# if (layout === '<?php echo static::LAYOUT_TABBED_HORIZONTAL ?>' || layout === '<?php echo static::LAYOUT_TABBED_VERTICAL ?>' ) { #>
473
					<div class="group-tabs-nav-holder">
474
						<ul class="group-tabs-nav"></ul>
475
476
						<div class="carbon-actions">
477
							<div class="carbon-button">
478
								<a href="#" class="button" data-group="{{{ multiple_groups ? '' : groups[0].name }}}">
479
									+
480
								</a>
481
482
								<# if (multiple_groups) { #>
483
									<ul>
484
										<# _.each(groups, function(group) { #>
485
											<li><a href="#" data-group="{{{ group.name }}}">{{{ group.label }}}</a></li>
486
										<# }); #>
487
									</ul>
488
								<# } #>
489
							</div>
490
						</div>
491
					</div><!-- /.group-tabs-nav-holder -->
492
				<# } #>
493
494
				<div class="carbon-groups-holder"></div>
495
				<div class="clear"></div>
496
			</div>
497
498
			<div class="carbon-actions">
499
				<div class="carbon-button">
500
					<a href="#" class="button" data-group="{{{ multiple_groups ? '' : groups[0].name }}}">
501
						{{{ crbl10n.complex_add_button.replace('%s', labels.singular_name) }}}
502
					</a>
503
504
					<# if (multiple_groups) { #>
505
						<ul>
506
							<# _.each(groups, function(group) { #>
507
								<li><a href="#" data-group="{{{ group.name }}}">{{{ group.label }}}</a></li>
508
							<# }); #>
509
						</ul>
510
					<# } #>
511
				</div>
512
			</div>
513
		</div>
514
		<?php
515
	}
516
517
	/**
518
	 * The Underscore template for the complex field group.
519
	 */
520
	public function template_group() {
521
		?>
522
		<div id="carbon-{{{ complex_name }}}-complex-container" class="carbon-row carbon-group-row" data-group-id="{{ id }}">
523
			<input type="hidden" name="{{{ complex_name + '[' + index + ']' }}}[_type]" value="{{ name }}" />
524
525
			<div class="carbon-drag-handle">
526
				<span class="group-number">{{{ order + 1 }}}</span><span class="group-name">{{{ label_template || label }}}</span>
527
			</div>
528
529
			<div class="carbon-group-actions carbon-group-actions-{{ layout }}">
530
				<a class="carbon-btn-duplicate dashicons-before dashicons-admin-page" href="#" title="<?php esc_attr_e( 'Clone', \Carbon_Fields\TEXT_DOMAIN ); ?>">
531
					<?php _e( 'Clone', \Carbon_Fields\TEXT_DOMAIN ); ?>
532
				</a>
533
534
				<a class="carbon-btn-remove dashicons-before dashicons-trash" href="#" title="<?php esc_attr_e( 'Remove', \Carbon_Fields\TEXT_DOMAIN ); ?>">
535
					<?php _e( 'Remove', \Carbon_Fields\TEXT_DOMAIN ); ?>
536
				</a>
537
538
				<a class="carbon-btn-collapse dashicons-before dashicons-arrow-up" href="#" title="<?php esc_attr_e( 'Collapse/Expand', \Carbon_Fields\TEXT_DOMAIN ); ?>">
539
					<?php _e( 'Collapse/Expand', \Carbon_Fields\TEXT_DOMAIN ); ?>
540
				</a>
541
			</div>
542
543
			<div class="fields-container">
544
				<# _.each(fields, function(field) { #>
545
					<div class="carbon-row carbon-subrow subrow-{{{ field.type }}} {{{ field.classes.join(' ') }}}">
546
						<label for="{{{ complex_id + '-' + field.id + '-' + index }}}">
547
							{{ field.label }}
548
549
							<# if (field.required) { #>
550
								 <span class="carbon-required">*</span>
551
							<# } #>
552
						</label>
553
554
						<div class="field-holder {{{ complex_id + '-' + field.id + '-' + index }}}"></div>
555
556
						<# if (field.help_text) { #>
557
							<em class="help-text">
558
								{{{ field.help_text }}}
559
							</em>
560
						<# } #>
561
562
						<em class="carbon-error"></em>
563
					</div>
564
				<# }) #>
565
			</div>
566
		</div>
567
		<?php
568
	}
569
570
	 /**
571
	 * The Underscore template for the group item tab.
572
	 */
573
	public function template_group_tab_item() {
574
		?>
575
		<li class="group-tab-item" data-group-id="{{ id }}">
576
			<a href="#">
577
				<span class="group-handle"></span>
578
579
				<# if (label_template || label) { #>
580
					<span class="group-name">{{{ label_template || label }}}</span>
581
				<# } #>
582
				<span class="group-number">{{{ order + 1 }}}</span>
583
				<span class="dashicons dashicons-warning carbon-complex-group-error-badge" ></span>
584
			</a>
585
		</li>
586
		<?php
587
	}
588
589
	/**
590
	 * Modify the layout of this field.
591
	 *
592
	 * @param string $layout
593
	 */
594
	public function set_layout( $layout ) {
595
		$available_layouts = array(
596
			static::LAYOUT_GRID,
597
			static::LAYOUT_TABBED_HORIZONTAL,
598
			static::LAYOUT_TABBED_VERTICAL,
599
		);
600
601
		if ( ! in_array( $layout,  $available_layouts ) ) {
602
			$error_message = 'Incorrect layout ``' . $layout . '" specified. ' .
603
				'Available layouts: ' . implode( ', ', $available_layouts );
604
605
			Incorrect_Syntax_Exception::raise( $error_message );
606
		}
607
608
		$this->layout = $layout;
609
610
		return $this;
611
	}
612
613
	/**
614
	 * Set the minimum number of entries.
615
	 *
616
	 * @param int $min
617
	 */
618
	public function set_min( $min ) {
619
		$this->values_min = intval( $min );
620
		return $this;
621
	}
622
623
	/**
624
	 * Get the minimum number of entries.
625
	 *
626
	 * @return int $min
627
	 */
628
	public function get_min() {
629
		return $this->values_min;
630
	}
631
632
	/**
633
	 * Set the maximum number of entries.
634
	 *
635
	 * @param int $max
636
	 */
637
	public function set_max( $max ) {
638
		$this->values_max = intval( $max );
639
		return $this;
640
	}
641
642
	/**
643
	 * Get the maximum number of entries.
644
	 *
645
	 * @return int $max
646
	 */
647
	public function get_max() {
648
		return $this->values_max;
649
	}
650
651
	/**
652
	 * Change the groups initial collapse state.
653
	 * This state relates to the state of which the groups are rendered.
654
	 *
655
	 * @param bool $collapsed
656
	 */
657
	public function set_collapsed( $collapsed = true ) {
658
		$this->collapsed = $collapsed;
659
660
		return $this;
661
	}
662
663
	/**
664
	 * Retrieve the groups of this field.
665
	 *
666
	 * @return array
667
	 */
668
	public function get_group_names() {
669
		return array_keys( $this->groups );
670
	}
671
672
	/**
673
	 * Retrieve a group by its name.
674
	 *
675
	 * @param  string $group_name        Group name
676
	 * @return Group_Field $group_object Group object
677
	 */
678
	public function get_group_by_name( $group_name ) {
679
		$group_object = null;
680
681
		foreach ( $this->groups as $group ) {
682
			if ( $group->get_name() == $group_name ) {
683
				$group_object = $group;
684
			}
685
		}
686
687
		return $group_object;
688
	}
689
}
690