Completed
Push — milestone/2.0 ( dae7a5...48be68 )
by
unknown
03:13
created

value_string_to_property_array()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 8
nc 1
nop 1
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace Carbon_Fields\Field;
4
5
use Carbon_Fields\Value_Set\Value_Set;
6
7
/**
8
 * Association field class.
9
 * Allows selecting and manually sorting entries from various types:
10
 *  - Posts
11
 *  - Terms
12
 *  - Users
13
 *  - Comments
14
 */
15
class Association_Field extends Relationship_Field {
16
	/**
17
	 * Types of entries to associate with.
18
	 * @var array
19
	 */
20
	protected $types = array(
21
		array(
22
			'type' => 'post',
23
			'post_type' => 'post',
24
		),
25
	);
26
27
	/**
28
	 * Modify the types.
29
	 * @param array $types New types
30
	 */
31
	public function set_types( $types ) {
32
		$this->types = $types;
33
		return $this;
34
	}
35
36
	/**
37
	 * Create a field from a certain type with the specified label.
38
	 * @param string $name  Field name
39
	 * @param string $label Field label
40
	 */
41
	protected function __construct( $name, $label ) {
42
		$this->value = new Value_Set( Value_Set::TYPE_VALUE_SET, array( 'type'=>'', 'subtype'=>'', 'object_id'=>0 ) );
0 ignored issues
show
introduced by
Expected 1 space between "'type'" and double arrow; 0 found
Loading history...
introduced by
Expected 1 space between double arrow and "''"; 0 found
Loading history...
introduced by
Expected 1 space before "=>"; 0 found
Loading history...
introduced by
Expected 1 space after "=>"; 0 found
Loading history...
introduced by
Expected 1 space between "'subtype'" and double arrow; 0 found
Loading history...
introduced by
Expected 1 space between "'object_id'" and double arrow; 0 found
Loading history...
introduced by
Expected 1 space between double arrow and "0"; 0 found
Loading history...
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
	 * Convert a colo:separated:string into it's expected components
56
	 * Used for backwards compatibility to CF 1.5
57
	 * 
58
	 * @param string $value_string
59
	 * @return array
60
	 */
61
	protected function value_string_to_property_array( $value_string ) {
62
		$value_pieces = explode( ':', $value_string );
63
		$property_array = array(
64
			Value_Set::VALUE_PROPERTY => $value_string,
65
			'type' => $value_pieces[0],
66
			'subtype' => $value_pieces[1],
67
			'object_id' => $value_pieces[2],
68
		);
69
		return $property_array;
70
	}
71
72
	/**
73
	 * Convert a colo:separated:string into it's expected components
74
	 * Used for backwards compatibility to CF 1.5
75
	 * 
76
	 * @param string $value_string
0 ignored issues
show
Documentation introduced by
There is no parameter named $value_string. Did you maybe mean $value_string_array?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
77
	 * @return array<array>
78
	 */
79
	protected function value_string_array_to_value_set( $value_string_array ) {
80
		$value_set = array();
81
		foreach ( $value_string_array as $raw_value_entry ) {
82
			if ( is_array( $raw_value_entry ) && isset( $raw_value_entry['type'] ) ) {
83
				// array is already in suitable format
84
				$value_set[] = $raw_value_entry;
85
				continue;
86
			}
87
88
			$value_string = is_array( $raw_value_entry ) ? $raw_value_entry[ Value_Set::VALUE_PROPERTY ] : $raw_value_entry;
89
			$property_array = $this->value_string_to_property_array( $value_string );
90
			$value_set[] = $property_array;
91
		}
92
		return $value_set;
93
	}
94
95
	/**
96
	 * Converts the field values into a usable associative array.
97
	 *
98
	 * The relationship data is saved in the database in the following format:
99
	 * 	array (
100
	 *		0 => 'post:page:4',
101
	 *		1 => 'term:category:2',
102
	 *		2 => 'user:user:1',
103
	 * 	)
104
	 * where the value of each array item contains:
105
	 * 	- Type of data (post, term, user or comment)
106
	 * 	- Subtype of data (the particular post type or taxonomy)
107
	 * 	- ID of the item (the database ID of the item)
108
	 */
109
	protected function value_to_json() {
110
		$value_set = $this->get_value();
111
		$value = array();
112
		foreach ( $value_set as $value_set_entry ) {
1 ignored issue
show
Bug introduced by
The expression $value_set of type null|string|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
113
			$item = array(
114
				'type' => $value_set_entry['type'],
115
				'subtype' => $value_set_entry['subtype'],
116
				'id' => $value_set_entry['object_id'],
117
				'title' => $this->get_title_by_type( $value_set_entry['object_id'], $value_set_entry['type'], $value_set_entry['subtype'] ),
118
				'label' => $this->get_item_label( $value_set_entry['object_id'], $value_set_entry['type'], $value_set_entry['subtype'] ),
119
				'is_trashed' => ( $value_set_entry['type'] == 'post' && get_post_status( $value_set_entry['object_id'] ) === 'trash' ),
120
			);
121
			$value[] = $item;
122
		}
123
		return $value;
124
	}
125
126
	/**
127
	 * Used to get the title of an item.
128
	 *
129
	 * Can be overriden or extended by the `carbon_relationship_title` filter.
130
	 *
131
	 * @param int     $id      The database ID of the item.
132
	 * @param string  $type    Item type (post, term, user, comment, or a custom one).
133
	 * @param string  $subtype The subtype - "page", "post", "category", etc.
134
	 * @return string $title The title of the item.
135
	 */
136
	public function get_title_by_type( $id, $type, $subtype = '' ) {
137
		$title = '';
138
139
		if ( $type === 'post' ) {
140
			$title = get_the_title( $id );
141
		} elseif ( $type === 'term' ) {
142
			$term = get_term_by( 'id', $id, $subtype );
143
			$title = $term->name;
144
		} elseif ( $type === 'user' ) {
145
			$title = get_the_author_meta( 'user_login', $id );
146
		} elseif ( $type === 'comment' ) {
147
			$title = get_comment_text( $id );
148
			$max = apply_filters( 'carbon_relationship_comment_length', 30, $this->get_name() );
149
			if ( strlen( $title ) > $max ) {
150
				$title = substr( $title, 0, $max ) . '...';
151
			}
152
		}
153
154
		if ( ! $title ) {
155
			$title = '(no title) - ID: ' . $id;
156
		}
157
158
		/**
159
		 * Filter the title of the relationship item.
160
		 *
161
		 * @param string $title   The unfiltered item title.
162
		 * @param string $name    Name of the relationship field.
163
		 * @param int    $id      The database ID of the item.
164
		 * @param string $type    Item type (post, term, user, comment, or a custom one).
165
		 * @param string $subtype Subtype - "page", "post", "category", etc.
166
		 */
167
		return apply_filters( 'carbon_relationship_title', $title, $this->get_name(), $id, $type, $subtype );
168
	}
169
170
	/**
171
	 * Used to get the label of an item.
172
	 *
173
	 * Can be overriden or extended by the `carbon_relationship_item_label` filter.
174
	 *
175
	 * @param int     $id      The database ID of the item.
176
	 * @param string  $type    Item type (post, term, user, comment, or a custom one).
177
	 * @param string  $subtype Subtype - "page", "post", "category", etc.
178
	 * @return string $label The label of the item.
179
	 */
180
	public function get_item_label( $id, $type, $subtype = '' ) {
181
		$label = $subtype ? $subtype : $type;
182
183
		if ( $type === 'post' ) {
184
			$post_type_object = get_post_type_object( $subtype );
185
			$label = $post_type_object->labels->singular_name;
186
		} elseif ( $type === 'term' ) {
187
			$taxonomy_object = get_taxonomy( $subtype );
188
			$label = $taxonomy_object->labels->singular_name;
189
		}
190
191
		/**
192
		 * Filter the label of the relationship item.
193
		 *
194
		 * @param string $label   The unfiltered item label.
195
		 * @param string $name    Name of the relationship field.
196
		 * @param int    $id      The database ID of the item.
197
		 * @param string $type    Item type (post, term, user, comment, or a custom one).
198
		 * @param string $subtype Subtype - "page", "post", "category", etc.
199
		 */
200
		return apply_filters( 'carbon_relationship_item_label', $label, $this->get_name(), $id, $type, $subtype );
201
	}
202
203
	/**
204
	 * Generate the item options for the relationship field.
205
	 *
206
	 * @return array $options The selectable options of the relationship field.
207
	 */
208
	public function get_options() {
209
		$options = array();
210
211
		foreach ( $this->types as $type ) {
212
213
			if ( $type['type'] === 'post' ) { // populate posts
214
215
				/**
216
				 * Filter the default query when fetching posts for a particular field.
217
				 *
218
				 * @param array $args The parameters, passed to get_posts().
219
				 */
220
				$filter_name = 'carbon_relationship_options_' . $this->get_name() . '_' . $type['type'] . '_' . $type['post_type'];
221
				$args = apply_filters( $filter_name, array(
222
					'post_type' => $type['post_type'],
223
					'posts_per_page' => -1,
1 ignored issue
show
introduced by
Disabling pagination is prohibited in VIP context, do not set posts_per_page to -1 ever.
Loading history...
224
					'fields' => 'ids',
225
					'suppress_filters' => false,
226
				) );
227
228
				// fetch and prepare posts as relationship items
229
				$posts = get_posts( $args );
230
				foreach ( $posts as &$p ) {
231
					$p = array(
232
						'id' => $p,
233
						'title' => $this->get_title_by_type( $p, $type['type'], $type['post_type'] ),
234
						'type' => $type['type'],
235
						'subtype' => $type['post_type'],
236
						'label' => $this->get_item_label( $p, $type['type'], $type['post_type'] ),
237
						'is_trashed' => ( get_post_status( $p ) == 'trash' ),
238
						'edit_link' => $this->get_object_edit_link( $type, $p ),
239
					);
240
				}
241
				$options = array_merge( $options, $posts );
242
243
			} elseif ( $type['type'] === 'term' ) { // populate taxonomy terms
244
245
				/**
246
				 * Filter the default parameters when fetching terms for a particular field.
247
				 *
248
				 * @param array $args The parameters, passed to get_terms().
249
				 */
250
				$filter_name = 'carbon_relationship_options_' . $this->get_name() . '_' . $type['type'] . '_' . $type['taxonomy'];
251
				$args = apply_filters( $filter_name, array(
252
					'hide_empty' => 0,
253
					'fields' => 'id=>name',
254
				) );
255
256
				// fetch and prepare terms as relationship items
257
				$terms = get_terms( $type['taxonomy'], $args );
258 View Code Duplication
				foreach ( $terms as $term_id => &$term ) {
259
					$term = array(
260
						'id' => $term_id,
261
						'title' => $term,
262
						'type' => $type['type'],
263
						'subtype' => $type['taxonomy'],
264
						'label' => $this->get_item_label( $term_id, $type['type'], $type['taxonomy'] ),
265
						'is_trashed' => false,
266
						'edit_link' => $this->get_object_edit_link( $type, $term_id ),
267
					);
268
				}
269
				$options = array_merge( $options, $terms );
270
271
			} elseif ( $type['type'] === 'user' ) { // populate users
272
273
				/**
274
				 * Filter the default parameters when fetching users for a particular field.
275
				 *
276
				 * @param array $args The parameters, passed to get_users().
277
				 */
278
				$filter_name = 'carbon_relationship_options_' . $this->get_name() . '_' . $type['type'];
279
				$args = apply_filters( $filter_name, array(
280
					'fields' => 'ID',
281
				) );
282
283
				// fetch and prepare users as relationship items
284
				$users = get_users( $args );
285 View Code Duplication
				foreach ( $users as &$u ) {
286
					$u = array(
287
						'id' => $u,
288
						'title' => $this->get_title_by_type( $u, $type['type'] ),
289
						'type' => $type['type'],
290
						'subtype' => 'user',
291
						'label' => $this->get_item_label( $u, $type['type'] ),
292
						'is_trashed' => false,
293
						'edit_link' => $this->get_object_edit_link( $type, $u ),
294
					);
295
				}
296
				$options = array_merge( $options, $users );
297
298
			} elseif ( $type['type'] === 'comment' ) { // populate comments
299
300
				/**
301
				 * Filter the default parameters when fetching comments for a particular field.
302
				 *
303
				 * @param array $args The parameters, passed to get_comments().
304
				 */
305
				$filter_name = 'carbon_relationship_options_' . $this->get_name() . '_' . $type['type'];
306
				$args = apply_filters( $filter_name, array(
307
					'fields' => 'ids',
308
				) );
309
310
				// fetch and prepare comments as relationship items
311
				$comments = get_comments( $args );
312 View Code Duplication
				foreach ( $comments as &$c ) {
313
					$c = array(
314
						'id' => $c,
315
						'title' => $this->get_title_by_type( $c, $type['type'] ),
316
						'type' => $type['type'],
317
						'subtype' => 'comment',
318
						'label' => $this->get_item_label( $c, $type['type'] ),
319
						'is_trashed' => false,
320
						'edit_link' => $this->get_object_edit_link( $type, $c ),
321
					);
322
				}
323
				$options = array_merge( $options, $comments );
324
325
			}
326
		}
327
328
		/**
329
		 * Filter the final list of options, available to a certain relationship field.
330
		 *
331
		 * @param array $options Unfiltered options items.
332
		 * @param string $name Name of the relationship field.
333
		 */
334
		$options = apply_filters( 'carbon_relationship_options', $options, $this->get_name() );
335
336
		return $options;
337
	}
338
339
	/**
340
	 * Retrieve the edit link of a particular object.
341
	 *
342
	 * @param  string $type Object type.
343
	 * @param  int $id      ID of the object.
344
	 * @return string       URL of the edit link.
345
	 */
346
	public function get_object_edit_link( $type, $id ) {
347
		switch ( $type['type'] ) {
348
349
			case 'post':
350
				$edit_link = get_edit_post_link( $id );
351
				break;
352
353
			case 'term':
354
				$edit_link = get_edit_term_link( $id, $type['taxonomy'], $type['type'] );
355
				break;
356
357
			case 'comment':
358
				$edit_link = get_edit_comment_link( $id );
359
				break;
360
361
			case 'user':
362
				$edit_link = get_edit_user_link( $id );
363
				break;
364
365
			default:
366
				$edit_link = false;
367
368
		}
369
370
		return $edit_link;
371
	}
372
373
	/**
374
	 * Return a differently formatted value for end-users
375
	 *
376
	 * @return mixed
377
	 **/
378
	public function get_formatted_value() {
379
		return Field::get_formatted_value();
380
	}
381
382
	/**
383
	 * Convert the field data into JSON representation.
384
	 * @param  bool $load Whether to load data from the datastore.
385
	 * @return mixed      The JSON field data.
386
	 */
387
	public function to_json( $load ) {
388
		$field_data = Field::to_json( $load );
389
390
		$field_data = array_merge( $field_data, array(
391
			'value' => $this->value_to_json(),
392
			'options' => $this->get_options(),
393
			'max' => $this->max,
394
			'allow_duplicates' => $this->allow_duplicates,
395
		) );
396
397
		$i = 0;
398
		foreach ( $field_data['value'] as $key => $value ) {
399
			$field_data['value'][ $key ]['fieldIndex'] = $i;
400
			$i++;
401
		}
402
		$field_data['nextfieldIndex'] = $i;
403
404
		return $field_data;
405
	}
406
407
	/**
408
	 * Serves as a backbone template for the relationship items.
409
	 * Used for both the selected and the selectable options.
410
	 *
411
	 * @param bool $display_input Whether to display the selected item input field.
412
	 */
413
	public function item_template( $display_input = true ) {
414
		?>
415
		<li>
416
			<span class="mobile-handle dashicons-before dashicons-menu"></span>
417
			<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 }}}">
418
				<# if ( item.edit_link ) { #>
419
					<em class="edit-link dashicons-before dashicons-edit" data-href="{{{ item.edit_link }}}"><?php _e( 'Edit', \Carbon_Fields\TEXT_DOMAIN ); ?></em>
420
				<# } #>
421
				<em>{{{ item.label }}}</em>
422
				<span class="dashicons-before dashicons-plus-alt"></span>
423
				{{{ item.title }}}
424
				<# if (item.is_trashed) { #>
425
					<i class="trashed dashicons-before dashicons-trash"></i>
426
				<# } #>
427
			</a>
428
			<?php if ( $display_input ) :  ?>
429
				<input type="hidden" name="{{{ name }}}[{{{ item.fieldIndex }}}]" value="{{{ item.type }}}:{{{ item.subtype }}}:{{{ item.id }}}" />
430
			<?php endif; ?>
431
		</li>
432
		<?php
433
	}
434
}
435