PluginProbe ʕ •ᴥ•ʔ
Secure Custom Fields / 6.4.1-beta6
Secure Custom Fields v6.4.1-beta6
6.9.5 6.9.4 6.9.3 6.9.2 6.9.1 6.9.0 6.8.9 6.8.7 6.8.8 6.8.6 6.8.4 6.8.5 trunk 6.4.0-beta1 6.4.0-beta2 6.4.1 6.4.1-beta3 6.4.1-beta4 6.4.1-beta5 6.4.1-beta6 6.4.1-beta7 6.4.2 6.5.0 6.5.1 6.5.2 6.5.3 6.5.4 6.5.5 6.5.6 6.5.7 6.6.0 6.7.0 6.7.1 6.8.0 6.8.1 6.8.2 6.8.3
secure-custom-fields / includes / fields / class-acf-field-post_object.php
secure-custom-fields / includes / fields Last commit date
class-acf-field-accordion.php 1 year ago class-acf-field-button-group.php 1 year ago class-acf-field-checkbox.php 1 year ago class-acf-field-clone.php 1 year ago class-acf-field-color_picker.php 1 year ago class-acf-field-date_picker.php 1 year ago class-acf-field-date_time_picker.php 1 year ago class-acf-field-email.php 1 year ago class-acf-field-file.php 1 year ago class-acf-field-flexible-content.php 1 year ago class-acf-field-gallery.php 1 year ago class-acf-field-google-map.php 1 year ago class-acf-field-group.php 1 year ago class-acf-field-icon_picker.php 1 year ago class-acf-field-image.php 1 year ago class-acf-field-link.php 1 year ago class-acf-field-message.php 1 year ago class-acf-field-number.php 1 year ago class-acf-field-oembed.php 1 year ago class-acf-field-output.php 1 year ago class-acf-field-page_link.php 1 year ago class-acf-field-password.php 1 year ago class-acf-field-post_object.php 1 year ago class-acf-field-radio.php 1 year ago class-acf-field-range.php 1 year ago class-acf-field-relationship.php 1 year ago class-acf-field-repeater.php 1 year ago class-acf-field-select.php 1 year ago class-acf-field-separator.php 1 year ago class-acf-field-tab.php 1 year ago class-acf-field-taxonomy.php 1 year ago class-acf-field-text.php 1 year ago class-acf-field-textarea.php 1 year ago class-acf-field-time_picker.php 1 year ago class-acf-field-true_false.php 1 year ago class-acf-field-url.php 1 year ago class-acf-field-user.php 1 year ago class-acf-field-wysiwyg.php 1 year ago class-acf-field.php 1 year ago class-acf-repeater-table.php 1 year ago index.php 1 year ago
class-acf-field-post_object.php
766 lines
1 <?php
2
3 if ( ! class_exists( 'acf_field_post_object' ) ) :
4
5 class acf_field_post_object extends acf_field {
6
7
8
9 /**
10 * This function will setup the field type data
11 *
12 * @since ACF 5.0.0
13 */
14 public function initialize() {
15 $this->name = 'post_object';
16 $this->label = __( 'Post Object', 'secure-custom-fields' );
17 $this->category = 'relational';
18 $this->description = __( 'An interactive and customizable UI for picking one or many posts, pages or post type items with the option to search. ', 'secure-custom-fields' );
19 $this->preview_image = acf_get_url() . '/assets/images/field-type-previews/field-preview-post-object.png';
20 $this->doc_url = 'https://www.advancedcustomfields.com/resources/post-object/';
21 $this->defaults = array(
22 'post_type' => array(),
23 'taxonomy' => array(),
24 'allow_null' => 0,
25 'multiple' => 0,
26 'return_format' => 'object',
27 'ui' => 1,
28 'bidirectional_target' => array(),
29 );
30
31 // extra
32 add_action( 'wp_ajax_acf/fields/post_object/query', array( $this, 'ajax_query' ) );
33 add_action( 'wp_ajax_nopriv_acf/fields/post_object/query', array( $this, 'ajax_query' ) );
34 add_filter( 'acf/conditional_logic/choices', array( $this, 'render_field_post_object_conditional_choices' ), 10, 3 );
35 }
36
37 /**
38 * Filters choices in post object conditions.
39 *
40 * @since ACF 6.3
41 *
42 * @param array $choices The selected choice.
43 * @param array $conditional_field The conditional field settings object.
44 * @param string $rule_value The rule value.
45 * @return array
46 */
47 public function render_field_post_object_conditional_choices( $choices, $conditional_field, $rule_value ) {
48 if ( ! is_array( $conditional_field ) || $conditional_field['type'] !== 'post_object' ) {
49 return $choices;
50 }
51 if ( ! empty( $rule_value ) ) {
52 $post_title = get_the_title( $rule_value );
53 $choices = array( $rule_value => $post_title );
54 }
55 return $choices;
56 }
57
58 /**
59 * AJAX query handler for post object fields.
60 *
61 * @since ACF 5.0.0
62 *
63 * @return void
64 */
65 public function ajax_query() {
66 $nonce = acf_request_arg( 'nonce', '' );
67 $key = acf_request_arg( 'field_key', '' );
68 $conditional_logic = (bool) acf_request_arg( 'conditional_logic', false );
69
70 if ( $conditional_logic ) {
71 if ( ! acf_current_user_can_admin() ) {
72 die();
73 }
74
75 // Use the standard ACF admin nonce.
76 $nonce = '';
77 $key = '';
78 }
79
80 if ( ! acf_verify_ajax( $nonce, $key ) ) {
81 die();
82 }
83
84 acf_send_ajax_results( $this->get_ajax_query( $_POST ) );
85 }
86
87 /**
88 * This function will return an array of data formatted for use in a select2 AJAX response
89 *
90 * @since ACF 5.0.9
91 *
92 * @param array $options The options being queried for the ajax request.
93 * @return array|boolean The AJAX response array, or false on failure.
94 */
95 public function get_ajax_query( $options = array() ) {
96 // defaults
97 $options = acf_parse_args(
98 $options,
99 array(
100 'post_id' => 0,
101 's' => '',
102 'field_key' => '',
103 'paged' => 1,
104 'include' => '',
105 )
106 );
107
108 // load field
109 $field = acf_get_field( $options['field_key'] );
110 if ( ! $field ) {
111 return false;
112 }
113
114 // vars
115 $results = array();
116 $args = array();
117 $s = false;
118 $is_search = false;
119
120 // paged
121 $args['posts_per_page'] = 20;
122 $args['paged'] = $options['paged'];
123
124 // search
125 if ( $options['s'] !== '' ) {
126
127 // strip slashes (search may be integer)
128 $s = wp_unslash( strval( $options['s'] ) );
129
130 // update vars
131 $args['s'] = $s;
132 $is_search = true;
133 }
134
135 if ( ! empty( $options['include'] ) ) {
136 $args['include'] = $options['include'];
137 }
138
139 // post_type
140 if ( ! empty( $field['post_type'] ) ) {
141 $args['post_type'] = acf_get_array( $field['post_type'] );
142 } else {
143 $args['post_type'] = acf_get_post_types();
144 }
145
146 // post status
147 if ( ! empty( $options['post_status'] ) ) {
148 $args['post_status'] = acf_get_array( $options['post_status'] );
149 } elseif ( ! empty( $field['post_status'] ) ) {
150 $args['post_status'] = acf_get_array( $field['post_status'] );
151 }
152
153 // If there is an include set, we will unset search to avoid attempting to further filter by the search term.
154 if ( isset( $args['include'] ) ) {
155 unset( $args['s'] );
156 }
157
158 // taxonomy
159 if ( ! empty( $field['taxonomy'] ) ) {
160
161 // vars
162 $terms = acf_decode_taxonomy_terms( $field['taxonomy'] );
163
164 // append to $args
165 $args['tax_query'] = array();
166
167 // now create the tax queries
168 foreach ( $terms as $k => $v ) {
169 $args['tax_query'][] = array(
170 'taxonomy' => $k,
171 'field' => 'slug',
172 'terms' => $v,
173 );
174 }
175 }
176
177 // filters
178 $args = apply_filters( 'acf/fields/post_object/query', $args, $field, $options['post_id'] );
179 $args = apply_filters( 'acf/fields/post_object/query/name=' . $field['name'], $args, $field, $options['post_id'] );
180 $args = apply_filters( 'acf/fields/post_object/query/key=' . $field['key'], $args, $field, $options['post_id'] );
181
182 // get posts grouped by post type
183 $groups = acf_get_grouped_posts( $args );
184
185 // bail early if no posts
186 if ( empty( $groups ) ) {
187 return false;
188 }
189
190 // loop
191 foreach ( array_keys( $groups ) as $group_title ) {
192
193 // vars
194 $posts = acf_extract_var( $groups, $group_title );
195
196 // data
197 $data = array(
198 'text' => $group_title,
199 'children' => array(),
200 );
201
202 // convert post objects to post titles
203 foreach ( array_keys( $posts ) as $post_id ) {
204 $posts[ $post_id ] = $this->get_post_title( $posts[ $post_id ], $field, $options['post_id'], $is_search, true );
205 }
206
207 // order posts by search
208 if ( $is_search && empty( $args['orderby'] ) && isset( $args['s'] ) ) {
209 $posts = acf_order_by_search( $posts, $args['s'] );
210 }
211
212 // append to $data
213 foreach ( array_keys( $posts ) as $post_id ) {
214 $data['children'][] = $this->get_post_result( $post_id, $posts[ $post_id ] );
215 }
216
217 // append to $results
218 $results[] = $data;
219 }
220
221 // optgroup or single
222 $post_type = acf_get_array( $args['post_type'] );
223 if ( count( $post_type ) == 1 ) {
224 $results = $results[0]['children'];
225 }
226
227 // vars
228 $response = array(
229 'results' => $results,
230 'limit' => $args['posts_per_page'],
231 );
232
233 // return
234 return $response;
235 }
236
237 /**
238 * This function will return an array containing id, text and maybe description data
239 *
240 * @since ACF 5.4.0
241 *
242 * @param mixed $id The ID of the post result.
243 * @param string $text The text for the response item.
244 * @return array The combined result array.
245 */
246 public function get_post_result( $id, $text ) {
247
248 // vars
249 $result = array(
250 'id' => $id,
251 'text' => $text,
252 );
253
254 // look for parent
255 $search = '| ' . __( 'Parent', 'secure-custom-fields' ) . ':';
256 $pos = strpos( $text, $search );
257
258 if ( $pos !== false ) {
259 $result['description'] = substr( $text, $pos + 2 );
260 $result['text'] = substr( $text, 0, $pos );
261 }
262
263 // return
264 return $result;
265 }
266
267
268 /**
269 * This function post object's filtered output post title
270 *
271 * @since ACF 5.0.0
272 *
273 * @param WP_Post $post The WordPress post.
274 * @param array $field The field being output.
275 * @param integer $post_id The post_id to which this value is saved to.
276 * @param integer $is_search An int-as-boolean value for whether we're performing a search.
277 * @param boolean $unescape Should we return an unescaped post title.
278 * @return string A potentially user filtered post title for the post, which may contain unsafe HTML.
279 */
280 public function get_post_title( $post, $field, $post_id = 0, $is_search = 0, $unescape = false ) {
281
282 // get post_id
283 if ( ! $post_id ) {
284 $post_id = acf_get_form_data( 'post_id' );
285 }
286
287 // vars
288 $title = acf_get_post_title( $post, $is_search );
289
290 // unescape for select2 output which handles the escaping.
291 if ( $unescape ) {
292 $title = html_entity_decode( $title );
293 }
294
295 // filters
296 $title = apply_filters( 'acf/fields/post_object/result', $title, $post, $field, $post_id );
297 $title = apply_filters( 'acf/fields/post_object/result/name=' . $field['_name'], $title, $post, $field, $post_id );
298 $title = apply_filters( 'acf/fields/post_object/result/key=' . $field['key'], $title, $post, $field, $post_id );
299
300 // return untrusted output.
301 return $title;
302 }
303
304
305 /**
306 * Create the HTML interface for the post object field.
307 *
308 * @since ACF 3.6
309 *
310 * @param array $field An array holding all the field's data.
311 * @return void
312 */
313 public function render_field( $field ) {
314 // Change Field into a select
315 $field['type'] = 'select';
316 $field['ui'] = 1;
317 $field['ajax'] = 1;
318 $field['nonce'] = wp_create_nonce( $field['key'] );
319 $field['choices'] = array();
320
321 // load posts
322 $posts = $this->get_posts( $field['value'], $field );
323
324 if ( $posts ) {
325 foreach ( array_keys( $posts ) as $i ) {
326
327 // vars
328 $post = acf_extract_var( $posts, $i );
329
330 // append to choices
331 $field['choices'][ $post->ID ] = $this->get_post_title( $post, $field );
332 }
333 }
334
335 // render
336 acf_render_field( $field );
337 }
338
339
340 /**
341 * Create extra options for post object field. This is rendered when editing.
342 * The value of $field['name'] can be used (like below) to save extra data to the $field.
343 *
344 * @since ACF 3.6
345 *
346 * @param array $field An array holding all the field's data.
347 */
348 public function render_field_settings( $field ) {
349 acf_render_field_setting(
350 $field,
351 array(
352 'label' => __( 'Filter by Post Type', 'secure-custom-fields' ),
353 'instructions' => '',
354 'type' => 'select',
355 'name' => 'post_type',
356 'choices' => acf_get_pretty_post_types(),
357 'multiple' => 1,
358 'ui' => 1,
359 'allow_null' => 1,
360 'placeholder' => __( 'All post types', 'secure-custom-fields' ),
361 )
362 );
363
364 acf_render_field_setting(
365 $field,
366 array(
367 'label' => __( 'Filter by Post Status', 'secure-custom-fields' ),
368 'instructions' => '',
369 'type' => 'select',
370 'name' => 'post_status',
371 'choices' => acf_get_pretty_post_statuses(),
372 'multiple' => 1,
373 'ui' => 1,
374 'allow_null' => 1,
375 'placeholder' => __( 'Any post status', 'secure-custom-fields' ),
376 )
377 );
378
379 acf_render_field_setting(
380 $field,
381 array(
382 'label' => __( 'Filter by Taxonomy', 'secure-custom-fields' ),
383 'instructions' => '',
384 'type' => 'select',
385 'name' => 'taxonomy',
386 'choices' => acf_get_taxonomy_terms(),
387 'multiple' => 1,
388 'ui' => 1,
389 'allow_null' => 1,
390 'placeholder' => __( 'All taxonomies', 'secure-custom-fields' ),
391 )
392 );
393
394 acf_render_field_setting(
395 $field,
396 array(
397 'label' => __( 'Return Format', 'secure-custom-fields' ),
398 'instructions' => '',
399 'type' => 'radio',
400 'name' => 'return_format',
401 'choices' => array(
402 'object' => __( 'Post Object', 'secure-custom-fields' ),
403 'id' => __( 'Post ID', 'secure-custom-fields' ),
404 ),
405 'layout' => 'horizontal',
406 )
407 );
408
409 acf_render_field_setting(
410 $field,
411 array(
412 'label' => __( 'Select Multiple', 'secure-custom-fields' ),
413 'instructions' => 'Allow content editors to select multiple values',
414 'name' => 'multiple',
415 'type' => 'true_false',
416 'ui' => 1,
417 )
418 );
419 }
420
421 /**
422 * Renders the field settings used in the "Validation" tab.
423 *
424 * @since ACF 6.0
425 *
426 * @param array $field The field settings array.
427 * @return void
428 */
429 public function render_field_validation_settings( $field ) {
430 acf_render_field_setting(
431 $field,
432 array(
433 'label' => __( 'Allow Null', 'secure-custom-fields' ),
434 'instructions' => '',
435 'name' => 'allow_null',
436 'type' => 'true_false',
437 'ui' => 1,
438 )
439 );
440 }
441
442 /**
443 * Renders the field settings used in the "Advanced" tab.
444 *
445 * @since ACF 6.2
446 *
447 * @param array $field The field settings array.
448 * @return void
449 */
450 public function render_field_advanced_settings( $field ) {
451 acf_render_bidirectional_field_settings( $field );
452 }
453
454 /**
455 * This filter is applied to the $value after it is loaded from the db
456 *
457 * @since ACF 3.6
458 *
459 * @param mixed $value The value found in the database
460 * @param mixed $post_id The post_id from which the value was loaded
461 * @param array $field The field array holding all the field options
462 * @return mixed $value
463 */
464 public function load_value( $value, $post_id, $field ) {
465
466 // ACF4 null
467 if ( $value === 'null' ) {
468 return false;
469 }
470
471 // return
472 return $value;
473 }
474
475
476 /**
477 * This filter is appied to the $value after it is loaded from the db and before it is returned to the template
478 *
479 * @since ACF 3.6
480 *
481 * @param mixed $value The value found in the database
482 * @param mixed $post_id The post_id from which the value was loaded
483 * @param array $field The field array holding all the field options
484 * @return mixed $value
485 */
486 public function format_value( $value, $post_id, $field ) {
487 $value = acf_get_numeric( $value );
488
489 // bail early if no value
490 if ( empty( $value ) ) {
491 return false;
492 }
493
494 // load posts if needed
495 if ( $field['return_format'] == 'object' ) {
496 $value = $this->get_posts( $value, $field );
497 }
498
499 // convert back from array if neccessary
500 if ( ! $field['multiple'] && is_array( $value ) ) {
501 $value = current( $value );
502 }
503
504 // return value
505 return $value;
506 }
507
508
509 /**
510 * Filters the field value before it is saved into the database.
511 *
512 * @since ACF 3.6
513 *
514 * @param mixed $value The value which will be saved in the database.
515 * @param integer $post_id The post_id of which the value will be saved.
516 * @param array $field The field array holding all the field options.
517 * @return mixed $value The modified value.
518 */
519 public function update_value( $value, $post_id, $field ) {
520
521 // Bail early if no value.
522 if ( empty( $value ) ) {
523 acf_update_bidirectional_values( array(), $post_id, $field );
524 return $value;
525 }
526
527 // Format array of values.
528 // - ensure each value is an id.
529 // - Parse each id as string for SQL LIKE queries.
530 if ( acf_is_sequential_array( $value ) ) {
531 $value = array_map( 'acf_idval', $value );
532 $value = array_map( 'strval', $value );
533
534 // Parse single value for id.
535 } else {
536 $value = acf_idval( $value );
537 }
538
539 acf_update_bidirectional_values( acf_get_array( $value ), $post_id, $field );
540
541 return $value;
542 }
543
544
545 /**
546 * This function will return an array of posts for a given field value
547 *
548 * @since ACF 5.0
549 *
550 * @param mixed $value The value of the field.
551 * @param array $field The field array holding all the field options.
552 * @return array $value An array of post objects.
553 */
554 public function get_posts( $value, $field ) {
555
556 // numeric
557 $value = acf_get_numeric( $value );
558
559 // bail early if no value
560 if ( empty( $value ) ) {
561 return false;
562 }
563
564 // get posts
565 $posts = acf_get_posts(
566 array(
567 'post__in' => $value,
568 'post_type' => $field['post_type'],
569 )
570 );
571
572 // return
573 return $posts;
574 }
575
576 /**
577 * Validates post object fields updated via the REST API.
578 *
579 * @since ACF 5.11
580 *
581 * @param boolean $valid The current validity booleean
582 * @param integer $value The value of the field
583 * @param array $field The field array
584 * @return boolean|WP_Error
585 */
586 public function validate_rest_value( $valid, $value, $field ) {
587 if ( is_null( $value ) ) {
588 return $valid;
589 }
590
591 $param = sprintf( '%s[%s]', $field['prefix'], $field['name'] );
592 $data = array( 'param' => $param );
593 $value = is_array( $value ) ? $value : array( $value );
594
595 $invalid_posts = array();
596 $post_type_errors = array();
597 $taxonomy_errors = array();
598
599 foreach ( $value as $post_id ) {
600 if ( is_string( $post_id ) ) {
601 continue;
602 }
603
604 $post_type = get_post_type( $post_id );
605 if ( ! $post_type ) {
606 $invalid_posts[] = $post_id;
607 continue;
608 }
609
610 if (
611 is_array( $field['post_type'] ) &&
612 ! empty( $field['post_type'] ) &&
613 ! in_array( $post_type, $field['post_type'] )
614 ) {
615 $post_type_errors[] = $post_id;
616 }
617
618 if ( is_array( $field['taxonomy'] ) && ! empty( $field['taxonomy'] ) ) {
619 $found = false;
620 foreach ( $field['taxonomy'] as $taxonomy_term ) {
621 $decoded = acf_decode_taxonomy_term( $taxonomy_term );
622 if ( $decoded && is_object_in_term( $post_id, $decoded['taxonomy'], $decoded['term'] ) ) {
623 $found = true;
624 break;
625 }
626 }
627
628 if ( ! $found ) {
629 $taxonomy_errors[] = $post_id;
630 }
631 }
632 }
633
634 if ( count( $invalid_posts ) ) {
635 $error = sprintf(
636 /* translators: %s: field name */
637 __( '%1$s must have a valid post ID.', 'secure-custom-fields' ),
638 $param
639 );
640 $data['value'] = $invalid_posts;
641 return new WP_Error( 'rest_invalid_param', $error, $data );
642 }
643
644 if ( count( $post_type_errors ) ) {
645 $error = sprintf(
646 /* translators: 1: field name, 2: post type */
647 _n(
648 '%1$s must be of post type %2$s.',
649 '%1$s must be of one of the following post types: %2$s',
650 count( $field['post_type'] ),
651 'secure-custom-fields'
652 ),
653 $param,
654 count( $field['post_type'] ) > 1 ? implode( ', ', $field['post_type'] ) : $field['post_type'][0]
655 );
656 $data['value'] = $post_type_errors;
657
658 return new WP_Error( 'rest_invalid_param', $error, $data );
659 }
660
661 if ( count( $taxonomy_errors ) ) {
662 $error = sprintf(
663 /* translators: 1: field name, 2: taxonomy term */
664 _n(
665 '%1$s must have term %2$s.',
666 '%1$s must have one of the following terms: %2$s',
667 count( $field['taxonomy'] ),
668 'secure-custom-fields'
669 ),
670 $param,
671 count( $field['taxonomy'] ) > 1 ? implode( ', ', $field['taxonomy'] ) : $field['taxonomy'][0]
672 );
673 $data['value'] = $taxonomy_errors;
674
675 return new WP_Error( 'rest_invalid_param', $error, $data );
676 }
677
678 return $valid;
679 }
680
681 /**
682 * Return the schema array for the REST API.
683 *
684 * @since ACF 5.11
685 *
686 * @param array $field The field array.
687 * @return array
688 */
689 public function get_rest_schema( array $field ) {
690 $schema = array(
691 'type' => array( 'integer', 'array', 'null' ),
692 'required' => ! empty( $field['required'] ),
693 'items' => array(
694 'type' => 'integer',
695 ),
696 );
697
698 if ( empty( $field['allow_null'] ) ) {
699 $schema['minItems'] = 1;
700 }
701
702 if ( empty( $field['multiple'] ) ) {
703 $schema['maxItems'] = 1;
704 }
705
706 return $schema;
707 }
708
709 /**
710 * REST link attributes generator for this field.
711 *
712 * @since ACF 5.11
713 * @see \acf_field::get_rest_links()
714 *
715 * @param mixed $value The raw (unformatted) field value.
716 * @param integer|string $post_id The post ID being queried.
717 * @param array $field The field array.
718 * @return array
719 */
720 public function get_rest_links( $value, $post_id, array $field ) {
721 $links = array();
722
723 if ( empty( $value ) ) {
724 return $links;
725 }
726
727 foreach ( (array) $value as $object_id ) {
728 if ( ! $post_type = get_post_type( $object_id ) ) {
729 continue;
730 }
731
732 if ( ! $post_type_object = get_post_type_object( $post_type ) ) {
733 continue;
734 }
735
736 $rest_base = acf_get_object_type_rest_base( $post_type_object );
737 $links[] = array(
738 'rel' => $post_type_object->name === 'attachment' ? 'acf:attachment' : 'acf:post',
739 'href' => rest_url( sprintf( '/wp/v2/%s/%s', $rest_base, $object_id ) ),
740 'embeddable' => true,
741 );
742 }
743
744 return $links;
745 }
746
747 /**
748 * Apply basic formatting to prepare the value for default REST output.
749 *
750 * @since ACF 5.11
751 *
752 * @param mixed $value The raw (unformatted) field value.
753 * @param integer|string $post_id The post ID being queried.
754 * @param array $field The field array.
755 * @return mixed
756 */
757 public function format_value_for_rest( $value, $post_id, array $field ) {
758 return acf_format_numerics( $value );
759 }
760 }
761
762
763 // initialize
764 acf_register_field_type( 'acf_field_post_object' );
765 endif; // class_exists check
766