1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Carbon_Fields\Field; |
4
|
|
|
|
5
|
|
|
/** |
6
|
|
|
* Relationship field class. |
7
|
|
|
* Allows selecting and manually sorting entries from any custom post type. |
8
|
|
|
*/ |
9
|
|
|
class Relationship_Field extends Field { |
10
|
|
|
protected $post_type = array( 'post' ); |
11
|
|
|
protected $max = -1; |
12
|
|
|
protected $allow_duplicates = false; |
13
|
|
|
|
14
|
|
|
/** |
15
|
|
|
* Admin initialization actions |
16
|
|
|
*/ |
17
|
|
|
public function admin_init() { |
18
|
|
|
$this->add_template( $this->get_type() . '_item', array( $this, 'item_template' ) ); |
19
|
|
|
|
20
|
|
|
parent::admin_init(); |
21
|
|
|
} |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* Load the field value from an input array based on it's name |
25
|
|
|
* |
26
|
|
|
* @param array $input (optional) Array of field names and values. Defaults to $_POST |
27
|
|
|
**/ |
28
|
|
View Code Duplication |
public function set_value_from_input( $input = null ) { |
|
|
|
|
29
|
|
|
if ( is_null( $input ) ) { |
30
|
|
|
$input = $_POST; |
|
|
|
|
31
|
|
|
} |
32
|
|
|
|
33
|
|
|
if ( ! isset( $input[ $this->name ] ) ) { |
34
|
|
|
$this->set_value( null ); |
35
|
|
|
} else { |
36
|
|
|
$value = stripslashes_deep( $input[ $this->name ] ); |
37
|
|
|
if ( is_array( $value ) ) { |
38
|
|
|
$value = array_values( $value ); |
39
|
|
|
} |
40
|
|
|
$this->set_value( $value ); |
41
|
|
|
} |
42
|
|
|
} |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* Set the post type of the entries. |
46
|
|
|
* |
47
|
|
|
* @param string|array $post_type Post type |
48
|
|
|
*/ |
49
|
|
|
public function set_post_type( $post_type ) { |
50
|
|
|
if ( ! is_array( $post_type ) ) { |
51
|
|
|
$post_type = array( $post_type ); |
52
|
|
|
} |
53
|
|
|
|
54
|
|
|
$this->post_type = $post_type; |
55
|
|
|
return $this; |
56
|
|
|
} |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* Set the maximum allowed number of selected entries. |
60
|
|
|
* |
61
|
|
|
* @param int $max |
62
|
|
|
*/ |
63
|
|
|
public function set_max( $max ) { |
64
|
|
|
$this->max = intval( $max ); |
65
|
|
|
return $this; |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* Specify whether to allow each entry to be selected multiple times. |
70
|
|
|
* |
71
|
|
|
* @param boolean $allow |
72
|
|
|
*/ |
73
|
|
|
public function allow_duplicates( $allow = true ) { |
74
|
|
|
$this->allow_duplicates = (bool) $allow; |
75
|
|
|
return $this; |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* Used to get the title of an item. |
80
|
|
|
* |
81
|
|
|
* Can be overriden or extended by the `carbon_relationship_title` filter. |
82
|
|
|
* |
83
|
|
|
* @param int $id The database ID of the item. |
84
|
|
|
* @param string $type Item type (post, term, user, comment, or a custom one). |
85
|
|
|
* @param string $subtype The subtype - "page", "post", "category", etc. |
86
|
|
|
* @return string $title The title of the item. |
87
|
|
|
*/ |
88
|
|
|
public function get_title_by_type( $id, $type, $subtype = '' ) { |
89
|
|
|
$title = get_the_title( $id ); |
90
|
|
|
if ( ! $title ) { |
91
|
|
|
$title = '(no title) - ID: ' . $id; |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
/** |
95
|
|
|
* Filter the title of the relationship item. |
96
|
|
|
* |
97
|
|
|
* @param string $title The unfiltered item title. |
98
|
|
|
* @param string $name Name of the relationship field. |
99
|
|
|
* @param int $id The database ID of the item. |
100
|
|
|
* @param string $type Item type (post, term, user, comment, or a custom one). |
101
|
|
|
* @param string $subtype Subtype - "page", "post", "category", etc. |
102
|
|
|
*/ |
103
|
|
|
return apply_filters( 'carbon_relationship_title', $title, $this->get_name(), $id, $type, $subtype ); |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* Used to get the label of an item. |
108
|
|
|
* |
109
|
|
|
* Can be overriden or extended by the `carbon_relationship_item_label` filter. |
110
|
|
|
* |
111
|
|
|
* @param int $id The database ID of the item. |
112
|
|
|
* @param string $type Item type (post, term, user, comment, or a custom one). |
113
|
|
|
* @param string $subtype Subtype - "page", "post", "category", etc. |
114
|
|
|
* @return string $label The label of the item. |
115
|
|
|
*/ |
116
|
|
|
public function get_item_label( $id, $type, $subtype = '' ) { |
117
|
|
|
$object = get_post_type_object( $subtype ); |
118
|
|
|
$label = $object ? $object->labels->singular_name : null; |
119
|
|
|
|
120
|
|
|
/** |
121
|
|
|
* Filter the label of the relationship item. |
122
|
|
|
* |
123
|
|
|
* @param string $label The unfiltered item label. |
124
|
|
|
* @param string $name Name of the relationship field. |
125
|
|
|
* @param int $id The database ID of the item. |
126
|
|
|
* @param string $type Item type (post, term, user, comment, or a custom one). |
127
|
|
|
* @param string $subtype Subtype - "page", "post", "category", etc. |
128
|
|
|
*/ |
129
|
|
|
return apply_filters( 'carbon_relationship_item_label', $label, $this->get_name(), $id, $type, $subtype ); |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
/** |
133
|
|
|
* Generate the item options for the relationship field. |
134
|
|
|
* |
135
|
|
|
* @return array $options The selectable options of the relationship field. |
136
|
|
|
*/ |
137
|
|
|
public function get_options() { |
138
|
|
|
$options = array(); |
139
|
|
|
/** |
140
|
|
|
* Filter the default query when fetching posts for a particular field. |
141
|
|
|
* |
142
|
|
|
* @param array $args The parameters, passed to get_posts(). |
143
|
|
|
*/ |
144
|
|
|
foreach ( $this->post_type as $post_type ) { |
145
|
|
|
$filter_name = 'carbon_relationship_options_' . $this->get_name() . '_post_' . $post_type; |
146
|
|
|
$args = apply_filters( $filter_name, array( |
147
|
|
|
'post_type' => $post_type, |
148
|
|
|
'posts_per_page' => -1, |
|
|
|
|
149
|
|
|
'fields' => 'ids', |
150
|
|
|
'suppress_filters' => false, |
151
|
|
|
) ); |
152
|
|
|
|
153
|
|
|
// fetch and prepare posts as relationship items |
154
|
|
|
$new_options = get_posts( $args ); |
155
|
|
|
foreach ( $new_options as &$p ) { |
156
|
|
|
$p = array( |
157
|
|
|
'id' => $p, |
158
|
|
|
'title' => $this->get_title_by_type( $p, 'post', $post_type ), |
159
|
|
|
'type' => 'post', |
160
|
|
|
'subtype' => $post_type, |
161
|
|
|
'label' => $this->get_item_label( $p, 'post', $post_type ), |
162
|
|
|
'is_trashed' => ( get_post_status( $p ) == 'trash' ), |
163
|
|
|
'edit_link' => get_edit_post_link( $p ), |
164
|
|
|
); |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
$options = array_merge( $options, $new_options ); |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
/** |
171
|
|
|
* Filter the final list of options, available to a certain relationship field. |
172
|
|
|
* |
173
|
|
|
* @param array $options Unfiltered options items. |
174
|
|
|
* @param string $name Name of the relationship field. |
175
|
|
|
*/ |
176
|
|
|
$options = apply_filters( 'carbon_relationship_options', $options, $this->get_name() ); |
177
|
|
|
|
178
|
|
|
return $options; |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
/** |
182
|
|
|
* Returns an array that holds the field data, suitable for JSON representation. |
183
|
|
|
* This data will be available in the Underscore template and the Backbone Model. |
184
|
|
|
* |
185
|
|
|
* @param bool $load Should the value be loaded from the database or use the value from the current instance. |
186
|
|
|
* @return array |
187
|
|
|
*/ |
188
|
|
|
public function to_json( $load ) { |
189
|
|
|
$field_data = parent::to_json( $load ); |
190
|
|
|
|
191
|
|
|
$field_data['nextfieldIndex'] = 0; |
192
|
|
|
if ( ! empty( $field_data['value'] ) ) { |
193
|
|
|
$value = array(); |
194
|
|
|
|
195
|
|
|
$field_data['value'] = maybe_unserialize( $field_data['value'] ); |
196
|
|
|
$i = 0; |
197
|
|
|
foreach ( $field_data['value'] as $single_value ) { |
198
|
|
|
$post_type = get_post_type( $single_value ); |
199
|
|
|
$value[] = array( |
200
|
|
|
'id' => $single_value, |
201
|
|
|
'title' => $this->get_title_by_type( $single_value, 'post', $post_type ), |
202
|
|
|
'type' => 'post', |
203
|
|
|
'subtype' => $post_type, |
204
|
|
|
'label' => $this->get_item_label( $single_value, 'post', $post_type ), |
205
|
|
|
'is_trashed' => ( get_post_status( $single_value ) == 'trash' ), |
206
|
|
|
'fieldIndex' => $i, |
207
|
|
|
); |
208
|
|
|
$i++; |
209
|
|
|
} |
210
|
|
|
$field_data['nextfieldIndex'] = $i; |
211
|
|
|
$field_data['value'] = $value; |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
$field_data = array_merge( $field_data, array( |
215
|
|
|
'options' => $this->get_options(), |
216
|
|
|
'max' => $this->max, |
217
|
|
|
'allow_duplicates' => $this->allow_duplicates, |
218
|
|
|
) ); |
219
|
|
|
|
220
|
|
|
return $field_data; |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
/** |
224
|
|
|
* The main Underscore template of this field. |
225
|
|
|
*/ |
226
|
|
|
public function template() { |
227
|
|
|
?> |
228
|
|
|
<div class="carbon-relationship-container"> |
229
|
|
|
<div class="selected-items-container"> |
230
|
|
|
<strong> |
231
|
|
|
<# |
232
|
|
|
var selected_items_length = 0; |
233
|
|
|
if ( value ) { |
234
|
|
|
selected_items_length = value.length; |
235
|
|
|
} #> |
236
|
|
|
<span class="selected-counter">{{{ selected_items_length }}}</span> |
237
|
|
|
<span class="selected-label" data-single-label="<?php _e( 'selected item', 'carbon-fields' ); ?>" data-plural-label="<?php _e( 'selected items', 'carbon-fields' ); ?>"> |
238
|
|
|
<?php _e( 'selected items', 'carbon-fields' ); ?> |
239
|
|
|
</span> |
240
|
|
|
|
241
|
|
|
<?php |
242
|
|
|
/* If set_max() has been set, show the allowed maximum items number */ |
243
|
|
|
?> |
244
|
|
|
<# if ( max !== -1 ) { #> |
245
|
|
|
<span class="remaining"><?php _e( 'out of', 'carbon-fields' ); ?> {{{ max }}}</span> |
246
|
|
|
<# } #> |
247
|
|
|
</strong> |
248
|
|
|
</div> |
249
|
|
|
|
250
|
|
|
<div class="search-field carbon-relationship-search dashicons-before dashicons-search"> |
251
|
|
|
<input type="text" class="search-field" placeholder="<?php esc_attr_e( 'Search...', 'carbon-fields' ); ?>" /> |
252
|
|
|
</div> |
253
|
|
|
|
254
|
|
|
<div class="carbon-relationship-body"> |
255
|
|
|
<div class="carbon-relationship-left"> |
256
|
|
|
<ul class="carbon-relationship-list"> |
257
|
|
|
<# if (options) { #> |
258
|
|
|
<# _.each(options, function(item) { #> |
259
|
|
|
<?php echo $this->item_template( false ); ?> |
|
|
|
|
260
|
|
|
<# }); #> |
261
|
|
|
<# } #> |
262
|
|
|
</ul> |
263
|
|
|
</div> |
264
|
|
|
|
265
|
|
|
<div class="carbon-relationship-right"> |
266
|
|
|
<label><?php _e( 'Associated:', 'carbon-fields' ); ?></label> |
267
|
|
|
|
268
|
|
|
<ul class="carbon-relationship-list"> |
269
|
|
|
<# if (value) { #> |
270
|
|
|
<# _.each(value, function(item) { #> |
271
|
|
|
<?php echo $this->item_template(); ?> |
|
|
|
|
272
|
|
|
<# }); #> |
273
|
|
|
<# } #> |
274
|
|
|
</ul> |
275
|
|
|
</div> |
276
|
|
|
</div> |
277
|
|
|
</div> |
278
|
|
|
<?php |
279
|
|
|
} |
280
|
|
|
|
281
|
|
|
/** |
282
|
|
|
* Serves as a Underscore template for the relationship items. |
283
|
|
|
* Used for both the selected and the selectable options. |
284
|
|
|
* |
285
|
|
|
* @param bool $display_input Whether to display the selected item input field. |
286
|
|
|
*/ |
287
|
|
|
public function item_template( $display_input = true ) { |
288
|
|
|
?> |
289
|
|
|
<li> |
290
|
|
|
<span class="mobile-handle dashicons-before dashicons-menu"></span> |
291
|
|
|
<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.id }}}"> |
292
|
|
|
<# if ( item.edit_link ) { #> |
293
|
|
|
<em class="edit-link dashicons-before dashicons-edit" data-href="{{{ item.edit_link }}}"><?php _e( 'Edit', 'carbon-fields' ); ?></em> |
294
|
|
|
<# } #> |
295
|
|
|
<em>{{{ item.label }}}</em> |
296
|
|
|
<span class="dashicons-before dashicons-plus-alt"></span> |
297
|
|
|
|
298
|
|
|
{{{ item.title }}} |
299
|
|
|
</a> |
300
|
|
|
<?php if ( $display_input ) : ?> |
301
|
|
|
<input type="hidden" name="{{{ name }}}[{{{ item.fieldIndex }}}]" value="{{{ item.id }}}" /> |
302
|
|
|
<?php endif; ?> |
303
|
|
|
</li> |
304
|
|
|
<?php |
305
|
|
|
} |
306
|
|
|
} |
307
|
|
|
|
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.