Completed
Push — milestone/2_0/react-ui ( feeecc...e46ba4 )
by
unknown
07:22
created

Complex_Field::get_prefilled_groups()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 20
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
cc 4
eloc 13
nc 4
nop 1
dl 0
loc 20
ccs 0
cts 15
cp 0
crap 20
rs 9.2
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
	/**
19
	 * Visual layout type constants
20
	 */
21
	const LAYOUT_GRID = 'grid'; // default
22
23
	const LAYOUT_TABBED_HORIZONTAL = 'tabbed-horizontal';
24
25
	const LAYOUT_TABBED_VERTICAL = 'tabbed-vertical';
26
27
	/**
28
	 * Key which defines what a group's type/name is
29
	 */
30
	const GROUP_TYPE_KEY = '_type';
31
32
	/**
33
	 * Default field value
34
	 *
35
	 * @var array
36
	 */
37
	protected $default_value = array();
38
39
	/**
40
	 * Complex field layout
41
	 * 
42
	 * @var string static::LAYOUT_* constant
43
	 */
44
	protected $layout = self::LAYOUT_GRID;
45
46
	/**
47
	 * Value tree describing the complex values ( ['value_set'] ) and all groups with their child fields ( ['groups'] )
48
	 * 
49
	 * @var array
50
	 */
51
	protected $value_tree = array(
52
		'value_set' => array(),
53
		'groups' => array(),
54
	);
55
56
	/**
57
	 * Array of groups registered for this complex field
58
	 * 
59
	 * @var array
60
	 */
61
	protected $groups = array();
62
63
	/**
64
	 * Minimum number of entries. -1 for no limit
65
	 * 
66
	 * @var integer
67
	 */
68
	protected $values_min = -1;
69
70
	/**
71
	 * Maximum number of entries. -1 for no limit
72
	 * 
73
	 * @var integer
74
	 */
75
	protected $values_max = -1;
76
77
	/**
78
	 * Default entry state - collapsed or not
79
	 * 
80
	 * @var boolean
81
	 */
82
	protected $collapsed = false;
83
84
	/**
85
	 * Entry labels
86
	 * These are translated in init()
87
	 * 
88
	 * @var array
89
	 */
90
	public $labels = array(
91
		'singular_name' => 'Entry',
92
		'plural_name' => 'Entries',
93
	);
94
95
	/**
96
	 * Create a field from a certain type with the specified label.
97
	 * 
98
	 * @param string $type  Field type
99
	 * @param string $name  Field name
100
	 * @param string $label Field label
101
	 */
102
	protected function __construct( $type, $name, $label ) {
103
		$this->set_value_set( new Value_Set( Value_Set::TYPE_MULTIPLE_VALUES ) );
104
		parent::__construct( $type, $name, $label );
105
	}
106
107
	/**
108
	 * Initialization tasks.
109
	 */
110
	public function init() {
111
		$this->labels = array(
112
			'singular_name' => __( $this->labels['singular_name'], \Carbon_Fields\TEXT_DOMAIN ),
1 ignored issue
show
introduced by
Expected next thing to be a escaping function, not '$this'
Loading history...
113
			'plural_name' => __( $this->labels['plural_name'], \Carbon_Fields\TEXT_DOMAIN ),
1 ignored issue
show
introduced by
Expected next thing to be a escaping function, not '$this'
Loading history...
114
		);
115
		parent::init();
116
	}
117
118
	/**
119
	 * Set array of hierarchy field names
120
	 */
121
	public function set_hierarchy( $hierarchy ) {
122
		parent::set_hierarchy( $hierarchy );
123
		$this->update_child_hierarchy();
124
	}
125
	
126
	/**
127
	 * Propagate hierarchy to child fields
128
	 */
129
	public function update_child_hierarchy() {
130
		$hierarchy = array_merge( $this->get_hierarchy(), array( $this->get_base_name() ) );
131
		$fields = $this->get_fields();
132
		foreach ( $fields as $field ) {
133
			$field->set_hierarchy( $hierarchy );
134
		}
135
	}
