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