PluginProbe
JetFormBuilder — Dynamic Blocks Form Builder / 3.6.6
JetFormBuilder — Dynamic Blocks Form Builder v3.6.6
3.6.6.1 3.6.6 3.6.5.4 3.6.5.3 3.6.5.2 3.6.5.1 3.6.5 3.6.4.2 3.6.4.1 3.6.4 3.6.3.1 3.6.3 3.6.2.2 3.6.2.1 3.6.2 3.6.1.1 3.6.1 3.6.0.1 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 All 134 releases
jetformbuilder / includes / classes / tools.php

tools.php in JetFormBuilder — Dynamic Blocks Form Builder 3.6.6, at includes/classes/tools.php

875 lines 20.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Jet_Form_Builder\Classes;
4
5 use Jet_Form_Builder\Plugin;
6 use Jet_Form_Builder\Generators\Registry as GeneratorRegistry;
7
8 // If this file is called directly, abort.
9 if ( ! defined( 'WPINC' ) ) {
10 die;
11 }
12
13 class Tools {
14
15 const EMPTY_DEEP_VALUE = self::class;
16
17 public static function is_editor() {
18 return self::is_block_editor() || self::is_elementor_editor() || self::is_bricks_editor();
19 }
20
21 public static function is_block_editor() {
22 $allowed_actions = array( 'add', 'edit' );
23
24 return (
25 in_array( self::sanitize_get_param( 'context' ), $allowed_actions, true )
26 || in_array( self::sanitize_get_param( 'action' ), $allowed_actions, true )
27 );
28 }
29
30 public static function sanitize_get_param( $param_name ) {
31 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
32 return ! empty( $_GET[ $param_name ] ) ? sanitize_key( $_GET[ $param_name ] ) : '';
33 }
34
35 public static function is_elementor_editor() {
36 if ( ! defined( 'ELEMENTOR_VERSION' ) ) {
37 return false;
38 }
39
40 return ( \Elementor\Plugin::instance()->editor->is_edit_mode() );
41 }
42
43 public static function is_bricks_editor() {
44 if ( ! defined( 'BRICKS_VERSION' ) ) {
45 return false;
46 }
47
48 return ( bricks_is_builder() );
49 }
50
51 /**
52 * Returns all post types list to use in JS components
53 *
54 * @param bool $placeholder
55 *
56 * @param array $args
57 * @param string $operator
58 *
59 * @return array [type] [description]
60 */
61 public static function get_post_types_for_js( $placeholder = false, $args = array(), $operator = 'and' ) {
62
63 $post_types = get_post_types( $args, 'objects', $operator );
64
65 $post_types_list = array();
66
67 if ( $placeholder && is_array( $placeholder ) ) {
68 $placeholder['value'] = isset( $placeholder['value'] ) ? $placeholder['value'] : '';
69 $post_types_list[] = $placeholder;
70 }
71
72 foreach ( $post_types as $post_type ) {
73 if ( Plugin::instance()->post_type->slug() !== $post_type->name ) {
74 $post_types_list[] = array(
75 'value' => $post_type->name,
76 'label' => $post_type->label,
77 );
78 }
79 }
80
81 return self::with_placeholder( $post_types_list );
82 }
83
84 /**
85 * Get post types list for options.
86 *
87 * @return array
88 */
89 public static function get_post_types_for_options(): array {
90 return self::get_post_types_for_js( false, array( 'public' => true ) );
91 }
92
93 private static function get_escape_func( $type ) {
94 switch ( $type ) {
95 case 'template':
96 default:
97 return array( self::class, 'esc_template' );
98 }
99 }
100
101 /**
102 * Sanitize WYSIWYG field
103 *
104 * @param $input
105 *
106 * @return string
107 */
108 public static function sanitize_wysiwyg( $input ): string {
109 $input = wp_specialchars_decode( stripslashes( $input ), ENT_COMPAT );
110
111 return wp_kses_post( $input );
112 }
113
114 /**
115 * Return all taxonomies list to use in JS components
116 *
117 * @param array $args
118 *
119 * @return array
120 */
121 public static function get_taxonomies_for_js( $args = array() ): array {
122 $taxonomies = get_taxonomies( $args, 'objects' );
123
124 return self::with_placeholder( self::prepare_list_for_js( $taxonomies, 'name', 'label' ) );
125 }
126
127 public static function get_taxonomies_for_modify( array $args = array() ): array {
128 $taxonomies = get_taxonomies( $args, 'objects' );
129 $response = array();
130
131 /** @var \WP_Taxonomy $taxonomy */
132 foreach ( $taxonomies as $taxonomy ) {
133 $response[] = array(
134 'label' => $taxonomy->label,
135 'value' => "jet_tax__{$taxonomy->name}",
136 );
137 }
138
139 return self::with_placeholder( $response );
140 }
141
142 public static function get_generators_list_for_js(): array {
143 $generators = Plugin::instance()->form->get_generators_list();
144
145 return self::prepare_list_for_js( $generators );
146 }
147
148 /**
149 * Get generator schemas for JS localization.
150 *
151 * @return array
152 */
153 public static function get_generator_schemas_for_js(): array {
154 if ( ! class_exists( GeneratorRegistry::class ) ) {
155 return array();
156 }
157
158 try {
159 $registry = GeneratorRegistry::instance();
160 return $registry->get_schemas_for_js();
161 } catch ( \Exception $e ) {
162 return array();
163 }
164 }
165
166 public static function get_allowed_mimes_list_for_js(): array {
167 return array_values( get_allowed_mime_types() );
168 }
169
170 /**
171 * Returns all registeredroles for JS
172 */
173 public static function get_user_roles_for_js( $exclude = array( 'administrator' ) ) {
174
175 $roles = self::get_user_roles( $exclude );
176 $result = array();
177
178 foreach ( $roles as $role => $label ) {
179 $result[] = array(
180 'value' => $role,
181 'label' => $label,
182 );
183 }
184
185 return self::with_placeholder( $result );
186 }
187
188 public static function get_options_pages_for_js() {
189 $pages = array();
190
191 if ( function_exists( 'jet_engine' ) ) {
192 $pages = jet_engine()->options_pages->get_options_pages_for_select();
193 }
194
195 return self::prepare_list_for_js( $pages );
196 }
197
198 /**
199 * Returns pages list
200 *
201 * @return [type] [description]
202 */
203 public static function get_pages_list_for_js() {
204 $pages = get_pages();
205
206 return self::prepare_list_for_js( $pages, 'ID', 'post_title' );
207 }
208
209 /**
210 * Returns pages list
211 *
212 * @param bool $for_elementor
213 *
214 * @param array $args
215 *
216 * @return array [description]
217 */
218 public static function get_forms_list_for_js( $for_elementor = false, $args = array() ) {
219 $posts = get_posts(
220 array_merge(
221 array(
222 'post_status' => 'publish',
223 'posts_per_page' => - 1,
224 'post_type' => jet_form_builder()->post_type->slug(),
225 ),
226 $args
227 )
228 );
229
230 $prepared_list_for_js = self::prepare_list_for_js( $posts, 'ID', 'post_title', $for_elementor );
231
232 if ( true === $for_elementor ) {
233 $manual_form_option = array(
234 'manual_form_id' => __( 'Enter Form ID Manually / Dynamically', 'jet-form-builder' ),
235 );
236
237 $prepared_list_for_js = array_replace( $manual_form_option, $prepared_list_for_js );
238 } else {
239 $manual_form_option[] = array(
240 'value' => -1,
241 'label' => __( 'Enter Form ID Manually / Dynamically', 'jet-form-builder' ),
242 );
243
244 $prepared_list_for_js = array_merge( $manual_form_option, $prepared_list_for_js );
245 }
246
247 return $prepared_list_for_js;
248 }
249
250 /**
251 * Returns all registered user roles
252 *
253 * @param string[] $exclude
254 *
255 * @return array [type] [description]
256 */
257 public static function get_user_roles( $exclude = array( 'administrator' ) ) {
258
259 if ( ! function_exists( 'get_editable_roles' ) ) {
260 return array();
261 } else {
262 $roles = get_editable_roles();
263 $result = array();
264
265 foreach ( $roles as $role => $data ) {
266 if ( ! in_array( $role, $exclude, true ) ) {
267 $result[ $role ] = $data['name'];
268 }
269 }
270
271 return $result;
272 }
273 }
274
275 /**
276 * Returns editable roles and ensures the core helper is loaded.
277 *
278 * @return array
279 */
280 public static function get_editable_roles_safe(): array {
281 if ( ! function_exists( 'get_editable_roles' ) ) {
282 require_once ABSPATH . 'wp-admin/includes/user.php';
283 }
284
285 if ( ! function_exists( 'get_editable_roles' ) ) {
286 return array();
287 }
288
289 return get_editable_roles();
290 }
291
292 /**
293 * Returns all registered roles without filtering by the current user's capabilities.
294 *
295 * @return array
296 */
297 public static function get_registered_roles_safe(): array {
298 $wp_roles = wp_roles();
299
300 if ( ! is_a( $wp_roles, \WP_Roles::class ) ) {
301 return array();
302 }
303
304 return $wp_roles->roles ?? array();
305 }
306
307 /**
308 * Returns auto-detected low-privilege roles that can be used as
309 * a starting point for self-service role transitions.
310 *
311 * @return string[]
312 */
313 public static function get_default_self_promotable_roles(): array {
314 $editable_roles = self::get_registered_roles_safe();
315
316 if ( empty( $editable_roles ) ) {
317 return array();
318 }
319
320 $roles = array();
321
322 foreach ( array_keys( $editable_roles ) as $role ) {
323 if ( self::is_safe_self_promotable_role( $role ) ) {
324 $roles[] = $role;
325 }
326 }
327
328 return array_values( array_unique( $roles ) );
329 }
330
331 public static function is_safe_self_promotable_role( string $role ): bool {
332 if ( 'administrator' === $role ) {
333 return false;
334 }
335
336 $wp_roles = wp_roles();
337
338 if ( ! isset( $wp_roles->roles[ $role ] ) ) {
339 return false;
340 }
341
342 $caps = array_filter( $wp_roles->roles[ $role ]['capabilities'] ?? array() );
343
344 $dangerous_caps = array(
345 'activate_plugins',
346 'create_users',
347 'delete_users',
348 'edit_users',
349 'list_users',
350 'promote_users',
351 'remove_users',
352 'manage_options',
353 'edit_theme_options',
354 'switch_themes',
355 'install_plugins',
356 'update_plugins',
357 'delete_plugins',
358 'install_themes',
359 'update_themes',
360 'delete_themes',
361 'unfiltered_html',
362 'edit_others_posts',
363 'delete_others_posts',
364 'publish_posts',
365 'manage_categories',
366 );
367
368 foreach ( $dangerous_caps as $cap ) {
369 if ( ! empty( $caps[ $cap ] ) ) {
370 return false;
371 }
372 }
373
374 return true;
375 }
376
377 /**
378 * Prepare passed array for using in JS options
379 *
380 * @param array $array
381 * @param null $value_key
382 * @param null $label_key
383 * @param bool $for_elementor
384 *
385 * Only if $for_elementor === false
386 * @param array $additional_attrs
387 *
388 * @return array [type] [description]
389 */
390 public static function prepare_list_for_js(
391 $collection = array(),
392 $value_key = null,
393 $label_key = null,
394 $for_elementor = false,
395 $additional_attrs = array()
396 ) {
397
398 $result = array();
399
400 if ( ! is_array( $collection ) || empty( $collection ) ) {
401 return $result;
402 }
403
404 foreach ( $collection as $key => $item ) {
405
406 $value = null;
407 $label = null;
408
409 if ( is_scalar( $item ) ) {
410 $value = $key;
411 $label = $item;
412 } else {
413 $value = self::get_property( $item, $value_key );
414 $label = self::get_property( $item, $label_key );
415 }
416
417 if ( $for_elementor ) {
418 $result[ $value ] = $label;
419 } else {
420 $prepared = array(
421 'value' => $value,
422 'label' => $label,
423 );
424 foreach ( $additional_attrs as $attr ) {
425 $prepared[ $attr ] = self::get_property( $item, $attr );
426 }
427
428 $result[] = $prepared;
429 }
430 }
431
432 return $result;
433 }
434
435 /**
436 * @param array $collection
437 * @param string $label
438 *
439 * @return array
440 */
441 public static function with_placeholder( array $collection, string $label = '--' ): array {
442 return array_merge(
443 array(
444 array( 'label' => $label, 'value' => '' ),
445 ),
446 $collection
447 );
448 }
449
450 /**
451 * Check if is valid timestamp
452 *
453 * @param mixed $timestamp
454 *
455 * @return boolean
456 */
457 public static function is_valid_timestamp( $timestamp ): bool {
458 return ( (string) (int) $timestamp === $timestamp || (int) $timestamp === $timestamp )
459 && ( $timestamp <= PHP_INT_MAX )
460 && ( $timestamp >= ~PHP_INT_MAX );
461 }
462
463 public static function array_merge_intersect_key( $source, $arrays ) {
464 foreach ( $source as $index => $path ) {
465 $name = $path['path'] ?? $index;
466
467 $deep_value = self::getDeepValue( $name, $arrays );
468
469 if ( self::EMPTY_DEEP_VALUE === $deep_value ) {
470 unset( $source[ $index ] );
471 } else {
472 $source[ $index ] = $deep_value;
473 }
474 }
475
476 return $source;
477 }
478
479 // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
480 public static function getDeepValue( $key, $source ) {
481 $keys = explode( '/', $key );
482 $last = end( $keys );
483 reset( $keys );
484
485 return self::deep( $keys, current( $keys ), $last, $source );
486 }
487
488 private static function deep( $collection, $key, $last, $source ) {
489
490 if ( isset( $source[ $key ] ) ) {
491 if ( $last !== $key ) {
492 return self::deep( $collection, next( $collection ), $last, $source[ $key ] );
493 }
494
495 return $source[ $key ];
496 }
497
498 return self::EMPTY_DEEP_VALUE;
499 }
500
501 public static function call( $callback, ...$params ) {
502 if ( ! is_callable( $callback ) ) {
503 return;
504 }
505
506 call_user_func( $callback, ...$params );
507 }
508
509 public static function decode_unserializable( $value ) {
510 $data = self::decode_json( $value );
511
512 // phpcs:ignore Universal.Operators.DisallowShortTernary.Found
513 return $data ?: maybe_unserialize( $value );
514 }
515
516 public static function decode_json( $json ) {
517 if ( is_array( $json ) ) {
518 foreach ( $json as $key => $row ) {
519 $json[ $key ] = static::decode_json( $row );
520 }
521
522 return $json;
523 }
524 if ( defined( 'JSON_INVALID_UTF8_IGNORE' ) ) {
525 // phpcs:ignore PHPCompatibility.Constants.NewConstants
526 return json_decode( $json, true, 512, JSON_INVALID_UTF8_IGNORE );
527 }
528
529 return json_decode( $json, true );
530 }
531
532 public static function encode_json( $json ) {
533 return wp_json_encode( $json, JSON_UNESCAPED_UNICODE );
534 }
535
536 public static function sanitize_recursive( $source = null ) {
537 if ( ! is_array( $source ) ) {
538 return self::sanitize( $source );
539 }
540
541 $result = array();
542
543 foreach ( $source as $key => $value ) {
544 $key = is_string( $key ) ? self::sanitize_array_key( $key ) : $key;
545
546 // When sanitized keys collide, keep the first value.
547 if ( array_key_exists( $key, $result ) ) {
548 continue;
549 }
550
551 $result[ $key ] = self::sanitize_recursive( $value );
552 }
553
554 return $result;
555 }
556
557 /**
558 * Strips tags from an array key. Keys without tags stay unchanged,
559 * preserving whitespace and percent-encoded sequences in field names.
560 *
561 * @param string $key
562 *
563 * @return string
564 */
565 public static function sanitize_array_key( string $key ): string {
566 return false === strpos( $key, '<' ) ? $key : wp_strip_all_tags( $key, false );
567 }
568
569 public static function maybe_recursive_sanitize( $source = null ) {
570 return self::sanitize_recursive( $source );
571 }
572
573 public static function sanitize( $source ) {
574 if ( self::is_url( $source ) ) {
575 return esc_url_raw( $source );
576 }
577
578 return self::sanitize_text_field( $source );
579 }
580
581 public static function is_url( $url ) {
582 return wp_http_validate_url( $url );
583 }
584
585 public static function sanitize_text_field( $source, $replace_enqueue = true ) {
586 $str = (string) $source;
587
588 $filtered = wp_check_invalid_utf8( $str );
589 $sanitize_callback = apply_filters( 'jet-form-builder/sanitize-string/callback', false );
590
591 if ( $sanitize_callback && is_callable( $sanitize_callback ) ) {
592 $filtered = call_user_func( $sanitize_callback, $filtered );
593 } elseif ( $replace_enqueue && false !== strpos( $filtered, '<' ) ) {
594 $filtered = wp_kses_post( $filtered );
595 }
596
597 return trim( $filtered );
598 }
599
600 /**
601 * @param $type
602 * @param mixed ...$values
603 *
604 * @return mixed
605 */
606 private static function call_escape_func( $type, ...$values ) {
607 return call_user_func( self::get_escape_func( $type ), ...$values );
608 }
609
610 public static function recursive_wp_kses( $source, $allowed_html = 'strip' ) {
611 if ( ! is_array( $source ) ) {
612 return wp_kses( $source, $allowed_html );
613 }
614
615 $result = array();
616 foreach ( $source as $key => $value ) {
617 $key = is_string( $key ) ? self::sanitize_array_key( $key ) : $key;
618
619 if ( array_key_exists( $key, $result ) ) {
620 continue;
621 }
622
623 $result[ $key ] = self::sanitize_recursive( $value );
624 }
625
626 return $result;
627 }
628
629 public static function sanitize_files( $source ) {
630 if ( ! is_array( $source ) ) {
631 return false;
632 }
633
634 $response = array();
635
636 foreach ( $source as $index => $item ) {
637 foreach ( $item as $key => $value ) {
638 switch ( $key ) {
639 case 'error':
640 case 'size':
641 $response[ $index ][ $key ] = absint( $value );
642 break;
643 case 'name':
644 $response[ $index ][ $key ] = sanitize_file_name( $value );
645 break;
646 case 'type':
647 $response[ $index ][ $key ] = sanitize_mime_type( $value );
648 break;
649 case 'tmp_name':
650 $response[ $index ][ $key ] = sanitize_text_field( $value );
651 break;
652 }
653 }
654 }
655
656 return $response;
657 }
658
659 /**
660 * @param $source
661 * @param bool $replace_enqueue
662 *
663 * @return string
664 */
665 private static function esc_template( $source, $replace_enqueue = true ): string {
666 return self::sanitize_text_field( $source, $replace_enqueue );
667 }
668
669 public static function get_jet_engine_version() {
670 return function_exists( 'jet_engine' )
671 ? jet_engine()->get_version()
672 : false;
673 }
674
675 public static function is_readable( string $filename ) {
676 // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
677 return strlen( $filename ) <= PHP_MAXPATHLEN && @is_readable( $filename );
678 }
679
680 /**
681 * Returns template path
682 *
683 * @param [type] $path [description]
684 *
685 * @return [type] [description]
686 */
687 public static function get_global_template( $path = '' ) {
688 return JET_FORM_BUILDER_PATH . 'templates/' . $path;
689 }
690
691 public static function get_property( $source, $name, $if_not_exist = '' ) {
692 if ( is_object( $source ) ) {
693 return $source->{$name} ?? $if_not_exist;
694 }
695
696 return $source[ $name ] ?? $if_not_exist;
697 }
698
699 public static function esc_template_string( $source, $replace_enqueue = true ) {
700 return self::call_escape_func( 'template', $source, $replace_enqueue );
701 }
702
703 public static function is_repeater_val( $value ): bool {
704 if ( is_array( $value ) && ! empty( $value ) ) {
705 foreach ( $value as $item ) {
706 return is_array( $item );
707 }
708 }
709
710 return false;
711 }
712
713 public static function set_current_post( $post_id ) {
714 global $post;
715
716 // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
717 $post = get_post( absint( $post_id ) );
718 }
719
720 public static function prepare_repeater_value( $value, $fields_map ): array {
721 $prepared_value = array();
722
723 foreach ( $value as $index => $row ) {
724 $prepared_row = array();
725
726 foreach ( $row as $item_key => $item_value ) {
727 $item_key = ! empty( $fields_map[ $item_key ] ) ? self::sanitize_text_field( $fields_map[ $item_key ] ) : $item_key;
728 $prepared_row[ $item_key ] = $item_value;
729 }
730
731 $prepared_value[ 'item-' . $index ] = $prepared_row;
732 }
733
734 return $prepared_value;
735 }
736
737 public static function is_webhook(): bool {
738 return (
739 defined( 'JET_FB_REST_WEBHOOK' ) &&
740 JET_FB_REST_WEBHOOK
741 );
742 }
743
744 public static function esc_attr( $value ) {
745 if ( ! is_scalar( $value ) ) {
746 return esc_attr( self::encode_json( $value ) );
747 }
748
749 return esc_attr( $value );
750 }
751
752 public static function get_suffix(): string {
753 return defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
754 }
755
756 /**
757 * Get from function below
758 *
759 * @return string
760 * @see \retrieve_password
761 */
762 public static function get_site_name(): string {
763 if ( is_multisite() ) {
764 return get_network()->site_name;
765 }
766
767 /*
768 * The blogname option is escaped with esc_html on the way into the database
769 * in sanitize_option. We want to reverse this for the plain text arena of emails.
770 */
771
772 return wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );
773 }
774
775 /**
776 * @param mixed $value
777 */
778 public static function to_string( $value ): string {
779 if ( is_array( $value ) ) {
780 return implode(
781 ',',
782 array_map( array( self::class, 'to_string' ), $value )
783 );
784 }
785
786 if ( is_object( $value ) && ! method_exists( $value, '__toString' ) ) {
787 return '';
788 }
789
790 return (string) $value;
791 }
792
793 public static function is_empty( $value ): bool {
794 return '0' !== $value && 0 !== $value && empty( $value );
795 }
796
797 public static function contains_registered_shortcode( $string ) {
798 global $shortcode_tags;
799
800 foreach ( array_keys( $shortcode_tags ) as $shortcode ) {
801 if ( strpos( $string, "[$shortcode" ) !== false ) {
802 return true;
803 }
804 }
805
806 return false;
807 }
808
809 /**
810 * Check if string is a WordPress password hash
811 * Supports all WordPress hash formats:
812 * - Old Portable Hash: $P$...
813 * - Bcrypt: $2a$, $2y$...
814 *
815 * @param string $hash Password hash to check
816 * @return bool
817 */
818 public static function is_wp_password_hash( $hash ) {
819 if ( ! is_string( $hash ) || empty( $hash ) ) {
820 return false;
821 }
822
823 // Old Portable Hash format: $P$[A-Za-z0-9./]{31}
824 if ( preg_match( '/^\$P\$[A-Za-z0-9\.\/]{31}$/', $hash ) ) {
825 return true;
826 }
827
828 // Bcrypt format: $2a$, $2x$, $2y$ followed by cost and hash
829 if ( preg_match( '/^\$2[axy]\$\d{2}\$[A-Za-z0-9\.\/]{53}$/', $hash ) ) {
830 return true;
831 }
832
833 return false;
834 }
835
836 public static function get_array_of_user_roles( $settings ) {
837 if ( is_string( $settings ) ) {
838 return '' === $settings ? array() : array( $settings );
839 }
840
841 if ( ! is_array( $settings ) ) {
842 return array();
843 }
844
845 return $settings;
846 }
847
848 public static function get_main_user_role_by_priority( $roles ): string {
849 if ( is_string( $roles ) ) {
850 $roles = array( $roles );
851 }
852 $wp_roles_priority = array(
853 'editor' => 4,
854 'author' => 3,
855 'contributor' => 2,
856 'subscriber' => 1,
857 );
858
859 $wp_roles = array_intersect( $roles, array_keys( $wp_roles_priority ) );
860 $custom_roles = array_diff( $roles, array_keys( $wp_roles_priority ) );
861
862 usort(
863 $wp_roles,
864 function ( $a, $b ) use ( $wp_roles_priority ) {
865 return ( $wp_roles_priority[ $b ] ?? 0 ) <=> ( $wp_roles_priority[ $a ] ?? 0 );
866 }
867 );
868
869 return $wp_roles[0] ?? $custom_roles[0] ?? '';
870 }
871
872
873
874 }
875