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