136
137
	/**
138
	 * Activate the field once the container is attached.
139
	 */
140
	public function activate() {
141
		parent::activate();
142
		$fields = $this->get_fields();
143
		foreach ( $fields as $field ) {
144
			$field->activate();
145
		}
146
	}
147
148
	/**
149
	 * Set the datastore of this field and propagate it to children
150
	 *
151
	 * @param Datastore_Interface $datastore
152
	 */
153 View Code Duplication
	public function set_datastore( Datastore_Interface $datastore, $set_as_default = false ) {
154
		if ( $set_as_default && ! $this->has_default_datastore() ) {
155
			return $this; // datastore has been overriden with a custom one - abort changing to a default one
156
		}
157
		$this->datastore = $datastore;
158
		$this->has_default_datastore = $set_as_default;
159
160
		foreach ( $this->groups as $group ) {
161
			$group->set_datastore( $this->get_datastore(), true );
162
		}
163
		return $this;
164
	}
165
166
	/**
167
	 * Retrieve all groups of fields.
168
	 *
169
	 * @return array $fields
170
	 */
171
	public function get_fields() {
172
		$fields = array();
173
174
		foreach ( $this->groups as $group ) {
175
			$group_fields = $group->get_fields();
176
177
			$fields = array_merge( $fields, $group_fields );
178
		}
179
180
		return $fields;
181
	}
182
183
	/**
184
	 * Add a set/group of fields.
185
	 *
186
	 * Accepted param variations:
187
	 *   - array<Field> $fields
188
	 *   - string $group_name, array<Field> $fields
189
	 *   - string $group_name, string $group_label, array<Field> $fields
190
	 * 
191
	 * @return $this
192
	 */
193
	public function add_fields() {
194
		$argv = func_get_args();
195
		$argc = count( $argv );
196
		$fields = $argv[ $argc - 1 ];
197
		$name = '';
198
		$label = null;
199
200
		if ( $argc >= 2 ) {
201
			$name = $argv[0];
202
		}
203
		
204
		if ( $argc >= 3 ) {
205
			$label = $argv[1];
206
		}
207
208
		$name = ! empty( $name ) ? $name : Group_Field::DEFAULT_GROUP_NAME;
209
210
		if ( array_key_exists( $name, $this->groups ) ) {
211
			Incorrect_Syntax_Exception::raise( 'Group with name "' . $name . '" in Complex Field "' . $this->get_label() . '" already exists.' );
212
		}
213
214
		foreach ( $fields as $field ) {
215
			if ( $field->get_base_name() === static::GROUP_TYPE_KEY ) {
216
				Incorrect_Syntax_Exception::raise( '"' . static::GROUP_TYPE_KEY . '" is a reserved keyword for Complex fields and cannot be used for a field name.' );
217
			}
218
		}
219
220
		$group = new Group_Field( $name, $label, $fields );
221
222
		$this->groups[ $group->get_name() ] = $group;
223
		$this->update_child_hierarchy();
224
225
		return $this;
226
	}
227
228
	/**
229
	 * Retrieve the groups of this field.
230
	 *
231
	 * @return array
232
	 */
233
	public function get_group_names() {
234
		return array_keys( $this->groups );
235
	}
236
237
	/**
238
	 * Retrieve a group by its name.
239
	 *
240
	 * @param  string $group_name        Group name
241
	 * @return Group_Field $group_object Group object
242
	 */
243
	public function get_group_by_name( $group_name ) {
244
		$group_object = null;
245
246
		foreach ( $this->groups as $group ) {
247
			if ( $group->get_name() == $group_name ) {
248
				$group_object = $group;
249
			}
250
		}
251
252
		return $group_object;
253
	}
254
255
	/**
256
	 * Set the group label Underscore template.
257
	 *
258
	 * @param  string|callable $template
259
	 * @return $this
260
	 */
