PluginProbe
Advanced Custom Fields (ACF®) / 6.2.8
Advanced Custom Fields (ACF®) v6.2.8
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 / api / api-template.php
api-template.php
1,514 lines 39.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * This function will return a custom field value for a specific field name/key + post_id.
5 * There is a 3rd parameter to turn on/off formating. This means that an image field will not use
6 * its 'return option' to format the value but return only what was saved in the database
7 *
8 * @since 3.6
9 *
10 * @param string $selector The field name or key.
11 * @param mixed $post_id The post_id of which the value is saved against.
12 * @param boolean $format_value Whether or not to format the value as described above.
13 * @param boolean $escape_html If we're formatting the value, make sure it's also HTML safe.
14 *
15 * @return mixed
16 */
17 function get_field( $selector, $post_id = false, $format_value = true, $escape_html = false ) {
18
19 // filter post_id
20 $post_id = acf_get_valid_post_id( $post_id );
21
22 // get field
23 $field = acf_maybe_get_field( $selector, $post_id );
24
25 // create dummy field
26 $dummy_field = false;
27 if ( ! $field ) {
28 $field = acf_get_valid_field(
29 array(
30 'name' => $selector,
31 'key' => '',
32 'type' => '',
33 )
34 );
35
36 // prevent formatting, flag as dummy in case $escape_html is true.
37 $format_value = false;
38 $dummy_field = true;
39 }
40
41 // get value for field
42 $value = acf_get_value( $post_id, $field );
43
44 // escape html is only compatible when formatting the value too
45 if ( ! $dummy_field && ! $format_value && $escape_html ) {
46 _doing_it_wrong( __FUNCTION__, __( 'Returning an escaped HTML value is only possible when format_value is also true. The field value has not been returned for security.', 'acf' ), '6.2.6' ); //phpcs:ignore -- escape not required.
47 return false;
48 }
49
50 // format value
51 if ( $format_value ) {
52 if ( $escape_html ) {
53 // return the escaped HTML version if requested.
54 if ( acf_field_type_supports( $field['type'], 'escaping_html' ) ) {
55 $value = acf_format_value( $value, $post_id, $field, true );
56 } else {
57 $new_value = acf_format_value( $value, $post_id, $field );
58 if ( is_array( $new_value ) ) {
59 $value = map_deep( $new_value, 'acf_esc_html' );
60 } else {
61 $value = acf_esc_html( $new_value );
62 }
63 }
64 } else {
65 // get value for field
66 $value = acf_format_value( $value, $post_id, $field );
67 }
68 }
69
70 // If we've built a dummy text field, we won't format the value, but they may still request it escaped. Use `acf_esc_html`
71 if ( $dummy_field && $escape_html ) {
72 if ( is_array( $value ) ) {
73 $value = map_deep( $value, 'acf_esc_html' );
74 } else {
75 $value = acf_esc_html( $value );
76 }
77 }
78
79 // return
80 return $value;
81 }
82
83 /**
84 * This function is the same as echo get_field(), but will escape the value for safe HTML output regardless of parameters.
85 *
86 * @since 1.0.3
87 *
88 * @param string $selector The field name or key.
89 * @param mixed $post_id The post_id of which the value is saved against.
90 * @param boolean $format_value Enable formatting of value. Default true.
91 *
92 * @return void
93 */
94 function the_field( $selector, $post_id = false, $format_value = true ) {
95 $field = get_field_object( $selector, $post_id, $format_value, true, $format_value );
96 $value = $field ? $field['value'] : get_field( $selector, $post_id, $format_value, $format_value );
97
98 if ( is_array( $value ) ) {
99 $value = implode( ', ', $value );
100 }
101
102 // If we're not a scalar we'd throw an error, so return early for safety.
103 if ( ! is_scalar( $value ) ) {
104 return;
105 }
106
107 // If $format_value is false, we've not been able to apply field level escaping as we're giving the raw DB value. Escape the output with `acf_esc_html`.
108 if ( ! $format_value ) {
109 $value = acf_esc_html( $value );
110 }
111
112 // Get the unescaped value while we're still logging removed_unsafe_html.
113 $unescaped_value = get_field( $selector, $post_id, $format_value, false );
114 if ( is_array( $unescaped_value ) ) {
115 $unescaped_value = implode( ', ', $unescaped_value );
116 }
117
118 $field_type = is_array( $field ) && isset( $field['type'] ) ? $field['type'] : 'text';
119 if ( apply_filters( 'acf/the_field/allow_unsafe_html', false, $selector, $post_id, $field_type, $field ) ) {
120 $value = $unescaped_value;
121 } elseif ( (string) $value !== (string) $unescaped_value ) {
122 do_action( 'acf/removed_unsafe_html', __FUNCTION__, $selector, $field, $post_id );
123 }
124
125 echo $value; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped by logic above.
126 }
127
128 /**
129 * Logs instances of ACF successfully escaping unsafe HTML.
130 *
131 * @since 6.2.5
132 *
133 * @param string $function The function that resulted in HTML being escaped.
134 * @param string $selector The selector (field key, name, etc.) passed to that function.
135 * @param array $field The field being queried when HTML was escaped.
136 * @param mixed $post_id The post ID the function was called on.
137 * @return void
138 */
139 function _acf_log_escaped_html( $function, $selector, $field, $post_id ) {
140 // If the notice isn't shown, no use in logging the errors.
141 if ( apply_filters( 'acf/admin/prevent_escaped_html_notice', false ) ) {
142 return;
143 }
144
145 // If the field isn't set, we've output a non-ACF field, so don't log anything.
146 if ( ! is_array( $field ) ) {
147 return;
148 }
149
150 $escaped = _acf_get_escaped_html_log();
151
152 // Only store up to 100 results at a time.
153 if ( count( $escaped ) >= 100 ) {
154 return;
155 }
156
157 // Bail if we already logged an error for this field.
158 if ( isset( $escaped[ $field['key'] ] ) ) {
159 return;
160 }
161
162 $escaped[ $field['key'] ] = array(
163 'selector' => $selector,
164 'function' => $function,
165 'field' => $field['label'],
166 'post_id' => $post_id,
167 );
168
169 _acf_update_escaped_html_log( $escaped );
170 }
171 add_action( 'acf/removed_unsafe_html', '_acf_log_escaped_html', 10, 4 );
172
173 /**
174 * Returns an array of instances where HTML was altered due to escaping in the_field or a shortcode.
175 *
176 * @since 6.2.5
177 *
178 * @return array
179 */
180 function _acf_get_escaped_html_log() {
181 $escaped = get_option( 'acf_escaped_html_log', array() );
182 return is_array( $escaped ) ? $escaped : array();
183 }
184
185 /**
186 * Updates the array of instances where HTML was altered due to escaping in the_field or a shortcode.
187 *
188 * @since 6.2.5
189 *
190 * @param array $escaped The array of instances.
191 * @return boolean True on success, or false on failure.
192 */
193 function _acf_update_escaped_html_log( $escaped = array() ) {
194 return update_option( 'acf_escaped_html_log', (array) $escaped, true );
195 }
196
197 /**
198 * Deletes the array of instances where HTML was altered due to escaping in the_field or a shortcode.
199 * Since 6.2.7, also clears the legacy `acf_will_escape_html_log` option to clean up.
200 *
201 * @since 6.2.5
202 *
203 * @return boolean True on success, or false on failure.
204 */
205 function _acf_delete_escaped_html_log() {
206 delete_option( 'acf_will_escape_html_log' );
207 return delete_option( 'acf_escaped_html_log' );
208 }
209
210 /**
211 * This function will return an array containing all the field data for a given field_name.
212 *
213 * @since 3.6
214 *
215 * @param string $selector The field name or key.
216 * @param mixed $post_id The post_id of which the value is saved against.
217 * @param boolean $format_value Whether to format the field value.
218 * @param boolean $load_value Whether to load the field value.
219 * @param boolean $escape_html Should the field return a HTML safe formatted value if $format_value is true.
220 *
221 * @return array|false $field
222 */
223 function get_field_object( $selector, $post_id = false, $format_value = true, $load_value = true, $escape_html = false ) {
224 // Compatibility with ACF ~4.
225 if ( is_array( $format_value ) && isset( $format_value['format_value'] ) ) {
226 $format_value = $format_value['format_value'];
227 }
228
229 $post_id = acf_get_valid_post_id( $post_id );
230 $field = acf_maybe_get_field( $selector, $post_id );
231
232 if ( ! $field ) {
233 return false;
234 }
235
236 if ( $load_value ) {
237 $field['value'] = acf_get_value( $post_id, $field );
238 }
239
240 // escape html is only compatible when formatting the value too
241 if ( ! $format_value && $escape_html ) {
242 _doing_it_wrong( __FUNCTION__, __( 'Returning an escaped HTML value is only possible when format_value is also true. The field value has not been returned for security.', 'acf' ), '6.2.6' ); //phpcs:ignore -- escape not required.
243 $field['value'] = false;
244 return $field;
245 }
246
247 // format value
248 if ( $load_value && $format_value ) {
249 if ( $escape_html ) {
250 // return the escaped HTML version if requested.
251 if ( acf_field_type_supports( $field['type'], 'escaping_html' ) ) {
252 $field['value'] = acf_format_value( $field['value'], $post_id, $field, true );
253 } else {
254 $new_value = acf_format_value( $field['value'], $post_id, $field );
255 if ( is_array( $new_value ) ) {
256 $field['value'] = map_deep( $new_value, 'acf_esc_html' );
257 } else {
258 $field['value'] = acf_esc_html( $new_value );
259 }
260 }
261 } else {
262 // get value for field
263 $field['value'] = acf_format_value( $field['value'], $post_id, $field );
264 }
265 }
266
267 return $field;
268 }
269
270 /**
271 * This function will return a field for the given selector.
272 * It will also review the field_reference to ensure the correct field is returned which makes it useful for the template API
273 *
274 * @since 5.2.3
275 *
276 * @param $selector (mixed) identifier of field. Can be an ID, key, name or post object
277 * @param $post_id (mixed) the post_id of which the value is saved against
278 * @param $strict (boolean) if true, return a field only when a field key is found.
279 *
280 * @return $field (array)
281 */
282 function acf_maybe_get_field( $selector, $post_id = false, $strict = true ) {
283
284 // init
285 acf_init();
286
287 // Check if field key was given.
288 if ( acf_is_field_key( $selector ) ) {
289 return acf_get_field( $selector );
290 }
291
292 // Lookup field via reference.
293 $post_id = acf_get_valid_post_id( $post_id );
294 $field = acf_get_meta_field( $selector, $post_id );
295 if ( $field ) {
296 return $field;
297 }
298
299 // Lookup field loosely via name.
300 if ( ! $strict ) {
301 return acf_get_field( $selector );
302 }
303
304 // Return no result.
305 return false;
306 }
307
308 /**
309 * This function will attempt to find a sub field
310 *
311 * @since 5.4.0
312 *
313 * @param $post_id (int)
314 * @return $post_id (int)
315 */
316 function acf_maybe_get_sub_field( $selectors, $post_id = false, $strict = true ) {
317
318 // bail early if not enough selectors
319 if ( ! is_array( $selectors ) || count( $selectors ) < 3 ) {
320 return false;
321 }
322
323 // vars
324 $offset = acf_get_setting( 'row_index_offset' );
325 $selector = acf_extract_var( $selectors, 0 );
326 $selectors = array_values( $selectors ); // reset keys
327
328 // attempt get field
329 $field = acf_maybe_get_field( $selector, $post_id, $strict );
330
331 // bail early if no field
332 if ( ! $field ) {
333 return false;
334 }
335
336 // loop
337 for ( $j = 0; $j < count( $selectors ); $j += 2 ) {
338
339 // vars
340 $sub_i = $selectors[ $j ];
341 $sub_s = $selectors[ $j + 1 ];
342 $field_name = $field['name'];
343
344 // find sub field
345 $field = acf_get_sub_field( $sub_s, $field );
346
347 // bail early if no sub field
348 if ( ! $field ) {
349 return false;
350 }
351
352 // add to name
353 $field['name'] = $field_name . '_' . ( $sub_i - $offset ) . '_' . $field['name'];
354 }
355
356 // return
357 return $field;
358 }
359
360 /**
361 * This function will return an array containing all the custom field values for a specific post_id.
362 * The function is not very elegant and wastes a lot of PHP memory / SQL queries if you are not using all the values.
363 *
364 * @since 3.6
365 *
366 * @param mixed $post_id The post_id of which the value is saved against.
367 * @param boolean $format_value Whether or not to format the field value.
368 * @param boolean $escape_html Should the field return a HTML safe formatted value if $format_value is true.
369 *
370 * @return array|false Associative array where field name => field value, or false on failure.
371 */
372 function get_fields( $post_id = false, $format_value = true, $escape_html = false ) {
373
374 // escape html is only compatible when formatting the value too
375 if ( ! $format_value && $escape_html ) {
376 _doing_it_wrong( __FUNCTION__, __( 'Returning escaped HTML values is only possible when format_value is also true. The field values have not been returned for security.', 'acf' ), '6.2.6' ); //phpcs:ignore -- escape not required.
377 return false;
378 }
379
380 // vars
381 $fields = get_field_objects( $post_id, $format_value, true, $escape_html );
382 $meta = array();
383
384 // bail early
385 if ( ! $fields ) {
386 return false;
387 }
388
389 // populate
390 foreach ( $fields as $k => $field ) {
391 $meta[ $k ] = $field['value'];
392 }
393
394 // return
395 return $meta;
396 }
397
398
399 /**
400 * This function will return an array containing all the custom field objects for a specific post_id.
401 * The function is not very elegant and wastes a lot of PHP memory / SQL queries if you are not using all the fields / values.
402 *
403 * @since 3.6
404 *
405 * @param mixed $post_id The post_id of which the value is saved against.
406 * @param boolean $format_value Whether or not to format the field value.
407 * @param boolean $load_value Whether or not to load the field value.
408 * @param boolean $escape_html Should the field return a HTML safe formatted value if $format_value is true.
409 *
410 * @return array|false Associative array where field name => field, or false on failure.
411 */
412 function get_field_objects( $post_id = false, $format_value = true, $load_value = true, $escape_html = false ) {
413
414 // init
415 acf_init();
416
417 // validate post_id
418 $post_id = acf_get_valid_post_id( $post_id );
419
420 // get meta
421 $meta = acf_get_meta( $post_id );
422
423 // bail early if no meta
424 if ( empty( $meta ) ) {
425 return false;
426 }
427
428 // escape html is only compatible when formatting the value too
429 if ( ! $format_value && $escape_html ) {
430 _doing_it_wrong( __FUNCTION__, __( 'Returning escaped HTML values is only possible when format_value is also true. The field values have not been returned for security.', 'acf' ), '6.2.6' ); //phpcs:ignore -- escape not required.
431 }
432
433 // populate vars
434 $fields = array();
435 foreach ( $meta as $key => $value ) {
436
437 // bail if reference key does not exist
438 if ( ! isset( $meta[ "_$key" ] ) || ( ! is_string( $meta[ "_$key" ] ) && ! is_numeric( $meta[ "_$key" ] ) ) ) {
439 continue;
440 }
441
442 // get field
443 $field = acf_get_field( $meta[ "_$key" ] );
444
445 // bail early if no field, or if the field's name is different to $key
446 // - solves problem where sub fields (and clone fields) are incorrectly allowed
447 if ( ! $field || $field['name'] !== $key ) {
448 continue;
449 }
450
451 // load value
452 if ( $load_value ) {
453 $field['value'] = acf_get_value( $post_id, $field );
454 }
455
456 // avoid returning field values when the function is called incorrectly.
457 if ( ! $format_value && $escape_html ) {
458 $field['value'] = false;
459 }
460
461 // format value
462 if ( $load_value && $format_value ) {
463 if ( $escape_html ) {
464 // return the escaped HTML version if requested.
465 if ( acf_field_type_supports( $field['type'], 'escaping_html' ) ) {
466 $field['value'] = acf_format_value( $field['value'], $post_id, $field, true );
467 } else {
468 $new_value = acf_format_value( $field['value'], $post_id, $field );
469 if ( is_array( $new_value ) ) {
470 $field['value'] = map_deep( $new_value, 'acf_esc_html' );
471 } else {
472 $field['value'] = acf_esc_html( $new_value );
473 }
474 }
475 } else {
476 // get value for field
477 $field['value'] = acf_format_value( $field['value'], $post_id, $field );
478 }
479 }
480
481 // append to $value
482 $fields[ $key ] = $field;
483 }
484
485 // no value
486 if ( empty( $fields ) ) {
487 return false;
488 }
489
490 // return
491 return $fields;
492 }
493
494
495 /**
496 * Checks if a field (such as Repeater or Flexible Content) has any rows of data to loop over.
497 * This function is intended to be used in conjunction with the_row() to step through available values.
498 *
499 * @since 4.3.0
500 *
501 * @param string $selector The field name or field key.
502 * @param mixed $post_id The post ID where the value is saved. Defaults to the current post.
503 * @return boolean
504 */
505 function have_rows( $selector, $post_id = false ) {
506
507 // Validate and backup $post_id.
508 $_post_id = $post_id;
509 $post_id = acf_get_valid_post_id( $post_id );
510
511 // Vars.
512 $key = "selector={$selector}/post_id={$post_id}";
513 $active_loop = acf_get_loop( 'active' );
514 $prev_loop = acf_get_loop( 'previous' );
515 $new_loop = false;
516 $sub_field = false;
517
518 // Check if no active loop.
519 if ( ! $active_loop ) {
520 $new_loop = 'parent';
521
522 // Detect "change" compared to the active loop.
523 } elseif ( $key !== $active_loop['key'] ) {
524
525 // Find sub field and check if a sub value exists.
526 $sub_field_exists = false;
527 $sub_field = acf_get_sub_field( $selector, $active_loop['field'] );
528 if ( $sub_field ) {
529 $sub_field_exists = isset( $active_loop['value'][ $active_loop['i'] ][ $sub_field['key'] ] );
530 }
531
532 // Detect change in post_id.
533 if ( $post_id != $active_loop['post_id'] ) {
534
535 // Case: Change in $post_id was due to this being a nested loop and not specifying the $post_id.
536 // Action: Move down one level into a new loop.
537 if ( empty( $_post_id ) && $sub_field_exists ) {
538 $new_loop = 'child';
539
540 // Case: Change in $post_id was due to a nested loop ending.
541 // Action: move up one level through the loops.
542 } elseif ( $prev_loop && $prev_loop['post_id'] == $post_id ) {
543 acf_remove_loop( 'active' );
544 $active_loop = $prev_loop;
545
546 // Case: Chang in $post_id is the most obvious, used in an WP_Query loop with multiple $post objects.
547 // Action: leave this current loop alone and create a new parent loop.
548 } else {
549 $new_loop = 'parent';
550 }
551
552 // Detect change in selector.
553 } elseif ( $selector != $active_loop['selector'] ) {
554
555 // Case: Change in $field_name was due to this being a nested loop.
556 // Action: move down one level into a new loop.
557 if ( $sub_field_exists ) {
558 $new_loop = 'child';
559
560 // Case: Change in $field_name was due to a nested loop ending.
561 // Action: move up one level through the loops.
562 } elseif ( $prev_loop && $prev_loop['selector'] == $selector && $prev_loop['post_id'] == $post_id ) {
563 acf_remove_loop( 'active' );
564 $active_loop = $prev_loop;
565
566 // Case: Change in $field_name is the most obvious, this is a new loop for a different field within the $post.
567 // Action: leave this current loop alone and create a new parent loop.
568 } else {
569 $new_loop = 'parent';
570 }
571 }
572 }
573
574 // Add loop if required.
575 if ( $new_loop ) {
576 $args = array(
577 'key' => $key,
578 'selector' => $selector,
579 'post_id' => $post_id,
580 'name' => null,
581 'value' => null,
582 'field' => null,
583 'i' => -1,
584 );
585
586 // Case: Parent loop.
587 if ( $new_loop === 'parent' ) {
588 $field = get_field_object( $selector, $post_id, false );
589 if ( $field ) {
590 $args['field'] = $field;
591 $args['value'] = $field['value'];
592 $args['name'] = $field['name'];
593 unset( $args['field']['value'] );
594 }
595
596 // Case: Child loop ($sub_field must exist).
597 } else {
598 $args['field'] = $sub_field;
599 $args['value'] = $active_loop['value'][ $active_loop['i'] ][ $sub_field['key'] ];
600 $args['name'] = "{$active_loop['name']}_{$active_loop['i']}_{$sub_field['name']}";
601 $args['post_id'] = $active_loop['post_id'];
602 }
603
604 // Bail early if value is either empty or a non array.
605 if ( ! $args['value'] || ! is_array( $args['value'] ) ) {
606 return false;
607 }
608
609 // Allow for non repeatable data for Group and Clone fields.
610 if ( acf_get_field_type_prop( $args['field']['type'], 'have_rows' ) === 'single' ) {
611 $args['value'] = array( $args['value'] );
612 }
613
614 // Add loop.
615 $active_loop = acf_add_loop( $args );
616 }
617
618 // Return true if next row exists.
619 if ( $active_loop && isset( $active_loop['value'][ $active_loop['i'] + 1 ] ) ) {
620 return true;
621 }
622
623 // Return false if no next row.
624 acf_remove_loop( 'active' );
625 return false;
626 }
627
628
629 /**
630 * This function will progress the global repeater or flexible content value 1 row
631 *
632 * @since 4.3.0
633 *
634 * @param N/A
635 * @return (array) the current row data
636 */
637 function the_row( $format = false ) {
638
639 // vars
640 $i = acf_get_loop( 'active', 'i' );
641
642 // increase
643 ++$i;
644
645 // update
646 acf_update_loop( 'active', 'i', $i );
647
648 // return
649 return get_row( $format );
650 }
651
652 function get_row( $format = false ) {
653
654 // vars
655 $loop = acf_get_loop( 'active' );
656
657 // bail early if no loop
658 if ( ! $loop ) {
659 return false;
660 }
661
662 // get value
663 $value = acf_maybe_get( $loop['value'], $loop['i'] );
664
665 // bail early if no current value
666 // possible if get_row_layout() is called before the_row()
667 if ( ! $value ) {
668 return false;
669 }
670
671 // format
672 if ( $format ) {
673
674 // vars
675 $field = $loop['field'];
676
677 // single row
678 if ( acf_get_field_type_prop( $field['type'], 'have_rows' ) === 'single' ) {
679
680 // format value
681 $value = acf_format_value( $value, $loop['post_id'], $field );
682
683 // multiple rows
684 } else {
685
686 // format entire value
687 // - solves problem where cached value is incomplete
688 // - no performance issues here thanks to cache
689 $value = acf_format_value( $loop['value'], $loop['post_id'], $field );
690 $value = acf_maybe_get( $value, $loop['i'] );
691 }
692 }
693
694 // return
695 return $value;
696 }
697
698 function get_row_index() {
699
700 // vars
701 $i = acf_get_loop( 'active', 'i' );
702 $offset = acf_get_setting( 'row_index_offset' );
703
704 // return
705 return $offset + $i;
706 }
707
708 function the_row_index() {
709 echo intval( get_row_index() );
710 }
711
712
713 /**
714 * This function is used inside a 'has_sub_field' while loop to return a sub field object
715 *
716 * @since 5.3.8
717 *
718 * @param $selector (string)
719 * @return (array)
720 */
721 function get_row_sub_field( $selector ) {
722
723 // vars
724 $row = acf_get_loop( 'active' );
725
726 // bail early if no row
727 if ( ! $row ) {
728 return false;
729 }
730
731 // attempt to find sub field
732 $sub_field = acf_get_sub_field( $selector, $row['field'] );
733
734 // bail early if no field
735 if ( ! $sub_field ) {
736 return false;
737 }
738
739 // update field's name based on row data
740 $sub_field['name'] = "{$row['name']}_{$row['i']}_{$sub_field['name']}";
741
742 // return
743 return $sub_field;
744 }
745
746
747 /**
748 * This function is used inside a 'has_sub_field' while loop to return a sub field value
749 *
750 * @since 5.3.8
751 *
752 * @param $selector (string)
753 * @return (mixed)
754 */
755 function get_row_sub_value( $selector ) {
756
757 // vars
758 $row = acf_get_loop( 'active' );
759
760 // bail early if no row
761 if ( ! $row ) {
762 return null;
763 }
764
765 // return value
766 if ( isset( $row['value'][ $row['i'] ][ $selector ] ) ) {
767 return $row['value'][ $row['i'] ][ $selector ];
768 }
769
770 // return
771 return null;
772 }
773
774
775 /**
776 * This function will find the current loop and unset it from the global array.
777 * To be used when loop finishes or a break is used
778 *
779 * @since 5.0.0
780 *
781 * @param $hard_reset (boolean) completely wipe the global variable, or just unset the active row
782 * @return (boolean)
783 */
784 function reset_rows() {
785
786 // remove last loop
787 acf_remove_loop( 'active' );
788
789 // return
790 return true;
791 }
792
793
794 /**
795 * This function is used inside a while loop to return either true or false (loop again or stop).
796 * When using a repeater or flexible content field, it will loop through the rows until
797 * there are none left or a break is detected
798 *
799 * @since 1.0.3
800 *
801 * @param $field_name (string) the field name
802 * @param $post_id (mixed) the post_id of which the value is saved against
803 * @return (boolean)
804 */
805 function has_sub_field( $field_name, $post_id = false ) {
806
807 // vars
808 $r = have_rows( $field_name, $post_id );
809
810 // if has rows, progress through 1 row for the while loop to work
811 if ( $r ) {
812 the_row();
813 }
814
815 // return
816 return $r;
817 }
818
819 /**
820 * Alias of has_sub_field
821 */
822 function has_sub_fields( $field_name, $post_id = false ) {
823 return has_sub_field( $field_name, $post_id );
824 }
825
826
827 /**
828 * This function is used inside a 'has_sub_field' while loop to return a sub field value
829 *
830 * @since 1.0.3
831 *
832 * @param string $selector The field name or key.
833 * @param boolean $format_value Whether or not to format the value as described above.
834 * @param boolean $escape_html If we're formatting the value, make sure it's also HTML safe.
835 *
836 * @return mixed
837 */
838 function get_sub_field( $selector = '', $format_value = true, $escape_html = false ) {
839
840 // get sub field
841 $sub_field = get_sub_field_object( $selector, $format_value, true, $escape_html );
842
843 // bail early if no sub field
844 if ( ! $sub_field ) {
845 return false;
846 }
847
848 // return
849 return $sub_field['value'];
850 }
851
852
853 /**
854 * This function is the same as echo get_sub_field(), but will escape the value for safe HTML output.
855 *
856 * @since 1.0.3
857 *
858 * @param string $field_name The field name.
859 * @param boolean $format_value Enable formatting of value. When false, the field value will be escaped at this level with `acf_esc_html`. Default true.
860 *
861 * @return void
862 */
863 function the_sub_field( $field_name, $format_value = true ) {
864 $field = get_sub_field_object( $field_name, $format_value, true, $format_value );
865 $value = ( is_array( $field ) && isset( $field['value'] ) ) ? $field['value'] : false;
866
867 if ( is_array( $value ) ) {
868 $value = implode( ', ', $value );
869 }
870
871 // If we're not a scalar we'd throw an error, so return early for safety.
872 if ( ! is_scalar( $value ) ) {
873 return;
874 }
875
876 // If $format_value is false, we've not been able to apply field level escaping as we're giving the raw DB value. Escape the output with `acf_esc_html`.
877 if ( ! $format_value ) {
878 $value = acf_esc_html( $value );
879 }
880
881 $unescaped_field = get_sub_field_object( $field_name, $format_value, true, false );
882 $unescaped_value = ( is_array( $unescaped_field ) && isset( $unescaped_field['value'] ) ) ? $unescaped_field['value'] : false;
883 if ( is_array( $unescaped_value ) ) {
884 $unescaped_value = implode( ', ', $unescaped_value );
885 }
886
887 $field_type = is_array( $field ) && isset( $field['type'] ) ? $field['type'] : 'text';
888 if ( apply_filters( 'acf/the_field/allow_unsafe_html', false, $field_name, 'sub_field', $field_type, $field ) ) {
889 $value = $unescaped_value;
890 } elseif ( (string) $value !== (string) $unescaped_value ) {
891 do_action( 'acf/removed_unsafe_html', __FUNCTION__, $field_name, $field, false );
892 }
893
894 echo $value; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped inside get_sub_field_object where necessary.
895 }
896
897
898 /**
899 * This function is used inside a 'has_sub_field' while loop to return a sub field object
900 *
901 * @since 3.5.8.1
902 *
903 * @param string $selector The field name or key.
904 * @param boolean $format_value Whether to format the field value.
905 * @param boolean $load_value Whether to load the field value.
906 * @param boolean $escape_html Should the field return a HTML safe formatted value.
907 *
908 * @return mixed
909 */
910 function get_sub_field_object( $selector, $format_value = true, $load_value = true, $escape_html = false ) {
911
912 $row = acf_get_loop( 'active' );
913
914 // bail early if no row
915 if ( ! $row ) {
916 return false;
917 }
918
919 // attempt to find sub field
920 $sub_field = get_row_sub_field( $selector );
921
922 // bail early if no sub field
923 if ( ! $sub_field ) {
924 return false;
925 }
926
927 // load value
928 if ( $load_value ) {
929 $sub_field['value'] = get_row_sub_value( $sub_field['key'] );
930 }
931
932 // escape html is only compatible when formatting the value too
933 if ( ! $format_value && $escape_html ) {
934 _doing_it_wrong( __FUNCTION__, __( 'Returning an escaped HTML value is only possible when format_value is also true. The field value has not been returned for security.', 'acf' ), '6.2.6' ); //phpcs:ignore -- escape not required.
935 $sub_field['value'] = false;
936 }
937
938 // format value
939 if ( $load_value && $format_value ) {
940 if ( $escape_html ) {
941 // return the escaped HTML version if requested.
942 if ( acf_field_type_supports( $sub_field['type'], 'escaping_html' ) ) {
943 $sub_field['value'] = acf_format_value( $sub_field['value'], $row['post_id'], $sub_field, true );
944 } else {
945 $new_value = acf_format_value( $sub_field['value'], $row['post_id'], $sub_field );
946 if ( is_array( $new_value ) ) {
947 $sub_field['value'] = map_deep( $new_value, 'acf_esc_html' );
948 } else {
949 $sub_field['value'] = acf_esc_html( $new_value );
950 }
951 }
952 } else {
953 // get value for field
954 $sub_field['value'] = acf_format_value( $sub_field['value'], $row['post_id'], $sub_field );
955 }
956 }
957
958 // return
959 return $sub_field;
960 }
961
962
963 /**
964 * This function will return a string representation of the current row layout within a 'have_rows' loop
965 *
966 * @since 3.0.6
967 *
968 * @return mixed
969 */
970 function get_row_layout() {
971
972 // vars
973 $row = get_row();
974
975 // return
976 if ( isset( $row['acf_fc_layout'] ) ) {
977 return $row['acf_fc_layout'];
978 }
979
980 // return
981 return false;
982 }
983
984 /**
985 * This function is used to add basic shortcode support for the ACF plugin
986 * eg. [acf field="heading" post_id="123" format_value="1"]
987 *
988 * @since 1.1.1
989 *
990 * @param array $atts The shortcode attributes.
991 *
992 * @return string|void
993 */
994 function acf_shortcode( $atts ) {
995 // Return if the ACF shortcode is disabled.
996 if ( ! acf_get_setting( 'enable_shortcode' ) ) {
997 return;
998 }
999
1000 if ( function_exists( 'wp_is_block_theme' ) && wp_is_block_theme() ) {
1001 // Prevent the ACF shortcode in FSE block template parts by default.
1002 if ( ! doing_filter( 'the_content' ) && ! apply_filters( 'acf/shortcode/allow_in_block_themes_outside_content', false ) ) {
1003 return;
1004 }
1005 }
1006
1007 // Limit previews of ACF shortcode data for users without publish_posts permissions.
1008 $preview_capability = apply_filters( 'acf/shortcode/preview_capability', 'publish_posts' );
1009 if ( is_preview() && ! current_user_can( $preview_capability ) ) {
1010 return apply_filters( 'acf/shortcode/preview_capability_message', __( '[ACF shortcode value disabled for preview]', 'acf' ) );
1011 }
1012
1013 // Mitigate issue where some AJAX requests can return ACF field data.
1014 $ajax_capability = apply_filters( 'acf/ajax/shortcode_capability', 'edit_posts' );
1015 if ( wp_doing_ajax() && ( $ajax_capability !== false ) && ! current_user_can( $ajax_capability ) ) {
1016 return;
1017 }
1018
1019 $atts = shortcode_atts(
1020 array(
1021 'field' => '',
1022 'post_id' => false,
1023 'format_value' => true,
1024 ),
1025 $atts,
1026 'acf'
1027 );
1028
1029 $access_already_prevented = apply_filters( 'acf/prevent_access_to_unknown_fields', false );
1030 $filter_applied = false;
1031
1032 if ( ! $access_already_prevented ) {
1033 $filter_applied = true;
1034 add_filter( 'acf/prevent_access_to_unknown_fields', '__return_true' );
1035 }
1036
1037 // Decode the post ID for filtering.
1038 $post_id = acf_get_valid_post_id( $atts['post_id'] );
1039 $decoded_post_id = acf_decode_post_id( $post_id );
1040
1041 // Try to get the field value, ensuring any non-safe HTML is stripped from wysiwyg fields via `acf_the_content`
1042 $field = get_field_object( $atts['field'], $post_id, $atts['format_value'], true, true );
1043 $value = $field ? $field['value'] : get_field( $atts['field'], $post_id, $atts['format_value'], true );
1044
1045 $field_type = is_array( $field ) && isset( $field['type'] ) ? $field['type'] : 'text';
1046
1047 if ( apply_filters( 'acf/shortcode/prevent_access', false, $atts, $decoded_post_id['id'], $decoded_post_id['type'], $field_type, $field ) ) {
1048 return;
1049 }
1050
1051 if ( is_array( $value ) ) {
1052 $value = implode( ', ', $value );
1053 }
1054
1055 // Temporarily always get the unescaped version for action comparison.
1056 $unescaped_value = get_field( $atts['field'], $post_id, $atts['format_value'], false );
1057
1058 if ( $filter_applied ) {
1059 remove_filter( 'acf/prevent_access_to_unknown_fields', '__return_true' );
1060 }
1061
1062 // Remove the filter preventing access to unknown filters now we've got all the values.
1063 if ( $filter_applied ) {
1064 remove_filter( 'acf/prevent_access_to_unknown_fields', '__return_true' );
1065 }
1066
1067 if ( is_array( $unescaped_value ) ) {
1068 $unescaped_value = implode( ', ', $unescaped_value );
1069 }
1070
1071 // Handle getting the unescaped version if we're allowed unsafe html.
1072 if ( apply_filters( 'acf/shortcode/allow_unsafe_html', false, $atts, $field_type, $field ) ) {
1073 $value = $unescaped_value;
1074 } elseif ( (string) $value !== (string) $unescaped_value ) {
1075 do_action( 'acf/removed_unsafe_html', __FUNCTION__, $atts['field'], $field, $post_id );
1076 }
1077
1078 return $value;
1079 }
1080 add_shortcode( 'acf', 'acf_shortcode' );
1081
1082
1083 /**
1084 * This function will update a value in the database
1085 *
1086 * @since 3.1.9
1087 *
1088 * @param string $selector The field name or key.
1089 * @param mixed $value The value to save in the database.
1090 * @param mixed $post_id The post_id of which the value is saved against.
1091 *
1092 * @return boolean
1093 */
1094 function update_field( $selector, $value, $post_id = false ) {
1095
1096 // filter post_id
1097 $post_id = acf_get_valid_post_id( $post_id );
1098
1099 // get field
1100 $field = acf_maybe_get_field( $selector, $post_id, false );
1101
1102 // create dummy field
1103 if ( ! $field ) {
1104 $field = acf_get_valid_field(
1105 array(
1106 'name' => $selector,
1107 'key' => '',
1108 'type' => '',
1109 )
1110 );
1111 }
1112
1113 // save
1114 return acf_update_value( $value, $post_id, $field );
1115 }
1116
1117
1118 /**
1119 * This function will update a value of a sub field in the database
1120 *
1121 * @since 5.0.0
1122 *
1123 * @param $selector (mixed) the sub field name or key, or an array of ancestors
1124 * @param $value (mixed) the value to save in the database
1125 * @param $post_id (mixed) the post_id of which the value is saved against
1126 *
1127 * @return boolean
1128 */
1129 function update_sub_field( $selector, $value, $post_id = false ) {
1130
1131 // vars
1132 $sub_field = false;
1133
1134 // get sub field
1135 if ( is_array( $selector ) ) {
1136 $post_id = acf_get_valid_post_id( $post_id );
1137 $sub_field = acf_maybe_get_sub_field( $selector, $post_id, false );
1138 } else {
1139 $post_id = acf_get_loop( 'active', 'post_id' );
1140 $sub_field = get_row_sub_field( $selector );
1141 }
1142
1143 // bail early if no sub field
1144 if ( ! $sub_field ) {
1145 return false;
1146 }
1147
1148 // update
1149 return acf_update_value( $value, $post_id, $sub_field );
1150 }
1151
1152
1153 /**
1154 * This function will remove a value from the database
1155 *
1156 * @since 3.1.9
1157 *
1158 * @param $selector (string) the field name or key
1159 * @param $post_id (mixed) the post_id of which the value is saved against
1160 *
1161 * @return boolean
1162 */
1163 function delete_field( $selector, $post_id = false ) {
1164
1165 // filter post_id
1166 $post_id = acf_get_valid_post_id( $post_id );
1167
1168 // get field
1169 $field = acf_maybe_get_field( $selector, $post_id );
1170
1171 // delete
1172 return $field ? acf_delete_value( $post_id, $field ) : false;
1173 }
1174
1175
1176 /**
1177 * This function will delete a value of a sub field in the database
1178 *
1179 * @since 5.0.0
1180 *
1181 * @param $selector (mixed) the sub field name or key, or an array of ancestors
1182 * @param $value (mixed) the value to save in the database
1183 * @param $post_id (mixed) the post_id of which the value is saved against
1184 * @return (boolean)
1185 */
1186 function delete_sub_field( $selector, $post_id = false ) {
1187 return update_sub_field( $selector, null, $post_id );
1188 }
1189
1190
1191 /**
1192 * This function will add a row of data to a field
1193 *
1194 * @since 5.2.3
1195 *
1196 * @param $selector (string)
1197 * @param $row (array)
1198 * @param $post_id (mixed)
1199 * @return (boolean)
1200 */
1201 function add_row( $selector, $row = false, $post_id = false ) {
1202
1203 // filter post_id
1204 $post_id = acf_get_valid_post_id( $post_id );
1205
1206 // get field
1207 $field = acf_maybe_get_field( $selector, $post_id, false );
1208
1209 // bail early if no field
1210 if ( ! $field ) {
1211 return false;
1212 }
1213
1214 // get raw value
1215 $value = acf_get_value( $post_id, $field );
1216
1217 // ensure array
1218 $value = acf_get_array( $value );
1219
1220 // append
1221 $value[] = $row;
1222
1223 // Paginated repeaters should be saved normally.
1224 $field['pagination'] = false;
1225
1226 // update value
1227 acf_update_value( $value, $post_id, $field );
1228
1229 // return
1230 return count( $value );
1231 }
1232
1233
1234 /**
1235 * This function will add a row of data to a field
1236 *
1237 * @since 5.2.3
1238 *
1239 * @param $selector (string)
1240 * @param $row (array)
1241 * @param $post_id (mixed)
1242 * @return (boolean)
1243 */
1244 function add_sub_row( $selector, $row = false, $post_id = false ) {
1245
1246 // vars
1247 $sub_field = false;
1248
1249 // get sub field
1250 if ( is_array( $selector ) ) {
1251 $post_id = acf_get_valid_post_id( $post_id );
1252 $sub_field = acf_maybe_get_sub_field( $selector, $post_id, false );
1253 } else {
1254 $post_id = acf_get_loop( 'active', 'post_id' );
1255 $sub_field = get_row_sub_field( $selector );
1256 }
1257
1258 // bail early if no sub field
1259 if ( ! $sub_field ) {
1260 return false;
1261 }
1262
1263 // get raw value
1264 $value = acf_get_value( $post_id, $sub_field );
1265
1266 // ensure array
1267 $value = acf_get_array( $value );
1268
1269 // append
1270 $value[] = $row;
1271
1272 // update
1273 acf_update_value( $value, $post_id, $sub_field );
1274
1275 // return
1276 return count( $value );
1277 }
1278
1279
1280 /**
1281 * This function will update a row of data to a field
1282 *
1283 * @since 5.2.3
1284 *
1285 * @param $selector (string)
1286 * @param $i (int)
1287 * @param $row (array)
1288 * @param $post_id (mixed)
1289 * @return (boolean)
1290 */
1291 function update_row( $selector, $i = 1, $row = false, $post_id = false ) {
1292
1293 // vars
1294 $offset = acf_get_setting( 'row_index_offset' );
1295 $i = $i - $offset;
1296
1297 // filter post_id
1298 $post_id = acf_get_valid_post_id( $post_id );
1299
1300 // get field
1301 $field = acf_maybe_get_field( $selector, $post_id, false );
1302
1303 // bail early if no field
1304 if ( ! $field ) {
1305 return false;
1306 }
1307
1308 // get raw value
1309 $value = acf_get_value( $post_id, $field );
1310
1311 // ensure array
1312 $value = acf_get_array( $value );
1313
1314 // update
1315 $value[ $i ] = $row;
1316
1317 // update value
1318 acf_update_value( $value, $post_id, $field );
1319
1320 // return
1321 return true;
1322 }
1323
1324
1325 /**
1326 * This function will add a row of data to a field
1327 *
1328 * @since 5.2.3
1329 *
1330 * @param $selector (string)
1331 * @param $row (array)
1332 * @param $post_id (mixed)
1333 * @return (boolean)
1334 */
1335 function update_sub_row( $selector, $i = 1, $row = false, $post_id = false ) {
1336
1337 // vars
1338 $sub_field = false;
1339 $offset = acf_get_setting( 'row_index_offset' );
1340 $i = $i - $offset;
1341
1342 // get sub field
1343 if ( is_array( $selector ) ) {
1344 $post_id = acf_get_valid_post_id( $post_id );
1345 $sub_field = acf_maybe_get_sub_field( $selector, $post_id, false );
1346 } else {
1347 $post_id = acf_get_loop( 'active', 'post_id' );
1348 $sub_field = get_row_sub_field( $selector );
1349 }
1350
1351 // bail early if no sub field
1352 if ( ! $sub_field ) {
1353 return false;
1354 }
1355
1356 // get raw value
1357 $value = acf_get_value( $post_id, $sub_field );
1358
1359 // ensure array
1360 $value = acf_get_array( $value );
1361
1362 // append
1363 $value[ $i ] = $row;
1364
1365 // update
1366 acf_update_value( $value, $post_id, $sub_field );
1367
1368 // return
1369 return true;
1370 }
1371
1372
1373 /**
1374 * This function will delete a row of data from a field
1375 *
1376 * @since 5.2.3
1377 *
1378 * @param $selector (string)
1379 * @param $i (int)
1380 * @param $post_id (mixed)
1381 * @return (boolean)
1382 */
1383 function delete_row( $selector, $i = 1, $post_id = false ) {
1384
1385 // vars
1386 $offset = acf_get_setting( 'row_index_offset' );
1387 $i = $i - $offset;
1388
1389 // filter post_id
1390 $post_id = acf_get_valid_post_id( $post_id );
1391
1392 // get field
1393 $field = acf_maybe_get_field( $selector, $post_id );
1394
1395 // bail early if no field
1396 if ( ! $field ) {
1397 return false;
1398 }
1399
1400 // get value
1401 $value = acf_get_value( $post_id, $field );
1402
1403 // ensure array
1404 $value = acf_get_array( $value );
1405
1406 // bail early if index doesn't exist
1407 if ( ! isset( $value[ $i ] ) ) {
1408 return false;
1409 }
1410
1411 // unset
1412 unset( $value[ $i ] );
1413
1414 // update
1415 acf_update_value( $value, $post_id, $field );
1416
1417 // return
1418 return true;
1419 }
1420
1421
1422 /**
1423 * This function will add a row of data to a field
1424 *
1425 * @since 5.2.3
1426 *
1427 * @param $selector (string)
1428 * @param $row (array)
1429 * @param $post_id (mixed)
1430 * @return (boolean)
1431 */
1432 function delete_sub_row( $selector, $i = 1, $post_id = false ) {
1433
1434 // vars
1435 $sub_field = false;
1436 $offset = acf_get_setting( 'row_index_offset' );
1437 $i = $i - $offset;
1438
1439 // get sub field
1440 if ( is_array( $selector ) ) {
1441 $post_id = acf_get_valid_post_id( $post_id );
1442 $sub_field = acf_maybe_get_sub_field( $selector, $post_id, false );
1443 } else {
1444 $post_id = acf_get_loop( 'active', 'post_id' );
1445 $sub_field = get_row_sub_field( $selector );
1446 }
1447
1448 // bail early if no sub field
1449 if ( ! $sub_field ) {
1450 return false;
1451 }
1452
1453 // get raw value
1454 $value = acf_get_value( $post_id, $sub_field );
1455
1456 // ensure array
1457 $value = acf_get_array( $value );
1458
1459 // bail early if index doesn't exist
1460 if ( ! isset( $value[ $i ] ) ) {
1461 return false;
1462 }
1463
1464 // append
1465 unset( $value[ $i ] );
1466
1467 // update
1468 acf_update_value( $value, $post_id, $sub_field );
1469
1470 // return
1471 return true;
1472 }
1473
1474
1475 /**
1476 * Depreceated Functions
1477 *
1478 * These functions are outdated
1479 *
1480 * @since 1.0.0
1481 *
1482 * @param n/a
1483 * @return n/a
1484 */
1485 function create_field( $field ) {
1486
1487 acf_render_field( $field );
1488 }
1489
1490 function render_field( $field ) {
1491
1492 acf_render_field( $field );
1493 }
1494
1495 function reset_the_repeater_field() {
1496
1497 return reset_rows();
1498 }
1499
1500 function the_repeater_field( $field_name, $post_id = false ) {
1501
1502 return has_sub_field( $field_name, $post_id );
1503 }
1504
1505 function the_flexible_field( $field_name, $post_id = false ) {
1506
1507 return has_sub_field( $field_name, $post_id );
1508 }
1509
1510 function acf_filter_post_id( $post_id ) {
1511
1512 return acf_get_valid_post_id( $post_id );
1513 }
1514