PluginProbe
Advanced Custom Fields (ACF®) / 5.9.7
Advanced Custom Fields (ACF®) v5.9.7
6.8.9 6.8.8 6.8.7 6.8.6 6.8.5 6.8.4 6.8.3 6.8.2 6.8.1 5.8.5 5.8.6 5.8.7 5.8.8 5.8.9 5.9.0 5.9.1 5.9.2 5.9.3 5.9.4 5.9.5 5.9.6 5.9.7 5.9.8 5.9.9 6.0.0 All 230 releases
advanced-custom-fields / includes / api / api-helpers.php
api-helpers.php
4,781 lines 82.3 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 if ( $src ) {
3167 $response['url'] = $src[0];
3168 $response['width'] = $src[1];
3169 $response['height'] = $src[2];
3170 }
3171 break;
3172 case 'video':
3173 $response['width'] = acf_maybe_get( $meta, 'width', 0 );
3174 $response['height'] = acf_maybe_get( $meta, 'height', 0 );
3175 if( $featured_id = get_post_thumbnail_id( $attachment->ID ) ) {
3176 $sizes_id = $featured_id;
3177 }
3178 break;
3179 case 'audio':
3180 if( $featured_id = get_post_thumbnail_id( $attachment->ID ) ) {
3181 $sizes_id = $featured_id;
3182 }
3183 break;
3184 }
3185
3186 // Load array of image sizes.
3187 if( $sizes_id ) {
3188 $sizes = get_intermediate_image_sizes();
3189 $sizes_data = array();
3190 foreach( $sizes as $size ) {
3191 $src = wp_get_attachment_image_src( $sizes_id, $size );
3192 if ( $src ) {
3193 $sizes_data[ $size ] = $src[0];
3194 $sizes_data[ $size . '-width' ] = $src[1];
3195 $sizes_data[ $size . '-height' ] = $src[2];
3196 }
3197 }
3198 $response['sizes'] = $sizes_data;
3199 }
3200
3201 /**
3202 * Filters the attachment $response after it has been loaded.
3203 *
3204 * @date 16/06/2020
3205 * @since 5.9.0
3206 *
3207 * @param array $response Array of loaded attachment data.
3208 * @param WP_Post $attachment Attachment object.
3209 * @param array|false $meta Array of attachment meta data, or false if there is none.
3210 */
3211 return apply_filters( "acf/load_attachment", $response, $attachment, $meta );
3212 }
3213
3214
3215 /*
3216 * acf_get_truncated
3217 *
3218 * This function will truncate and return a string
3219 *
3220 * @type function
3221 * @date 8/08/2014
3222 * @since 5.0.0
3223 *
3224 * @param $text (string)
3225 * @param $length (int)
3226 * @return (string)
3227 */
3228
3229 function acf_get_truncated( $text, $length = 64 ) {
3230
3231 // vars
3232 $text = trim($text);
3233 $the_length = strlen( $text );
3234
3235
3236 // cut
3237 $return = substr( $text, 0, ($length - 3) );
3238
3239
3240 // ...
3241 if( $the_length > ($length - 3) ) {
3242
3243 $return .= '...';
3244
3245 }
3246
3247
3248 // return
3249 return $return;
3250
3251 }
3252
3253 /*
3254 * acf_current_user_can_admin
3255 *
3256 * This function will return true if the current user can administrate the ACF field groups
3257 *
3258 * @type function
3259 * @date 9/02/2015
3260 * @since 5.1.5
3261 *
3262 * @param $post_id (int)
3263 * @return $post_id (int)
3264 */
3265
3266 function acf_current_user_can_admin() {
3267
3268 if( acf_get_setting('show_admin') && current_user_can(acf_get_setting('capability')) ) {
3269
3270 return true;
3271
3272 }
3273
3274
3275 // return
3276 return false;
3277
3278 }
3279
3280
3281 /*
3282 * acf_get_filesize
3283 *
3284 * This function will return a numeric value of bytes for a given filesize string
3285 *
3286 * @type function
3287 * @date 18/02/2015
3288 * @since 5.1.5
3289 *
3290 * @param $size (mixed)
3291 * @return (int)
3292 */
3293
3294 function acf_get_filesize( $size = 1 ) {
3295
3296 // vars
3297 $unit = 'MB';
3298 $units = array(
3299 'TB' => 4,
3300 'GB' => 3,
3301 'MB' => 2,
3302 'KB' => 1,
3303 );
3304
3305
3306 // look for $unit within the $size parameter (123 KB)
3307 if( is_string($size) ) {
3308
3309 // vars
3310 $custom = strtoupper( substr($size, -2) );
3311
3312 foreach( $units as $k => $v ) {
3313
3314 if( $custom === $k ) {
3315
3316 $unit = $k;
3317 $size = substr($size, 0, -2);
3318
3319 }
3320
3321 }
3322
3323 }
3324
3325
3326 // calc bytes
3327 $bytes = floatval($size) * pow(1024, $units[$unit]);
3328
3329
3330 // return
3331 return $bytes;
3332
3333 }
3334
3335
3336 /*
3337 * acf_format_filesize
3338 *
3339 * This function will return a formatted string containing the filesize and unit
3340 *
3341 * @type function
3342 * @date 18/02/2015
3343 * @since 5.1.5
3344 *
3345 * @param $size (mixed)
3346 * @return (int)
3347 */
3348
3349 function acf_format_filesize( $size = 1 ) {
3350
3351 // convert
3352 $bytes = acf_get_filesize( $size );
3353
3354
3355 // vars
3356 $units = array(
3357 'TB' => 4,
3358 'GB' => 3,
3359 'MB' => 2,
3360 'KB' => 1,
3361 );
3362
3363
3364 // loop through units
3365 foreach( $units as $k => $v ) {
3366
3367 $result = $bytes / pow(1024, $v);
3368
3369 if( $result >= 1 ) {
3370
3371 return $result . ' ' . $k;
3372
3373 }
3374
3375 }
3376
3377
3378 // return
3379 return $bytes . ' B';
3380
3381 }
3382
3383
3384 /*
3385 * acf_get_valid_terms
3386 *
3387 * This function will replace old terms with new split term ids
3388 *
3389 * @type function
3390 * @date 27/02/2015
3391 * @since 5.1.5
3392 *
3393 * @param $terms (int|array)
3394 * @param $taxonomy (string)
3395 * @return $terms
3396 */
3397
3398 function acf_get_valid_terms( $terms = false, $taxonomy = 'category' ) {
3399
3400 // force into array
3401 $terms = acf_get_array($terms);
3402
3403
3404 // force ints
3405 $terms = array_map('intval', $terms);
3406
3407
3408 // bail early if function does not yet exist or
3409 if( !function_exists('wp_get_split_term') || empty($terms) ) {
3410
3411 return $terms;
3412
3413 }
3414
3415
3416 // attempt to find new terms
3417 foreach( $terms as $i => $term_id ) {
3418
3419 $new_term_id = wp_get_split_term($term_id, $taxonomy);
3420
3421 if( $new_term_id ) {
3422
3423 $terms[ $i ] = $new_term_id;
3424
3425 }
3426
3427 }
3428
3429
3430 // return
3431 return $terms;
3432
3433 }
3434
3435
3436 /*
3437 * acf_validate_attachment
3438 *
3439 * This function will validate an attachment based on a field's restrictions and return an array of errors
3440 *
3441 * @type function
3442 * @date 3/07/2015
3443 * @since 5.2.3
3444 *
3445 * @param $attachment (array) attachment data. Changes based on context
3446 * @param $field (array) field settings containing restrictions
3447 * @param $context (string) $file is different when uploading / preparing
3448 * @return $errors (array)
3449 */
3450
3451 function acf_validate_attachment( $attachment, $field, $context = 'prepare' ) {
3452
3453 // vars
3454 $errors = array();
3455 $file = array(
3456 'type' => '',
3457 'width' => 0,
3458 'height' => 0,
3459 'size' => 0
3460 );
3461
3462
3463 // upload
3464 if( $context == 'upload' ) {
3465
3466 // vars
3467 $file['type'] = pathinfo($attachment['name'], PATHINFO_EXTENSION);
3468 $file['size'] = filesize($attachment['tmp_name']);
3469
3470 if( strpos($attachment['type'], 'image') !== false ) {
3471
3472 $size = getimagesize($attachment['tmp_name']);
3473 $file['width'] = acf_maybe_get($size, 0);
3474 $file['height'] = acf_maybe_get($size, 1);
3475
3476 }
3477
3478 // prepare
3479 } elseif( $context == 'prepare' ) {
3480
3481 $file['type'] = pathinfo($attachment['filename'], PATHINFO_EXTENSION);
3482 $file['size'] = acf_maybe_get($attachment, 'filesizeInBytes', 0);
3483 $file['width'] = acf_maybe_get($attachment, 'width', 0);
3484 $file['height'] = acf_maybe_get($attachment, 'height', 0);
3485
3486 // custom
3487 } else {
3488
3489 $file = array_merge($file, $attachment);
3490 $file['type'] = pathinfo($attachment['filename'], PATHINFO_EXTENSION);
3491
3492 }
3493
3494
3495 // image
3496 if( $file['width'] || $file['height'] ) {
3497
3498 // width
3499 $min_width = (int) acf_maybe_get($field, 'min_width', 0);
3500 $max_width = (int) acf_maybe_get($field, 'max_width', 0);
3501
3502 if( $file['width'] ) {
3503
3504 if( $min_width && $file['width'] < $min_width ) {
3505
3506 // min width
3507 $errors['min_width'] = sprintf(__('Image width must be at least %dpx.', 'acf'), $min_width );
3508
3509 } elseif( $max_width && $file['width'] > $max_width ) {
3510
3511 // min width
3512 $errors['max_width'] = sprintf(__('Image width must not exceed %dpx.', 'acf'), $max_width );
3513
3514 }
3515
3516 }
3517
3518
3519 // height
3520 $min_height = (int) acf_maybe_get($field, 'min_height', 0);
3521 $max_height = (int) acf_maybe_get($field, 'max_height', 0);
3522
3523 if( $file['height'] ) {
3524
3525 if( $min_height && $file['height'] < $min_height ) {
3526
3527 // min height
3528 $errors['min_height'] = sprintf(__('Image height must be at least %dpx.', 'acf'), $min_height );
3529
3530 } elseif( $max_height && $file['height'] > $max_height ) {
3531
3532 // min height
3533 $errors['max_height'] = sprintf(__('Image height must not exceed %dpx.', 'acf'), $max_height );
3534
3535 }
3536
3537 }
3538
3539 }
3540
3541
3542 // file size
3543 if( $file['size'] ) {
3544
3545 $min_size = acf_maybe_get($field, 'min_size', 0);
3546 $max_size = acf_maybe_get($field, 'max_size', 0);
3547
3548 if( $min_size && $file['size'] < acf_get_filesize($min_size) ) {
3549
3550 // min width
3551 $errors['min_size'] = sprintf(__('File size must be at least %s.', 'acf'), acf_format_filesize($min_size) );
3552
3553 } elseif( $max_size && $file['size'] > acf_get_filesize($max_size) ) {
3554
3555 // min width
3556 $errors['max_size'] = sprintf(__('File size must not exceed %s.', 'acf'), acf_format_filesize($max_size) );
3557
3558 }
3559
3560 }
3561
3562
3563 // file type
3564 if( $file['type'] ) {
3565
3566 $mime_types = acf_maybe_get($field, 'mime_types', '');
3567
3568 // lower case
3569 $file['type'] = strtolower($file['type']);
3570 $mime_types = strtolower($mime_types);
3571
3572
3573 // explode
3574 $mime_types = str_replace(array(' ', '.'), '', $mime_types);
3575 $mime_types = explode(',', $mime_types); // split pieces
3576 $mime_types = array_filter($mime_types); // remove empty pieces
3577
3578 if( !empty($mime_types) && !in_array($file['type'], $mime_types) ) {
3579
3580 // glue together last 2 types
3581 if( count($mime_types) > 1 ) {
3582
3583 $last1 = array_pop($mime_types);
3584 $last2 = array_pop($mime_types);
3585
3586 $mime_types[] = $last2 . ' ' . __('or', 'acf') . ' ' . $last1;
3587
3588 }
3589
3590 $errors['mime_types'] = sprintf(__('File type must be %s.', 'acf'), implode(', ', $mime_types) );
3591
3592 }
3593
3594 }
3595
3596
3597 /**
3598 * Filters the errors for a file before it is uploaded or displayed in the media modal.
3599 *
3600 * @date 3/07/2015
3601 * @since 5.2.3
3602 *
3603 * @param array $errors An array of errors.
3604 * @param array $file An array of data for a single file.
3605 * @param array $attachment An array of attachment data which differs based on the context.
3606 * @param array $field The field array.
3607 * @param string $context The curent context (uploading, preparing)
3608 */
3609 $errors = apply_filters( "acf/validate_attachment/type={$field['type']}", $errors, $file, $attachment, $field, $context );
3610 $errors = apply_filters( "acf/validate_attachment/name={$field['_name']}", $errors, $file, $attachment, $field, $context );
3611 $errors = apply_filters( "acf/validate_attachment/key={$field['key']}", $errors, $file, $attachment, $field, $context );
3612 $errors = apply_filters( "acf/validate_attachment", $errors, $file, $attachment, $field, $context );
3613
3614
3615 // return
3616 return $errors;
3617
3618 }
3619
3620
3621 /*
3622 * _acf_settings_uploader
3623 *
3624 * Dynamic logic for uploader setting
3625 *
3626 * @type function
3627 * @date 7/05/2015
3628 * @since 5.2.3
3629 *
3630 * @param $uploader (string)
3631 * @return $uploader
3632 */
3633
3634 add_filter('acf/settings/uploader', '_acf_settings_uploader');
3635
3636 function _acf_settings_uploader( $uploader ) {
3637
3638 // if can't upload files
3639 if( !current_user_can('upload_files') ) {
3640
3641 $uploader = 'basic';
3642
3643 }
3644
3645
3646 // return
3647 return $uploader;
3648 }
3649
3650
3651 /*
3652 * acf_translate_keys
3653 *
3654 * description
3655 *
3656 * @type function
3657 * @date 7/12/2015
3658 * @since 5.3.2
3659 *
3660 * @param $post_id (int)
3661 * @return $post_id (int)
3662 */
3663
3664 /*
3665 function acf_translate_keys( $array, $keys ) {
3666
3667 // bail early if no keys
3668 if( empty($keys) ) return $array;
3669
3670
3671 // translate
3672 foreach( $keys as $k ) {
3673
3674 // bail ealry if not exists
3675 if( !isset($array[ $k ]) ) continue;
3676
3677
3678 // translate
3679 $array[ $k ] = acf_translate( $array[ $k ] );
3680
3681 }
3682
3683
3684 // return
3685 return $array;
3686
3687 }
3688 */
3689
3690
3691 /*
3692 * acf_translate
3693 *
3694 * This function will translate a string using the new 'l10n_textdomain' setting
3695 * Also works for arrays which is great for fields - select -> choices
3696 *
3697 * @type function
3698 * @date 4/12/2015
3699 * @since 5.3.2
3700 *
3701 * @param $string (mixed) string or array containins strings to be translated
3702 * @return $string
3703 */
3704
3705 function acf_translate( $string ) {
3706
3707 // vars
3708 $l10n = acf_get_setting('l10n');
3709 $textdomain = acf_get_setting('l10n_textdomain');
3710
3711
3712 // bail early if not enabled
3713 if( !$l10n ) return $string;
3714
3715
3716 // bail early if no textdomain
3717 if( !$textdomain ) return $string;
3718
3719
3720 // is array
3721 if( is_array($string) ) {
3722
3723 return array_map('acf_translate', $string);
3724
3725 }
3726
3727
3728 // bail early if not string
3729 if( !is_string($string) ) return $string;
3730
3731
3732 // bail early if empty
3733 if( $string === '' ) return $string;
3734
3735
3736 // allow for var_export export
3737 if( acf_get_setting('l10n_var_export') ){
3738
3739 // bail early if already translated
3740 if( substr($string, 0, 7) === '!!__(!!' ) return $string;
3741
3742
3743 // return
3744 return "!!__(!!'" . $string . "!!', !!'" . $textdomain . "!!')!!";
3745
3746 }
3747
3748
3749 // vars
3750 return __( $string, $textdomain );
3751
3752 }
3753
3754
3755 /*
3756 * acf_maybe_add_action
3757 *
3758 * This function will determine if the action has already run before adding / calling the function
3759 *
3760 * @type function
3761 * @date 13/01/2016
3762 * @since 5.3.2
3763 *
3764 * @param $post_id (int)
3765 * @return $post_id (int)
3766 */
3767
3768 function acf_maybe_add_action( $tag, $function_to_add, $priority = 10, $accepted_args = 1 ) {
3769
3770 // if action has already run, execute it
3771 // - if currently doing action, allow $tag to be added as per usual to allow $priority ordering needed for 3rd party asset compatibility
3772 if( did_action($tag) && !doing_action($tag) ) {
3773
3774 call_user_func( $function_to_add );
3775
3776 // if action has not yet run, add it
3777 } else {
3778
3779 add_action( $tag, $function_to_add, $priority, $accepted_args );
3780
3781 }
3782
3783 }
3784
3785
3786 /*
3787 * acf_is_row_collapsed
3788 *
3789 * This function will return true if the field's row is collapsed
3790 *
3791 * @type function
3792 * @date 2/03/2016
3793 * @since 5.3.2
3794 *
3795 * @param $post_id (int)
3796 * @return $post_id (int)
3797 */
3798
3799 function acf_is_row_collapsed( $field_key = '', $row_index = 0 ) {
3800
3801 // collapsed
3802 $collapsed = acf_get_user_setting('collapsed_' . $field_key, '');
3803
3804
3805 // cookie fallback ( version < 5.3.2 )
3806 if( $collapsed === '' ) {
3807
3808 $collapsed = acf_extract_var($_COOKIE, "acf_collapsed_{$field_key}", '');
3809 $collapsed = str_replace('|', ',', $collapsed);
3810
3811
3812 // update
3813 acf_update_user_setting( 'collapsed_' . $field_key, $collapsed );
3814
3815 }
3816
3817
3818 // explode
3819 $collapsed = explode(',', $collapsed);
3820 $collapsed = array_filter($collapsed, 'is_numeric');
3821
3822
3823 // collapsed class
3824 return in_array($row_index, $collapsed);
3825
3826 }
3827
3828
3829 /*
3830 * acf_get_attachment_image
3831 *
3832 * description
3833 *
3834 * @type function
3835 * @date 24/10/16
3836 * @since 5.5.0
3837 *
3838 * @param $post_id (int)
3839 * @return $post_id (int)
3840 */
3841
3842 function acf_get_attachment_image( $attachment_id = 0, $size = 'thumbnail' ) {
3843
3844 // vars
3845 $url = wp_get_attachment_image_src($attachment_id, 'thumbnail');
3846 $alt = get_post_meta($attachment_id, '_wp_attachment_image_alt', true);
3847
3848
3849 // bail early if no url
3850 if( !$url ) return '';
3851
3852
3853 // return
3854 $value = '<img src="' . $url . '" alt="' . $alt . '" />';
3855
3856 }
3857
3858
3859 /*
3860 * acf_get_post_thumbnail
3861 *
3862 * This function will return a thumbail image url for a given post
3863 *
3864 * @type function
3865 * @date 3/05/2016
3866 * @since 5.3.8
3867 *
3868 * @param $post (obj)
3869 * @param $size (mixed)
3870 * @return (string)
3871 */
3872
3873 function acf_get_post_thumbnail( $post = null, $size = 'thumbnail' ) {
3874
3875 // vars
3876 $data = array(
3877 'url' => '',
3878 'type' => '',
3879 'html' => ''
3880 );
3881
3882
3883 // post
3884 $post = get_post($post);
3885
3886
3887 // bail early if no post
3888 if( !$post ) return $data;
3889
3890
3891 // vars
3892 $thumb_id = $post->ID;
3893 $mime_type = acf_maybe_get(explode('/', $post->post_mime_type), 0);
3894
3895
3896 // attachment
3897 if( $post->post_type === 'attachment' ) {
3898
3899 // change $thumb_id
3900 if( $mime_type === 'audio' || $mime_type === 'video' ) {
3901
3902 $thumb_id = get_post_thumbnail_id($post->ID);
3903
3904 }
3905
3906 // post
3907 } else {
3908
3909 $thumb_id = get_post_thumbnail_id($post->ID);
3910
3911 }
3912
3913
3914 // try url
3915 $data['url'] = wp_get_attachment_image_src($thumb_id, $size);
3916 $data['url'] = acf_maybe_get($data['url'], 0);
3917
3918
3919 // default icon
3920 if( !$data['url'] && $post->post_type === 'attachment' ) {
3921
3922 $data['url'] = wp_mime_type_icon($post->ID);
3923 $data['type'] = 'icon';
3924
3925 }
3926
3927
3928 // html
3929 $data['html'] = '<img src="' . $data['url'] . '" alt="" />';
3930
3931
3932 // return
3933 return $data;
3934
3935 }
3936
3937 /**
3938 * acf_get_browser
3939 *
3940 * Returns the name of the current browser.
3941 *
3942 * @date 17/01/2014
3943 * @since 5.0.0
3944 *
3945 * @param void
3946 * @return string
3947 */
3948 function acf_get_browser() {
3949
3950 // Check server var.
3951 if( isset($_SERVER['HTTP_USER_AGENT']) ) {
3952 $agent = $_SERVER['HTTP_USER_AGENT'];
3953
3954 // Loop over search terms.
3955 $browsers = array(
3956 'Firefox' => 'firefox',
3957 'Trident' => 'msie',
3958 'MSIE' => 'msie',
3959 'Edge' => 'edge',
3960 'Chrome' => 'chrome',
3961 'Safari' => 'safari',
3962 );
3963 foreach( $browsers as $k => $v ) {
3964 if( strpos($agent, $k) !== false ) {
3965 return $v;
3966 }
3967 }
3968 }
3969
3970 // Return default.
3971 return '';
3972 }
3973
3974
3975 /*
3976 * acf_is_ajax
3977 *
3978 * This function will reutrn true if performing a wp ajax call
3979 *
3980 * @type function
3981 * @date 7/06/2016
3982 * @since 5.3.8
3983 *
3984 * @param n/a
3985 * @return (boolean)
3986 */
3987
3988 function acf_is_ajax( $action = '' ) {
3989
3990 // vars
3991 $is_ajax = false;
3992
3993
3994 // check if is doing ajax
3995 if( defined('DOING_AJAX') && DOING_AJAX ) {
3996
3997 $is_ajax = true;
3998
3999 }
4000
4001
4002 // check $action
4003 if( $action && acf_maybe_get($_POST, 'action') !== $action ) {
4004
4005 $is_ajax = false;
4006
4007 }
4008
4009
4010 // return
4011 return $is_ajax;
4012
4013 }
4014
4015
4016
4017
4018 /*
4019 * acf_format_date
4020 *
4021 * This function will accept a date value and return it in a formatted string
4022 *
4023 * @type function
4024 * @date 16/06/2016
4025 * @since 5.3.8
4026 *
4027 * @param $value (string)
4028 * @return $format (string)
4029 */
4030
4031 function acf_format_date( $value, $format ) {
4032
4033 // bail early if no value
4034 if( !$value ) return $value;
4035
4036
4037 // vars
4038 $unixtimestamp = 0;
4039
4040
4041 // numeric (either unix or YYYYMMDD)
4042 if( is_numeric($value) && strlen($value) !== 8 ) {
4043
4044 $unixtimestamp = $value;
4045
4046 } else {
4047
4048 $unixtimestamp = strtotime($value);
4049
4050 }
4051
4052
4053 // return
4054 return date_i18n($format, $unixtimestamp);
4055
4056 }
4057
4058 /**
4059 * acf_clear_log
4060 *
4061 * Deletes the debug.log file.
4062 *
4063 * @date 21/1/19
4064 * @since 5.7.10
4065 *
4066 * @param type $var Description. Default.
4067 * @return type Description.
4068 */
4069 function acf_clear_log() {
4070 unlink( WP_CONTENT_DIR . '/debug.log' );
4071 }
4072
4073 /*
4074 * acf_log
4075 *
4076 * description
4077 *
4078 * @type function
4079 * @date 24/06/2016
4080 * @since 5.3.8
4081 *
4082 * @param $post_id (int)
4083 * @return $post_id (int)
4084 */
4085
4086 function acf_log() {
4087
4088 // vars
4089 $args = func_get_args();
4090
4091 // loop
4092 foreach( $args as $i => $arg ) {
4093
4094 // array | object
4095 if( is_array($arg) || is_object($arg) ) {
4096 $arg = print_r($arg, true);
4097
4098 // bool
4099 } elseif( is_bool($arg) ) {
4100 $arg = 'bool(' . ( $arg ? 'true' : 'false' ) . ')';
4101 }
4102
4103 // update
4104 $args[ $i ] = $arg;
4105 }
4106
4107 // log
4108 error_log( implode(' ', $args) );
4109 }
4110
4111 /**
4112 * acf_dev_log
4113 *
4114 * Used to log variables only if ACF_DEV is defined
4115 *
4116 * @date 25/8/18
4117 * @since 5.7.4
4118 *
4119 * @param mixed
4120 * @return void
4121 */
4122 function acf_dev_log() {
4123 if( defined('ACF_DEV') && ACF_DEV ) {
4124 call_user_func_array('acf_log', func_get_args());
4125 }
4126 }
4127
4128 /*
4129 * acf_doing
4130 *
4131 * This function will tell ACF what task it is doing
4132 *
4133 * @type function
4134 * @date 28/06/2016
4135 * @since 5.3.8
4136 *
4137 * @param $event (string)
4138 * @param context (string)
4139 * @return n/a
4140 */
4141
4142 function acf_doing( $event = '', $context = '' ) {
4143
4144 acf_update_setting( 'doing', $event );
4145 acf_update_setting( 'doing_context', $context );
4146
4147 }
4148
4149
4150 /*
4151 * acf_is_doing
4152 *
4153 * This function can be used to state what ACF is doing, or to check
4154 *
4155 * @type function
4156 * @date 28/06/2016
4157 * @since 5.3.8
4158 *
4159 * @param $event (string)
4160 * @param context (string)
4161 * @return (boolean)
4162 */
4163
4164 function acf_is_doing( $event = '', $context = '' ) {
4165
4166 // vars
4167 $doing = false;
4168
4169
4170 // task
4171 if( acf_get_setting('doing') === $event ) {
4172
4173 $doing = true;
4174
4175 }
4176
4177
4178 // context
4179 if( $context && acf_get_setting('doing_context') !== $context ) {
4180
4181 $doing = false;
4182
4183 }
4184
4185
4186 // return
4187 return $doing;
4188
4189 }
4190
4191
4192 /*
4193 * acf_is_plugin_active
4194 *
4195 * This function will return true if the ACF plugin is active
4196 * - May be included within a theme or other plugin
4197 *
4198 * @type function
4199 * @date 13/07/2016
4200 * @since 5.4.0
4201 *
4202 * @param $basename (int)
4203 * @return $post_id (int)
4204 */
4205
4206
4207 function acf_is_plugin_active() {
4208
4209 // vars
4210 $basename = acf_get_setting('basename');
4211
4212
4213 // ensure is_plugin_active() exists (not on frontend)
4214 if( !function_exists('is_plugin_active') ) {
4215
4216 include_once( ABSPATH . 'wp-admin/includes/plugin.php' );
4217
4218 }
4219
4220
4221 // return
4222 return is_plugin_active($basename);
4223
4224 }
4225
4226 /*
4227 * acf_send_ajax_results
4228 *
4229 * This function will print JSON data for a Select2 AJAX query
4230 *
4231 * @type function
4232 * @date 19/07/2016
4233 * @since 5.4.0
4234 *
4235 * @param $response (array)
4236 * @return n/a
4237 */
4238
4239 function acf_send_ajax_results( $response ) {
4240
4241 // validate
4242 $response = wp_parse_args($response, array(
4243 'results' => array(),
4244 'more' => false,
4245 'limit' => 0
4246 ));
4247
4248
4249 // limit
4250 if( $response['limit'] && $response['results']) {
4251
4252 // vars
4253 $total = 0;
4254
4255 foreach( $response['results'] as $result ) {
4256
4257 // parent
4258 $total++;
4259
4260
4261 // children
4262 if( !empty($result['children']) ) {
4263
4264 $total += count( $result['children'] );
4265
4266 }
4267
4268 }
4269
4270
4271 // calc
4272 if( $total >= $response['limit'] ) {
4273
4274 $response['more'] = true;
4275
4276 }
4277
4278 }
4279
4280
4281 // return
4282 wp_send_json( $response );
4283
4284 }
4285
4286
4287 /*
4288 * acf_is_sequential_array
4289 *
4290 * This function will return true if the array contains only numeric keys
4291 *
4292 * @source http://stackoverflow.com/questions/173400/how-to-check-if-php-array-is-associative-or-sequential
4293 * @type function
4294 * @date 9/09/2016
4295 * @since 5.4.0
4296 *
4297 * @param $array (array)
4298 * @return (boolean)
4299 */
4300
4301 function acf_is_sequential_array( $array ) {
4302
4303 // bail ealry if not array
4304 if( !is_array($array) ) return false;
4305
4306
4307 // loop
4308 foreach( $array as $key => $value ) {
4309
4310 // bail ealry if is string
4311 if( is_string($key) ) return false;
4312
4313 }
4314
4315
4316 // return
4317 return true;
4318
4319 }
4320
4321
4322 /*
4323 * acf_is_associative_array
4324 *
4325 * This function will return true if the array contains one or more string keys
4326 *
4327 * @source http://stackoverflow.com/questions/173400/how-to-check-if-php-array-is-associative-or-sequential
4328 * @type function
4329 * @date 9/09/2016
4330 * @since 5.4.0
4331 *
4332 * @param $array (array)
4333 * @return (boolean)
4334 */
4335
4336 function acf_is_associative_array( $array ) {
4337
4338 // bail ealry if not array
4339 if( !is_array($array) ) return false;
4340
4341
4342 // loop
4343 foreach( $array as $key => $value ) {
4344
4345 // bail ealry if is string
4346 if( is_string($key) ) return true;
4347
4348 }
4349
4350
4351 // return
4352 return false;
4353
4354 }
4355
4356
4357 /*
4358 * acf_add_array_key_prefix
4359 *
4360 * This function will add a prefix to all array keys
4361 * Useful to preserve numeric keys when performing array_multisort
4362 *
4363 * @type function
4364 * @date 15/09/2016
4365 * @since 5.4.0
4366 *
4367 * @param $array (array)
4368 * @param $prefix (string)
4369 * @return (array)
4370 */
4371
4372 function acf_add_array_key_prefix( $array, $prefix ) {
4373
4374 // vars
4375 $array2 = array();
4376
4377
4378 // loop
4379 foreach( $array as $k => $v ) {
4380
4381 $k2 = $prefix . $k;
4382 $array2[ $k2 ] = $v;
4383
4384 }
4385
4386
4387 // return
4388 return $array2;
4389
4390 }
4391
4392
4393 /*
4394 * acf_remove_array_key_prefix
4395 *
4396 * This function will remove a prefix to all array keys
4397 * Useful to preserve numeric keys when performing array_multisort
4398 *
4399 * @type function
4400 * @date 15/09/2016
4401 * @since 5.4.0
4402 *
4403 * @param $array (array)
4404 * @param $prefix (string)
4405 * @return (array)
4406 */
4407
4408 function acf_remove_array_key_prefix( $array, $prefix ) {
4409
4410 // vars
4411 $array2 = array();
4412 $l = strlen($prefix);
4413
4414
4415 // loop
4416 foreach( $array as $k => $v ) {
4417
4418 $k2 = (substr($k, 0, $l) === $prefix) ? substr($k, $l) : $k;
4419 $array2[ $k2 ] = $v;
4420
4421 }
4422
4423
4424 // return
4425 return $array2;
4426
4427 }
4428
4429
4430 /*
4431 * acf_strip_protocol
4432 *
4433 * This function will remove the proticol from a url
4434 * Used to allow licences to remain active if a site is switched to https
4435 *
4436 * @type function
4437 * @date 10/01/2017
4438 * @since 5.5.4
4439 * @author Aaron
4440 *
4441 * @param $url (string)
4442 * @return (string)
4443 */
4444
4445 function acf_strip_protocol( $url ) {
4446
4447 // strip the protical
4448 return str_replace(array('http://','https://'), '', $url);
4449
4450 }
4451
4452
4453 /*
4454 * acf_connect_attachment_to_post
4455 *
4456 * This function will connect an attacment (image etc) to the post
4457 * Used to connect attachements uploaded directly to media that have not been attaced to a post
4458 *
4459 * @type function
4460 * @date 11/01/2017
4461 * @since 5.8.0 Added filter to prevent connection.
4462 * @since 5.5.4
4463 *
4464 * @param int $attachment_id The attachment ID.
4465 * @param int $post_id The post ID.
4466 * @return bool True if attachment was connected.
4467 */
4468 function acf_connect_attachment_to_post( $attachment_id = 0, $post_id = 0 ) {
4469
4470 // Bail ealry if $attachment_id is not valid.
4471 if( !$attachment_id || !is_numeric($attachment_id) ) {
4472 return false;
4473 }
4474
4475 // Bail ealry if $post_id is not valid.
4476 if( !$post_id || !is_numeric($post_id) ) {
4477 return false;
4478 }
4479
4480 /**
4481 * Filters whether or not to connect the attachment.
4482 *
4483 * @date 8/11/18
4484 * @since 5.8.0
4485 *
4486 * @param bool $bool Returning false will prevent the connection. Default true.
4487 * @param int $attachment_id The attachment ID.
4488 * @param int $post_id The post ID.
4489 */
4490 if( !apply_filters('acf/connect_attachment_to_post', true, $attachment_id, $post_id) ) {
4491 return false;
4492 }
4493
4494 // vars
4495 $post = get_post( $attachment_id );
4496
4497 // Check if is valid post.
4498 if( $post && $post->post_type == 'attachment' && $post->post_parent == 0 ) {
4499
4500 // update
4501 wp_update_post( array('ID' => $post->ID, 'post_parent' => $post_id) );
4502
4503 // return
4504 return true;
4505 }
4506
4507 // return
4508 return true;
4509 }
4510
4511
4512 /*
4513 * acf_encrypt
4514 *
4515 * This function will encrypt a string using PHP
4516 * https://bhoover.com/using-php-openssl_encrypt-openssl_decrypt-encrypt-decrypt-data/
4517 *
4518 * @type function
4519 * @date 27/2/17
4520 * @since 5.5.8
4521 *
4522 * @param $data (string)
4523 * @return (string)
4524 */
4525
4526
4527 function acf_encrypt( $data = '' ) {
4528
4529 // bail ealry if no encrypt function
4530 if( !function_exists('openssl_encrypt') ) return base64_encode($data);
4531
4532
4533 // generate a key
4534 $key = wp_hash('acf_encrypt');
4535
4536
4537 // Generate an initialization vector
4538 $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc'));
4539
4540
4541 // Encrypt the data using AES 256 encryption in CBC mode using our encryption key and initialization vector.
4542 $encrypted_data = openssl_encrypt($data, 'aes-256-cbc', $key, 0, $iv);
4543
4544
4545 // The $iv is just as important as the key for decrypting, so save it with our encrypted data using a unique separator (::)
4546 return base64_encode($encrypted_data . '::' . $iv);
4547
4548 }
4549
4550
4551 /*
4552 * acf_decrypt
4553 *
4554 * This function will decrypt an encrypted string using PHP
4555 * https://bhoover.com/using-php-openssl_encrypt-openssl_decrypt-encrypt-decrypt-data/
4556 *
4557 * @type function
4558 * @date 27/2/17
4559 * @since 5.5.8
4560 *
4561 * @param $data (string)
4562 * @return (string)
4563 */
4564
4565 function acf_decrypt( $data = '' ) {
4566
4567 // bail ealry if no decrypt function
4568 if( !function_exists('openssl_decrypt') ) return base64_decode($data);
4569
4570
4571 // generate a key
4572 $key = wp_hash('acf_encrypt');
4573
4574
4575 // To decrypt, split the encrypted data from our IV - our unique separator used was "::"
4576 list($encrypted_data, $iv) = explode('::', base64_decode($data), 2);
4577
4578
4579 // decrypt
4580 return openssl_decrypt($encrypted_data, 'aes-256-cbc', $key, 0, $iv);
4581
4582 }
4583
4584 /**
4585 * acf_parse_markdown
4586 *
4587 * A very basic regex-based Markdown parser function based off [slimdown](https://gist.github.com/jbroadway/2836900).
4588 *
4589 * @date 6/8/18
4590 * @since 5.7.2
4591 *
4592 * @param string $text The string to parse.
4593 * @return string
4594 */
4595
4596 function acf_parse_markdown( $text = '' ) {
4597
4598 // trim
4599 $text = trim($text);
4600
4601 // rules
4602 $rules = array (
4603 '/=== (.+?) ===/' => '<h2>$1</h2>', // headings
4604 '/== (.+?) ==/' => '<h3>$1</h3>', // headings
4605 '/= (.+?) =/' => '<h4>$1</h4>', // headings
4606 '/\[([^\[]+)\]\(([^\)]+)\)/' => '<a href="$2">$1</a>', // links
4607 '/(\*\*)(.*?)\1/' => '<strong>$2</strong>', // bold
4608 '/(\*)(.*?)\1/' => '<em>$2</em>', // intalic
4609 '/`(.*?)`/' => '<code>$1</code>', // inline code
4610 '/\n\*(.*)/' => "\n<ul>\n\t<li>$1</li>\n</ul>", // ul lists
4611 '/\n[0-9]+\.(.*)/' => "\n<ol>\n\t<li>$1</li>\n</ol>", // ol lists
4612 '/<\/ul>\s?<ul>/' => '', // fix extra ul
4613 '/<\/ol>\s?<ol>/' => '', // fix extra ol
4614 );
4615 foreach( $rules as $k => $v ) {
4616 $text = preg_replace($k, $v, $text);
4617 }
4618
4619 // autop
4620 $text = wpautop($text);
4621
4622 // return
4623 return $text;
4624 }
4625
4626 /**
4627 * acf_get_sites
4628 *
4629 * Returns an array of sites for a network.
4630 *
4631 * @date 29/08/2016
4632 * @since 5.4.0
4633 *
4634 * @param void
4635 * @return array
4636 */
4637 function acf_get_sites() {
4638 $results = array();
4639 $sites = get_sites( array( 'number' => 0 ) );
4640 if( $sites ) {
4641 foreach( $sites as $site ) {
4642 $results[] = get_site( $site )->to_array();
4643 }
4644 }
4645 return $results;
4646 }
4647
4648 /**
4649 * acf_convert_rules_to_groups
4650 *
4651 * Converts an array of rules from ACF4 to an array of groups for ACF5
4652 *
4653 * @date 25/8/18
4654 * @since 5.7.4
4655 *
4656 * @param array $rules An array of rules.
4657 * @param string $anyorall The anyorall setting used in ACF4. Defaults to 'any'.
4658 * @return array
4659 */
4660 function acf_convert_rules_to_groups( $rules, $anyorall = 'any' ) {
4661
4662 // vars
4663 $groups = array();
4664 $index = 0;
4665
4666 // loop
4667 foreach( $rules as $rule ) {
4668
4669 // extract vars
4670 $group = acf_extract_var( $rule, 'group_no' );
4671 $order = acf_extract_var( $rule, 'order_no' );
4672
4673 // calculate group if not defined
4674 if( $group === null ) {
4675 $group = $index;
4676
4677 // use $anyorall to determine if a new group is needed
4678 if( $anyorall == 'any' ) {
4679 $index++;
4680 }
4681 }
4682
4683 // calculate order if not defined
4684 if( $order === null ) {
4685 $order = isset($groups[ $group ]) ? count($groups[ $group ]) : 0;
4686 }
4687
4688 // append to group
4689 $groups[ $group ][ $order ] = $rule;
4690
4691 // sort groups
4692 ksort( $groups[ $group ] );
4693 }
4694
4695 // sort groups
4696 ksort( $groups );
4697
4698 // return
4699 return $groups;
4700 }
4701
4702 /**
4703 * acf_register_ajax
4704 *
4705 * Regsiters an ajax callback.
4706 *
4707 * @date 5/10/18
4708 * @since 5.7.7
4709 *
4710 * @param string $name The ajax action name.
4711 * @param array $callback The callback function or array.
4712 * @param bool $public Whether to allow access to non logged in users.
4713 * @return void
4714 */
4715 function acf_register_ajax( $name = '', $callback = false, $public = false ) {
4716
4717 // vars
4718 $action = "acf/ajax/$name";
4719
4720 // add action for logged-in users
4721 add_action( "wp_ajax_$action", $callback );
4722
4723 // add action for non logged-in users
4724 if( $public ) {
4725 add_action( "wp_ajax_nopriv_$action", $callback );
4726 }
4727 }
4728
4729 /**
4730 * acf_str_camel_case
4731 *
4732 * Converts a string into camelCase.
4733 * Thanks to https://stackoverflow.com/questions/31274782/convert-array-keys-from-underscore-case-to-camelcase-recursively
4734 *
4735 * @date 24/10/18
4736 * @since 5.8.0
4737 *
4738 * @param string $string The string ot convert.
4739 * @return string
4740 */
4741 function acf_str_camel_case( $string = '' ) {
4742 return lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', $string))));
4743 }
4744
4745 /**
4746 * acf_array_camel_case
4747 *
4748 * Converts all aray keys to camelCase.
4749 *
4750 * @date 24/10/18
4751 * @since 5.8.0
4752 *
4753 * @param array $array The array to convert.
4754 * @return array
4755 */
4756 function acf_array_camel_case( $array = array() ) {
4757 $array2 = array();
4758 foreach( $array as $k => $v ) {
4759 $array2[ acf_str_camel_case($k) ] = $v;
4760 }
4761 return $array2;
4762 }
4763
4764 /**
4765 * Returns true if the current screen is using the block editor.
4766 *
4767 * @date 13/12/18
4768 * @since 5.8.0
4769 *
4770 * @return bool
4771 */
4772 function acf_is_block_editor() {
4773 if ( function_exists( 'get_current_screen' ) ) {
4774 $screen = get_current_screen();
4775 if( $screen && method_exists( $screen, 'is_block_editor' ) ) {
4776 return $screen->is_block_editor();
4777 }
4778 }
4779 return false;
4780 }
4781