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 ) { |
|
|
|
|
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 ) { |
|
|
|
|
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
|
|
|
|
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.