Completed
Push — milestone/2.0 ( c3aeae...1980fc )
by
unknown
06:00
created

Association_Field::value_to_json()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

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