Completed
Push — development ( 5ed9d2...646735 )
by
unknown
04:14
created

Association_Field::set_min()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
ccs 0
cts 3
cp 0
crap 2
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 Field {
16
17
	/**
18
	 * WP_Toolset instance for WP data loading
19
	 *
20
	 * @var Carbon_Fields\Toolset\WP_Toolset
21
	 */
22
	protected $wp_toolset;
23
24
	/**
25
	 * Min number of selected items allowed. -1 for no limit
26
	 *
27
	 * @var integer
28
	 */
29
	protected $min = -1;
30
31
	/**
32
	 * Max number of selected items allowed. -1 for no limit
33
	 *
34
	 * @var integer
35
	 */
36
	protected $max = -1;
37
38
	/**
39
	 * Allow items to be added multiple times
40
	 *
41
	 * @var boolean
42
	 */
43
	protected $duplicates_allowed = false;
44
45
	/**
46
	 * Default field value
47
	 *
48
	 * @var array
49
	 */
50
	protected $default_value = array();
51
52
	/**
53
	 * Types of entries to associate with.
54
	 * @var array
55
	 */
56
	protected $types = array(
57
		array(
58
			'type' => 'post',
59
			'post_type' => 'post',
60
		),
61
	);
62
63
	/**
64
	 * Create a field from a certain type with the specified label.
65
	 *
66
	 * @param string $type  Field type
67
	 * @param string $name  Field name
68
	 * @param string $label Field label
69
	 */
70
	public function __construct( $type, $name, $label ) {
71
		$this->wp_toolset = \Carbon_Fields\Carbon_Fields::resolve( 'wp_toolset' );
72
		$this->set_value_set( new Value_Set( Value_Set::TYPE_VALUE_SET, array( 'type' => '', 'subtype' => '', 'id' => 0 ) ) );
73
		parent::__construct( $type, $name, $label );
74
	}
75
76
	/**
77
	 * {@inheritDoc}
78
	 */
79
	public function set_value_from_input( $input ) {
80
		$value = array();
81
		if ( isset( $input[ $this->get_name() ] ) ) {
82
			$value = stripslashes_deep( $input[ $this->get_name() ] );
83
			if ( is_array( $value ) ) {
84
				$value = array_values( $value );
85
			}
86
		}
87
		$this->set_value( $value );
88
		return $this;
89
	}
90
91
	/**
92
	 * {@inheritDoc}
93
	 */
94
	public function set_value( $value ) {
95
		$value = $this->value_string_array_to_value_set( $value );
96
		return parent::set_value( $value );
97
	}
98
99
	/**
100
	 * Get value string for legacy value
101
	 *
102
	 * @return string
103
	 */
104
	protected function get_value_string_for_legacy_value( $legacy_value ) {
105
		$entry_type = 'post';
106
		$entry_subtype = 'post';
107
108
		// attempt to find a suitable type that is registered to this field as post type is not stored for legacy data
109
		foreach ( $this->types as $type ) {
110
			if ( $type['type'] === $entry_type ) {
111
				$entry_subtype = $type['post_type'];
112
				break;
113
			}
114
		}
115
116
		return $entry_type . ':' . $entry_subtype . ':' . $legacy_value;
117
	}
118
119
	/**
120
	 * Convert a colon:separated:string into its expected components
121
	 * Used for backwards compatibility to CF 1.5
122
	 *
123
	 * @param string $value_string
124
	 * @return array
125
	 */
126
	protected function value_string_to_property_array( $value_string ) {
127
		if ( is_numeric( $value_string ) ) {
128
			// we are dealing with legacy data that only contains a post ID
129
			$value_string = $this->get_value_string_for_legacy_value( $value_string );
130
		}
131
132
		$value_pieces = explode( ':', $value_string );
133
		$type = isset( $value_pieces[0] ) ? $value_pieces[0] : 'post';
134
		$subtype = isset( $value_pieces[1] ) ? $value_pieces[1] : 'post';
135
		$id = isset( $value_pieces[2] ) ? $value_pieces[2] : 0;
136
137
		$property_array = array(
138
			Value_Set::VALUE_PROPERTY => $value_string,
139
			'type' => $type,
140
			'subtype' => $subtype,
141
			'id' => intval( $id ),
142
		);
143
		return $property_array;
144
	}
145
146
	/**
147
	 * Convert a colon:separated:string into its expected components
148
	 * Used for backwards compatibility to CF 1.5
149
	 *
150
	 * @param array $value_string_array
151
	 * @return array<array>
152
	 */
153
	protected function value_string_array_to_value_set( $value_string_array ) {
154
		$value_set = array();
155
		foreach ( $value_string_array as $raw_value_entry ) {
156
			$value_string = $raw_value_entry;
157
158
			if ( is_array( $raw_value_entry ) ) {
159
				if ( isset( $raw_value_entry['type'] ) ) {
160
					// array is already in suitable format
161
					$value_set[] = $raw_value_entry;
162
					continue;
163
				}
164
				$value_string = $raw_value_entry[ Value_Set::VALUE_PROPERTY ];
165
			}
166
			$value_string = trim( $value_string );
167
			if ( empty( $value_string ) ) {
168
				continue;
169
			}
170
171
			$property_array = $this->value_string_to_property_array( $value_string );
172
			$value_set[] = $property_array;
173
		}
174
		return $value_set;
175
	}
176
177
	/**
178
	 * Used to get the title of an item.
179
	 *
180
	 * Can be overriden or extended by the `carbon_association_title` filter.
181
	 *
182
	 * @param int $id The database ID of the item.
183
	 * @param string $type Item type (post, term, user, comment, or a custom one).
184
	 * @param string $subtype The subtype - "page", "post", "category", etc.
185
	 * @return string $title The title of the item.
186
	 */
187
	protected function get_title_by_type( $id, $type, $subtype = '' ) {
188
		$title = '';
189
190
		$method = 'get_' . $type . '_title';
191
		$callable = array( $this->wp_toolset, $method );
192
		if ( is_callable( $callable ) ) {
193
			$title = call_user_func( $callable, $id, $subtype );
194
		}
195
196
		if ( $type === 'comment' ) {
197
			$max = apply_filters( 'carbon_fields_association_field_comment_length', 30, $this->get_base_name() );
198
			if ( strlen( $title ) > $max ) {
199
				$title = substr( $title, 0, $max ) . '...';
200
			}
201
		}
202
203
		/**
204
		 * Filter the title of the association item.
205
		 *
206
		 * @param string $title   The unfiltered item title.
207
		 * @param string $name    Name of the association field.
208
		 * @param int    $id      The database ID of the item.
209
		 * @param string $type    Item type (post, term, user, comment, or a custom one).
210
		 * @param string $subtype Subtype - "page", "post", "category", etc.
211
		 */
212
		$title = apply_filters( 'carbon_fields_association_field_title', $title, $this->get_base_name(), $id, $type, $subtype );
213
214
		if ( ! $title ) {
215
			$title = '(no title) - ID: ' . $id;
216
		}
217
218
		return $title;
219
	}
220
221
	/**
222
	 * Used to get the label of an item.
223
	 *
224
	 * Can be overriden or extended by the `carbon_association_item_label` filter.
225
	 *
226
	 * @param int     $id      The database ID of the item.
227
	 * @param string  $type    Item type (post, term, user, comment, or a custom one).
228
	 * @param string  $subtype Subtype - "page", "post", "category", etc.
229
	 * @return string $label The label of the item.
230
	 */
231
	protected function get_item_label( $id, $type, $subtype = '' ) {
232
		$label = $subtype ? $subtype : $type;
233
234
		if ( $type === 'post' ) {
235
			$post_type_object = get_post_type_object( $subtype );
236
			$label = $post_type_object->labels->singular_name;
237
		} elseif ( $type === 'term' ) {
238
			$taxonomy_object = get_taxonomy( $subtype );
239
			$label = $taxonomy_object->labels->singular_name;
240
		}
241
242
		/**
243
		 * Filter the label of the association item.
244
		 *
245
		 * @param string $label   The unfiltered item label.
246
		 * @param string $name    Name of the association field.
247
		 * @param int    $id      The database ID of the item.
248
		 * @param string $type    Item type (post, term, user, comment, or a custom one).
249
		 * @param string $subtype Subtype - "page", "post", "category", etc.
250
		 */
251
		return apply_filters( 'carbon_fields_association_field_item_label', $label, $this->get_base_name(), $id, $type, $subtype );
252
	}
253
254
	/**
255
	 * Get post options
256
	 *
257
	 * @return array $options
258
	 */
259
	protected function get_post_options( $type ) {
260
		/**
261
		 * Filter the default query when fetching posts for a particular field.
262
		 *
263
		 * @param array $args The parameters, passed to get_posts().
264
		 */
265
		$filter_name = 'carbon_fields_association_field_options_' . $this->get_base_name() . '_' . $type['type'] . '_' . $type['post_type'];
266
		$args = apply_filters( $filter_name, array(
267
			'post_type' => $type['post_type'],
268
			'posts_per_page' => -1,
269
			'fields' => 'ids',
270
			'suppress_filters' => false,
271
		) );
272
273
		// fetch and prepare posts as association items
274
		$posts = get_posts( $args );
275
		foreach ( $posts as &$p ) {
276
			$p = array(
277
				'id' => intval( $p ),
278
				'title' => $this->get_title_by_type( $p, $type['type'], $type['post_type'] ),
279
				'type' => $type['type'],
280
				'subtype' => $type['post_type'],
281
				'label' => $this->get_item_label( $p, $type['type'], $type['post_type'] ),
282
				'is_trashed' => ( get_post_status( $p ) == 'trash' ),
283
				'edit_link' => $this->get_object_edit_link( $type, $p ),
284
			);
285
		}
286
		return $posts;
287
	}
288
289
	/**
290
	 * Get term options
291
	 *
292
	 * @return array $options
293
	 */
294
	protected function get_term_options( $type ) {
295
		/**
296
		 * Filter the default parameters when fetching terms for a particular field.
297
		 *
298
		 * @param array $args The parameters, passed to get_terms().
299
		 */
300
		$filter_name = 'carbon_fields_association_field_options_' . $this->get_base_name() . '_' . $type['type'] . '_' . $type['taxonomy'];
301
		$args = apply_filters( $filter_name, array(
302
			'hide_empty' => 0,
303
			'fields' => 'id=>name',
304
		) );
305
306
		// fetch and prepare terms as association items
307
		$terms = get_terms( $type['taxonomy'], $args );
308
		foreach ( $terms as $term_id => &$term ) {
309
			$term = array(
310
				'id' => intval( $term_id ),
311
				'title' => $term,
312
				'type' => $type['type'],
313
				'subtype' => $type['taxonomy'],
314
				'label' => $this->get_item_label( $term_id, $type['type'], $type['taxonomy'] ),
315
				'is_trashed' => false,
316
				'edit_link' => $this->get_object_edit_link( $type, $term_id ),
317
			);
318
		}
319
		return $terms;
320
	}
321
322
	/**
323
	 * Get user options
324
	 *
325
	 * @return array $options
326
	 */
327 View Code Duplication
	protected function get_user_options( $type ) {
328
		/**
329
		 * Filter the default parameters when fetching users for a particular field.
330
		 *
331
		 * @param array $args The parameters, passed to get_users().
332
		 */
333
		$filter_name = 'carbon_fields_association_field_options_' . $this->get_base_name() . '_' . $type['type'];
334
		$args = apply_filters( $filter_name, array(
335
			'fields' => 'ID',
336
		) );
337
338
		// fetch and prepare users as association items
339
		$users = get_users( $args );
340
		foreach ( $users as &$u ) {
341
			$u = array(
342
				'id' => intval( $u ),
343
				'title' => $this->get_title_by_type( $u, $type['type'] ),
344
				'type' => $type['type'],
345
				'subtype' => 'user',
346
				'label' => $this->get_item_label( $u, $type['type'] ),
347
				'is_trashed' => false,
348
				'edit_link' => $this->get_object_edit_link( $type, $u ),
349
			);
350
		}
351
		return $users;
352
	}
353
354
	/**
355
	 * Get comment options
356
	 *
357
	 * @return array $options
358
	 */
359 View Code Duplication
	protected function get_comment_options( $type ) {
360
		/**
361
		 * Filter the default parameters when fetching comments for a particular field.
362
		 *
363
		 * @param array $args The parameters, passed to get_comments().
364
		 */
365
		$filter_name = 'carbon_fields_association_field_options_' . $this->get_base_name() . '_' . $type['type'];
366
		$args = apply_filters( $filter_name, array(
367
			'fields' => 'ids',
368
		) );
369
370
		// fetch and prepare comments as association items
371
		$comments = get_comments( $args );
372
		foreach ( $comments as &$c ) {
373
			$c = array(
374
				'id' => intval( $c ),
375
				'title' => $this->get_title_by_type( $c, $type['type'] ),
376
				'type' => $type['type'],
377
				'subtype' => 'comment',
378
				'label' => $this->get_item_label( $c, $type['type'] ),
379
				'is_trashed' => false,
380
				'edit_link' => $this->get_object_edit_link( $type, $c ),
381
			);
382
		}
383
		return $comments;
384
	}
385
386
	/**
387
	 * Generate the item options.
388
	 *
389
	 * @return array $options The selectable options of the association field.
390
	 */
391
	public function get_options() {
392
		$options = array();
393
394
		foreach ( $this->types as $type ) {
395
			$method = 'get_' . $type['type'] . '_options';
396
			$callable = array( $this, $method );
397
			if ( is_callable( $callable ) ) {
398
				$options = array_merge( $options, call_user_func( $callable, $type ) );
399
			}
400
		}
401
402
		/**
403
		 * Filter the final list of options, available to a certain association field.
404
		 *
405
		 * @param array $options Unfiltered options items.
406
		 * @param string $name Name of the association field.
407
		 */
408
		$options = apply_filters( 'carbon_fields_association_field_options', $options, $this->get_base_name() );
409
410
		return $options;
411
	}
412
413
	/**
414
	 * Retrieve the edit link of a particular object.
415
	 *
416
	 * @param  string $type Object type.
417
	 * @param  int $id      ID of the object.
418
	 * @return string       URL of the edit link.
419
	 */
420
	protected function get_object_edit_link( $type, $id ) {
421
		switch ( $type['type'] ) {
422
423
			case 'post':
424
				$edit_link = get_edit_post_link( $id );
425
				break;
426
427
			case 'term':
428
				$edit_link = get_edit_term_link( $id, $type['taxonomy'], $type['type'] );
429
				break;
430
431
			case 'comment':
432
				$edit_link = get_edit_comment_link( $id );
433
				break;
434
435
			case 'user':
436
				$edit_link = get_edit_user_link( $id );
437
				break;
438
439
			default:
440
				$edit_link = false;
441
442
		}
443
444
		return $edit_link;
445
	}
446
447
	/**
448
	 * Modify the types.
449
	 *
450
	 * @param  array $types New types
451
	 * @return self  $this
452
	 */
453
	public function set_types( $types ) {
454
		$this->types = $types;
455
		return $this;
456
	}
457
458
	/**
459
	 * Get the minimum allowed number of selected entries.
460
	 *
461
	 * @return int
462
	 */
463
	public function get_min() {
464
		return $this->min;
465
	}
466
467
	/**
468
	 * Set the minimum allowed number of selected entries.
469
	 *
470
	 * @param  int   $min
471
	 * @return self  $this
472
	 */
473
	public function set_min( $min ) {
474
		$this->min = intval( $min );
475
		return $this;
476
	}
477
478
	/**
479
	 * Get the maximum allowed number of selected entries.
480
	 *
481
	 * @return int
482
	 */
483
	public function get_max() {
484
		return $this->max;
485
	}
486
487
	/**
488
	 * Set the maximum allowed number of selected entries.
489
	 *
490
	 * @param  int   $max
491
	 * @return self  $this
492
	 */
493
	public function set_max( $max ) {
494
		$this->max = intval( $max );
495
		return $this;
496
	}
497
498
	/**
499
	 * Get whether entry duplicates are allowed.
500
	 *
501
	 * @return boolean
502
	 */
503
	public function get_duplicates_allowed() {
504
		return $this->duplicates_allowed;
505
	}
506
507
	/**
508
	 * Set whether entry duplicates are allowed.
509
	 *
510
	 * @param  boolean $allowed
511
	 * @return self    $this
512
	 */
513
	public function set_duplicates_allowed( $allowed ) {
514
		$this->duplicates_allowed = $allowed;
515
		return $this;
516
	}
517
518
	/**
519
	 * Specify whether to allow each entry to be selected multiple times.
520
	 * Backwards-compatibility alias.
521
	 *
522
	 * @param  boolean $allow
523
	 * @return self    $this
524
	 */
525
	public function allow_duplicates( $allow = true ) {
526
		return $this->set_duplicates_allowed( $allow );
527
	}
528
529
	/**
530
	 * Converts the field values into a usable associative array.
531
	 *
532
	 * The association data is saved in the database in the following format:
533
	 * 	array (
534
	 *		0 => 'post:page:4',
535
	 *		1 => 'term:category:2',
536
	 *		2 => 'user:user:1',
537
	 * 	)
538
	 * where the value of each array item contains:
539
	 * 	- Type of data (post, term, user or comment)
540
	 * 	- Subtype of data (the particular post type or taxonomy)
541
	 * 	- ID of the item (the database ID of the item)
542
	 */
543
	protected function value_to_json() {
544
		$value_set = $this->get_value();
545
		$value = array();
546
		foreach ( $value_set as $entry ) {
547
			$item = array(
548
				'type' => $entry['type'],
549
				'subtype' => $entry['subtype'],
550
				'id' => intval( $entry['id'] ),
551
				'title' => $this->get_title_by_type( $entry['id'], $entry['type'], $entry['subtype'] ),
552
				'label' => $this->get_item_label( $entry['id'], $entry['type'], $entry['subtype'] ),
553
				'is_trashed' => ( $entry['type'] == 'post' && get_post_status( $entry['id'] ) === 'trash' ),
554
			);
555
			$value[] = $item;
556
		}
557
		return $value;
558
	}
559
560
	/**
561
	 * Convert the field data into JSON representation.
562
	 * @param  bool $load Whether to load data from the datastore.
563
	 * @return mixed      The JSON field data.
564
	 */
565
	public function to_json( $load ) {
566
		$field_data = parent::to_json( $load );
567
568
		$field_data = array_merge( $field_data, array(
569
			'value' => $this->value_to_json(),
570
			'options' => $this->get_options(),
571
			'min' => $this->get_min(),
572
			'max' => $this->get_max(),
573
			'duplicates_allowed' => $this->duplicates_allowed,
574
		) );
575
576
		return $field_data;
577
	}
578
}
579