PluginProbe ʕ •ᴥ•ʔ
WPForms – Easy Form Builder for WordPress – Contact Forms, Payment Forms, Surveys, & More / 1.9.8.7
WPForms – Easy Form Builder for WordPress – Contact Forms, Payment Forms, Surveys, & More v1.9.8.7
1.10.1.1 1.10.1 1.10.0.5 trunk 1.1.4 1.1.4.2 1.1.5 1.1.5.1 1.1.6 1.1.6.1 1.1.7 1.1.7.1 1.1.7.2 1.1.8 1.1.8.1 1.1.8.2 1.1.8.3 1.1.8.4 1.10.0.1 1.10.0.2 1.10.0.3 1.10.0.4 1.2.0 1.2.0.1 1.2.1 1.2.2 1.2.2.1 1.2.2.2 1.2.3 1.2.3.1 1.2.3.2 1.2.4 1.2.4.1 1.2.5 1.2.5.1 1.2.6 1.2.7 1.2.8 1.2.8.1 1.2.9 1.3.0 1.3.1 1.3.1.1 1.3.1.2 1.3.2 1.3.3 1.3.5 1.3.6 1.3.6.1 1.3.6.2 1.3.7.2 1.3.7.3 1.3.7.4 1.3.8 1.3.9.1 1.4.0.1 1.4.1.1 1.4.2 1.4.2.1 1.4.2.2 1.4.3 1.4.4 1.4.4.1 1.4.5 1.4.5.1 1.4.5.2 1.4.5.3 1.4.6 1.4.7.1 1.4.7.2 1.4.8.1 1.4.9 1.5.0.1 1.5.0.3 1.5.0.4 1.5.1 1.5.1.1 1.5.1.3 1.5.2.1 1.5.2.2 1.5.2.3 1.5.3 1.5.3.1 1.5.4.1 1.5.4.2 1.5.5 1.5.5.1 1.5.6 1.5.6.2 1.5.7 1.5.8.2 1.5.9.1 1.5.9.4 1.5.9.5 1.6.0.1 1.6.0.2 1.6.1 1.6.2.2 1.6.2.3 1.6.3.1 1.6.4 1.6.4.1 1.6.5 1.6.6 1.6.7 1.6.7.1 1.6.7.2 1.6.7.3 1.6.8 1.6.8.1 1.6.9 1.7.0 1.7.1.1 1.7.1.2 1.7.2 1.7.2.1 1.7.3 1.7.4 1.7.4.1 1.7.4.2 1.7.5.1 1.7.5.2 1.7.5.3 1.7.5.5 1.7.6 1.7.7 1.7.7.1 1.7.7.2 1.7.8 1.7.9 1.7.9.1 1.8.0.1 1.8.0.2 1.8.1.1 1.8.1.2 1.8.1.3 1.8.2.1 1.8.2.2 1.8.2.3 1.8.3 1.8.3.1 1.8.4 1.8.4.1 1.8.5.2 1.8.5.3 1.8.5.4 1.8.6.2 1.8.6.3 1.8.6.4 1.8.7.2 1.8.8.2 1.8.8.3 1.8.9.1 1.8.9.2 1.8.9.4 1.8.9.5 1.8.9.6 1.9.0.1 1.9.0.2 1.9.0.3 1.9.0.4 1.9.1.1 1.9.1.2 1.9.1.3 1.9.1.4 1.9.1.5 1.9.1.6 1.9.2.1 1.9.2.2 1.9.2.3 1.9.3.1 1.9.3.2 1.9.4.1 1.9.4.2 1.9.5 1.9.5.1 1.9.5.2 1.9.6 1.9.6.1 1.9.6.2 1.9.7.1 1.9.7.2 1.9.7.3 1.9.8.1 1.9.8.2 1.9.8.4 1.9.8.7 1.9.9.2 1.9.9.3 1.9.9.4
wpforms-lite / includes / functions / checks.php
wpforms-lite / includes / functions Last commit date
access.php 8 months ago builder.php 11 months ago checks.php 6 months ago colors.php 2 years ago data-presets.php 6 months ago date-time.php 11 months ago debug.php 9 months ago education.php 11 months ago escape-sanitize.php 6 months ago filesystem-media.php 1 year ago form-fields.php 8 months ago forms.php 10 months ago list.php 1 year ago payments.php 10 months ago plugins.php 11 months ago privacy.php 1 year ago providers.php 11 months ago utilities.php 5 months ago
checks.php
596 lines
1 <?php
2 /**
3 * Helper functions to perform various checks across the core plugin and addons.
4 *
5 * @since 1.8.0
6 */
7
8 // phpcs:disable Generic.Commenting.DocComment.MissingShort
9 /** @noinspection PhpUndefinedNamespaceInspection */
10 /** @noinspection PhpUndefinedClassInspection */
11 // phpcs:enable Generic.Commenting.DocComment.MissingShort
12
13 use WPForms\Tasks\Tasks;
14 use WPForms\Vendor\TrueBV\Punycode;
15
16 /**
17 * Check if a string is a valid URL.
18 *
19 * @since 1.0.0
20 * @since 1.5.8 Changed the pattern used to validate the URL.
21 *
22 * @param string $url Input URL.
23 *
24 * @return bool
25 * @noinspection RegExpUnnecessaryNonCapturingGroup
26 * @noinspection RegExpRedundantEscape
27 */
28 function wpforms_is_url( $url ): bool {
29
30 // The pattern taken from https://gist.github.com/dperini/729294.
31 // It is the best choice according to the https://mathiasbynens.be/demo/url-regex.
32 $pattern = '%^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9\x{00a1}-\x{ffff}][a-z0-9\x{00a1}-\x{ffff}_-]{0,62})?[a-z0-9\x{00a1}-\x{ffff}]\.)+(?:[a-z\x{00a1}-\x{ffff}]{2,}\.?))(?::\d{2,5})?(?:[/?#]\S*)?$%iu';
33
34 if ( preg_match( $pattern, trim( $url ) ) ) {
35 return true;
36 }
37
38 return false;
39 }
40
41 /**
42 * Verify that an email is valid.
43 * See the linked RFC.
44 *
45 * @see https://www.rfc-editor.org/rfc/inline-errata/rfc3696.html
46 *
47 * @since 1.7.3
48 *
49 * @param string $email Email address to verify.
50 *
51 * @return string|false Returns a valid email address on success, false on failure.
52 */
53 function wpforms_is_email( $email ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
54
55 static $punycode;
56
57 // Do not allow callables, arrays, and objects.
58 if ( ! is_scalar( $email ) ) {
59 return false;
60 }
61
62 // Allow smart tags in the email address.
63 if ( preg_match( '/{.+?}/', $email ) ) {
64 return $email;
65 }
66
67 // Email can't be longer than 254 octets,
68 // otherwise it can't be used to send an email address (limitation in the MAIL and RCPT commands).
69 // 1 octet = 8 bits = 1 byte.
70 if ( strlen( $email ) > 254 ) {
71 return false;
72 }
73
74 $email_arr = explode( '@', $email );
75
76 if ( count( $email_arr ) !== 2 ) {
77 return false;
78 }
79
80 [ $local, $domain ] = $email_arr;
81
82 /**
83 * RFC requires local part to be no longer than 64 octets.
84 * Punycode library checks for 63 octets.
85 *
86 * @link https://github.com/true/php-punycode/blob/master/src/Punycode.php#L182.
87 */
88 if ( strlen( $local ) > 63 ) {
89 return false;
90 }
91
92 $domain_arr = explode( '.', $domain );
93
94 foreach ( $domain_arr as $domain_label ) {
95 $domain_label = trim( $domain_label );
96
97 if ( ! $domain_label ) {
98 return false;
99 }
100
101 // The RFC says: 'A DNS label may be no more than 63 octets long'.
102 if ( strlen( $domain_label ) > 63 ) {
103 return false;
104 }
105 }
106
107 if ( ! $punycode ) {
108 $punycode = new Punycode();
109 }
110
111 /**
112 * The wp_mail() uses phpMailer, which uses is_email() as verification callback.
113 * For verification, phpMailer sends the email address where the domain part is punycode encoded only.
114 * We follow here the same principle.
115 */
116 $email_check = $local . '@' . $punycode->encode( $domain );
117
118 // Other limitations are checked by the native WordPress function is_email().
119 return is_email( $email_check ) ? $local . '@' . $domain : false;
120 }
121
122 /**
123 * Check whether the string is json-encoded.
124 *
125 * @since 1.7.5
126 *
127 * @param string $value A string.
128 *
129 * @return bool
130 */
131 function wpforms_is_json( $value ): bool {
132
133 return (
134 is_string( $value ) &&
135 is_array( json_decode( $value, true ) ) &&
136 json_last_error() === JSON_ERROR_NONE
137 );
138 }
139
140 /**
141 * Check whether the current page is in AMP mode or not.
142 * We need to check for specific functions, as there is no special AMP header.
143 *
144 * @since 1.4.1
145 *
146 * @param bool $check_theme_support Whether theme support should be checked. Defaults to true.
147 *
148 * @return bool
149 */
150 function wpforms_is_amp( $check_theme_support = true ): bool {
151
152 $is_amp = false;
153
154 // Check for AMP by AMP Project Contributors.
155 if ( function_exists( 'amp_is_request' ) && amp_is_request() ) {
156 $is_amp = true;
157 }
158
159 if ( $is_amp && $check_theme_support ) {
160 $is_amp = current_theme_supports( 'amp' );
161 }
162
163 /**
164 * Filters AMP flag.
165 *
166 * @since 1.4.1
167 *
168 * @param bool $is_amp Current page AMP status.
169 *
170 * @return bool
171 */
172 return (bool) apply_filters( 'wpforms_is_amp', $is_amp );
173 }
174
175 /**
176 * Helper function to determine if loading on WPForms related admin page.
177 *
178 * Here we determine if the current administration page is owned/created by
179 * WPForms. This is done in compliance with WordPress best practices for
180 * development, so that we only load required WPForms CSS and JS files on pages
181 * we create. As a result, we do not load our assets admin wide, where they might
182 * conflict with other plugins needlessly, also leading to a better, faster user
183 * experience for our users.
184 *
185 * @since 1.3.9
186 *
187 * @param string $slug Slug identifier for a specific WPForms admin page.
188 * @param string $view Slug identifier for a specific WPForms admin page view ("subpage").
189 *
190 * @return bool
191 */
192 function wpforms_is_admin_page( $slug = '', $view = '' ): bool {
193
194 // phpcs:disable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
195 $page = ( (array) ( $_REQUEST['page'] ?? '' ) )[0];
196
197 // Check against basic requirements.
198 if (
199 strpos( $page, 'wpforms' ) === false ||
200 ! is_admin()
201 ) {
202 return false;
203 }
204
205 // Check against page slug identifier.
206 if (
207 ( ! empty( $slug ) && $_REQUEST['page'] !== 'wpforms-' . $slug ) ||
208 ( empty( $slug ) && $_REQUEST['page'] === 'wpforms-builder' )
209 ) {
210 return false;
211 }
212
213 // Check against sublevel page view.
214 if (
215 ! empty( $view ) &&
216 ( empty( $_REQUEST['view'] ) || $_REQUEST['view'] !== $view )
217 ) {
218 return false;
219 }
220 // phpcs:enable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
221
222 return true;
223 }
224
225 /**
226 * Check if a string is empty.
227 *
228 * @since 1.5.0
229 *
230 * @param string $value String to test.
231 *
232 * @return bool
233 */
234 function wpforms_is_empty_string( $value ): bool {
235 // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement
236 return $value === '';
237 }
238
239 /**
240 * Determine if the request is a rest API call.
241 *
242 * Case #1: After WP_REST_Request initialization
243 * Case #2: Support "plain" permalink settings
244 * Case #3: It can happen that WP_Rewrite is not yet initialized,
245 * so do this (wp-settings.php)
246 * Case #4: URL Path begins with wp-json/ (your REST prefix)
247 * Also supports WP installations in sub folders
248 *
249 * @since 1.8.8
250 *
251 * @return bool True if the request is a REST API call, false if not.
252 * @author matzeeable
253 */
254 function wpforms_is_rest(): bool {
255
256 if ( ! isset( $_SERVER['REQUEST_URI'] ) ) {
257 return false;
258 }
259
260 // Case #1.
261 if ( defined( 'REST_REQUEST' ) && constant( 'REST_REQUEST' ) ) {
262 return true;
263 }
264
265 // Case #2.
266 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
267 $rest_route = isset( $_GET['rest_route'] ) ?
268 filter_input( INPUT_GET, 'rest_route', FILTER_SANITIZE_FULL_SPECIAL_CHARS ) :
269 '';
270
271 if ( strpos( trim( $rest_route, '\\/' ), rest_get_url_prefix() ) === 0 ) {
272 return true;
273 }
274
275 // Case #3.
276 global $wp_rewrite;
277 if ( $wp_rewrite === null ) {
278 // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
279 $wp_rewrite = new WP_Rewrite();
280 }
281
282 // Case #4.
283 $current_url = (string) wp_parse_url( add_query_arg( [] ), PHP_URL_PATH );
284 $rest_url = wp_parse_url( trailingslashit( rest_url() ), PHP_URL_PATH );
285
286 return strpos( $current_url, $rest_url ) === 0;
287 }
288
289 /**
290 * Determine if the request is a WPForms related rest API call.
291 *
292 * NOTE: The function shouldn't be used before the `rest_api_init` action.
293 *
294 * @since 1.9.6.1
295 *
296 * @return bool True if the request is a WPForms related rest API call, false if not.
297 */
298 function wpforms_is_wpforms_rest(): bool {
299
300 if ( ! wpforms_is_rest() ) {
301 return false;
302 }
303
304 $rest_url = wp_parse_url( trailingslashit( rest_url() ) );
305 $current_url = wp_parse_url( trailingslashit( wpforms_current_url() ) );
306 $rest_url['path'] = $rest_url['path'] ?? '';
307
308 // phpcs:disable WordPress.Security.NonceVerification.Recommended
309 $is_rest_plain = $rest_url['path'] === '/index.php' && ! empty( $_GET['rest_route'] );
310 $is_rest_post_name = strpos( $rest_url['path'], '/wp-json/' ) !== false;
311
312 if ( $is_rest_plain ) {
313 $rest_route = sanitize_text_field( wp_unslash( $_GET['rest_route'] ) );
314
315 return strpos( $rest_route, '/wpforms/' ) !== false;
316 }
317 // phpcs:enable WordPress.Security.NonceVerification.Recommended
318
319 if ( $is_rest_post_name ) {
320 return strpos( $current_url['path'] ?? '', '/wpforms/' ) !== false;
321 }
322
323 return false;
324 }
325
326 /**
327 * Determine if the request is WPForms AJAX.
328 *
329 * @since 1.8.0
330 * @since 1.9.1 Added an optional parameter to check for a specific action.
331 *
332 * @param string $action Certain AJAX action to check. Optional. Default is empty.
333 *
334 * @return bool
335 */
336 function wpforms_is_ajax( string $action = '' ): bool {
337
338 if ( ! wp_doing_ajax() ) {
339 return false;
340 }
341
342 // Make sure the request target is admin-ajax.php.
343 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
344 if ( isset( $_SERVER['SCRIPT_FILENAME'] ) && basename( sanitize_text_field( wp_normalize_path( $_SERVER['SCRIPT_FILENAME'] ) ) ) !== 'admin-ajax.php' ) {
345 return false;
346 }
347
348 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
349 $request_action = isset( $_REQUEST['action'] ) ? sanitize_key( $_REQUEST['action'] ) : '';
350 $is_wpforms_action = strpos( $request_action, 'wpforms_' ) === 0;
351
352 if ( empty( $action ) ) {
353 return $is_wpforms_action;
354 }
355
356 return $is_wpforms_action && $action === $request_action;
357 }
358
359 /**
360 * Determine if request is frontend AJAX.
361 *
362 * @since 1.5.8.2
363 * @since 1.6.5 Added filterable frontend ajax actions list as a fallback to missing referer cases.
364 * @since 1.6.7.1 Removed a requirement for an AJAX action to be a WPForms action if referer is not missing.
365 * @since 1.8.0 Added clear separation between frontend and admin AJAX requests, see `wpforms_is_admin_ajax()`.
366 *
367 * @return bool
368 */
369 function wpforms_is_frontend_ajax(): bool {
370
371 if ( wpforms_is_ajax() && ! wpforms_is_admin_ajax() ) {
372 return true;
373 }
374
375 // Try detecting a frontend AJAX call indirectly by comparing the current action
376 // with a known frontend actions list in case there's no HTTP referer.
377
378 $ref = wp_get_raw_referer();
379
380 if ( $ref ) {
381 return false;
382 }
383
384 $frontend_actions = [
385 'wpforms_submit',
386 'wpforms_file_upload_speed_test',
387 'wpforms_upload_chunk_init',
388 'wpforms_upload_chunk',
389 'wpforms_file_chunks_uploaded',
390 'wpforms_remove_file',
391 'wpforms_restricted_email',
392 'wpforms_form_locker_unique_answer',
393 'wpforms_form_abandonment',
394 ];
395
396 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
397 $action = isset( $_REQUEST['action'] ) ? sanitize_key( $_REQUEST['action'] ) : '';
398
399 /**
400 * Allow modifying the list of frontend AJAX actions.
401 *
402 * This filter may be running as early as `plugins_loaded` hook.
403 * Please mind the hook order when using it.
404 *
405 * @since 1.6.5
406 *
407 * @param array $frontend_actions A list of frontend actions.
408 */
409 $frontend_actions = (array) apply_filters( 'wpforms_is_frontend_ajax_frontend_actions', $frontend_actions );
410
411 return in_array( $action, $frontend_actions, true );
412 }
413
414 /**
415 * Determine if request is admin AJAX.
416 *
417 * @since 1.8.0
418 *
419 * @return bool
420 */
421 function wpforms_is_admin_ajax(): bool {
422
423 if ( ! wpforms_is_ajax() ) {
424 return false;
425 }
426
427 $ref = wp_get_raw_referer();
428
429 if ( ! $ref ) {
430 return false;
431 }
432
433 $path = wp_parse_url( $ref, PHP_URL_PATH );
434 $admin_path = wp_parse_url( admin_url(), PHP_URL_PATH );
435
436 // Is an admin AJAX call if HTTP referer contain an admin path.
437 return strpos( $path, $admin_path ) !== false;
438 }
439
440 /**
441 * Check if Gutenberg is active.
442 *
443 * @since 1.6.2
444 *
445 * @return bool True if Gutenberg is active.
446 * @noinspection PhpUndefinedFunctionInspection
447 */
448 function wpforms_is_gutenberg_active(): bool {
449
450 require_once ABSPATH . 'wp-admin/includes/plugin.php';
451
452 if ( is_plugin_active( 'classic-editor/classic-editor.php' ) ) {
453 return in_array( get_option( 'classic-editor-replace' ), [ 'no-replace', 'block' ], true );
454 }
455
456 if ( is_plugin_active( 'disable-gutenberg/disable-gutenberg.php' ) ) {
457 return ! disable_gutenberg();
458 }
459
460 return true;
461 }
462
463 /**
464 * Check if website support Divi Builder.
465 *
466 * @since 1.9.2.3
467 *
468 * @return bool True if Divi builder plugin or Divi or Extra theme is active.
469 */
470 function wpforms_is_divi_active(): bool {
471
472 if ( function_exists( 'et_divi_builder_init_plugin' ) ) {
473 return true;
474 }
475
476 $allow_themes = [ 'Divi', 'Extra' ];
477 $theme_name = get_template();
478
479 return in_array( $theme_name, $allow_themes, true );
480 }
481
482 /**
483 * Determines whether the current request is a WP CLI request.
484 *
485 * @since 1.7.6
486 *
487 * @return bool
488 */
489 function wpforms_doing_wp_cli(): bool {
490
491 return defined( 'WP_CLI' ) && WP_CLI;
492 }
493
494 /**
495 * Determines whether the Action Scheduler task is executing.
496 *
497 * @since 1.9.4
498 *
499 * @return bool
500 */
501 function wpforms_doing_scheduled_action(): bool {
502
503 return class_exists( Tasks::class ) && Tasks::is_executing();
504 }
505
506 /**
507 * Determines whether search functionality is enabled for Choices.js elements in the admin area.
508 *
509 * @since 1.8.3
510 *
511 * @param array $data Data to be displayed in the dropdown.
512 *
513 * @return string
514 */
515 function wpforms_choices_js_is_search_enabled( $data ): string {
516
517 /**
518 * Filter max number of items at which no search box is displayed.
519 *
520 * @since 1.8.3
521 *
522 * @param int $count Max items count.
523 */
524 return count( $data ) >= apply_filters( 'wpforms_choices_js_is_search_enabled_max_limit', 20 ) ? 'true' : 'false';
525 }
526
527 /**
528 * Check if a form is a template.
529 *
530 * @since 1.8.8
531 *
532 * @param int|WP_Post $form Form ID or object.
533 *
534 * @return bool True if the form is a template.
535 */
536 function wpforms_is_form_template( $form ): bool {
537
538 $template_post_type = 'wpforms-template';
539
540 if ( $form instanceof WP_Post ) {
541 return $form->post_type === $template_post_type;
542 }
543
544 return $template_post_type === get_post_type( $form );
545 }
546
547 /**
548 * Checks if the current screen is using the block editor.
549 *
550 * @since 1.8.8
551 *
552 * @return bool True if the current screen is using the block editor, false otherwise.
553 */
554 function wpforms_is_block_editor(): bool {
555
556 $screen = get_current_screen();
557
558 return $screen && method_exists( $screen, 'is_block_editor' ) && $screen->is_block_editor();
559 }
560
561 /**
562 * Check for the editor page.
563 *
564 * @since 1.9.0
565 *
566 * @return bool True if the page is in the editor, false otherwise.
567 */
568 function wpforms_is_editor_page(): bool {
569
570 // phpcs:disable WordPress.Security.NonceVerification
571 $rest_request = defined( 'REST_REQUEST' ) && REST_REQUEST;
572 $context = isset( $_REQUEST['context'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['context'] ) ) : '';
573 $post_action = isset( $_POST['action'] ) ? sanitize_text_field( wp_unslash( $_POST['action'] ) ) : '';
574 $get_action = isset( $_GET['action'] ) ? sanitize_text_field( wp_unslash( $_GET['action'] ) ) : '';
575
576 $is_gutenberg = $rest_request && $context === 'edit';
577 $is_elementor = $post_action === 'elementor_ajax' || $get_action === 'elementor';
578 $is_divi = wpforms_is_divi_editor();
579 // phpcs:enable WordPress.Security.NonceVerification
580
581 return $is_gutenberg || $is_elementor || $is_divi;
582 }
583
584 /**
585 * Determines whether the current context is the Divi editor.
586 *
587 * @since 1.9.4
588 *
589 * @return bool
590 */
591 function wpforms_is_divi_editor(): bool {
592
593 // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended
594 return ! empty( $_GET['et_fb'] ) || ( isset( $_POST['action'] ) && sanitize_key( $_POST['action'] ) === 'wpforms_divi_preview' );
595 }
596