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