261
	public function set_header_template( $template ) {
262
		if ( count( $this->groups ) === 0 ) {
263
			Incorrect_Syntax_Exception::raise( "Can't set group label template. There are no present groups for Complex Field " . $this->get_label() . '.' );
264
		}
265
266
		$template = is_callable( $template ) ? call_user_func( $template ) : $template;
267
268
		// Assign the template to the group that was added last
269
		$values = array_values( $this->groups );
270
		$group = end( $values );
271
		$group->set_label_template( $template );
272
273
		$this->groups[ $group->get_name() ] = $group;
274
275
		return $this;
276
	}
277
278
	/**
279
	 * Set the field labels.
280
	 * Currently supported values:
281
	 *  - singular_name - the singular entry label
282
	 *  - plural_name - the plural entries label
283
	 *
284
	 * @param  array $labels Labels
285
	 */
286
	public function setup_labels( $labels ) {
287
		$this->labels = array_merge( $this->labels, $labels );
288
		return $this;
289
	}
290
291
	/**
292
	 * Return a clone of a field with hierarchy settings applied
293
	 *
294
	 * @param Field $field
295
	 * @param Field $parent_field
296
	 * @param int $group_index
297
	 * @return Field
298
	 */
299
	public function get_clone_under_field_in_hierarchy( $field, $parent_field, $group_index = 0 ) {
300
		$clone = clone $field;
301
		$clone->set_hierarchy( array_merge( $parent_field->get_hierarchy(), array( $parent_field->get_base_name() ) ) );
302
		$clone->set_hierarchy_index( array_merge( $parent_field->get_hierarchy_index(), array( $group_index ) ) );
303
		return $clone;
304
	}
305
306
	protected function get_prefilled_group_fields( $group_fields, $group_values, $group_index ) {
307
		$fields = array();
308
309
		foreach ( $group_fields as $field ) {
310
			$clone = $this->get_clone_under_field_in_hierarchy( $field, $this, $group_index );
311
			if ( isset( $group_values[ $clone->get_base_name() ] ) ) {
312
				$group_value = $group_values[ $clone->get_base_name() ];
313
				
314
				if ( isset( $group_value['value_set'] ) ) {
315
					$clone->set_value( $group_value['value_set'] );
316
				}
317
318
				if ( is_a( $clone, get_class() ) ) {
319
					$clone->set_value_tree( $group_value );
320
				}
321
			}
322
			$fields[] = $clone;
323
		}
324
325
		return $fields;
326
	}
327
328
	protected function get_prefilled_groups( $value_tree ) {
329
		$fields = array();
330
331
		if ( empty( $value_tree ) ) {
332
			return $fields;
333
		}
334
335
		foreach ( $value_tree['value_set'] as $group_index => $value ) {
336
			$group_name = $value[ Value_Set::VALUE_PROPERTY ];
337
			$group = $this->get_group_by_name( $group_name );
338
			$group_fields = $group->get_fields();
339
			$group_values = array();
340
			if ( isset( $value_tree['groups'][ $group_index ] ) ) {
341
				$group_values = $value_tree['groups'][ $group_index ];
342
			}
343
			$fields[ $group_index ] = array( static::GROUP_TYPE_KEY => $group->get_name() ) + $this->get_prefilled_group_fields( $group_fields, $group_values, $group_index );
344
		}
345
346
		return $fields;
347
	}
348
349
	/**
350
	 * Load the field value from an input array based on it's name.
351
	 *
352
	 * @param array $input Array of field names and values.
353
	 */
