PluginProbe
Advanced Custom Fields (ACF®) / 6.2.8
Advanced Custom Fields (ACF®) v6.2.8
6.8.9 6.8.8 6.8.7 6.8.6 6.8.5 6.8.4 6.8.3 6.8.2 6.8.1 5.8.5 5.8.6 5.8.7 5.8.8 5.8.9 5.9.0 5.9.1 5.9.2 5.9.3 5.9.4 5.9.5 5.9.6 5.9.7 5.9.8 5.9.9 6.0.0 All 230 releases
advanced-custom-fields / includes / api / api-helpers.php

api-helpers.php in Advanced Custom Fields (ACF®) 6.2.8, at includes/api/api-helpers.php

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