PluginProbe ʕ •ᴥ•ʔ
Advanced Custom Fields (ACF®) / 6.8.8
Advanced Custom Fields (ACF®) v6.8.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 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 / api / api-helpers.php
advanced-custom-fields / includes / api Last commit date
api-helpers.php 2 weeks ago api-template.php 3 months ago api-term.php 6 months ago index.php 2 years ago
api-helpers.php
4470 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 /**
13 * This function will return true for a non empty array
14 *
15 * @since 5.4.0
16 *
17 * @param mixed $array The variable to test.
18 * @return boolean
19 */
20 function acf_is_array( $array ) {
21 return ( is_array( $array ) && ! empty( $array ) );
22 }
23
24 /**
25 * Alias of acf()->has_setting()
26 *
27 * @since 5.6.5
28 *
29 * @param string $name Name of the setting to check for.
30 * @return boolean
31 */
32 function acf_has_setting( $name = '' ) {
33 return acf()->has_setting( $name );
34 }
35
36 /**
37 * acf_raw_setting
38 *
39 * alias of acf()->get_setting()
40 *
41 * @since 5.6.5
42 *
43 * @param n/a
44 * @return n/a
45 */
46 function acf_raw_setting( $name = '' ) {
47 return acf()->get_setting( $name );
48 }
49
50 /**
51 * acf_update_setting
52 *
53 * alias of acf()->update_setting()
54 *
55 * @since 5.0.0
56 *
57 * @param $name (string)
58 * @param $value (mixed)
59 * @return n/a
60 */
61 function acf_update_setting( $name, $value ) {
62 // validate name.
63 $name = acf_validate_setting( $name );
64
65 // update.
66 return acf()->update_setting( $name, $value );
67 }
68
69 /**
70 * acf_validate_setting
71 *
72 * Returns the changed setting name if available.
73 *
74 * @since 5.6.5
75 *
76 * @param n/a
77 * @return n/a
78 */
79 function acf_validate_setting( $name = '' ) {
80 return apply_filters( 'acf/validate_setting', $name );
81 }
82
83 /**
84 * Alias of acf()->get_setting()
85 *
86 * @since 5.0.0
87 *
88 * @param string $name The name of the setting to test.
89 * @param string $value An optional default value for the setting if it doesn't exist.
90 * @return n/a
91 */
92 function acf_get_setting( $name, $value = null ) {
93 $name = acf_validate_setting( $name );
94
95 // replace default setting value if it exists.
96 if ( acf_has_setting( $name ) ) {
97 $value = acf_raw_setting( $name );
98 }
99
100 // filter.
101 $value = apply_filters( "acf/settings/{$name}", $value );
102
103 return $value;
104 }
105
106 /**
107 * Return an array of ACF's internal post type names
108 *
109 * @since 6.1
110 * @return array An array of ACF's internal post type names
111 */
112 function acf_get_internal_post_types() {
113 return array( 'acf-field-group', 'acf-post-type', 'acf-taxonomy', 'acf-ui-options-page' );
114 }
115
116 /**
117 * acf_append_setting
118 *
119 * This function will add a value into the settings array found in the acf object
120 *
121 * @since 5.0.0
122 *
123 * @param $name (string)
124 * @param $value (mixed)
125 * @return n/a
126 */
127 function acf_append_setting( $name, $value ) {
128
129 // vars
130 $setting = acf_raw_setting( $name );
131
132 // bail early if not array
133 if ( ! is_array( $setting ) ) {
134 $setting = array();
135 }
136
137 // append
138 $setting[] = $value;
139
140 // update
141 return acf_update_setting( $name, $setting );
142 }
143
144 /**
145 * acf_get_data
146 *
147 * Returns data.
148 *
149 * @since 5.0.0
150 *
151 * @param string $name
152 * @return mixed
153 */
154 function acf_get_data( $name ) {
155 return acf()->get_data( $name );
156 }
157
158 /**
159 * acf_set_data
160 *
161 * Sets data.
162 *
163 * @since 5.0.0
164 *
165 * @param string $name
166 * @param mixed $value
167 * @return n/a
168 */
169 function acf_set_data( $name, $value ) {
170 return acf()->set_data( $name, $value );
171 }
172
173 /**
174 * Appends data to an existing key.
175 *
176 * @since 5.9.0
177 *
178 * @param string $name The data name.
179 * @param mixed $data The data to append to name.
180 */
181 function acf_append_data( $name, $data ) {
182 $prev_data = acf()->get_data( $name );
183 if ( is_array( $prev_data ) ) {
184 $data = array_merge( $prev_data, $data );
185 }
186 acf()->set_data( $name, $data );
187 }
188
189 /**
190 * Alias of acf()->init() - the core ACF init function.
191 *
192 * @since 5.0.0
193 */
194 function acf_init() {
195 acf()->init();
196 }
197
198 /**
199 * acf_has_done
200 *
201 * This function will return true if this action has already been done
202 *
203 * @since 5.3.2
204 *
205 * @param $name (string)
206 * @return (boolean)
207 */
208 function acf_has_done( $name ) {
209
210 // return true if already done
211 if ( acf_raw_setting( "has_done_{$name}" ) ) {
212 return true;
213 }
214
215 // update setting and return
216 acf_update_setting( "has_done_{$name}", true );
217 return false;
218 }
219
220 /**
221 * This function will return the path to a file within an external folder
222 *
223 * @since 5.5.8
224 *
225 * @param string $file Directory path.
226 * @param string $path Optional file path.
227 * @return string File path.
228 */
229 function acf_get_external_path( $file, $path = '' ) {
230 return plugin_dir_path( $file ) . $path;
231 }
232
233 /**
234 * This function will return the url to a file within an internal ACF folder
235 *
236 * @since 5.5.8
237 *
238 * @param string $file Directory path.
239 * @param string $path Optional file path.
240 * @return string File path.
241 */
242 function acf_get_external_dir( $file, $path = '' ) {
243 return acf_plugin_dir_url( $file ) . $path;
244 }
245
246 /**
247 * This function will calculate the url to a plugin folder.
248 * Different to the WP plugin_dir_url(), this function can calculate for urls outside of the plugins folder (theme include).
249 *
250 * @since 5.6.8
251 *
252 * @param string $file A file path inside the ACF plugin to get the plugin directory path from.
253 * @return string The plugin directory path.
254 */
255 function acf_plugin_dir_url( $file ) {
256 $path = plugin_dir_path( $file );
257 $path = wp_normalize_path( $path );
258
259 // check plugins.
260 $check_path = wp_normalize_path( realpath( WP_PLUGIN_DIR ) );
261 if ( strpos( $path, $check_path ) === 0 ) {
262 return str_replace( $check_path, plugins_url(), $path );
263 }
264
265 // check wp-content.
266 $check_path = wp_normalize_path( realpath( WP_CONTENT_DIR ) );
267 if ( strpos( $path, $check_path ) === 0 ) {
268 return str_replace( $check_path, content_url(), $path );
269 }
270
271 // check root.
272 $check_path = wp_normalize_path( realpath( ABSPATH ) );
273 if ( strpos( $path, $check_path ) === 0 ) {
274 return str_replace( $check_path, site_url( '/' ), $path );
275 }
276
277 // return.
278 return plugin_dir_url( $file );
279 }
280
281 /**
282 * This function will merge together 2 arrays and also convert any numeric values to ints
283 *
284 * @since 5.0.0
285 *
286 * @param array $args The configured arguments array.
287 * @param array $defaults The default properties for the passed args to inherit.
288 * @return array $args Parsed arguments with defaults applied.
289 */
290 function acf_parse_args( $args, $defaults = array() ) {
291 $args = wp_parse_args( $args, $defaults );
292
293 // parse types
294 $args = acf_parse_types( $args );
295
296 return $args;
297 }
298
299 /**
300 * acf_parse_types
301 *
302 * This function will convert any numeric values to int and trim strings
303 *
304 * @since 5.0.0
305 *
306 * @param $var (mixed)
307 * @return $var (mixed)
308 */
309 function acf_parse_types( $array ) {
310 return array_map( 'acf_parse_type', $array );
311 }
312
313 /**
314 * acf_parse_type
315 *
316 * description
317 *
318 * @since 5.0.9
319 *
320 * @param $post_id (int)
321 * @return $post_id (int)
322 */
323 function acf_parse_type( $v ) {
324
325 // Check if is string.
326 if ( is_string( $v ) ) {
327
328 // Trim ("Word " = "Word").
329 $v = trim( $v );
330
331 // Convert int strings to int ("123" = 123).
332 if ( is_numeric( $v ) && strval( intval( $v ) ) === $v ) {
333 $v = intval( $v );
334 }
335 }
336
337 // return.
338 return $v;
339 }
340
341 /**
342 * This function will load in a file from the 'admin/views' folder and allow variables to be passed through
343 *
344 * @since 5.0.0
345 *
346 * @param string $view_path
347 * @param array $view_args
348 */
349 function acf_get_view( $view_path = '', $view_args = array() ) {
350 // allow view file name shortcut
351 if ( substr( $view_path, -4 ) !== '.php' ) {
352 $view_path = acf_get_path( "includes/admin/views/{$view_path}.php" );
353 }
354
355 // include
356 if ( file_exists( $view_path ) ) {
357 // Use `EXTR_SKIP` here to prevent `$view_path` from being accidentally/maliciously overridden.
358 extract( $view_args, EXTR_SKIP );
359 include $view_path;
360 }
361 }
362
363 /**
364 * acf_merge_atts
365 *
366 * description
367 *
368 * @since 5.0.9
369 *
370 * @param $post_id (int)
371 * @return $post_id (int)
372 */
373 function acf_merge_atts( $atts, $extra = array() ) {
374
375 // bail early if no $extra
376 if ( empty( $extra ) ) {
377 return $atts;
378 }
379
380 // trim
381 $extra = array_map( 'trim', $extra );
382 $extra = array_filter( $extra );
383
384 // merge in new atts
385 foreach ( $extra as $k => $v ) {
386
387 // append
388 if ( $k == 'class' || $k == 'style' ) {
389 $atts[ $k ] .= ' ' . $v;
390
391 // merge
392 } else {
393 $atts[ $k ] = $v;
394 }
395 }
396
397 return $atts;
398 }
399
400 /**
401 * This function will create and echo a basic nonce input
402 *
403 * @since 5.6.0
404 *
405 * @param string $nonce The nonce parameter string.
406 */
407 function acf_nonce_input( $nonce = '' ) {
408 echo '<input type="hidden" name="_acf_nonce" value="' . esc_attr( wp_create_nonce( $nonce ) ) . '" />';
409 }
410
411 /**
412 * This function will remove the var from the array, and return the var
413 *
414 * @since 5.0.0
415 *
416 * @param array $extract_array an array passed as reference to be extracted.
417 * @param string $key The key to extract from the array.
418 * @param mixed $default_value The default value if it doesn't exist in the extract array.
419 * @return mixed Extracted var or default.
420 */
421 function acf_extract_var( &$extract_array, $key, $default_value = null ) {
422 // check if exists - uses array_key_exists to extract NULL values (isset will fail).
423 if ( is_array( $extract_array ) && array_key_exists( $key, $extract_array ) ) {
424
425 // store and unset value.
426 $v = $extract_array[ $key ];
427 unset( $extract_array[ $key ] );
428
429 return $v;
430 }
431
432 return $default_value;
433 }
434
435 /**
436 * This function will remove the vars from the array, and return the vars
437 *
438 * @since 5.0.0
439 *
440 * @param array $extract_array an array passed as reference to be extracted.
441 * @param array $keys An array of keys to extract from the original array.
442 * @return array An array of extracted values.
443 */
444 function acf_extract_vars( &$extract_array, $keys ) {
445 $r = array();
446
447 foreach ( $keys as $key ) {
448 $r[ $key ] = acf_extract_var( $extract_array, $key );
449 }
450
451 return $r;
452 }
453
454 /**
455 * acf_get_sub_array
456 *
457 * This function will return a sub array of data
458 *
459 * @since 5.3.2
460 *
461 * @param $post_id (int)
462 * @return $post_id (int)
463 */
464 function acf_get_sub_array( $array, $keys ) {
465
466 $r = array();
467
468 foreach ( $keys as $key ) {
469 $r[ $key ] = $array[ $key ];
470 }
471
472 return $r;
473 }
474
475 /**
476 * Returns an array of post type names.
477 *
478 * @since 5.0.0
479 *
480 * @param array $args Optional. An array of key => value arguments to match against the post type objects. Default empty array.
481 * @return array A list of post type names.
482 */
483 function acf_get_post_types( $args = array() ) {
484 $post_types = array();
485
486 // extract special arg
487 $exclude = acf_extract_var( $args, 'exclude', array() );
488 $exclude[] = 'acf-field';
489 $exclude[] = 'acf-field-group';
490 $exclude[] = 'acf-post-type';
491 $exclude[] = 'acf-taxonomy';
492 $exclude[] = 'acf-ui-options-page';
493
494 // Get post type objects.
495 $objects = get_post_types( $args, 'objects' );
496
497 foreach ( $objects as $i => $object ) {
498 // Bail early if is exclude.
499 if ( in_array( $i, $exclude ) ) {
500 continue;
501 }
502
503 // Bail early if is builtin (WP) private post type
504 // i.e. nav_menu_item, revision, customize_changeset, etc.
505 if ( $object->_builtin && ! $object->public ) {
506 continue;
507 }
508
509 $post_types[] = $i;
510 }
511
512 return apply_filters( 'acf/get_post_types', $post_types, $args );
513 }
514
515 function acf_get_pretty_post_types( $post_types = array() ) {
516
517 // get post types
518 if ( empty( $post_types ) ) {
519
520 // get all custom post types
521 $post_types = acf_get_post_types();
522 }
523
524 // get labels
525 $ref = array();
526 $r = array();
527
528 foreach ( $post_types as $post_type ) {
529
530 // vars
531 $label = acf_get_post_type_label( $post_type );
532
533 // append to r
534 $r[ $post_type ] = $label;
535
536 // increase counter
537 if ( ! isset( $ref[ $label ] ) ) {
538 $ref[ $label ] = 0;
539 }
540
541 ++$ref[ $label ];
542 }
543
544 // get slugs
545 foreach ( array_keys( $r ) as $i ) {
546
547 // vars
548 $post_type = $r[ $i ];
549
550 if ( $ref[ $post_type ] > 1 ) {
551 $r[ $i ] .= ' (' . $i . ')';
552 }
553 }
554
555 // return
556 return $r;
557 }
558
559 /**
560 * Function acf_get_post_stati()
561 *
562 * Returns an array of post status names.
563 *
564 * @since 6.1.0
565 *
566 * @param array $args Optional. An array of key => value arguments to match against the post status objects. Default empty array.
567 * @return array A list of post status names.
568 */
569 function acf_get_post_stati( $args = array() ) {
570
571 $args['internal'] = false;
572
573 $post_statuses = get_post_stati( $args );
574
575 unset( $post_statuses['acf-disabled'] );
576
577 $post_statuses = (array) apply_filters( 'acf/get_post_stati', $post_statuses, $args );
578
579 return $post_statuses;
580 }
581 /**
582 * Function acf_get_pretty_post_statuses()
583 *
584 * Returns a clean array of post status names.
585 *
586 * @since 6.1.0
587 *
588 * @param array $post_statuses Optional. An array of post status objects. Default empty array.
589 * @return array An array of post status names.
590 */
591 function acf_get_pretty_post_statuses( $post_statuses = array() ) {
592
593 // Get all post statuses.
594 $post_statuses = array_merge( $post_statuses, acf_get_post_stati() );
595
596 $ref = array();
597 $result = array();
598
599 foreach ( $post_statuses as $post_status ) {
600 $label = acf_get_post_status_label( $post_status );
601
602 $result[ $post_status ] = $label;
603
604 if ( ! isset( $ref[ $label ] ) ) {
605 $ref[ $label ] = 0;
606 }
607
608 ++$ref[ $label ];
609 }
610
611 foreach ( array_keys( $result ) as $i ) {
612 $post_status = $result[ $i ];
613
614 if ( $ref[ $post_status ] > 1 ) {
615 $result[ $i ] .= ' (' . $i . ')';
616 }
617 }
618
619 return $result;
620 }
621
622 /**
623 * acf_get_post_type_label
624 *
625 * This function will return a pretty label for a specific post_type
626 *
627 * @since 5.4.0
628 *
629 * @param $post_type (string)
630 * @return (string)
631 */
632 function acf_get_post_type_label( $post_type ) {
633
634 // vars
635 $label = $post_type;
636
637 // check that object exists
638 // - case exists when importing field group from another install and post type does not exist
639 if ( post_type_exists( $post_type ) ) {
640 $obj = get_post_type_object( $post_type );
641 $label = $obj->labels->singular_name;
642 }
643
644 // return
645 return $label;
646 }
647
648 /**
649 * Function acf_get_post_status_label()
650 *
651 * This function will return a pretty label for a specific post_status
652 *
653 * @since 6.1.0
654 *
655 * @param string $post_status The post status.
656 * @return string The post status label.
657 */
658 function acf_get_post_status_label( $post_status ) {
659 $label = $post_status;
660 $obj = get_post_status_object( $post_status );
661 $label = is_object( $obj ) ? $obj->label : '';
662
663 return $label;
664 }
665
666 /**
667 * acf_verify_nonce
668 *
669 * This function will look at the $_POST['_acf_nonce'] value and return true or false
670 *
671 * @since 5.0.0
672 *
673 * @param $nonce (string)
674 * @return (boolean)
675 */
676 function acf_verify_nonce( $value ) {
677
678 // vars
679 $nonce = acf_maybe_get_POST( '_acf_nonce' );
680
681 // bail early nonce does not match (post|user|comment|term)
682 if ( ! $nonce || ! wp_verify_nonce( $nonce, $value ) ) {
683 return false;
684 }
685
686 // reset nonce (only allow 1 save)
687 $_POST['_acf_nonce'] = false;
688
689 // return
690 return true;
691 }
692
693 /**
694 * Returns true if the current AJAX request is valid.
695 * It's action will also allow WPML to set the lang and avoid AJAX get_posts issues
696 *
697 * @since 5.2.3
698 *
699 * @param string $nonce The nonce to check.
700 * @param string $action The action of the nonce.
701 * @param boolean $action_is_field If the action is a field, modify the action to match validate the field type.
702 * @param string $expected_field_type Optional field type the resolved field must be when $action_is_field is true. Prevents a nonce minted for one field type from being accepted by an AJAX handler that expects a different one. Defaults to empty (no type validation).
703 * @return boolean
704 */
705 function acf_verify_ajax( $nonce = '', $action = '', $action_is_field = false, $expected_field_type = '' ) {
706 // Bail early if we don't have a nonce to check.
707 if ( empty( $nonce ) && empty( $_REQUEST['nonce'] ) ) {
708 return false;
709 }
710
711 // Build the action if we're trying to validate a specific field nonce.
712 if ( $action_is_field ) {
713 if ( ! acf_is_field_key( $action ) ) {
714 return false;
715 }
716
717 $field = acf_get_field( $action );
718
719 if ( empty( $field['type'] ) ) {
720 return false;
721 }
722
723 if ( ! empty( $expected_field_type ) && $field['type'] !== $expected_field_type ) {
724 return false;
725 }
726
727 $action = 'acf_field_' . $field['type'] . '_' . $action;
728 }
729
730 $nonce_to_check = ! empty( $nonce ) ? $nonce : $_REQUEST['nonce']; // phpcs:ignore WordPress.Security -- We're verifying a nonce here.
731 $nonce_action = ! empty( $action ) ? $action : 'acf_nonce';
732
733 // Bail if nonce can't be verified.
734 if ( ! wp_verify_nonce( sanitize_text_field( $nonce_to_check ), $nonce_action ) ) {
735 return false;
736 }
737
738 // Action for 3rd party customization (WPML).
739 do_action( 'acf/verify_ajax' );
740
741 return true;
742 }
743
744 /**
745 * acf_get_image_sizes
746 *
747 * This function will return an array of available image sizes
748 *
749 * @since 5.0.0
750 *
751 * @param n/a
752 * @return (array)
753 */
754 function acf_get_image_sizes() {
755
756 // vars
757 $sizes = array(
758 'thumbnail' => __( 'Thumbnail', 'acf' ),
759 'medium' => __( 'Medium', 'acf' ),
760 'large' => __( 'Large', 'acf' ),
761 );
762
763 // find all sizes
764 $all_sizes = get_intermediate_image_sizes();
765
766 // add extra registered sizes
767 if ( ! empty( $all_sizes ) ) {
768 foreach ( $all_sizes as $size ) {
769
770 // bail early if already in array
771 if ( isset( $sizes[ $size ] ) ) {
772 continue;
773 }
774
775 // append to array
776 $label = str_replace( '-', ' ', $size );
777 $label = ucwords( $label );
778 $sizes[ $size ] = $label;
779 }
780 }
781
782 // add sizes
783 foreach ( array_keys( $sizes ) as $s ) {
784
785 // vars
786 $data = acf_get_image_size( $s );
787
788 // append
789 if ( $data['width'] && $data['height'] ) {
790 $sizes[ $s ] .= ' (' . $data['width'] . ' x ' . $data['height'] . ')';
791 }
792 }
793
794 // add full end
795 $sizes['full'] = __( 'Full Size', 'acf' );
796
797 // filter for 3rd party customization
798 $sizes = apply_filters( 'acf/get_image_sizes', $sizes );
799
800 // return
801 return $sizes;
802 }
803
804 function acf_get_image_size( $s = '' ) {
805
806 // global
807 global $_wp_additional_image_sizes;
808
809 // rename for nicer code
810 $_sizes = $_wp_additional_image_sizes;
811
812 // vars
813 $data = array(
814 'width' => isset( $_sizes[ $s ]['width'] ) ? $_sizes[ $s ]['width'] : get_option( "{$s}_size_w" ),
815 'height' => isset( $_sizes[ $s ]['height'] ) ? $_sizes[ $s ]['height'] : get_option( "{$s}_size_h" ),
816 );
817
818 // return
819 return $data;
820 }
821
822 /**
823 * acf_version_compare
824 *
825 * Similar to the version_compare() function but with extra functionality.
826 *
827 * @since 5.5.0
828 *
829 * @param string $left The left version number.
830 * @param string $compare The compare operator.
831 * @param string $right The right version number.
832 * @return boolean
833 */
834 function acf_version_compare( $left = '', $compare = '>', $right = '' ) {
835
836 // Detect 'wp' placeholder.
837 if ( $left === 'wp' ) {
838 global $wp_version;
839 $left = $wp_version;
840 }
841
842 // Return result.
843 return version_compare( $left, $right, $compare );
844 }
845
846 /**
847 * acf_get_full_version
848 *
849 * This function will remove any '-beta1' or '-RC1' strings from a version
850 *
851 * @since 5.5.0
852 *
853 * @param $version (string)
854 * @return (string)
855 */
856 function acf_get_full_version( $version = '1' ) {
857
858 // remove '-beta1' or '-RC1'
859 if ( $pos = strpos( $version, '-' ) ) {
860 $version = substr( $version, 0, $pos );
861 }
862
863 // return
864 return $version;
865 }
866
867 /**
868 * acf_get_terms
869 *
870 * This function is a wrapper for the get_terms() function
871 *
872 * @since 5.4.0
873 *
874 * @param $args (array)
875 * @return (array)
876 */
877 function acf_get_terms( $args ) {
878
879 // defaults
880 $args = wp_parse_args(
881 $args,
882 array(
883 'taxonomy' => null,
884 'hide_empty' => false,
885 'update_term_meta_cache' => false,
886 )
887 );
888
889 // return
890 return get_terms( $args );
891 }
892
893 /**
894 * acf_get_taxonomy_terms
895 *
896 * This function will return an array of available taxonomy terms
897 *
898 * @since 5.0.0
899 *
900 * @param $taxonomies (array)
901 * @return (array)
902 */
903 function acf_get_taxonomy_terms( $taxonomies = array() ) {
904
905 // force array
906 $taxonomies = acf_get_array( $taxonomies );
907
908 // get pretty taxonomy names
909 $taxonomies = acf_get_pretty_taxonomies( $taxonomies );
910
911 // vars
912 $r = array();
913
914 // populate $r
915 foreach ( array_keys( $taxonomies ) as $taxonomy ) {
916
917 // vars
918 $label = $taxonomies[ $taxonomy ];
919 $is_hierarchical = is_taxonomy_hierarchical( $taxonomy );
920 $terms = acf_get_terms(
921 array(
922 'taxonomy' => $taxonomy,
923 'hide_empty' => false,
924 )
925 );
926
927 // bail early i no terms
928 if ( empty( $terms ) ) {
929 continue;
930 }
931
932 // sort into hierachial order!
933 if ( $is_hierarchical ) {
934 $terms = _get_term_children( 0, $terms, $taxonomy );
935 }
936
937 // add placeholder
938 $r[ $label ] = array();
939
940 // add choices
941 foreach ( $terms as $term ) {
942 $k = "{$taxonomy}:{$term->slug}";
943 $r[ $label ][ $k ] = acf_get_term_title( $term );
944 }
945 }
946
947 // return
948 return $r;
949 }
950
951 /**
952 * acf_decode_taxonomy_terms
953 *
954 * This function decodes the $taxonomy:$term strings into a nested array
955 *
956 * @since 5.0.0
957 *
958 * @param $terms (array)
959 * @return (array)
960 */
961 function acf_decode_taxonomy_terms( $strings = false ) {
962
963 // bail early if no terms
964 if ( empty( $strings ) ) {
965 return false;
966 }
967
968 // vars
969 $terms = array();
970
971 // loop
972 foreach ( $strings as $string ) {
973
974 // vars
975 $data = acf_decode_taxonomy_term( $string );
976 $taxonomy = $data['taxonomy'];
977 $term = $data['term'];
978
979 // create empty array
980 if ( ! isset( $terms[ $taxonomy ] ) ) {
981 $terms[ $taxonomy ] = array();
982 }
983
984 // append
985 $terms[ $taxonomy ][] = $term;
986 }
987
988 // return
989 return $terms;
990 }
991
992 /**
993 * acf_decode_taxonomy_term
994 *
995 * This function will return the taxonomy and term slug for a given value
996 *
997 * @since 5.0.0
998 *
999 * @param $string (string)
1000 * @return (array)
1001 */
1002 function acf_decode_taxonomy_term( $value ) {
1003
1004 // vars
1005 $data = array(
1006 'taxonomy' => '',
1007 'term' => '',
1008 );
1009
1010 // int
1011 if ( is_numeric( $value ) ) {
1012 $data['term'] = $value;
1013
1014 // string
1015 } elseif ( is_string( $value ) ) {
1016 $value = explode( ':', $value );
1017 $data['taxonomy'] = isset( $value[0] ) ? $value[0] : '';
1018 $data['term'] = isset( $value[1] ) ? $value[1] : '';
1019
1020 // error
1021 } else {
1022 return false;
1023 }
1024
1025 // allow for term_id (Used by ACF v4)
1026 if ( is_numeric( $data['term'] ) ) {
1027
1028 // global
1029 global $wpdb;
1030
1031 // find taxonomy
1032 if ( ! $data['taxonomy'] ) {
1033 $data['taxonomy'] = $wpdb->get_var( $wpdb->prepare( "SELECT taxonomy FROM $wpdb->term_taxonomy WHERE term_id = %d LIMIT 1", $data['term'] ) );
1034 }
1035
1036 // find term (may have numeric slug '123')
1037 $term = get_term_by( 'slug', $data['term'], $data['taxonomy'] );
1038
1039 // attempt get term via ID (ACF4 uses ID)
1040 if ( ! $term ) {
1041 $term = get_term( $data['term'], $data['taxonomy'] );
1042 }
1043
1044 // bail early if no term
1045 if ( ! $term ) {
1046 return false;
1047 }
1048
1049 // update
1050 $data['taxonomy'] = $term->taxonomy;
1051 $data['term'] = $term->slug;
1052 }
1053
1054 // return
1055 return $data;
1056 }
1057
1058 /**
1059 * acf_array
1060 *
1061 * Casts the value into an array.
1062 *
1063 * @since 5.7.10
1064 *
1065 * @param mixed $val The value to cast.
1066 * @return array
1067 */
1068 function acf_array( $val = array() ) {
1069 return (array) $val;
1070 }
1071
1072 /**
1073 * Returns a non-array value.
1074 *
1075 * @since 5.8.10
1076 *
1077 * @param mixed $val The value to review.
1078 * @return mixed
1079 */
1080 function acf_unarray( $val ) {
1081 if ( is_array( $val ) ) {
1082 return reset( $val );
1083 }
1084 return $val;
1085 }
1086
1087 /**
1088 * acf_get_array
1089 *
1090 * This function will force a variable to become an array
1091 *
1092 * @since 5.0.0
1093 *
1094 * @param $var (mixed)
1095 * @return (array)
1096 */
1097 function acf_get_array( $var = false, $delimiter = '' ) {
1098
1099 // array
1100 if ( is_array( $var ) ) {
1101 return $var;
1102 }
1103
1104 // bail early if empty
1105 if ( acf_is_empty( $var ) ) {
1106 return array();
1107 }
1108
1109 // string
1110 if ( is_string( $var ) && $delimiter ) {
1111 return explode( $delimiter, $var );
1112 }
1113
1114 // place in array
1115 return (array) $var;
1116 }
1117
1118 /**
1119 * acf_get_numeric
1120 *
1121 * This function will return numeric values
1122 *
1123 * @since 5.4.0
1124 *
1125 * @param $value (mixed)
1126 * @return (mixed)
1127 */
1128 function acf_get_numeric( $value = '' ) {
1129
1130 // vars
1131 $numbers = array();
1132 $is_array = is_array( $value );
1133
1134 // loop
1135 foreach ( (array) $value as $v ) {
1136 if ( is_numeric( $v ) ) {
1137 $numbers[] = (int) $v;
1138 }
1139 }
1140
1141 // bail early if is empty
1142 if ( empty( $numbers ) ) {
1143 return false;
1144 }
1145
1146 // convert array
1147 if ( ! $is_array ) {
1148 $numbers = $numbers[0];
1149 }
1150
1151 // return
1152 return $numbers;
1153 }
1154
1155 /**
1156 * acf_get_posts
1157 *
1158 * Similar to the get_posts() function but with extra functionality.
1159 *
1160 * @since 5.1.5
1161 *
1162 * @param array $args The query args.
1163 * @return array
1164 */
1165 function acf_get_posts( $args = array() ) {
1166
1167 // Vars.
1168 $posts = array();
1169
1170 // Apply default args.
1171 $args = wp_parse_args(
1172 $args,
1173 array(
1174 'posts_per_page' => -1,
1175 'post_type' => '',
1176 'post_status' => 'any',
1177 'update_post_meta_cache' => false,
1178 'update_post_term_cache' => false,
1179 )
1180 );
1181
1182 // Avoid default 'post' post_type by providing all public types.
1183 if ( ! $args['post_type'] ) {
1184 $args['post_type'] = acf_get_post_types();
1185 }
1186
1187 if ( ! $args['post_status'] ) {
1188 $args['post_status'] = acf_get_post_stati();
1189 }
1190
1191 // Check if specific post IDs have been provided.
1192 if ( $args['post__in'] ) {
1193
1194 // Clean value into an array of IDs.
1195 $args['post__in'] = array_map( 'intval', acf_array( $args['post__in'] ) );
1196 }
1197
1198 /**
1199 * Filters the args used in `acf_get_posts()` that are passed to `get_posts()`.
1200 *
1201 * @since 6.1.7
1202 *
1203 * @param array $args The args passed to `get_posts()`.
1204 */
1205 $args = apply_filters( 'acf/acf_get_posts/args', $args );
1206
1207 // Query posts.
1208 $posts = get_posts( $args );
1209
1210 // Remove any potential empty results.
1211 $posts = array_filter( $posts );
1212
1213 // Manually order results.
1214 if ( $posts && $args['post__in'] ) {
1215 $order = array();
1216 foreach ( $posts as $i => $post ) {
1217 $order[ $i ] = array_search( $post->ID, $args['post__in'] );
1218 }
1219 array_multisort( $order, $posts );
1220 }
1221
1222 /**
1223 * Filters the results found in the `acf_get_posts()` function.
1224 *
1225 * @since 6.1.7
1226 *
1227 * @param array $posts The results from the `get_posts()` call.
1228 */
1229 return apply_filters( 'acf/acf_get_posts/results', $posts );
1230 }
1231
1232 /**
1233 * _acf_query_remove_post_type
1234 *
1235 * This function will remove the 'wp_posts.post_type' WHERE clause completely
1236 * When using 'post__in', this clause is unneccessary and slow.
1237 *
1238 * @since 5.1.5
1239 *
1240 * @param $sql (string)
1241 * @return $sql
1242 */
1243 function _acf_query_remove_post_type( $sql ) {
1244
1245 // global
1246 global $wpdb;
1247
1248 // bail early if no 'wp_posts.ID IN'
1249 if ( strpos( $sql, "$wpdb->posts.ID IN" ) === false ) {
1250 return $sql;
1251 }
1252
1253 // get bits
1254 $glue = 'AND';
1255 $bits = explode( $glue, $sql );
1256
1257 // loop through $where and remove any post_type queries
1258 foreach ( $bits as $i => $bit ) {
1259 if ( strpos( $bit, "$wpdb->posts.post_type" ) !== false ) {
1260 unset( $bits[ $i ] );
1261 }
1262 }
1263
1264 // join $where back together
1265 $sql = implode( $glue, $bits );
1266
1267 // return
1268 return $sql;
1269 }
1270
1271 /**
1272 * acf_get_grouped_posts
1273 *
1274 * This function will return all posts grouped by post_type
1275 * This is handy for select settings
1276 *
1277 * @since 5.0.0
1278 *
1279 * @param array $args The query arguments.
1280 * @return array
1281 */
1282 function acf_get_grouped_posts( $args ) {
1283
1284 // vars
1285 $data = array();
1286
1287 // defaults
1288 $args = wp_parse_args(
1289 $args,
1290 array(
1291 'posts_per_page' => -1,
1292 'paged' => 0,
1293 'post_type' => 'post',
1294 'orderby' => 'menu_order title',
1295 'order' => 'ASC',
1296 'post_status' => 'any',
1297 'suppress_filters' => false,
1298 'update_post_meta_cache' => false,
1299 )
1300 );
1301
1302 // Narrow anonymous callers to publicly-viewable types/statuses.
1303 if ( ! is_user_logged_in() ) {
1304 $requested_post_types = acf_get_array( $args['post_type'] );
1305
1306 if ( in_array( 'any', $requested_post_types, true ) ) {
1307 $requested_post_types = get_post_types();
1308 }
1309
1310 $viewable_post_types = array_values( array_filter( $requested_post_types, 'is_post_type_viewable' ) );
1311
1312 if ( empty( $viewable_post_types ) ) {
1313 return $data;
1314 }
1315
1316 $requested_post_statuses = acf_get_array( $args['post_status'] );
1317 $public_post_statuses = array_values( array_filter( get_post_stati(), 'is_post_status_viewable' ) );
1318
1319 if ( empty( $requested_post_statuses ) ) {
1320 return $data;
1321 }
1322
1323 if ( in_array( 'any', $requested_post_statuses, true ) ) {
1324 $viewable_post_statuses = $public_post_statuses;
1325
1326 if ( in_array( 'attachment', $viewable_post_types, true ) ) {
1327 $viewable_post_statuses[] = 'inherit';
1328 }
1329 } else {
1330 $viewable_post_statuses = array_values( array_intersect( $requested_post_statuses, $public_post_statuses ) );
1331
1332 if ( in_array( 'attachment', $viewable_post_types, true ) && in_array( 'inherit', $requested_post_statuses, true ) ) {
1333 $viewable_post_statuses[] = 'inherit';
1334 }
1335 }
1336
1337 if ( empty( $viewable_post_statuses ) ) {
1338 return $data;
1339 }
1340
1341 $args['post_type'] = $viewable_post_types;
1342 $args['post_status'] = array_values( array_unique( $viewable_post_statuses ) );
1343 }
1344
1345 // find array of post_type
1346 $post_types = acf_get_array( $args['post_type'] );
1347 $is_single_post_type = ( count( $post_types ) === 1 );
1348
1349 // WordPress 6.8+ sorts post_type arrays for cache key generation
1350 // We need to use the same sorted order when processing results
1351 if (
1352 ! $is_single_post_type &&
1353 $args['posts_per_page'] !== -1 &&
1354 version_compare( get_bloginfo( 'version' ), '6.8', '>=' )
1355 ) {
1356 sort( $post_types );
1357 }
1358
1359 $post_types_labels = acf_get_pretty_post_types( $post_types );
1360
1361 // attachment doesn't work if it is the only item in an array
1362 if ( $is_single_post_type ) {
1363 $args['post_type'] = reset( $post_types );
1364 }
1365
1366 // add filter to orderby post type
1367 if ( ! $is_single_post_type ) {
1368 add_filter( 'posts_orderby', '_acf_orderby_post_type', 10, 2 );
1369 }
1370
1371 // get posts
1372 $posts = get_posts( $args );
1373
1374 // remove this filter (only once)
1375 if ( ! $is_single_post_type ) {
1376 remove_filter( 'posts_orderby', '_acf_orderby_post_type', 10, 2 );
1377 }
1378
1379 // loop
1380 foreach ( $post_types as $post_type ) {
1381
1382 // vars
1383 $this_posts = array();
1384 $this_group = array();
1385
1386 // populate $this_posts
1387 foreach ( $posts as $post ) {
1388 if ( $post->post_type == $post_type ) {
1389 $this_posts[] = $post;
1390 }
1391 }
1392
1393 // bail early if no posts for this post type
1394 if ( empty( $this_posts ) ) {
1395 continue;
1396 }
1397
1398 // sort into hierachial order!
1399 // this will fail if a search has taken place because parents wont exist
1400 if ( is_post_type_hierarchical( $post_type ) && empty( $args['s'] ) ) {
1401
1402 // vars
1403 $post_id = $this_posts[0]->ID;
1404 $parent_id = acf_maybe_get( $args, 'post_parent', 0 );
1405 $offset = 0;
1406 $length = count( $this_posts );
1407
1408 // get all posts from this post type
1409 $all_posts = get_posts(
1410 array_merge(
1411 $args,
1412 array(
1413 'posts_per_page' => -1,
1414 'paged' => 0,
1415 'post_type' => $post_type,
1416 )
1417 )
1418 );
1419
1420 // find starting point (offset)
1421 foreach ( $all_posts as $i => $post ) {
1422 if ( $post->ID == $post_id ) {
1423 $offset = $i;
1424 break;
1425 }
1426 }
1427
1428 // order posts
1429 $ordered_posts = get_page_children( $parent_id, $all_posts );
1430
1431 // compare aray lengths
1432 // if $ordered_posts is smaller than $all_posts, WP has lost posts during the get_page_children() function
1433 // this is possible when get_post( $args ) filter out parents (via taxonomy, meta and other search parameters)
1434 if ( count( $ordered_posts ) == count( $all_posts ) ) {
1435 $this_posts = array_slice( $ordered_posts, $offset, $length );
1436 }
1437 }
1438
1439 // populate $this_posts
1440 foreach ( $this_posts as $post ) {
1441 $this_group[ $post->ID ] = $post;
1442 }
1443
1444 // group by post type
1445 $label = $post_types_labels[ $post_type ];
1446 $data[ $label ] = $this_group;
1447 }
1448
1449 // return
1450 return $data;
1451 }
1452
1453 /**
1454 * Normalizes an args array so `perm=readable` actually applies inside `WP_Query`.
1455 *
1456 * WP_Query ignores `perm=readable` when `post_status='any'` (Trac #52094). Callers
1457 * that opt into `perm=readable` therefore need `post_status` enumerated. This helper
1458 * is idempotent and safe to call before and after the per-field `acf/fields/<type>/query`
1459 * filters run — the post-filter call catches callbacks that reset `post_status`
1460 * back to 'any' while leaving `perm` untouched.
1461 *
1462 * @since 6.8.7
1463 *
1464 * @param array $args WP_Query args.
1465 * @return array Args with `post_status` enumerated when `perm=readable` requires it.
1466 */
1467 function acf_ensure_perm_readable_post_status( $args ) {
1468 if ( ! isset( $args['perm'] ) || 'readable' !== $args['perm'] ) {
1469 return $args;
1470 }
1471
1472 $status = isset( $args['post_status'] ) ? $args['post_status'] : '';
1473 $is_any = empty( $status )
1474 || 'any' === $status
1475 || ( is_array( $status ) && in_array( 'any', $status, true ) );
1476
1477 if ( $is_any ) {
1478 $args['post_status'] = get_post_stati( array( 'exclude_from_search' => false ) );
1479 }
1480
1481 return $args;
1482 }
1483
1484 /**
1485 * The internal ACF function to add order by post types for use in `acf_get_grouped_posts`
1486 *
1487 * @param string $orderby The current orderby value for a query.
1488 * @param object $wp_query The WP_Query.
1489 * @return string The potentially modified orderby string.
1490 */
1491 function _acf_orderby_post_type( $orderby, $wp_query ) {
1492 global $wpdb;
1493
1494 $post_types = $wp_query->get( 'post_type' );
1495
1496 // Prepend the SQL.
1497 if ( is_array( $post_types ) ) {
1498 $post_types = array_map( 'esc_sql', $post_types );
1499 $post_types = implode( "','", $post_types );
1500 $orderby = "FIELD({$wpdb->posts}.post_type,'$post_types')," . $orderby;
1501 }
1502
1503 return $orderby;
1504 }
1505
1506 function acf_get_post_title( $post = 0, $is_search = false ) {
1507
1508 // vars
1509 $post = get_post( $post );
1510 $title = '';
1511 $prepend = '';
1512 $append = '';
1513
1514 // bail early if no post
1515 if ( ! $post ) {
1516 return '';
1517 }
1518
1519 // title
1520 $title = get_the_title( $post->ID );
1521
1522 // empty
1523 if ( $title === '' ) {
1524 $title = __( '(no title)', 'acf' );
1525 }
1526
1527 // status
1528 if ( get_post_status( $post->ID ) != 'publish' ) {
1529 $append .= ' (' . get_post_status( $post->ID ) . ')';
1530 }
1531
1532 // ancestors
1533 if ( $post->post_type !== 'attachment' ) {
1534
1535 // get ancestors
1536 $ancestors = get_ancestors( $post->ID, $post->post_type );
1537 $prepend .= str_repeat( '- ', count( $ancestors ) );
1538 }
1539
1540 // merge
1541 $title = $prepend . $title . $append;
1542
1543 // return
1544 return $title;
1545 }
1546
1547 function acf_order_by_search( $array, $search ) {
1548
1549 // vars
1550 $weights = array();
1551 $needle = strtolower( $search );
1552
1553 // add key prefix
1554 foreach ( array_keys( $array ) as $k ) {
1555 $array[ '_' . $k ] = acf_extract_var( $array, $k );
1556 }
1557
1558 // add search weight
1559 foreach ( $array as $k => $v ) {
1560
1561 // vars
1562 $weight = 0;
1563 $haystack = strtolower( $v );
1564 $strpos = strpos( $haystack, $needle );
1565
1566 // detect search match
1567 if ( $strpos !== false ) {
1568
1569 // set eright to length of match
1570 $weight = strlen( $search );
1571
1572 // increase weight if match starts at begining of string
1573 if ( $strpos == 0 ) {
1574 ++$weight;
1575 }
1576 }
1577
1578 // append to wights
1579 $weights[ $k ] = $weight;
1580 }
1581
1582 // sort the array with menu_order ascending
1583 array_multisort( $weights, SORT_DESC, $array );
1584
1585 // remove key prefix
1586 foreach ( array_keys( $array ) as $k ) {
1587 $array[ substr( $k, 1 ) ] = acf_extract_var( $array, $k );
1588 }
1589
1590 // return
1591 return $array;
1592 }
1593
1594 /**
1595 * acf_get_pretty_user_roles
1596 *
1597 * description
1598 *
1599 * @since 5.3.2
1600 *
1601 * @param $post_id (int)
1602 * @return $post_id (int)
1603 */
1604 function acf_get_pretty_user_roles( $allowed = false ) {
1605
1606 // vars
1607 $editable_roles = get_editable_roles();
1608 $allowed = acf_get_array( $allowed );
1609 $roles = array();
1610
1611 // loop
1612 foreach ( $editable_roles as $role_name => $role_details ) {
1613
1614 // bail early if not allowed
1615 if ( ! empty( $allowed ) && ! in_array( $role_name, $allowed ) ) {
1616 continue;
1617 }
1618
1619 // append
1620 $roles[ $role_name ] = translate_user_role( $role_details['name'] );
1621 }
1622
1623 // return
1624 return $roles;
1625 }
1626
1627 /**
1628 * acf_get_grouped_users
1629 *
1630 * This function will return all users grouped by role
1631 * This is handy for select settings
1632 *
1633 * @since 5.0.0
1634 *
1635 * @param $args (array)
1636 * @return (array)
1637 */
1638 function acf_get_grouped_users( $args = array() ) {
1639
1640 // vars
1641 $r = array();
1642
1643 // defaults
1644 $args = wp_parse_args(
1645 $args,
1646 array(
1647 'users_per_page' => -1,
1648 'paged' => 0,
1649 'role' => '',
1650 'orderby' => 'login',
1651 'order' => 'ASC',
1652 )
1653 );
1654
1655 // offset
1656 $i = 0;
1657 $min = 0;
1658 $max = 0;
1659 $users_per_page = acf_extract_var( $args, 'users_per_page' );
1660 $paged = acf_extract_var( $args, 'paged' );
1661
1662 if ( $users_per_page > 0 ) {
1663
1664 // prevent paged from being -1
1665 $paged = max( 0, $paged );
1666
1667 // set min / max
1668 $min = ( ( $paged - 1 ) * $users_per_page ) + 1; // 1, 11
1669 $max = ( $paged * $users_per_page ); // 10, 20
1670
1671 }
1672
1673 // find array of post_type
1674 $user_roles = acf_get_pretty_user_roles( $args['role'] );
1675
1676 // fix role
1677 if ( is_array( $args['role'] ) ) {
1678
1679 // global
1680 global $wp_version, $wpdb;
1681
1682 // vars
1683 $roles = acf_extract_var( $args, 'role' );
1684
1685 // new WP has role__in
1686 if ( version_compare( $wp_version, '4.4', '>=' ) ) {
1687 $args['role__in'] = $roles;
1688
1689 // old WP doesn't have role__in
1690 } else {
1691
1692 // vars
1693 $blog_id = get_current_blog_id();
1694 $meta_query = array( 'relation' => 'OR' );
1695
1696 // loop
1697 foreach ( $roles as $role ) {
1698 $meta_query[] = array(
1699 'key' => $wpdb->get_blog_prefix( $blog_id ) . 'capabilities',
1700 'value' => '"' . $role . '"',
1701 'compare' => 'LIKE',
1702 );
1703 }
1704
1705 // append
1706 $args['meta_query'] = $meta_query;
1707 }
1708 }
1709
1710 // get posts
1711 $users = get_users( $args );
1712
1713 // loop
1714 foreach ( $user_roles as $user_role_name => $user_role_label ) {
1715
1716 // vars
1717 $this_users = array();
1718 $this_group = array();
1719
1720 // populate $this_posts
1721 foreach ( array_keys( $users ) as $key ) {
1722
1723 // bail early if not correct role
1724 if ( ! in_array( $user_role_name, $users[ $key ]->roles ) ) {
1725 continue;
1726 }
1727
1728 // extract user
1729 $user = acf_extract_var( $users, $key );
1730
1731 // increase
1732 ++$i;
1733
1734 // bail early if too low
1735 if ( $min && $i < $min ) {
1736 continue;
1737 }
1738
1739 // bail early if too high (don't bother looking at any more users)
1740 if ( $max && $i > $max ) {
1741 break;
1742 }
1743
1744 // group by post type
1745 $this_users[ $user->ID ] = $user;
1746 }
1747
1748 // bail early if no posts for this post type
1749 if ( empty( $this_users ) ) {
1750 continue;
1751 }
1752
1753 // append
1754 $r[ $user_role_label ] = $this_users;
1755 }
1756
1757 // return
1758 return $r;
1759 }
1760
1761 /**
1762 * acf_json_encode
1763 *
1764 * Returns json_encode() ready for file / database use.
1765 *
1766 * @since 5.0.0
1767 *
1768 * @param array $json The array of data to encode.
1769 * @return string
1770 */
1771 function acf_json_encode( $json ) {
1772 return json_encode( $json, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE );
1773 }
1774
1775 /**
1776 * acf_str_exists
1777 *
1778 * This function will return true if a sub string is found
1779 *
1780 * @since 5.0.0
1781 *
1782 * @param $needle (string)
1783 * @param $haystack (string)
1784 * @return (boolean)
1785 */
1786 function acf_str_exists( $needle, $haystack ) {
1787
1788 // return true if $haystack contains the $needle
1789 if ( is_string( $haystack ) && strpos( $haystack, $needle ) !== false ) {
1790 return true;
1791 }
1792
1793 // return
1794 return false;
1795 }
1796
1797 /**
1798 * A legacy function designed for developer debugging.
1799 *
1800 * @deprecated 6.2.6 Removed for security, but keeping the definition in case third party devs have it in their code.
1801 * @since 5.0.0
1802 *
1803 * @return false
1804 */
1805 function acf_debug() {
1806 _deprecated_function( __FUNCTION__, '6.2.7' );
1807 return false;
1808 }
1809
1810 /**
1811 * A legacy function designed for developer debugging.
1812 *
1813 * @deprecated 6.2.6 Removed for security, but keeping the definition in case third party devs have it in their code.
1814 * @since 5.0.0
1815 *
1816 * @return false
1817 */
1818 function acf_debug_start() {
1819 _deprecated_function( __FUNCTION__, '6.2.7' );
1820 return false;
1821 }
1822
1823 /**
1824 * A legacy function designed for developer debugging.
1825 *
1826 * @deprecated 6.2.6 Removed for security, but keeping the definition in case third party devs have it in their code.
1827 * @since 5.0.0
1828 *
1829 * @return false
1830 */
1831 function acf_debug_end() {
1832 _deprecated_function( __FUNCTION__, '6.2.7' );
1833 return false;
1834 }
1835
1836 /**
1837 * acf_encode_choices
1838 *
1839 * description
1840 *
1841 * @since 5.0.0
1842 *
1843 * @param $post_id (int)
1844 * @return $post_id (int)
1845 */
1846 function acf_encode_choices( $array = array(), $show_keys = true ) {
1847
1848 // bail early if not array (maybe a single string)
1849 if ( ! is_array( $array ) ) {
1850 return $array;
1851 }
1852
1853 // bail early if empty array
1854 if ( empty( $array ) ) {
1855 return '';
1856 }
1857
1858 // vars
1859 $string = '';
1860
1861 // if allowed to show keys (good for choices, not for default values)
1862 if ( $show_keys ) {
1863
1864 // loop
1865 foreach ( $array as $k => $v ) {
1866
1867 // ignore if key and value are the same
1868 if ( strval( $k ) == strval( $v ) ) {
1869 continue;
1870 }
1871
1872 // show key in the value
1873 $array[ $k ] = $k . ' : ' . $v;
1874 }
1875 }
1876
1877 // implode
1878 $string = implode( "\n", $array );
1879
1880 // return
1881 return $string;
1882 }
1883
1884 function acf_decode_choices( $string = '', $array_keys = false ) {
1885
1886 // bail early if already array
1887 if ( is_array( $string ) ) {
1888 return $string;
1889
1890 // allow numeric values (same as string)
1891 } elseif ( is_numeric( $string ) ) {
1892
1893 // do nothing
1894 // bail early if not a string
1895 } elseif ( ! is_string( $string ) ) {
1896 return array();
1897
1898 // bail early if is empty string
1899 } elseif ( $string === '' ) {
1900 return array();
1901 }
1902
1903 // vars
1904 $array = array();
1905
1906 // explode
1907 $lines = explode( "\n", $string );
1908
1909 // key => value
1910 foreach ( $lines as $line ) {
1911
1912 // vars
1913 $k = trim( $line );
1914 $v = trim( $line );
1915
1916 // look for ' : '
1917 if ( acf_str_exists( ' : ', $line ) ) {
1918 $line = explode( ' : ', $line );
1919
1920 $k = trim( $line[0] );
1921 $v = trim( $line[1] );
1922 }
1923
1924 // append
1925 $array[ $k ] = $v;
1926 }
1927
1928 // return only array keys? (good for checkbox default_value)
1929 if ( $array_keys ) {
1930 return array_keys( $array );
1931 }
1932
1933 // return
1934 return $array;
1935 }
1936
1937 /**
1938 * acf_str_replace
1939 *
1940 * This function will replace an array of strings much like str_replace
1941 * The difference is the extra logic to avoid replacing a string that has alread been replaced
1942 * This is very useful for replacing date characters as they overlap with eachother
1943 *
1944 * @since 5.3.8
1945 *
1946 * @param $post_id (int)
1947 * @return $post_id (int)
1948 */
1949 function acf_str_replace( $string = '', $search_replace = array() ) {
1950
1951 // vars
1952 $ignore = array();
1953
1954 // remove potential empty search to avoid PHP error
1955 unset( $search_replace[''] );
1956
1957 // loop over conversions
1958 foreach ( $search_replace as $search => $replace ) {
1959
1960 // ignore this search, it was a previous replace
1961 if ( in_array( $search, $ignore ) ) {
1962 continue;
1963 }
1964
1965 // bail early if subsctring not found
1966 if ( strpos( $string, $search ) === false ) {
1967 continue;
1968 }
1969
1970 // replace
1971 $string = str_replace( $search, $replace, $string );
1972
1973 // append to ignore
1974 $ignore[] = $replace;
1975 }
1976
1977 // return
1978 return $string;
1979 }
1980
1981 /**
1982 * date & time formats
1983 *
1984 * These settings contain an association of format strings from PHP => JS
1985 *
1986 * @since 5.3.8
1987 *
1988 * @param n/a
1989 * @return n/a
1990 */
1991
1992 acf_update_setting(
1993 'php_to_js_date_formats',
1994 array(
1995
1996 // Year
1997 'Y' => 'yy', // Numeric, 4 digits 1999, 2003
1998 'y' => 'y', // Numeric, 2 digits 99, 03
1999
2000
2001 // Month
2002 'm' => 'mm', // Numeric, with leading zeros 01–12
2003 'n' => 'm', // Numeric, without leading zeros 1–12
2004 'F' => 'MM', // Textual full January – December
2005 'M' => 'M', // Textual three letters Jan - Dec
2006
2007
2008 // Weekday
2009 'l' => 'DD', // Full name (lowercase 'L') Sunday – Saturday
2010 'D' => 'D', // Three letter name Mon – Sun
2011
2012
2013 // Day of Month
2014 'd' => 'dd', // Numeric, with leading zeros 01–31
2015 'j' => 'd', // Numeric, without leading zeros 1–31
2016 'S' => '', // The English suffix for the day of the month st, nd or th in the 1st, 2nd or 15th.
2017
2018 )
2019 );
2020
2021 acf_update_setting(
2022 'php_to_js_time_formats',
2023 array(
2024
2025 'a' => 'tt', // Lowercase Ante meridiem and Post meridiem am or pm
2026 'A' => 'TT', // Uppercase Ante meridiem and Post meridiem AM or PM
2027 'h' => 'hh', // 12-hour format of an hour with leading zeros 01 through 12
2028 'g' => 'h', // 12-hour format of an hour without leading zeros 1 through 12
2029 'H' => 'HH', // 24-hour format of an hour with leading zeros 00 through 23
2030 'G' => 'H', // 24-hour format of an hour without leading zeros 0 through 23
2031 'i' => 'mm', // Minutes with leading zeros 00 to 59
2032 's' => 'ss', // Seconds, with leading zeros 00 through 59
2033
2034 )
2035 );
2036
2037
2038 /**
2039 * acf_split_date_time
2040 *
2041 * This function will split a format string into seperate date and time
2042 *
2043 * @since 5.3.8
2044 *
2045 * @param $date_time (string)
2046 * @return $formats (array)
2047 */
2048 function acf_split_date_time( $date_time = '' ) {
2049
2050 // vars
2051 $php_date = acf_get_setting( 'php_to_js_date_formats' );
2052 $php_time = acf_get_setting( 'php_to_js_time_formats' );
2053 $chars = str_split( $date_time );
2054 $type = 'date';
2055
2056 // default
2057 $data = array(
2058 'date' => '',
2059 'time' => '',
2060 );
2061
2062 // loop
2063 foreach ( $chars as $i => $c ) {
2064
2065 // find type
2066 // - allow misc characters to append to previous type
2067 if ( isset( $php_date[ $c ] ) ) {
2068 $type = 'date';
2069 } elseif ( isset( $php_time[ $c ] ) ) {
2070 $type = 'time';
2071 }
2072
2073 // append char
2074 $data[ $type ] .= $c;
2075 }
2076
2077 // trim
2078 $data['date'] = trim( $data['date'] );
2079 $data['time'] = trim( $data['time'] );
2080
2081 // return
2082 return $data;
2083 }
2084
2085 /**
2086 * acf_convert_date_to_php
2087 *
2088 * This fucntion converts a date format string from JS to PHP
2089 *
2090 * @since 5.0.0
2091 *
2092 * @param $date (string)
2093 * @return (string)
2094 */
2095 function acf_convert_date_to_php( $date = '' ) {
2096
2097 // vars
2098 $php_to_js = acf_get_setting( 'php_to_js_date_formats' );
2099 $js_to_php = array_flip( $php_to_js );
2100
2101 // return
2102 return acf_str_replace( $date, $js_to_php );
2103 }
2104
2105 /**
2106 * acf_convert_date_to_js
2107 *
2108 * This fucntion converts a date format string from PHP to JS
2109 *
2110 * @since 5.0.0
2111 *
2112 * @param $date (string)
2113 * @return (string)
2114 */
2115 function acf_convert_date_to_js( $date = '' ) {
2116
2117 // vars
2118 $php_to_js = acf_get_setting( 'php_to_js_date_formats' );
2119
2120 // return
2121 return acf_str_replace( $date, $php_to_js );
2122 }
2123
2124 /**
2125 * acf_convert_time_to_php
2126 *
2127 * This fucntion converts a time format string from JS to PHP
2128 *
2129 * @since 5.0.0
2130 *
2131 * @param $time (string)
2132 * @return (string)
2133 */
2134 function acf_convert_time_to_php( $time = '' ) {
2135
2136 // vars
2137 $php_to_js = acf_get_setting( 'php_to_js_time_formats' );
2138 $js_to_php = array_flip( $php_to_js );
2139
2140 // return
2141 return acf_str_replace( $time, $js_to_php );
2142 }
2143
2144 /**
2145 * acf_convert_time_to_js
2146 *
2147 * This fucntion converts a date format string from PHP to JS
2148 *
2149 * @since 5.0.0
2150 *
2151 * @param $time (string)
2152 * @return (string)
2153 */
2154 function acf_convert_time_to_js( $time = '' ) {
2155
2156 // vars
2157 $php_to_js = acf_get_setting( 'php_to_js_time_formats' );
2158
2159 // return
2160 return acf_str_replace( $time, $php_to_js );
2161 }
2162
2163 /**
2164 * acf_update_user_setting
2165 *
2166 * description
2167 *
2168 * @since 5.0.0
2169 *
2170 * @param $post_id (int)
2171 * @return $post_id (int)
2172 */
2173 function acf_update_user_setting( $name, $value ) {
2174
2175 // get current user id
2176 $user_id = get_current_user_id();
2177
2178 // get user settings
2179 $settings = get_user_meta( $user_id, 'acf_user_settings', true );
2180
2181 // ensure array
2182 $settings = acf_get_array( $settings );
2183
2184 // delete setting (allow 0 to save)
2185 if ( acf_is_empty( $value ) ) {
2186 unset( $settings[ $name ] );
2187
2188 // append setting
2189 } else {
2190 $settings[ $name ] = $value;
2191 }
2192
2193 // update user data
2194 return update_metadata( 'user', $user_id, 'acf_user_settings', $settings );
2195 }
2196
2197 /**
2198 * acf_get_user_setting
2199 *
2200 * description
2201 *
2202 * @since 5.0.0
2203 *
2204 * @param $post_id (int)
2205 * @return $post_id (int)
2206 */
2207 function acf_get_user_setting( $name = '', $default = false ) {
2208
2209 // get current user id
2210 $user_id = get_current_user_id();
2211
2212 // get user settings
2213 $settings = get_user_meta( $user_id, 'acf_user_settings', true );
2214
2215 // ensure array
2216 $settings = acf_get_array( $settings );
2217
2218 // bail arly if no settings
2219 if ( ! isset( $settings[ $name ] ) ) {
2220 return $default;
2221 }
2222
2223 // return
2224 return $settings[ $name ];
2225 }
2226
2227 /**
2228 * acf_in_array
2229 *
2230 * description
2231 *
2232 * @since 5.0.0
2233 *
2234 * @param $post_id (int)
2235 * @return $post_id (int)
2236 */
2237 function acf_in_array( $value = '', $array = false ) {
2238
2239 // bail early if not array
2240 if ( ! is_array( $array ) ) {
2241 return false;
2242 }
2243
2244 // find value in array
2245 return in_array( $value, $array );
2246 }
2247
2248 /**
2249 * acf_get_valid_post_id
2250 *
2251 * This function will return a valid post_id based on the current screen / parameter
2252 *
2253 * @since 5.0.0
2254 *
2255 * @param $post_id (mixed)
2256 * @return $post_id (mixed)
2257 */
2258 function acf_get_valid_post_id( $post_id = 0 ) {
2259
2260 // allow filter to short-circuit load_value logic
2261 $preload = apply_filters( 'acf/pre_load_post_id', null, $post_id );
2262 if ( $preload !== null ) {
2263 return $preload;
2264 }
2265
2266 // vars
2267 $_post_id = $post_id;
2268
2269 // if not $post_id, load queried object
2270 if ( ! $post_id ) {
2271
2272 // try for global post (needed for setup_postdata)
2273 $post_id = (int) get_the_ID();
2274
2275 // try for current screen
2276 if ( ! $post_id ) {
2277 $post_id = get_queried_object();
2278 }
2279 }
2280
2281 // $post_id may be an object.
2282 // todo: Compare class types instead.
2283 if ( is_object( $post_id ) ) {
2284
2285 // post
2286 if ( isset( $post_id->post_type, $post_id->ID ) ) {
2287 $post_id = $post_id->ID;
2288
2289 // user
2290 } elseif ( isset( $post_id->roles, $post_id->ID ) ) {
2291 $post_id = 'user_' . $post_id->ID;
2292
2293 // term
2294 } elseif ( isset( $post_id->taxonomy, $post_id->term_id ) ) {
2295 $post_id = 'term_' . $post_id->term_id;
2296
2297 // comment
2298 } elseif ( isset( $post_id->comment_ID ) ) {
2299 $post_id = 'comment_' . $post_id->comment_ID;
2300
2301 // default
2302 } else {
2303 $post_id = 0;
2304 }
2305 }
2306
2307 // allow for option == options
2308 if ( $post_id === 'option' ) {
2309 $post_id = 'options';
2310 }
2311
2312 // append language code
2313 if ( $post_id == 'options' ) {
2314 $dl = acf_get_setting( 'default_language' );
2315 $cl = acf_get_setting( 'current_language' );
2316
2317 if ( $cl && $cl !== $dl ) {
2318 $post_id .= '_' . $cl;
2319 }
2320 }
2321
2322 // filter for 3rd party
2323 $post_id = apply_filters( 'acf/validate_post_id', $post_id, $_post_id );
2324
2325 // return
2326 return $post_id;
2327 }
2328
2329
2330
2331 /**
2332 * acf_get_post_id_info
2333 *
2334 * This function will return the type and id for a given $post_id string
2335 *
2336 * @since 5.4.0
2337 *
2338 * @param $post_id (mixed)
2339 * @return $info (array)
2340 */
2341 function acf_get_post_id_info( $post_id = 0 ) {
2342
2343 // vars
2344 $info = array(
2345 'type' => 'post',
2346 'id' => 0,
2347 );
2348
2349 // bail early if no $post_id
2350 if ( ! $post_id ) {
2351 return $info;
2352 }
2353
2354 // check cache
2355 // - this function will most likely be called multiple times (saving loading fields from post)
2356 // $cache_key = "get_post_id_info/post_id={$post_id}";
2357 // if( acf_isset_cache($cache_key) ) return acf_get_cache($cache_key);
2358 // numeric
2359 if ( is_numeric( $post_id ) ) {
2360 $info['id'] = (int) $post_id;
2361
2362 // string
2363 } elseif ( is_string( $post_id ) ) {
2364
2365 // vars
2366 $glue = '_';
2367 $type = explode( $glue, $post_id );
2368 $id = array_pop( $type );
2369 $type = implode( $glue, $type );
2370 $meta = array( 'post', 'user', 'comment', 'term' );
2371
2372 // check if is taxonomy (ACF < 5.5)
2373 // - avoid scenario where taxonomy exists with name of meta type
2374 if ( ! in_array( $type, $meta ) && acf_isset_termmeta( $type ) ) {
2375 $type = 'term';
2376 }
2377
2378 // meta
2379 if ( is_numeric( $id ) && in_array( $type, $meta ) ) {
2380 $info['type'] = $type;
2381 $info['id'] = (int) $id;
2382
2383 // option
2384 } else {
2385 $info['type'] = 'option';
2386 $info['id'] = $post_id;
2387 }
2388 }
2389
2390 // update cache
2391 // acf_set_cache($cache_key, $info);
2392 // filter
2393 $info = apply_filters( 'acf/get_post_id_info', $info, $post_id );
2394
2395 // return
2396 return $info;
2397 }
2398
2399 /**
2400 * acf_isset_termmeta
2401 *
2402 * This function will return true if the termmeta table exists
2403 * https://developer.wordpress.org/reference/functions/get_term_meta/
2404 *
2405 * @since 5.4.0
2406 *
2407 * @param $post_id (int)
2408 * @return $post_id (int)
2409 */
2410 function acf_isset_termmeta( $taxonomy = '' ) {
2411
2412 // bail early if no table
2413 if ( get_option( 'db_version' ) < 34370 ) {
2414 return false;
2415 }
2416
2417 // check taxonomy
2418 if ( $taxonomy && ! taxonomy_exists( $taxonomy ) ) {
2419 return false;
2420 }
2421
2422 // return
2423 return true;
2424 }
2425
2426 /**
2427 * This function will walk through the $_FILES data and upload each found.
2428 *
2429 * @since 5.0.9
2430 *
2431 * @param array $ancestors An internal parameter, not required.
2432 */
2433 function acf_upload_files( $ancestors = array() ) {
2434
2435 if ( empty( $_FILES['acf'] ) ) {
2436 return;
2437 }
2438
2439 $file = acf_sanitize_files_array( $_FILES['acf'] ); // phpcs:disable WordPress.Security.NonceVerification.Missing -- Verified upstream.
2440
2441 // walk through ancestors.
2442 if ( ! empty( $ancestors ) ) {
2443 foreach ( $ancestors as $a ) {
2444 foreach ( array_keys( $file ) as $k ) {
2445 $file[ $k ] = $file[ $k ][ $a ];
2446 }
2447 }
2448 }
2449
2450 // is array?
2451 if ( is_array( $file['name'] ) ) {
2452 foreach ( array_keys( $file['name'] ) as $k ) {
2453 $_ancestors = array_merge( $ancestors, array( $k ) );
2454
2455 acf_upload_files( $_ancestors );
2456 }
2457
2458 return;
2459 }
2460
2461 // Bail early if file has error (no file uploaded).
2462 if ( $file['error'] ) {
2463 return;
2464 }
2465
2466 $field_key = end( $ancestors );
2467 $nonce_name = $field_key . '_file_nonce';
2468
2469 if ( empty( $_REQUEST['acf'][ $nonce_name ] ) || ! wp_verify_nonce( sanitize_text_field( $_REQUEST['acf'][ $nonce_name ] ), 'acf/file_uploader_nonce/' . $field_key ) ) {
2470 return;
2471 }
2472
2473 // Assign global _acfuploader for media validation.
2474 $_POST['_acfuploader'] = $field_key;
2475
2476 // file found!
2477 $attachment_id = acf_upload_file( $file );
2478
2479 // update $_POST
2480 array_unshift( $ancestors, 'acf' );
2481 acf_update_nested_array( $_POST, $ancestors, $attachment_id );
2482 }
2483
2484 /**
2485 * acf_upload_file
2486 *
2487 * This function will uploade a $_FILE
2488 *
2489 * @since 5.0.9
2490 *
2491 * @param $uploaded_file (array) array found from $_FILE data
2492 * @return $id (int) new attachment ID
2493 */
2494 function acf_upload_file( $uploaded_file ) {
2495
2496 // required
2497 // require_once( ABSPATH . "/wp-load.php" ); // WP should already be loaded
2498 require_once ABSPATH . '/wp-admin/includes/media.php'; // video functions
2499 require_once ABSPATH . '/wp-admin/includes/file.php';
2500 require_once ABSPATH . '/wp-admin/includes/image.php';
2501
2502 // required for wp_handle_upload() to upload the file
2503 $upload_overrides = array( 'test_form' => false );
2504
2505 // Restrict uploads to image MIME types for image and gallery fields.
2506 // phpcs:disable WordPress.Security.NonceVerification.Missing -- Field key is used for validation context only.
2507 if ( ! empty( $_POST['_acfuploader'] ) ) {
2508 $field = acf_get_field( sanitize_text_field( wp_unslash( $_POST['_acfuploader'] ) ) );
2509 if ( acf_is_image_field( $field ) ) {
2510 $upload_overrides['mimes'] = acf_get_image_mime_types( $field );
2511 }
2512 }
2513 // phpcs:enable WordPress.Security.NonceVerification.Missing
2514
2515 // upload
2516 $file = wp_handle_upload( $uploaded_file, $upload_overrides );
2517
2518 // bail early if upload failed
2519 if ( isset( $file['error'] ) ) {
2520 return $file['error'];
2521 }
2522
2523 // vars
2524 $url = $file['url'];
2525 $type = $file['type'];
2526 $file = $file['file'];
2527 $filename = basename( $file );
2528
2529 // Construct the object array
2530 $object = array(
2531 'post_title' => $filename,
2532 'post_mime_type' => $type,
2533 'guid' => $url,
2534 );
2535
2536 // Save the data
2537 $id = wp_insert_attachment( $object, $file );
2538
2539 // Add the meta-data
2540 wp_update_attachment_metadata( $id, wp_generate_attachment_metadata( $id, $file ) );
2541
2542 /** This action is documented in wp-admin/custom-header.php */
2543 do_action( 'wp_create_file_in_uploads', $file, $id ); // For replication
2544
2545 // return new ID
2546 return $id;
2547 }
2548
2549 /**
2550 * acf_update_nested_array
2551 *
2552 * This function will update a nested array value. Useful for modifying the $_POST array
2553 *
2554 * @since 5.0.9
2555 *
2556 * @param $array (array) target array to be updated
2557 * @param $ancestors (array) array of keys to navigate through to find the child
2558 * @param $value (mixed) The new value
2559 * @return (boolean)
2560 */
2561 function acf_update_nested_array( &$array, $ancestors, $value ) {
2562
2563 // if no more ancestors, update the current var
2564 if ( empty( $ancestors ) ) {
2565 $array = $value;
2566
2567 // return
2568 return true;
2569 }
2570
2571 // shift the next ancestor from the array
2572 $k = array_shift( $ancestors );
2573
2574 // if exists
2575 if ( isset( $array[ $k ] ) ) {
2576 return acf_update_nested_array( $array[ $k ], $ancestors, $value );
2577 }
2578
2579 // return
2580 return false;
2581 }
2582
2583 /**
2584 * acf_is_screen
2585 *
2586 * This function will return true if all args are matched for the current screen
2587 *
2588 * @since 5.1.5
2589 *
2590 * @param $post_id (int)
2591 * @return $post_id (int)
2592 */
2593 function acf_is_screen( $id = '' ) {
2594
2595 // bail early if not defined
2596 if ( ! function_exists( 'get_current_screen' ) ) {
2597 return false;
2598 }
2599
2600 // vars
2601 $current_screen = get_current_screen();
2602
2603 // no screen
2604 if ( ! $current_screen ) {
2605 return false;
2606
2607 // array
2608 } elseif ( is_array( $id ) ) {
2609 return in_array( $current_screen->id, $id );
2610
2611 // string
2612 } else {
2613 return ( $id === $current_screen->id );
2614 }
2615 }
2616
2617 /**
2618 * Check if we're in an ACF admin screen
2619 *
2620 * @since 6.2.2
2621 *
2622 * @return boolean Returns true if the current screen is an ACF admin screen.
2623 */
2624 function acf_is_acf_admin_screen() {
2625 if ( ! is_admin() || ! function_exists( 'get_current_screen' ) ) {
2626 return false;
2627 }
2628 $screen = get_current_screen();
2629 if ( $screen && ! empty( $screen->post_type ) && substr( $screen->post_type, 0, 4 ) === 'acf-' ) {
2630 return true;
2631 }
2632
2633 return false;
2634 }
2635
2636 /**
2637 * acf_maybe_get
2638 *
2639 * This function will return a var if it exists in an array
2640 *
2641 * @since 5.1.5
2642 *
2643 * @param $array (array) the array to look within
2644 * @param $key (key) the array key to look for. Nested values may be found using '/'
2645 * @param $default (mixed) the value returned if not found
2646 * @return $post_id (int)
2647 */
2648 function acf_maybe_get( $array = array(), $key = 0, $default = null ) {
2649
2650 return isset( $array[ $key ] ) ? $array[ $key ] : $default;
2651 }
2652
2653 function acf_maybe_get_POST( $key = '', $default = null ) {
2654
2655 return isset( $_POST[ $key ] ) ? acf_sanitize_request_args( $_POST[ $key ] ) : $default; // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing -- Checked elsewhere.
2656 }
2657
2658 function acf_maybe_get_GET( $key = '', $default = null ) {
2659
2660 return isset( $_GET[ $key ] ) ? acf_sanitize_request_args( $_GET[ $key ] ) : $default; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Checked elsewhere.
2661 }
2662
2663 /**
2664 * Returns an array of attachment data.
2665 *
2666 * @since 5.1.5
2667 *
2668 * @param integer|WP_Post The attachment ID or object
2669 * @return array|false
2670 */
2671 function acf_get_attachment( $attachment ) {
2672
2673 // Allow filter to short-circuit load attachment logic.
2674 // Alternatively, this filter may be used to switch blogs for multisite media functionality.
2675 $response = apply_filters( 'acf/pre_load_attachment', null, $attachment );
2676 if ( $response !== null ) {
2677 return $response;
2678 }
2679
2680 // Get the attachment post object.
2681 $attachment = get_post( $attachment );
2682 if ( ! $attachment ) {
2683 return false;
2684 }
2685 if ( $attachment->post_type !== 'attachment' ) {
2686 return false;
2687 }
2688
2689 // Load various attachment details.
2690 $meta = wp_get_attachment_metadata( $attachment->ID );
2691 $attached_file = get_attached_file( $attachment->ID );
2692 if ( strpos( $attachment->post_mime_type, '/' ) !== false ) {
2693 list( $type, $subtype ) = explode( '/', $attachment->post_mime_type );
2694 } else {
2695 list( $type, $subtype ) = array( $attachment->post_mime_type, '' );
2696 }
2697
2698 // Generate response.
2699 $response = array(
2700 'ID' => $attachment->ID,
2701 'id' => $attachment->ID,
2702 'title' => $attachment->post_title,
2703 'filename' => wp_basename( $attached_file ),
2704 'filesize' => 0,
2705 'url' => wp_get_attachment_url( $attachment->ID ),
2706 'link' => get_attachment_link( $attachment->ID ),
2707 'alt' => get_post_meta( $attachment->ID, '_wp_attachment_image_alt', true ),
2708 'author' => $attachment->post_author,
2709 'description' => $attachment->post_content,
2710 'caption' => $attachment->post_excerpt,
2711 'name' => $attachment->post_name,
2712 'status' => $attachment->post_status,
2713 'uploaded_to' => $attachment->post_parent,
2714 'date' => $attachment->post_date_gmt,
2715 'modified' => $attachment->post_modified_gmt,
2716 'menu_order' => $attachment->menu_order,
2717 'mime_type' => $attachment->post_mime_type,
2718 'type' => $type,
2719 'subtype' => $subtype,
2720 'icon' => wp_mime_type_icon( $attachment->ID ),
2721 );
2722
2723 // Append filesize data.
2724 if ( isset( $meta['filesize'] ) ) {
2725 $response['filesize'] = $meta['filesize'];
2726 } else {
2727 /**
2728 * Allows shortcutting our ACF's `filesize` call to prevent us making filesystem calls.
2729 * Mostly useful for third party plugins which may offload media to other services, and filesize calls will induce a remote download.
2730 *
2731 * @since 6.2.2
2732 *
2733 * @param int|null The default filesize.
2734 * @param WP_Post $attachment The attachment post object we're looking for the filesize for.
2735 */
2736 $shortcut_filesize = apply_filters( 'acf/filesize', null, $attachment );
2737 if ( $shortcut_filesize ) {
2738 $response['filesize'] = intval( $shortcut_filesize );
2739 } elseif ( file_exists( $attached_file ) ) {
2740 $response['filesize'] = filesize( $attached_file );
2741 }
2742 }
2743
2744 // Restrict the loading of image "sizes".
2745 $sizes_id = 0;
2746
2747 // Type specific logic.
2748 switch ( $type ) {
2749 case 'image':
2750 $sizes_id = $attachment->ID;
2751 $src = wp_get_attachment_image_src( $attachment->ID, 'full' );
2752 if ( $src ) {
2753 $response['url'] = $src[0];
2754 $response['width'] = $src[1];
2755 $response['height'] = $src[2];
2756 }
2757 break;
2758 case 'video':
2759 $response['width'] = acf_maybe_get( $meta, 'width', 0 );
2760 $response['height'] = acf_maybe_get( $meta, 'height', 0 );
2761 if ( $featured_id = get_post_thumbnail_id( $attachment->ID ) ) {
2762 $sizes_id = $featured_id;
2763 }
2764 break;
2765 case 'audio':
2766 if ( $featured_id = get_post_thumbnail_id( $attachment->ID ) ) {
2767 $sizes_id = $featured_id;
2768 }
2769 break;
2770 }
2771
2772 // Load array of image sizes.
2773 if ( $sizes_id ) {
2774 $sizes = get_intermediate_image_sizes();
2775 $sizes_data = array();
2776 foreach ( $sizes as $size ) {
2777 $src = wp_get_attachment_image_src( $sizes_id, $size );
2778 if ( $src ) {
2779 $sizes_data[ $size ] = $src[0];
2780 $sizes_data[ $size . '-width' ] = $src[1];
2781 $sizes_data[ $size . '-height' ] = $src[2];
2782 }
2783 }
2784 $response['sizes'] = $sizes_data;
2785 }
2786
2787 /**
2788 * Filters the attachment $response after it has been loaded.
2789 *
2790 * @since 5.9.0
2791 *
2792 * @param array $response Array of loaded attachment data.
2793 * @param WP_Post $attachment Attachment object.
2794 * @param array|false $meta Array of attachment meta data, or false if there is none.
2795 */
2796 return apply_filters( 'acf/load_attachment', $response, $attachment, $meta );
2797 }
2798
2799 /**
2800 * This function will truncate and return a string
2801 *
2802 * @since 5.0.0
2803 *
2804 * @param string $text The text to truncate.
2805 * @param integer $length The number of characters to allow in the string.
2806 *
2807 * @return string
2808 */
2809 function acf_get_truncated( $text, $length = 64 ) {
2810 $text = trim( $text );
2811 $the_length = function_exists( 'mb_strlen' ) ? mb_strlen( $text ) : strlen( $text );
2812
2813 $cut_length = $length - 3;
2814 $return = function_exists( 'mb_substr' ) ? mb_substr( $text, 0, $cut_length ) : substr( $text, 0, $cut_length );
2815
2816 if ( $the_length > $cut_length ) {
2817 $return .= '...';
2818 }
2819
2820 return $return;
2821 }
2822
2823 /**
2824 * acf_current_user_can_admin
2825 *
2826 * This function will return true if the current user can administrate the ACF field groups
2827 *
2828 * @since 5.1.5
2829 *
2830 * @param $post_id (int)
2831 * @return $post_id (int)
2832 */
2833 function acf_current_user_can_admin() {
2834
2835 if ( acf_get_setting( 'show_admin' ) && current_user_can( acf_get_setting( 'capability' ) ) ) {
2836 return true;
2837 }
2838
2839 // return
2840 return false;
2841 }
2842
2843 /**
2844 * Wrapper function for current_user_can( 'edit_post', $post_id ).
2845 *
2846 * @since 6.3.4
2847 *
2848 * @param integer $post_id The post ID to check.
2849 * @return boolean
2850 */
2851 function acf_current_user_can_edit_post( int $post_id ): bool {
2852 /**
2853 * The `edit_post` capability is a meta capability, which
2854 * gets converted to the correct post type object `edit_post`
2855 * equivalent.
2856 *
2857 * If the post type does not have `map_meta_cap` enabled and the user is
2858 * not manually mapping the `edit_post` capability, this will fail
2859 * unless the role has the `edit_post` capability added to a user/role.
2860 *
2861 * However, more (core) stuff will likely break in this scenario.
2862 */
2863 $user_can_edit = current_user_can( 'edit_post', $post_id );
2864
2865 return (bool) apply_filters( 'acf/current_user_can_edit_post', $user_can_edit, $post_id );
2866 }
2867
2868 /**
2869 * Checks if the current user can edit a given ACF context.
2870 *
2871 * Handles post, user, term, comment, woo_order, block, and option contexts returned by acf_decode_post_id().
2872 *
2873 * @since 6.7.2
2874 *
2875 * @param array $post_id_info The result of acf_decode_post_id(), containing 'type' and 'id'.
2876 * @param string $options_page_slug Optional. The options page menu slug, used to look up the page's capability.
2877 * @return boolean
2878 */
2879 function acf_current_user_can_edit_in_context( array $post_id_info, string $options_page_slug = '' ): bool {
2880 $type = $post_id_info['type'] ?? '';
2881 $id = $post_id_info['id'] ?? 0;
2882
2883 switch ( $type ) {
2884 case 'post':
2885 return acf_current_user_can_edit_post( (int) $id );
2886
2887 case 'user':
2888 return current_user_can( 'edit_user', (int) $id );
2889
2890 case 'term':
2891 return current_user_can( 'edit_term', (int) $id );
2892
2893 case 'comment':
2894 return current_user_can( 'edit_comment', (int) $id );
2895
2896 case 'woo_order':
2897 return current_user_can( 'edit_shop_orders' ); // phpcs:ignore
2898
2899 case 'block':
2900 return current_user_can( 'edit_posts' );
2901
2902 case 'option':
2903 if ( ! empty( $options_page_slug ) && function_exists( 'acf_get_options_page' ) ) {
2904 $page = acf_get_options_page( $options_page_slug );
2905
2906 if ( ! empty( $page['capability'] ) && ! empty( $page['post_id'] ) ) {
2907 // Ensure the page's post_id matches the requested post_id.
2908 if ( acf_get_valid_post_id( $page['post_id'] ) !== $id ) {
2909 return false;
2910 }
2911
2912 return current_user_can( $page['capability'] );
2913 }
2914 }
2915
2916 return current_user_can( 'manage_options' );
2917
2918 default:
2919 return (bool) apply_filters( 'acf/current_user_can_edit_in_context', false, $post_id_info );
2920 }
2921 }
2922
2923 /**
2924 * acf_get_filesize
2925 *
2926 * This function will return a numeric value of bytes for a given filesize string
2927 *
2928 * @since 5.1.5
2929 *
2930 * @param $size (mixed)
2931 * @return (int)
2932 */
2933 function acf_get_filesize( $size = 1 ) {
2934
2935 // vars
2936 $unit = 'MB';
2937 $units = array(
2938 'TB' => 4,
2939 'GB' => 3,
2940 'MB' => 2,
2941 'KB' => 1,
2942 );
2943
2944 // look for $unit within the $size parameter (123 KB)
2945 if ( is_string( $size ) ) {
2946
2947 // vars
2948 $custom = strtoupper( substr( $size, -2 ) );
2949
2950 foreach ( $units as $k => $v ) {
2951 if ( $custom === $k ) {
2952 $unit = $k;
2953 $size = substr( $size, 0, -2 );
2954 }
2955 }
2956 }
2957
2958 // calc bytes
2959 $bytes = floatval( $size ) * pow( 1024, $units[ $unit ] );
2960
2961 // return
2962 return $bytes;
2963 }
2964
2965 /**
2966 * acf_format_filesize
2967 *
2968 * This function will return a formatted string containing the filesize and unit
2969 *
2970 * @since 5.1.5
2971 *
2972 * @param $size (mixed)
2973 * @return (int)
2974 */
2975 function acf_format_filesize( $size = 1 ) {
2976
2977 // convert
2978 $bytes = acf_get_filesize( $size );
2979
2980 // vars
2981 $units = array(
2982 'TB' => 4,
2983 'GB' => 3,
2984 'MB' => 2,
2985 'KB' => 1,
2986 );
2987
2988 // loop through units
2989 foreach ( $units as $k => $v ) {
2990 $result = $bytes / pow( 1024, $v );
2991
2992 if ( $result >= 1 ) {
2993 return $result . ' ' . $k;
2994 }
2995 }
2996
2997 // return
2998 return $bytes . ' B';
2999 }
3000
3001 /**
3002 * acf_get_valid_terms
3003 *
3004 * This function will replace old terms with new split term ids
3005 *
3006 * @since 5.1.5
3007 *
3008 * @param $terms (int|array)
3009 * @param $taxonomy (string)
3010 * @return $terms
3011 */
3012 function acf_get_valid_terms( $terms = false, $taxonomy = 'category' ) {
3013
3014 // force into array
3015 $terms = acf_get_array( $terms );
3016
3017 // force ints
3018 $terms = array_map( 'intval', $terms );
3019
3020 // bail early if function does not yet exist or
3021 if ( ! function_exists( 'wp_get_split_term' ) || empty( $terms ) ) {
3022 return $terms;
3023 }
3024
3025 // attempt to find new terms
3026 foreach ( $terms as $i => $term_id ) {
3027 $new_term_id = wp_get_split_term( $term_id, $taxonomy );
3028
3029 if ( $new_term_id ) {
3030 $terms[ $i ] = $new_term_id;
3031 }
3032 }
3033
3034 // return
3035 return $terms;
3036 }
3037
3038 /**
3039 * Returns true when the field only accepts image uploads.
3040 *
3041 * @since 6.8.7
3042 *
3043 * @param array $field The field array.
3044 * @return boolean
3045 */
3046 function acf_is_image_field( $field ) {
3047 if ( ! is_array( $field ) ) {
3048 /**
3049 * Filters whether the supplied field should be treated as an image field.
3050 *
3051 * @since 6.8.7
3052 *
3053 * @param bool $is_image_field Whether the field only accepts image uploads.
3054 * @param array $field The field array.
3055 */
3056 return (bool) apply_filters( 'acf/is_image_field', false, $field );
3057 }
3058
3059 $is_image_field = in_array( acf_maybe_get( $field, 'type', '' ), array( 'image', 'gallery' ), true );
3060
3061 /**
3062 * Filters whether the supplied field should be treated as an image field.
3063 *
3064 * @since 6.8.7
3065 *
3066 * @param bool $is_image_field Whether the field only accepts image uploads.
3067 * @param array $field The field array.
3068 */
3069 $field_type = acf_maybe_get( $field, 'type', '' );
3070 $field_name = acf_maybe_get( $field, '_name', '' );
3071 $field_key = acf_maybe_get( $field, 'key', '' );
3072
3073 $is_image_field = apply_filters( "acf/is_image_field/type={$field_type}", $is_image_field, $field );
3074 $is_image_field = apply_filters( "acf/is_image_field/name={$field_name}", $is_image_field, $field );
3075 $is_image_field = apply_filters( "acf/is_image_field/key={$field_key}", $is_image_field, $field );
3076 $is_image_field = apply_filters( 'acf/is_image_field', $is_image_field, $field );
3077
3078 return $is_image_field;
3079 }
3080
3081 /**
3082 * Returns allowed MIME types restricted to images.
3083 *
3084 * @since 6.8.7
3085 *
3086 * @param array|false $field Optional field array for field-specific filtering.
3087 * @return array
3088 */
3089 function acf_get_image_mime_types( $field = false ) {
3090 static $image_mimes = null;
3091
3092 if ( null === $image_mimes ) {
3093 $image_mimes = array();
3094 $allowed_mimes = get_allowed_mime_types();
3095
3096 foreach ( $allowed_mimes as $ext => $mime ) {
3097 if ( wp_match_mime_types( 'image', $mime ) ) {
3098 $image_mimes[ $ext ] = $mime;
3099 }
3100 }
3101
3102 /**
3103 * Filters the image MIME types allowed for image and gallery fields.
3104 *
3105 * @since 6.8.7
3106 *
3107 * @param array $image_mimes The allowed image MIME types keyed by extension.
3108 */
3109 $image_mimes = apply_filters( 'acf/get_image_mime_types', $image_mimes );
3110 }
3111
3112 if ( ! is_array( $field ) || empty( $field['type'] ) ) {
3113 return $image_mimes;
3114 }
3115
3116 /**
3117 * Filters the image MIME types allowed for a specific field.
3118 *
3119 * @since 6.8.7
3120 *
3121 * @param array $image_mimes The allowed image MIME types keyed by extension.
3122 * @param array $field The field array.
3123 */
3124 $field_type = acf_maybe_get( $field, 'type', '' );
3125 $field_name = acf_maybe_get( $field, '_name', '' );
3126 $field_key = acf_maybe_get( $field, 'key', '' );
3127
3128 $field_mimes = apply_filters( "acf/get_image_mime_types/type={$field_type}", $image_mimes, $field );
3129 $field_mimes = apply_filters( "acf/get_image_mime_types/name={$field_name}", $field_mimes, $field );
3130 $field_mimes = apply_filters( "acf/get_image_mime_types/key={$field_key}", $field_mimes, $field );
3131
3132 return $field_mimes;
3133 }
3134
3135 /**
3136 * Applies filters to image attachment validation errors.
3137 *
3138 * @since 6.8.7
3139 *
3140 * @param array $errors Validation errors.
3141 * @param array $file Normalized file data.
3142 * @param array $attachment Raw attachment data for the current context.
3143 * @param array $field The field array.
3144 * @param string $context The validation context.
3145 * @return array
3146 */
3147 function acf_apply_validate_is_image_attachment_filters( $errors, $file, $attachment, $field, $context ) {
3148 /**
3149 * Filters the errors for image attachment validation.
3150 *
3151 * @since 6.8.7
3152 *
3153 * @param array $errors An array of errors.
3154 * @param array $file An array of data for a single file.
3155 * @param array $attachment An array of attachment data which differs based on the context.
3156 * @param array $field The field array.
3157 * @param string $context The current context (uploading, preparing, basic_upload).
3158 */
3159 $field_type = acf_maybe_get( $field, 'type', '' );
3160 $field_name = acf_maybe_get( $field, '_name', '' );
3161 $field_key = acf_maybe_get( $field, 'key', '' );
3162
3163 $errors = apply_filters( "acf/validate_is_image_attachment/type={$field_type}", $errors, $file, $attachment, $field, $context );
3164 $errors = apply_filters( "acf/validate_is_image_attachment/name={$field_name}", $errors, $file, $attachment, $field, $context );
3165 $errors = apply_filters( "acf/validate_is_image_attachment/key={$field_key}", $errors, $file, $attachment, $field, $context );
3166 $errors = apply_filters( 'acf/validate_is_image_attachment', $errors, $file, $attachment, $field, $context );
3167
3168 return $errors;
3169 }
3170
3171 /**
3172 * Validates image attachment data during upload.
3173 *
3174 * @since 6.8.7
3175 *
3176 * @param array $errors Validation errors.
3177 * @param array $file Normalized file data.
3178 * @param array $attachment Raw attachment data for the current context.
3179 * @param array $field The field array.
3180 * @param string $context The validation context.
3181 * @param string $error_message The error message to use when validation fails.
3182 * @return array
3183 */
3184 function acf_validate_is_image_attachment_upload( $errors, $file, $attachment, $field, $context, $error_message ) {
3185 if ( empty( $attachment['tmp_name'] ) || ! file_exists( $attachment['tmp_name'] ) ) {
3186 $errors['invalid_image'] = $error_message;
3187
3188 return acf_apply_validate_is_image_attachment_filters( $errors, $file, $attachment, $field, $context );
3189 }
3190
3191 $checked = wp_check_filetype_and_ext( $attachment['tmp_name'], $attachment['name'], acf_get_image_mime_types( $field ) );
3192
3193 if ( empty( $checked['ext'] ) || empty( $checked['type'] ) || empty( wp_match_mime_types( 'image', $checked['type'] ) ) ) {
3194 $errors['invalid_image'] = $error_message;
3195
3196 return acf_apply_validate_is_image_attachment_filters( $errors, $file, $attachment, $field, $context );
3197 }
3198
3199 /**
3200 * Filters the list of image MIME types that cannot be validated by
3201 * wp_get_image_mime(), which reads a binary magic number and only
3202 * recognizes raster formats. Vector / XML image types (e.g. SVG)
3203 * always fail that check by design and must be excluded from it.
3204 *
3205 * @since 6.8.8
3206 *
3207 * @param array $vector_mime_types MIME types to skip the binary magic-number check for.
3208 */
3209 $vector_mime_types = (array) apply_filters( 'acf/validate_is_image_attachment/vector_mime_types', array( 'image/svg+xml' ) );
3210
3211 if ( ! in_array( $checked['type'], $vector_mime_types, true ) ) {
3212 $real_mime = wp_get_image_mime( $attachment['tmp_name'] );
3213
3214 if ( ! $real_mime || empty( wp_match_mime_types( 'image', $real_mime ) ) ) {
3215 $errors['invalid_image'] = $error_message;
3216 }
3217 }
3218
3219 return acf_apply_validate_is_image_attachment_filters( $errors, $file, $attachment, $field, $context );
3220 }
3221
3222 /**
3223 * Validates image attachment data when preparing for the media library.
3224 *
3225 * @since 6.8.7
3226 *
3227 * @param array $errors Validation errors.
3228 * @param array $file Normalized file data.
3229 * @param array $attachment Raw attachment data for the current context.
3230 * @param array $field The field array.
3231 * @param string $context The validation context.
3232 * @param string $error_message The error message to use when validation fails.
3233 * @return array
3234 */
3235 function acf_validate_is_image_attachment_prepare( $errors, $file, $attachment, $field, $context, $error_message ) {
3236 $attachment_id = (int) acf_maybe_get( $attachment, 'id', 0 );
3237
3238 if ( $attachment_id ) {
3239 if ( ! wp_attachment_is_image( $attachment_id ) ) {
3240 $errors['invalid_image'] = $error_message;
3241 }
3242
3243 return acf_apply_validate_is_image_attachment_filters( $errors, $file, $attachment, $field, $context );
3244 }
3245
3246 $mime = acf_maybe_get( $attachment, 'mime', acf_maybe_get( $attachment, 'type', '' ) );
3247
3248 if ( $mime && empty( wp_match_mime_types( 'image', $mime ) ) ) {
3249 $errors['invalid_image'] = $error_message;
3250 }
3251
3252 return acf_apply_validate_is_image_attachment_filters( $errors, $file, $attachment, $field, $context );
3253 }
3254
3255 /**
3256 * Validates image attachment data during basic uploader pre-save checks.
3257 *
3258 * @since 6.8.7
3259 *
3260 * @param array $errors Validation errors.
3261 * @param array $file Normalized file data.
3262 * @param array $attachment Raw attachment data for the current context.
3263 * @param array $field The field array.
3264 * @param string $context The validation context.
3265 * @param string $error_message The error message to use when validation fails.
3266 * @return array
3267 */
3268 function acf_validate_is_image_attachment_basic_upload( $errors, $file, $attachment, $field, $context, $error_message ) {
3269 $mime = acf_maybe_get( $attachment, 'type', '' );
3270
3271 if ( $mime && empty( wp_match_mime_types( 'image', $mime ) ) ) {
3272 $errors['invalid_image'] = $error_message;
3273 }
3274
3275 return acf_apply_validate_is_image_attachment_filters( $errors, $file, $attachment, $field, $context );
3276 }
3277
3278 /**
3279 * Validates that an attachment is an image for image and gallery fields.
3280 *
3281 * @since 6.8.7
3282 *
3283 * @param array $errors Existing validation errors.
3284 * @param array $file Normalized file data.
3285 * @param array $attachment Raw attachment data for the current context.
3286 * @param array $field The field array.
3287 * @param string $context The validation context.
3288 * @return array
3289 */
3290 function acf_validate_is_image_attachment( $errors, $file, $attachment, $field, $context ) {
3291 if ( ! acf_is_image_field( $field ) ) {
3292 return $errors;
3293 }
3294
3295 /**
3296 * Filters the error message used when an attachment is not a valid image.
3297 *
3298 * @since 6.8.7
3299 *
3300 * @param string $error_message The default error message.
3301 * @param array $field The field array.
3302 * @param string $context The current context (uploading, preparing, basic_upload).
3303 * @param array $file An array of data for a single file.
3304 * @param array $attachment An array of attachment data which differs based on the context.
3305 */
3306 $error_message = apply_filters( 'acf/validate_is_image_attachment/error_message', __( 'File must be a valid image.', 'acf' ), $field, $context, $file, $attachment );
3307
3308 if ( 'upload' === $context ) {
3309 return acf_validate_is_image_attachment_upload( $errors, $file, $attachment, $field, $context, $error_message );
3310 }
3311
3312 if ( 'prepare' === $context ) {
3313 return acf_validate_is_image_attachment_prepare( $errors, $file, $attachment, $field, $context, $error_message );
3314 }
3315
3316 return acf_validate_is_image_attachment_basic_upload( $errors, $file, $attachment, $field, $context, $error_message );
3317 }
3318
3319 /**
3320 * acf_validate_attachment
3321 *
3322 * This function will validate an attachment based on a field's restrictions and return an array of errors
3323 *
3324 * @since 5.2.3
3325 *
3326 * @param $attachment (array) attachment data. Changes based on context
3327 * @param $field (array) field settings containing restrictions
3328 * @param context (string) $file is different when uploading / preparing
3329 * @return $errors (array)
3330 */
3331 function acf_validate_attachment( $attachment, $field, $context = 'prepare' ) {
3332
3333 // vars
3334 $errors = array();
3335 $file = array(
3336 'type' => '',
3337 'width' => 0,
3338 'height' => 0,
3339 'size' => 0,
3340 );
3341
3342 // upload
3343 if ( $context == 'upload' ) {
3344
3345 // vars
3346 $file['type'] = pathinfo( $attachment['name'], PATHINFO_EXTENSION );
3347 $file['size'] = filesize( $attachment['tmp_name'] );
3348
3349 if ( acf_is_image_field( $field ) || strpos( $attachment['type'], 'image' ) !== false ) {
3350 $size = function_exists( 'wp_getimagesize' ) ? wp_getimagesize( $attachment['tmp_name'] ) : getimagesize( $attachment['tmp_name'] );
3351
3352 if ( $size ) {
3353 $file['width'] = acf_maybe_get( $size, 0 );
3354 $file['height'] = acf_maybe_get( $size, 1 );
3355 }
3356 }
3357
3358 // prepare
3359 } elseif ( $context == 'prepare' ) {
3360 $use_path = isset( $attachment['filename'] ) ? $attachment['filename'] : $attachment['url'];
3361 $file['type'] = pathinfo( $use_path, PATHINFO_EXTENSION );
3362 $file['size'] = acf_maybe_get( $attachment, 'filesizeInBytes', 0 );
3363 $file['width'] = acf_maybe_get( $attachment, 'width', 0 );
3364 $file['height'] = acf_maybe_get( $attachment, 'height', 0 );
3365
3366 // custom
3367 } else {
3368 $file = array_merge( $file, $attachment );
3369 $use_path = isset( $attachment['filename'] ) ? $attachment['filename'] : $attachment['url'];
3370 $file['type'] = pathinfo( $use_path, PATHINFO_EXTENSION );
3371 }
3372
3373 // image
3374 if ( $file['width'] || $file['height'] ) {
3375
3376 // width
3377 $min_width = (int) acf_maybe_get( $field, 'min_width', 0 );
3378 $max_width = (int) acf_maybe_get( $field, 'max_width', 0 );
3379
3380 if ( $file['width'] ) {
3381 if ( $min_width && $file['width'] < $min_width ) {
3382
3383 // min width
3384 $errors['min_width'] = sprintf( __( 'Image width must be at least %dpx.', 'acf' ), $min_width );
3385 } elseif ( $max_width && $file['width'] > $max_width ) {
3386
3387 // min width
3388 $errors['max_width'] = sprintf( __( 'Image width must not exceed %dpx.', 'acf' ), $max_width );
3389 }
3390 }
3391
3392 // height
3393 $min_height = (int) acf_maybe_get( $field, 'min_height', 0 );
3394 $max_height = (int) acf_maybe_get( $field, 'max_height', 0 );
3395
3396 if ( $file['height'] ) {
3397 if ( $min_height && $file['height'] < $min_height ) {
3398
3399 // min height
3400 $errors['min_height'] = sprintf( __( 'Image height must be at least %dpx.', 'acf' ), $min_height );
3401 } elseif ( $max_height && $file['height'] > $max_height ) {
3402
3403 // min height
3404 $errors['max_height'] = sprintf( __( 'Image height must not exceed %dpx.', 'acf' ), $max_height );
3405 }
3406 }
3407 }
3408
3409 // file size
3410 if ( $file['size'] ) {
3411 $min_size = acf_maybe_get( $field, 'min_size', 0 );
3412 $max_size = acf_maybe_get( $field, 'max_size', 0 );
3413
3414 if ( $min_size && $file['size'] < acf_get_filesize( $min_size ) ) {
3415
3416 // min width
3417 $errors['min_size'] = sprintf( __( 'File size must be at least %s.', 'acf' ), acf_format_filesize( $min_size ) );
3418 } elseif ( $max_size && $file['size'] > acf_get_filesize( $max_size ) ) {
3419
3420 // min width
3421 $errors['max_size'] = sprintf( __( 'File size must not exceed %s.', 'acf' ), acf_format_filesize( $max_size ) );
3422 }
3423 }
3424
3425 // file type
3426 if ( $file['type'] ) {
3427 $mime_types = acf_maybe_get( $field, 'mime_types', '' );
3428
3429 // lower case
3430 $file['type'] = strtolower( $file['type'] );
3431 $mime_types = strtolower( $mime_types );
3432
3433 // explode
3434 $mime_types = str_replace( array( ' ', '.' ), '', $mime_types );
3435 $mime_types = explode( ',', $mime_types ); // split pieces
3436 $mime_types = array_filter( $mime_types ); // remove empty pieces
3437
3438 if ( ! empty( $mime_types ) && ! in_array( $file['type'], $mime_types ) ) {
3439
3440 // glue together last 2 types
3441 if ( count( $mime_types ) > 1 ) {
3442 $last1 = array_pop( $mime_types );
3443 $last2 = array_pop( $mime_types );
3444
3445 $mime_types[] = $last2 . ' ' . __( 'or', 'acf' ) . ' ' . $last1;
3446 }
3447
3448 $errors['mime_types'] = sprintf( __( 'File type must be %s.', 'acf' ), implode( ', ', $mime_types ) );
3449 }
3450 }
3451
3452 /**
3453 * Filters the errors for a file before it is uploaded or displayed in the media modal.
3454 *
3455 * @since 5.2.3
3456 *
3457 * @param array $errors An array of errors.
3458 * @param array $file An array of data for a single file.
3459 * @param array $attachment An array of attachment data which differs based on the context.
3460 * @param array $field The field array.
3461 * @param string $context The curent context (uploading, preparing)
3462 */
3463 $errors = apply_filters( "acf/validate_attachment/type={$field['type']}", $errors, $file, $attachment, $field, $context );
3464 $errors = apply_filters( "acf/validate_attachment/name={$field['_name']}", $errors, $file, $attachment, $field, $context );
3465 $errors = apply_filters( "acf/validate_attachment/key={$field['key']}", $errors, $file, $attachment, $field, $context );
3466 $errors = apply_filters( 'acf/validate_attachment', $errors, $file, $attachment, $field, $context );
3467
3468 // return
3469 return $errors;
3470 }
3471
3472 /**
3473 * _acf_settings_uploader
3474 *
3475 * Dynamic logic for uploader setting
3476 *
3477 * @since 5.2.3
3478 *
3479 * @param $uploader (string)
3480 * @return $uploader
3481 */
3482
3483 add_filter( 'acf/settings/uploader', '_acf_settings_uploader' );
3484
3485 function _acf_settings_uploader( $uploader ) {
3486
3487 // if can't upload files
3488 if ( ! current_user_can( 'upload_files' ) ) {
3489 $uploader = 'basic';
3490 }
3491
3492 // return
3493 return $uploader;
3494 }
3495
3496 /**
3497 * acf_translate
3498 *
3499 * This function will translate a string using the new 'l10n_textdomain' setting
3500 * Also works for arrays which is great for fields - select -> choices
3501 *
3502 * @since 5.3.2
3503 *
3504 * @param $string (mixed) string or array containins strings to be translated
3505 * @return $string
3506 */
3507 function acf_translate( $string ) {
3508
3509 // vars
3510 $l10n = acf_get_setting( 'l10n' );
3511 $textdomain = acf_get_setting( 'l10n_textdomain' );
3512
3513 // bail early if not enabled
3514 if ( ! $l10n ) {
3515 return $string;
3516 }
3517
3518 // bail early if no textdomain
3519 if ( ! $textdomain ) {
3520 return $string;
3521 }
3522
3523 // is array
3524 if ( is_array( $string ) ) {
3525 return array_map( 'acf_translate', $string );
3526 }
3527
3528 // bail early if not string
3529 if ( ! is_string( $string ) ) {
3530 return $string;
3531 }
3532
3533 // bail early if empty
3534 if ( $string === '' ) {
3535 return $string;
3536 }
3537
3538 // allow for var_export export
3539 if ( acf_get_setting( 'l10n_var_export' ) ) {
3540
3541 // bail early if already translated
3542 if ( substr( $string, 0, 7 ) === '!!__(!!' ) {
3543 return $string;
3544 }
3545
3546 // return
3547 return "!!__(!!'" . $string . "!!', !!'" . $textdomain . "!!')!!";
3548 }
3549
3550 // vars
3551 return __( $string, $textdomain );
3552 }
3553
3554 /**
3555 * acf_maybe_add_action
3556 *
3557 * This function will determine if the action has already run before adding / calling the function
3558 *
3559 * @since 5.3.2
3560 *
3561 * @param $post_id (int)
3562 * @return $post_id (int)
3563 */
3564 function acf_maybe_add_action( $tag, $function_to_add, $priority = 10, $accepted_args = 1 ) {
3565
3566 // if action has already run, execute it
3567 // - if currently doing action, allow $tag to be added as per usual to allow $priority ordering needed for 3rd party asset compatibility
3568 if ( did_action( $tag ) && ! doing_action( $tag ) ) {
3569 call_user_func( $function_to_add );
3570
3571 // if action has not yet run, add it
3572 } else {
3573 add_action( $tag, $function_to_add, $priority, $accepted_args );
3574 }
3575 }
3576
3577 /**
3578 * acf_is_row_collapsed
3579 *
3580 * This function will return true if the field's row is collapsed
3581 *
3582 * @since 5.3.2
3583 *
3584 * @param $post_id (int)
3585 * @return $post_id (int)
3586 */
3587 function acf_is_row_collapsed( $field_key = '', $row_index = 0 ) {
3588
3589 // collapsed
3590 $collapsed = acf_get_user_setting( 'collapsed_' . $field_key, '' );
3591
3592 // cookie fallback ( version < 5.3.2 )
3593 if ( $collapsed === '' ) {
3594 $collapsed = acf_extract_var( $_COOKIE, "acf_collapsed_{$field_key}", '' );
3595 $collapsed = str_replace( '|', ',', $collapsed );
3596
3597 // update
3598 acf_update_user_setting( 'collapsed_' . $field_key, $collapsed );
3599 }
3600
3601 // explode
3602 $collapsed = explode( ',', $collapsed );
3603 $collapsed = array_filter( $collapsed, 'is_numeric' );
3604
3605 // collapsed class
3606 return in_array( $row_index, $collapsed );
3607 }
3608
3609 /**
3610 * Return an image tag for the provided attachment ID
3611 *
3612 * @since 5.5.0
3613 * @deprecated 6.3.2
3614 *
3615 * @param integer $attachment_id The attachment ID
3616 * @param string $size The image size to use in the image tag.
3617 * @return false
3618 */
3619 function acf_get_attachment_image( $attachment_id = 0, $size = 'thumbnail' ) {
3620 // report function as deprecated
3621 _deprecated_function( __FUNCTION__, '6.3.2' );
3622 return false;
3623 }
3624
3625 /**
3626 * acf_get_post_thumbnail
3627 *
3628 * This function will return a thumbail image url for a given post
3629 *
3630 * @since 5.3.8
3631 *
3632 * @param $post (obj)
3633 * @param $size (mixed)
3634 * @return (string)
3635 */
3636 function acf_get_post_thumbnail( $post = null, $size = 'thumbnail' ) {
3637
3638 // vars
3639 $data = array(
3640 'url' => '',
3641 'type' => '',
3642 'html' => '',
3643 );
3644
3645 // post
3646 $post = get_post( $post );
3647
3648 // bail early if no post
3649 if ( ! $post ) {
3650 return $data;
3651 }
3652
3653 // vars
3654 $thumb_id = $post->ID;
3655 $mime_type = acf_maybe_get( explode( '/', $post->post_mime_type ), 0 );
3656
3657 // attachment
3658 if ( $post->post_type === 'attachment' ) {
3659
3660 // change $thumb_id
3661 if ( $mime_type === 'audio' || $mime_type === 'video' ) {
3662 $thumb_id = get_post_thumbnail_id( $post->ID );
3663 }
3664
3665 // post
3666 } else {
3667 $thumb_id = get_post_thumbnail_id( $post->ID );
3668 }
3669
3670 // try url
3671 $data['url'] = wp_get_attachment_image_src( $thumb_id, $size );
3672 $data['url'] = acf_maybe_get( $data['url'], 0 );
3673
3674 // default icon
3675 if ( ! $data['url'] && $post->post_type === 'attachment' ) {
3676 $data['url'] = wp_mime_type_icon( $post->ID );
3677 $data['type'] = 'icon';
3678 }
3679
3680 // html
3681 $data['html'] = '<img src="' . $data['url'] . '" alt="" />';
3682
3683 // return
3684 return $data;
3685 }
3686
3687 /**
3688 * acf_get_browser
3689 *
3690 * Returns the name of the current browser.
3691 *
3692 * @since 5.0.0
3693 *
3694 * @param void
3695 * @return string
3696 */
3697 function acf_get_browser() {
3698
3699 // Check server var.
3700 if ( isset( $_SERVER['HTTP_USER_AGENT'] ) ) {
3701 $agent = sanitize_text_field( $_SERVER['HTTP_USER_AGENT'] );
3702
3703 // Loop over search terms.
3704 $browsers = array(
3705 'Firefox' => 'firefox',
3706 'Trident' => 'msie',
3707 'MSIE' => 'msie',
3708 'Edge' => 'edge',
3709 'Chrome' => 'chrome',
3710 'Safari' => 'safari',
3711 );
3712 foreach ( $browsers as $k => $v ) {
3713 if ( strpos( $agent, $k ) !== false ) {
3714 return $v;
3715 }
3716 }
3717 }
3718
3719 // Return default.
3720 return '';
3721 }
3722
3723 /**
3724 * acf_is_ajax
3725 *
3726 * This function will reutrn true if performing a wp ajax call
3727 *
3728 * @since 5.3.8
3729 *
3730 * @param n/a
3731 * @return (boolean)
3732 */
3733 function acf_is_ajax( $action = '' ) {
3734
3735 // vars
3736 $is_ajax = false;
3737
3738 // check if is doing ajax
3739 if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
3740 $is_ajax = true;
3741 }
3742
3743 // phpcs:disable WordPress.Security.NonceVerification.Missing
3744 // check $action
3745 if ( $action && acf_maybe_get( $_POST, 'action' ) !== $action ) {
3746 // phpcs:enable WordPress.Security.NonceVerification.Missing
3747 $is_ajax = false;
3748 }
3749
3750 // return
3751 return $is_ajax;
3752 }
3753
3754 /**
3755 * Returns a date value in a formatted string.
3756 *
3757 * @since 5.3.8
3758 *
3759 * @param string $value The date value to format.
3760 * @param string $format The format to use.
3761 * @return string
3762 */
3763 function acf_format_date( $value, $format ) {
3764 // Bail early if no value or value is not what we expect.
3765 if ( ! $value || ( ! is_string( $value ) && ! is_int( $value ) ) ) {
3766 return $value;
3767 }
3768
3769 // Numeric (either unix or YYYYMMDD).
3770 if ( is_numeric( $value ) && strlen( $value ) !== 8 ) {
3771 $unixtimestamp = $value;
3772 } else {
3773 $unixtimestamp = strtotime( $value );
3774 }
3775
3776 return date_i18n( $format, $unixtimestamp );
3777 }
3778
3779 /**
3780 * Previously, deletes the debug.log file.
3781 *
3782 * @since 5.7.10
3783 * @deprecated 6.2.7
3784 */
3785 function acf_clear_log() {
3786 _deprecated_function( __FUNCTION__, '6.2.7' );
3787 return false;
3788 }
3789
3790 /**
3791 * acf_log
3792 *
3793 * description
3794 *
3795 * @since 5.3.8
3796 *
3797 * @param $post_id (int)
3798 * @return $post_id (int)
3799 */
3800 function acf_log() {
3801
3802 // vars
3803 $args = func_get_args();
3804
3805 // loop
3806 foreach ( $args as $i => $arg ) {
3807
3808 // array | object
3809 if ( is_array( $arg ) || is_object( $arg ) ) {
3810 $arg = print_r( $arg, true );
3811
3812 // bool
3813 } elseif ( is_bool( $arg ) ) {
3814 $arg = 'bool(' . ( $arg ? 'true' : 'false' ) . ')';
3815 }
3816
3817 // update
3818 $args[ $i ] = $arg;
3819 }
3820
3821 // log
3822 error_log( implode( ' ', $args ) );
3823 }
3824
3825 /**
3826 * acf_dev_log
3827 *
3828 * Used to log variables only if ACF_DEV is defined
3829 *
3830 * @since 5.7.4
3831 *
3832 * @param mixed
3833 * @return void
3834 */
3835 function acf_dev_log() {
3836 if ( defined( 'ACF_DEV' ) && ACF_DEV ) {
3837 call_user_func_array( 'acf_log', func_get_args() );
3838 }
3839 }
3840
3841 /**
3842 * acf_doing
3843 *
3844 * This function will tell ACF what task it is doing
3845 *
3846 * @since 5.3.8
3847 *
3848 * @param $event (string)
3849 * @param context (string)
3850 * @return n/a
3851 */
3852 function acf_doing( $event = '', $context = '' ) {
3853
3854 acf_update_setting( 'doing', $event );
3855 acf_update_setting( 'doing_context', $context );
3856 }
3857
3858 /**
3859 * acf_is_doing
3860 *
3861 * This function can be used to state what ACF is doing, or to check
3862 *
3863 * @since 5.3.8
3864 *
3865 * @param $event (string)
3866 * @param context (string)
3867 * @return (boolean)
3868 */
3869 function acf_is_doing( $event = '', $context = '' ) {
3870
3871 // vars
3872 $doing = false;
3873
3874 // task
3875 if ( acf_get_setting( 'doing' ) === $event ) {
3876 $doing = true;
3877 }
3878
3879 // context
3880 if ( $context && acf_get_setting( 'doing_context' ) !== $context ) {
3881 $doing = false;
3882 }
3883
3884 // return
3885 return $doing;
3886 }
3887
3888 /**
3889 * acf_is_plugin_active
3890 *
3891 * This function will return true if the ACF plugin is active
3892 * - May be included within a theme or other plugin
3893 *
3894 * @since 5.4.0
3895 *
3896 * @param $basename (int)
3897 * @return $post_id (int)
3898 */
3899 function acf_is_plugin_active() {
3900
3901 // vars
3902 $basename = acf_get_setting( 'basename' );
3903
3904 // ensure is_plugin_active() exists (not on frontend)
3905 if ( ! function_exists( 'is_plugin_active' ) ) {
3906 include_once ABSPATH . 'wp-admin/includes/plugin.php';
3907 }
3908
3909 // return
3910 return is_plugin_active( $basename );
3911 }
3912
3913 /**
3914 * acf_send_ajax_results
3915 *
3916 * This function will print JSON data for a Select2 AJAX query
3917 *
3918 * @since 5.4.0
3919 *
3920 * @param $response (array)
3921 * @return n/a
3922 */
3923 function acf_send_ajax_results( $response ) {
3924
3925 // validate
3926 $response = wp_parse_args(
3927 $response,
3928 array(
3929 'results' => array(),
3930 'more' => false,
3931 'limit' => 0,
3932 )
3933 );
3934
3935 // limit
3936 if ( $response['limit'] && $response['results'] ) {
3937
3938 // vars
3939 $total = 0;
3940
3941 foreach ( $response['results'] as $result ) {
3942
3943 // parent
3944 ++$total;
3945
3946 // children
3947 if ( ! empty( $result['children'] ) ) {
3948 $total += count( $result['children'] );
3949 }
3950 }
3951
3952 // calc
3953 if ( $total >= $response['limit'] ) {
3954 $response['more'] = true;
3955 }
3956 }
3957
3958 // return
3959 wp_send_json( $response );
3960 }
3961
3962 /**
3963 * acf_is_sequential_array
3964 *
3965 * This function will return true if the array contains only numeric keys
3966 *
3967 * @source http://stackoverflow.com/questions/173400/how-to-check-if-php-array-is-associative-or-sequential
3968 *
3969 * @since 5.4.0
3970 *
3971 * @param $array (array)
3972 * @return (boolean)
3973 */
3974 function acf_is_sequential_array( $array ) {
3975
3976 // bail early if not array
3977 if ( ! is_array( $array ) ) {
3978 return false;
3979 }
3980
3981 // loop
3982 foreach ( $array as $key => $value ) {
3983
3984 // bail early if is string
3985 if ( is_string( $key ) ) {
3986 return false;
3987 }
3988 }
3989
3990 // return
3991 return true;
3992 }
3993
3994 /**
3995 * acf_is_associative_array
3996 *
3997 * This function will return true if the array contains one or more string keys
3998 *
3999 * @source http://stackoverflow.com/questions/173400/how-to-check-if-php-array-is-associative-or-sequential
4000 *
4001 * @since 5.4.0
4002 *
4003 * @param $array (array)
4004 * @return (boolean)
4005 */
4006 function acf_is_associative_array( $array ) {
4007
4008 // bail early if not array
4009 if ( ! is_array( $array ) ) {
4010 return false;
4011 }
4012
4013 // loop
4014 foreach ( $array as $key => $value ) {
4015
4016 // bail early if is string
4017 if ( is_string( $key ) ) {
4018 return true;
4019 }
4020 }
4021
4022 // return
4023 return false;
4024 }
4025
4026 /**
4027 * acf_add_array_key_prefix
4028 *
4029 * This function will add a prefix to all array keys
4030 * Useful to preserve numeric keys when performing array_multisort
4031 *
4032 * @since 5.4.0
4033 *
4034 * @param $array (array)
4035 * @param $prefix (string)
4036 * @return (array)
4037 */
4038 function acf_add_array_key_prefix( $array, $prefix ) {
4039
4040 // vars
4041 $array2 = array();
4042
4043 // loop
4044 foreach ( $array as $k => $v ) {
4045 $k2 = $prefix . $k;
4046 $array2[ $k2 ] = $v;
4047 }
4048
4049 // return
4050 return $array2;
4051 }
4052
4053 /**
4054 * acf_remove_array_key_prefix
4055 *
4056 * This function will remove a prefix to all array keys
4057 * Useful to preserve numeric keys when performing array_multisort
4058 *
4059 * @since 5.4.0
4060 *
4061 * @param $array (array)
4062 * @param $prefix (string)
4063 * @return (array)
4064 */
4065 function acf_remove_array_key_prefix( $array, $prefix ) {
4066
4067 // vars
4068 $array2 = array();
4069 $l = strlen( $prefix );
4070
4071 // loop
4072 foreach ( $array as $k => $v ) {
4073 $k2 = ( substr( $k, 0, $l ) === $prefix ) ? substr( $k, $l ) : $k;
4074 $array2[ $k2 ] = $v;
4075 }
4076
4077 // return
4078 return $array2;
4079 }
4080
4081 /**
4082 * This function will remove the proticol from a url
4083 * Used to allow licenses to remain active if a site is switched to https
4084 *
4085 * @since 5.5.4
4086 *
4087 * @param string $url The URL to strip the protocol from.
4088 * @return string
4089 */
4090 function acf_strip_protocol( $url ) {
4091
4092 // strip the protocol
4093 return str_replace( array( 'http://', 'https://' ), '', $url );
4094 }
4095
4096 /**
4097 * This function will connect an attacment (image etc) to the post
4098 * Used to connect attachements uploaded directly to media that have not been attaced to a post
4099 *
4100 * @since 5.8.0 Added filter to prevent connection.
4101 * @since 5.5.4
4102 *
4103 * @param integer $attachment_id The attachment ID.
4104 * @param integer $post_id The post ID.
4105 * @return boolean True if attachment was connected.
4106 */
4107 function acf_connect_attachment_to_post( $attachment_id = 0, $post_id = 0 ) {
4108
4109 // bail early if $attachment_id is not valid.
4110 if ( ! $attachment_id || ! is_numeric( $attachment_id ) ) {
4111 return false;
4112 }
4113
4114 // bail early if $post_id is not valid.
4115 if ( ! $post_id || ! is_numeric( $post_id ) ) {
4116 return false;
4117 }
4118
4119 /**
4120 * Filters whether or not to connect the attachment.
4121 *
4122 * @since 5.8.0
4123 *
4124 * @param bool $bool Returning false will prevent the connection. Default true.
4125 * @param int $attachment_id The attachment ID.
4126 * @param int $post_id The post ID.
4127 */
4128 if ( ! apply_filters( 'acf/connect_attachment_to_post', true, $attachment_id, $post_id ) ) {
4129 return false;
4130 }
4131
4132 // vars
4133 $post = get_post( $attachment_id );
4134
4135 // Check if is valid post.
4136 if ( $post && $post->post_type == 'attachment' && $post->post_parent == 0 ) {
4137
4138 // update
4139 wp_update_post(
4140 array(
4141 'ID' => $post->ID,
4142 'post_parent' => $post_id,
4143 )
4144 );
4145
4146 // return
4147 return true;
4148 }
4149
4150 // return
4151 return true;
4152 }
4153
4154 /**
4155 * Encrypts a string using PHP.
4156 * https://bhoover.com/using-php-openssl_encrypt-openssl_decrypt-encrypt-decrypt-data/
4157 *
4158 * @since 5.5.8
4159 *
4160 * @param string $data The data to encrypt.
4161 * @return string|false Encrypted string, or false if encryption fails.
4162 */
4163 function acf_encrypt( $data = '' ) {
4164
4165 if ( ! function_exists( 'openssl_encrypt' ) ) {
4166 return false;
4167 }
4168
4169 $key = wp_hash( 'acf_encrypt' );
4170 $mac_key = wp_hash( 'acf_encrypt_mac' );
4171 $iv = openssl_random_pseudo_bytes( openssl_cipher_iv_length( 'aes-256-cbc' ) );
4172
4173 $encrypted_data = openssl_encrypt( $data, 'aes-256-cbc', $key, 0, $iv );
4174
4175 $payload = $encrypted_data . '::' . $iv;
4176 $hmac = hash_hmac( 'sha256', $payload, $mac_key, true );
4177
4178 return base64_encode( $payload . $hmac );
4179 }
4180
4181 /**
4182 * Decrypts an encrypted string using PHP.
4183 * https://bhoover.com/using-php-openssl_encrypt-openssl_decrypt-encrypt-decrypt-data/
4184 *
4185 * @since 5.5.8
4186 *
4187 * @param string $data The string to decrypt.
4188 * @return string|false Decrypted string, or false if the payload is malformed or decryption fails.
4189 */
4190 function acf_decrypt( $data = '' ) {
4191
4192 if ( ! function_exists( 'openssl_decrypt' ) ) {
4193 return false;
4194 }
4195
4196 $raw = base64_decode( (string) $data, true );
4197 if ( false === $raw ) {
4198 return false;
4199 }
4200
4201 if ( strlen( $raw ) <= 32 ) {
4202 return false;
4203 }
4204
4205 $mac_key = wp_hash( 'acf_encrypt_mac' );
4206 $hmac = substr( $raw, -32 );
4207 $payload = substr( $raw, 0, -32 );
4208
4209 $expected = hash_hmac( 'sha256', $payload, $mac_key, true );
4210 if ( ! hash_equals( $expected, $hmac ) ) {
4211 return false;
4212 }
4213
4214 if ( strpos( $payload, '::' ) === false ) {
4215 return false;
4216 }
4217
4218 $key = wp_hash( 'acf_encrypt' );
4219
4220 list( $encrypted_data, $iv ) = explode( '::', $payload, 2 );
4221
4222 return openssl_decrypt( $encrypted_data, 'aes-256-cbc', $key, 0, $iv );
4223 }
4224
4225 /**
4226 * acf_parse_markdown
4227 *
4228 * A very basic regex-based Markdown parser function based off [slimdown](https://gist.github.com/jbroadway/2836900).
4229 *
4230 * @since 5.7.2
4231 *
4232 * @param string $text The string to parse.
4233 * @return string
4234 */
4235 function acf_parse_markdown( $text = '' ) {
4236
4237 // trim
4238 $text = trim( $text );
4239
4240 // rules
4241 $rules = array(
4242 '/=== (.+?) ===/' => '<h2>$1</h2>', // headings
4243 '/== (.+?) ==/' => '<h3>$1</h3>', // headings
4244 '/= (.+?) =/' => '<h4>$1</h4>', // headings
4245 '/\[([^\[]+)\]\(([^\)]+)\)/' => '<a href="$2">$1</a>', // links
4246 '/(\*\*)(.*?)\1/' => '<strong>$2</strong>', // bold
4247 '/(\*)(.*?)\1/' => '<em>$2</em>', // intalic
4248 '/`(.*?)`/' => '<code>$1</code>', // inline code
4249 '/\n\*(.*)/' => "\n<ul>\n\t<li>$1</li>\n</ul>", // ul lists
4250 '/\n[0-9]+\.(.*)/' => "\n<ol>\n\t<li>$1</li>\n</ol>", // ol lists
4251 '/<\/ul>\s?<ul>/' => '', // fix extra ul
4252 '/<\/ol>\s?<ol>/' => '', // fix extra ol
4253 );
4254 foreach ( $rules as $k => $v ) {
4255 $text = preg_replace( $k, $v, $text );
4256 }
4257
4258 // autop
4259 $text = wpautop( $text );
4260
4261 // return
4262 return $text;
4263 }
4264
4265 /**
4266 * acf_get_sites
4267 *
4268 * Returns an array of sites for a network.
4269 *
4270 * @since 5.4.0
4271 *
4272 * @param void
4273 * @return array
4274 */
4275 function acf_get_sites() {
4276 $results = array();
4277 $sites = get_sites( array( 'number' => 0 ) );
4278 if ( $sites ) {
4279 foreach ( $sites as $site ) {
4280 $results[] = get_site( $site )->to_array();
4281 }
4282 }
4283 return $results;
4284 }
4285
4286 /**
4287 * acf_convert_rules_to_groups
4288 *
4289 * Converts an array of rules from ACF4 to an array of groups for ACF5
4290 *
4291 * @since 5.7.4
4292 *
4293 * @param array $rules An array of rules.
4294 * @param string $anyorall The anyorall setting used in ACF4. Defaults to 'any'.
4295 * @return array
4296 */
4297 function acf_convert_rules_to_groups( $rules, $anyorall = 'any' ) {
4298
4299 // vars
4300 $groups = array();
4301 $index = 0;
4302
4303 // loop
4304 foreach ( $rules as $rule ) {
4305
4306 // extract vars
4307 $group = acf_extract_var( $rule, 'group_no' );
4308 $order = acf_extract_var( $rule, 'order_no' );
4309
4310 // calculate group if not defined
4311 if ( $group === null ) {
4312 $group = $index;
4313
4314 // use $anyorall to determine if a new group is needed
4315 if ( $anyorall == 'any' ) {
4316 ++$index;
4317 }
4318 }
4319
4320 // calculate order if not defined
4321 if ( $order === null ) {
4322 $order = isset( $groups[ $group ] ) ? count( $groups[ $group ] ) : 0;
4323 }
4324
4325 // append to group
4326 $groups[ $group ][ $order ] = $rule;
4327
4328 // sort groups
4329 ksort( $groups[ $group ] );
4330 }
4331
4332 // sort groups
4333 ksort( $groups );
4334
4335 // return
4336 return $groups;
4337 }
4338
4339 /**
4340 * acf_register_ajax
4341 *
4342 * Regsiters an ajax callback.
4343 *
4344 * @since 5.7.7
4345 *
4346 * @param string $name The ajax action name.
4347 * @param array $callback The callback function or array.
4348 * @param boolean $public Whether to allow access to non logged in users.
4349 * @return void
4350 */
4351 function acf_register_ajax( $name = '', $callback = false, $public = false ) {
4352
4353 // vars
4354 $action = "acf/ajax/$name";
4355
4356 // add action for logged-in users
4357 add_action( "wp_ajax_$action", $callback );
4358
4359 // add action for non logged-in users
4360 if ( $public ) {
4361 add_action( "wp_ajax_nopriv_$action", $callback );
4362 }
4363 }
4364
4365 /**
4366 * acf_str_camel_case
4367 *
4368 * Converts a string into camelCase.
4369 * Thanks to https://stackoverflow.com/questions/31274782/convert-array-keys-from-underscore-case-to-camelcase-recursively
4370 *
4371 * @since 5.8.0
4372 *
4373 * @param string $string The string ot convert.
4374 * @return string
4375 */
4376 function acf_str_camel_case( $string = '' ) {
4377 return lcfirst( str_replace( ' ', '', ucwords( str_replace( '_', ' ', $string ) ) ) );
4378 }
4379
4380 /**
4381 * acf_array_camel_case
4382 *
4383 * Converts all aray keys to camelCase.
4384 *
4385 * @since 5.8.0
4386 *
4387 * @param array $array The array to convert.
4388 * @return array
4389 */
4390 function acf_array_camel_case( $array = array() ) {
4391 $array2 = array();
4392 foreach ( $array as $k => $v ) {
4393 $array2[ acf_str_camel_case( $k ) ] = $v;
4394 }
4395 return $array2;
4396 }
4397
4398 /**
4399 * Returns true if the current screen is using the block editor.
4400 *
4401 * @since 5.8.0
4402 *
4403 * @return boolean
4404 */
4405 function acf_is_block_editor() {
4406 if ( function_exists( 'get_current_screen' ) ) {
4407 $screen = get_current_screen();
4408 if ( $screen && method_exists( $screen, 'is_block_editor' ) ) {
4409 return $screen->is_block_editor();
4410 }
4411 }
4412 return false;
4413 }
4414
4415 /**
4416 * Return an array of the WordPress reserved terms
4417 *
4418 * @since 6.1
4419 *
4420 * @return array The WordPress reserved terms list.
4421 */
4422 function acf_get_wp_reserved_terms() {
4423 return array( 'action', 'attachment', 'attachment_id', 'author', 'author_name', 'calendar', 'cat', 'category', 'category__and', 'category__in', 'category__not_in', 'category_name', 'comments_per_page', 'comments_popup', 'custom', 'customize_messenger_channel', 'customized', 'cpage', 'day', 'debug', 'embed', 'error', 'exact', 'feed', 'fields', 'hour', 'link', 'link_category', 'm', 'minute', 'monthnum', 'more', 'name', 'nav_menu', 'nonce', 'nopaging', 'offset', 'order', 'orderby', 'p', 'page', 'page_id', 'paged', 'pagename', 'pb', 'perm', 'post', 'post__in', 'post__not_in', 'post_format', 'post_mime_type', 'post_status', 'post_tag', 'post_type', 'posts', 'posts_per_archive_page', 'posts_per_page', 'preview', 'robots', 's', 'search', 'second', 'sentence', 'showposts', 'static', 'status', 'subpost', 'subpost_id', 'tag', 'tag__and', 'tag__in', 'tag__not_in', 'tag_id', 'tag_slug__and', 'tag_slug__in', 'taxonomy', 'tb', 'term', 'terms', 'theme', 'themes', 'title', 'type', 'types', 'w', 'withcomments', 'withoutcomments', 'year' );
4424 }
4425
4426 /**
4427 * Detect if we're on a multisite subsite.
4428 *
4429 * @since 6.2.4
4430 *
4431 * @return boolean true if we're in a multisite install and not on the main site
4432 */
4433 function acf_is_multisite_sub_site() {
4434 if ( is_multisite() && ! is_main_site() ) {
4435 return true;
4436 }
4437 return false;
4438 }
4439
4440 /**
4441 * Detect if we're on a multisite main site.
4442 *
4443 * @since 6.2.4
4444 *
4445 * @return boolean true if we're in a multisite install and on the main site
4446 */
4447 function acf_is_multisite_main_site() {
4448 if ( is_multisite() && is_main_site() ) {
4449 return true;
4450 }
4451 return false;
4452 }
4453
4454 /**
4455 * Allow filterable permissions metabox callbacks.
4456 *
4457 * @since 6.3.10
4458 *
4459 * @param boolean $enable_meta_box_cb_edit Can the current user edit metabox callbacks.
4460 * @return boolean
4461 */
4462 function acf_settings_enable_meta_box_cb_edit( $enable_meta_box_cb_edit ): bool {
4463 if ( ! is_super_admin() ) {
4464 return false;
4465 }
4466
4467 return (bool) $enable_meta_box_cb_edit;
4468 }
4469 add_filter( 'acf/settings/enable_meta_box_cb_edit', 'acf_settings_enable_meta_box_cb_edit', 1 );
4470