354
	public function set_value_from_input( $input ) {
355
		if ( ! isset( $input[ $this->get_name() ] ) ) {
356
			return;
357
		}
358
359
		$value_tree = array(
360
			'value_set' => array(),
361
			'groups' => array(),
362
		);
363
		$input_groups = $input[ $this->get_name() ];
364
		$input_group_index = 0;
365
		foreach ( $input_groups as $values ) {
366
			if ( ! isset( $values[ static::GROUP_TYPE_KEY ] ) || ! isset( $this->groups[ $values[ static::GROUP_TYPE_KEY ] ] ) ) {
367
				continue;
368
			}
369
370
			$group = $this->get_group_by_name( $values[ static::GROUP_TYPE_KEY ] );
371
			$group_fields = $group->get_fields();
372
			$group_field_names = array_flip( $group->get_field_names() );
373
			
374
			$value_group = array( static::GROUP_TYPE_KEY => $values[ static::GROUP_TYPE_KEY ] );
375
			unset( $values[ static::GROUP_TYPE_KEY ] );
376
377
			// trim input values to those used by the field
378
			$values = array_intersect_key( $values, $group_field_names );
379
			
380
			foreach ( $group_fields as $field ) {
381
				$tmp_field = $this->get_clone_under_field_in_hierarchy( $field, $this, $input_group_index );
382
383
				$tmp_field->set_value_from_input( $values );
384
				if ( is_a( $tmp_field, get_class() ) ) {
385
					$value_group[ $tmp_field->get_base_name() ] = $tmp_field->get_value_tree();
386
				} else {
387
					$value_group[ $tmp_field->get_base_name() ] = array(
388
						'value_set' => $tmp_field->get_full_value(),
389
					);
390
				}
391
			}
392
393
			$value_tree['value_set'][] = array(
394
				Value_Set::VALUE_PROPERTY => $group->get_name(),
395
			);
396
			$value_tree['groups'][] = $value_group;
397
			$input_group_index++;
398
		}
399
400
		$this->set_value( $value_tree['value_set'] );
401
		$this->set_value_tree( $value_tree );
402
	}
403
404
	/**
405
	 * Load all groups of fields and their data.
406
	 */
407
	public function load() {
408
		$value_tree = $this->get_datastore()->load( $this );
409
		$value = array();
410
		if ( isset( $value_tree['value_set'] ) ) {
411
			$value = $value_tree['value_set'];
412
		}
413
		$this->set_value( $value );
414
415
		if ( $this->get_value() ) {
416
			$this->set_value_tree( $value_tree );
417
		}
418
	}
419
420
	/**
421
	 * Save all contained groups of fields.
422
	 */
423
	public function save() {
424
		// Only delete root field values as nested field values should be deleted in a cascading manner by the datastore
425
		$hierarchy = $this->get_hierarchy();
426
		if ( empty( $hierarchy ) ) {
427
			$this->delete();
428
		}
429
		
430
		$this->get_datastore()->save( $this );
431
432
		$field_groups = $this->get_prefilled_groups( $this->get_value_tree() );
433
434
		foreach ( $field_groups as $group_index => $fields ) {
435
			foreach ( $fields as $field ) {
436
				if ( ! is_a( $field, __NAMESPACE__ . '\\Field' ) ) {
437
					continue;
438
				}
439
				$field->save();
440
			}
441
		}
442
	}
443
444
	/**
445
	 * Return the full value tree of all groups and their fields
446
	 *
447
	 * @return mixed
448
	 */
449
	public function get_value_tree() {
450
		return (array) $this->value_tree;
451
	}
452
453
	/**
454
	 * Set the full value tree of all groups and their fields
455
	 *
456
	 * @see  Internal Glossary in DEVELOPMENT.MD
457
	 */
458
	public function set_value_tree( $value_tree ) {
459
		$this->value_tree = $value_tree;
460
	}
461
462
	/**
463
	 * Return a differently formatted value for end-users
464
	 *
465
	 * @return mixed
466
	 */
467
	public function get_formatted_value() {
468
		$field_groups = $this->get_prefilled_groups( $this->get_value_tree() );
469
		
470
		$value = array();
471
		foreach ( $field_groups as $group_index => $field_group ) {
472
			$value[ $group_index ] = array();
473
			foreach ( $field_group as $key => $field ) {
474
				if ( is_a( $field, __NAMESPACE__ . '\\Field' ) ) {
475
					$value[ $group_index ][ $field->get_base_name() ] = $field->get_formatted_value(); 
476
				} else {
477
					$value[ $group_index ][ $key ] = $field;
478
				}
479
			}
480
		}
481
		return $value;
482
	}
