PluginProbe ʕ •ᴥ•ʔ
Advanced Custom Fields (ACF®) / 6.8.9
Advanced Custom Fields (ACF®) v6.8.9
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 6.0.1 6.0.2 6.0.3 6.0.4 6.0.5 6.0.6 6.0.7 6.1.0 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.1.6 6.1.7 6.1.8 6.2.0 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.2.6 6.2.6.1 6.2.7 6.2.8 6.2.9 6.3.0 6.3.1 6.3.10.2 6.3.11 6.3.12 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.6.1 6.4.0 6.4.0.1 6.4.1 6.4.2 6.4.3 6.5.0 6.5.1 6.6.0 6.6.1 6.6.2 6.7.0 6.7.1 6.7.2 6.8.0 trunk 1.0.0 1.0.2 1.0.3 1.0.5 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.1.1 2.1.3 2.1.4 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.0.6 3.0.7 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 3.1.5 3.1.6 3.1.7 3.1.8 3.1.9 3.2.0 3.2.2 3.2.3 3.2.4 3.2.5 3.2.6 3.2.7 3.2.8 3.2.9 3.3.0 3.3.1 3.3.2 3.3.3 3.3.4 3.3.5 3.3.6 3.3.7 3.3.8 3.3.9 3.4.0 3.4.1 3.4.2 3.4.3 3.5.0 3.5.1 3.5.2 3.5.3 3.5.4 3.5.5 3.5.6 3.5.7 3.5.8 4.0.0 4.0.1 4.0.2 4.0.3 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4 4.1.5 4.1.6 4.1.8 4.2.0 4.2.1 4.2.2 4.3.0 4.3.1 4.3.2 4.3.3 4.3.4 4.3.5 4.3.6 4.3.7 4.3.8 4.3.9 4.4.0 4.4.1 4.4.10 4.4.11 4.4.12 4.4.2 4.4.3 4.4.4 4.4.5 4.4.6 4.4.7 4.4.8 4.4.9 5.10 5.10.1 5.10.2 5.11 5.11.1 5.11.2 5.11.3 5.11.4 5.12 5.12.1 5.12.2 5.12.3 5.12.4 5.12.5 5.12.6 5.6.10 5.6.2 5.6.3 5.6.4 5.6.5 5.6.6 5.6.7 5.6.8 5.6.9 5.7.0 5.7.1 5.7.10 5.7.12 5.7.13 5.7.2 5.7.3 5.7.4 5.7.5 5.7.6 5.7.7 5.7.8 5.7.9 5.8.0 5.8.1 5.8.10 5.8.11 5.8.12 5.8.13 5.8.14 5.8.2 5.8.3 5.8.4
advanced-custom-fields / includes / fields / class-acf-field-relationship.php
advanced-custom-fields / includes / fields Last commit date
class-acf-field-accordion.php 5 months ago class-acf-field-button-group.php 5 months ago class-acf-field-checkbox.php 2 months ago class-acf-field-color_picker.php 5 months ago class-acf-field-date_picker.php 5 months ago class-acf-field-date_time_picker.php 5 months ago class-acf-field-email.php 5 months ago class-acf-field-file.php 5 months ago class-acf-field-google-map.php 5 months ago class-acf-field-group.php 5 months ago class-acf-field-icon_picker.php 6 months ago class-acf-field-image.php 1 month ago class-acf-field-link.php 5 months ago class-acf-field-message.php 6 months ago class-acf-field-number.php 5 months ago class-acf-field-oembed.php 2 months ago class-acf-field-output.php 6 months ago class-acf-field-page_link.php 1 month ago class-acf-field-password.php 5 months ago class-acf-field-post_object.php 1 month ago class-acf-field-radio.php 2 months ago class-acf-field-range.php 5 months ago class-acf-field-relationship.php 1 month ago class-acf-field-select.php 2 months ago class-acf-field-separator.php 6 months ago class-acf-field-tab.php 6 months ago class-acf-field-taxonomy.php 2 months ago class-acf-field-text.php 5 months ago class-acf-field-textarea.php 5 months ago class-acf-field-time_picker.php 5 months ago class-acf-field-true_false.php 5 months ago class-acf-field-url.php 5 months ago class-acf-field-user.php 2 months ago class-acf-field-wysiwyg.php 5 months ago class-acf-field.php 5 months ago index.php 2 years ago
class-acf-field-relationship.php
972 lines
1 <?php
2 /**
3 * @package ACF
4 * @author WP Engine
5 *
6 * © 2026 Advanced Custom Fields (ACF®). All rights reserved.
7 * "ACF" is a trademark of WP Engine.
8 * Licensed under the GNU General Public License v2 or later.
9 * https://www.gnu.org/licenses/gpl-2.0.html
10 */
11
12 if ( ! class_exists( 'acf_field_relationship' ) ) :
13
14 class acf_field_relationship extends acf_field {
15
16
17 /**
18 * This function will setup the field type data
19 *
20 * @type function
21 * @date 5/03/2014
22 * @since 5.0.0
23 */
24 public function initialize() {
25 $this->name = 'relationship';
26 $this->label = __( 'Relationship', 'acf' );
27 $this->category = 'relational';
28 $this->description = __( 'A dual-column interface to select one or more posts, pages, or custom post type items to create a relationship with the item that you\'re currently editing. Includes options to search and filter.', 'acf' );
29 $this->preview_image = acf_get_url() . '/assets/images/field-type-previews/field-preview-relationship.png';
30 $this->doc_url = acf_add_url_utm_tags( 'https://www.advancedcustomfields.com/resources/relationship/', 'docs', 'field-type-selection' );
31 $this->defaults = array(
32 'post_type' => array(),
33 'taxonomy' => array(),
34 'min' => 0,
35 'max' => 0,
36 'filters' => array( 'search', 'post_type', 'taxonomy' ),
37 'elements' => array(),
38 'return_format' => 'object',
39 'bidirectional_target' => array(),
40 );
41 add_filter( 'acf/conditional_logic/choices', array( $this, 'render_field_relation_conditional_choices' ), 10, 3 );
42
43 // extra
44 add_action( 'wp_ajax_acf/fields/relationship/query', array( $this, 'ajax_query' ) );
45 add_action( 'wp_ajax_nopriv_acf/fields/relationship/query', array( $this, 'ajax_query' ) );
46 }
47
48 /**
49 * Filters choices in relation conditions.
50 *
51 * @since 6.3
52 *
53 * @param array $choices The selected choice.
54 * @param array $conditional_field The conditional field settings object.
55 * @param string $rule_value The rule value.
56 * @return array
57 */
58 public function render_field_relation_conditional_choices( $choices, $conditional_field, $rule_value ) {
59 if ( ! is_array( $conditional_field ) || $conditional_field['type'] !== 'relationship' ) {
60 return $choices;
61 }
62 if ( ! empty( $rule_value ) ) {
63 $post_title = esc_html( get_the_title( $rule_value ) );
64 $choices = array( $rule_value => $post_title );
65 }
66 return $choices;
67 }
68
69 /**
70 * description
71 *
72 * @type function
73 * @date 16/12/2015
74 * @since 5.3.2
75 *
76 * @param $post_id (int)
77 * @return $post_id (int)
78 */
79 function input_admin_enqueue_scripts() {
80
81 // localize
82 acf_localize_text(
83 array(
84 // 'Minimum values reached ( {min} values )' => __('Minimum values reached ( {min} values )', 'acf'),
85 'Maximum values reached ( {max} values )' => __( 'Maximum values reached ( {max} values )', 'acf' ),
86 'Loading' => __( 'Loading', 'acf' ),
87 'No matches found' => __( 'No matches found', 'acf' ),
88 )
89 );
90 }
91
92 /**
93 * Returns AJAX results for the Relationship field.
94 *
95 * @since 5.0.0
96 *
97 * @return void
98 */
99 public function ajax_query() {
100 $nonce = acf_request_arg( 'nonce', '' );
101 $key = acf_request_arg( 'field_key', '' );
102 $conditional_logic = (bool) acf_request_arg( 'conditional_logic', false );
103
104 if ( $conditional_logic ) {
105 if ( ! acf_current_user_can_admin() ) {
106 die();
107 }
108
109 // Use the standard ACF admin nonce.
110 $nonce = '';
111 $key = '';
112 }
113
114 if ( ! acf_verify_ajax( $nonce, $key, ! $conditional_logic, 'relationship' ) ) {
115 die();
116 }
117
118 acf_send_ajax_results( $this->get_ajax_query( $_POST ) );
119 }
120
121 /**
122 * Returns an array of data formatted for use in a select2 AJAX response.
123 *
124 * @since 5.0.9
125 *
126 * @param array $options An array of options for the query.
127 * @return array|false
128 */
129 public function get_ajax_query( $options = array() ) {
130 // defaults
131 $options = wp_parse_args(
132 $options,
133 array(
134 'post_id' => 0,
135 's' => '',
136 'field_key' => '',
137 'paged' => 1,
138 'post_type' => '',
139 'include' => '',
140 'taxonomy' => '',
141 )
142 );
143
144 // load field
145 $field = acf_get_field( $options['field_key'] );
146 if ( ! $field ) {
147 return false;
148 }
149
150 // vars
151 $results = array();
152 $args = array();
153 $s = false;
154 $is_search = false;
155
156 // paged
157 $args['posts_per_page'] = 20;
158 $args['paged'] = (int) $options['paged'];
159
160 // search
161 if ( $options['s'] !== '' && empty( $options['include'] ) ) {
162 // strip slashes (search may be integer)
163 $s = wp_unslash( strval( $options['s'] ) );
164
165 // update vars
166 $args['s'] = $s;
167 $is_search = true;
168 }
169
170 // post_type - validate user input against field config to prevent bypass.
171 if ( ! empty( $field['post_type'] ) ) {
172 $allowed_post_types = acf_get_array( $field['post_type'] );
173
174 if ( ! empty( $options['post_type'] ) ) {
175 // User is filtering - only allow post types within field config.
176 $requested_types = acf_get_array( $options['post_type'] );
177 $args['post_type'] = array_intersect( $requested_types, $allowed_post_types );
178
179 if ( empty( $args['post_type'] ) ) {
180 // Requested types not allowed, fall back to field config.
181 $args['post_type'] = $allowed_post_types;
182 }
183 } else {
184 $args['post_type'] = $allowed_post_types;
185 }
186 } elseif ( ! empty( $options['post_type'] ) ) {
187 // No field restriction, allow user filter.
188 $args['post_type'] = acf_get_array( $options['post_type'] );
189 } else {
190 $args['post_type'] = acf_get_post_types();
191 }
192
193 // Post status - use field config only, don't accept from user input.
194 if ( ! empty( $field['post_status'] ) ) {
195 $args['post_status'] = acf_get_array( $field['post_status'] );
196 }
197
198 // taxonomy - validate user input against field config to prevent bypass.
199 if ( ! empty( $field['taxonomy'] ) ) {
200 // Field has taxonomy restrictions.
201 $allowed_terms = acf_decode_taxonomy_terms( $field['taxonomy'] );
202
203 if ( ! empty( $options['taxonomy'] ) ) {
204 // User is filtering - validate both taxonomy AND term are allowed.
205 $term = acf_decode_taxonomy_term( $options['taxonomy'] );
206
207 if ( $term && isset( $allowed_terms[ $term['taxonomy'] ] ) && in_array( $term['term'], $allowed_terms[ $term['taxonomy'] ], true ) ) {
208 // Taxonomy:term combination is allowed.
209 $args['tax_query'] = array();
210 $args['tax_query'][] = array(
211 'taxonomy' => $term['taxonomy'],
212 'field' => 'slug',
213 'terms' => $term['term'],
214 );
215 } else {
216 // Requested taxonomy:term not allowed, fall back to field config.
217 $args['tax_query'] = array( 'relation' => 'OR' );
218
219 foreach ( $allowed_terms as $k => $v ) {
220 $args['tax_query'][] = array(
221 'taxonomy' => $k,
222 'field' => 'slug',
223 'terms' => $v,
224 );
225 }
226 }
227 } else {
228 // No user filter, use field config.
229 $args['tax_query'] = array( 'relation' => 'OR' );
230
231 foreach ( $allowed_terms as $k => $v ) {
232 $args['tax_query'][] = array(
233 'taxonomy' => $k,
234 'field' => 'slug',
235 'terms' => $v,
236 );
237 }
238 }
239 } elseif ( ! empty( $options['taxonomy'] ) ) {
240 // No field restriction, allow user filter.
241 $term = acf_decode_taxonomy_term( $options['taxonomy'] );
242
243 if ( $term ) {
244 $args['tax_query'] = array();
245 $args['tax_query'][] = array(
246 'taxonomy' => $term['taxonomy'],
247 'field' => 'slug',
248 'terms' => $term['term'],
249 );
250 }
251 }
252
253 if ( ! empty( $options['include'] ) ) {
254 // If we have an include, we need to return only the selected posts.
255 $args['post__in'] = array( (int) $options['include'] );
256 }
257
258 $args['perm'] = 'readable';
259 $args = acf_ensure_perm_readable_post_status( $args );
260
261 // filters
262 $args = apply_filters( 'acf/fields/relationship/query', $args, $field, $options['post_id'] );
263 $args = apply_filters( 'acf/fields/relationship/query/name=' . $field['name'], $args, $field, $options['post_id'] );
264 $args = apply_filters( 'acf/fields/relationship/query/key=' . $field['key'], $args, $field, $options['post_id'] );
265
266 // Re-normalize in case a filter reset `post_status` to 'any' while leaving `perm=readable`.
267 $args = acf_ensure_perm_readable_post_status( $args );
268
269 // get posts grouped by post type
270 $groups = acf_get_grouped_posts( $args );
271
272 // bail early if no posts
273 if ( empty( $groups ) ) {
274 return false;
275 }
276
277 // loop
278 foreach ( array_keys( $groups ) as $group_title ) {
279
280 // vars
281 $posts = acf_extract_var( $groups, $group_title );
282
283 // data
284 $data = array(
285 'text' => $group_title,
286 'children' => array(),
287 );
288
289 // convert post objects to post titles
290 foreach ( array_keys( $posts ) as $post_id ) {
291 $posts[ $post_id ] = $this->get_post_title( $posts[ $post_id ], $field, $options['post_id'] );
292 }
293
294 // order posts by search
295 if ( $is_search && empty( $args['orderby'] ) && isset( $args['s'] ) ) {
296 $posts = acf_order_by_search( $posts, $args['s'] );
297 }
298
299 // append to $data
300 foreach ( array_keys( $posts ) as $post_id ) {
301 $data['children'][] = $this->get_post_result( $post_id, $posts[ $post_id ] );
302 }
303
304 // append to $results
305 $results[] = $data;
306 }
307
308 // add as optgroup or results
309 if ( count( $args['post_type'] ) === 1 ) {
310 $results = $results[0]['children'];
311 }
312
313 return array(
314 'results' => $results,
315 'limit' => $args['posts_per_page'],
316 );
317 }
318
319 /**
320 * This function will return an array containing id, text and maybe description data
321 *
322 * @type function
323 * @date 7/07/2016
324 * @since 5.4.0
325 *
326 * @param $id (mixed)
327 * @param $text (string)
328 * @return (array)
329 */
330 function get_post_result( $id, $text ) {
331
332 // vars
333 $result = array(
334 'id' => $id,
335 'text' => $text,
336 );
337
338 // return
339 return $result;
340 }
341
342
343 /**
344 * This function returns the HTML for a result
345 *
346 * @type function
347 * @date 1/11/2013
348 * @since 5.0.0
349 *
350 * @param $post (object)
351 * @param $field (array)
352 * @param $post_id (int) the post_id to which this value is saved to
353 * @return (string)
354 */
355 function get_post_title( $post, $field, $post_id = 0, $is_search = 0 ) {
356
357 // get post_id
358 if ( ! $post_id ) {
359 $post_id = acf_get_form_data( 'post_id' );
360 }
361
362 // vars
363 $title = esc_html( acf_get_post_title( $post, $is_search ) );
364
365 // featured_image
366 if ( acf_in_array( 'featured_image', $field['elements'] ) ) {
367
368 // vars
369 $class = 'thumbnail';
370 $thumbnail = acf_get_post_thumbnail( $post->ID, array( 17, 17 ) );
371
372 // icon
373 if ( $thumbnail['type'] == 'icon' ) {
374 $class .= ' -' . $thumbnail['type'];
375 }
376
377 // append
378 $title = '<div class="' . $class . '">' . $thumbnail['html'] . '</div>' . $title;
379 }
380
381 // filters
382 $title = apply_filters( 'acf/fields/relationship/result', $title, $post, $field, $post_id );
383 $title = apply_filters( 'acf/fields/relationship/result/name=' . $field['_name'], $title, $post, $field, $post_id );
384 $title = apply_filters( 'acf/fields/relationship/result/key=' . $field['key'], $title, $post, $field, $post_id );
385
386 // return
387 return $title;
388 }
389
390
391 /**
392 * Create the HTML interface for your field
393 *
394 * @param $field - an array holding all the field's data
395 *
396 * @type action
397 * @since 3.6
398 * @date 23/01/13
399 */
400 function render_field( $field ) {
401
402 // vars
403 $post_type = acf_get_array( $field['post_type'] );
404 $taxonomy = acf_get_array( $field['taxonomy'] );
405 $filters = acf_get_array( $field['filters'] );
406
407 // filters
408 $filter_count = count( $filters );
409 $filter_post_type_choices = array();
410 $filter_taxonomy_choices = array();
411
412 // post_type filter
413 if ( in_array( 'post_type', $filters ) ) {
414 $filter_post_type_choices = array(
415 '' => __( 'Select post type', 'acf' ),
416 ) + acf_get_pretty_post_types( $post_type );
417 }
418
419 // taxonomy filter
420 if ( in_array( 'taxonomy', $filters ) ) {
421 $term_choices = array();
422 $filter_taxonomy_choices = array(
423 '' => __( 'Select taxonomy', 'acf' ),
424 );
425
426 // check for specific taxonomy setting
427 if ( $taxonomy ) {
428 $terms = acf_get_encoded_terms( $taxonomy );
429 $term_choices = acf_get_choices_from_terms( $terms, 'slug' );
430
431 // if no terms were specified, find all terms
432 } else {
433
434 // restrict taxonomies by the post_type selected
435 $term_args = array();
436 if ( $post_type ) {
437 $term_args['taxonomy'] = acf_get_taxonomies(
438 array(
439 'post_type' => $post_type,
440 )
441 );
442 }
443
444 // get terms
445 $terms = acf_get_grouped_terms( $term_args );
446 $term_choices = acf_get_choices_from_grouped_terms( $terms, 'slug' );
447 }
448
449 // append term choices
450 $filter_taxonomy_choices = $filter_taxonomy_choices + $term_choices;
451 }
452
453 // div attributes
454 $atts = array(
455 'id' => $field['id'],
456 'class' => "acf-relationship {$field['class']}",
457 'data-min' => $field['min'],
458 'data-max' => $field['max'],
459 'data-s' => '',
460 'data-paged' => 1,
461 'data-post_type' => '',
462 'data-taxonomy' => '',
463 'data-nonce' => wp_create_nonce( 'acf_field_' . $this->name . '_' . $field['key'] ),
464 );
465
466 ?>
467 <div <?php echo acf_esc_attrs( $atts ); ?>>
468
469 <?php
470 acf_hidden_input(
471 array(
472 'name' => $field['name'],
473 'value' => '',
474 )
475 );
476 ?>
477
478 <?php
479
480 /* filters */
481 if ( $filter_count ) :
482 ?>
483 <div class="filters -f<?php echo esc_attr( $filter_count ); ?>">
484 <?php
485
486 /* search */
487 if ( in_array( 'search', $filters ) ) :
488 ?>
489 <div class="filter -search">
490 <?php
491 acf_text_input(
492 array(
493 'placeholder' => __( 'Search...', 'acf' ),
494 'data-filter' => 's',
495 )
496 );
497 ?>
498 </div>
499 <?php
500 endif;
501
502 /* post_type */
503 if ( in_array( 'post_type', $filters ) ) :
504 ?>
505 <div class="filter -post_type">
506 <?php
507 acf_select_input(
508 array(
509 'choices' => $filter_post_type_choices,
510 'data-filter' => 'post_type',
511 )
512 );
513 ?>
514 </div>
515 <?php
516 endif;
517
518 /* post_type */
519 if ( in_array( 'taxonomy', $filters ) ) :
520 ?>
521 <div class="filter -taxonomy">
522 <?php
523 acf_select_input(
524 array(
525 'choices' => $filter_taxonomy_choices,
526 'data-filter' => 'taxonomy',
527 )
528 );
529 ?>
530 </div>
531 <?php endif; ?>
532 </div>
533 <?php endif; ?>
534
535 <div class="selection">
536 <div class="choices">
537 <ul class="acf-bl list choices-list"></ul>
538 </div>
539 <div class="values">
540 <ul class="acf-bl list values-list">
541 <?php
542 if ( ! empty( $field['value'] ) ) :
543
544 // get posts
545 $posts = acf_get_posts(
546 array(
547 'post__in' => $field['value'],
548 'post_type' => $field['post_type'],
549 )
550 );
551
552 // loop
553 foreach ( $posts as $post ) :
554 ?>
555 <li>
556 <?php
557 acf_hidden_input(
558 array(
559 'name' => $field['name'] . '[]',
560 'value' => $post->ID,
561 )
562 );
563 ?>
564 <span tabindex="0" data-id="<?php echo esc_attr( $post->ID ); ?>" class="acf-rel-item acf-rel-item-remove">
565 <?php echo acf_esc_html( $this->get_post_title( $post, $field ) ); ?>
566 <a href="#" class="acf-icon -minus small dark" data-name="remove_item"></a>
567 </span>
568 </li>
569 <?php endforeach; ?>
570 <?php endif; ?>
571 </ul>
572 </div>
573 </div>
574 </div>
575 <?php
576 }
577
578
579 /**
580 * Create extra options for your field. This is rendered when editing a field.
581 * The value of $field['name'] can be used (like bellow) to save extra data to the $field
582 *
583 * @type action
584 * @since 3.6
585 * @date 23/01/13
586 *
587 * @param $field - an array holding all the field's data
588 */
589 function render_field_settings( $field ) {
590 acf_render_field_setting(
591 $field,
592 array(
593 'label' => __( 'Filter by Post Type', 'acf' ),
594 'instructions' => '',
595 'type' => 'select',
596 'name' => 'post_type',
597 'choices' => acf_get_pretty_post_types(),
598 'multiple' => 1,
599 'ui' => 1,
600 'allow_null' => 1,
601 'placeholder' => __( 'All post types', 'acf' ),
602 )
603 );
604
605 acf_render_field_setting(
606 $field,
607 array(
608 'label' => __( 'Filter by Post Status', 'acf' ),
609 'instructions' => '',
610 'type' => 'select',
611 'name' => 'post_status',
612 'choices' => acf_get_pretty_post_statuses(),
613 'multiple' => 1,
614 'ui' => 1,
615 'allow_null' => 1,
616 'placeholder' => __( 'Any post status', 'acf' ),
617 )
618 );
619
620 acf_render_field_setting(
621 $field,
622 array(
623 'label' => __( 'Filter by Taxonomy', 'acf' ),
624 'instructions' => '',
625 'type' => 'select',
626 'name' => 'taxonomy',
627 'choices' => acf_get_taxonomy_terms(),
628 'multiple' => 1,
629 'ui' => 1,
630 'allow_null' => 1,
631 'placeholder' => __( 'All taxonomies', 'acf' ),
632 )
633 );
634
635 acf_render_field_setting(
636 $field,
637 array(
638 'label' => __( 'Filters', 'acf' ),
639 'instructions' => '',
640 'type' => 'checkbox',
641 'name' => 'filters',
642 'choices' => array(
643 'search' => __( 'Search', 'acf' ),
644 'post_type' => __( 'Post Type', 'acf' ),
645 'taxonomy' => __( 'Taxonomy', 'acf' ),
646 ),
647 )
648 );
649
650 acf_render_field_setting(
651 $field,
652 array(
653 'label' => __( 'Return Format', 'acf' ),
654 'instructions' => '',
655 'type' => 'radio',
656 'name' => 'return_format',
657 'choices' => array(
658 'object' => __( 'Post Object', 'acf' ),
659 'id' => __( 'Post ID', 'acf' ),
660 ),
661 'layout' => 'horizontal',
662 )
663 );
664 }
665
666 /**
667 * Renders the field settings used in the "Validation" tab.
668 *
669 * @since 6.0
670 *
671 * @param array $field The field settings array.
672 * @return void
673 */
674 function render_field_validation_settings( $field ) {
675 $field['min'] = empty( $field['min'] ) ? '' : $field['min'];
676 $field['max'] = empty( $field['max'] ) ? '' : $field['max'];
677
678 acf_render_field_setting(
679 $field,
680 array(
681 'label' => __( 'Minimum Posts', 'acf' ),
682 'instructions' => '',
683 'type' => 'number',
684 'name' => 'min',
685 )
686 );
687
688 acf_render_field_setting(
689 $field,
690 array(
691 'label' => __( 'Maximum Posts', 'acf' ),
692 'instructions' => '',
693 'type' => 'number',
694 'name' => 'max',
695 )
696 );
697 }
698
699 /**
700 * Renders the field settings used in the "Presentation" tab.
701 *
702 * @since 6.0
703 *
704 * @param array $field The field settings array.
705 * @return void
706 */
707 function render_field_presentation_settings( $field ) {
708 acf_render_field_setting(
709 $field,
710 array(
711 'label' => __( 'Elements', 'acf' ),
712 'instructions' => __( 'Selected elements will be displayed in each result', 'acf' ),
713 'type' => 'checkbox',
714 'name' => 'elements',
715 'choices' => array(
716 'featured_image' => __( 'Featured Image', 'acf' ),
717 ),
718 )
719 );
720 }
721
722 /**
723 * Renders the field settings used in the "Advanced" tab.
724 *
725 * @since 6.2
726 *
727 * @param array $field The field settings array.
728 * @return void
729 */
730 public function render_field_advanced_settings( $field ) {
731 acf_render_bidirectional_field_settings( $field );
732 }
733
734 /**
735 * This filter is applied to the $value after it is loaded from the db and before it is returned to the template
736 *
737 * @type filter
738 * @since 3.6
739 * @date 23/01/13
740 *
741 * @param $value (mixed) the value which was loaded from the database
742 * @param $post_id (mixed) the post_id from which the value was loaded
743 * @param $field (array) the field array holding all the field options
744 *
745 * @return $value (mixed) the modified value
746 */
747 function format_value( $value, $post_id, $field ) {
748
749 // bail early if no value
750 if ( empty( $value ) ) {
751 return $value;
752 }
753
754 // force value to array
755 $value = acf_get_array( $value );
756
757 // convert to int
758 $value = array_map( 'intval', $value );
759
760 // load posts if needed
761 if ( $field['return_format'] == 'object' ) {
762
763 // get posts
764 $value = acf_get_posts(
765 array(
766 'post__in' => $value,
767 'post_type' => $field['post_type'],
768 )
769 );
770 }
771
772 // return
773 return $value;
774 }
775
776
777 /**
778 * description
779 *
780 * @type function
781 * @date 11/02/2014
782 * @since 5.0.0
783 *
784 * @param $post_id (int)
785 * @return $post_id (int)
786 */
787 function validate_value( $valid, $value, $field, $input ) {
788
789 // default
790 if ( empty( $value ) || ! is_array( $value ) ) {
791 $value = array();
792 }
793
794 // min
795 if ( count( $value ) < $field['min'] ) {
796 $valid = _n( '%1$s requires at least %2$s selection', '%1$s requires at least %2$s selections', $field['min'], 'acf' );
797 $valid = sprintf( $valid, $field['label'], $field['min'] );
798 }
799
800 // return
801 return $valid;
802 }
803
804
805 /**
806 * Filters the field value before it is saved into the database.
807 *
808 * @since 3.6
809 *
810 * @param mixed $value The value which will be saved in the database.
811 * @param integer $post_id The post_id of which the value will be saved.
812 * @param array $field The field array holding all the field options.
813 *
814 * @return mixed $value The modified value.
815 */
816 public function update_value( $value, $post_id, $field ) {
817
818 // Bail early if no value.
819 if ( empty( $value ) ) {
820 acf_update_bidirectional_values( array(), $post_id, $field );
821 return $value;
822 }
823
824 // Format array of values.
825 // - ensure each value is an id.
826 // - Parse each id as string for SQL LIKE queries.
827 if ( acf_is_sequential_array( $value ) ) {
828 $value = array_map( 'acf_idval', $value );
829 $value = array_map( 'strval', $value );
830
831 // Parse single value for id.
832 } else {
833 $value = acf_idval( $value );
834 }
835
836 acf_update_bidirectional_values( acf_get_array( $value ), $post_id, $field );
837
838 // Return value.
839 return $value;
840 }
841
842 /**
843 * Validates relationship fields updated via the REST API.
844 *
845 * @param boolean $valid The current validity booleean
846 * @param integer $value The value of the field
847 * @param array $field The field array
848 * @return boolean|WP_Error
849 */
850 public function validate_rest_value( $valid, $value, $field ) {
851 return acf_get_field_type( 'post_object' )->validate_rest_value( $valid, $value, $field );
852 }
853
854 /**
855 * Return the schema array for the REST API.
856 *
857 * @param array $field
858 * @return array
859 */
860 public function get_rest_schema( array $field ) {
861 $schema = array(
862 'type' => array( 'integer', 'array', 'null' ),
863 'required' => ! empty( $field['required'] ),
864 'items' => array(
865 'type' => 'integer',
866 ),
867 );
868
869 if ( empty( $field['allow_null'] ) ) {
870 $schema['minItems'] = 1;
871 }
872
873 if ( ! empty( $field['min'] ) ) {
874 $schema['minItems'] = (int) $field['min'];
875 }
876
877 if ( ! empty( $field['max'] ) ) {
878 $schema['maxItems'] = (int) $field['max'];
879 }
880
881 return $schema;
882 }
883
884 /**
885 * @see \acf_field::get_rest_links()
886 * @param mixed $value The raw (unformatted) field value.
887 * @param integer|string $post_id
888 * @param array $field
889 * @return array
890 */
891 public function get_rest_links( $value, $post_id, array $field ) {
892 $links = array();
893
894 if ( empty( $value ) ) {
895 return $links;
896 }
897
898 foreach ( (array) $value as $object_id ) {
899 if ( ! $post_type = get_post_type( $object_id ) or ! $post_type = get_post_type_object( $post_type ) ) {
900 continue;
901 }
902 $rest_base = acf_get_object_type_rest_base( $post_type );
903 $links[] = array(
904 'rel' => $post_type->name === 'attachment' ? 'acf:attachment' : 'acf:post',
905 'href' => rest_url( sprintf( '/wp/v2/%s/%s', $rest_base, $object_id ) ),
906 'embeddable' => true,
907 );
908 }
909
910 return $links;
911 }
912
913 /**
914 * Apply basic formatting to prepare the value for default REST output.
915 *
916 * @param mixed $value
917 * @param string|integer $post_id
918 * @param array $field
919 * @return mixed
920 */
921 public function format_value_for_rest( $value, $post_id, array $field ) {
922 return acf_format_numerics( $value );
923 }
924
925 /**
926 * Formats the field value for JSON-LD output.
927 *
928 * @since 6.8.0
929 *
930 * @param mixed $value The value of the field.
931 * @param integer|string $post_id The ID of the post.
932 * @param array $field The field array.
933 * @return mixed
934 */
935 public function format_value_for_jsonld( $value, $post_id, $field ) {
936 $value = acf_format_numerics( $value );
937
938 if ( ! $value ) {
939 return $value;
940 }
941
942 if ( is_array( $value ) ) {
943 return array_map(
944 function ( $post_id ) {
945 return get_permalink( $post_id );
946 },
947 $value
948 );
949 }
950
951 return get_permalink( $value );
952 }
953
954 /**
955 * Returns an array of JSON-LD Property output types that are supported by this field type.
956 *
957 * @since 6.8
958 *
959 * @return string[]
960 */
961 public function get_jsonld_output_types(): array {
962 return array( 'Thing', 'URL' );
963 }
964 }
965
966
967 // initialize
968 acf_register_field_type( 'acf_field_relationship' );
969 endif; // class_exists check
970
971 ?>
972