Complex_Field   F
last analyzed

Complexity

Total Complexity 83

Size/Duplication

Total Lines 686
Duplicated Lines 1.46 %

Coupling/Cohesion

Components 2
Dependencies 5

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
dl 10
loc 686
ccs 0
cts 300
cp 0
rs 1.914
c 0
b 0
f 0
wmc 83
lcom 2
cbo 5

34 Methods

Rating   Name   Duplication   Size   Complexity  
A setup_labels() 0 4 1
A get_clone_under_field_in_hierarchy() 0 6 1
A external_to_internal_value() 0 12 3
A get_value_tree() 0 3 1
A get_prefilled_group_fields() 0 13 3
A get_prefilled_groups() 0 20 4
B set_value_from_input() 0 40 7
B save() 0 23 6
A get_formatted_value() 0 20 5
A set_value() 0 10 3
A set_default_value() 0 9 3
A __construct() 0 4 1
A init() 0 7 1
A set_hierarchy() 0 5 1
A set_datastore() 10 10 3
A get_group_names() 0 3 1
A set_value_tree() 0 4 1
A get_min() 0 3 1
A set_min() 0 4 1
A get_max() 0 3 1
A set_max() 0 4 1
A get_collapsed() 0 3 1
A set_collapsed() 0 4 1
A get_duplicate_groups_allowed() 0 3 1
A set_duplicate_groups_allowed() 0 4 1
A update_child_hierarchy() 0 7 2
A activate() 0 7 2
A update_child_datastore() 0 5 2
A get_fields() 0 11 2
B add_fields() 0 41 8
A get_group_by_name() 0 11 3
A set_header_template() 0 15 3
B to_json() 0 57 6
A set_layout() 0 19 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Complex_Field often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Complex_Field, and based on these observations, apply Extract Interface, too.

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