Completed
Push — milestone/2.0 ( 5944fd...9cc217 )
by
unknown
02:37
created

Association_Field   B

Complexity

Total Complexity 41

Size/Duplication

Total Lines 455
Duplicated Lines 11.43 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 3
Bugs 1 Features 1
Metric Value
c 3
b 1
f 1
dl 52
loc 455
rs 8.2769
wmc 41
lcom 1
cbo 4

17 Methods

Rating   Name   Duplication   Size   Complexity  
A set_value() 0 4 1
A __construct() 0 5 1
A get_formatted_value() 0 3 1
A value_string_to_property_array() 0 10 1
A value_string_array_to_value_set() 0 19 4
B get_title_by_type() 0 33 5
B get_item_label() 0 22 4
B get_post_options() 0 29 2
B get_term_options() 0 27 2
B get_user_options() 26 26 2
B get_comment_options() 26 26 2
A get_options() 0 21 3
B get_object_edit_link() 0 26 5
A set_types() 0 4 1
A value_to_json() 0 16 3
A to_json() 0 19 2
A item_template() 0 21 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 Association_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 Association_Field, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Carbon_Fields\Field;
4
5
use Carbon_Fields\App;
6
use Carbon_Fields\Value_Set\Value_Set;
7
8
/**
9
 * Association field class.
10
 * Allows selecting and manually sorting entries from various types:
11
 *  - Posts
12
 *  - Terms
13
 *  - Users
14
 *  - Comments
15
 */
