PluginProbe ʕ •ᴥ•ʔ
Advanced Custom Fields (ACF®) / 6.8.7
Advanced Custom Fields (ACF®) v6.8.7
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 4 weeks ago api-template.php 3 months ago api-term.php 5 months ago index.php 2 years ago
api-helpers.php
4456 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 $real_mime = wp_get_image_mime( $attachment['tmp_name'] );
3200
3201 if ( ! $real_mime || empty( wp_match_mime_types( 'image', $real_mime ) ) ) {
3202 $errors['invalid_image'] = $error_message;
3203 }
3204
3205 return acf_apply_validate_is_image_attachment_filters( $errors, $file, $attachment, $field, $context );
3206 }
3207
3208 /**
3209 * Validates image attachment data when preparing for the media library.
3210 *
3211 * @since 6.8.7
3212 *
3213 * @param array $errors Validation errors.
3214 * @param array $file Normalized file data.
3215 * @param array $attachment Raw attachment data for the current context.
3216 * @param array $field The field array.
3217 * @param string $context The validation context.
3218 * @param string $error_message The error message to use when validation fails.
3219 * @return array
3220 */
3221 function acf_validate_is_image_attachment_prepare( $errors, $file, $attachment, $field, $context, $error_message ) {
3222 $attachment_id = (int) acf_maybe_get( $attachment, 'id', 0 );
3223
3224 if ( $attachment_id ) {
3225 if ( ! wp_attachment_is_image( $attachment_id ) ) {
3226 $errors['invalid_image'] = $error_message;
3227 }
3228
3229 return acf_apply_validate_is_image_attachment_filters( $errors, $file, $attachment, $field, $context );
3230 }
3231
3232 $mime = acf_maybe_get( $attachment, 'mime', acf_maybe_get( $attachment, 'type', '' ) );
3233
3234 if ( $mime && empty( wp_match_mime_types( 'image', $mime ) ) ) {
3235 $errors['invalid_image'] = $error_message;
3236 }
3237
3238 return acf_apply_validate_is_image_attachment_filters( $errors, $file, $attachment, $field, $context );
3239 }
3240
3241 /**
3242 * Validates image attachment data during basic uploader pre-save checks.
3243 *
3244 * @since 6.8.7
3245 *
3246 * @param array $errors Validation errors.
3247 * @param array $file Normalized file data.
3248 * @param array $attachment Raw attachment data for the current context.
3249 * @param array $field The field array.
3250 * @param string $context The validation context.
3251 * @param string $error_message The error message to use when validation fails.
3252 * @return array
3253 */
3254 function acf_validate_is_image_attachment_basic_upload( $errors, $file, $attachment, $field, $context, $error_message ) {
3255 $mime = acf_maybe_get( $attachment, 'type', '' );
3256
3257 if ( $mime && empty( wp_match_mime_types( 'image', $mime ) ) ) {
3258 $errors['invalid_image'] = $error_message;
3259 }
3260
3261 return acf_apply_validate_is_image_attachment_filters( $errors, $file, $attachment, $field, $context );
3262 }
3263
3264 /**
3265 * Validates that an attachment is an image for image and gallery fields.
3266 *
3267 * @since 6.8.7
3268 *
3269 * @param array $errors Existing validation errors.
3270 * @param array $file Normalized file data.
3271 * @param array $attachment Raw attachment data for the current context.
3272 * @param array $field The field array.
3273 * @param string $context The validation context.
3274 * @return array
3275 */
3276 function acf_validate_is_image_attachment( $errors, $file, $attachment, $field, $context ) {
3277 if ( ! acf_is_image_field( $field ) ) {
3278 return $errors;
3279 }
3280
3281 /**
3282 * Filters the error message used when an attachment is not a valid image.
3283 *
3284 * @since 6.8.7
3285 *
3286 * @param string $error_message The default error message.
3287 * @param array $field The field array.
3288 * @param string $context The current context (uploading, preparing, basic_upload).
3289 * @param array $file An array of data for a single file.
3290 * @param array $attachment An array of attachment data which differs based on the context.
3291 */
3292 $error_message = apply_filters( 'acf/validate_is_image_attachment/error_message', __( 'File must be a valid image.', 'acf' ), $field, $context, $file, $attachment );
3293
3294 if ( 'upload' === $context ) {
3295 return acf_validate_is_image_attachment_upload( $errors, $file, $attachment, $field, $context, $error_message );
3296 }
3297
3298 if ( 'prepare' === $context ) {
3299 return acf_validate_is_image_attachment_prepare( $errors, $file, $attachment, $field, $context, $error_message );
3300 }
3301
3302 return acf_validate_is_image_attachment_basic_upload( $errors, $file, $attachment, $field, $context, $error_message );
3303 }
3304
3305 /**
3306 * acf_validate_attachment
3307 *
3308 * This function will validate an attachment based on a field's restrictions and return an array of errors
3309 *
3310 * @since 5.2.3
3311 *
3312 * @param $attachment (array) attachment data. Changes based on context
3313 * @param $field (array) field settings containing restrictions
3314 * @param context (string) $file is different when uploading / preparing
3315 * @return $errors (array)
3316 */
3317 function acf_validate_attachment( $attachment, $field, $context = 'prepare' ) {
3318
3319 // vars
3320 $errors = array();
3321 $file = array(
3322 'type' => '',
3323 'width' => 0,
3324 'height' => 0,
3325 'size' => 0,
3326 );
3327
3328 // upload
3329 if ( $context == 'upload' ) {
3330
3331 // vars
3332 $file['type'] = pathinfo( $attachment['name'], PATHINFO_EXTENSION );
3333 $file['size'] = filesize( $attachment['tmp_name'] );
3334
3335 if ( acf_is_image_field( $field ) || strpos( $attachment['type'], 'image' ) !== false ) {
3336 $size = function_exists( 'wp_getimagesize' ) ? wp_getimagesize( $attachment['tmp_name'] ) : getimagesize( $attachment['tmp_name'] );
3337
3338 if ( $size ) {
3339 $file['width'] = acf_maybe_get( $size, 0 );
3340 $file['height'] = acf_maybe_get( $size, 1 );
3341 }
3342 }
3343
3344 // prepare
3345 } elseif ( $context == 'prepare' ) {
3346 $use_path = isset( $attachment['filename'] ) ? $attachment['filename'] : $attachment['url'];
3347 $file['type'] = pathinfo( $use_path, PATHINFO_EXTENSION );
3348 $file['size'] = acf_maybe_get( $attachment, 'filesizeInBytes', 0 );
3349 $file['width'] = acf_maybe_get( $attachment, 'width', 0 );
3350 $file['height'] = acf_maybe_get( $attachment, 'height', 0 );
3351
3352 // custom
3353 } else {
3354 $file = array_merge( $file, $attachment );
3355 $use_path = isset( $attachment['filename'] ) ? $attachment['filename'] : $attachment['url'];
3356 $file['type'] = pathinfo( $use_path, PATHINFO_EXTENSION );
3357 }
3358
3359 // image
3360 if ( $file['width'] || $file['height'] ) {
3361
3362 // width
3363 $min_width = (int) acf_maybe_get( $field, 'min_width', 0 );
3364 $max_width = (int) acf_maybe_get( $field, 'max_width', 0 );
3365
3366 if ( $file['width'] ) {
3367 if ( $min_width && $file['width'] < $min_width ) {
3368
3369 // min width
3370 $errors['min_width'] = sprintf( __( 'Image width must be at least %dpx.', 'acf' ), $min_width );
3371 } elseif ( $max_width && $file['width'] > $max_width ) {
3372
3373 // min width
3374 $errors['max_width'] = sprintf( __( 'Image width must not exceed %dpx.', 'acf' ), $max_width );
3375 }
3376 }
3377
3378 // height
3379 $min_height = (int) acf_maybe_get( $field, 'min_height', 0 );
3380 $max_height = (int) acf_maybe_get( $field, 'max_height', 0 );
3381
3382 if ( $file['height'] ) {
3383 if ( $min_height && $file['height'] < $min_height ) {
3384
3385 // min height
3386 $errors['min_height'] = sprintf( __( 'Image height must be at least %dpx.', 'acf' ), $min_height );
3387 } elseif ( $max_height && $file['height'] > $max_height ) {
3388
3389 // min height
3390 $errors['max_height'] = sprintf( __( 'Image height must not exceed %dpx.', 'acf' ), $max_height );
3391 }
3392 }
3393 }
3394
3395 // file size
3396 if ( $file['size'] ) {
3397 $min_size = acf_maybe_get( $field, 'min_size', 0 );
3398 $max_size = acf_maybe_get( $field, 'max_size', 0 );
3399
3400 if ( $min_size && $file['size'] < acf_get_filesize( $min_size ) ) {
3401
3402 // min width
3403 $errors['min_size'] = sprintf( __( 'File size must be at least %s.', 'acf' ), acf_format_filesize( $min_size ) );
3404 } elseif ( $max_size && $file['size'] > acf_get_filesize( $max_size ) ) {
3405
3406 // min width
3407 $errors['max_size'] = sprintf( __( 'File size must not exceed %s.', 'acf' ), acf_format_filesize( $max_size ) );
3408 }
3409 }
3410
3411 // file type
3412 if ( $file['type'] ) {
3413 $mime_types = acf_maybe_get( $field, 'mime_types', '' );
3414
3415 // lower case
3416 $file['type'] = strtolower( $file['type'] );
3417 $mime_types = strtolower( $mime_types );
3418
3419 // explode
3420 $mime_types = str_replace( array( ' ', '.' ), '', $mime_types );
3421 $mime_types = explode( ',', $mime_types ); // split pieces
3422 $mime_types = array_filter( $mime_types ); // remove empty pieces
3423
3424 if ( ! empty( $mime_types ) && ! in_array( $file['type'], $mime_types ) ) {
3425
3426 // glue together last 2 types
3427 if ( count( $mime_types ) > 1 ) {
3428 $last1 = array_pop( $mime_types );
3429 $last2 = array_pop( $mime_types );
3430
3431 $mime_types[] = $last2 . ' ' . __( 'or', 'acf' ) . ' ' . $last1;
3432 }
3433
3434 $errors['mime_types'] = sprintf( __( 'File type must be %s.', 'acf' ), implode( ', ', $mime_types ) );
3435 }
3436 }
3437
3438 /**
3439 * Filters the errors for a file before it is uploaded or displayed in the media modal.
3440 *
3441 * @since 5.2.3
3442 *
3443 * @param array $errors An array of errors.
3444 * @param array $file An array of data for a single file.
3445 * @param array $attachment An array of attachment data which differs based on the context.
3446 * @param array $field The field array.
3447 * @param string $context The curent context (uploading, preparing)
3448 */
3449 $errors = apply_filters( "acf/validate_attachment/type={$field['type']}", $errors, $file, $attachment, $field, $context );
3450 $errors = apply_filters( "acf/validate_attachment/name={$field['_name']}", $errors, $file, $attachment, $field, $context );
3451 $errors = apply_filters( "acf/validate_attachment/key={$field['key']}", $errors, $file, $attachment, $field, $context );
3452 $errors = apply_filters( 'acf/validate_attachment', $errors, $file, $attachment, $field, $context );
3453
3454 // return
3455 return $errors;
3456 }
3457
3458 /**
3459 * _acf_settings_uploader
3460 *
3461 * Dynamic logic for uploader setting
3462 *
3463 * @since 5.2.3
3464 *
3465 * @param $uploader (string)
3466 * @return $uploader
3467 */
3468
3469 add_filter( 'acf/settings/uploader', '_acf_settings_uploader' );
3470
3471 function _acf_settings_uploader( $uploader ) {
3472
3473 // if can't upload files
3474 if ( ! current_user_can( 'upload_files' ) ) {
3475 $uploader = 'basic';
3476 }
3477
3478 // return
3479 return $uploader;
3480 }
3481
3482 /**
3483 * acf_translate
3484 *
3485 * This function will translate a string using the new 'l10n_textdomain' setting
3486 * Also works for arrays which is great for fields - select -> choices
3487 *
3488 * @since 5.3.2
3489 *
3490 * @param $string (mixed) string or array containins strings to be translated
3491 * @return $string
3492 */
3493 function acf_translate( $string ) {
3494
3495 // vars
3496 $l10n = acf_get_setting( 'l10n' );
3497 $textdomain = acf_get_setting( 'l10n_textdomain' );
3498
3499 // bail early if not enabled
3500 if ( ! $l10n ) {
3501 return $string;
3502 }
3503
3504 // bail early if no textdomain
3505 if ( ! $textdomain ) {
3506 return $string;
3507 }
3508
3509 // is array
3510 if ( is_array( $string ) ) {
3511 return array_map( 'acf_translate', $string );
3512 }
3513
3514 // bail early if not string
3515 if ( ! is_string( $string ) ) {
3516 return $string;
3517 }
3518
3519 // bail early if empty
3520 if ( $string === '' ) {
3521 return $string;
3522 }
3523
3524 // allow for var_export export
3525 if ( acf_get_setting( 'l10n_var_export' ) ) {
3526
3527 // bail early if already translated
3528 if ( substr( $string, 0, 7 ) === '!!__(!!' ) {
3529 return $string;
3530 }
3531
3532 // return
3533 return "!!__(!!'" . $string . "!!', !!'" . $textdomain . "!!')!!";
3534 }
3535
3536 // vars
3537 return __( $string, $textdomain );
3538 }
3539
3540 /**
3541 * acf_maybe_add_action
3542 *
3543 * This function will determine if the action has already run before adding / calling the function
3544 *
3545 * @since 5.3.2
3546 *
3547 * @param $post_id (int)
3548 * @return $post_id (int)
3549 */
3550 function acf_maybe_add_action( $tag, $function_to_add, $priority = 10, $accepted_args = 1 ) {
3551
3552 // if action has already run, execute it
3553 // - if currently doing action, allow $tag to be added as per usual to allow $priority ordering needed for 3rd party asset compatibility
3554 if ( did_action( $tag ) && ! doing_action( $tag ) ) {
3555 call_user_func( $function_to_add );
3556
3557 // if action has not yet run, add it
3558 } else {
3559 add_action( $tag, $function_to_add, $priority, $accepted_args );
3560 }
3561 }
3562
3563 /**
3564 * acf_is_row_collapsed
3565 *
3566 * This function will return true if the field's row is collapsed
3567 *
3568 * @since 5.3.2
3569 *
3570 * @param $post_id (int)
3571 * @return $post_id (int)
3572 */
3573 function acf_is_row_collapsed( $field_key = '', $row_index = 0 ) {
3574
3575 // collapsed
3576 $collapsed = acf_get_user_setting( 'collapsed_' . $field_key, '' );
3577
3578 // cookie fallback ( version < 5.3.2 )
3579 if ( $collapsed === '' ) {
3580 $collapsed = acf_extract_var( $_COOKIE, "acf_collapsed_{$field_key}", '' );
3581 $collapsed = str_replace( '|', ',', $collapsed );
3582
3583 // update
3584 acf_update_user_setting( 'collapsed_' . $field_key, $collapsed );
3585 }
3586
3587 // explode
3588 $collapsed = explode( ',', $collapsed );
3589 $collapsed = array_filter( $collapsed, 'is_numeric' );
3590
3591 // collapsed class
3592 return in_array( $row_index, $collapsed );
3593 }
3594
3595 /**
3596 * Return an image tag for the provided attachment ID
3597 *
3598 * @since 5.5.0
3599 * @deprecated 6.3.2
3600 *
3601 * @param integer $attachment_id The attachment ID
3602 * @param string $size The image size to use in the image tag.
3603 * @return false
3604 */
3605 function acf_get_attachment_image( $attachment_id = 0, $size = 'thumbnail' ) {
3606 // report function as deprecated
3607 _deprecated_function( __FUNCTION__, '6.3.2' );
3608 return false;
3609 }
3610
3611 /**
3612 * acf_get_post_thumbnail
3613 *
3614 * This function will return a thumbail image url for a given post
3615 *
3616 * @since 5.3.8
3617 *
3618 * @param $post (obj)
3619 * @param $size (mixed)
3620 * @return (string)
3621 */
3622 function acf_get_post_thumbnail( $post = null, $size = 'thumbnail' ) {
3623
3624 // vars
3625 $data = array(
3626 'url' => '',
3627 'type' => '',
3628 'html' => '',
3629 );
3630
3631 // post
3632 $post = get_post( $post );
3633
3634 // bail early if no post
3635 if ( ! $post ) {
3636 return $data;
3637 }
3638
3639 // vars
3640 $thumb_id = $post->ID;
3641 $mime_type = acf_maybe_get( explode( '/', $post->post_mime_type ), 0 );
3642
3643 // attachment
3644 if ( $post->post_type === 'attachment' ) {
3645
3646 // change $thumb_id
3647 if ( $mime_type === 'audio' || $mime_type === 'video' ) {
3648 $thumb_id = get_post_thumbnail_id( $post->ID );
3649 }
3650
3651 // post
3652 } else {
3653 $thumb_id = get_post_thumbnail_id( $post->ID );
3654 }
3655
3656 // try url
3657 $data['url'] = wp_get_attachment_image_src( $thumb_id, $size );
3658 $data['url'] = acf_maybe_get( $data['url'], 0 );
3659
3660 // default icon
3661 if ( ! $data['url'] && $post->post_type === 'attachment' ) {
3662 $data['url'] = wp_mime_type_icon( $post->ID );
3663 $data['type'] = 'icon';
3664 }
3665
3666 // html
3667 $data['html'] = '<img src="' . $data['url'] . '" alt="" />';
3668
3669 // return
3670 return $data;
3671 }
3672
3673 /**
3674 * acf_get_browser
3675 *
3676 * Returns the name of the current browser.
3677 *
3678 * @since 5.0.0
3679 *
3680 * @param void
3681 * @return string
3682 */
3683 function acf_get_browser() {
3684
3685 // Check server var.
3686 if ( isset( $_SERVER['HTTP_USER_AGENT'] ) ) {
3687 $agent = sanitize_text_field( $_SERVER['HTTP_USER_AGENT'] );
3688
3689 // Loop over search terms.
3690 $browsers = array(
3691 'Firefox' => 'firefox',
3692 'Trident' => 'msie',
3693 'MSIE' => 'msie',
3694 'Edge' => 'edge',
3695 'Chrome' => 'chrome',
3696 'Safari' => 'safari',
3697 );
3698 foreach ( $browsers as $k => $v ) {
3699 if ( strpos( $agent, $k ) !== false ) {
3700 return $v;
3701 }
3702 }
3703 }
3704
3705 // Return default.
3706 return '';
3707 }
3708
3709 /**
3710 * acf_is_ajax
3711 *
3712 * This function will reutrn true if performing a wp ajax call
3713 *
3714 * @since 5.3.8
3715 *
3716 * @param n/a
3717 * @return (boolean)
3718 */
3719 function acf_is_ajax( $action = '' ) {
3720
3721 // vars
3722 $is_ajax = false;
3723
3724 // check if is doing ajax
3725 if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
3726 $is_ajax = true;
3727 }
3728
3729 // phpcs:disable WordPress.Security.NonceVerification.Missing
3730 // check $action
3731 if ( $action && acf_maybe_get( $_POST, 'action' ) !== $action ) {
3732 // phpcs:enable WordPress.Security.NonceVerification.Missing
3733 $is_ajax = false;
3734 }
3735
3736 // return
3737 return $is_ajax;
3738 }
3739
3740 /**
3741 * Returns a date value in a formatted string.
3742 *
3743 * @since 5.3.8
3744 *
3745 * @param string $value The date value to format.
3746 * @param string $format The format to use.
3747 * @return string
3748 */
3749 function acf_format_date( $value, $format ) {
3750 // Bail early if no value or value is not what we expect.
3751 if ( ! $value || ( ! is_string( $value ) && ! is_int( $value ) ) ) {
3752 return $value;
3753 }
3754
3755 // Numeric (either unix or YYYYMMDD).
3756 if ( is_numeric( $value ) && strlen( $value ) !== 8 ) {
3757 $unixtimestamp = $value;
3758 } else {
3759 $unixtimestamp = strtotime( $value );
3760 }
3761
3762 return date_i18n( $format, $unixtimestamp );
3763 }
3764
3765 /**
3766 * Previously, deletes the debug.log file.
3767 *
3768 * @since 5.7.10
3769 * @deprecated 6.2.7
3770 */
3771 function acf_clear_log() {
3772 _deprecated_function( __FUNCTION__, '6.2.7' );
3773 return false;
3774 }
3775
3776 /**
3777 * acf_log
3778 *
3779 * description
3780 *
3781 * @since 5.3.8
3782 *
3783 * @param $post_id (int)
3784 * @return $post_id (int)
3785 */
3786 function acf_log() {
3787
3788 // vars
3789 $args = func_get_args();
3790
3791 // loop
3792 foreach ( $args as $i => $arg ) {
3793
3794 // array | object
3795 if ( is_array( $arg ) || is_object( $arg ) ) {
3796 $arg = print_r( $arg, true );
3797
3798 // bool
3799 } elseif ( is_bool( $arg ) ) {
3800 $arg = 'bool(' . ( $arg ? 'true' : 'false' ) . ')';
3801 }
3802
3803 // update
3804 $args[ $i ] = $arg;
3805 }
3806
3807 // log
3808 error_log( implode( ' ', $args ) );
3809 }
3810
3811 /**
3812 * acf_dev_log
3813 *
3814 * Used to log variables only if ACF_DEV is defined
3815 *
3816 * @since 5.7.4
3817 *
3818 * @param mixed
3819 * @return void
3820 */
3821 function acf_dev_log() {
3822 if ( defined( 'ACF_DEV' ) && ACF_DEV ) {
3823 call_user_func_array( 'acf_log', func_get_args() );
3824 }
3825 }
3826
3827 /**
3828 * acf_doing
3829 *
3830 * This function will tell ACF what task it is doing
3831 *
3832 * @since 5.3.8
3833 *
3834 * @param $event (string)
3835 * @param context (string)
3836 * @return n/a
3837 */
3838 function acf_doing( $event = '', $context = '' ) {
3839
3840 acf_update_setting( 'doing', $event );
3841 acf_update_setting( 'doing_context', $context );
3842 }
3843
3844 /**
3845 * acf_is_doing
3846 *
3847 * This function can be used to state what ACF is doing, or to check
3848 *
3849 * @since 5.3.8
3850 *
3851 * @param $event (string)
3852 * @param context (string)
3853 * @return (boolean)
3854 */
3855 function acf_is_doing( $event = '', $context = '' ) {
3856
3857 // vars
3858 $doing = false;
3859
3860 // task
3861 if ( acf_get_setting( 'doing' ) === $event ) {
3862 $doing = true;
3863 }
3864
3865 // context
3866 if ( $context && acf_get_setting( 'doing_context' ) !== $context ) {
3867 $doing = false;
3868 }
3869
3870 // return
3871 return $doing;
3872 }
3873
3874 /**
3875 * acf_is_plugin_active
3876 *
3877 * This function will return true if the ACF plugin is active
3878 * - May be included within a theme or other plugin
3879 *
3880 * @since 5.4.0
3881 *
3882 * @param $basename (int)
3883 * @return $post_id (int)
3884 */
3885 function acf_is_plugin_active() {
3886
3887 // vars
3888 $basename = acf_get_setting( 'basename' );
3889
3890 // ensure is_plugin_active() exists (not on frontend)
3891 if ( ! function_exists( 'is_plugin_active' ) ) {
3892 include_once ABSPATH . 'wp-admin/includes/plugin.php';
3893 }
3894
3895 // return
3896 return is_plugin_active( $basename );
3897 }
3898
3899 /**
3900 * acf_send_ajax_results
3901 *
3902 * This function will print JSON data for a Select2 AJAX query
3903 *
3904 * @since 5.4.0
3905 *
3906 * @param $response (array)
3907 * @return n/a
3908 */
3909 function acf_send_ajax_results( $response ) {
3910
3911 // validate
3912 $response = wp_parse_args(
3913 $response,
3914 array(
3915 'results' => array(),
3916 'more' => false,
3917 'limit' => 0,
3918 )
3919 );
3920
3921 // limit
3922 if ( $response['limit'] && $response['results'] ) {
3923
3924 // vars
3925 $total = 0;
3926
3927 foreach ( $response['results'] as $result ) {
3928
3929 // parent
3930 ++$total;
3931
3932 // children
3933 if ( ! empty( $result['children'] ) ) {
3934 $total += count( $result['children'] );
3935 }
3936 }
3937
3938 // calc
3939 if ( $total >= $response['limit'] ) {
3940 $response['more'] = true;
3941 }
3942 }
3943
3944 // return
3945 wp_send_json( $response );
3946 }
3947
3948 /**
3949 * acf_is_sequential_array
3950 *
3951 * This function will return true if the array contains only numeric keys
3952 *
3953 * @source http://stackoverflow.com/questions/173400/how-to-check-if-php-array-is-associative-or-sequential
3954 *
3955 * @since 5.4.0
3956 *
3957 * @param $array (array)
3958 * @return (boolean)
3959 */
3960 function acf_is_sequential_array( $array ) {
3961
3962 // bail early if not array
3963 if ( ! is_array( $array ) ) {
3964 return false;
3965 }
3966
3967 // loop
3968 foreach ( $array as $key => $value ) {
3969
3970 // bail early if is string
3971 if ( is_string( $key ) ) {
3972 return false;
3973 }
3974 }
3975
3976 // return
3977 return true;
3978 }
3979
3980 /**
3981 * acf_is_associative_array
3982 *
3983 * This function will return true if the array contains one or more string keys
3984 *
3985 * @source http://stackoverflow.com/questions/173400/how-to-check-if-php-array-is-associative-or-sequential
3986 *
3987 * @since 5.4.0
3988 *
3989 * @param $array (array)
3990 * @return (boolean)
3991 */
3992 function acf_is_associative_array( $array ) {
3993
3994 // bail early if not array
3995 if ( ! is_array( $array ) ) {
3996 return false;
3997 }
3998
3999 // loop
4000 foreach ( $array as $key => $value ) {
4001
4002 // bail early if is string
4003 if ( is_string( $key ) ) {
4004 return true;
4005 }
4006 }
4007
4008 // return
4009 return false;
4010 }
4011
4012 /**
4013 * acf_add_array_key_prefix
4014 *
4015 * This function will add a prefix to all array keys
4016 * Useful to preserve numeric keys when performing array_multisort
4017 *
4018 * @since 5.4.0
4019 *
4020 * @param $array (array)
4021 * @param $prefix (string)
4022 * @return (array)
4023 */
4024 function acf_add_array_key_prefix( $array, $prefix ) {
4025
4026 // vars
4027 $array2 = array();
4028
4029 // loop
4030 foreach ( $array as $k => $v ) {
4031 $k2 = $prefix . $k;
4032 $array2[ $k2 ] = $v;
4033 }
4034
4035 // return
4036 return $array2;
4037 }
4038
4039 /**
4040 * acf_remove_array_key_prefix
4041 *
4042 * This function will remove a prefix to all array keys
4043 * Useful to preserve numeric keys when performing array_multisort
4044 *
4045 * @since 5.4.0
4046 *
4047 * @param $array (array)
4048 * @param $prefix (string)
4049 * @return (array)
4050 */
4051 function acf_remove_array_key_prefix( $array, $prefix ) {
4052
4053 // vars
4054 $array2 = array();
4055 $l = strlen( $prefix );
4056
4057 // loop
4058 foreach ( $array as $k => $v ) {
4059 $k2 = ( substr( $k, 0, $l ) === $prefix ) ? substr( $k, $l ) : $k;
4060 $array2[ $k2 ] = $v;
4061 }
4062
4063 // return
4064 return $array2;
4065 }
4066
4067 /**
4068 * This function will remove the proticol from a url
4069 * Used to allow licenses to remain active if a site is switched to https
4070 *
4071 * @since 5.5.4
4072 *
4073 * @param string $url The URL to strip the protocol from.
4074 * @return string
4075 */
4076 function acf_strip_protocol( $url ) {
4077
4078 // strip the protocol
4079 return str_replace( array( 'http://', 'https://' ), '', $url );
4080 }
4081
4082 /**
4083 * This function will connect an attacment (image etc) to the post
4084 * Used to connect attachements uploaded directly to media that have not been attaced to a post
4085 *
4086 * @since 5.8.0 Added filter to prevent connection.
4087 * @since 5.5.4
4088 *
4089 * @param integer $attachment_id The attachment ID.
4090 * @param integer $post_id The post ID.
4091 * @return boolean True if attachment was connected.
4092 */
4093 function acf_connect_attachment_to_post( $attachment_id = 0, $post_id = 0 ) {
4094
4095 // bail early if $attachment_id is not valid.
4096 if ( ! $attachment_id || ! is_numeric( $attachment_id ) ) {
4097 return false;
4098 }
4099
4100 // bail early if $post_id is not valid.
4101 if ( ! $post_id || ! is_numeric( $post_id ) ) {
4102 return false;
4103 }
4104
4105 /**
4106 * Filters whether or not to connect the attachment.
4107 *
4108 * @since 5.8.0
4109 *
4110 * @param bool $bool Returning false will prevent the connection. Default true.
4111 * @param int $attachment_id The attachment ID.
4112 * @param int $post_id The post ID.
4113 */
4114 if ( ! apply_filters( 'acf/connect_attachment_to_post', true, $attachment_id, $post_id ) ) {
4115 return false;
4116 }
4117
4118 // vars
4119 $post = get_post( $attachment_id );
4120
4121 // Check if is valid post.
4122 if ( $post && $post->post_type == 'attachment' && $post->post_parent == 0 ) {
4123
4124 // update
4125 wp_update_post(
4126 array(
4127 'ID' => $post->ID,
4128 'post_parent' => $post_id,
4129 )
4130 );
4131
4132 // return
4133 return true;
4134 }
4135
4136 // return
4137 return true;
4138 }
4139
4140 /**
4141 * Encrypts a string using PHP.
4142 * https://bhoover.com/using-php-openssl_encrypt-openssl_decrypt-encrypt-decrypt-data/
4143 *
4144 * @since 5.5.8
4145 *
4146 * @param string $data The data to encrypt.
4147 * @return string|false Encrypted string, or false if encryption fails.
4148 */
4149 function acf_encrypt( $data = '' ) {
4150
4151 if ( ! function_exists( 'openssl_encrypt' ) ) {
4152 return false;
4153 }
4154
4155 $key = wp_hash( 'acf_encrypt' );
4156 $mac_key = wp_hash( 'acf_encrypt_mac' );
4157 $iv = openssl_random_pseudo_bytes( openssl_cipher_iv_length( 'aes-256-cbc' ) );
4158
4159 $encrypted_data = openssl_encrypt( $data, 'aes-256-cbc', $key, 0, $iv );
4160
4161 $payload = $encrypted_data . '::' . $iv;
4162 $hmac = hash_hmac( 'sha256', $payload, $mac_key, true );
4163
4164 return base64_encode( $payload . $hmac );
4165 }
4166
4167 /**
4168 * Decrypts an encrypted string using PHP.
4169 * https://bhoover.com/using-php-openssl_encrypt-openssl_decrypt-encrypt-decrypt-data/
4170 *
4171 * @since 5.5.8
4172 *
4173 * @param string $data The string to decrypt.
4174 * @return string|false Decrypted string, or false if the payload is malformed or decryption fails.
4175 */
4176 function acf_decrypt( $data = '' ) {
4177
4178 if ( ! function_exists( 'openssl_decrypt' ) ) {
4179 return false;
4180 }
4181
4182 $raw = base64_decode( (string) $data, true );
4183 if ( false === $raw ) {
4184 return false;
4185 }
4186
4187 if ( strlen( $raw ) <= 32 ) {
4188 return false;
4189 }
4190
4191 $mac_key = wp_hash( 'acf_encrypt_mac' );
4192 $hmac = substr( $raw, -32 );
4193 $payload = substr( $raw, 0, -32 );
4194
4195 $expected = hash_hmac( 'sha256', $payload, $mac_key, true );
4196 if ( ! hash_equals( $expected, $hmac ) ) {
4197 return false;
4198 }
4199
4200 if ( strpos( $payload, '::' ) === false ) {
4201 return false;
4202 }
4203
4204 $key = wp_hash( 'acf_encrypt' );
4205
4206 list( $encrypted_data, $iv ) = explode( '::', $payload, 2 );
4207
4208 return openssl_decrypt( $encrypted_data, 'aes-256-cbc', $key, 0, $iv );
4209 }
4210
4211 /**
4212 * acf_parse_markdown
4213 *
4214 * A very basic regex-based Markdown parser function based off [slimdown](https://gist.github.com/jbroadway/2836900).
4215 *
4216 * @since 5.7.2
4217 *
4218 * @param string $text The string to parse.
4219 * @return string
4220 */
4221 function acf_parse_markdown( $text = '' ) {
4222
4223 // trim
4224 $text = trim( $text );
4225
4226 // rules
4227 $rules = array(
4228 '/=== (.+?) ===/' => '<h2>$1</h2>', // headings
4229 '/== (.+?) ==/' => '<h3>$1</h3>', // headings
4230 '/= (.+?) =/' => '<h4>$1</h4>', // headings
4231 '/\[([^\[]+)\]\(([^\)]+)\)/' => '<a href="$2">$1</a>', // links
4232 '/(\*\*)(.*?)\1/' => '<strong>$2</strong>', // bold
4233 '/(\*)(.*?)\1/' => '<em>$2</em>', // intalic
4234 '/`(.*?)`/' => '<code>$1</code>', // inline code
4235 '/\n\*(.*)/' => "\n<ul>\n\t<li>$1</li>\n</ul>", // ul lists
4236 '/\n[0-9]+\.(.*)/' => "\n<ol>\n\t<li>$1</li>\n</ol>", // ol lists
4237 '/<\/ul>\s?<ul>/' => '', // fix extra ul
4238 '/<\/ol>\s?<ol>/' => '', // fix extra ol
4239 );
4240 foreach ( $rules as $k => $v ) {
4241 $text = preg_replace( $k, $v, $text );
4242 }
4243
4244 // autop
4245 $text = wpautop( $text );
4246
4247 // return
4248 return $text;
4249 }
4250
4251 /**
4252 * acf_get_sites
4253 *
4254 * Returns an array of sites for a network.
4255 *
4256 * @since 5.4.0
4257 *
4258 * @param void
4259 * @return array
4260 */
4261 function acf_get_sites() {
4262 $results = array();
4263 $sites = get_sites( array( 'number' => 0 ) );
4264 if ( $sites ) {
4265 foreach ( $sites as $site ) {
4266 $results[] = get_site( $site )->to_array();
4267 }
4268 }
4269 return $results;
4270 }
4271
4272 /**
4273 * acf_convert_rules_to_groups
4274 *
4275 * Converts an array of rules from ACF4 to an array of groups for ACF5
4276 *
4277 * @since 5.7.4
4278 *
4279 * @param array $rules An array of rules.
4280 * @param string $anyorall The anyorall setting used in ACF4. Defaults to 'any'.
4281 * @return array
4282 */
4283 function acf_convert_rules_to_groups( $rules, $anyorall = 'any' ) {
4284
4285 // vars
4286 $groups = array();
4287 $index = 0;
4288
4289 // loop
4290 foreach ( $rules as $rule ) {
4291
4292 // extract vars
4293 $group = acf_extract_var( $rule, 'group_no' );
4294 $order = acf_extract_var( $rule, 'order_no' );
4295
4296 // calculate group if not defined
4297 if ( $group === null ) {
4298 $group = $index;
4299
4300 // use $anyorall to determine if a new group is needed
4301 if ( $anyorall == 'any' ) {
4302 ++$index;
4303 }
4304 }
4305
4306 // calculate order if not defined
4307 if ( $order === null ) {
4308 $order = isset( $groups[ $group ] ) ? count( $groups[ $group ] ) : 0;
4309 }
4310
4311 // append to group
4312 $groups[ $group ][ $order ] = $rule;
4313
4314 // sort groups
4315 ksort( $groups[ $group ] );
4316 }
4317
4318 // sort groups
4319 ksort( $groups );
4320
4321 // return
4322 return $groups;
4323 }
4324
4325 /**
4326 * acf_register_ajax
4327 *
4328 * Regsiters an ajax callback.
4329 *
4330 * @since 5.7.7
4331 *
4332 * @param string $name The ajax action name.
4333 * @param array $callback The callback function or array.
4334 * @param boolean $public Whether to allow access to non logged in users.
4335 * @return void
4336 */
4337 function acf_register_ajax( $name = '', $callback = false, $public = false ) {
4338
4339 // vars
4340 $action = "acf/ajax/$name";
4341
4342 // add action for logged-in users
4343 add_action( "wp_ajax_$action", $callback );
4344
4345 // add action for non logged-in users
4346 if ( $public ) {
4347 add_action( "wp_ajax_nopriv_$action", $callback );
4348 }
4349 }
4350
4351 /**
4352 * acf_str_camel_case
4353 *
4354 * Converts a string into camelCase.
4355 * Thanks to https://stackoverflow.com/questions/31274782/convert-array-keys-from-underscore-case-to-camelcase-recursively
4356 *
4357 * @since 5.8.0
4358 *
4359 * @param string $string The string ot convert.
4360 * @return string
4361 */
4362 function acf_str_camel_case( $string = '' ) {
4363 return lcfirst( str_replace( ' ', '', ucwords( str_replace( '_', ' ', $string ) ) ) );
4364 }
4365
4366 /**
4367 * acf_array_camel_case
4368 *
4369 * Converts all aray keys to camelCase.
4370 *
4371 * @since 5.8.0
4372 *
4373 * @param array $array The array to convert.
4374 * @return array
4375 */
4376 function acf_array_camel_case( $array = array() ) {
4377 $array2 = array();
4378 foreach ( $array as $k => $v ) {
4379 $array2[ acf_str_camel_case( $k ) ] = $v;
4380 }
4381 return $array2;
4382 }
4383
4384 /**
4385 * Returns true if the current screen is using the block editor.
4386 *
4387 * @since 5.8.0
4388 *
4389 * @return boolean
4390 */
4391 function acf_is_block_editor() {
4392 if ( function_exists( 'get_current_screen' ) ) {
4393 $screen = get_current_screen();
4394 if ( $screen && method_exists( $screen, 'is_block_editor' ) ) {
4395 return $screen->is_block_editor();
4396 }
4397 }
4398 return false;
4399 }
4400
4401 /**
4402 * Return an array of the WordPress reserved terms
4403 *
4404 * @since 6.1
4405 *
4406 * @return array The WordPress reserved terms list.
4407 */
4408 function acf_get_wp_reserved_terms() {
4409 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' );
4410 }
4411
4412 /**
4413 * Detect if we're on a multisite subsite.
4414 *
4415 * @since 6.2.4
4416 *
4417 * @return boolean true if we're in a multisite install and not on the main site
4418 */
4419 function acf_is_multisite_sub_site() {
4420 if ( is_multisite() && ! is_main_site() ) {
4421 return true;
4422 }
4423 return false;
4424 }
4425
4426 /**
4427 * Detect if we're on a multisite main site.
4428 *
4429 * @since 6.2.4
4430 *
4431 * @return boolean true if we're in a multisite install and on the main site
4432 */
4433 function acf_is_multisite_main_site() {
4434 if ( is_multisite() && is_main_site() ) {
4435 return true;
4436 }
4437 return false;
4438 }
4439
4440 /**
4441 * Allow filterable permissions metabox callbacks.
4442 *
4443 * @since 6.3.10
4444 *
4445 * @param boolean $enable_meta_box_cb_edit Can the current user edit metabox callbacks.
4446 * @return boolean
4447 */
4448 function acf_settings_enable_meta_box_cb_edit( $enable_meta_box_cb_edit ): bool {
4449 if ( ! is_super_admin() ) {
4450 return false;
4451 }
4452
4453 return (bool) $enable_meta_box_cb_edit;
4454 }
4455 add_filter( 'acf/settings/enable_meta_box_cb_edit', 'acf_settings_enable_meta_box_cb_edit', 1 );
4456