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