16
class Association_Field extends Relationship_Field {
17
	
18
	/**
19
	 * WP_Toolset instance
20
	 * @var Carbon_Fields\Toolset\WP_Toolset
21
	 */
22
	protected $wp_toolset;
23
24
	/**
25
	 * Types of entries to associate with.
26
	 * @var array
27
	 */
28
	protected $types = array(
29
		array(
30
			'type' => 'post',
31
			'post_type' => 'post',
32
		),
33
	);
34
35
	/**
36
	 * Create a field from a certain type with the specified label.
37
	 * @param string $name  Field name
38
	 * @param string $label Field label
39
	 */
40
	protected function __construct( $name, $label ) {
41
		$this->wp_toolset = App::resolve( 'wp_toolset' );
42
		$this->value = new Value_Set( Value_Set::TYPE_VALUE_SET, array( 'type' => '', 'subtype' => '', 'object_id' => 0 ) );
43
		Field::__construct( $name, $label );
44
	}
45
46
	/**
47
	 * Alias for $this->value()->set( $value );
48
	 **/
49
	public function set_value( $value ) {
50
		$value = $this->value_string_array_to_value_set( $value );
51
		parent::set_value( $value );
52
	}
53
54
	/**
55
	 * Return a differently formatted value for end-users
56
	 *
57
	 * @return mixed
58
	 **/
59
	public function get_formatted_value() {
60
		return Field::get_formatted_value();
61
	}
62
63
	/**
64
	 * Convert a colo:separated:string into it's expected components
65
	 * Used for backwards compatibility to CF 1.5
66
	 * 
67
	 * @param string $value_string
68
	 * @return array
69
	 */
70
	protected function value_string_to_property_array( $value_string ) {
71
		$value_pieces = explode( ':', $value_string );
72
		$property_array = array(
73
			Value_Set::VALUE_PROPERTY => $value_string,
74
			'type' => $value_pieces[0],
75
			'subtype' => $value_pieces[1],
76
			'object_id' => $value_pieces[2],
77
		);
78
		return $property_array;
79
	}
80
81
	/**
82
	 * Convert a colon:separated:string into it's expected components
83
	 * Used for backwards compatibility to CF 1.5
84
	 * 
85
	 * @param array $value_string_array
86
	 * @return array<array>
87
	 */
88
	protected function value_string_array_to_value_set( $value_string_array ) {
89
		$value_set = array();
90
		foreach ( $value_string_array as $raw_value_entry ) {
91
			$value_string = $raw_value_entry;
92
93
			if ( is_array( $raw_value_entry ) ) {
94
				if ( isset( $raw_value_entry['type'] ) ) {
95
					// array is already in suitable format
96
					$value_set[] = $raw_value_entry;
97
					continue;
98
				}
99
				$value_string = $raw_value_entry[ Value_Set::VALUE_PROPERTY ];
100
			}
101
102
			$property_array = $this->value_string_to_property_array( $value_string );
103
			$value_set[] = $property_array;
104
		}
105
		return $value_set;
106
	}
107
108
	/**
109
	 * Used to get the title of an item.
110
	 *
111
	 * Can be overriden or extended by the `carbon_association_title` filter.
112
	 *
113
	 * @param int $id The database ID of the item.
114
	 * @param string $type Item type (post, term, user, comment, or a custom one).
115
	 * @param string $subtype The subtype - "page", "post", "category", etc.
116
	 * @return string $title The title of the item.
117
	 */
118
	protected function get_title_by_type( $id, $type, $subtype = '' ) {
119
		$title = '';
120
121
		$method = 'get_' . $type . '_title';
122
		$callable = array( $this->wp_toolset, $method );
123
		if ( is_callable( $callable ) ) {
124
			$title = call_user_func( $callable, $id, $subtype );
125
		}
126
127
		if ( $type === 'comment' ) {
128
			$max = apply_filters( 'carbon_association_comment_length', 30, $this->get_name() );
129
			if ( strlen( $title ) > $max ) {
130
				$title = substr( $title, 0, $max ) . '...';
131
			}
132
		}
133
134
		/**
135
		 * Filter the title of the association item.
136
		 *
137
		 * @param string $title   The unfiltered item title.
138
		 * @param string $name    Name of the association field.
139
		 * @param int    $id      The database ID of the item.
140
		 * @param string $type    Item type (post, term, user, comment, or a custom one).
141
		 * @param string $subtype Subtype - "page", "post", "category", etc.
142
		 */
143
		$title = apply_filters( 'carbon_association_title', $title, $this->get_name(), $id, $type, $subtype );
144
145
		if ( ! $title ) {
146
			$title = '(no title) - ID: ' . $id;
147
		}
148
149
		return $title;
150
	}
151
152
	/**
153
	 * Used to get the label of an item.
154
	 *
155
	 * Can be overriden or extended by the `carbon_association_item_label` filter.
156
	 *
157
	 * @param int     $id      The database ID of the item.
158
	 * @param string  $type    Item type (post, term, user, comment, or a custom one).
159
	 * @param string  $subtype Subtype - "page", "post", "category", etc.
160
	 * @return string $label The label of the item.
161
	 */
162
	protected function get_item_label( $id, $type, $subtype = '' ) {
163
		$label = $subtype ? $subtype : $type;
164
165
		if ( $type === 'post' ) {
166
			$post_type_object = get_post_type_object( $subtype );
167
			$label = $post_type_object->labels->singular_name;
168
		} elseif ( $type === 'term' ) {
169
			$taxonomy_object = get_taxonomy( $subtype );
170
			$label = $taxonomy_object->labels->singular_name;
171
		}
172
173
		/**
174
		 * Filter the label of the association item.
175
		 *
176
		 * @param string $label   The unfiltered item label.
177
		 * @param string $name    Name of the association field.
178
		 * @param int    $id      The database ID of the item.
179
		 * @param string $type    Item type (post, term, user, comment, or a custom one).
180
		 * @param string $subtype Subtype - "page", "post", "category", etc.
181
		 */
182
		return apply_filters( 'carbon_association_item_label', $label, $this->get_name(), $id, $type, $subtype );
183
	}
184
185
	/**
186
	 * Get post options
187
	 *
188
	 * @return array $options
189
	 */
190
	protected function get_post_options( $type ) {
191
		/**
192
		 * Filter the default query when fetching posts for a particular field.
193
		 *
194
		 * @param array $args The parameters, passed to get_posts().
195
		 */
196
		$filter_name = 'carbon_association_options_' . $this->get_name() . '_' . $type['type'] . '_' . $type['post_type'];
197
		$args = apply_filters( $filter_name, array(
198
			'post_type' => $type['post_type'],
199
			'posts_per_page' => -1,
200
			'fields' => 'ids',
201
			'suppress_filters' => false,
202
		) );
203
204
		// fetch and prepare posts as association items
205
		$posts = get_posts( $args );
206
		foreach ( $posts as &$p ) {
207
			$p = array(
208
				'id' => $p,
209
				'title' => $this->get_title_by_type( $p, $type['type'], $type['post_type'] ),
210
				'type' => $type['type'],
211
				'subtype' => $type['post_type'],
212
				'label' => $this->get_item_label( $p, $type['type'], $type['post_type'] ),
213
				'is_trashed' => ( get_post_status( $p ) == 'trash' ),
214
				'edit_link' => $this->get_object_edit_link( $type, $p ),
215
			);
216
		}
217
		return $posts;
218
	}
219
220
	/**
221
	 * Get term options
222
	 *
223
	 * @return array $options
224
	 */
225
	protected function get_term_options( $type ) {
226
		/**
227
		 * Filter the default parameters when fetching terms for a particular field.
228
		 *
229
		 * @param array $args The parameters, passed to get_terms().
230
		 */
231
		$filter_name = 'carbon_association_options_' . $this->get_name() . '_' . $type['type'] . '_' . $type['taxonomy'];
232
		$args = apply_filters( $filter_name, array(
233
			'hide_empty' => 0,
234
			'fields' => 'id=>name',
235
		) );
236
237
		// fetch and prepare terms as association items
238
		$terms = get_terms( $type['taxonomy'], $args );
239
		foreach ( $terms as $term_id => &$term ) {
240
			$term = array(
241
				'id' => $term_id,
242
				'title' => $term,
243
				'type' => $type['type'],
244
				'subtype' => $type['taxonomy'],
245
				'label' => $this->get_item_label( $term_id, $type['type'], $type['taxonomy'] ),
246
				'is_trashed' => false,
247
				'edit_link' => $this->get_object_edit_link( $type, $term_id ),
248
			);
249
		}
250
		return $terms;
251
	}
252
253
	/**
254
	 * Get user options
255
	 *
256
	 * @return array $options
257
	 */
258 View Code Duplication
	protected function get_user_options( $type ) {
1 ignored issue
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...
259
		/**
260
		 * Filter the default parameters when fetching users for a particular field.
261
		 *
262
		 * @param array $args The parameters, passed to get_users().
263
		 */
264
		$filter_name = 'carbon_association_options_' . $this->get_name() . '_' . $type['type'];
265
		$args = apply_filters( $filter_name, array(
266
			'fields' => 'ID',
267
		) );
268
269
		// fetch and prepare users as association items
270
		$users = get_users( $args );
271
		foreach ( $users as &$u ) {
272
			$u = array(
273
				'id' => $u,
274
				'title' => $this->get_title_by_type( $u, $type['type'] ),
275
				'type' => $type['type'],
276
				'subtype' => 'user',
277
				'label' => $this->get_item_label( $u, $type['type'] ),
278
				'is_trashed' => false,
279
				'edit_link' => $this->get_object_edit_link( $type, $u ),
280
			);
281
		}
282
		return $users;
283
	}
284
285
	/**
286
	 * Get comment options
287
	 *
288
	 * @return array $options
289
	 */
290 View Code Duplication
	protected function get_comment_options( $type ) {
1 ignored issue
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...
291
		/**
292
		 * Filter the default parameters when fetching comments for a particular field.
293
		 *
294
		 * @param array $args The parameters, passed to get_comments().
295
		 */
296
		$filter_name = 'carbon_association_options_' . $this->get_name() . '_' . $type['type'];
297
		$args = apply_filters( $filter_name, array(
298
			'fields' => 'ids',
299
		) );
300
301
		// fetch and prepare comments as association items
302
		$comments = get_comments( $args );
303
		foreach ( $comments as &$c ) {
304
			$c = array(
305
				'id' => $c,
306
				'title' => $this->get_title_by_type( $c, $type['type'] ),
307
				'type' => $type['type'],
308
				'subtype' => 'comment',
309
				'label' => $this->get_item_label( $c, $type['type'] ),
310
				'is_trashed' => false,
311
				'edit_link' => $this->get_object_edit_link( $type, $c ),
312
			);
313
		}
314
		return $comments;
315
	}
316
317
	/**
318
	 * Generate the item options.
319
	 *
320
	 * @return array $options The selectable options of the association field.
321
	 */
322
	public function get_options() {
323
		$options = array();
324
325
		foreach ( $this->types as $type ) {
326
			$method = 'get_' . $type['type'] . '_options';
327
			$callable = array( $this, $method );
328
			if ( is_callable( $callable ) ) {
329
				$options = array_merge( $options, call_user_func( $callable, $type ) );
330
			}
331
		}
332
333
		/**
334
		 * Filter the final list of options, available to a certain association field.
335
		 *
336
		 * @param array $options Unfiltered options items.
337
		 * @param string $name Name of the association field.
338
		 */
339
		$options = apply_filters( 'carbon_association_options', $options, $this->get_name() );
340
341
		return $options;
342
	}
343
344
	/**
345
	 * Retrieve the edit link of a particular object.
346
	 *
347
	 * @param  string $type Object type.
348
	 * @param  int $id      ID of the object.
349
	 * @return string       URL of the edit link.
350
	 */
351
	protected function get_object_edit_link( $type, $id ) {
352
		switch ( $type['type'] ) {
353
354
			case 'post':
355
				$edit_link = get_edit_post_link( $id );
356
				break;
357
358
			case 'term':
359
				$edit_link = get_edit_term_link( $id, $type['taxonomy'], $type['type'] );
360
				break;
361
362
			case 'comment':
363
				$edit_link = get_edit_comment_link( $id );
364
				break;
365
366
			case 'user':
367
				$edit_link = get_edit_user_link( $id );
368
				break;
369
370
			default:
371
				$edit_link = false;
372
373
		}
374
375
		return $edit_link;
376
	}
377
378
	/**
379
	 * Modify the types.
380
	 * @param array $types New types
381
	 */
382
	public function set_types( $types ) {
383
		$this->types = $types;
384
		return $this;
385
	}
386
387
	/**
388
	 * Converts the field values into a usable associative array.
389
	 *
390
	 * The association data is saved in the database in the following format:
391
	 * 	array (
392
	 *		0 => 'post:page:4',
393
	 *		1 => 'term:category:2',
394
	 *		2 => 'user:user:1',
395
	 * 	)
396
	 * where the value of each array item contains:
397
	 * 	- Type of data (post, term, user or comment)
398
	 * 	- Subtype of data (the particular post type or taxonomy)
399
	 * 	- ID of the item (the database ID of the item)
400
	 */
401
	protected function value_to_json() {
402
		$value_set = $this->get_value();
403
		$value = array();
404
		foreach ( $value_set as $value_set_entry ) {
405
			$item = array(
406
				'type' => $value_set_entry['type'],
407
				'subtype' => $value_set_entry['subtype'],
408
				'id' => $value_set_entry['object_id'],
409
				'title' => $this->get_title_by_type( $value_set_entry['object_id'], $value_set_entry['type'], $value_set_entry['subtype'] ),
410
				'label' => $this->get_item_label( $value_set_entry['object_id'], $value_set_entry['type'], $value_set_entry['subtype'] ),
411
				'is_trashed' => ( $value_set_entry['type'] == 'post' && get_post_status( $value_set_entry['object_id'] ) === 'trash' ),
412
			);
413
			$value[] = $item;
414
		}
415
		return $value;
416
	}
417
418
	/**
419
	 * Convert the field data into JSON representation.
420
	 * @param  bool $load Whether to load data from the datastore.
421
	 * @return mixed      The JSON field data.
422
	 */
423
	public function to_json( $load ) {
424
		$field_data = Field::to_json( $load );
425
426
		$field_data = array_merge( $field_data, array(
427
			'value' => $this->value_to_json(),
428
			'options' => $this->get_options(),
429
			'max' => $this->max,
430
			'allow_duplicates' => $this->allow_duplicates,
431
		) );
432
433
		$i = 0;
434
		foreach ( $field_data['value'] as $key => $value ) {
435
			$field_data['value'][ $key ]['fieldIndex'] = $i;
436
			$i++;
437
		}
438
		$field_data['nextfieldIndex'] = $i;
439
440
		return $field_data;
441
	}
442
443
	/**
444
	 * Serves as a backbone template for the association items.
445
	 * Used for both the selected and the selectable options.
446
	 *
447
	 * @param bool $display_input Whether to display the selected item input field.
448
	 */
449
	public function item_template( $display_input = true ) {
450
		?>
451
		<li>
452
			<span class="mobile-handle dashicons-before dashicons-menu"></span>
453
			<a href="#" data-item-id="{{{ item.id }}}" data-item-title="{{{ item.title }}}" data-item-type="{{{ item.type }}}" data-item-subtype="{{{ item.subtype }}}" data-item-label="{{{ item.label }}}" data-value="{{{ item.type }}}:{{{ item.subtype }}}:{{{ item.id }}}">
454
				<# if ( item.edit_link ) { #>
455
					<em class="edit-link dashicons-before dashicons-edit" data-href="{{{ item.edit_link }}}"><?php _e( 'Edit', \Carbon_Fields\TEXT_DOMAIN ); ?></em>
456
				<# } #>
457
				<em>{{{ item.label }}}</em>
458
				<span class="dashicons-before dashicons-plus-alt"></span>
459
				{{{ item.title }}}
460
				<# if (item.is_trashed) { #>
461
					<i class="trashed dashicons-before dashicons-trash"></i>
462
				<# } #>
463
			</a>
464
			<?php if ( $display_input ) :  ?>
465
				<input type="hidden" name="{{{ name }}}[{{{ item.fieldIndex }}}]" value="{{{ item.type }}}:{{{ item.subtype }}}:{{{ item.id }}}" />
466
			<?php endif; ?>
467
		</li>
468
		<?php
469
	}
470
}
471