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
← All changes | includes/classes/tools.php +874 -283 1.0.0 → 3.6.6 View file →
@@ -1,283 +1,874 @@
1 -<?php
2 -
3 -namespace Jet_Form_Builder\Classes;
4 -
5 -// If this file is called directly, abort.
6 -use Jet_Form_Builder\Plugin;
7 -
8 -if ( ! defined( 'WPINC' ) ) {
9 - die;
10 -}
11 -
12 -
13 -class Tools {
14 -
15 - public static function is_editor() {
16 - $action = ! empty( $_GET['context'] ) ? esc_attr( $_GET['context'] ) : '';
17 -
18 - return in_array( $action, array( 'add', 'edit' ) );
19 - }
20 -
21 - /**
22 - * Returns all post types list to use in JS components
23 - *
24 - * @param bool $placeholder
25 - *
26 - * @param array $args
27 - * @param string $operator
28 - *
29 - * @return array [type] [description]
30 - */
31 - public static function get_post_types_for_js( $placeholder = false, $args = array(), $operator = 'and' ) {
32 -
33 - $post_types = get_post_types( $args, 'objects', $operator );
34 -
35 - $post_types_list = array();
36 -
37 - if ( $placeholder && is_array( $placeholder ) ) {
38 - $placeholder['value'] = isset( $placeholder['value'] ) ? $placeholder['value'] : '';
39 - $post_types_list[] = $placeholder;
40 - }
41 -
42 - foreach ( $post_types as $post_type ) {
43 - if ( $post_type->name !== Plugin::instance()->post_type->slug() ) {
44 - $post_types_list[] = array(
45 - 'value' => $post_type->name,
46 - 'label' => $post_type->label,
47 - );
48 - }
49 - }
50 -
51 -
52 - return $post_types_list;
53 - }
54 -
55 - /**
56 - * Get post types list for options.
57 - *
58 - * @return array
59 - */
60 - public static function get_post_types_for_options() {
61 -
62 - return self::get_post_types_for_js(
63 - array( 'label' => '--' ),
64 - array( 'public' => true )
65 - );
66 -
67 - }
68 -
69 - /**
70 - * Sanitize WYSIWYG field
71 - *
72 - * @param $input
73 - *
74 - * @return string
75 - */
76 - public static function sanitize_wysiwyg( $input ) {
77 - $input = wpautop( $input );
78 -
79 - return wp_kses_post( $input );
80 - }
81 -
82 - /**
83 - * Return all taxonomies list to use in JS components
84 - *
85 - * @return [type] [description]
86 - */
87 - public static function get_taxonomies_for_js() {
88 - $taxonomies = get_taxonomies( array(), 'objects' );
89 -
90 - return self::with_placeholder( self::prepare_list_for_js( $taxonomies, 'name', 'label' ) );
91 - }
92 -
93 - public static function get_generators_list_for_js() {
94 - $generators = Plugin::instance()->form->get_generators_list();
95 -
96 - return self::prepare_list_for_js( $generators );
97 - }
98 -
99 - public static function get_allowed_mimes_list_for_js() {
100 - $mimes = get_allowed_mime_types();
101 -
102 - $mimes_list = array();
103 - foreach ( $mimes as $mime ) {
104 - $mimes_list[] = array(
105 - 'label' => $mime,
106 - 'value' => $mime
107 - );
108 - }
109 -
110 - return $mimes_list;
111 - }
112 -
113 - /**
114 - * Returns all registeredroles for JS
115 - */
116 - public static function get_user_roles_for_js() {
117 -
118 - $roles = self::get_user_roles();
119 - $result = array();
120 -
121 - foreach ( $roles as $role => $label ) {
122 - $result[] = array(
123 - 'value' => $role,
124 - 'label' => $label,
125 - );
126 - }
127 -
128 - return self::with_placeholder( $result );
129 - }
130 -
131 - public static function get_options_pages_for_js() {
132 - $pages = array();
133 -
134 - if ( function_exists( 'jet_engine' ) ) {
135 - $pages = jet_engine()->options_pages->get_options_pages_for_select();
136 - }
137 -
138 - return self::prepare_list_for_js( $pages );
139 - }
140 -
141 - /**
142 - * Returns pages list
143 - * @return [type] [description]
144 - */
145 - public static function get_pages_list_for_js() {
146 - $pages = get_pages();
147 -
148 - return self::prepare_list_for_js( $pages, 'ID', 'post_title' );
149 - }
150 -
151 - /**
152 - * Returns pages list
153 - * @return [type] [description]
154 - */
155 - public static function get_forms_list_for_js() {
156 - $posts = get_posts( array(
157 - 'post_status' => 'publish',
158 - 'posts_per_page' => - 1,
159 - 'post_type' => jet_form_builder()->post_type->slug(),
160 - ) );
161 -
162 - return self::prepare_list_for_js( $posts, 'ID', 'post_title' );
163 - }
164 -
165 - /**
166 - * Returns all registered user roles
167 - *
168 - * @return [type] [description]
169 - */
170 - public static function get_user_roles() {
171 -
172 - if ( ! function_exists( 'get_editable_roles' ) ) {
173 - return array();
174 - } else {
175 - $roles = get_editable_roles();
176 - $result = array();
177 -
178 - foreach ( $roles as $role => $data ) {
179 - $result[ $role ] = $data['name'];
180 - }
181 -
182 - return $result;
183 - }
184 - }
185 -
186 - /**
187 - * Prepare passed array for using in JS options
188 - *
189 - * @return [type] [description]
190 - */
191 - public static function prepare_list_for_js( $array = array(), $value_key = null, $label_key = null ) {
192 -
193 - $result = array();
194 -
195 - if ( ! is_array( $array ) || empty( $array ) ) {
196 - return $result;
197 - }
198 -
199 - foreach ( $array as $key => $item ) {
200 -
201 - $value = null;
202 - $label = null;
203 -
204 - if ( is_object( $item ) ) {
205 - $value = $item->$value_key;
206 - $label = $item->$label_key;
207 - } elseif ( is_array( $item ) ) {
208 - $value = $item[ $value_key ];
209 - $label = $item[ $label_key ];
210 - } else {
211 - $value = $key;
212 - $label = $item;
213 - }
214 -
215 - $result[] = array(
216 - 'value' => $value,
217 - 'label' => $label,
218 - );
219 - }
220 -
221 - return $result;
222 -
223 - }
224 -
225 - public static function with_placeholder( $array, $label = '--' ) {
226 - return array_merge(
227 - array(
228 - array( 'label' => $label, 'value' => '' ),
229 - ),
230 - $array
231 - );
232 - }
233 -
234 - /**
235 - * Check if is valid timestamp
236 - *
237 - * @param mixed $timestamp
238 - *
239 - * @return boolean
240 - */
241 - public static function is_valid_timestamp( $timestamp ) {
242 - return ( ( string ) ( int ) $timestamp === $timestamp || ( int ) $timestamp === $timestamp )
243 - && ( $timestamp <= PHP_INT_MAX )
244 - && ( $timestamp >= ~PHP_INT_MAX );
245 - }
246 -
247 - public static function maybe_recursive_sanitize( $source = null ) {
248 - if ( ! is_array( $source ) ) {
249 - return esc_attr( $source );
250 - }
251 -
252 - $result = array();
253 - foreach ( $source as $key => $value ) {
254 - $result[ $key ] = self::maybe_recursive_sanitize( $value );
255 - }
256 -
257 - return $result;
258 - }
259 -
260 - public static function sanitize_files( $source ) {
261 - if ( ! is_array( $source ) ) {
262 - return false;
263 - }
264 -
265 - $response = array();
266 -
267 - foreach ( $source as $index => $item ) {
268 - foreach ( $item as $key => $value ) {
269 - switch ( $key ) {
270 - case 'error':
271 - case 'size':
272 - $response[ $index ][ $key ] = absint( $value );
273 - break;
274 - default:
275 - $response[ $index ][ $key ] = esc_attr( $value );
276 - }
277 - }
278 - }
279 -
280 - return $response;
281 - }
282 -
283 -}
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 +}