483
484
	/**
485
	 * Returns an array that holds the field data, suitable for JSON representation.
486
	 *
487
	 * @param bool $load  Should the value be loaded from the database or use the value from the current instance.
488
	 * @return array
489
	 */
490
	public function to_json( $load ) {
491
		$complex_data = parent::to_json( $load );
492
493
		$groups_data = array();
494
		foreach ( $this->groups as $group ) {
495
			$groups_data[] = $group->to_json( false );
496
		}
497
498
		$field_groups = $this->get_prefilled_groups( $this->get_value_tree() );
499
		$value_data = array();
500
		foreach ( $field_groups as $group_index => $fields ) {
501
			$group = $this->get_group_by_name( $fields[ static::GROUP_TYPE_KEY ] );
502
503
			$data = array(
504
				'name' => $group->get_name(),
505
				'label' => $group->get_label(),
506
				'group_id' => $group->get_group_id(),
507
				'fields' => array(),
508
			);
509
510
			foreach ( $fields as $field ) {
511
				if ( ! is_a( $field, __NAMESPACE__ . '\\Field' ) ) {
512
					continue;
513
				}
514
				$data['fields'][] = $field->to_json( false );
515
			}
516
517
			$value_data[] = $data;
518
		}
519
520
		$complex_data = array_merge( $complex_data, array(
521
			'layout' => $this->layout,
522
			'labels' => $this->labels,
523
			'min' => $this->get_min(),
524
			'max' => $this->get_max(),
525
			'multiple_groups' => count( $groups_data ) > 1,
526
			'groups' => $groups_data,
527
			'value' => $value_data,
528
			'collapsed' => $this->get_collapsed(),
529
		) );
530
		return $complex_data;
531
	}
532
533
	/**
534
	 * Modify the layout of this field.
535
	 *
536
	 * @param string $layout
537
	 */
538
	public function set_layout( $layout ) {
539
		$available_layouts = array(
540
			static::LAYOUT_GRID,
541
			static::LAYOUT_TABBED_HORIZONTAL,
542
			static::LAYOUT_TABBED_VERTICAL,
543
		);
544
545
		if ( ! in_array( $layout,  $available_layouts ) ) {
546
			$error_message = 'Incorrect layout ``' . $layout . '" specified. ' .
547
				'Available layouts: ' . implode( ', ', $available_layouts );
548
549
			Incorrect_Syntax_Exception::raise( $error_message );
550
		}
551
552
		$this->layout = $layout;
553
554
		return $this;
555
	}
556
557
	/**
558
	 * Get the minimum number of entries.
559
	 *
560
	 * @return int $min
561
	 */
562
	public function get_min() {
563
		return $this->values_min;
564
	}
565
566
	/**
567
	 * Set the minimum number of entries.
568
	 *
569
	 * @param int $min
570
	 */
571
	public function set_min( $min ) {
572
		$this->values_min = intval( $min );
573
		return $this;
574
	}
575
576
	/**
577
	 * Get the maximum number of entries.
578
	 *
579
	 * @return int $max
580
	 */
581
	public function get_max() {
582
		return $this->values_max;
583
	}
584
585
	/**
586
	 * Set the maximum number of entries.
587
	 *
588
	 * @param int $max
589
	 */
590
	public function set_max( $max ) {
591
		$this->values_max = intval( $max );
592
		return $this;
593
	}
594
595
	/**
596
	 * Get collapsed state
597
	 *
598
	 * @return bool
599
	 */
600
	public function get_collapsed() {
601
		return $this->collapsed;
602
	}
603
604
	/**
605
	 * Change the groups initial collapse state.
606
	 * This state relates to the state of which the groups are rendered.
607
	 *
608
	 * @param bool $collapsed
609
	 */
610
	public function set_collapsed( $collapsed = true ) {
611
		$this->collapsed = $collapsed;
612
		return $this;
613
	}
614
}
615