PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / trunk
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More vtrunk
6.35 6.34 6.33.1 6.33 6.32.1 6.32 6.31 6.25 6.25.1 6.26 6.26.1 6.27 6.28 6.29 6.3 6.3.1 6.3.2 6.30 6.4 6.4.1 6.4.2 6.5 6.5.1 6.5.2 6.5.3 All 141 releases
← All changes | classes/helpers/FrmAppHelper.php +1979 -698 6.4.1 → trunk View file →
@@ -3,27 +3,43 @@
3 3 die( 'You are not allowed to call this page directly.' );
4 4 }
5 5
6 6 class FrmAppHelper {
7 - public static $db_version = 98; // Version of the database we are moving to.
8 - public static $pro_db_version = 37; //deprecated
9 - public static $font_version = 7;
10 7
11 8 /**
12 - * @var bool $added_gmt_offset_filter
9 + * Version of the database we are moving to.
10 + *
11 + * @var int
13 12 */
13 + public static $db_version = 106;
14 +
15 + /**
16 + * Used by the API add-on.
17 + *
18 + * @var float
19 + */
20 + public static $font_version = 7;
21 +
22 + /**
23 + * @var bool
24 + */
14 25 private static $added_gmt_offset_filter = false;
15 26
16 27 /**
17 28 * @since 2.0
29 + *
30 + * @var string
18 31 */
19 - public static $plug_version = '6.4.1';
32 + public static $plug_version = '6.35';
20 33
21 34 /**
35 + * @var bool
36 + */
37 + private static $included_svg = false;
38 +
39 + /**
22 40 * @since 1.07.02
23 41 *
24 - * @param none
25 - *
26 42 * @return string The version of this plugin
27 43 */
28 44 public static function plugin_version() {
29 45 return self::$plug_version;
@@ -28,21 +44,33 @@
28 44 public static function plugin_version() {
29 45 return self::$plug_version;
30 46 }
31 47
48 + /**
49 + * @return string
50 + */
32 51 public static function plugin_folder() {
33 52 return basename( self::plugin_path() );
34 53 }
35 54
55 + /**
56 + * @return string
57 + */
36 58 public static function plugin_path() {
37 - return dirname( dirname( dirname( __FILE__ ) ) );
59 + return dirname( __DIR__, 2 );
38 60 }
39 61
62 + /**
63 + * @return string
64 + */
40 65 public static function plugin_url() {
41 - // Prevously FRM_URL constant.
66 + // Previously FRM_URL constant.
42 67 return plugins_url( '', self::plugin_path() . '/formidable.php' );
43 68 }
44 69
70 + /**
71 + * @return string
72 + */
45 73 public static function relative_plugin_url() {
46 74 return str_replace( array( 'https:', 'http:' ), '', self::plugin_url() );
47 75 }
48 76
@@ -57,8 +85,9 @@
57 85 * Get the name of this site
58 86 * Used for [sitename] shortcode
59 87 *
60 88 * @since 2.0
89 + *
61 90 * @return string
62 91 */
63 92 public static function site_name() {
64 93 return get_option( 'blogname' );
@@ -63,11 +92,17 @@
63 92 public static function site_name() {
64 93 return get_option( 'blogname' );
65 94 }
66 95
96 + /**
97 + * @param string $url
98 + *
99 + * @return string
100 + */
67 101 public static function make_affiliate_url( $url ) {
68 102 $affiliate_id = self::get_affiliate();
69 - if ( ! empty( $affiliate_id ) ) {
103 +
104 + if ( $affiliate_id ) {
70 105 $url = str_replace( array( 'http://', 'https://' ), '', $url );
71 106 $url = 'http://www.shareasale.com/r.cfm?u=' . absint( $affiliate_id ) . '&b=841990&m=64739&afftrack=plugin&urllink=' . urlencode( $url );
72 107 }
73 108
@@ -73,8 +108,11 @@
73 108
74 109 return $url;
75 110 }
76 111
112 + /**
113 + * @return int
114 + */
77 115 public static function get_affiliate() {
78 116 return absint( apply_filters( 'frm_affiliate_id', 0 ) );
79 117 }
80 118
@@ -79,65 +117,167 @@
79 117 }
80 118
81 119 /**
82 120 * @since 3.04.02
83 - * @param array|string $args
121 + *
122 + * @param array|string $args If a string is passed, it is used for the utm_campaign attribute.
84 123 * @param string $page
85 124 */
86 125 public static function admin_upgrade_link( $args, $page = '' ) {
87 - if ( empty( $page ) ) {
88 - $page = 'https://formidableforms.com/lite-upgrade/';
89 - } else {
126 + if ( $page ) {
90 127 $page = str_replace( 'https://formidableforms.com/', '', $page );
91 128 $page = 'https://formidableforms.com/' . $page;
129 + } else {
130 + $page = 'https://formidableforms.com/lite-upgrade/';
92 131 }
93 132
94 - $anchor = '';
95 - if ( is_array( $args ) ) {
96 - $medium = $args['medium'];
97 - if ( isset( $args['content'] ) ) {
98 - $content = $args['content'];
99 - }
100 - if ( isset( $args['anchor'] ) ) {
101 - $anchor = '#' . $args['anchor'];
102 - }
103 - } else {
104 - $medium = $args;
105 - }
133 + $args = is_array( $args ) ? self::adjust_legacy_utm_args( $args ) : array( 'campaign' => $args );
106 134
107 135 $query_args = array(
108 - 'utm_source' => 'WordPress',
109 - 'utm_medium' => $medium,
110 - 'utm_campaign' => 'liteplugin',
136 + 'utm_source' => 'plugin',
137 + 'utm_medium' => self::get_utm_medium(),
111 138 );
139 + $query_args = self::maybe_add_utm_license( $query_args );
112 140
113 - if ( isset( $content ) ) {
114 - $query_args['utm_content'] = $content;
141 + if ( isset( $args['campaign'] ) ) {
142 + $query_args['utm_campaign'] = $args['campaign'];
115 143 }
116 144
117 - if ( is_array( $args ) && isset( $args['param'] ) ) {
145 + if ( isset( $args['content'] ) ) {
146 + $query_args['utm_content'] = $args['content'];
147 + }
148 +
149 + if ( isset( $args['param'] ) ) {
118 150 $query_args['f'] = $args['param'];
119 151 }
120 152
121 - if ( is_array( $args ) && ! empty( $args['plan'] ) ) {
153 + if ( ! empty( $args['plan'] ) ) {
122 154 $query_args['plan'] = $args['plan'];
123 155 }
124 156
125 - $link = add_query_arg( $query_args, $page ) . $anchor;
157 + $link = add_query_arg( $query_args, $page );
158 +
159 + if ( isset( $args['anchor'] ) ) {
160 + $link .= '#' . $args['anchor'];
161 + }
162 +
126 163 return self::make_affiliate_url( $link );
127 164 }
128 165
129 166 /**
167 + * If medium is "pro", add an additional utm_license param with their active license type.
168 + *
169 + * @since 6.25.1
170 + *
171 + * @param array $query_args
172 + * @param string $link
173 + *
174 + * @return array
175 + */
176 + private static function maybe_add_utm_license( $query_args, $link = '' ) {
177 + $medium = $query_args['utm_medium'] ?? self::pull_medium_from_link( $link );
178 +
179 + if ( 'pro' === $medium && is_callable( 'FrmProAddonsController::get_readable_license_type' ) ) {
180 + $query_args['utm_license'] = strtolower( FrmProAddonsController::get_readable_license_type() );
181 + }
182 +
183 + return $query_args;
184 + }
185 +
186 + /**
187 + * @since 6.26
188 + *
189 + * @param string $link
190 + *
191 + * @return string
192 + */
193 + private static function pull_medium_from_link( $link ) {
194 + if ( ! $link ) {
195 + return '';
196 + }
197 +
198 + $parsed = parse_url( $link );
199 +
200 + if ( ! is_array( $parsed ) || ! isset( $parsed['query'] ) ) {
201 + return '';
202 + }
203 +
204 + $query_args = wp_parse_args( $parsed['query'] );
205 +
206 + return ! empty( $query_args['utm_medium'] ) ? $query_args['utm_medium'] : '';
207 + }
208 +
209 + /**
210 + * @since 6.25.1
211 + *
212 + * @return string
213 + */
214 + private static function get_utm_medium() {
215 + return self::pro_is_connected() ? 'pro' : 'lite';
216 + }
217 +
218 + /**
219 + * Change campaign from "liteplugin" to what we're currently using for medium.
220 + *
221 + * @since 6.25.1
222 + *
223 + * @param array $args
224 + *
225 + * @return array
226 + */
227 + private static function adjust_legacy_utm_args( $args ) {
228 + if ( isset( $args['medium'] ) ) {
229 + $args['campaign'] = $args['medium'];
230 + unset( $args['medium'] );
231 + }
232 +
233 + return $args;
234 + }
235 +
236 + /**
237 + * @since 6.21
238 + *
239 + * @param string $cta_link
240 + * @param array $utm
241 + */
242 + public static function maybe_add_missing_utm( $cta_link, $utm ) {
243 + $utm = self::adjust_legacy_utm_args( $utm );
244 + $query_args = array();
245 +
246 + if ( ! str_contains( $cta_link, 'utm_source' ) ) {
247 + $query_args['utm_source'] = 'plugin';
248 + }
249 +
250 + if ( ! str_contains( $cta_link, 'utm_medium' ) ) {
251 + $query_args['utm_medium'] = self::get_utm_medium();
252 + }
253 +
254 + if ( ! str_contains( $cta_link, 'utm_campaign' ) && isset( $utm['campaign'] ) ) {
255 + $query_args['utm_campaign'] = $utm['campaign'];
256 + }
257 +
258 + if ( ! str_contains( $cta_link, 'utm_content' ) && isset( $utm['content'] ) ) {
259 + $query_args['utm_content'] = $utm['content'];
260 + }
261 +
262 + $query_args = self::maybe_add_utm_license( $query_args, $cta_link );
263 +
264 + return $query_args ? add_query_arg( $query_args, $cta_link ) : $cta_link;
265 + }
266 +
267 + /**
130 268 * Get the Formidable settings
131 269 *
132 270 * @since 2.0
133 271 *
134 272 * @param array $args - May include the form id when values need translation.
135 - * @return FrmSettings $frm_setings
273 + *
274 + * @return FrmSettings
136 275 */
137 276 public static function get_settings( $args = array() ) {
138 277 global $frm_settings;
139 - if ( empty( $frm_settings ) ) {
278 +
279 + if ( ! $frm_settings ) {
140 280 $frm_settings = new FrmSettings( $args );
141 281 } elseif ( isset( $args['current_form'] ) ) {
142 282 // If the global has already been set, allow strings to be filtered.
143 283 $frm_settings->maybe_filter_for_form( $args );
@@ -145,16 +285,41 @@
145 285
146 286 return $frm_settings;
147 287 }
148 288
289 + /**
290 + * @return string
291 + */
149 292 public static function get_menu_name() {
150 - $frm_settings = self::get_settings();
293 + if ( ! self::pro_is_installed() || FrmAddonsController::is_license_expired() ) {
294 + return 'Formidable';
295 + }
151 296
152 - return $frm_settings->menu;
297 + return self::get_settings()->menu;
153 298 }
154 299
155 300 /**
301 + * Determine if the current branding is set to 'formidable'.
302 + * Checks the menu icon, and verifies if it matches the formidable branding.
303 + *
304 + * @since 6.4.2
305 + *
306 + * @return bool True if the menu icon is the logo, false otherwise.
307 + */
308 + public static function is_formidable_branding() {
309 + if ( ! self::pro_is_installed() ) {
310 + return true;
311 + }
312 +
313 + return str_contains( self::get_menu_icon_class(), 'frm_logo_icon' );
314 + }
315 +
316 + /**
156 317 * @since 3.05
318 + *
319 + * @param array $atts
320 + *
321 + * @return string
157 322 */
158 323 public static function svg_logo( $atts = array() ) {
159 324 $defaults = array(
160 325 'height' => 18,
@@ -163,23 +328,31 @@
163 328 'orange' => '#f05a24',
164 329 );
165 330 $atts = array_merge( $defaults, $atts );
166 331
332 + // phpcs:disable SlevomatCodingStandard.Files.LineLength.LineTooLong
167 333 return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 599.68 601.37" width="' . esc_attr( $atts['width'] ) . '" height="' . esc_attr( $atts['height'] ) . '">
168 334 <path fill="' . esc_attr( $atts['orange'] ) . '" d="M289.6 384h140v76h-140z"/>
169 335 <path fill="' . esc_attr( $atts['fill'] ) . '" d="M400.2 147h-200c-17 0-30.6 12.2-30.6 29.3V218h260v-71zM397.9 264H169.6v196h75V340H398a32.2 32.2 0 0 0 30.1-21.4 24.3 24.3 0 0 0 1.7-8.7V264zM299.8 601.4A300.3 300.3 0 0 1 0 300.7a299.8 299.8 0 1 1 511.9 212.6 297.4 297.4 0 0 1-212 88zm0-563A262 262 0 0 0 38.3 300.7a261.6 261.6 0 1 0 446.5-185.5 259.5 259.5 0 0 0-185-76.8z"/>
170 336 </svg>';
337 + // phpcs:enable SlevomatCodingStandard.Files.LineLength.LineTooLong
171 338 }
172 339
173 340 /**
174 341 * @since 4.0
342 + *
343 + * @param array $atts
344 + *
345 + * @return void
175 346 */
176 347 public static function show_logo( $atts = array() ) {
177 - echo self::kses( self::svg_logo( $atts ), 'all' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
348 + self::kses_echo( self::svg_logo( $atts ), 'all' );
178 349 }
179 350
180 351 /**
181 352 * @since 4.03.02
353 + *
354 + * @return void
182 355 */
183 356 public static function show_header_logo() {
184 357 $icon = self::svg_logo(
185 358 array(
@@ -188,10 +361,11 @@
188 361 )
189 362 );
190 363
191 364 $new_icon = apply_filters( 'frm_icon', $icon, true );
365 +
192 366 if ( $new_icon !== $icon ) {
193 - if ( strpos( $new_icon, '<svg' ) === 0 ) {
367 + if ( str_starts_with( $new_icon, '<svg' ) ) {
194 368 $icon = str_replace( 'viewBox="0 0 20', 'width="30" height="35" style="color:#929699" viewBox="0 0 20', $new_icon );
195 369 } else {
196 370 // Show nothing if it isn't an SVG.
197 371 $icon = '<div style="height:39px"></div>';
@@ -196,26 +370,43 @@
196 370 // Show nothing if it isn't an SVG.
197 371 $icon = '<div style="height:39px"></div>';
198 372 }
199 373 }
200 - echo self::kses( $icon, 'all' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
374 + self::kses_echo( $icon, 'all' );
201 375 }
202 376
203 377 /**
204 378 * @since 2.02.04
379 + *
380 + * @return bool
205 381 */
206 382 public static function ips_saved() {
207 383 $frm_settings = self::get_settings();
208 -
209 384 return ! $frm_settings->no_ips;
210 385 }
211 386
387 + /**
388 + * @return bool
389 + */
212 390 public static function pro_is_installed() {
213 - return apply_filters( 'frm_pro_installed', false );
391 + return (bool) apply_filters( 'frm_pro_installed', false );
214 392 }
215 393
216 394 /**
395 + * Check if the Pro plugin is installed, whether authorized or not.
396 + *
397 + * @since 6.8.3
398 + *
399 + * @return bool
400 + */
401 + public static function pro_is_included() {
402 + return function_exists( 'load_formidable_pro' );
403 + }
404 +
405 + /**
217 406 * @since 4.06.02
407 + *
408 + * @return bool
218 409 */
219 410 public static function pro_is_connected() {
220 411 global $frm_vars;
221 412 return self::pro_is_installed() && $frm_vars['pro_is_authorized'];
@@ -222,39 +413,94 @@
222 413 }
223 414
224 415 /**
225 416 * @since 4.06
417 + * @since 6.16.2 Added $check_for_settings parameter
418 + *
419 + * @param bool $check_for_settings
420 + *
421 + * @return bool
226 422 */
227 - public static function is_form_builder_page() {
228 - $action = self::simple_get( 'frm_action', 'sanitize_title' );
229 - return self::is_admin_page( 'formidable' ) && ( $action === 'edit' || $action === 'settings' || $action === 'duplicate' );
423 + public static function is_form_builder_page( $check_for_settings = true ) {
424 + $action = self::simple_get( 'frm_action', 'sanitize_title' );
425 + $check_actions = array( 'edit', 'duplicate' );
426 +
427 + if ( $check_for_settings ) {
428 + $check_actions[] = 'settings';
429 + }
430 +
431 + return self::is_admin_page( 'formidable' ) && in_array( $action, $check_actions, true );
230 432 }
231 433
434 + /**
435 + * @return bool
436 + */
232 437 public static function is_formidable_admin() {
233 - $page = self::simple_get( 'page', 'sanitize_title' );
234 - $is_formidable = strpos( $page, 'formidable' ) !== false;
235 - if ( empty( $page ) ) {
236 - $is_formidable = self::is_view_builder_page();
438 + $page = self::simple_get( 'page', 'sanitize_title' );
439 +
440 + if ( ! $page ) {
441 + return self::is_view_builder_page();
237 442 }
238 443
239 - return $is_formidable;
444 + return str_contains( $page, 'formidable' );
240 445 }
241 446
242 447 /**
448 + * Checks if is a list page.
449 + *
450 + * @since 6.19
451 + *
452 + * @param string $page The name of the page to check.
453 + *
454 + * @return bool
455 + */
456 + public static function is_admin_list_page( $page = 'formidable' ) {
457 + if ( 'formidable' === $page ) {
458 + return self::on_form_listing_page();
459 + }
460 +
461 + if ( ! self::is_admin_page( $page ) ) {
462 + return false;
463 + }
464 +
465 + if ( 'formidable-entries' === $page ) {
466 + $action = self::simple_get( 'frm_action' );
467 +
468 + if ( ! $action || in_array( $action, self::get_entries_listing_page_form_actions(), true ) ) {
469 + return true;
470 + }
471 + }
472 +
473 + // Check edit or settings page.
474 + return ! self::simple_get( 'frm_action' );
475 + }
476 +
477 + /**
478 + * @since 6.20
479 + *
480 + * @return array<string>
481 + */
482 + private static function get_entries_listing_page_form_actions() {
483 + return array( 'list', 'destroy' );
484 + }
485 +
486 + /**
243 487 * Check for certain page in Formidable settings
244 488 *
245 489 * @since 2.0
246 490 *
247 - * @param string $page The name of the page to check
491 + * @param string $page The name of the page to check.
248 492 *
249 - * @return boolean
493 + * @return bool
250 494 */
251 495 public static function is_admin_page( $page = 'formidable' ) {
252 496 global $pagenow;
253 497 $get_page = self::simple_get( 'page', 'sanitize_title' );
498 +
254 499 if ( $pagenow ) {
255 - // allow this to be true during ajax load i.e. ajax form builder loading
500 + // Allow this to be true during ajax load i.e. ajax form builder loading
256 501 $is_page = ( $pagenow === 'admin.php' || $pagenow === 'admin-ajax.php' ) && $get_page === $page;
502 +
257 503 if ( $is_page ) {
258 504 return true;
259 505 }
260 506 }
@@ -266,21 +512,23 @@
266 512 * If the current page is for editing or creating a view.
267 513 * Returns false for the views listing page.
268 514 *
269 515 * @since 4.0
516 + *
517 + * @return bool
270 518 */
271 519 public static function is_view_builder_page() {
272 520 global $pagenow;
273 521
274 - if ( $pagenow !== 'post.php' && $pagenow !== 'post-new.php' && $pagenow !== 'edit.php' ) {
522 + if ( ! in_array( $pagenow, array( 'post.php', 'post-new.php', 'edit.php' ), true ) ) {
275 523 return false;
276 524 }
277 525
278 526 $post_type = self::simple_get( 'post_type', 'sanitize_title' );
279 527
280 - if ( empty( $post_type ) ) {
281 - $post_id = self::simple_get( 'post', 'absint' );
282 - $post = get_post( $post_id );
528 + if ( ! $post_type ) {
529 + $post_id = self::simple_get( 'post', 'absint' );
530 + $post = get_post( $post_id );
283 531 $post_type = $post ? $post->post_type : '';
284 532 }
285 533
286 534 return $post_type === 'frm_display';
@@ -290,17 +538,15 @@
290 538 * Check for the form preview page
291 539 *
292 540 * @since 2.0
293 541 *
294 - * @param None
295 - *
296 - * @return boolean
542 + * @return bool
297 543 */
298 544 public static function is_preview_page() {
299 545 global $pagenow;
300 546 $action = self::simple_get( 'action', 'sanitize_title' );
301 547
302 - return $pagenow && $pagenow == 'admin-ajax.php' && $action == 'frm_forms_preview';
548 + return $pagenow === 'admin-ajax.php' && $action === 'frm_forms_preview';
303 549 }
304 550
305 551 /**
306 552 * Check for ajax except the form preview page
@@ -306,16 +552,17 @@
306 552 * Check for ajax except the form preview page
307 553 *
308 554 * @since 2.0
309 555 *
310 - * @param None
311 - *
312 - * @return boolean
556 + * @return bool
313 557 */
314 558 public static function doing_ajax() {
315 559 return wp_doing_ajax() && ! self::is_preview_page();
316 560 }
317 561
562 + /**
563 + * @return string
564 + */
318 565 public static function js_suffix() {
319 566 return defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
320 567 }
321 568
@@ -320,13 +567,14 @@
320 567 }
321 568
322 569 /**
323 570 * @since 2.0.8
571 + *
572 + * @return bool
324 573 */
325 574 public static function prevent_caching() {
326 575 global $frm_vars;
327 -
328 - return isset( $frm_vars['prevent_caching'] ) && $frm_vars['prevent_caching'];
576 + return ! empty( $frm_vars['prevent_caching'] );
329 577 }
330 578
331 579 /**
332 580 * Check if on an admin page
@@ -332,9 +580,9 @@
332 580 * Check if on an admin page
333 581 *
334 582 * @since 2.0
335 583 *
336 - * @return boolean
584 + * @return bool
337 585 */
338 586 public static function is_admin() {
339 587 $is_admin = is_admin() && ! wp_doing_ajax();
340 588
@@ -339,8 +587,9 @@
339 587 $is_admin = is_admin() && ! wp_doing_ajax();
340 588
341 589 /**
342 590 * @since 6.0
591 + *
343 592 * @param bool $is_admin
344 593 */
345 594 return apply_filters( 'frm_is_admin', $is_admin );
346 595 }
@@ -349,17 +598,23 @@
349 598 * Check if value contains blank value or empty array
350 599 *
351 600 * @since 2.0
352 601 *
353 - * @param mixed $value - value to check
354 - * @param string
602 + * @param mixed $value Value to check.
603 + * @param string $empty
355 604 *
356 - * @return boolean
605 + * @return bool
357 606 */
358 607 public static function is_empty_value( $value, $empty = '' ) {
359 - return ( is_array( $value ) && empty( $value ) ) || $value === $empty;
608 + return $value === array() || $value === $empty;
360 609 }
361 610
611 + /**
612 + * @param mixed $value
613 + * @param string $empty
614 + *
615 + * @return bool
616 + */
362 617 public static function is_not_empty_value( $value, $empty = '' ) {
363 618 return ! self::is_empty_value( $value, $empty );
364 619 }
365 620
@@ -376,8 +631,28 @@
376 631 return isset( $_SERVER[ $value ] ) ? wp_strip_all_tags( wp_unslash( $_SERVER[ $value ] ) ) : '';
377 632 }
378 633
379 634 /**
635 + * Get the server OS
636 + *
637 + * @since 6.4.2
638 + *
639 + * @return string
640 + */
641 + public static function get_server_os() {
642 + if ( function_exists( 'php_uname' ) ) {
643 + return php_uname( 's' );
644 + }
645 +
646 + if ( ! defined( 'PHP_OS' ) ) {
647 + return '';
648 + }
649 +
650 + // match the same response for Windows server as php_uname('s')
651 + return in_array( PHP_OS, array( 'WIN32', 'WINNT', 'Windows_NT' ), true ) ? 'Windows NT' : PHP_OS;
652 + }
653 +
654 + /**
380 655 * Check for the IP address in several places (when custom headers are enabled).
381 656 * Used by [ip] shortcode.
382 657 *
383 658 * @return string The IP address of the current user
@@ -391,10 +666,12 @@
391 666 continue;
392 667 }
393 668
394 669 $key = self::get_server_value( $key );
670 +
395 671 foreach ( explode( ',', $key ) as $ip ) {
396 - $ip = trim( $ip ); // Just to be safe.
672 + // Just to be safe.
673 + $ip = trim( $ip );
397 674
398 675 if ( filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) !== false ) {
399 676 return sanitize_text_field( $ip );
400 677 }
@@ -447,16 +724,26 @@
447 724 */
448 725 return apply_filters( 'frm_use_custom_header_ip', $should_use_custom_header_ip );
449 726 }
450 727
728 + /**
729 + * @param string $param
730 + * @param mixed $default
731 + * @param string $src
732 + * @param callable|string $sanitize
733 + *
734 + * @return mixed
735 + */
451 736 public static function get_param( $param, $default = '', $src = 'get', $sanitize = '' ) {
452 - if ( strpos( $param, '[' ) ) {
737 + if ( str_contains( $param, '[' ) ) {
453 738 $params = explode( '[', $param );
454 739 $param = $params[0];
455 740 }
456 741
457 742 if ( $src === 'get' ) {
458 - $value = isset( $_POST[ $param ] ) ? wp_unslash( $_POST[ $param ] ) : ( isset( $_GET[ $param ] ) ? wp_unslash( $_GET[ $param ] ) : $default ); // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
743 + // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
744 + $value = isset( $_POST[ $param ] ) ? wp_unslash( $_POST[ $param ] ) : ( isset( $_GET[ $param ] ) ? wp_unslash( $_GET[ $param ] ) : $default );
745 +
459 746 if ( ! isset( $_POST[ $param ] ) && isset( $_GET[ $param ] ) && ! is_array( $value ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
460 747 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
461 748 $value = htmlspecialchars_decode( wp_unslash( $_GET[ $param ] ) );
462 749 }
@@ -471,29 +758,41 @@
471 758 )
472 759 );
473 760 }
474 761
475 - if ( isset( $params ) && is_array( $value ) && ! empty( $value ) ) {
476 - foreach ( $params as $k => $p ) {
477 - if ( ! $k || ! is_array( $value ) ) {
478 - continue;
479 - }
762 + if ( ! isset( $params ) || ! is_array( $value ) || ! $value ) {
763 + return $value;
764 + }
480 765
481 - $p = trim( $p, ']' );
482 - $value = isset( $value[ $p ] ) ? $value[ $p ] : $default;
766 + foreach ( $params as $k => $p ) {
767 + if ( ! $k || ! is_array( $value ) ) {
768 + continue;
483 769 }
770 +
771 + $p = trim( $p, ']' );
772 + $value = $value[ $p ] ?? $default;
484 773 }
485 774
486 775 return $value;
487 776 }
488 777
778 + /**
779 + * Get a value from $_POST data.
780 + *
781 + * @param string $param The key we are trying to access data from in $_POST.
782 + * @param mixed $default The default if nothing is being sent.
783 + * @param callable|string $sanitize Make sure to pass a sanitize method here. This function will NOT sanitize by default.
784 + * @param bool $serialized
785 + *
786 + * @return mixed
787 + */
489 788 public static function get_post_param( $param, $default = '', $sanitize = '', $serialized = false ) {
490 789 return self::get_simple_request(
491 790 array(
492 - 'type' => 'post',
493 - 'param' => $param,
494 - 'default' => $default,
495 - 'sanitize' => $sanitize,
791 + 'type' => 'post',
792 + 'param' => $param,
793 + 'default' => $default,
794 + 'sanitize' => $sanitize,
496 795 'serialized' => $serialized,
497 796 )
498 797 );
499 798 }
@@ -504,9 +803,9 @@
504 803 * @param string $param
505 804 * @param string $sanitize
506 805 * @param string $default
507 806 *
508 - * @return string|array
807 + * @return array|int|string
509 808 */
510 809 public static function simple_get( $param, $sanitize = 'sanitize_text_field', $default = '' ) {
511 810 return self::get_simple_request(
512 811 array(
@@ -524,39 +823,38 @@
524 823 * @since 2.0.6
525 824 *
526 825 * @param array $args
527 826 *
528 - * @return string|array
827 + * @return array|string
529 828 */
530 829 public static function get_simple_request( $args ) {
531 830 $defaults = array(
532 - 'param' => '',
533 - 'default' => '',
534 - 'type' => 'get',
535 - 'sanitize' => 'sanitize_text_field',
831 + 'param' => '',
832 + 'default' => '',
833 + 'type' => 'get',
834 + 'sanitize' => 'sanitize_text_field',
536 835 'serialized' => false,
537 836 );
538 837 $args = wp_parse_args( $args, $defaults );
838 + $value = $args['default'];
539 839
540 - $value = $args['default'];
541 - if ( $args['type'] == 'get' ) {
840 + if ( $args['type'] === 'get' ) {
542 841 if ( $_GET && isset( $_GET[ $args['param'] ] ) ) {
543 842 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Missing
544 843 $value = wp_unslash( $_GET[ $args['param'] ] );
545 844 }
546 - } elseif ( $args['type'] == 'post' ) {
845 + } elseif ( $args['type'] === 'post' ) {
547 846 if ( isset( $_POST[ $args['param'] ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
548 847 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Missing
549 848 $value = wp_unslash( $_POST[ $args['param'] ] );
849 +
550 850 if ( $args['serialized'] === true && is_serialized_string( $value ) && is_serialized( $value ) ) {
551 851 self::unserialize_or_decode( $value );
552 852 }
553 853 }
554 - } else {
555 - if ( isset( $_REQUEST[ $args['param'] ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
556 - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
557 - $value = wp_unslash( $_REQUEST[ $args['param'] ] );
558 - }
854 + } elseif ( isset( $_REQUEST[ $args['param'] ] ) ) {
855 + // phpcs:ignore phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
856 + $value = wp_unslash( $_REQUEST[ $args['param'] ] );
559 857 }
560 858
561 859 self::sanitize_value( $args['sanitize'], $value );
562 860
@@ -569,34 +867,56 @@
569 867 * @since 2.0.8
570 868 *
571 869 * @param string $value
572 870 *
573 - * @return string $value
871 + * @return string Value.
574 872 */
575 873 public static function preserve_backslashes( $value ) {
576 874 // If backslashes have already been added, don't add them again
577 - if ( strpos( $value, '\\\\' ) === false ) {
578 - $value = addslashes( $value );
875 + return str_contains( $value, '\\\\' ) ? $value : addslashes( $value );
876 + }
877 +
878 + /**
879 + * Sanitize a value in-place.
880 + * If $value is an array, the sanitize function will get called for each item.
881 + *
882 + * @param callable $sanitize
883 + * @param mixed $value
884 + *
885 + * @return void
886 + */
887 + public static function sanitize_value( $sanitize, &$value ) {
888 + if ( ! $sanitize ) {
889 + return;
579 890 }
580 891
581 - return $value;
582 - }
892 + if ( is_object( $value ) ) {
893 + $value = '';
894 + return;
895 + }
583 896
584 - public static function sanitize_value( $sanitize, &$value ) {
585 - if ( ! empty( $sanitize ) ) {
586 - if ( is_array( $value ) ) {
587 - $temp_values = $value;
588 - foreach ( $temp_values as $k => $v ) {
589 - self::sanitize_value( $sanitize, $value[ $k ] );
590 - }
591 - } else {
592 - $value = call_user_func( $sanitize, $value );
897 + if ( is_array( $value ) ) {
898 + $temp_values = $value;
899 +
900 + foreach ( $temp_values as $k => $v ) {
901 + self::sanitize_value( $sanitize, $value[ $k ] );
593 902 }
903 +
904 + return;
594 905 }
906 +
907 + $value = call_user_func( $sanitize, $value );
595 908 }
596 909
910 + /**
911 + * @param array $sanitize_method
912 + * @param array $values
913 + *
914 + * @return void
915 + */
597 916 public static function sanitize_request( $sanitize_method, &$values ) {
598 917 $temp_values = $values;
918 +
599 919 foreach ( $temp_values as $k => $val ) {
600 920 if ( isset( $sanitize_method[ $k ] ) ) {
601 921 $values[ $k ] = call_user_func( $sanitize_method[ $k ], $val );
602 922 }
@@ -604,23 +924,69 @@
604 924 }
605 925
606 926 /**
607 927 * @since 4.0.04
928 + *
929 + * @param mixed $value
930 + *
931 + * @return void
608 932 */
609 933 public static function sanitize_with_html( &$value ) {
610 - self::sanitize_value( 'wp_kses_post', $value );
934 + if ( current_user_can( 'frm_edit_entries' ) || current_user_can( 'administrator' ) ) {
935 + // Only strip unsafe HTML like scripts for a privileged user submitting a form.
936 + self::sanitize_value( 'wp_kses_post', $value );
937 + } else {
938 + self::sanitize_value( self::class . '::strip_most_html', $value );
939 + }
611 940 self::decode_specialchars( $value );
941 + self::sanitize_value( 'FrmHtmlSanitizer::sanitize_url_attributes', $value );
612 942 }
613 943
614 944 /**
945 + * Allow only a small set of very basic HTML for unprivileged users.
946 + *
947 + * @since 6.7.1
948 + *
949 + * @param string $value
950 + */
951 + public static function strip_most_html( $value ) {
952 + if ( '' === $value ) {
953 + return $value;
954 + }
955 +
956 + $allowed_html = array(
957 + 'b' => array(),
958 + 'br' => array(),
959 + 'strong' => array(),
960 + 'p' => array(),
961 + 'i' => array(),
962 + 'ul' => array(),
963 + 'ol' => array(),
964 + 'li' => array(),
965 + );
966 +
967 + /**
968 + * @since 6.7.1
969 + *
970 + * @param array $allowed_html
971 + */
972 + $allowed_html = apply_filters( 'frm_allowed_form_input_html', $allowed_html );
973 +
974 + return wp_kses( $value, $allowed_html );
975 + }
976 +
977 + /**
615 978 * Do wp_specialchars_decode to get back '&' that wp_kses_post might have turned to '&amp;'
616 979 * this MUST be done, else we'll be back to the '& entity' problem.
617 980 *
618 981 * @since 4.0.04
982 + *
983 + * @param mixed $value Value to decode, passed by reference.
619 984 */
620 985 public static function decode_specialchars( &$value ) {
621 986 if ( is_array( $value ) ) {
622 987 $temp_values = $value;
988 +
623 989 foreach ( $temp_values as $k => $v ) {
624 990 self::decode_specialchars( $value[ $k ] );
625 991 }
626 992 } else {
@@ -634,13 +1000,13 @@
634 1000 * Adapted from wp_specialchars_decode().
635 1001 *
636 1002 * @since 4.03.01
637 1003 *
638 - * @param string $string The string to prep.
1004 + * @param string $string The string to prep, passed by reference.
639 1005 */
640 1006 private static function decode_amp( &$string ) {
641 1007 // Don't bother if there are no entities - saves a lot of processing
642 - if ( empty( $string ) || strpos( $string, '&' ) === false ) {
1008 + if ( ! $string || ! str_contains( $string, '&' ) ) {
643 1009 return;
644 1010 }
645 1011
646 1012 $translation = array(
@@ -646,10 +1012,12 @@
646 1012 $translation = array(
647 1013 '&quot;' => '"',
648 1014 '&#034;' => '"',
649 1015 '&#x22;' => '"',
650 - '&lt; ' => '< ', // The space preserves the HTML.
651 - '&#060; ' => '< ', // The space preserves the HTML.
1016 + // The space preserves the HTML.
1017 + '&lt; ' => '< ',
1018 + // The space preserves the HTML.
1019 + '&#060; ' => '< ',
652 1020 '&gt;' => '>',
653 1021 '&#062;' => '>',
654 1022 '&amp;' => '&',
655 1023 '&#038;' => '&',
@@ -676,17 +1044,29 @@
676 1044 * Sanitize the value, and allow some HTML
677 1045 *
678 1046 * @since 2.0
679 1047 *
680 - * @param string $value
681 - * @param array|string $allowed 'all' for everything included as defaults
1048 + * @param string $value The value to sanitize.
1049 + * @param array|string $allowed Allowed HTML tags and attributes, or 'all' for defaults.
682 1050 *
683 1051 * @return string
684 1052 */
685 1053 public static function kses( $value, $allowed = array() ) {
686 - $allowed_html = self::allowed_html( $allowed );
1054 + return wp_kses( $value, self::allowed_html( $allowed ) );
1055 + }
687 1056
688 - return wp_kses( $value, $allowed_html );
1057 + /**
1058 + * Sanitizes and echoes a given value.
1059 + *
1060 + * @since 6.18
1061 + *
1062 + * @param string $value The value to sanitize and output.
1063 + * @param array|string $allowed Allowed HTML tags and attributes.
1064 + *
1065 + * @return void
1066 + */
1067 + public static function kses_echo( $value, $allowed = array() ) {
1068 + echo self::kses( $value, $allowed ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
689 1069 }
690 1070
691 1071 /**
692 1072 * The regular kses function strips [button_action] from submit button HTML.
@@ -693,21 +1073,23 @@
693 1073 *
694 1074 * @since 5.0.13
695 1075 *
696 1076 * @param string $html
1077 + *
697 1078 * @return string
698 1079 */
699 1080 public static function kses_submit_button( $html ) {
700 - $included_button_action = false !== strpos( $html, '[button_action]' );
701 - $included_back_hook = false !== strpos( $html, '[back_hook]' );
702 - $included_draft_hook = false !== strpos( $html, '[draft_hook]' );
1081 + $included_button_action = str_contains( $html, '[button_action]' );
1082 + $included_back_hook = str_contains( $html, '[back_hook]' );
1083 + $included_draft_hook = str_contains( $html, '[draft_hook]' );
703 1084 add_filter( 'safe_style_css', 'FrmAppHelper::allow_visibility_style' );
704 1085 add_filter( 'frm_striphtml_allowed_tags', 'FrmAppHelper::add_allowed_submit_button_tags' );
705 1086 $html = self::kses( $html, 'all' );
706 1087 remove_filter( 'safe_style_css', 'FrmAppHelper::allow_visibility_style' );
707 1088 remove_filter( 'frm_striphtml_allowed_tags', 'FrmAppHelper::add_allowed_submit_button_tags' );
1089 +
708 1090 if ( $included_button_action ) {
709 - if ( false !== strpos( $html, '<input type="submit"' ) ) {
1091 + if ( str_contains( $html, '<input type="submit"' ) ) {
710 1092 $pattern = '/(<input type="submit")([^>]*)(\/>)/';
711 1093 $html = preg_replace( $pattern, '$1$2[button_action] $3', $html, 1 );
712 1094 } else {
713 1095 $pattern = '/(<button)(.*)(class=")(.*)(frm_button_submit)(.*)(")(.*)([^>]+)(>)/';
@@ -713,14 +1095,17 @@
713 1095 $pattern = '/(<button)(.*)(class=")(.*)(frm_button_submit)(.*)(")(.*)([^>]+)(>)/';
714 1096 $html = preg_replace( $pattern, '$1$2$3$4$5$6$7 [button_action]$8$9$10', $html, 1 );
715 1097 }
716 1098 }
1099 +
717 1100 if ( $included_back_hook ) {
718 1101 $html = str_replace( 'class="frm_prev_page"', 'class="frm_prev_page" [back_hook]', $html );
719 1102 }
1103 +
720 1104 if ( $included_draft_hook ) {
721 - $html = str_replace( 'class="frm_save_draft"', 'class="frm_save_draft" [draft_hook]', $html );
1105 + return str_replace( 'class="frm_save_draft"', 'class="frm_save_draft" [draft_hook]', $html );
722 1106 }
1107 +
723 1108 return $html;
724 1109 }
725 1110
726 1111 /**
@@ -726,8 +1111,9 @@
726 1111 /**
727 1112 * @since 5.0.13
728 1113 *
729 1114 * @param array $allowed_attr
1115 + *
730 1116 * @return array
731 1117 */
732 1118 public static function allow_visibility_style( $allowed_attr ) {
733 1119 $allowed_attr[] = 'visibility';
@@ -737,8 +1123,9 @@
737 1123 /**
738 1124 * @since 5.0.13
739 1125 *
740 1126 * @param array $allowed_html
1127 + *
741 1128 * @return array
742 1129 */
743 1130 public static function add_allowed_submit_button_tags( $allowed_html ) {
744 1131 $allowed_html['input'] = array(
@@ -755,17 +1142,22 @@
755 1142 }
756 1143
757 1144 /**
758 1145 * @since 2.05.03
1146 + *
1147 + * @param array|string $allowed
1148 + *
1149 + * @return array
759 1150 */
760 1151 private static function allowed_html( $allowed ) {
761 1152 $html = self::safe_html();
762 1153 $allowed_html = array();
1154 +
763 1155 if ( $allowed === 'all' ) {
764 1156 $allowed_html = $html;
765 - } elseif ( ! empty( $allowed ) ) {
1157 + } elseif ( $allowed ) {
766 1158 foreach ( (array) $allowed as $a ) {
767 - $allowed_html[ $a ] = isset( $html[ $a ] ) ? $html[ $a ] : array();
1159 + $allowed_html[ $a ] = $html[ $a ] ?? array();
768 1160 }
769 1161 }
770 1162
771 1163 return apply_filters( 'frm_striphtml_allowed_tags', $allowed_html );
@@ -772,8 +1164,10 @@
772 1164 }
773 1165
774 1166 /**
775 1167 * @since 2.05.03
1168 + *
1169 + * @return array
776 1170 */
777 1171 private static function safe_html() {
778 1172 $allow_class = array(
779 1173 'class' => true,
@@ -780,9 +1174,9 @@
780 1174 'id' => true,
781 1175 );
782 1176
783 1177 return array(
784 - 'a' => array(
1178 + 'a' => array(
785 1179 'class' => true,
786 1180 'href' => true,
787 1181 'id' => true,
788 1182 'rel' => true,
@@ -851,16 +1245,16 @@
851 1245 'cite' => true,
852 1246 'title' => true,
853 1247 ),
854 1248 'rect' => array(
855 - 'class' => true,
856 - 'fill' => true,
857 - 'height' => true,
858 - 'width' => true,
859 - 'x' => true,
860 - 'y' => true,
861 - 'rx' => true,
862 - 'stroke' => true,
1249 + 'class' => true,
1250 + 'fill' => true,
1251 + 'height' => true,
1252 + 'width' => true,
1253 + 'x' => true,
1254 + 'y' => true,
1255 + 'rx' => true,
1256 + 'stroke' => true,
863 1257 'stroke-opacity' => true,
864 1258 'stroke-width' => true,
865 1259 ),
866 1260 'section' => $allow_class,
@@ -895,9 +1289,9 @@
895 1289 'xlink:href' => true,
896 1290 ),
897 1291 'ul' => $allow_class,
898 1292 'label' => array(
899 - 'for' => true,
1293 + 'for' => true,
900 1294 'class' => true,
901 1295 'id' => true,
902 1296 ),
903 1297 'button' => array(
@@ -906,8 +1300,13 @@
906 1300 ),
907 1301 'legend' => array(
908 1302 'class' => true,
909 1303 ),
1304 + 'option' => array(
1305 + 'class' => true,
1306 + 'value' => true,
1307 + 'selected' => true,
1308 + ),
910 1309 );
911 1310 }
912 1311
913 1312 /**
@@ -915,19 +1314,21 @@
915 1314 *
916 1315 * @since 2.0
917 1316 */
918 1317 public static function remove_get_action() {
919 - if ( ! isset( $_GET ) ) {
1318 + if ( empty( $_GET ) ) {
920 1319 return;
921 1320 }
922 1321
923 1322 $action_name = isset( $_GET['action'] ) ? 'action' : ( isset( $_GET['action2'] ) ? 'action2' : '' );
924 - if ( empty( $action_name ) ) {
1323 +
1324 + if ( ! $action_name ) {
925 1325 return;
926 1326 }
927 1327
928 1328 $new_action = self::get_param( $action_name, '', 'get', 'sanitize_text_field' );
929 - if ( ! empty( $new_action ) ) {
1329 +
1330 + if ( $new_action ) {
930 1331 $_SERVER['REQUEST_URI'] = str_replace( '&action=' . $new_action, '', self::get_server_value( 'REQUEST_URI' ) );
931 1332 }
932 1333 }
933 1334
@@ -934,21 +1335,23 @@
934 1335 /**
935 1336 * Check the WP query for a parameter
936 1337 *
937 1338 * @since 2.0
938 - * @return string|array
1339 + *
1340 + * @param array|string $value
1341 + * @param string $param
1342 + *
1343 + * @return array|string
939 1344 */
940 1345 public static function get_query_var( $value, $param ) {
1346 + // phpcs:ignore Universal.Operators.StrictComparisons
941 1347 if ( $value != '' ) {
942 1348 return $value;
943 1349 }
944 1350
945 1351 global $wp_query;
946 - if ( isset( $wp_query->query_vars[ $param ] ) ) {
947 - $value = $wp_query->query_vars[ $param ];
948 - }
949 1352
950 - return $value;
1353 + return $wp_query->query_vars[ $param ] ?? $value;
951 1354 }
952 1355
953 1356 /**
954 1357 * Try to show the SVG if possible. Otherwise, use the font icon.
@@ -953,48 +1356,82 @@
953 1356 /**
954 1357 * Try to show the SVG if possible. Otherwise, use the font icon.
955 1358 *
956 1359 * @since 4.0.02
1360 + *
957 1361 * @param string $class
958 1362 * @param array $atts
1363 + *
1364 + * @return string|null
959 1365 */
960 1366 public static function icon_by_class( $class, $atts = array() ) {
961 1367 $echo = ! isset( $atts['echo'] ) || $atts['echo'];
1368 +
962 1369 if ( isset( $atts['echo'] ) ) {
963 1370 unset( $atts['echo'] );
964 1371 }
965 1372
966 - $html_atts = self::array_to_html_params( $atts );
967 -
968 1373 $icon = trim( str_replace( array( 'frm_icon_font', 'frmfont ' ), '', $class ) );
969 1374
970 - // Replace icons that have been removed.
1375 + // Replace icons that have been removed or renamed.
971 1376 $deprecated = array(
972 - 'frm_clone_solid_icon' => 'frm_clone_icon',
1377 + 'frm_clone_solid_icon' => 'frm_clone_icon',
1378 + 'frm_keyalt_icon' => 'frm_key_icon',
1379 + 'frm_keyalt_solid_icon' => 'frm_key_solid_icon',
973 1380 );
1381 +
974 1382 if ( isset( $deprecated[ $icon ] ) ) {
975 - $icon = $deprecated[ $icon ];
1383 + $icon = $deprecated[ $icon ];
976 1384 $class = str_replace( $icon, $deprecated[ $icon ], $class );
977 1385 }
978 1386
979 - if ( $icon === $class ) {
980 - $icon = '<i class="' . esc_attr( $class ) . '"' . $html_atts . '></i>';
981 - } else {
982 - $class = strpos( $icon, ' ' ) === false ? '' : ' ' . $icon;
983 - if ( strpos( $icon, ' ' ) ) {
1387 + $is_font_icon = $icon === $class;
1388 +
1389 + if ( ! $is_font_icon ) {
1390 + $class = str_contains( $icon, ' ' ) ? ' ' . $icon : '';
1391 +
1392 + if ( str_contains( $icon, ' ' ) ) {
984 1393 $icon = explode( ' ', $icon );
985 1394 $icon = reset( $icon );
986 1395 }
987 - $icon = '<svg class="frmsvg' . esc_attr( $class ) . '"' . $html_atts . '>
988 - <use xlink:href="#' . esc_attr( $icon ) . '" />
989 - </svg>';
990 1396 }
991 1397
992 - if ( $echo ) {
993 - echo self::kses_icon( $icon ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
994 - } else {
995 - return $icon;
1398 + if ( $atts ) {
1399 + // A caller passed attributes, so kses still has to decide which of them survive. Its
1400 + // allowlist comes from safe_html() through the frm_striphtml_allowed_tags filter, and
1401 + // add-ons widen it around their own icons, so there is no fixed list to check against.
1402 + $html_atts = self::array_to_html_params( $atts );
1403 + $markup = $is_font_icon
1404 + ? '<i class="' . esc_attr( $class ) . '"' . $html_atts . '></i>'
1405 + : '<svg class="frmsvg' . esc_attr( $class ) . '"' . $html_atts . '><use href="#' . esc_attr( $icon ) . '" /></svg>';
1406 +
1407 + if ( ! $echo ) {
1408 + return $markup;
1409 + }
1410 +
1411 + echo self::kses_icon( $markup ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1412 + return null;
996 1413 }
1414 +
1415 + /**
1416 + * With no attributes from the caller, the tag is nothing but this method's own markup
1417 + * around an escaped class and icon id, so there is nothing left for kses to decide and it
1418 + * can be skipped. Echoing the pieces rather than a finished string keeps that safe to the
1419 + * escaping sniff without an annotation.
1420 + *
1421 + * This is the path the form builder takes for most of the tens of thousands of icons it
1422 + * renders on a large form, and the kses pass was most of what each one cost.
1423 + */
1424 + $callback = function () use ( $is_font_icon, $class, $icon ) {
1425 + if ( $is_font_icon ) {
1426 + echo '<i class="' . esc_attr( $class ) . '"></i>';
1427 + return;
1428 + }
1429 +
1430 + echo '<svg class="frmsvg' . esc_attr( $class ) . '"><use href="#' . esc_attr( $icon ) . '" /></svg>';
1431 + };
1432 +
1433 + return self::clip( $callback, $echo );
997 1434 }
998 1435
999 1436 /**
1000 1437 * Run kses for icons. It needs to add a few filters first in order to preserve some custom style values.
@@ -1001,8 +1438,9 @@
1001 1438 *
1002 1439 * @since 5.0.13
1003 1440 *
1004 1441 * @param string $icon
1442 + *
1005 1443 * @return string
1006 1444 */
1007 1445 public static function kses_icon( $icon ) {
1008 1446 add_filter( 'safe_style_css', 'FrmAppHelper::allow_vars_in_styles' );
@@ -1018,13 +1456,15 @@
1018 1456 /**
1019 1457 * @since 5.0.13.1
1020 1458 *
1021 1459 * @param array $allowed_html
1460 + *
1022 1461 * @return array
1023 1462 */
1024 1463 public static function add_allowed_icon_tags( $allowed_html ) {
1025 1464 $allowed_html['svg']['data-open'] = true;
1026 1465 $allowed_html['svg']['title'] = true;
1466 + $allowed_html['svg']['tabindex'] = true;
1027 1467 return $allowed_html;
1028 1468 }
1029 1469
1030 1470 /**
@@ -1030,8 +1470,9 @@
1030 1470 /**
1031 1471 * @since 5.0.13
1032 1472 *
1033 1473 * @param array $allowed_attr
1474 + *
1034 1475 * @return array
1035 1476 */
1036 1477 public static function allow_vars_in_styles( $allowed_attr ) {
1037 1478 $allowed_attr[] = '--primary-700';
@@ -1044,9 +1485,9 @@
1044 1485 * @param bool $allow_css
1045 1486 * @param string $css_string
1046 1487 */
1047 1488 public static function allow_style( $allow_css, $css_string ) {
1048 - if ( ! $allow_css && 0 === strpos( $css_string, '--primary-700:' ) ) {
1489 + if ( ! $allow_css && str_starts_with( $css_string, '--primary-700:' ) ) {
1049 1490 $split = explode( ':', $css_string, 2 );
1050 1491 $allow_css = 2 === count( $split ) && self::is_a_valid_color( $split[1] );
1051 1492 }
1052 1493 return $allow_css;
@@ -1055,19 +1496,22 @@
1055 1496 /**
1056 1497 * @since 5.0.13
1057 1498 *
1058 1499 * @param string $value
1500 + *
1059 1501 * @return bool
1060 1502 */
1061 1503 private static function is_a_valid_color( $value ) {
1062 1504 $match = 0;
1063 - if ( 0 === strpos( $value, 'rgba(' ) ) {
1505 +
1506 + if ( str_starts_with( $value, 'rgba(' ) ) {
1064 1507 $match = preg_match( '/^rgba\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3}),\s*(\d*(?:\.\d+)?)\)$/', $value );
1065 - } elseif ( 0 === strpos( $value, 'rgb(' ) ) {
1508 + } elseif ( str_starts_with( $value, 'rgb(' ) ) {
1066 1509 $match = preg_match( '/^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/', $value );
1067 - } elseif ( 0 === strpos( $value, '#' ) ) {
1510 + } elseif ( str_starts_with( $value, '#' ) ) {
1068 1511 $match = preg_match( '/^#([a-f0-9]{6}|[a-f0-9]{3})\b$/', $value );
1069 1512 }
1513 +
1070 1514 return (bool) $match;
1071 1515 }
1072 1516
1073 1517 /**
@@ -1073,11 +1517,19 @@
1073 1517 /**
1074 1518 * Include svg images.
1075 1519 *
1076 1520 * @since 4.0.02
1521 + *
1522 + * @return void
1077 1523 */
1078 1524 public static function include_svg() {
1079 - include_once self::plugin_path() . '/images/icons.svg';
1525 + if ( self::$included_svg ) {
1526 + return;
1527 + }
1528 +
1529 + // Use readfile instead of include_once because of a default security rule in Snuffleupagus.
1530 + readfile( self::plugin_path() . '/images/icons.svg' );
1531 + self::$included_svg = true;
1080 1532 }
1081 1533
1082 1534 /**
1083 1535 * Convert an associative array to HTML values.
@@ -1086,17 +1538,20 @@
1086 1538 * @since 5.0.13 added $echo parameter.
1087 1539 *
1088 1540 * @param array $atts
1089 1541 * @param bool $echo
1542 + *
1090 1543 * @return string|void
1091 1544 */
1092 1545 public static function array_to_html_params( $atts, $echo = false ) {
1093 - $callback = function() use ( $atts ) {
1094 - if ( $atts ) {
1095 - foreach ( $atts as $key => $value ) {
1096 - echo ' ' . esc_attr( $key ) . '="' . esc_attr( $value ) . '"';
1097 - }
1546 + $callback = function () use ( $atts ) {
1547 + if ( ! $atts ) {
1548 + return;
1098 1549 }
1550 +
1551 + foreach ( $atts as $key => $value ) {
1552 + echo ' ' . esc_attr( $key ) . '="' . esc_attr( $value ) . '"';
1553 + }
1099 1554 };
1100 1555 return self::clip( $callback, $echo );
1101 1556 }
1102 1557
@@ -1106,9 +1561,12 @@
1106 1561 * @since 5.0.13
1107 1562 *
1108 1563 * @param Closure $echo_function
1109 1564 * @param bool $echo
1110 - * @return string|void
1565 + *
1566 + * @return string|null
1567 + *
1568 + * @psalm-return ($echo is true ? null : string)
1111 1569 */
1112 1570 public static function clip( $echo_function, $echo = false ) {
1113 1571 if ( ! $echo ) {
1114 1572 ob_start();
@@ -1117,28 +1575,30 @@
1117 1575 if ( is_callable( $echo_function ) ) {
1118 1576 $echo_function();
1119 1577 }
1120 1578
1121 - if ( ! $echo ) {
1122 - $return = ob_get_contents();
1123 - ob_end_clean();
1124 - return $return;
1125 - }
1579 + return $echo ? null : ob_get_clean();
1126 1580 }
1127 1581
1128 1582 /**
1129 1583 * @since 3.0
1584 + *
1585 + * @param array $atts
1586 + *
1587 + * @return void
1130 1588 */
1131 1589 public static function get_admin_header( $atts ) {
1132 - $has_nav = ( isset( $atts['form'] ) && ! empty( $atts['form'] ) && ( ! isset( $atts['is_template'] ) || ! $atts['is_template'] ) );
1133 - if ( ! isset( $atts['close'] ) || empty( $atts['close'] ) ) {
1590 + $has_nav = ! empty( $atts['form'] ) && empty( $atts['is_template'] );
1591 +
1592 + if ( empty( $atts['close'] ) ) {
1134 1593 $atts['close'] = admin_url( 'admin.php?page=formidable' );
1135 1594 }
1595 +
1136 1596 if ( ! isset( $atts['import_link'] ) ) {
1137 1597 $atts['import_link'] = false;
1138 1598 }
1139 1599
1140 - include( self::plugin_path() . '/classes/views/shared/admin-header.php' );
1600 + include self::plugin_path() . '/classes/views/shared/admin-header.php';
1141 1601 }
1142 1602
1143 1603 /**
1144 1604 * @since 6.0
@@ -1143,16 +1603,19 @@
1143 1603 /**
1144 1604 * @since 6.0
1145 1605 *
1146 1606 * @param string $type
1607 + *
1147 1608 * @return void
1148 1609 */
1149 1610 public static function import_link( $type = 'secondary' ) {
1611 + // phpcs:disable Generic.WhiteSpace.ScopeIndent
1150 1612 ?>
1151 1613 <a href="<?php echo esc_url( admin_url( 'admin.php?page=formidable-import' ) ); ?>" class="button frm-button-<?php echo esc_attr( $type ); ?> frm_animate_bg">
1152 1614 <?php esc_html_e( 'Import', 'formidable' ); ?>
1153 1615 </a>
1154 1616 <?php
1617 + // phpcs:enable Generic.WhiteSpace.ScopeIndent
1155 1618 }
1156 1619
1157 1620 /**
1158 1621 * Print applicable admin banner.
@@ -1159,8 +1622,9 @@
1159 1622 *
1160 1623 * @since 5.4.2
1161 1624 *
1162 1625 * @param bool $should_show_lite_upgrade
1626 + *
1163 1627 * @return void
1164 1628 */
1165 1629 public static function print_admin_banner( $should_show_lite_upgrade ) {
1166 1630 if ( ! current_user_can( 'administrator' ) ) {
@@ -1167,19 +1631,44 @@
1167 1631 FrmInbox::maybe_show_banner();
1168 1632 return;
1169 1633 }
1170 1634
1171 - if ( self::maybe_show_license_warning() || FrmInbox::maybe_show_banner() || ! $should_show_lite_upgrade || self::pro_is_installed() ) {
1635 + if ( FrmSalesApi::maybe_show_banner() || self::maybe_show_license_warning() || FrmInbox::maybe_show_banner() || ! $should_show_lite_upgrade || self::pro_is_installed() ) {
1172 1636 // Print license warning or inbox banner and exit if either prints.
1173 1637 // And exit before printing the upgrade bar if it shouldn't be shown.
1174 1638 return;
1175 1639 }
1640 + // phpcs:disable Generic.WhiteSpace.ScopeIndent
1176 1641 ?>
1177 1642 <div class="frm-upgrade-bar">
1178 - <span>You're using Formidable Forms Lite. To unlock more features consider</span>
1179 - <a href="<?php echo esc_url( self::admin_upgrade_link( 'top-bar' ) ); ?>" target="_blank" rel="noopener">upgrading to Pro</a>.
1643 + <div class="frm-upgrade-bar-inner">
1644 + <?php
1645 + $cta_text = FrmSalesApi::get_best_sale_value( 'lite_banner_cta_text' );
1646 +
1647 + if ( ! $cta_text ) {
1648 + $cta_text = __( 'upgrading to PRO', 'formidable' );
1649 + }
1650 +
1651 + $upgrade_link = FrmSalesApi::get_best_sale_value( 'lite_banner_cta_link' );
1652 + $utm = array(
1653 + 'campaign' => 'settings-license',
1654 + 'content' => 'lite-banner',
1655 + );
1656 +
1657 + $upgrade_link = $upgrade_link ? self::maybe_add_missing_utm( $upgrade_link, $utm ) : self::admin_upgrade_link( $utm );
1658 +
1659 + printf(
1660 + /* translators: %1$s: Start link HTML, %2$s: CTA text ("upgrading to PRO" by default), %3$s: End link HTML */
1661 + esc_html__( 'You\'re using Formidable Forms Lite. To unlock more features consider %1$s%2$s%3$s.', 'formidable' ),
1662 + '<a href="' . esc_url( $upgrade_link ) . '" target="_blank" rel="noopener">',
1663 + esc_html( $cta_text ),
1664 + '</a>'
1665 + );
1666 + ?>
1667 + </div>
1180 1668 </div>
1181 1669 <?php
1670 + // phpcs:enable Generic.WhiteSpace.ScopeIndent
1182 1671 }
1183 1672
1184 1673 /**
1185 1674 * @since 5.4.2
@@ -1193,14 +1682,18 @@
1193 1682 /**
1194 1683 * Render a button for a new item (Form, Application, etc).
1195 1684 *
1196 1685 * @since 3.0
1686 + *
1197 1687 * @param array $atts {
1688 + * Details about the button.
1689 + *
1198 1690 * @type array $link_hook Custom link hook, calls do_action and exits early.
1199 1691 * @type string $new_link Href value, default #.
1200 1692 * @type string $class Custom class names, space separated.
1201 1693 * @type string $button_text Button text. Default "Add New".
1202 1694 * }
1695 + *
1203 1696 * @return void
1204 1697 */
1205 1698 public static function add_new_item_link( $atts ) {
1206 1699 if ( isset( $atts['link_hook'] ) ) {
@@ -1207,9 +1700,9 @@
1207 1700 do_action( $atts['link_hook']['hook'], $atts['link_hook']['param'] );
1208 1701 return;
1209 1702 }
1210 1703
1211 - if ( empty( $atts['new_link'] ) && empty( $atts['trigger_new_form_modal'] ) && empty( $atts['class'] ) ) {
1704 + if ( empty( $atts['new_link'] ) && empty( $atts['create_form'] ) && empty( $atts['class'] ) ) {
1212 1705 // Do not render a button if none of these attributes are set.
1213 1706 return;
1214 1707 }
1215 1708
@@ -1215,12 +1708,8 @@
1215 1708
1216 1709 $href = ! empty( $atts['new_link'] ) ? esc_url( $atts['new_link'] ) : '#';
1217 1710 $class = 'button button-primary frm-button-primary';
1218 1711
1219 - if ( ! empty( $atts['trigger_new_form_modal'] ) ) {
1220 - $class .= ' frm-trigger-new-form-modal';
1221 - }
1222 -
1223 1712 if ( ! empty( $atts['class'] ) ) {
1224 1713 $class .= ' ' . $atts['class'];
1225 1714 }
1226 1715
@@ -1230,8 +1719,12 @@
1230 1719 }
1231 1720
1232 1721 /**
1233 1722 * @since 3.06
1723 + *
1724 + * @param array $atts
1725 + *
1726 + * @return void
1234 1727 */
1235 1728 public static function show_search_box( $atts ) {
1236 1729 $defaults = array(
1237 1730 'placeholder' => '',
@@ -1237,10 +1730,12 @@
1237 1730 'placeholder' => '',
1238 1731 'tosearch' => '',
1239 1732 'text' => __( 'Search', 'formidable' ),
1240 1733 'input_id' => '',
1734 + 'value' => false,
1735 + 'class' => '',
1241 1736 );
1242 - $atts = array_merge( $defaults, $atts );
1737 + $atts = array_merge( $defaults, $atts );
1243 1738
1244 1739 if ( $atts['input_id'] === 'template' && empty( $atts['tosearch'] ) ) {
1245 1740 $atts['tosearch'] = 'frm-card';
1246 1741 }
@@ -1245,8 +1740,9 @@
1245 1740 $atts['tosearch'] = 'frm-card';
1246 1741 }
1247 1742
1248 1743 $class = 'frm-search-input';
1744 +
1249 1745 if ( ! empty( $atts['tosearch'] ) ) {
1250 1746 $class .= ' frm-auto-search';
1251 1747 }
1252 1748
@@ -1251,21 +1747,35 @@
1251 1747 }
1252 1748
1253 1749 $input_id = $atts['input_id'] . '-search-input';
1254 1750
1751 + $input_atts = array(
1752 + 'type' => 'search',
1753 + 'id' => $input_id,
1754 + 'name' => 's',
1755 + 'placeholder' => $atts['placeholder'],
1756 + 'class' => $class,
1757 + 'data-tosearch' => $atts['tosearch'],
1758 + );
1759 +
1760 + if ( is_string( $atts['value'] ) ) {
1761 + $input_atts['value'] = $atts['value'];
1762 + } elseif ( isset( $_REQUEST['s'] ) ) {
1763 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1764 + $input_atts['value'] = wp_unslash( $_REQUEST['s'] );
1765 + }
1766 +
1767 + if ( ! empty( $atts['tosearch'] ) ) {
1768 + $input_atts['autocomplete'] = 'off';
1769 + }
1770 + // phpcs:disable Generic.WhiteSpace.ScopeIndent
1255 1771 ?>
1256 - <p class="frm-search">
1772 + <p class="frm-search <?php echo esc_attr( $atts['class'] ); ?>">
1257 1773 <label class="screen-reader-text" for="<?php echo esc_attr( $input_id ); ?>">
1258 1774 <?php echo esc_html( $atts['text'] ); ?>:
1259 1775 </label>
1260 - <span class="frmfont frm_search_icon"></span>
1261 - <input type="search" id="<?php echo esc_attr( $input_id ); ?>" name="s"
1262 - value="<?php _admin_search_query(); ?>" placeholder="<?php echo esc_attr( $atts['placeholder'] ); ?>"
1263 - class="<?php echo esc_attr( $class ); ?>" data-tosearch="<?php echo esc_attr( $atts['tosearch'] ); ?>"
1264 - <?php if ( ! empty( $atts['tosearch'] ) ) { ?>
1265 - autocomplete="off"
1266 - <?php } ?>
1267 - />
1776 + <?php self::icon_by_class( 'frmfont frm_search_icon frm_svg20' ); ?>
1777 + <input <?php self::array_to_html_params( $input_atts, true ); ?> />
1268 1778 <?php
1269 1779 if ( empty( $atts['tosearch'] ) ) {
1270 1780 submit_button( $atts['text'], 'button-secondary', '', false, array( 'id' => 'search-submit' ) );
1271 1781 }
@@ -1271,16 +1781,21 @@
1271 1781 }
1272 1782 ?>
1273 1783 </p>
1274 1784 <?php
1785 + // phpcs:enable Generic.WhiteSpace.ScopeIndent
1275 1786 }
1276 1787
1277 1788 /**
1278 - * @param string $type
1789 + * @param string $type Hook type slug.
1790 + * @param object|null $object Optional related object.
1791 + *
1792 + * @return void
1279 1793 */
1280 1794 public static function trigger_hook_load( $type, $object = null ) {
1281 1795 // Only load the form hooks once.
1282 1796 $hooks_loaded = apply_filters( 'frm_' . $type . '_hooks_loaded', false, $object );
1797 +
1283 1798 if ( ! $hooks_loaded ) {
1284 1799 do_action( 'frm_load_' . $type . '_hooks' );
1285 1800 }
1286 1801 }
@@ -1285,11 +1800,14 @@
1285 1800 }
1286 1801 }
1287 1802
1288 1803 /**
1289 - * Save all front-end js scripts into a single file
1804 + * Save all front-end js scripts into a single file.
1805 + * And save an additional single file of all front-end Stripe JS scripts.
1290 1806 *
1291 1807 * @since 3.0
1808 + *
1809 + * @return void
1292 1810 */
1293 1811 public static function save_combined_js() {
1294 1812 $file_atts = apply_filters(
1295 1813 'frm_js_location',
@@ -1302,10 +1820,34 @@
1302 1820
1303 1821 $files = array(
1304 1822 self::plugin_path() . '/js/formidable.min.js',
1305 1823 );
1824 + /**
1825 + * @param array $files
1826 + */
1306 1827 $files = apply_filters( 'frm_combined_js_files', $files );
1307 1828 $new_file->combine_files( $files );
1829 +
1830 + // Create the minified Stripe Script.
1831 + $file_atts = apply_filters(
1832 + 'frm_stripe_js_location',
1833 + array(
1834 + 'file_name' => 'frmstrp.min.js',
1835 + 'new_file_path' => self::plugin_path() . '/js',
1836 + )
1837 + );
1838 + $new_file = new FrmCreateFile( $file_atts );
1839 + $files = array(
1840 + FrmStrpLiteAppHelper::plugin_path() . 'js/frmstrp.min.js',
1841 + );
1842 +
1843 + /**
1844 + * @since 6.5
1845 + *
1846 + * @param array $files
1847 + */
1848 + $files = apply_filters( 'frm_stripe_combined_js_files', $files );
1849 + $new_file->combine_files( $files );
1308 1850 }
1309 1851
1310 1852 /**
1311 1853 * Check a value from a shortcode to see if true or false.
@@ -1312,14 +1854,14 @@
1312 1854 * True when value is 1, true, 'true', 'yes'
1313 1855 *
1314 1856 * @since 1.07.10
1315 1857 *
1316 - * @param string $value The value to compare
1858 + * @param bool|int|string $value The value to compare.
1317 1859 *
1318 - * @return boolean True or False
1860 + * @return bool
1319 1861 */
1320 1862 public static function is_true( $value ) {
1321 - return ( true === $value || 1 == $value || 'true' == $value || 'yes' == $value );
1863 + return true === $value || '1' === (string) $value || 'true' === $value || 'yes' === $value;
1322 1864 }
1323 1865
1324 1866 /**
1325 1867 * Gets all post from a specific post type.
@@ -1327,8 +1869,9 @@
1327 1869 *
1328 1870 * @since 4.10.01 Add `$post_type` argument.
1329 1871 *
1330 1872 * @param string $post_type Post type to query. Default is `page`.
1873 + *
1331 1874 * @return WP_Post[]
1332 1875 */
1333 1876 public static function get_pages( $post_type = 'page' ) {
1334 1877 $query = array(
@@ -1347,8 +1890,9 @@
1347 1890 *
1348 1891 * @since 5.0.09
1349 1892 *
1350 1893 * @param string $post_type Post type to query. Default is `page`.
1894 + *
1351 1895 * @return array
1352 1896 */
1353 1897 public static function get_post_ids_and_titles( $post_type = 'page' ) {
1354 1898 return FrmDb::get_results(
@@ -1373,13 +1917,12 @@
1373 1917 *
1374 1918 * @param array $args Selection arguments.
1375 1919 */
1376 1920 public static function maybe_autocomplete_pages_options( $args ) {
1377 - $args = self::preformat_selection_args( $args );
1378 -
1921 + $args = self::preformat_selection_args( $args );
1379 1922 $pages_count = wp_count_posts( $args['post_type'] );
1380 1923
1381 - if ( $pages_count->publish <= 50 ) {
1924 + if ( ! isset( $pages_count->publish ) || $pages_count->publish <= 50 ) {
1382 1925 self::wp_pages_dropdown( $args );
1383 1926 return;
1384 1927 }
1385 1928
@@ -1385,9 +1928,9 @@
1385 1928
1386 1929 wp_enqueue_script( 'jquery-ui-autocomplete' );
1387 1930
1388 1931 $selected = self::get_post_param( $args['field_name'], $args['page_id'], 'absint' );
1389 - $title = '';
1932 + $title = '';
1390 1933
1391 1934 if ( $selected ) {
1392 1935 $title = get_the_title( $selected );
1393 1936 }
@@ -1403,18 +1946,119 @@
1403 1946 <?php
1404 1947 }
1405 1948
1406 1949 /**
1407 - * @param array $args
1408 - * @param string $page_id Deprecated.
1409 - * @param boolean $truncate Deprecated.
1950 + * Maybe show an HTML select or autocomplete input based on the number of options.
1951 + *
1952 + * @since 6.21
1953 + *
1954 + * @param array $args Args. See the method for details.
1410 1955 */
1956 + public static function maybe_autocomplete_options( $args ) {
1957 + $defaults = array(
1958 + 'truncate' => false,
1959 + 'placeholder' => ' ',
1960 + 'name' => '',
1961 + 'id' => '',
1962 + 'selected' => '',
1963 + 'source' => array(),
1964 + 'dropdown_limit' => 50,
1965 + 'autocomplete_placeholder' => __( 'Select an option', 'formidable' ),
1966 + 'value_key' => 'value',
1967 + 'label_key' => 'label',
1968 + );
1969 +
1970 + $args = wp_parse_args( $args, $defaults );
1971 + $html_attrs = array();
1972 +
1973 + if ( ! empty( $args['name'] ) ) {
1974 + $html_attrs['name'] = $args['name'];
1975 + }
1976 +
1977 + if ( ! empty( $args['id'] ) ) {
1978 + $html_attrs['id'] = $args['id'];
1979 + }
1980 +
1981 + // phpcs:disable Generic.WhiteSpace.ScopeIndent
1982 + if ( count( $args['source'] ) <= $args['dropdown_limit'] ) {
1983 + ?>
1984 + <select <?php self::array_to_html_params( $html_attrs, true ); ?>>
1985 + <option value=""><?php echo esc_html( $args['placeholder'] ); ?></option>
1986 + <?php
1987 + foreach ( $args['source'] as $key => $source ) :
1988 + $value_label = self::get_dropdown_value_and_label_from_option( $source, $key, $args );
1989 +
1990 + if ( ! empty( $args['truncate'] ) ) {
1991 + $value_label['label'] = self::truncate( $value_label['label'], $args['truncate'] );
1992 + }
1993 + ?>
1994 + <option value="<?php echo esc_attr( $value_label['value'] ); ?>" <?php selected( $value_label['value'], $args['selected'] ); ?>><?php echo esc_html( $value_label['label'] ); ?></option><?php // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong ?>
1995 + <?php endforeach; ?>
1996 + </select>
1997 + <?php
1998 + return;
1999 + }
2000 + // phpcs:enable Generic.WhiteSpace.ScopeIndent
2001 +
2002 + $options = array();
2003 + $autocomplete_value = '';
2004 +
2005 + foreach ( $args['source'] as $key => $source ) {
2006 + $value_label = self::get_dropdown_value_and_label_from_option( $source, $key, $args );
2007 +
2008 + if ( $value_label['value'] === $args['selected'] ) {
2009 + $autocomplete_value = $value_label['label'];
2010 + }
2011 +
2012 + $options[] = $value_label;
2013 + }
2014 +
2015 + $html_attrs['type'] = 'hidden';
2016 + $html_attrs['class'] = 'frm_autocomplete_value_input';
2017 + $html_attrs['value'] = $args['selected'];
2018 + ?>
2019 + <input type="text" class="frm-custom-search"
2020 + data-source="<?php echo esc_attr( wp_json_encode( $options ) ); ?>"
2021 + placeholder="<?php echo esc_attr( $args['autocomplete_placeholder'] ); ?>"
2022 + value="<?php echo esc_attr( $autocomplete_value ); ?>" />
2023 + <input <?php self::array_to_html_params( $html_attrs, true ); ?> />
2024 + <?php
2025 + }
2026 +
2027 + /**
2028 + * Gets dropdown value and label from autodropdown option.
2029 + *
2030 + * @since 6.21
2031 + *
2032 + * @param array|string $option Autocomplete option.
2033 + * @param string $key Array key of the option.
2034 + * @param array $args See {@see FrmAppHelper::maybe_autocomplete_options()}.
2035 + *
2036 + * @return array
2037 + */
2038 + private static function get_dropdown_value_and_label_from_option( $option, $key, $args ) {
2039 + if ( is_array( $option ) ) {
2040 + $value = $option[ $args['value_key'] ] ?? '';
2041 + $label = $option[ $args['label_key'] ] ?? '';
2042 + } else {
2043 + $value = $key;
2044 + $label = $option;
2045 + }
2046 +
2047 + return compact( 'value', 'label' );
2048 + }
2049 +
2050 + /**
2051 + * @param array $args
2052 + * @param string $page_id Deprecated.
2053 + * @param bool $truncate Deprecated.
2054 + */
1411 2055 public static function wp_pages_dropdown( $args = array(), $page_id = '', $truncate = false ) {
1412 2056 self::prep_page_dropdown_params( $page_id, $truncate, $args );
1413 2057
1414 2058 $pages = self::get_post_ids_and_titles( $args['post_type'] );
1415 2059 $selected = self::get_post_param( $args['field_name'], $args['page_id'], 'absint' );
1416 -
2060 + // phpcs:disable Generic.WhiteSpace.ScopeIndent
1417 2061 ?>
1418 2062 <select name="<?php echo esc_attr( $args['field_name'] ); ?>" id="<?php echo esc_attr( $args['field_name'] ); ?>" class="frm-pages-dropdown">
1419 2063 <option value=""><?php echo esc_html( $args['placeholder'] ); ?></option>
1420 2064 <?php foreach ( $pages as $page ) { ?>
@@ -1423,8 +2067,9 @@
1423 2067 </option>
1424 2068 <?php } ?>
1425 2069 </select>
1426 2070 <?php
2071 + // phpcs:enable Generic.WhiteSpace.ScopeIndent
1427 2072 }
1428 2073
1429 2074 /**
1430 2075 * Fill in missing parameters passed to wp_pages_dropdown().
@@ -1430,8 +2075,14 @@
1430 2075 * Fill in missing parameters passed to wp_pages_dropdown().
1431 2076 * This is for reverse compatibility with switching 3 params to 1.
1432 2077 *
1433 2078 * @since 4.03.06
2079 + *
2080 + * @param string $page_id Deprecated.
2081 + * @param bool $truncate Deprecated.
2082 + * @param mixed $args
2083 + *
2084 + * @return void
1434 2085 */
1435 2086 private static function prep_page_dropdown_params( $page_id, $truncate, &$args ) {
1436 2087 if ( ! is_array( $args ) ) {
1437 2088 $args = array(
@@ -1448,16 +2099,20 @@
1448 2099 * Filter to format args for page dropdown or autocomplete
1449 2100 *
1450 2101 * @since 4.03.06
1451 2102 * @since 4.10.01 Added `post_type` and `autocomplete_placeholder` to the arguments array.
2103 + *
2104 + * @param array $args
2105 + *
2106 + * @return array
1452 2107 */
1453 2108 private static function preformat_selection_args( $args ) {
1454 2109 $defaults = array(
1455 - 'truncate' => false,
1456 - 'placeholder' => ' ',
1457 - 'field_name' => '',
1458 - 'page_id' => '',
1459 - 'post_type' => 'page',
2110 + 'truncate' => false,
2111 + 'placeholder' => ' ',
2112 + 'field_name' => '',
2113 + 'page_id' => '',
2114 + 'post_type' => 'page',
1460 2115 'autocomplete_placeholder' => __( 'Select a Page', 'formidable' ),
1461 2116 );
1462 2117
1463 2118 return array_merge( $defaults, $args );
@@ -1462,13 +2117,18 @@
1462 2117
1463 2118 return array_merge( $defaults, $args );
1464 2119 }
1465 2120
2121 + /**
2122 + * @param int $post_id
2123 + *
2124 + * @return string
2125 + */
1466 2126 public static function post_edit_link( $post_id ) {
1467 2127 $post = get_post( $post_id );
2128 +
1468 2129 if ( $post ) {
1469 2130 $post_url = admin_url( 'post.php?post=' . $post_id . '&action=edit' );
1470 -
1471 2131 return '<a href="' . esc_url( $post_url ) . '">' . self::truncate( $post->post_title, 50 ) . '</a>';
1472 2132 }
1473 2133
1474 2134 return '';
@@ -1481,11 +2141,9 @@
1481 2141 *
1482 2142 * @return bool
1483 2143 */
1484 2144 public static function is_full_screen() {
1485 - return self::is_form_builder_page() ||
1486 - self::is_style_editor_page() ||
1487 - self::is_full_screen_view_builder_page();
2145 + return self::is_form_builder_page() || self::is_style_editor_page() || self::is_full_screen_view_builder_page();
1488 2146 }
1489 2147
1490 2148 /**
1491 2149 * Check if user is on the style editor or its alternative URL.
@@ -1495,8 +2153,9 @@
1495 2153 * @since 5.5.3
1496 2154 * @since 6.0 Added the $view parameter. Previously there was only a 'edit' view.
1497 2155 *
1498 2156 * @param string $view Supports 'edit', 'list', and ''. If '', both 'edit' and 'list' will match.
2157 + *
1499 2158 * @return bool
1500 2159 */
1501 2160 public static function is_style_editor_page( $view = '' ) {
1502 2161 if ( ! self::is_admin_page( 'formidable-styles' ) && ! self::is_admin_page( 'formidable-styles2' ) ) {
@@ -1528,35 +2187,29 @@
1528 2187 return self::is_admin_page( 'formidable-views-editor' );
1529 2188 }
1530 2189
1531 2190 /**
1532 - * @since 4.0
1533 - * @deprecated 5.5.3
2191 + * @param string $field_name
2192 + * @param array|string $capability
2193 + * @param string $multiple 'single' and 'multiple'.
1534 2194 */
1535 - public static function maybe_full_screen_link( $link ) {
1536 - _deprecated_function( __METHOD__, '5.5.3' );
1537 - return $link;
1538 - }
1539 -
1540 - /**
1541 - * @param string $field_name
1542 - * @param string|array $capability
1543 - * @param string $multiple 'single' and 'multiple'
1544 - */
1545 2195 public static function wp_roles_dropdown( $field_name, $capability, $multiple = 'single' ) {
2196 + // phpcs:disable Generic.WhiteSpace.ScopeIndent
1546 2197 ?>
1547 2198 <select name="<?php echo esc_attr( $field_name ); ?>" id="<?php echo esc_attr( $field_name ); ?>"
1548 - <?php echo ( 'multiple' === $multiple ) ? 'multiple="multiple"' : ''; ?>
2199 + <?php echo 'multiple' === $multiple ? 'multiple="multiple"' : ''; ?>
1549 2200 class="frm_multiselect">
1550 2201 <?php self::roles_options( $capability ); ?>
1551 2202 </select>
1552 2203 <?php
2204 + // phpcs:enable Generic.WhiteSpace.ScopeIndent
1553 2205 }
1554 2206
1555 2207 /**
1556 2208 * @since 4.07
2209 + *
1557 2210 * @param array|string $selected
1558 - * @param string $current
2211 + * @param string $current
1559 2212 */
1560 2213 private static function selected( $selected, $current ) {
1561 2214 if ( is_callable( 'FrmProAppHelper::selected' ) ) {
1562 2215 FrmProAppHelper::selected( $selected, $current );
@@ -1565,12 +2218,13 @@
1565 2218 }
1566 2219 }
1567 2220
1568 2221 /**
1569 - * @param string|array $capability
2222 + * @param array|string $capability
1570 2223 */
1571 2224 public static function roles_options( $capability ) {
1572 2225 global $frm_vars;
2226 +
1573 2227 if ( isset( $frm_vars['editable_roles'] ) ) {
1574 2228 $editable_roles = $frm_vars['editable_roles'];
1575 2229 } else {
1576 2230 $editable_roles = get_editable_roles();
@@ -1579,9 +2233,9 @@
1579 2233
1580 2234 foreach ( $editable_roles as $role => $details ) {
1581 2235 $name = translate_user_role( $details['name'] );
1582 2236 ?>
1583 - <option value="<?php echo esc_attr( $role ); ?>" <?php self::selected( $capability, $role ); ?>><?php echo esc_attr( $name ); ?> </option>
2237 + <option value="<?php echo esc_attr( $role ); ?>" <?php self::selected( $capability, $role ); ?>><?php echo esc_html( $name ); ?></option>
1584 2238 <?php
1585 2239 unset( $role, $details );
1586 2240 }
1587 2241 }
@@ -1591,8 +2245,9 @@
1591 2245 *
1592 2246 * @since 5.0 Parameter `$type` supports `pro_only` value.
1593 2247 *
1594 2248 * @param string $type Supports `auto`, `pro`, or `pro_only`.
2249 + *
1595 2250 * @return array
1596 2251 */
1597 2252 public static function frm_capabilities( $type = 'auto' ) {
1598 2253 if ( ! self::pro_is_installed() && ! in_array( $type, array( 'pro', 'pro_only' ), true ) ) {
@@ -1602,9 +2257,8 @@
1602 2257 $pro_cap = array(
1603 2258 'frm_create_entries' => __( 'Add Entries from Admin Area', 'formidable' ),
1604 2259 'frm_edit_entries' => __( 'Edit Entries from Admin Area', 'formidable' ),
1605 2260 'frm_view_reports' => __( 'View Reports', 'formidable' ),
1606 - 'frm_edit_displays' => __( 'Add/Edit Views', 'formidable' ),
1607 2261 );
1608 2262 /**
1609 2263 * @since 5.3.1
1610 2264 *
@@ -1627,9 +2281,9 @@
1627 2281 * @return array<string,string>
1628 2282 */
1629 2283 private static function get_lite_capabilities() {
1630 2284 return array(
1631 - 'frm_view_forms' => __( 'View Forms', 'formidable' ),
2285 + 'frm_view_forms' => __( 'View Forms List', 'formidable' ),
1632 2286 'frm_edit_forms' => __( 'Add and Edit Forms', 'formidable' ),
1633 2287 'frm_delete_forms' => __( 'Delete Forms', 'formidable' ),
1634 2288 'frm_change_settings' => __( 'Access this Settings Page', 'formidable' ),
1635 2289 'frm_view_entries' => __( 'View Entries from Admin Area', 'formidable' ),
@@ -1658,21 +2312,18 @@
1658 2312 if ( $role === 'loggedin' || ! $role ) {
1659 2313 return is_user_logged_in();
1660 2314 }
1661 2315
1662 - if ( $role == 1 ) {
2316 + if ( (int) $role === 1 ) {
1663 2317 $role = 'administrator';
1664 2318 }
1665 2319
1666 - if ( ! is_user_logged_in() ) {
1667 - return false;
1668 - }
1669 -
1670 - return current_user_can( $role );
2320 + return is_user_logged_in() ? current_user_can( $role ) : false;
1671 2321 }
1672 2322
1673 2323 /**
1674 - * @param string|array $needed_role
2324 + * @param array|string $needed_role
2325 + *
1675 2326 * @return bool
1676 2327 */
1677 2328 public static function user_has_permission( $needed_role ) {
1678 2329 if ( is_array( $needed_role ) ) {
@@ -1686,18 +2337,20 @@
1686 2337 }
1687 2338
1688 2339 $can = self::current_user_can( $needed_role );
1689 2340
1690 - if ( $can || in_array( $needed_role, array( '-1', 'loggedout' ) ) ) {
2341 + if ( $can || in_array( $needed_role, array( '-1', 'loggedout' ), true ) ) {
1691 2342 return $can;
1692 2343 }
1693 2344
1694 2345 $roles = array( 'administrator', 'editor', 'author', 'contributor', 'subscriber' );
2346 +
1695 2347 foreach ( $roles as $role ) {
1696 2348 if ( current_user_can( $role ) ) {
1697 2349 return true;
1698 2350 }
1699 - if ( $role == $needed_role ) {
2351 +
2352 + if ( $role === $needed_role ) {
1700 2353 break;
1701 2354 }
1702 2355 }
1703 2356
@@ -1715,11 +2368,11 @@
1715 2368 if ( ! current_user_can( 'administrator' ) || current_user_can( 'frm_view_forms' ) ) {
1716 2369 return;
1717 2370 }
1718 2371
1719 - $user_id = get_current_user_id();
1720 - $user = new WP_User( $user_id );
2372 + $user = new WP_User( get_current_user_id() );
1721 2373 $frm_roles = self::frm_capabilities();
2374 +
1722 2375 foreach ( $frm_roles as $frm_role => $frm_role_description ) {
1723 2376 $user->add_cap( $frm_role );
1724 2377 unset( $frm_role, $frm_role_description );
1725 2378 }
@@ -1728,35 +2381,47 @@
1728 2381 /**
1729 2382 * Make sure admins have permission to see the menu items
1730 2383 *
1731 2384 * @since 2.0.6
2385 + *
2386 + * @param string $cap
2387 + *
2388 + * @return void
1732 2389 */
1733 2390 public static function force_capability( $cap = 'frm_change_settings' ) {
1734 - if ( current_user_can( 'administrator' ) && ! current_user_can( $cap ) ) {
1735 - $role = get_role( 'administrator' );
1736 - $frm_roles = self::frm_capabilities();
1737 - foreach ( $frm_roles as $frm_role => $frm_role_description ) {
1738 - $role->add_cap( $frm_role );
1739 - }
2391 + if ( ! current_user_can( 'administrator' ) || current_user_can( $cap ) ) {
2392 + return;
1740 2393 }
2394 +
2395 + $role = get_role( 'administrator' );
2396 + $frm_roles = self::frm_capabilities();
2397 +
2398 + foreach ( $frm_roles as $frm_role => $frm_role_description ) {
2399 + $role->add_cap( $frm_role );
2400 + }
1741 2401 }
1742 2402
1743 2403 /**
1744 - * Check if the user has permision for action.
2404 + * Check if the user has permission for action.
1745 2405 * Return permission message and stop the action if no permission
1746 2406 *
1747 2407 * @since 2.0
1748 2408 *
1749 2409 * @param string $permission
2410 + * @param string $show_message
1750 2411 */
1751 2412 public static function permission_check( $permission, $show_message = 'show' ) {
1752 2413 $permission_error = self::permission_nonce_error( $permission );
1753 - if ( $permission_error !== false ) {
1754 - if ( 'hide' == $show_message ) {
1755 - $permission_error = '';
1756 - }
1757 - wp_die( esc_html( $permission_error ) );
2414 +
2415 + if ( $permission_error === false ) {
2416 + return;
1758 2417 }
2418 +
2419 + if ( 'hide' === $show_message ) {
2420 + $permission_error = '';
2421 + }
2422 +
2423 + wp_die( esc_html( $permission_error ) );
1759 2424 }
1760 2425
1761 2426 /**
1762 2427 * Check user permission and nonce
@@ -1763,24 +2428,27 @@
1763 2428 *
1764 2429 * @since 2.0
1765 2430 *
1766 2431 * @param string $permission
2432 + * @param string $nonce_name
2433 + * @param string $nonce
1767 2434 *
1768 2435 * @return false|string The permission message or false if allowed
1769 2436 */
1770 2437 public static function permission_nonce_error( $permission, $nonce_name = '', $nonce = '' ) {
1771 - if ( ! empty( $permission ) && ! current_user_can( $permission ) && ! current_user_can( 'administrator' ) ) {
2438 + if ( $permission && ! current_user_can( $permission ) && ! current_user_can( 'administrator' ) ) {
1772 2439 $frm_settings = self::get_settings();
1773 -
1774 2440 return $frm_settings->admin_permission;
1775 2441 }
1776 2442
1777 2443 $error = false;
1778 - if ( empty( $nonce_name ) ) {
2444 +
2445 + if ( ! $nonce_name ) {
1779 2446 return $error;
1780 2447 }
1781 2448
1782 - $nonce_value = ( $_REQUEST && isset( $_REQUEST[ $nonce_name ] ) ) ? sanitize_text_field( wp_unslash( $_REQUEST[ $nonce_name ] ) ) : '';
2449 + $nonce_value = $_REQUEST && isset( $_REQUEST[ $nonce_name ] ) ? sanitize_text_field( wp_unslash( $_REQUEST[ $nonce_name ] ) ) : '';
2450 +
1783 2451 if ( $_REQUEST && ( ! isset( $_REQUEST[ $nonce_name ] ) || ! wp_verify_nonce( $nonce_value, $nonce ) ) ) {
1784 2452 $frm_settings = self::get_settings();
1785 2453 $error = $frm_settings->admin_permission;
1786 2454 }
@@ -1787,8 +2455,14 @@
1787 2455
1788 2456 return $error;
1789 2457 }
1790 2458
2459 + /**
2460 + * @param array|string $values
2461 + * @param string $current
2462 + *
2463 + * @return void
2464 + */
1791 2465 public static function checked( $values, $current ) {
1792 2466 if ( self::check_selected( $values, $current ) ) {
1793 2467 echo ' checked="checked"';
1794 2468 }
@@ -1793,40 +2467,74 @@
1793 2467 echo ' checked="checked"';
1794 2468 }
1795 2469 }
1796 2470
2471 + /**
2472 + * @param array|string $values
2473 + * @param string|null $current
2474 + *
2475 + * @return bool
2476 + */
1797 2477 public static function check_selected( $values, $current ) {
1798 2478 $values = self::recursive_function_map( $values, 'trim' );
1799 2479 $values = self::recursive_function_map( $values, 'htmlspecialchars_decode' );
1800 - $current = htmlspecialchars_decode( trim( $current ) );
2480 + $current = is_null( $current ) ? '' : htmlspecialchars_decode( trim( $current ) );
1801 2481
1802 - return ( is_array( $values ) && in_array( $current, $values ) ) || ( ! is_array( $values ) && $values == $current );
2482 + // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict, Universal.Operators.StrictComparisons
2483 + return is_array( $values ) ? in_array( $current, $values ) : $values == $current;
1803 2484 }
1804 2485
2486 + /**
2487 + * @param array|string $value
2488 + * @param callable|string $function
2489 + *
2490 + * @return array|string
2491 + */
1805 2492 public static function recursive_function_map( $value, $function ) {
1806 2493 if ( is_array( $value ) ) {
1807 2494 $original_function = $function;
1808 - if ( count( $value ) ) {
1809 - $function = explode( ', ', FrmDb::prepare_array_values( $value, $function ) );
1810 - } else {
1811 - $function = array( $function );
1812 - }
1813 - if ( ! self::is_assoc( $value ) ) {
1814 - $value = array_map( array( 'FrmAppHelper', 'recursive_function_map' ), $value, $function );
1815 - } else {
2495 + $function = count( $value ) ? explode( ', ', FrmDb::prepare_array_values( $value, $function ) ) : array( $function );
2496 +
2497 + if ( self::is_assoc( $value ) ) {
1816 2498 foreach ( $value as $k => $v ) {
1817 2499 if ( ! is_array( $v ) ) {
1818 2500 $value[ $k ] = call_user_func( $original_function, $v );
1819 2501 }
1820 2502 }
2503 + } else {
2504 + $value = array_map( array( 'FrmAppHelper', 'recursive_function_map' ), $value, $function );
1821 2505 }
1822 2506 } else {
2507 + $value = self::maybe_update_value_if_null( $value, $function );
1823 2508 $value = call_user_func( $function, $value );
2509 + }//end if
2510 +
2511 + return $value;
2512 + }
2513 +
2514 + /**
2515 + * Updates value to empty string if it is null and being passed to a string function.
2516 + *
2517 + * @since 6.8.4
2518 + *
2519 + * @param mixed $value
2520 + * @param string $function
2521 + *
2522 + * @return mixed
2523 + */
2524 + private static function maybe_update_value_if_null( $value, $function ) {
2525 + if ( null === $value && in_array( $function, array( 'trim', 'strlen' ), true ) ) {
2526 + return '';
1824 2527 }
1825 2528
1826 2529 return $value;
1827 2530 }
1828 2531
2532 + /**
2533 + * @param array $array
2534 + *
2535 + * @return bool
2536 + */
1829 2537 public static function is_assoc( $array ) {
1830 2538 return (bool) count( array_filter( array_keys( $array ), 'is_string' ) );
1831 2539 }
1832 2540
@@ -1831,20 +2539,24 @@
1831 2539 }
1832 2540
1833 2541 /**
1834 2542 * Flatten a multi-dimensional array
2543 + *
2544 + * @param array $array
2545 + * @param string $keys
2546 + *
2547 + * @return array
1835 2548 */
1836 2549 public static function array_flatten( $array, $keys = 'keep' ) {
1837 2550 $return = array();
2551 +
1838 2552 foreach ( $array as $key => $value ) {
1839 2553 if ( is_array( $value ) ) {
1840 2554 $return = array_merge( $return, self::array_flatten( $value, $keys ) );
2555 + } elseif ( $keys === 'keep' ) {
2556 + $return[ $key ] = $value;
1841 2557 } else {
1842 - if ( $keys == 'keep' ) {
1843 - $return[ $key ] = $value;
1844 - } else {
1845 - $return[] = $value;
1846 - }
2558 + $return[] = $value;
1847 2559 }
1848 2560 }
1849 2561
1850 2562 return $return;
@@ -1849,16 +2561,43 @@
1849 2561
1850 2562 return $return;
1851 2563 }
1852 2564
2565 + /**
2566 + * Flatten an array before imploding it to avoid Array to string conversion warnings.
2567 + *
2568 + * @since 6.16.1
2569 + *
2570 + * @param string $sep
2571 + * @param array $array
2572 + *
2573 + * @return string
2574 + */
2575 + public static function safe_implode( $sep, $array ) {
2576 + $array = self::array_flatten( $array );
2577 + return implode( $sep, $array );
2578 + }
2579 +
2580 + /**
2581 + * @param string $text
2582 + * @param bool $is_rich_text
2583 + *
2584 + * @return string
2585 + */
1853 2586 public static function esc_textarea( $text, $is_rich_text = false ) {
1854 2587 $safe_text = str_replace( '&quot;', '"', $text );
2588 +
1855 2589 if ( ! $is_rich_text ) {
1856 2590 $safe_text = htmlspecialchars( $safe_text, ENT_NOQUOTES );
1857 2591 }
2592 +
1858 2593 $safe_text = str_replace( '&amp; ', '& ', $safe_text );
1859 2594
1860 - return apply_filters( 'esc_textarea', $safe_text, $text );
2595 + /**
2596 + * @param string $safe_text
2597 + * @param string $text
2598 + */
2599 + return (string) apply_filters( 'esc_textarea', $safe_text, $text );
1861 2600 }
1862 2601
1863 2602 /**
1864 2603 * Add auto paragraphs to text areas
@@ -1863,44 +2602,59 @@
1863 2602 /**
1864 2603 * Add auto paragraphs to text areas
1865 2604 *
1866 2605 * @since 2.0
2606 + *
2607 + * @param mixed $content
2608 + *
2609 + * @return mixed
1867 2610 */
1868 2611 public static function use_wpautop( $content ) {
1869 - if ( apply_filters( 'frm_use_wpautop', true ) && ! is_array( $content ) ) {
1870 - $content = wpautop( str_replace( '<br>', '<br />', $content ) );
2612 + if ( apply_filters( 'frm_use_wpautop', true ) && is_string( $content ) ) {
2613 + return wpautop( str_replace( '<br>', '<br />', $content ) );
1871 2614 }
1872 2615
1873 2616 return $content;
1874 2617 }
1875 2618
2619 + /**
2620 + * Replace quotes with their HTML entities.
2621 + *
2622 + * @param string $val
2623 + *
2624 + * @return string
2625 + */
1876 2626 public static function replace_quotes( $val ) {
1877 2627 // Replace double quotes.
1878 2628 $val = str_replace( array( '&#8220;', '&#8221;', '&#8243;' ), '"', $val );
1879 2629
1880 2630 // Replace single quotes.
1881 - $val = str_replace( array( '&#8216;', '&#8217;', '&#8242;', '&prime;', '&rsquo;', '&lsquo;' ), "'", $val );
1882 -
1883 - return $val;
2631 + return str_replace( array( '&#8216;', '&#8217;', '&#8242;', '&prime;', '&rsquo;', '&lsquo;' ), "'", $val );
1884 2632 }
1885 2633
1886 2634 /**
1887 - * @param string $handle
2635 + * @param string $handle
2636 + * @param int|string $default
2637 + *
2638 + * @return int|string
1888 2639 */
1889 2640 public static function script_version( $handle, $default = 0 ) {
1890 2641 global $wp_scripts;
2642 +
1891 2643 if ( ! $wp_scripts ) {
1892 2644 return $default;
1893 2645 }
1894 2646
1895 2647 $ver = $default;
2648 +
1896 2649 if ( ! isset( $wp_scripts->registered[ $handle ] ) ) {
1897 2650 return $ver;
1898 2651 }
1899 2652
1900 2653 $query = $wp_scripts->registered[ $handle ];
2654 +
1901 2655 if ( is_object( $query ) && ! empty( $query->ver ) ) {
1902 - $ver = $query->ver;
2656 + return $query->ver;
1903 2657 }
1904 2658
1905 2659 return $ver;
1906 2660 }
@@ -1909,17 +2663,23 @@
1909 2663 * @since 5.0.13 added $echo param.
1910 2664 *
1911 2665 * @param string $url
1912 2666 * @param bool $echo
1913 - * @return string|void
2667 + *
2668 + * @return string|null
1914 2669 */
1915 2670 public static function js_redirect( $url, $echo = false ) {
1916 - $callback = function() use ( $url ) {
2671 + $callback = function () use ( $url ) {
1917 2672 echo '<script type="text/javascript">window.location="' . esc_url_raw( $url ) . '"</script>';
1918 2673 };
1919 2674 return self::clip( $callback, $echo );
1920 2675 }
1921 2676
2677 + /**
2678 + * @param int|string $user_id
2679 + *
2680 + * @return int|string
2681 + */
1922 2682 public static function get_user_id_param( $user_id ) {
1923 2683 if ( ! $user_id || is_numeric( $user_id ) ) {
1924 2684 return $user_id;
1925 2685 }
@@ -1924,16 +2684,13 @@
1924 2684 return $user_id;
1925 2685 }
1926 2686
1927 2687 $user_id = sanitize_text_field( $user_id );
1928 - if ( $user_id == 'current' ) {
2688 +
2689 + if ( $user_id === 'current' ) {
1929 2690 $user_id = get_current_user_id();
1930 2691 } else {
1931 - if ( is_email( $user_id ) ) {
1932 - $user = get_user_by( 'email', $user_id );
1933 - } else {
1934 - $user = get_user_by( 'login', $user_id );
1935 - }
2692 + $user = is_email( $user_id ) ? get_user_by( 'email', $user_id ) : get_user_by( 'login', $user_id );
1936 2693
1937 2694 if ( $user ) {
1938 2695 $user_id = $user->ID;
1939 2696 }
@@ -1942,8 +2699,14 @@
1942 2699
1943 2700 return $user_id;
1944 2701 }
1945 2702
2703 + /**
2704 + * @param string $filename
2705 + * @param array $atts
2706 + *
2707 + * @return false|string
2708 + */
1946 2709 public static function get_file_contents( $filename, $atts = array() ) {
1947 2710 if ( ! is_file( $filename ) ) {
1948 2711 return false;
1949 2712 }
@@ -1949,13 +2712,10 @@
1949 2712 }
1950 2713
1951 2714 extract( $atts ); // phpcs:ignore WordPress.PHP.DontExtract
1952 2715 ob_start();
1953 - include( $filename );
1954 - $contents = ob_get_contents();
1955 - ob_end_clean();
1956 -
1957 - return $contents;
2716 + include $filename;
2717 + return ob_get_clean();
1958 2718 }
1959 2719
1960 2720 /**
1961 2721 * @param string $name
@@ -1965,8 +2725,9 @@
1965 2725 * @param int $num_chars
1966 2726 */
1967 2727 public static function get_unique_key( $name, $table_name, $column, $id = 0, $num_chars = 5 ) {
1968 2728 $key = '';
2729 +
1969 2730 if ( $name ) {
1970 2731 $key = sanitize_key( $name );
1971 2732 $key = self::maybe_clear_long_key( $key, $column );
1972 2733 }
@@ -1986,31 +2747,31 @@
1986 2747 $column
1987 2748 );
1988 2749
1989 2750 // Create a unique field id if it has already been used.
1990 - if ( in_array( $key, $similar_keys, true ) ) {
1991 - $key = self::maybe_truncate_key_before_appending( $column, $key );
2751 + if ( ! in_array( $key, $similar_keys, true ) ) {
2752 + return $key;
2753 + }
1992 2754
1993 - /**
1994 - * Allow for a custom separator between the attempted key and the generated suffix.
1995 - *
1996 - * @since 5.2.03
1997 - *
1998 - * @param string $separator. Default empty.
1999 - * @param string $key the key without the added suffix.
2000 - */
2001 - $separator = apply_filters( 'frm_unique_' . $column . '_separator', '', $key );
2755 + $key = self::maybe_truncate_key_before_appending( $column, $key );
2002 2756
2003 - $suffix = 2;
2004 - do {
2005 - $key_check = $key . $separator . $suffix;
2006 - ++$suffix;
2007 - } while ( in_array( $key_check, $similar_keys, true ) );
2757 + /**
2758 + * Allow for a custom separator between the attempted key and the generated suffix.
2759 + *
2760 + * @since 5.2.03
2761 + *
2762 + * @param string $separator. Default empty.
2763 + * @param string $key the key without the added suffix.
2764 + */
2765 + $separator = apply_filters( 'frm_unique_' . $column . '_separator', '', $key );
2008 2766
2009 - $key = $key_check;
2010 - }
2767 + $suffix = 2;
2768 + do {
2769 + $key_check = $key . $separator . $suffix;
2770 + ++$suffix;
2771 + } while ( in_array( $key_check, $similar_keys, true ) );
2011 2772
2012 - return $key;
2773 + return $key_check;
2013 2774 }
2014 2775
2015 2776 /**
2016 2777 * Avoid trying to append to a really long key,
@@ -2017,20 +2778,26 @@
2017 2778 * The database limit is 100 for form and field keys so we want to avoid getting too close.
2018 2779 *
2019 2780 * @param string $column
2020 2781 * @param string $key
2782 + *
2021 2783 * @return string
2022 2784 */
2023 2785 private static function maybe_truncate_key_before_appending( $column, $key ) {
2024 - if ( in_array( $column, array( 'form_key', 'field_key' ), true ) ) {
2025 - $max_key_length_before_truncating = 60;
2026 - if ( strlen( $key ) > $max_key_length_before_truncating ) {
2027 - $key = substr( $key, 0, $max_key_length_before_truncating );
2028 - if ( is_numeric( $key ) ) {
2029 - $key .= 'a';
2030 - }
2786 + if ( ! in_array( $column, array( 'form_key', 'field_key' ), true ) ) {
2787 + return $key;
2788 + }
2789 +
2790 + $max_key_length_before_truncating = 60;
2791 +
2792 + if ( strlen( $key ) > $max_key_length_before_truncating ) {
2793 + $key = substr( $key, 0, $max_key_length_before_truncating );
2794 +
2795 + if ( is_numeric( $key ) ) {
2796 + $key .= 'a';
2031 2797 }
2032 2798 }
2799 +
2033 2800 return $key;
2034 2801 }
2035 2802
2036 2803 /**
@@ -2037,29 +2804,36 @@
2037 2804 * Possibly reset a key to avoid conflicts with column size limits.
2038 2805 *
2039 2806 * @param string $key
2040 2807 * @param string $column
2808 + *
2041 2809 * @return string either the original key value, or an empty string if the key was too long.
2042 2810 */
2043 2811 private static function maybe_clear_long_key( $key, $column ) {
2044 2812 if ( 'field_key' === $column && strlen( $key ) >= 70 ) {
2045 - $key = '';
2813 + return '';
2046 2814 }
2047 2815 return $key;
2048 2816 }
2049 2817
2050 2818 /**
2819 + * @since 6.21 This is changed from `private` to `public`.
2820 + *
2051 2821 * @param int $num_chars
2822 + *
2052 2823 * @return string
2053 2824 */
2054 - private static function generate_new_key( $num_chars ) {
2055 - $max_slug_value = pow( 36, $num_chars );
2056 - $min_slug_value = 37; // we want to have at least 2 characters in the slug
2057 - return base_convert( rand( $min_slug_value, $max_slug_value ), 10, 36 );
2825 + public static function generate_new_key( $num_chars ) {
2826 + $max_slug_value = 36 ** $num_chars;
2827 +
2828 + // We want to have at least 2 characters in the slug.
2829 + $min_slug_value = 37;
2830 + return base_convert( random_int( $min_slug_value, $max_slug_value ), 10, 36 );
2058 2831 }
2059 2832
2060 2833 /**
2061 2834 * @param string $key
2835 + *
2062 2836 * @return string
2063 2837 */
2064 2838 private static function prevent_numeric_and_reserved_keys( $key ) {
2065 2839 if ( is_numeric( $key ) ) {
@@ -2073,12 +2847,14 @@
2073 2847 'editlink',
2074 2848 'siteurl',
2075 2849 'evenodd',
2076 2850 );
2851 +
2077 2852 if ( in_array( $key, $not_allowed, true ) ) {
2078 2853 $key .= 'a';
2079 2854 }
2080 2855 }
2856 +
2081 2857 return $key;
2082 2858 }
2083 2859
2084 2860 /**
@@ -2083,11 +2859,16 @@
2083 2859
2084 2860 /**
2085 2861 * Editing a Form or Entry
2086 2862 *
2087 - * @param string $table
2863 + * @param object $record
2864 + * @param string $table
2865 + * @param array|string $fields
2866 + * @param bool $default
2867 + * @param array $post_values
2868 + * @param array $args
2088 2869 *
2089 - * @return bool|array
2870 + * @return array|bool
2090 2871 */
2091 2872 public static function setup_edit_vars( $record, $table, $fields = '', $default = false, $post_values = array(), $args = array() ) {
2092 2873 if ( ! $record ) {
2093 2874 return false;
@@ -2092,9 +2873,9 @@
2092 2873 if ( ! $record ) {
2093 2874 return false;
2094 2875 }
2095 2876
2096 - if ( empty( $post_values ) ) {
2877 + if ( ! $post_values ) {
2097 2878 $post_values = wp_unslash( $_POST ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
2098 2879 }
2099 2880
2100 2881 $values = array(
@@ -2102,9 +2883,9 @@
2102 2883 'fields' => array(),
2103 2884 );
2104 2885
2105 2886 foreach ( array( 'name', 'description' ) as $var ) {
2106 - $default_val = isset( $record->{$var} ) ? $record->{$var} : '';
2887 + $default_val = $record->{$var} ?? '';
2107 2888 $values[ $var ] = self::get_param( $var, $default_val, 'get', 'wp_kses_post' );
2108 2889 unset( $var, $default_val );
2109 2890 }
2110 2891
@@ -2122,48 +2903,67 @@
2122 2903
2123 2904 return $values;
2124 2905 }
2125 2906
2907 + /**
2908 + * @param array|string $fields
2909 + * @param object $record
2910 + * @param array $values
2911 + * @param array $args
2912 + *
2913 + * @return void
2914 + */
2126 2915 private static function prepare_field_arrays( $fields, $record, array &$values, $args ) {
2127 - if ( ! empty( $fields ) ) {
2128 - foreach ( (array) $fields as $field ) {
2129 - if ( ! self::is_admin_page() ) {
2130 - // Don't prep default values on the form settings page.
2131 - $field->default_value = apply_filters( 'frm_get_default_value', $field->default_value, $field, true );
2132 - }
2133 - $args['parent_form_id'] = isset( $args['parent_form_id'] ) ? $args['parent_form_id'] : $field->form_id;
2134 - self::fill_field_defaults( $field, $record, $values, $args );
2916 + if ( ! $fields ) {
2917 + return;
2918 + }
2919 +
2920 + foreach ( (array) $fields as $field ) {
2921 + if ( ! self::is_admin_page() ) {
2922 + // Don't prep default values on the form settings page.
2923 + $field->default_value = apply_filters( 'frm_get_default_value', $field->default_value, $field, true );
2135 2924 }
2925 +
2926 + $args['parent_form_id'] = $args['parent_form_id'] ?? $field->form_id;
2927 + self::fill_field_defaults( $field, $record, $values, $args );
2136 2928 }
2137 2929 }
2138 2930
2931 + /**
2932 + * @param object $field
2933 + * @param object $record
2934 + * @param array $values
2935 + * @param array $args
2936 + *
2937 + * @return void
2938 + */
2139 2939 private static function fill_field_defaults( $field, $record, array &$values, $args ) {
2140 2940 $post_values = $args['post_values'];
2141 2941
2142 2942 if ( $args['default'] ) {
2143 2943 $meta_value = $field->default_value;
2944 + } elseif ( $record->post_id && self::pro_is_installed() && ! empty( $field->field_options['post_field'] ) ) {
2945 + if ( ! isset( $field->field_options['custom_field'] ) ) {
2946 + $field->field_options['custom_field'] = '';
2947 + }
2948 +
2949 + $meta_value = FrmProEntryMetaHelper::get_post_value(
2950 + $record->post_id,
2951 + $field->field_options['post_field'],
2952 + $field->field_options['custom_field'],
2953 + array(
2954 + 'truncate' => false,
2955 + 'type' => $field->type,
2956 + 'form_id' => $field->form_id,
2957 + 'field' => $field,
2958 + )
2959 + );
2144 2960 } else {
2145 - if ( $record->post_id && self::pro_is_installed() && isset( $field->field_options['post_field'] ) && $field->field_options['post_field'] ) {
2146 - if ( ! isset( $field->field_options['custom_field'] ) ) {
2147 - $field->field_options['custom_field'] = '';
2148 - }
2149 - $meta_value = FrmProEntryMetaHelper::get_post_value(
2150 - $record->post_id,
2151 - $field->field_options['post_field'],
2152 - $field->field_options['custom_field'],
2153 - array(
2154 - 'truncate' => false,
2155 - 'type' => $field->type,
2156 - 'form_id' => $field->form_id,
2157 - 'field' => $field,
2158 - )
2159 - );
2160 - } else {
2161 - $meta_value = FrmEntryMeta::get_meta_value( $record, $field->id );
2162 - }
2163 - }
2961 + $meta_value = FrmEntryMeta::get_meta_value( $record, $field->id );
2962 + }//end if
2164 2963
2165 - $field_type = isset( $post_values['field_options'][ 'type_' . $field->id ] ) ? $post_values['field_options'][ 'type_' . $field->id ] : $field->type;
2964 + $field_type = $post_values['field_options'][ 'type_' . $field->id ] ?? $field->type;
2965 +
2166 2966 if ( isset( $post_values['item_meta'][ $field->id ] ) ) {
2167 2967 $new_value = $post_values['item_meta'][ $field->id ];
2168 2968 self::unserialize_or_decode( $new_value );
2169 2969 } else {
@@ -2178,9 +2978,9 @@
2178 2978 $args['field_type'] = $field_type;
2179 2979
2180 2980 FrmFieldsHelper::prepare_edit_front_field( $field_array, $field, $values['id'], $args );
2181 2981
2182 - if ( ! isset( $field_array['unique'] ) || ! $field_array['unique'] ) {
2982 + if ( empty( $field_array['unique'] ) ) {
2183 2983 $field_array['unique_msg'] = '';
2184 2984 }
2185 2985
2186 2986 $field_array = array_merge( (array) $field->field_options, $field_array );
@@ -2209,12 +3009,15 @@
2209 3009 );
2210 3010 }
2211 3011
2212 3012 /**
3013 + * @param object $record
2213 3014 * @param string $table
3015 + * @param array $post_values
3016 + * @param array $values
2214 3017 */
2215 3018 private static function fill_form_opts( $record, $table, $post_values, array &$values ) {
2216 - if ( $table == 'entries' ) {
3019 + if ( $table === 'entries' ) {
2217 3020 $form = $record->form_id;
2218 3021 FrmForm::maybe_get_form( $form );
2219 3022 } else {
2220 3023 $form = $record;
@@ -2244,15 +3047,21 @@
2244 3047 }
2245 3048
2246 3049 /**
2247 3050 * Set to POST value or default
3051 + *
3052 + * @param array $post_values
3053 + * @param array $values
3054 + *
3055 + * @return void
2248 3056 */
2249 3057 private static function fill_form_defaults( $post_values, array &$values ) {
2250 3058 $form_defaults = FrmFormsHelper::get_default_opts();
2251 3059
2252 3060 foreach ( $form_defaults as $opt => $default ) {
3061 + // phpcs:ignore Universal.Operators.StrictComparisons
2253 3062 if ( ! isset( $values[ $opt ] ) || $values[ $opt ] == '' ) {
2254 - $values[ $opt ] = ( $post_values && isset( $post_values['options'][ $opt ] ) ) ? $post_values['options'][ $opt ] : $default;
3063 + $values[ $opt ] = $post_values['options'][ $opt ] ?? $default;
2255 3064 }
2256 3065
2257 3066 unset( $opt, $default );
2258 3067 }
@@ -2262,9 +3071,9 @@
2262 3071 }
2263 3072
2264 3073 foreach ( array( 'before', 'after', 'submit' ) as $h ) {
2265 3074 if ( ! isset( $values[ $h . '_html' ] ) ) {
2266 - $values[ $h . '_html' ] = ( isset( $post_values['options'][ $h . '_html' ] ) ? $post_values['options'][ $h . '_html' ] : FrmFormsHelper::get_default_html( $h ) );
3075 + $values[ $h . '_html' ] = $post_values['options'][ $h . '_html' ] ?? FrmFormsHelper::get_default_html( $h );
2267 3076 }
2268 3077 unset( $h );
2269 3078 }
2270 3079 }
@@ -2273,46 +3082,70 @@
2273 3082 * @since 2.2.10
2274 3083 *
2275 3084 * @param array $post_values
2276 3085 *
2277 - * @return boolean|int
3086 + * @return bool|int
2278 3087 */
2279 3088 public static function custom_style_value( $post_values ) {
2280 - if ( ! empty( $post_values ) && isset( $post_values['options']['custom_style'] ) ) {
2281 - $custom_style = absint( $post_values['options']['custom_style'] );
2282 - } else {
2283 - $frm_settings = self::get_settings();
2284 - $custom_style = ( $frm_settings->load_style != 'none' );
3089 + if ( $post_values && isset( $post_values['options']['custom_style'] ) ) {
3090 + return absint( $post_values['options']['custom_style'] );
2285 3091 }
2286 3092
2287 - return $custom_style;
3093 + $frm_settings = self::get_settings();
3094 + return $frm_settings->load_style !== 'none';
2288 3095 }
2289 3096
2290 - public static function truncate( $str, $length, $minword = 3, $continue = '...' ) {
2291 - if ( is_array( $str ) ) {
3097 + /**
3098 + * Truncate a string.
3099 + *
3100 + * Note: By default, this function will allow for a few additional characters more than $length.
3101 + * If a string has no spaces, it allows up to 50 additional characters. To force a true length limit,
3102 + * use $force_length_limit = true.
3103 + *
3104 + * @param mixed $original_string
3105 + * @param int|string $length
3106 + * @param int $minword
3107 + * @param string $continue
3108 + * @param bool $force_length_limit Force the final string to never exceed the length limit.
3109 + *
3110 + * @return string
3111 + */
3112 + public static function truncate( $original_string, $length, $minword = 3, $continue = '...', $force_length_limit = false ) {
3113 + if ( ! is_string( $original_string ) && ! is_int( $original_string ) ) {
2292 3114 return '';
2293 3115 }
2294 3116
2295 - $length = (int) $length;
2296 - $str = wp_strip_all_tags( $str );
3117 + $length = (int) $length;
3118 +
3119 + if ( $length === 0 ) {
3120 + return '';
3121 + }
3122 +
3123 + $str = wp_strip_all_tags( (string) $original_string );
2297 3124 $original_len = self::mb_function( array( 'mb_strlen', 'strlen' ), array( $str ) );
2298 3125
2299 - if ( $length == 0 ) {
2300 - return '';
2301 - } elseif ( $length <= 10 ) {
3126 + if ( $length <= 10 ) {
2302 3127 $sub = self::mb_function( array( 'mb_substr', 'substr' ), array( $str, 0, $length ) );
2303 3128
2304 - return $sub . ( ( $length < $original_len ) ? $continue : '' );
3129 + if ( $force_length_limit ) {
3130 + return $sub;
3131 + }
3132 +
3133 + return $sub . ( $length < $original_len ? $continue : '' );
2305 3134 }
2306 3135
2307 - $sub = '';
2308 - $len = 0;
3136 + $sub = '';
3137 + $len = 0;
3138 + $words = self::mb_function( array( 'mb_split', 'explode' ), array( ' ', $str ) );
2309 3139
2310 - $words = self::mb_function( array( 'mb_split', 'explode' ), array( ' ', $str ) );
3140 + if ( ! is_array( $words ) ) {
3141 + return $original_string;
3142 + }
2311 3143
2312 3144 foreach ( $words as $word ) {
2313 - $part = ( ( $sub != '' ) ? ' ' : '' ) . $word;
3145 + $part = ( $sub !== '' ? ' ' : '' ) . $word;
2314 3146 $total_len = self::mb_function( array( 'mb_strlen', 'strlen' ), array( $sub . $part ) );
3147 +
2315 3148 if ( $total_len > $length && substr_count( $sub, ' ' ) ) {
2316 3149 break;
2317 3150 }
2318 3151
@@ -2325,14 +3158,72 @@
2325 3158
2326 3159 unset( $total_len, $word );
2327 3160 }
2328 3161
2329 - return $sub . ( ( $len < $original_len ) ? $continue : '' );
3162 + $sub = self::maybe_force_truncate_on_string_with_no_spaces( $sub, $length, $force_length_limit );
3163 +
3164 + if ( $force_length_limit ) {
3165 + // Ensure the final string doesn't exceed the length limit.
3166 + $final_len = self::mb_function( array( 'mb_strlen', 'strlen' ), array( $sub ) );
3167 +
3168 + if ( $final_len > $length ) {
3169 + return self::mb_function( array( 'mb_substr', 'substr' ), array( $sub, 0, $length ) );
3170 + }
3171 +
3172 + return $sub;
3173 + }
3174 +
3175 + return $sub . ( $len < $original_len ? $continue : '' );
2330 3176 }
2331 3177
3178 + /**
3179 + * If the string is still too long because there may not have been any spaces, force truncate.
3180 + *
3181 + * @since 6.5.4
3182 + *
3183 + * @param string $sub Current substring.
3184 + * @param int $length The length limit.
3185 + * @param bool $force_length_limit Force the string to not exceed the length limit.
3186 + *
3187 + * @return string
3188 + */
3189 + private static function maybe_force_truncate_on_string_with_no_spaces( $sub, $length, $force_length_limit = false ) {
3190 + if ( ! $force_length_limit ) {
3191 + if ( strlen( $sub ) < $length + 50 ) {
3192 + // If the string isn't way over the limit, leave it.
3193 + return $sub;
3194 + }
3195 +
3196 + $first_space = strpos( $sub, ' ', $length );
3197 +
3198 + if ( false !== $first_space ) {
3199 + // Ignore anything with spaces.
3200 + return $sub;
3201 + }
3202 +
3203 + return substr( $sub, 0, $length + 10 );
3204 + }
3205 +
3206 + // When force_length_limit is true, ensure the string doesn't exceed the length.
3207 + $final_len = self::mb_function( array( 'mb_strlen', 'strlen' ), array( $sub ) );
3208 +
3209 + if ( $final_len > $length ) {
3210 + return self::mb_function( array( 'mb_substr', 'substr' ), array( $sub, 0, $length ) );
3211 + }
3212 +
3213 + return $sub;
3214 + }
3215 +
3216 + /**
3217 + * @param array $function_names
3218 + * @param array $args
3219 + *
3220 + * @return mixed
3221 + */
2332 3222 public static function mb_function( $function_names, $args ) {
2333 3223 $mb_function_name = $function_names[0];
2334 3224 $function_name = $function_names[1];
3225 +
2335 3226 if ( function_exists( $mb_function_name ) ) {
2336 3227 $function_name = $mb_function_name;
2337 3228 }
2338 3229
@@ -2338,14 +3229,21 @@
2338 3229
2339 3230 return call_user_func_array( $function_name, $args );
2340 3231 }
2341 3232
3233 + /**
3234 + * @param string $date
3235 + * @param string $date_format
3236 + * @param string $time_format
3237 + *
3238 + * @return string
3239 + */
2342 3240 public static function get_formatted_time( $date, $date_format = '', $time_format = '' ) {
2343 - if ( empty( $date ) ) {
3241 + if ( ! $date ) {
2344 3242 return $date;
2345 3243 }
2346 3244
2347 - if ( empty( $date_format ) ) {
3245 + if ( ! $date_format ) {
2348 3246 $date_format = get_option( 'date_format' );
2349 3247 }
2350 3248
2351 3249 if ( preg_match( '/^\d{1-2}\/\d{1-2}\/\d{4}$/', $date ) && self::pro_is_installed() ) {
@@ -2353,10 +3251,10 @@
2353 3251 $date = FrmProAppHelper::convert_date( $date, $frmpro_settings->date_format, 'Y-m-d' );
2354 3252 }
2355 3253
2356 3254 $formatted = self::get_localized_date( $date_format, $date );
3255 + $do_time = gmdate( 'H:i:s', strtotime( $date ) ) !== '00:00:00';
2357 3256
2358 - $do_time = ( gmdate( 'H:i:s', strtotime( $date ) ) != '00:00:00' );
2359 3257 if ( $do_time ) {
2360 3258 $formatted .= self::add_time_to_date( $time_format, $date );
2361 3259 }
2362 3260
@@ -2362,45 +3260,52 @@
2362 3260
2363 3261 return $formatted;
2364 3262 }
2365 3263
3264 + /**
3265 + * @param string $time_format
3266 + * @param string $date
3267 + *
3268 + * @return string
3269 + */
2366 3270 private static function add_time_to_date( $time_format, $date ) {
2367 - if ( empty( $time_format ) ) {
3271 + if ( ! $time_format ) {
2368 3272 $time_format = get_option( 'time_format' );
2369 3273 }
2370 3274
2371 3275 $trimmed_format = trim( $time_format );
2372 - $time = '';
2373 - if ( $time_format && ! empty( $trimmed_format ) ) {
2374 - $time = ' ' . __( 'at', 'formidable' ) . ' ' . self::get_localized_date( $time_format, $date );
3276 +
3277 + if ( $time_format && $trimmed_format ) {
3278 + return ' ' . __( 'at', 'formidable' ) . ' ' . self::get_localized_date( $time_format, $date );
2375 3279 }
2376 3280
2377 - return $time;
3281 + return '';
2378 3282 }
2379 3283
2380 3284 /**
2381 3285 * @since 2.0.8
3286 + *
3287 + * @param string $date_format
3288 + * @param string $date
3289 + *
3290 + * @return string
2382 3291 */
2383 3292 public static function get_localized_date( $date_format, $date ) {
2384 3293 $date = get_date_from_gmt( $date );
2385 -
2386 3294 return date_i18n( $date_format, strtotime( $date ) );
2387 3295 }
2388 3296
2389 3297 /**
2390 - * Gets the time ago in words
3298 + * Gets the time ago in words.
2391 3299 *
2392 - * @param int $from in seconds
2393 - * @param int|string $to in seconds
3300 + * @param int $from In seconds.
3301 + * @param int|string $to In seconds.
3302 + * @param int|string $levels Number of time units to include or a specific unit.
2394 3303 *
2395 - * @return string $time_ago
3304 + * @return string Time ago.
2396 3305 */
2397 3306 public static function human_time_diff( $from, $to = '', $levels = 1 ) {
2398 - if ( empty( $to ) && 0 !== $to ) {
2399 - $now = new DateTime();
2400 - } else {
2401 - $now = new DateTime( '@' . $to );
2402 - }
3307 + $now = ! $to && 0 !== $to ? new DateTime() : new DateTime( '@' . $to );
2403 3308 $ago = new DateTime( '@' . $from );
2404 3309
2405 3310 // Get the time difference
2406 3311 $diff_object = $now->diff( $ago );
@@ -2406,9 +3311,9 @@
2406 3311 $diff_object = $now->diff( $ago );
2407 3312 $diff = get_object_vars( $diff_object );
2408 3313
2409 3314 // Add week amount and update day amount
2410 - $diff['w'] = floor( $diff['d'] / 7 );
3315 + $diff['w'] = floor( $diff['d'] / 7 );
2411 3316 $diff['d'] -= $diff['w'] * 7;
2412 3317
2413 3318 $time_strings = self::get_time_strings();
2414 3319
@@ -2414,10 +3319,11 @@
2414 3319
2415 3320 if ( ! is_numeric( $levels ) ) {
2416 3321 // Show time in specified unit.
2417 3322 $levels = self::get_unit( $levels );
3323 +
2418 3324 if ( isset( $time_strings[ $levels ] ) ) {
2419 - $diff = array(
3325 + $diff = array(
2420 3326 $levels => self::time_format( $levels, $diff ),
2421 3327 );
2422 3328 $time_strings = array(
2423 3329 $levels => $time_strings[ $levels ],
@@ -2422,13 +3328,14 @@
2422 3328 $time_strings = array(
2423 3329 $levels => $time_strings[ $levels ],
2424 3330 );
2425 3331 }
3332 +
2426 3333 $levels = 1;
2427 3334 }
2428 3335
2429 3336 foreach ( $time_strings as $k => $v ) {
2430 - if ( isset( $diff[ $k ] ) && $diff[ $k ] ) {
3337 + if ( ! empty( $diff[ $k ] ) ) {
2431 3338 $time_strings[ $k ] = $diff[ $k ] . ' ' . ( $diff[ $k ] > 1 ? $v[1] : $v[0] );
2432 3339 } elseif ( isset( $diff[ $k ] ) && count( $time_strings ) === 1 ) {
2433 3340 // Account for 0.
2434 3341 $time_strings[ $k ] = $diff[ $k ] . ' ' . $v[1];
@@ -2436,17 +3343,21 @@
2436 3343 unset( $time_strings[ $k ] );
2437 3344 }
2438 3345 }
2439 3346
2440 - $levels_deep = apply_filters( 'frm_time_ago_levels', $levels, compact( 'time_strings', 'from', 'to' ) );
2441 - $time_strings = array_slice( $time_strings, 0, absint( $levels_deep ) );
2442 - $time_ago_string = implode( ' ', $time_strings );
3347 + $levels_deep = apply_filters( 'frm_time_ago_levels', $levels, compact( 'time_strings', 'from', 'to' ) );
3348 + $time_strings = array_slice( $time_strings, 0, absint( $levels_deep ) );
2443 3349
2444 - return $time_ago_string;
3350 + return implode( ' ', $time_strings );
2445 3351 }
2446 3352
2447 3353 /**
2448 3354 * @since 4.05.01
3355 + *
3356 + * @param string $unit
3357 + * @param array $diff
3358 + *
3359 + * @return int
2449 3360 */
2450 3361 private static function time_format( $unit, $diff ) {
2451 3362 $return = array(
2452 3363 'y' => 'y',
@@ -2451,14 +3362,14 @@
2451 3362 $return = array(
2452 3363 'y' => 'y',
2453 3364 'd' => 'days',
2454 3365 );
3366 +
2455 3367 if ( isset( $return[ $unit ] ) ) {
2456 3368 return $diff[ $return[ $unit ] ];
2457 3369 }
2458 3370
2459 3371 $total = $diff['days'] * self::convert_time( 'd', $unit );
2460 -
2461 3372 $times = array( 'h', 'i', 's' );
2462 3373
2463 3374 foreach ( $times as $time ) {
2464 3375 if ( ! isset( $diff[ $time ] ) ) {
@@ -2472,8 +3383,13 @@
2472 3383 }
2473 3384
2474 3385 /**
2475 3386 * @since 4.05.01
3387 + *
3388 + * @param string $from
3389 + * @param string $to
3390 + *
3391 + * @return int
2476 3392 */
2477 3393 private static function convert_time( $from, $to ) {
2478 3394 $convert = array(
2479 3395 's' => 1,
@@ -2489,28 +3405,35 @@
2489 3405 }
2490 3406
2491 3407 /**
2492 3408 * @since 4.05.01
3409 + *
3410 + * @param string $unit
3411 + *
3412 + * @return int|string
2493 3413 */
2494 3414 private static function get_unit( $unit ) {
2495 3415 $units = self::get_time_strings();
3416 +
2496 3417 if ( isset( $units[ $unit ] ) || is_numeric( $unit ) ) {
2497 3418 return $unit;
2498 3419 }
2499 3420
2500 3421 foreach ( $units as $u => $strings ) {
2501 - if ( in_array( $unit, $strings ) ) {
3422 + if ( in_array( $unit, $strings, true ) ) {
2502 3423 return $u;
2503 3424 }
2504 3425 }
3426 +
2505 3427 return 1;
2506 3428 }
2507 3429
2508 3430 /**
2509 3431 * Get the translatable time strings. The untranslated version is a failsafe
2510 - * in case langauges are changing for the unit set in the shortcode.
3432 + * in case languages are changing for the unit set in the shortcode.
2511 3433 *
2512 3434 * @since 2.0.20
3435 + *
2513 3436 * @return array
2514 3437 */
2515 3438 private static function get_time_strings() {
2516 3439 return array(
@@ -2554,35 +3477,48 @@
2554 3477
2555 3478 // Pagination Methods.
2556 3479
2557 3480 /**
2558 - * @param integer $current_p
3481 + * @param int $r_count
3482 + * @param int $current_p
3483 + * @param int $p_size
3484 + *
3485 + * @return int
2559 3486 */
2560 3487 public static function get_last_record_num( $r_count, $current_p, $p_size ) {
2561 - return ( ( $r_count < ( $current_p * $p_size ) ) ? $r_count : ( $current_p * $p_size ) );
3488 + return $r_count < $current_p * $p_size ? $r_count : $current_p * $p_size;
2562 3489 }
2563 3490
2564 3491 /**
2565 - * @param integer $current_p
3492 + * @param int $r_count
3493 + * @param int $current_p
3494 + * @param int $p_size
3495 + *
3496 + * @return int
2566 3497 */
2567 3498 public static function get_first_record_num( $r_count, $current_p, $p_size ) {
3499 + // phpcs:ignore Universal.Operators.StrictComparisons
2568 3500 if ( $current_p == 1 ) {
2569 3501 return 1;
2570 - } else {
2571 - return ( self::get_last_record_num( $r_count, ( $current_p - 1 ), $p_size ) + 1 );
2572 3502 }
3503 + return self::get_last_record_num( $r_count, $current_p - 1, $p_size ) + 1;
2573 3504 }
2574 3505
2575 3506 /**
3507 + * @param array $json_vars
3508 + *
2576 3509 * @return array
2577 3510 */
2578 3511 public static function json_to_array( $json_vars ) {
2579 3512 $vars = array();
3513 +
2580 3514 foreach ( $json_vars as $jv ) {
2581 3515 $jv_name = explode( '[', $jv['name'] );
2582 3516 $last = count( $jv_name ) - 1;
3517 +
2583 3518 foreach ( $jv_name as $p => $n ) {
2584 3519 $name = trim( $n, ']' );
3520 +
2585 3521 if ( ! isset( $l1 ) ) {
2586 3522 $l1 = $name;
2587 3523 }
2588 3524
@@ -2593,9 +3529,9 @@
2593 3529 if ( ! isset( $l3 ) ) {
2594 3530 $l3 = $name;
2595 3531 }
2596 3532
2597 - $this_val = ( $p == $last ) ? $jv['value'] : array();
3533 + $this_val = $p === $last ? $jv['value'] : array();
2598 3534
2599 3535 switch ( $p ) {
2600 3536 case 0:
2601 3537 $l1 = $name;
@@ -2617,12 +3553,12 @@
2617 3553 self::add_value_to_array( $name, $l4, $this_val, $vars[ $l1 ][ $l2 ][ $l3 ] );
2618 3554 }
2619 3555
2620 3556 unset( $this_val, $n );
2621 - }
3557 + }//end foreach
2622 3558
2623 3559 unset( $last, $jv );
2624 - }
3560 + }//end foreach
2625 3561
2626 3562 return $vars;
2627 3563 }
2628 3564
@@ -2628,10 +3564,15 @@
2628 3564
2629 3565 /**
2630 3566 * @param string $name
2631 3567 * @param string $l1
3568 + * @param mixed $val
3569 + * @param array $vars
3570 + *
3571 + * @return void
2632 3572 */
2633 3573 public static function add_value_to_array( $name, $l1, $val, &$vars ) {
3574 + // phpcs:ignore Universal.Operators.StrictComparisons
2634 3575 if ( $name == '' ) {
2635 3576 $vars[] = $val;
2636 3577 } elseif ( ! isset( $vars[ $l1 ] ) ) {
2637 3578 $vars[ $l1 ] = $val;
@@ -2637,19 +3578,26 @@
2637 3578 $vars[ $l1 ] = $val;
2638 3579 }
2639 3580 }
2640 3581
3582 + /**
3583 + * @param string $name
3584 + * @param string $class
3585 + * @param string $form_name
3586 + *
3587 + * @return void
3588 + */
2641 3589 public static function maybe_add_tooltip( $name, $class = 'closed', $form_name = '' ) {
2642 3590 $tooltips = array(
2643 3591 'action_title' => __( 'Give this action a label for easy reference.', 'formidable' ),
2644 - 'email_to' => __( 'Add one or more recipient addresses separated by a ",". FORMAT: Name <[email protected]> or [email protected]. [admin_email] is the address set in WP General Settings.', 'formidable' ),
3592 + 'email_to' => __( 'Add one or more recipient addresses separated by a ",". FORMAT: Name <[email protected]> or [email protected]. [default-email] is the address set in the global "Default Email Address" settings.', 'formidable' ), // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
2645 3593 'cc' => __( 'Add CC addresses separated by a ",". FORMAT: Name <[email protected]> or [email protected].', 'formidable' ),
2646 3594 'bcc' => __( 'Add BCC addresses separated by a ",". FORMAT: Name <[email protected]> or [email protected].', 'formidable' ),
2647 - 'reply_to' => __( 'If you would like a different reply to address than the "from" address, add a single address here. FORMAT: Name <[email protected]> or [email protected].', 'formidable' ),
3595 + 'reply_to' => __( 'If you would like a different reply to address than the "from" address, add a single address here. FORMAT: Name <[email protected]> or [email protected].', 'formidable' ), // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
2648 3596 'from' => __( 'Enter the name and/or email address of the sender. FORMAT: John Bates <[email protected]> or [email protected].', 'formidable' ),
2649 3597 /* translators: %1$s: Form name, %2$s: Date */
2650 - 'email_subject' => esc_attr( sprintf( __( 'If you leave the subject blank, the default will be used: %1$s Form submitted on %2$s', 'formidable' ), $form_name, self::site_name() ) ),
2651 - 'new_tab' => __( 'This option will open the link in a new browser tab. Please note that some popup blockers may prevent this from happening, in which case the link will be displayed.', 'formidable' ),
3598 + 'email_subject' => esc_attr( sprintf( __( 'If you leave the subject blank, the default will be used: %1$s Form submitted on %2$s', 'formidable' ), $form_name, self::site_name() ) ), // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
3599 + 'new_tab' => __( 'This option will open the link in a new browser tab. Please note that some popup blockers may prevent this from happening, in which case the link will be displayed.', 'formidable' ), // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
2652 3600 );
2653 3601
2654 3602 if ( ! isset( $tooltips[ $name ] ) ) {
2655 3603 return;
@@ -2654,9 +3602,9 @@
2654 3602 if ( ! isset( $tooltips[ $name ] ) ) {
2655 3603 return;
2656 3604 }
2657 3605
2658 - if ( 'open' == $class ) {
3606 + if ( 'open' === $class ) {
2659 3607 echo ' frm_help"';
2660 3608 } else {
2661 3609 echo ' class="frm_help"';
2662 3610 }
@@ -2662,9 +3610,9 @@
2662 3610 }
2663 3611
2664 3612 echo ' title="' . esc_attr( $tooltips[ $name ] );
2665 3613
2666 - if ( 'open' != $class ) {
3614 + if ( 'open' !== $class ) {
2667 3615 echo '"';
2668 3616 }
2669 3617 }
2670 3618
@@ -2669,20 +3617,28 @@
2669 3617 }
2670 3618
2671 3619 /**
2672 3620 * Add the current_page class to that page in the form nav
3621 + *
3622 + * @param int|string $page
3623 + * @param int|string $current_page
3624 + * @param array $action
3625 + *
3626 + * @return void
2673 3627 */
2674 3628 public static function select_current_page( $page, $current_page, $action = array() ) {
3629 + // phpcs:ignore Universal.Operators.StrictComparisons
2675 3630 if ( $current_page != $page ) {
2676 3631 return;
2677 3632 }
2678 3633
2679 3634 $frm_action = self::simple_get( 'frm_action', 'sanitize_title' );
3635 +
2680 3636 if ( 'lite-reports' === $frm_action ) {
2681 3637 $frm_action = 'reports';
2682 3638 }
2683 3639
2684 - if ( empty( $action ) || ( ! empty( $frm_action ) && in_array( $frm_action, $action ) ) ) {
3640 + if ( ! $action || ( $frm_action && in_array( $frm_action, $action, true ) ) ) {
2685 3641 echo ' class="current_page"';
2686 3642 }
2687 3643 }
2688 3644
@@ -2712,14 +3668,19 @@
2712 3668
2713 3669 // Add extra slashes for \r\n since WP strips them.
2714 3670 $post_content = str_replace( array( '\\r', '\\n', '\\u', '\\t' ), array( '\\\\r', '\\\\n', '\\\\u', '\\\\t' ), $post_content );
2715 3671
2716 - // allow for &quot
2717 - $post_content = str_replace( '&quot;', '\\"', $post_content );
2718 -
2719 - return $post_content;
3672 + // Allow for &quot
3673 + return str_replace( '&quot;', '\\"', $post_content );
2720 3674 }
2721 3675
3676 + /**
3677 + * @param array|string $val
3678 + * @param int|string $key
3679 + * @param array $post_content
3680 + *
3681 + * @return void
3682 + */
2722 3683 private static function prepare_action_slashes( $val, $key, &$post_content ) {
2723 3684 if ( ! isset( $post_content[ $key ] ) || is_numeric( $val ) ) {
2724 3685 return;
2725 3686 }
@@ -2728,15 +3689,17 @@
2728 3689 foreach ( $val as $k1 => $v1 ) {
2729 3690 self::prepare_action_slashes( $v1, $k1, $post_content[ $key ] );
2730 3691 unset( $k1, $v1 );
2731 3692 }
2732 - } else {
2733 - // Strip all slashes so everything is the same, no matter where the value is coming from
2734 - $val = stripslashes( $val );
2735 3693
2736 - // Add backslashes before double quotes and forward slashes only
2737 - $post_content[ $key ] = addcslashes( $val, '"\\/' );
3694 + return;
2738 3695 }
3696 +
3697 + // Strip all slashes so everything is the same, no matter where the value is coming from
3698 + $val = stripslashes( $val );
3699 +
3700 + // Add backslashes before double quotes and forward slashes only
3701 + $post_content[ $key ] = addcslashes( $val, '"\\/' );
2739 3702 }
2740 3703
2741 3704 /**
2742 3705 * Check for either json or serialized data. This is temporary while transitioning
@@ -2744,8 +3707,9 @@
2744 3707 *
2745 3708 * @since 4.02.03
2746 3709 *
2747 3710 * @param array|string $value
3711 + *
2748 3712 * @return void
2749 3713 */
2750 3714 public static function unserialize_or_decode( &$value ) {
2751 3715 if ( is_array( $value ) ) {
@@ -2751,13 +3715,9 @@
2751 3715 if ( is_array( $value ) ) {
2752 3716 return;
2753 3717 }
2754 3718
2755 - if ( is_serialized( $value ) ) {
2756 - $value = self::maybe_unserialize_array( $value );
2757 - } else {
2758 - $value = self::maybe_json_decode( $value, false );
2759 - }
3719 + $value = is_serialized( $value ) ? self::maybe_unserialize_array( $value ) : self::maybe_json_decode( $value, false );
2760 3720 }
2761 3721
2762 3722 /**
2763 3723 * Safely unserialize an array if necessary.
@@ -2765,8 +3725,9 @@
2765 3725 *
2766 3726 * @since 6.2
2767 3727 *
2768 3728 * @param mixed $value
3729 + *
2769 3730 * @return mixed
2770 3731 */
2771 3732 public static function maybe_unserialize_array( $value ) {
2772 3733 if ( ! is_string( $value ) ) {
@@ -2773,18 +3734,15 @@
2773 3734 return $value;
2774 3735 }
2775 3736
2776 3737 // Since we only expect an array, skip anything that doesn't start with a:.
2777 - if ( ! is_serialized( $value ) || 'a:' !== substr( $value, 0, 2 ) ) {
3738 + if ( ! is_serialized( $value ) || ! str_starts_with( $value, 'a:' ) ) {
2778 3739 return $value;
2779 3740 }
2780 3741
2781 3742 $parsed = FrmSerializedStringParserHelper::get()->parse( $value );
2782 - if ( is_array( $parsed ) ) {
2783 - $value = $parsed;
2784 - }
2785 3743
2786 - return $value;
3744 + return is_array( $parsed ) ? $parsed : $value;
2787 3745 }
2788 3746
2789 3747 /**
2790 3748 * Decode a JSON string.
@@ -2789,11 +3747,12 @@
2789 3747 /**
2790 3748 * Decode a JSON string.
2791 3749 * Do not switch shortcodes like [24] to array unless intentional ie XML values.
2792 3750 *
2793 - * @param mixed $string
2794 - * @param bool $single_to_array
2795 - * @return mixed
3751 + * @param array|string|null $string
3752 + * @param bool $single_to_array
3753 + *
3754 + * @return array|string|null
2796 3755 */
2797 3756 public static function maybe_json_decode( $string, $single_to_array = true ) {
2798 3757 if ( is_array( $string ) || is_null( $string ) ) {
2799 3758 return $string;
@@ -2798,20 +3757,19 @@
2798 3757 if ( is_array( $string ) || is_null( $string ) ) {
2799 3758 return $string;
2800 3759 }
2801 3760
2802 - $new_string = json_decode( $string, true );
2803 - if ( function_exists( 'json_last_error' ) ) {
2804 - // php 5.3+
2805 - $single_value = false;
2806 - if ( ! $single_to_array ) {
2807 - $single_value = is_array( $new_string ) && count( $new_string ) === 1 && isset( $new_string[0] );
2808 - }
2809 - if ( json_last_error() == JSON_ERROR_NONE && is_array( $new_string ) && ! $single_value ) {
2810 - $string = $new_string;
2811 - }
3761 + $new_string = json_decode( $string, true );
3762 + $single_value = false;
3763 +
3764 + if ( ! $single_to_array ) {
3765 + $single_value = is_array( $new_string ) && count( $new_string ) === 1 && isset( $new_string[0] );
2812 3766 }
2813 3767
3768 + if ( json_last_error() === JSON_ERROR_NONE && is_array( $new_string ) && ! $single_value ) {
3769 + return $new_string;
3770 + }
3771 +
2814 3772 return $string;
2815 3773 }
2816 3774
2817 3775 /**
@@ -2817,8 +3775,9 @@
2817 3775 /**
2818 3776 * @since 6.2.3
2819 3777 *
2820 3778 * @param string $value
3779 + *
2821 3780 * @return string
2822 3781 */
2823 3782 public static function maybe_utf8_encode( $value ) {
2824 3783 $from_format = 'ISO-8859-1';
@@ -2827,13 +3786,15 @@
2827 3786 if ( function_exists( 'mb_check_encoding' ) && function_exists( 'mb_convert_encoding' ) ) {
2828 3787 if ( mb_check_encoding( $value, $from_format ) ) {
2829 3788 return mb_convert_encoding( $value, $to_format, $from_format );
2830 3789 }
3790 +
2831 3791 return $value;
2832 3792 }
2833 3793
2834 3794 if ( function_exists( 'iconv' ) ) {
2835 3795 $converted = iconv( $from_format, $to_format, $value );
3796 +
2836 3797 // Value is false if $value is not ISO-8859-1.
2837 3798 if ( false !== $converted ) {
2838 3799 return $converted;
2839 3800 }
@@ -2845,8 +3806,12 @@
2845 3806 /**
2846 3807 * Reformat the json serialized array in name => value array.
2847 3808 *
2848 3809 * @since 4.02.03
3810 + *
3811 + * @param array $form
3812 + *
3813 + * @return void
2849 3814 */
2850 3815 public static function format_form_data( &$form ) {
2851 3816 $formatted = array();
2852 3817
@@ -2853,17 +3818,20 @@
2853 3818 foreach ( $form as $input ) {
2854 3819 if ( ! isset( $input['name'] ) ) {
2855 3820 continue;
2856 3821 }
3822 +
2857 3823 $key = $input['name'];
2858 - if ( isset( $formatted[ $key ] ) ) {
2859 - if ( is_array( $formatted[ $key ] ) ) {
2860 - $formatted[ $key ][] = $input['value'];
2861 - } else {
2862 - $formatted[ $key ] = array( $formatted[ $key ], $input['value'] );
2863 - }
3824 +
3825 + if ( ! isset( $formatted[ $key ] ) ) {
3826 + $formatted[ $key ] = $input['value'];
3827 + continue;
3828 + }
3829 +
3830 + if ( is_array( $formatted[ $key ] ) ) {
3831 + $formatted[ $key ][] = $input['value'];
2864 3832 } else {
2865 - $formatted[ $key ] = $input['value'];
3833 + $formatted[ $key ] = array( $formatted[ $key ], $input['value'] );
2866 3834 }
2867 3835 }
2868 3836
2869 3837 parse_str( http_build_query( $formatted ), $form );
@@ -2870,30 +3838,34 @@
2870 3838 }
2871 3839
2872 3840 /**
2873 3841 * @since 4.02.03
3842 + *
3843 + * @param array|string $value
3844 + *
3845 + * @return string
2874 3846 */
2875 3847 public static function maybe_json_encode( $value ) {
2876 - if ( is_array( $value ) ) {
2877 - $value = wp_json_encode( $value );
2878 - }
2879 - return $value;
3848 + return is_array( $value ) ? wp_json_encode( $value ) : $value;
2880 3849 }
2881 3850
2882 3851 /**
3852 + * Echo The javascript to open and highlight the Formidable menu
3853 + *
2883 3854 * @since 1.07.10
2884 3855 *
2885 - * @param string $post_type The name of the post type that may need to be highlighted
2886 - * echo The javascript to open and highlight the Formidable menu
3856 + * @param string $post_type The name of the post type that may need to be highlighted.
3857 + *
3858 + * @return void
2887 3859 */
2888 3860 public static function maybe_highlight_menu( $post_type ) {
2889 3861 global $post;
2890 3862
2891 - if ( isset( $_REQUEST['post_type'] ) && $_REQUEST['post_type'] != $post_type ) {
3863 + if ( isset( $_REQUEST['post_type'] ) && $_REQUEST['post_type'] !== $post_type ) {
2892 3864 return;
2893 3865 }
2894 3866
2895 - if ( is_object( $post ) && $post->post_type != $post_type ) {
3867 + if ( is_object( $post ) && $post->post_type !== $post_type ) {
2896 3868 return;
2897 3869 }
2898 3870
2899 3871 self::load_admin_wide_js();
@@ -2903,12 +3875,15 @@
2903 3875 /**
2904 3876 * Load the JS file on non-Formidable pages in the admin area
2905 3877 *
2906 3878 * @since 2.0
3879 + *
3880 + * @param bool $load
3881 + *
3882 + * @return void
2907 3883 */
2908 3884 public static function load_admin_wide_js( $load = true ) {
2909 - $version = self::plugin_version();
2910 - wp_register_script( 'formidable_admin_global', self::plugin_url() . '/js/formidable_admin_global.js', array( 'jquery' ), $version );
3885 + wp_register_script( 'formidable_admin_global', self::plugin_url() . '/js/formidable_admin_global.js', array( 'jquery' ), self::plugin_version() );
2911 3886
2912 3887 $global_strings = array(
2913 3888 'updating_msg' => __( 'Please wait while your site updates.', 'formidable' ),
2914 3889 'deauthorize' => __( 'Are you sure you want to deauthorize Formidable Forms on this site?', 'formidable' ),
@@ -2914,12 +3889,13 @@
2914 3889 'deauthorize' => __( 'Are you sure you want to deauthorize Formidable Forms on this site?', 'formidable' ),
2915 3890 'url' => self::plugin_url(),
2916 3891 'app_url' => 'https://formidableforms.com/',
2917 3892 'applicationsUrl' => admin_url( 'admin.php?page=formidable-applications' ),
2918 - 'canAccessApplicationDashboard' => current_user_can( is_callable( 'FrmProApplicationsHelper::get_required_templates_capability' ) ? FrmProApplicationsHelper::get_required_templates_capability() : 'frm_edit_forms' ),
3893 + 'canAccessApplicationDashboard' => current_user_can( is_callable( 'FrmProApplicationsHelper::get_required_templates_capability' ) ? FrmProApplicationsHelper::get_required_templates_capability() : 'frm_edit_forms' ), // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
2919 3894 'loading' => __( 'Loading&hellip;', 'formidable' ),
2920 3895 'nonce' => wp_create_nonce( 'frm_ajax' ),
2921 3896 'proIncludesSliderJs' => is_callable( 'FrmProFormsHelper::prepare_custom_currency' ),
3897 + 'inboxSlideIn' => FrmInbox::get_inbox_slide_in_value_for_js(),
2922 3898 );
2923 3899 wp_localize_script( 'formidable_admin_global', 'frmGlobal', $global_strings );
2924 3900
2925 3901 if ( $load ) {
@@ -2928,8 +3904,10 @@
2928 3904 }
2929 3905
2930 3906 /**
2931 3907 * @since 2.0.9
3908 + *
3909 + * @return void
2932 3910 */
2933 3911 public static function load_font_style() {
2934 3912 wp_enqueue_style( 'frm_fonts', self::plugin_url() . '/css/frm_fonts.css', array(), self::plugin_version() );
2935 3913 }
@@ -2935,99 +3913,224 @@
2935 3913 }
2936 3914
2937 3915 /**
2938 3916 * @param string $location
3917 + *
3918 + * @return void
2939 3919 */
2940 3920 public static function localize_script( $location ) {
2941 - global $wp_scripts;
3921 + global $wp_scripts, $wp_version;
2942 3922
2943 - $ajax_url = admin_url( 'admin-ajax.php', is_ssl() ? 'admin' : 'http' );
2944 - $ajax_url = apply_filters( 'frm_ajax_url', $ajax_url );
2945 -
2946 3923 $script_strings = array(
2947 - 'ajax_url' => $ajax_url,
2948 - 'images_url' => self::plugin_url() . '/images',
2949 - 'loading' => __( 'Loading&hellip;', 'formidable' ),
2950 - 'remove' => __( 'Remove', 'formidable' ),
2951 - 'offset' => apply_filters( 'frm_scroll_offset', 4 ),
2952 - 'nonce' => wp_create_nonce( 'frm_ajax' ),
2953 - 'id' => __( 'ID', 'formidable' ),
2954 - 'no_results' => __( 'No results match', 'formidable' ),
2955 - 'file_spam' => __( 'That file looks like Spam.', 'formidable' ),
2956 - 'calc_error' => __( 'There is an error in the calculation in the field with key', 'formidable' ),
2957 - 'empty_fields' => __( 'Please complete the preceding required fields before uploading a file.', 'formidable' ),
2958 - 'focus_first_error' => self::should_focus_first_error(),
2959 - 'include_alert_role' => self::should_include_alert_role_on_field_errors(),
3924 + 'ajax_url' => esc_url_raw( self::get_ajax_url() ),
3925 + 'images_url' => self::plugin_url() . '/images',
3926 + 'loading' => __( 'Loading&hellip;', 'formidable' ),
3927 + 'remove' => __( 'Remove', 'formidable' ),
3928 + 'offset' => apply_filters( 'frm_scroll_offset', 4 ),
3929 + 'nonce' => wp_create_nonce( 'frm_ajax' ),
3930 + 'id' => __( 'ID', 'formidable' ),
3931 + 'no_results' => __( 'No results match', 'formidable' ),
3932 + 'file_spam' => __( 'That file looks like Spam.', 'formidable' ),
3933 + 'calc_error' => __( 'There is an error in the calculation in the field with key', 'formidable' ),
3934 + 'empty_fields' => __( 'Please complete the preceding required fields before uploading a file.', 'formidable' ),
3935 + 'focus_first_error' => self::should_focus_first_error(),
3936 + 'include_alert_role' => self::should_include_alert_role_on_field_errors(),
3937 + // We need to keep this setting for a few versions because Pro checks for this.
3938 + 'include_resend_email' => false,
2960 3939 );
2961 3940
2962 3941 $data = $wp_scripts->get_data( 'formidable', 'data' );
2963 - if ( empty( $data ) ) {
3942 +
3943 + if ( ! $data ) {
2964 3944 wp_localize_script( 'formidable', 'frm_js', $script_strings );
2965 3945 }
2966 3946
2967 - if ( $location == 'admin' ) {
2968 - $frm_settings = self::get_settings();
3947 + if ( $location === 'admin' ) {
2969 3948 $admin_script_strings = array(
2970 - 'desc' => __( '(Click to add description)', 'formidable' ),
2971 - 'blank' => __( '(Blank)', 'formidable' ),
2972 - 'no_label' => __( '(no label)', 'formidable' ),
2973 - 'saving' => '', // Deprecated in 6.0.
2974 - 'saved' => '', // Deprecated in 6.0.
2975 - 'ok' => __( 'OK', 'formidable' ),
2976 - 'cancel' => __( 'Cancel', 'formidable' ),
2977 - 'default_label' => __( 'Default', 'formidable' ),
2978 - 'clear_default' => __( 'Clear default value when typing', 'formidable' ),
2979 - 'no_clear_default' => __( 'Do not clear default value when typing', 'formidable' ),
2980 - 'valid_default' => __( 'Default value will pass form validation', 'formidable' ),
2981 - 'no_valid_default' => __( 'Default value will NOT pass form validation', 'formidable' ),
2982 - 'confirm' => __( 'Are you sure?', 'formidable' ),
2983 - 'conf_delete' => __( 'Are you sure you want to delete this field and all data associated with it?', 'formidable' ),
2984 - 'conf_delete_sec' => __( 'All fields inside this Section will be deleted along with their data. Are you sure you want to delete this group of fields?', 'formidable' ),
2985 - 'conf_no_repeat' => __( 'Warning: If you have entries with multiple rows, all but the first row will be lost.', 'formidable' ),
2986 - 'default_unique' => $frm_settings->unique_msg,
2987 - 'default_conf' => __( 'The entered values do not match', 'formidable' ),
2988 - 'enter_email' => __( 'Enter Email', 'formidable' ),
2989 - 'confirm_email' => __( 'Confirm Email', 'formidable' ),
2990 - 'conditional_text' => __( 'Conditional content here', 'formidable' ),
2991 - 'new_option' => __( 'New Option', 'formidable' ),
2992 - 'css_invalid_size' => __( 'In certain browsers (e.g. Firefox) text will not display correctly if the field height is too small relative to the field padding and text size. Please increase your field height or decrease your field padding.', 'formidable' ),
2993 - 'enter_password' => __( 'Enter Password', 'formidable' ),
2994 - 'confirm_password' => __( 'Confirm Password', 'formidable' ),
2995 - 'import_complete' => __( 'Import Complete', 'formidable' ),
2996 - 'updating' => __( 'Please wait while your site updates.', 'formidable' ),
2997 - 'no_save_warning' => __( 'Warning: There is no way to retrieve unsaved entries.', 'formidable' ),
2998 - 'private_label' => __( 'Private', 'formidable' ),
2999 - 'jquery_ui_url' => '',
3000 - 'pro_url' => is_callable( 'FrmProAppHelper::plugin_url' ) ? FrmProAppHelper::plugin_url() : '',
3001 - 'no_licenses' => __( 'No new licenses were found', 'formidable' ),
3002 - 'unmatched_parens' => __( 'This calculation has at least one unmatched ( ) { } [ ].', 'formidable' ),
3003 - 'view_shortcodes' => __( 'This calculation may have shortcodes that work in Views but not forms.', 'formidable' ),
3004 - 'text_shortcodes' => __( 'This calculation may have shortcodes that work in text calculations but not numeric calculations.', 'formidable' ),
3005 - 'only_one_action' => __( 'This form action is limited to one per form. Please edit the existing form action.', 'formidable' ),
3006 - 'unsafe_params' => FrmFormsHelper::reserved_words(),
3949 + 'desc' => __( '(Click to add description)', 'formidable' ),
3950 + 'blank' => __( '(Blank)', 'formidable' ),
3951 + 'no_label' => self::get_no_label_text(),
3952 + 'ok' => __( 'OK', 'formidable' ),
3953 + 'cancel' => __( 'Cancel', 'formidable' ),
3954 + 'default_label' => __( 'Default', 'formidable' ),
3955 + 'clear_default' => __( 'Clear default value when typing', 'formidable' ),
3956 + 'no_clear_default' => __( 'Do not clear default value when typing', 'formidable' ),
3957 + 'valid_default' => __( 'Default value will pass form validation', 'formidable' ),
3958 + 'no_valid_default' => __( 'Default value will NOT pass form validation', 'formidable' ),
3959 + 'confirm' => __( 'Are you sure?', 'formidable' ),
3960 + 'conf_delete' => __( 'Are you sure you want to delete this field and all data associated with it?', 'formidable' ),
3961 + 'conf_delete_sec' => __( 'All fields inside this Section will be deleted along with their data. Are you sure you want to delete this group of fields?', 'formidable' ), // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
3962 + 'conf_no_repeat' => __( 'Warning: If you have entries with multiple rows, all but the first row will be lost.', 'formidable' ),
3963 + 'default_unique' => FrmFieldsHelper::default_unique_msg(),
3964 + 'default_conf' => __( 'The entered values do not match', 'formidable' ),
3965 + 'enter_email' => __( 'Enter Email', 'formidable' ),
3966 + 'confirm_email' => __( 'Confirm Email', 'formidable' ),
3967 + 'conditional_text' => __( 'Conditional content here', 'formidable' ),
3968 + 'new_option' => __( 'New Option', 'formidable' ),
3969 + 'css_invalid_size' => __( 'In certain browsers (e.g. Firefox) text will not display correctly if the field height is too small relative to the field padding and text size. Please increase your field height or decrease your field padding.', 'formidable' ), // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
3970 + 'enter_password' => __( 'Enter Password', 'formidable' ),
3971 + 'confirm_password' => __( 'Confirm Password', 'formidable' ),
3972 + 'import_complete' => __( 'Import Complete', 'formidable' ),
3973 + 'updating' => __( 'Please wait while your site updates.', 'formidable' ),
3974 + 'no_save_warning' => __( 'Warning: There is no way to retrieve unsaved entries.', 'formidable' ),
3975 + 'private_label' => __( 'Private', 'formidable' ),
3976 + 'jquery_ui_url' => '',
3977 + 'pro_url' => is_callable( 'FrmProAppHelper::plugin_url' ) ? FrmProAppHelper::plugin_url() : '',
3978 + 'no_licenses' => __( 'No new licenses were found', 'formidable' ),
3979 + 'unmatched_parens' => __( 'This calculation has at least one unmatched ( ) { } [ ].', 'formidable' ),
3980 + 'view_shortcodes' => __( 'This calculation may have shortcodes that work in Views but not forms.', 'formidable' ),
3981 + 'text_shortcodes' => __( 'This calculation may have shortcodes that work in text calculations but not numeric calculations.', 'formidable' ),
3982 + /* translators: %d is the number of allowed actions per form */
3983 + 'only_one_action' => sprintf( __( 'This form action is limited to %d per form.', 'formidable' ), 1 ),
3984 + 'edit_action_text' => __( 'Please edit the existing form action.', 'formidable' ),
3985 + 'only_one_payment_action' => __( 'This form already has a payment action, and is currently limited to a single payment action.', 'formidable' ),
3986 + 'only_one_stripe_action' => __( 'This form already has a payment action. Multiple Stripe actions are available when using the Stripe add-on, available for Formidable Business licenses and higher.', 'formidable' ), // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
3987 + 'unsafe_params' => FrmFormsHelper::reserved_words(),
3007 3988 /* Translators: %s is the name of a Detail Page Slug that is a reserved word.*/
3008 - 'slug_is_reserved' => sprintf( __( 'The Detail Page Slug "%s" is reserved by WordPress. This may cause problems. Is this intentional?', 'formidable' ), '****' ),
3989 + 'slug_is_reserved' => sprintf( __( 'The Detail Page Slug "%s" is reserved by WordPress. This may cause problems. Is this intentional?', 'formidable' ), '****' ), // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
3009 3990 /* Translators: %s is the name of a parameter that is a reserved word. More than one word could be listed here, though that would not be common. */
3010 - 'param_is_reserved' => sprintf( __( 'The parameter "%s" is reserved by WordPress. This may cause problems when included in the URL. Is this intentional? ', 'formidable' ), '****' ),
3011 - 'reserved_words' => __( 'See the list of reserved words in WordPress.', 'formidable' ),
3012 - 'repeat_limit_min' => __( 'Please enter a Repeat Limit that is greater than 1.', 'formidable' ),
3013 - 'checkbox_limit' => __( 'Please select a limit between 0 and 200.', 'formidable' ),
3014 - 'install' => __( 'Install', 'formidable' ),
3015 - 'active' => __( 'Active', 'formidable' ),
3016 - 'select_a_field' => __( 'Select a Field', 'formidable' ),
3017 - 'no_items_found' => __( 'No items found.', 'formidable' ),
3018 - 'field_already_used' => __( 'Oops. You have already used that field.', 'formidable' ),
3991 + 'param_is_reserved' => sprintf( __( 'The parameter "%s" is reserved by WordPress. This may cause problems when included in the URL. Is this intentional? ', 'formidable' ), '****' ), // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
3992 + 'reserved_words' => __( 'See the list of reserved words in WordPress.', 'formidable' ),
3993 + 'repeat_limit_min' => __( 'Please enter a Repeat Limit that is greater than 1.', 'formidable' ),
3994 + 'checkbox_limit' => __( 'Please select a limit between 0 and 200.', 'formidable' ),
3995 + 'install' => __( 'Install', 'formidable' ),
3996 + 'active' => __( 'Active', 'formidable' ),
3997 + 'installed' => __( 'Installed', 'formidable' ),
3998 + 'not_installed' => __( 'Not Installed', 'formidable' ),
3999 + 'select_a_field' => __( 'Select a Field', 'formidable' ),
4000 + 'no_items_found' => __( 'No items found.', 'formidable' ),
4001 + 'field_already_used' => __( 'Oops. You have already used that field.', 'formidable' ),
4002 +
4003 + // Deprecated in 6.0.
4004 + 'saving' => '',
4005 +
4006 + // Deprecated in 6.0.
4007 + 'saved' => '',
4008 +
4009 + // translators: %1$s: HTML open tag, %2$s: HTML end tag.
4010 + 'holdShiftMsg' => esc_html__( 'You can hold %1$sShift%2$s on your keyboard to select multiple fields', 'formidable' ),
4011 + 'noTitleText' => FrmFormsHelper::get_no_title_text(),
4012 +
4013 + // In older versions this event listener causes the section to immediately close again
4014 + // When the h3 element is clicked. It's only required in WP 6.7+.
4015 + 'requireAccordionTitleClickListener' => version_compare( $wp_version, '6.7', '>=' ),
3019 4016 );
4017 +
4018 + self::add_form_builder_modal_data( $admin_script_strings );
4019 +
4020 + /**
4021 + * @param array $admin_script_strings
4022 + */
3020 4023 $admin_script_strings = apply_filters( 'frm_admin_script_strings', $admin_script_strings );
3021 4024
3022 4025 $data = $wp_scripts->get_data( 'formidable_admin', 'data' );
3023 - if ( empty( $data ) ) {
4026 +
4027 + if ( ! $data ) {
3024 4028 wp_localize_script( 'formidable_admin', 'frm_admin_js', $admin_script_strings );
3025 4029 }
4030 + }//end if
4031 + }
4032 +
4033 + /**
4034 + * @param array $admin_script_strings
4035 + *
4036 + * @return void
4037 + */
4038 + private static function add_form_builder_modal_data( &$admin_script_strings ) {
4039 + if ( ! self::is_form_builder_page() || self::pro_is_installed() ) {
4040 + return;
3026 4041 }
4042 +
4043 + $stripe_connected = FrmStrpLiteConnectHelper::at_least_one_mode_is_setup();
4044 + $square_connected = FrmSquareLiteConnectHelper::at_least_one_mode_is_setup();
4045 + $paypal_connected = FrmPayPalLiteConnectHelper::at_least_one_mode_is_setup();
4046 + $gateway_connected = $stripe_connected || $square_connected || $paypal_connected;
4047 + $payments_settings_url = FrmStrpLiteAppController::get_payments_settings_url();
4048 +
4049 + if ( ! $gateway_connected ) {
4050 + // This modal shows when user clicks on one of the pricing fields and no payment gateways configured.
4051 + $admin_script_strings['paymentsSettingsModal'] = array(
4052 + 'title' => __( 'Setup a Payment Gateway first', 'formidable' ),
4053 + 'msg' => __( 'To use the payment fields, please install and configure a payment gateway in your account settings.', 'formidable' ),
4054 + 'closeText' => __( 'Close', 'formidable' ),
4055 + 'actionUrl' => $payments_settings_url,
4056 + 'actionText' => __( 'Go to Payment Settings', 'formidable' ),
4057 + 'noCenter' => true,
4058 + );
4059 + }
4060 +
4061 + // This modal shows on load once after upgrading the plugin.
4062 + $show_pricing_fields_modal = get_option( 'frm_show_pricing_fields_modal' );
4063 +
4064 + if ( ! $show_pricing_fields_modal ) {
4065 + return;
4066 + }
4067 +
4068 + $admin_script_strings['pricingFieldsModal'] = array(
4069 + 'title' => esc_html__( 'Start Accepting Payments Today!', 'formidable' ),
4070 + 'img' => esc_url( self::plugin_url() . '/images/upsell/pricing-fields.png' ),
4071 + 'noCenter' => true,
4072 + );
4073 +
4074 + if ( $gateway_connected ) {
4075 + $gateway_texts = array();
4076 +
4077 + if ( $stripe_connected ) {
4078 + $gateway_texts['stripe'] = esc_html__( 'Stripe', 'formidable' );
4079 + }
4080 +
4081 + if ( $square_connected ) {
4082 + $gateway_texts['square'] = esc_html__( 'Square', 'formidable' );
4083 + }
4084 +
4085 + if ( $paypal_connected ) {
4086 + $gateway_texts['paypal'] = esc_html__( 'PayPal', 'formidable' );
4087 + }
4088 +
4089 + $admin_script_strings['pricingFieldsModal']['msg'] = sprintf(
4090 + // translators: %s: Stripe, Square, or PayPal.
4091 + esc_html__( 'You already have %s connected, so these have already been unlocked.', 'formidable' ),
4092 + esc_html( implode( ' ' . esc_html__( 'and', 'formidable' ) . ' ', $gateway_texts ) )
4093 + );
4094 + } else {
4095 + $admin_script_strings['pricingFieldsModal']['closeText'] = __( 'I\'ll do it later!', 'formidable' );
4096 + $admin_script_strings['pricingFieldsModal']['actionText'] = __( 'Setup Payments Now', 'formidable' );
4097 + $admin_script_strings['pricingFieldsModal']['actionUrl'] = $payments_settings_url;
4098 + // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
4099 + $admin_script_strings['pricingFieldsModal']['msg'] = __( 'We\'ve unlocked Product, Quantity, and Total fields for Lite users! You can now transform your forms into checkout pages. To start collecting revenue, simply connect your preferred payment gateway (Stripe, Square, or PayPal) in your settings.', 'formidable' );
4100 + }//end if
4101 +
4102 + delete_option( 'frm_show_pricing_fields_modal' );
3027 4103 }
3028 4104
3029 4105 /**
4106 + * Get the no label text.
4107 + *
4108 + * @since 6.25.1
4109 + *
4110 + * @return string
4111 + */
4112 + public static function get_no_label_text() {
4113 + return __( '(no label)', 'formidable' );
4114 + }
4115 +
4116 + /**
4117 + * @since 6.5
4118 + *
4119 + * @return string
4120 + */
4121 + public static function get_ajax_url() {
4122 + $ajax_url = admin_url( 'admin-ajax.php', is_ssl() ? 'admin' : 'http' );
4123 +
4124 + /**
4125 + * @since 2.0.13
4126 + *
4127 + * @param string $ajax_url
4128 + */
4129 + return apply_filters( 'frm_ajax_url', $ajax_url );
4130 + }
4131 +
4132 + /**
3030 4133 * Returns whether or not the first errored input should be auto-focused (default true).
3031 4134 *
3032 4135 * @since 5.2.05
3033 4136 *
@@ -3052,9 +4155,11 @@
3052 4155 * Echo the message on the plugins listing page
3053 4156 *
3054 4157 * @since 1.07.10
3055 4158 *
3056 - * @param float $min_version The version the add-on requires
4159 + * @param float $min_version The version the add-on requires.
4160 + *
4161 + * @return void
3057 4162 */
3058 4163 public static function min_version_notice( $min_version ) {
3059 4164 $frm_version = self::plugin_version();
3060 4165
@@ -3063,11 +4168,11 @@
3063 4168 return;
3064 4169 }
3065 4170
3066 4171 $wp_list_table = _get_list_table( 'WP_Plugins_List_Table' );
3067 - echo '<tr class="plugin-update-tr active"><th colspan="' . absint( $wp_list_table->get_column_count() ) . '" class="check-column plugin-update colspanchange"><div class="update-message">' .
3068 - esc_html__( 'You are running an outdated version of Formidable. This plugin may not work correctly if you do not update Formidable.', 'formidable' ) .
3069 - '</div></td></tr>';
4172 + echo '<tr class="plugin-update-tr active"><th colspan="' . absint( $wp_list_table->get_column_count() ) . '" class="check-column plugin-update colspanchange"><div class="update-message">' . // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
4173 + esc_html__( 'You are running an outdated version of Formidable. This plugin may not work correctly if you do not update Formidable.', 'formidable' ) .
4174 + '</div></td></tr>';
3070 4175 }
3071 4176
3072 4177 /**
3073 4178 * If Pro is far outdated, show a message.
@@ -3073,8 +4178,10 @@
3073 4178 * If Pro is far outdated, show a message.
3074 4179 *
3075 4180 * @since 4.0.01
3076 4181 *
4182 + * @param string $min_version
4183 + *
3077 4184 * @return void
3078 4185 */
3079 4186 public static function min_pro_version_notice( $min_version ) {
3080 4187 if ( ! self::is_formidable_admin() ) {
@@ -3084,26 +4191,14 @@
3084 4191
3085 4192 self::php_version_notice();
3086 4193
3087 4194 $is_pro = self::pro_is_installed() && class_exists( 'FrmProDb' );
4195 +
3088 4196 if ( ! $is_pro || self::meets_min_pro_version( $min_version ) ) {
3089 4197 return;
3090 4198 }
3091 4199
3092 - $pro_version = FrmProDb::$plug_version;
3093 - $expired = FrmAddonsController::is_license_expired();
3094 - ?>
3095 - <div class="frm-banner-alert frm_error_style frm_previous_install">
3096 - <?php
3097 - esc_html_e( 'You are running a version of Formidable Forms that may not be compatible with your version of Formidable Forms Pro.', 'formidable' );
3098 - if ( empty( $expired ) ) {
3099 - echo ' Please <a href="' . esc_url( admin_url( 'plugins.php?s=formidable%20forms%20pro' ) ) . '">update now</a>.';
3100 - } else {
3101 - echo '<br/>Please <a href="https://formidableforms.com/account/downloads/?utm_source=WordPress&utm_medium=outdated">renew now</a> to get the latest version.';
3102 - }
3103 - ?>
3104 - </div>
3105 - <?php
4200 + include self::plugin_path() . '/classes/views/addons/min-version-notice.php';
3106 4201 }
3107 4202
3108 4203 /**
3109 4204 * If Pro is installed, check the version number.
@@ -3108,8 +4203,12 @@
3108 4203 /**
3109 4204 * If Pro is installed, check the version number.
3110 4205 *
3111 4206 * @since 4.0.01
4207 + *
4208 + * @param string $min_version
4209 + *
4210 + * @return bool
3112 4211 */
3113 4212 public static function meets_min_pro_version( $min_version ) {
3114 4213 return ! class_exists( 'FrmProDb' ) || version_compare( FrmProDb::$plug_version, $min_version, '>=' );
3115 4214 }
@@ -3114,24 +4213,22 @@
3114 4213 return ! class_exists( 'FrmProDb' ) || version_compare( FrmProDb::$plug_version, $min_version, '>=' );
3115 4214 }
3116 4215
3117 4216 /**
3118 - * Show a message if the browser or PHP version is below the recommendations.
4217 + * Show a message if the PHP version is below the recommendations.
3119 4218 *
3120 4219 * @since 4.0.02
4220 + *
4221 + * @return void
3121 4222 */
3122 4223 private static function php_version_notice() {
3123 4224 $message = array();
3124 - if ( version_compare( phpversion(), '5.6', '<' ) ) {
3125 - $message[] = __( 'The version of PHP on your server is too low. If this is not corrected, you may see issues with Formidable Forms. Please contact your web host and ask to be updated to PHP 7.0+.', 'formidable' );
3126 - }
3127 4225
3128 - $browser = self::get_server_value( 'HTTP_USER_AGENT' );
3129 - $is_ie = strpos( $browser, 'MSIE' ) !== false;
3130 - if ( $is_ie ) {
3131 - $message[] = __( 'You are using an outdated browser that is not compatible with Formidable Forms. Please update to a more current browser (we recommend Chrome).', 'formidable' );
4226 + if ( version_compare( phpversion(), '7.0', '<' ) ) {
4227 + $message[] = __( 'The version of PHP on your server is too low. If this is not corrected, you may see issues with Formidable Forms. Please contact your web host and ask to be updated to PHP 7.0+.', 'formidable' ); // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
3132 4228 }
3133 4229
4230 + // phpcs:disable Generic.WhiteSpace.ScopeIndent
3134 4231 foreach ( $message as $m ) {
3135 4232 ?>
3136 4233 <div class="frm-banner-alert frm_error_style frm_previous_install">
3137 4234 <?php echo esc_html( $m ); ?>
@@ -3137,12 +4234,14 @@
3137 4234 <?php echo esc_html( $m ); ?>
3138 4235 </div>
3139 4236 <?php
3140 4237 }
4238 + // phpcs:enable Generic.WhiteSpace.ScopeIndent
3141 4239 }
3142 4240
3143 4241 /**
3144 4242 * @param string $type
4243 + *
3145 4244 * @return array<string,string>
3146 4245 */
3147 4246 public static function locales( $type = 'date' ) {
3148 4247 $locales = array(
@@ -3236,12 +4335,12 @@
3236 4335 'zu' => __( 'Zulu', 'formidable' ),
3237 4336 );
3238 4337
3239 4338 if ( $type === 'captcha' ) {
3240 - // remove the languages unavailable for the captcha
4339 + // Remove the languages unavailable for the captcha
3241 4340 $unset = array( 'sq', 'bs', 'eo', 'fo', 'fr-CH', 'sr-SR', 'ar-DZ', 'be', 'cy-GB', 'kk', 'km', 'ky', 'lb', 'mk', 'nb', 'nn', 'rm', 'tj' );
3242 4341 } else {
3243 - // remove the languages unavailable for the datepicker
4342 + // Remove the languages unavailable for the datepicker
3244 4343 $unset = array( 'fil', 'fr-CA', 'de-AT', 'de-CH', 'iw', 'hi', 'pt', 'pt-PT', 'es-419', 'mr', 'lo', 'kn', 'si', 'gu', 'bn', 'zu', 'ur', 'te', 'sw', 'am' );
3245 4344 }
3246 4345
3247 4346 $locales = array_diff_key( $locales, array_flip( $unset ) );
@@ -3252,32 +4351,27 @@
3252 4351 * @since 5.4.5 Added $args parameter with type.
3253 4352 *
3254 4353 * @param array<string,string> $locales
3255 4354 * @param array $args {
4355 + *
3256 4356 * @type string $type
3257 4357 * }
3258 4358 */
3259 - $locales = apply_filters( 'frm_locales', $locales, compact( 'type' ) );
3260 -
3261 - return $locales;
4359 + return apply_filters( 'frm_locales', $locales, compact( 'type' ) );
3262 4360 }
3263 4361
3264 4362 /**
3265 - * Output HTML containing reference text for accessibility
3266 - *
3267 - * @deprecated 5.1
4363 + * @return string
3268 4364 */
3269 - public static function multiselect_accessibility() {
3270 - _deprecated_function( __METHOD__, '5.1' );
3271 - }
3272 -
3273 4365 public static function get_menu_icon_class() {
3274 4366 if ( is_callable( 'FrmProAppHelper::get_settings' ) ) {
3275 4367 $settings = FrmProAppHelper::get_settings();
4368 +
3276 4369 if ( is_object( $settings ) && ! empty( $settings->menu_icon ) ) {
3277 4370 return $settings->menu_icon;
3278 4371 }
3279 4372 }
4373 +
3280 4374 return 'frmfont frm_logo_icon';
3281 4375 }
3282 4376
3283 4377 /**
@@ -3295,10 +4389,9 @@
3295 4389 * @type array $input_attrs Attributes of value input.
3296 4390 * }
3297 4391 */
3298 4392 public static function images_dropdown( $args ) {
3299 - $args = self::fill_default_images_dropdown_args( $args );
3300 -
4393 + $args = self::fill_default_images_dropdown_args( $args );
3301 4394 $input_attrs_str = self::get_images_dropdown_input_attrs( $args );
3302 4395 ob_start();
3303 4396 include self::plugin_path() . '/classes/views/shared/images-dropdown.php';
3304 4397 $output = ob_get_clean();
@@ -3319,8 +4412,9 @@
3319 4412 *
3320 4413 * @since 5.0.04
3321 4414 *
3322 4415 * @param array $args The arguments.
4416 + *
3323 4417 * @return array
3324 4418 */
3325 4419 private static function fill_default_images_dropdown_args( $args ) {
3326 4420 $defaults = array(
@@ -3333,14 +4427,8 @@
3333 4427
3334 4428 $new_args['options'] = (array) $new_args['options'];
3335 4429 $new_args['input_attrs'] = (array) $new_args['input_attrs'];
3336 4430
3337 - // Set the number of columns.
3338 - $new_args['col_class'] = ceil( 12 / count( $new_args['options'] ) );
3339 - if ( $new_args['col_class'] > 6 ) {
3340 - $new_args['col_class'] = ceil( $new_args['col_class'] / 2 );
3341 - }
3342 -
3343 4431 /**
3344 4432 * Allows modifying the arguments of images_dropdown() method.
3345 4433 *
3346 4434 * @since 5.0.04
@@ -3356,8 +4444,9 @@
3356 4444 *
3357 4445 * @since 5.0.04
3358 4446 *
3359 4447 * @param array $args The arguments.
4448 + *
3360 4449 * @return string
3361 4450 */
3362 4451 private static function get_images_dropdown_input_attrs( $args ) {
3363 4452 $input_attrs = $args['input_attrs'];
@@ -3364,8 +4453,9 @@
3364 4453 $input_attrs['type'] = 'radio';
3365 4454 $input_attrs['name'] = $args['name'];
3366 4455
3367 4456 $input_attrs_str = '';
4457 +
3368 4458 foreach ( $input_attrs as $key => $input_attr ) {
3369 4459 $input_attrs_str .= ' ' . sprintf( '%s="%s"', esc_attr( $key ), esc_attr( $input_attr ) );
3370 4460 }
3371 4461
@@ -3380,8 +4470,23 @@
3380 4470 return apply_filters( 'frm_images_dropdown_input_attrs', $input_attrs_str, $args );
3381 4471 }
3382 4472
3383 4473 /**
4474 + * @since 6.7.1
4475 + *
4476 + * @param array $option Option data.
4477 + * @param array $args The arguments of images_dropdown() method.
4478 + *
4479 + * @return array
4480 + */
4481 + public static function get_images_dropdown_atts( $option, $args ) {
4482 + $image = self::get_images_dropdown_option_image( $option, $args );
4483 + $classes = self::get_images_dropdown_option_classes( $option, $args );
4484 + $custom_attrs = self::get_images_dropdown_option_html_attrs( $option, $args );
4485 + return compact( 'image', 'classes', 'custom_attrs' );
4486 + }
4487 +
4488 + /**
3384 4489 * Gets the image of each option in images_dropdown() method.
3385 4490 *
3386 4491 * @since 5.0.04
3387 4492 *
@@ -3386,8 +4491,9 @@
3386 4491 * @since 5.0.04
3387 4492 *
3388 4493 * @param array $option Option data.
3389 4494 * @param array $args The arguments of images_dropdown() method.
4495 + *
3390 4496 * @return string
3391 4497 */
3392 4498 private static function get_images_dropdown_option_image( $option, $args ) {
3393 4499 $image = self::icon_by_class(
@@ -3392,9 +4498,9 @@
3392 4498 private static function get_images_dropdown_option_image( $option, $args ) {
3393 4499 $image = self::icon_by_class(
3394 4500 'frmfont ' . $option['svg'],
3395 4501 array(
3396 - 'echo' => false,
4502 + 'echo' => false,
3397 4503 )
3398 4504 );
3399 4505
3400 4506 $args['option'] = $option;
@@ -3416,8 +4522,9 @@
3416 4522 * @since 5.0.04
3417 4523 *
3418 4524 * @param array $option Option data.
3419 4525 * @param array $args The arguments of images_dropdown() method.
4526 + *
3420 4527 * @return string
3421 4528 */
3422 4529 private static function get_images_dropdown_option_classes( $option, $args ) {
3423 4530 $classes = '';
@@ -3445,17 +4552,19 @@
3445 4552 * @since 5.0.04
3446 4553 *
3447 4554 * @param array $option Option data.
3448 4555 * @param array $args The arguments of images_dropdown() method.
4556 + *
3449 4557 * @return string
3450 4558 */
3451 4559 private static function get_images_dropdown_option_html_attrs( $option, $args ) {
3452 4560 $html_attrs = '';
4561 +
3453 4562 if ( ! empty( $option['custom_attrs'] ) && is_array( $option['custom_attrs'] ) ) {
3454 4563 $html_attrs_arr = array();
3455 4564
3456 4565 foreach ( $option['custom_attrs'] as $key => $value ) {
3457 - if ( in_array( $key, array( 'type', 'class', 'data-value' ) ) ) {
4566 + if ( in_array( $key, array( 'type', 'class', 'data-value' ), true ) ) {
3458 4567 continue;
3459 4568 }
3460 4569
3461 4570 $html_attrs_arr[] = sprintf( '%s="%s"', esc_attr( $key ), esc_attr( $value ) );
@@ -3513,14 +4622,13 @@
3513 4622 * @since 5.0.07
3514 4623 *
3515 4624 * @param array $values
3516 4625 * @param array $keys
4626 + *
3517 4627 * @return array
3518 4628 */
3519 4629 public static function maybe_filter_array( $values, $keys ) {
3520 - $allow_unfiltered_html = self::allow_unfiltered_html();
3521 -
3522 - if ( $allow_unfiltered_html ) {
4630 + if ( self::allow_unfiltered_html() ) {
3523 4631 return $values;
3524 4632 }
3525 4633
3526 4634 foreach ( $keys as $key ) {
@@ -3532,36 +4640,87 @@
3532 4640 return $values;
3533 4641 }
3534 4642
3535 4643 /**
3536 - * Some back end fields allow privleged users to add scripts.
4644 + * Some back end fields allow privileged users to add scripts.
3537 4645 * A site that uses the DISALLOW_UNFILTERED_HTML always remove scripts on echo.
3538 4646 *
3539 4647 * @since 5.0.13
3540 4648 *
3541 4649 * @param string $value
3542 - * @param array|string $allowed 'all' for everything included as defaults
4650 + * @param array|string $allowed 'all' for everything included as defaults.
4651 + *
3543 4652 * @return string
3544 4653 */
3545 4654 public static function maybe_kses( $value, $allowed = 'all' ) {
3546 4655 if ( self::should_never_allow_unfiltered_html() ) {
3547 - $value = self::kses( $value, $allowed );
4656 + return self::kses( $value, $allowed );
3548 4657 }
3549 4658 return $value;
3550 4659 }
3551 4660
3552 4661 /**
3553 - * @since 5.0.16
4662 + * Check if an option attribute used in an [input] shortcode is safe.
3554 4663 *
4664 + * @since 6.11.2
4665 + *
4666 + * @param string $key
4667 + * @param string $context Either 'display' or 'update'. On update, we want to allow a few keys that are never displayed.
4668 + *
3555 4669 * @return bool
3556 4670 */
3557 - public static function show_landing_pages() {
3558 - return self::show_new_feature( 'landing' );
4671 + public static function input_key_is_safe( $key, $context = 'display' ) {
4672 + if ( 'update' === $context && in_array( $key, array( 'opt', 'label' ), true ) ) {
4673 + $safe = true;
4674 + } elseif ( str_starts_with( $key, 'data-' ) ) {
4675 + // Allow all data attributes.
4676 + $safe = true;
4677 + } elseif ( str_starts_with( $key, 'aria-' ) ) {
4678 + // Allow all aria attributes.
4679 + $safe = true;
4680 + } else {
4681 + $safe_keys = array(
4682 + 'class',
4683 + 'required',
4684 + 'title',
4685 + 'placeholder',
4686 + 'value',
4687 + 'readonly',
4688 + 'disabled',
4689 + 'size',
4690 + 'maxlength',
4691 + 'min',
4692 + 'max',
4693 + 'pattern',
4694 + 'step',
4695 + 'autofocus',
4696 + 'width',
4697 + 'height',
4698 + 'autocomplete',
4699 + 'tabindex',
4700 + 'role',
4701 + 'style',
4702 + );
4703 + $safe = in_array( $key, $safe_keys, true );
4704 + }//end if
4705 +
4706 + /**
4707 + * Filter the $safe value so additional keys can be allowed or disallowed.
4708 + *
4709 + * @since 6.11.2
4710 + *
4711 + * @param bool $safe True if the key is considered safe.
4712 + * @param string $key
4713 + * @param string $context Either 'display' or 'update'.
4714 + */
4715 + return (bool) apply_filters( 'frm_input_key_is_safe', $safe, $key, $context );
3559 4716 }
3560 4717
3561 4718 /**
3562 4719 * @since 5.0.16
3563 4720 *
4721 + * @param string $medium
4722 + *
3564 4723 * @return array
3565 4724 */
3566 4725 public static function get_landing_page_upgrade_data_params( $medium = 'landing' ) {
3567 4726 $params = array(
@@ -3568,8 +4727,9 @@
3568 4727 'medium' => $medium,
3569 4728 'upgrade' => __( 'Form Landing Pages', 'formidable' ),
3570 4729 'message' => __( 'Easily manage a landing page for your form. Upgrade to get form landing pages.', 'formidable' ),
3571 4730 'screenshot' => 'landing.png',
4731 + 'learn-more' => self::get_doc_url( 'landing-page-forms', 'form-landing-page-settings', false ),
3572 4732 );
3573 4733 return self::get_upgrade_data_params( 'landing', $params );
3574 4734 }
3575 4735
@@ -3575,20 +4735,10 @@
3575 4735
3576 4736 /**
3577 4737 * @since 5.0.17
3578 4738 *
3579 - * @param string $plugin
3580 - * @return string|false
3581 - */
3582 - private static function get_plan_required( $plugin ) {
3583 - $link = FrmAddonsController::install_link( $plugin );
3584 - return FrmFormsHelper::get_plan_required( $link );
3585 - }
3586 -
3587 - /**
3588 - * @since 5.0.17
4739 + * @param string $feature
3589 4740 *
3590 - * @param string
3591 4741 * @return bool
3592 4742 */
3593 4743 public static function show_new_feature( $feature ) {
3594 4744 $link = FrmAddonsController::install_link( $feature );
@@ -3595,16 +4745,21 @@
3595 4745 return array_key_exists( 'status', $link ) || array_key_exists( 'class', $link );
3596 4746 }
3597 4747
3598 4748 /**
4749 + * Enhances upgrade data parameters with installation link and plan requirement information.
4750 + *
3599 4751 * @since 5.0.17
3600 4752 *
3601 - * @param string $plugin
3602 - * @param array $params
3603 - * @return array
4753 + * @param string $plugin The plugin slug to get installation data for.
4754 + * @param array $params Initial parameters for the upgrade data.
4755 + * @param bool $detailed Whether to include detailed information.
4756 + *
4757 + * @return array Modified parameters with installation data.
3604 4758 */
3605 - public static function get_upgrade_data_params( $plugin, $params ) {
4759 + public static function get_upgrade_data_params( $plugin, $params, $detailed = false ) {
3606 4760 $link = FrmAddonsController::install_link( $plugin );
4761 +
3607 4762 if ( ! $link ) {
3608 4763 return $params;
3609 4764 }
3610 4765
@@ -3610,15 +4765,20 @@
3610 4765
3611 4766 if ( ! empty( $link['url'] ) && self::pro_is_installed() ) {
3612 4767 $params['oneclick'] = json_encode( $link );
3613 4768 unset( $params['message'] );
4769 +
3614 4770 if ( ! isset( $params['medium'] ) ) {
3615 4771 $params['medium'] = $plugin;
3616 4772 }
3617 4773 } else {
3618 - $params['requires'] = FrmFormsHelper::get_plan_required( $link );
4774 + $params['requires'] = $params['requires'] ?? FrmFormsHelper::get_plan_required( $link );
3619 4775 }
3620 4776
4777 + if ( $detailed ) {
4778 + $params['plugin-status'] = $link['status'] ?? '';
4779 + }
4780 +
3621 4781 return $params;
3622 4782 }
3623 4783
3624 4784 /**
@@ -3627,8 +4787,9 @@
3627 4787 *
3628 4788 * @since 5.0.17
3629 4789 *
3630 4790 * @param string $text
4791 + *
3631 4792 * @return bool
3632 4793 */
3633 4794 public static function ctype_xdigit( $text ) {
3634 4795 if ( function_exists( 'ctype_xdigit' ) ) {
@@ -3643,12 +4804,14 @@
3643 4804 * @since 5.2.01
3644 4805 */
3645 4806 public static function set_current_screen_and_hook_suffix() {
3646 4807 global $hook_suffix;
4808 +
3647 4809 if ( is_null( $hook_suffix ) ) {
3648 4810 // $hook_suffix gets used in substr so make sure it's not null. PHP 8.1 deprecates null in substr.
3649 4811 $hook_suffix = ''; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
3650 4812 }
4813 +
3651 4814 set_current_screen();
3652 4815 }
3653 4816
3654 4817 /**
@@ -3655,15 +4818,15 @@
3655 4818 * Shows pill text.
3656 4819 *
3657 4820 * @since 5.2.02
3658 4821 *
3659 - * @param string $text Text in the pill. Default is NEW.
4822 + * @param string|null $text Text in the pill. Default is NEW.
3660 4823 */
3661 4824 public static function show_pill_text( $text = null ) {
3662 4825 if ( null === $text ) {
3663 4826 $text = __( 'NEW', 'formidable' );
3664 4827 }
3665 - echo '<span class="frm-new-pill">' . esc_html( $text ) . '</span>';
4828 + echo '<span class="frm-meta-tag frm-new-pill">' . esc_html( $text ) . '</span>';
3666 4829 }
3667 4830
3668 4831 /**
3669 4832 * Count the number of decimals digits.
@@ -3670,9 +4833,10 @@
3670 4833 *
3671 4834 * @since 5.2.07
3672 4835 *
3673 4836 * @param mixed $num Number.
3674 - * @return int|false Returns `false` if the passed parameter is not number.
4837 + *
4838 + * @return false|int Returns `false` if the passed parameter is not number.
3675 4839 */
3676 4840 public static function count_decimals( $num ) {
3677 4841 if ( ! is_numeric( $num ) ) {
3678 4842 return false;
@@ -3679,8 +4843,9 @@
3679 4843 }
3680 4844
3681 4845 $num = (string) $num;
3682 4846 $parts = explode( '.', $num );
4847 +
3683 4848 if ( 1 === count( $parts ) ) {
3684 4849 return 0;
3685 4850 }
3686 4851
@@ -3703,9 +4868,9 @@
3703 4868 }
3704 4869
3705 4870 add_filter(
3706 4871 'option_gmt_offset',
3707 - function( $offset ) {
4872 + function ( $offset ) {
3708 4873 if ( ! is_string( $offset ) || is_numeric( $offset ) ) {
3709 4874 // Leave a valid value alone.
3710 4875 return $offset;
3711 4876 }
@@ -3762,17 +4927,20 @@
3762 4927 * @since 5.5.5
3763 4928 *
3764 4929 * @param string $url
3765 4930 * @param string $expected_extension
4931 + *
3766 4932 * @return bool
3767 4933 */
3768 4934 public static function validate_url_is_in_s3_bucket( $url, $expected_extension ) {
3769 - $file_is_in_expected_s3_bucket = 0 === strpos( $url, 'https://s3.amazonaws.com/fp.strategy11.com' );
4935 + $file_is_in_expected_s3_bucket = str_starts_with( $url, 'https://s3.amazonaws.com/fp.strategy11.com' );
4936 +
3770 4937 if ( ! $file_is_in_expected_s3_bucket ) {
3771 4938 return false;
3772 4939 }
3773 4940
3774 4941 $parsed = parse_url( $url );
4942 +
3775 4943 if ( ! is_array( $parsed ) ) {
3776 4944 // URL is malformed.
3777 4945 return false;
3778 4946 }
@@ -3778,74 +4946,13 @@
3778 4946 }
3779 4947
3780 4948 $path = $parsed['path'];
3781 4949 $ext = pathinfo( $path, PATHINFO_EXTENSION );
3782 - if ( $expected_extension !== $ext ) {
3783 - // The URL isn't to an XML file.
3784 - return false;
3785 - }
3786 -
3787 - return true;
4950 + // The URL isn't to an XML file.
4951 + return $expected_extension === $ext;
3788 4952 }
3789 4953
3790 4954 /**
3791 - * @since 4.08
3792 - * @deprecated 4.09.01
3793 - */
3794 - public static function expiring_message() {
3795 - _deprecated_function( __METHOD__, '4.09.01', 'FrmProAddonsController::expiring_message' );
3796 - if ( is_callable( 'FrmProAddonsController::expiring_message' ) ) {
3797 - FrmProAddonsController::expiring_message();
3798 - }
3799 - }
3800 -
3801 - /**
3802 - * Use the WP 4.7 wp_doing_ajax function
3803 - *
3804 - * @since 2.05.07
3805 - * @deprecated 4.04.04
3806 - */
3807 - public static function wp_doing_ajax() {
3808 - _deprecated_function( __METHOD__, '4.04.04', 'wp_doing_ajax' );
3809 - return wp_doing_ajax();
3810 - }
3811 -
3812 - /**
3813 - * @deprecated 4.0
3814 - */
3815 - public static function insert_opt_html( $args ) {
3816 - _deprecated_function( __METHOD__, '4.0', 'FrmFormsHelper::insert_opt_html' );
3817 - FrmFormsHelper::insert_opt_html( $args );
3818 - }
3819 -
3820 - /**
3821 - * @deprecated 3.01
3822 - * @codeCoverageIgnore
3823 - */
3824 - public static function sanitize_array( &$values ) {
3825 - FrmDeprecated::sanitize_array( $values );
3826 - }
3827 -
3828 - /**
3829 - * @since 2.0
3830 - * @deprecated 5.0.13
3831 - *
3832 - * @return string The base Google APIS url for the current version of jQuery UI
3833 - */
3834 - public static function jquery_ui_base_url() {
3835 - _deprecated_function( __FUNCTION__, '5.0.13', 'FrmProAppHelper::jquery_ui_base_url' );
3836 - return is_callable( 'FrmProAppHelper::jquery_ui_base_url' ) ? FrmProAppHelper::jquery_ui_base_url() : '';
3837 - }
3838 -
3839 - /**
3840 - * @since 4.07
3841 - * @deprecated 6.0
3842 - */
3843 - public static function renewal_message() {
3844 - _deprecated_function( __METHOD__, '6.0', 'FrmProAddonsController::renewal_message' );
3845 - }
3846 -
3847 - /**
3848 4955 * Display a dismissable warning message and save its dismissal state.
3849 4956 *
3850 4957 * @since 6.3
3851 4958 *
@@ -3850,8 +4957,9 @@
3850 4957 * @since 6.3
3851 4958 *
3852 4959 * @param string $message The warning message to display.
3853 4960 * @param string $option The unique identifier for the dismissal state of the message and the WP Ajax action.
4961 + *
3854 4962 * @return void
3855 4963 */
3856 4964 public static function add_dismissable_warning_message( $message = '', $option = '' ) {
3857 4965 if ( ! $message || ! $option ) {
@@ -3857,9 +4965,9 @@
3857 4965 if ( ! $message || ! $option ) {
3858 4966 return;
3859 4967 }
3860 4968
3861 - $ajax_callback = function() use ( $option ) {
4969 + $ajax_callback = function () use ( $option ) {
3862 4970 self::dismiss_warning_message( $option );
3863 4971 };
3864 4972
3865 4973 // We're handling JS codes with `doJsonPost` and it adds 'frm_' to the beginning of the action.
@@ -3867,9 +4975,9 @@
3867 4975 add_action( 'wp_ajax_frm_' . $option, $ajax_callback );
3868 4976
3869 4977 add_filter(
3870 4978 'frm_message_list',
3871 - function( $show_messages ) use ( $message, $option ) {
4979 + function ( $show_messages ) use ( $message, $option ) {
3872 4980 if ( get_option( $option, false ) ) {
3873 4981 return $show_messages;
3874 4982 }
3875 4983
@@ -3876,9 +4984,9 @@
3876 4984 $dismiss_icon = self::icon_by_class(
3877 4985 'frmfont frm_close_icon',
3878 4986 array(
3879 4987 'aria-label' => _x( 'Dismiss', 'warning message: close icon label', 'formidable' ),
3880 - 'echo' => false,
4988 + 'echo' => false,
3881 4989 )
3882 4990 );
3883 4991
3884 4992 $show_messages[] = $message;
@@ -3894,8 +5002,9 @@
3894 5002 *
3895 5003 * @since 6.3
3896 5004 *
3897 5005 * @param string $option The unique identifier for the dismissal state of the message.
5006 + *
3898 5007 * @return void
3899 5008 */
3900 5009 public static function dismiss_warning_message( $option = '' ) {
3901 5010 self::permission_check( 'frm_change_settings' );
@@ -3901,10 +5010,182 @@
3901 5010 self::permission_check( 'frm_change_settings' );
3902 5011 check_ajax_referer( 'frm_ajax', 'nonce' );
3903 5012
3904 5013 if ( $option ) {
3905 - update_option( $option, true, 'no' );
5014 + update_option( $option, true, false );
3906 5015 }
3907 5016
3908 5017 wp_send_json_success();
5018 + }
5019 +
5020 + /**
5021 + * Lite license copy.
5022 + * Used in FrmDashboardController & FrmSettingsController
5023 + *
5024 + * @since 6.8
5025 + *
5026 + * @return string
5027 + */
5028 + public static function copy_for_lite_license() {
5029 + $message = __( 'You\'re using Formidable Forms Lite - no license needed. Enjoy!', 'formidable' ) . ' 🙂';
5030 + return apply_filters( 'frm_license_type_text', $message );
5031 + }
5032 +
5033 + /**
5034 + * Removes scripts that are unnecessarily loaded across the pages!
5035 + *
5036 + * @since 6.9
5037 + *
5038 + * @return void
5039 + */
5040 + public static function dequeue_extra_global_scripts() {
5041 + wp_dequeue_script( 'frm-surveys-admin' );
5042 + wp_dequeue_script( 'frm-quizzes-form-action' );
5043 + }
5044 +
5045 + /**
5046 + * Shows tooltip icon.
5047 + *
5048 + * @since 6.12
5049 + *
5050 + * @param string $tooltip_text Tooltip text.
5051 + * @param array $atts Tooltip wrapper HTML attributes.
5052 + *
5053 + * @return void
5054 + */
5055 + public static function tooltip_icon( $tooltip_text, $atts = array() ) {
5056 + $atts['title'] = $tooltip_text;
5057 +
5058 + if ( isset( $atts['class'] ) ) {
5059 + $atts['class'] .= ' frm_help';
5060 + } else {
5061 + $atts['class'] = 'frm_help';
5062 + }
5063 + // phpcs:disable Generic.WhiteSpace.ScopeIndent
5064 + ?>
5065 + <span <?php self::array_to_html_params( $atts, true ); ?>>
5066 + <?php self::icon_by_class( 'frmfont frm_tooltip_icon' ); ?>
5067 + </span>
5068 + <?php
5069 + // phpcs:enable Generic.WhiteSpace.ScopeIndent
5070 + }
5071 +
5072 + /**
5073 + * Prints errors for settings in onboarding wizard or template settings.
5074 + *
5075 + * @since 6.15
5076 + *
5077 + * @param array $args Args.
5078 + *
5079 + * @return void
5080 + */
5081 + public static function print_setting_error( $args ) {
5082 + $args = wp_parse_args(
5083 + $args,
5084 + array(
5085 + 'id' => '',
5086 + 'errors' => array(),
5087 + 'class' => '',
5088 + )
5089 + );
5090 +
5091 + $args['class'] .= ' frm-validation-error frm-mt-xs frm_hidden';
5092 +
5093 + // phpcs:disable Generic.WhiteSpace.ScopeIndent
5094 + ?>
5095 + <span id="<?php echo esc_attr( $args['id'] ); ?>" class="<?php echo esc_attr( $args['class'] ); ?>">
5096 + <?php
5097 + if ( is_array( $args['errors'] ) ) {
5098 + foreach ( $args['errors'] as $key => $msg ) {
5099 + ?>
5100 + <span frm-error="<?php echo esc_attr( $key ); ?>"><?php echo esc_html( $msg ); ?></span>
5101 + <?php
5102 + }
5103 + } else {
5104 + echo '<span>' . esc_html( $args['errors'] ) . '</span>';
5105 + }
5106 + ?>
5107 + </span>
5108 + <?php
5109 + // phpcs:enable Generic.WhiteSpace.ScopeIndent
5110 + }
5111 +
5112 + /**
5113 + * Check if GDPR is enabled.
5114 + *
5115 + * @since 6.19
5116 + *
5117 + * @return bool
5118 + */
5119 + public static function is_gdpr_enabled() {
5120 + $frm_settings = self::get_settings();
5121 + return $frm_settings->enable_gdpr || $frm_settings->no_ips || $frm_settings->custom_header_ip || $frm_settings->no_gdpr_cookies;
5122 + }
5123 +
5124 + /**
5125 + * Check if GDPR cookies are disabled.
5126 + *
5127 + * @since 6.19
5128 + *
5129 + * @return bool
5130 + */
5131 + public static function no_gdpr_cookies() {
5132 + $frm_settings = self::get_settings();
5133 + return $frm_settings->enable_gdpr && $frm_settings->no_gdpr_cookies;
5134 + }
5135 +
5136 + /**
5137 + * Check if a string is valid UTF-8.
5138 + *
5139 + * @since 6.24
5140 + *
5141 + * @param string|null $string The string to check.
5142 + *
5143 + * @return bool
5144 + */
5145 + public static function is_valid_utf8( $string ) {
5146 + if ( is_null( $string ) ) {
5147 + // Return true so we do not attempt to change encoding on a null value.
5148 + return true;
5149 + }
5150 +
5151 + // wp_is_valid_utf8 is added in WP 6.9.
5152 + if ( function_exists( 'wp_is_valid_utf8' ) ) {
5153 + return wp_is_valid_utf8( $string );
5154 + }
5155 +
5156 + // As of WP 6.9, seems_utf8 is deprecated.
5157 + return function_exists( 'seems_utf8' ) ? seems_utf8( $string ) : false;
5158 + }
5159 +
5160 + /**
5161 + * Get a documentation URL with UTM parameters and affiliate tracking.
5162 + *
5163 + * @since 6.26
5164 + *
5165 + * @param string $path The relative path to append to the base URL.
5166 + * @param string $campaign The campaign to use for UTM parameters.
5167 + * @param bool $add_kb_base Whether to prepend 'knowledgebase/' to the path. Default true.
5168 + *
5169 + * @return string The processed URL with UTM parameters and affiliate tracking.
5170 + */
5171 + public static function get_doc_url( $path, $campaign, $add_kb_base = true ) {
5172 + $path = trim( $path, '/' );
5173 +
5174 + if ( $add_kb_base ) {
5175 + $path = 'knowledgebase/' . $path;
5176 + }
5177 +
5178 + return self::maybe_add_missing_utm( 'https://formidableforms.com/' . $path, array( 'campaign' => $campaign ) );
5179 + }
5180 +
5181 + /**
5182 + * @since 5.0.16
5183 + * @deprecated 6.26
5184 + *
5185 + * @return bool
5186 + */
5187 + public static function show_landing_pages() {
5188 + _deprecated_function( __METHOD__, '6.26' );
5189 + return true;
3909 5190 }
3910 5191 }