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