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