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