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