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 +1951 -707 6.4.2 → 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.2';
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,32 +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 /**
156 301 * Determine if the current branding is set to 'formidable'.
302 + * Checks the menu icon, and verifies if it matches the formidable branding.
157 303 *
158 - * Checks the menu title, retrieved through get_menu_name,
159 - * and verifies if it matches the 'formidable' branding.
304 + * @since 6.4.2
160 305 *
161 - * @since x.x
162 - *
163 - * @return bool True if the menu title is 'formidable', false otherwise.
306 + * @return bool True if the menu icon is the logo, false otherwise.
164 307 */
165 308 public static function is_formidable_branding() {
166 - $menu_title = self::get_menu_name();
309 + if ( ! self::pro_is_installed() ) {
310 + return true;
311 + }
167 312
168 - return 'formidable' === strtolower( trim( $menu_title ) );
313 + return str_contains( self::get_menu_icon_class(), 'frm_logo_icon' );
169 314 }
170 315
171 316 /**
172 317 * @since 3.05
318 + *
319 + * @param array $atts
320 + *
321 + * @return string
173 322 */
174 323 public static function svg_logo( $atts = array() ) {
175 324 $defaults = array(
176 325 'height' => 18,
@@ -179,23 +328,31 @@
179 328 'orange' => '#f05a24',
180 329 );
181 330 $atts = array_merge( $defaults, $atts );
182 331
332 + // phpcs:disable SlevomatCodingStandard.Files.LineLength.LineTooLong
183 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'] ) . '">
184 334 <path fill="' . esc_attr( $atts['orange'] ) . '" d="M289.6 384h140v76h-140z"/>
185 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"/>
186 336 </svg>';
337 + // phpcs:enable SlevomatCodingStandard.Files.LineLength.LineTooLong
187 338 }
188 339
189 340 /**
190 341 * @since 4.0
342 + *
343 + * @param array $atts
344 + *
345 + * @return void
191 346 */
192 347 public static function show_logo( $atts = array() ) {
193 - echo self::kses( self::svg_logo( $atts ), 'all' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
348 + self::kses_echo( self::svg_logo( $atts ), 'all' );
194 349 }
195 350
196 351 /**
197 352 * @since 4.03.02
353 + *
354 + * @return void
198 355 */
199 356 public static function show_header_logo() {
200 357 $icon = self::svg_logo(
201 358 array(
@@ -204,10 +361,11 @@
204 361 )
205 362 );
206 363
207 364 $new_icon = apply_filters( 'frm_icon', $icon, true );
365 +
208 366 if ( $new_icon !== $icon ) {
209 - if ( strpos( $new_icon, '<svg' ) === 0 ) {
367 + if ( str_starts_with( $new_icon, '<svg' ) ) {
210 368 $icon = str_replace( 'viewBox="0 0 20', 'width="30" height="35" style="color:#929699" viewBox="0 0 20', $new_icon );
211 369 } else {
212 370 // Show nothing if it isn't an SVG.
213 371 $icon = '<div style="height:39px"></div>';
@@ -212,26 +370,43 @@
212 370 // Show nothing if it isn't an SVG.
213 371 $icon = '<div style="height:39px"></div>';
214 372 }
215 373 }
216 - echo self::kses( $icon, 'all' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
374 + self::kses_echo( $icon, 'all' );
217 375 }
218 376
219 377 /**
220 378 * @since 2.02.04
379 + *
380 + * @return bool
221 381 */
222 382 public static function ips_saved() {
223 383 $frm_settings = self::get_settings();
224 -
225 384 return ! $frm_settings->no_ips;
226 385 }
227 386
387 + /**
388 + * @return bool
389 + */
228 390 public static function pro_is_installed() {
229 - return apply_filters( 'frm_pro_installed', false );
391 + return (bool) apply_filters( 'frm_pro_installed', false );
230 392 }
231 393
232 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 + /**
233 406 * @since 4.06.02
407 + *
408 + * @return bool
234 409 */
235 410 public static function pro_is_connected() {
236 411 global $frm_vars;
237 412 return self::pro_is_installed() && $frm_vars['pro_is_authorized'];
@@ -238,39 +413,94 @@
238 413 }
239 414
240 415 /**
241 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
242 422 */
243 - public static function is_form_builder_page() {
244 - $action = self::simple_get( 'frm_action', 'sanitize_title' );
245 - 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 );
246 432 }
247 433
434 + /**
435 + * @return bool
436 + */
248 437 public static function is_formidable_admin() {
249 - $page = self::simple_get( 'page', 'sanitize_title' );
250 - $is_formidable = strpos( $page, 'formidable' ) !== false;
251 - if ( empty( $page ) ) {
252 - $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();
253 442 }
254 443
255 - return $is_formidable;
444 + return str_contains( $page, 'formidable' );
256 445 }
257 446
258 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 + /**
259 487 * Check for certain page in Formidable settings
260 488 *
261 489 * @since 2.0
262 490 *
263 - * @param string $page The name of the page to check
491 + * @param string $page The name of the page to check.
264 492 *
265 - * @return boolean
493 + * @return bool
266 494 */
267 495 public static function is_admin_page( $page = 'formidable' ) {
268 496 global $pagenow;
269 497 $get_page = self::simple_get( 'page', 'sanitize_title' );
498 +
270 499 if ( $pagenow ) {
271 - // 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
272 501 $is_page = ( $pagenow === 'admin.php' || $pagenow === 'admin-ajax.php' ) && $get_page === $page;
502 +
273 503 if ( $is_page ) {
274 504 return true;
275 505 }
276 506 }
@@ -282,21 +512,23 @@
282 512 * If the current page is for editing or creating a view.
283 513 * Returns false for the views listing page.
284 514 *
285 515 * @since 4.0
516 + *
517 + * @return bool
286 518 */
287 519 public static function is_view_builder_page() {
288 520 global $pagenow;
289 521
290 - 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 ) ) {
291 523 return false;
292 524 }
293 525
294 526 $post_type = self::simple_get( 'post_type', 'sanitize_title' );
295 527
296 - if ( empty( $post_type ) ) {
297 - $post_id = self::simple_get( 'post', 'absint' );
298 - $post = get_post( $post_id );
528 + if ( ! $post_type ) {
529 + $post_id = self::simple_get( 'post', 'absint' );
530 + $post = get_post( $post_id );
299 531 $post_type = $post ? $post->post_type : '';
300 532 }
301 533
302 534 return $post_type === 'frm_display';
@@ -306,17 +538,15 @@
306 538 * Check for the form preview page
307 539 *
308 540 * @since 2.0
309 541 *
310 - * @param None
311 - *
312 - * @return boolean
542 + * @return bool
313 543 */
314 544 public static function is_preview_page() {
315 545 global $pagenow;
316 546 $action = self::simple_get( 'action', 'sanitize_title' );
317 547
318 - return $pagenow && $pagenow == 'admin-ajax.php' && $action == 'frm_forms_preview';
548 + return $pagenow === 'admin-ajax.php' && $action === 'frm_forms_preview';
319 549 }
320 550
321 551 /**
322 552 * Check for ajax except the form preview page
@@ -322,16 +552,17 @@
322 552 * Check for ajax except the form preview page
323 553 *
324 554 * @since 2.0
325 555 *
326 - * @param None
327 - *
328 - * @return boolean
556 + * @return bool
329 557 */
330 558 public static function doing_ajax() {
331 559 return wp_doing_ajax() && ! self::is_preview_page();
332 560 }
333 561
562 + /**
563 + * @return string
564 + */
334 565 public static function js_suffix() {
335 566 return defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
336 567 }
337 568
@@ -336,13 +567,14 @@
336 567 }
337 568
338 569 /**
339 570 * @since 2.0.8
571 + *
572 + * @return bool
340 573 */
341 574 public static function prevent_caching() {
342 575 global $frm_vars;
343 -
344 - return isset( $frm_vars['prevent_caching'] ) && $frm_vars['prevent_caching'];
576 + return ! empty( $frm_vars['prevent_caching'] );
345 577 }
346 578
347 579 /**
348 580 * Check if on an admin page
@@ -348,9 +580,9 @@
348 580 * Check if on an admin page
349 581 *
350 582 * @since 2.0
351 583 *
352 - * @return boolean
584 + * @return bool
353 585 */
354 586 public static function is_admin() {
355 587 $is_admin = is_admin() && ! wp_doing_ajax();
356 588
@@ -355,8 +587,9 @@
355 587 $is_admin = is_admin() && ! wp_doing_ajax();
356 588
357 589 /**
358 590 * @since 6.0
591 + *
359 592 * @param bool $is_admin
360 593 */
361 594 return apply_filters( 'frm_is_admin', $is_admin );
362 595 }
@@ -365,17 +598,23 @@
365 598 * Check if value contains blank value or empty array
366 599 *
367 600 * @since 2.0
368 601 *
369 - * @param mixed $value - value to check
370 - * @param string
602 + * @param mixed $value Value to check.
603 + * @param string $empty
371 604 *
372 - * @return boolean
605 + * @return bool
373 606 */
374 607 public static function is_empty_value( $value, $empty = '' ) {
375 - return ( is_array( $value ) && empty( $value ) ) || $value === $empty;
608 + return $value === array() || $value === $empty;
376 609 }
377 610
611 + /**
612 + * @param mixed $value
613 + * @param string $empty
614 + *
615 + * @return bool
616 + */
378 617 public static function is_not_empty_value( $value, $empty = '' ) {
379 618 return ! self::is_empty_value( $value, $empty );
380 619 }
381 620
@@ -394,14 +633,13 @@
394 633
395 634 /**
396 635 * Get the server OS
397 636 *
398 - * @since 6.4.x
637 + * @since 6.4.2
399 638 *
400 639 * @return string
401 640 */
402 641 public static function get_server_os() {
403 -
404 642 if ( function_exists( 'php_uname' ) ) {
405 643 return php_uname( 's' );
406 644 }
407 645
@@ -428,10 +666,12 @@
428 666 continue;
429 667 }
430 668
431 669 $key = self::get_server_value( $key );
670 +
432 671 foreach ( explode( ',', $key ) as $ip ) {
433 - $ip = trim( $ip ); // Just to be safe.
672 + // Just to be safe.
673 + $ip = trim( $ip );
434 674
435 675 if ( filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) !== false ) {
436 676 return sanitize_text_field( $ip );
437 677 }
@@ -484,16 +724,26 @@
484 724 */
485 725 return apply_filters( 'frm_use_custom_header_ip', $should_use_custom_header_ip );
486 726 }
487 727
728 + /**
729 + * @param string $param
730 + * @param mixed $default
731 + * @param string $src
732 + * @param callable|string $sanitize
733 + *
734 + * @return mixed
735 + */
488 736 public static function get_param( $param, $default = '', $src = 'get', $sanitize = '' ) {
489 - if ( strpos( $param, '[' ) ) {
737 + if ( str_contains( $param, '[' ) ) {
490 738 $params = explode( '[', $param );
491 739 $param = $params[0];
492 740 }
493 741
494 742 if ( $src === 'get' ) {
495 - $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 +
496 746 if ( ! isset( $_POST[ $param ] ) && isset( $_GET[ $param ] ) && ! is_array( $value ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
497 747 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
498 748 $value = htmlspecialchars_decode( wp_unslash( $_GET[ $param ] ) );
499 749 }
@@ -508,29 +758,41 @@
508 758 )
509 759 );
510 760 }
511 761
512 - if ( isset( $params ) && is_array( $value ) && ! empty( $value ) ) {
513 - foreach ( $params as $k => $p ) {
514 - if ( ! $k || ! is_array( $value ) ) {
515 - continue;
516 - }
762 + if ( ! isset( $params ) || ! is_array( $value ) || ! $value ) {
763 + return $value;
764 + }
517 765
518 - $p = trim( $p, ']' );
519 - $value = isset( $value[ $p ] ) ? $value[ $p ] : $default;
766 + foreach ( $params as $k => $p ) {
767 + if ( ! $k || ! is_array( $value ) ) {
768 + continue;
520 769 }
770 +
771 + $p = trim( $p, ']' );
772 + $value = $value[ $p ] ?? $default;
521 773 }
522 774
523 775 return $value;
524 776 }
525 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 + */
526 788 public static function get_post_param( $param, $default = '', $sanitize = '', $serialized = false ) {
527 789 return self::get_simple_request(
528 790 array(
529 - 'type' => 'post',
530 - 'param' => $param,
531 - 'default' => $default,
532 - 'sanitize' => $sanitize,
791 + 'type' => 'post',
792 + 'param' => $param,
793 + 'default' => $default,
794 + 'sanitize' => $sanitize,
533 795 'serialized' => $serialized,
534 796 )
535 797 );
536 798 }
@@ -541,9 +803,9 @@
541 803 * @param string $param
542 804 * @param string $sanitize
543 805 * @param string $default
544 806 *
545 - * @return string|array
807 + * @return array|int|string
546 808 */
547 809 public static function simple_get( $param, $sanitize = 'sanitize_text_field', $default = '' ) {
548 810 return self::get_simple_request(
549 811 array(
@@ -561,39 +823,38 @@
561 823 * @since 2.0.6
562 824 *
563 825 * @param array $args
564 826 *
565 - * @return string|array
827 + * @return array|string
566 828 */
567 829 public static function get_simple_request( $args ) {
568 830 $defaults = array(
569 - 'param' => '',
570 - 'default' => '',
571 - 'type' => 'get',
572 - 'sanitize' => 'sanitize_text_field',
831 + 'param' => '',
832 + 'default' => '',
833 + 'type' => 'get',
834 + 'sanitize' => 'sanitize_text_field',
573 835 'serialized' => false,
574 836 );
575 837 $args = wp_parse_args( $args, $defaults );
838 + $value = $args['default'];
576 839
577 - $value = $args['default'];
578 - if ( $args['type'] == 'get' ) {
840 + if ( $args['type'] === 'get' ) {
579 841 if ( $_GET && isset( $_GET[ $args['param'] ] ) ) {
580 842 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Missing
581 843 $value = wp_unslash( $_GET[ $args['param'] ] );
582 844 }
583 - } elseif ( $args['type'] == 'post' ) {
845 + } elseif ( $args['type'] === 'post' ) {
584 846 if ( isset( $_POST[ $args['param'] ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
585 847 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Missing
586 848 $value = wp_unslash( $_POST[ $args['param'] ] );
849 +
587 850 if ( $args['serialized'] === true && is_serialized_string( $value ) && is_serialized( $value ) ) {
588 851 self::unserialize_or_decode( $value );
589 852 }
590 853 }
591 - } else {
592 - if ( isset( $_REQUEST[ $args['param'] ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
593 - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
594 - $value = wp_unslash( $_REQUEST[ $args['param'] ] );
595 - }
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'] ] );
596 857 }
597 858
598 859 self::sanitize_value( $args['sanitize'], $value );
599 860
@@ -606,34 +867,56 @@
606 867 * @since 2.0.8
607 868 *
608 869 * @param string $value
609 870 *
610 - * @return string $value
871 + * @return string Value.
611 872 */
612 873 public static function preserve_backslashes( $value ) {
613 874 // If backslashes have already been added, don't add them again
614 - if ( strpos( $value, '\\\\' ) === false ) {
615 - $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;
616 890 }
617 891
618 - return $value;
619 - }
892 + if ( is_object( $value ) ) {
893 + $value = '';
894 + return;
895 + }
620 896
621 - public static function sanitize_value( $sanitize, &$value ) {
622 - if ( ! empty( $sanitize ) ) {
623 - if ( is_array( $value ) ) {
624 - $temp_values = $value;
625 - foreach ( $temp_values as $k => $v ) {
626 - self::sanitize_value( $sanitize, $value[ $k ] );
627 - }
628 - } else {
629 - $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 ] );
630 902 }
903 +
904 + return;
631 905 }
906 +
907 + $value = call_user_func( $sanitize, $value );
632 908 }
633 909
910 + /**
911 + * @param array $sanitize_method
912 + * @param array $values
913 + *
914 + * @return void
915 + */
634 916 public static function sanitize_request( $sanitize_method, &$values ) {
635 917 $temp_values = $values;
918 +
636 919 foreach ( $temp_values as $k => $val ) {
637 920 if ( isset( $sanitize_method[ $k ] ) ) {
638 921 $values[ $k ] = call_user_func( $sanitize_method[ $k ], $val );
639 922 }
@@ -641,23 +924,69 @@
641 924 }
642 925
643 926 /**
644 927 * @since 4.0.04
928 + *
929 + * @param mixed $value
930 + *
931 + * @return void
645 932 */
646 933 public static function sanitize_with_html( &$value ) {
647 - 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 + }
648 940 self::decode_specialchars( $value );
941 + self::sanitize_value( 'FrmHtmlSanitizer::sanitize_url_attributes', $value );
649 942 }
650 943
651 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 + /**
652 978 * Do wp_specialchars_decode to get back '&' that wp_kses_post might have turned to '&amp;'
653 979 * this MUST be done, else we'll be back to the '& entity' problem.
654 980 *
655 981 * @since 4.0.04
982 + *
983 + * @param mixed $value Value to decode, passed by reference.
656 984 */
657 985 public static function decode_specialchars( &$value ) {
658 986 if ( is_array( $value ) ) {
659 987 $temp_values = $value;
988 +
660 989 foreach ( $temp_values as $k => $v ) {
661 990 self::decode_specialchars( $value[ $k ] );
662 991 }
663 992 } else {
@@ -671,13 +1000,13 @@
671 1000 * Adapted from wp_specialchars_decode().
672 1001 *
673 1002 * @since 4.03.01
674 1003 *
675 - * @param string $string The string to prep.
1004 + * @param string $string The string to prep, passed by reference.
676 1005 */
677 1006 private static function decode_amp( &$string ) {
678 1007 // Don't bother if there are no entities - saves a lot of processing
679 - if ( empty( $string ) || strpos( $string, '&' ) === false ) {
1008 + if ( ! $string || ! str_contains( $string, '&' ) ) {
680 1009 return;
681 1010 }
682 1011
683 1012 $translation = array(
@@ -683,10 +1012,12 @@
683 1012 $translation = array(
684 1013 '&quot;' => '"',
685 1014 '&#034;' => '"',
686 1015 '&#x22;' => '"',
687 - '&lt; ' => '< ', // The space preserves the HTML.
688 - '&#060; ' => '< ', // The space preserves the HTML.
1016 + // The space preserves the HTML.
1017 + '&lt; ' => '< ',
1018 + // The space preserves the HTML.
1019 + '&#060; ' => '< ',
689 1020 '&gt;' => '>',
690 1021 '&#062;' => '>',
691 1022 '&amp;' => '&',
692 1023 '&#038;' => '&',
@@ -713,17 +1044,29 @@
713 1044 * Sanitize the value, and allow some HTML
714 1045 *
715 1046 * @since 2.0
716 1047 *
717 - * @param string $value
718 - * @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.
719 1050 *
720 1051 * @return string
721 1052 */
722 1053 public static function kses( $value, $allowed = array() ) {
723 - $allowed_html = self::allowed_html( $allowed );
1054 + return wp_kses( $value, self::allowed_html( $allowed ) );
1055 + }
724 1056
725 - 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
726 1069 }
727 1070
728 1071 /**
729 1072 * The regular kses function strips [button_action] from submit button HTML.
@@ -730,21 +1073,23 @@
730 1073 *
731 1074 * @since 5.0.13
732 1075 *
733 1076 * @param string $html
1077 + *
734 1078 * @return string
735 1079 */
736 1080 public static function kses_submit_button( $html ) {
737 - $included_button_action = false !== strpos( $html, '[button_action]' );
738 - $included_back_hook = false !== strpos( $html, '[back_hook]' );
739 - $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]' );
740 1084 add_filter( 'safe_style_css', 'FrmAppHelper::allow_visibility_style' );
741 1085 add_filter( 'frm_striphtml_allowed_tags', 'FrmAppHelper::add_allowed_submit_button_tags' );
742 1086 $html = self::kses( $html, 'all' );
743 1087 remove_filter( 'safe_style_css', 'FrmAppHelper::allow_visibility_style' );
744 1088 remove_filter( 'frm_striphtml_allowed_tags', 'FrmAppHelper::add_allowed_submit_button_tags' );
1089 +
745 1090 if ( $included_button_action ) {
746 - if ( false !== strpos( $html, '<input type="submit"' ) ) {
1091 + if ( str_contains( $html, '<input type="submit"' ) ) {
747 1092 $pattern = '/(<input type="submit")([^>]*)(\/>)/';
748 1093 $html = preg_replace( $pattern, '$1$2[button_action] $3', $html, 1 );
749 1094 } else {
750 1095 $pattern = '/(<button)(.*)(class=")(.*)(frm_button_submit)(.*)(")(.*)([^>]+)(>)/';
@@ -750,14 +1095,17 @@
750 1095 $pattern = '/(<button)(.*)(class=")(.*)(frm_button_submit)(.*)(")(.*)([^>]+)(>)/';
751 1096 $html = preg_replace( $pattern, '$1$2$3$4$5$6$7 [button_action]$8$9$10', $html, 1 );
752 1097 }
753 1098 }
1099 +
754 1100 if ( $included_back_hook ) {
755 1101 $html = str_replace( 'class="frm_prev_page"', 'class="frm_prev_page" [back_hook]', $html );
756 1102 }
1103 +
757 1104 if ( $included_draft_hook ) {
758 - $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 );
759 1106 }
1107 +
760 1108 return $html;
761 1109 }
762 1110
763 1111 /**
@@ -763,8 +1111,9 @@
763 1111 /**
764 1112 * @since 5.0.13
765 1113 *
766 1114 * @param array $allowed_attr
1115 + *
767 1116 * @return array
768 1117 */
769 1118 public static function allow_visibility_style( $allowed_attr ) {
770 1119 $allowed_attr[] = 'visibility';
@@ -774,8 +1123,9 @@
774 1123 /**
775 1124 * @since 5.0.13
776 1125 *
777 1126 * @param array $allowed_html
1127 + *
778 1128 * @return array
779 1129 */
780 1130 public static function add_allowed_submit_button_tags( $allowed_html ) {
781 1131 $allowed_html['input'] = array(
@@ -792,17 +1142,22 @@
792 1142 }
793 1143
794 1144 /**
795 1145 * @since 2.05.03
1146 + *
1147 + * @param array|string $allowed
1148 + *
1149 + * @return array
796 1150 */
797 1151 private static function allowed_html( $allowed ) {
798 1152 $html = self::safe_html();
799 1153 $allowed_html = array();
1154 +
800 1155 if ( $allowed === 'all' ) {
801 1156 $allowed_html = $html;
802 - } elseif ( ! empty( $allowed ) ) {
1157 + } elseif ( $allowed ) {
803 1158 foreach ( (array) $allowed as $a ) {
804 - $allowed_html[ $a ] = isset( $html[ $a ] ) ? $html[ $a ] : array();
1159 + $allowed_html[ $a ] = $html[ $a ] ?? array();
805 1160 }
806 1161 }
807 1162
808 1163 return apply_filters( 'frm_striphtml_allowed_tags', $allowed_html );
@@ -809,8 +1164,10 @@
809 1164 }
810 1165
811 1166 /**
812 1167 * @since 2.05.03
1168 + *
1169 + * @return array
813 1170 */
814 1171 private static function safe_html() {
815 1172 $allow_class = array(
816 1173 'class' => true,
@@ -817,9 +1174,9 @@
817 1174 'id' => true,
818 1175 );
819 1176
820 1177 return array(
821 - 'a' => array(
1178 + 'a' => array(
822 1179 'class' => true,
823 1180 'href' => true,
824 1181 'id' => true,
825 1182 'rel' => true,
@@ -888,16 +1245,16 @@
888 1245 'cite' => true,
889 1246 'title' => true,
890 1247 ),
891 1248 'rect' => array(
892 - 'class' => true,
893 - 'fill' => true,
894 - 'height' => true,
895 - 'width' => true,
896 - 'x' => true,
897 - 'y' => true,
898 - 'rx' => true,
899 - '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,
900 1257 'stroke-opacity' => true,
901 1258 'stroke-width' => true,
902 1259 ),
903 1260 'section' => $allow_class,
@@ -932,9 +1289,9 @@
932 1289 'xlink:href' => true,
933 1290 ),
934 1291 'ul' => $allow_class,
935 1292 'label' => array(
936 - 'for' => true,
1293 + 'for' => true,
937 1294 'class' => true,
938 1295 'id' => true,
939 1296 ),
940 1297 'button' => array(
@@ -943,8 +1300,13 @@
943 1300 ),
944 1301 'legend' => array(
945 1302 'class' => true,
946 1303 ),
1304 + 'option' => array(
1305 + 'class' => true,
1306 + 'value' => true,
1307 + 'selected' => true,
1308 + ),
947 1309 );
948 1310 }
949 1311
950 1312 /**
@@ -952,19 +1314,21 @@
952 1314 *
953 1315 * @since 2.0
954 1316 */
955 1317 public static function remove_get_action() {
956 - if ( ! isset( $_GET ) ) {
1318 + if ( empty( $_GET ) ) {
957 1319 return;
958 1320 }
959 1321
960 1322 $action_name = isset( $_GET['action'] ) ? 'action' : ( isset( $_GET['action2'] ) ? 'action2' : '' );
961 - if ( empty( $action_name ) ) {
1323 +
1324 + if ( ! $action_name ) {
962 1325 return;
963 1326 }
964 1327
965 1328 $new_action = self::get_param( $action_name, '', 'get', 'sanitize_text_field' );
966 - if ( ! empty( $new_action ) ) {
1329 +
1330 + if ( $new_action ) {
967 1331 $_SERVER['REQUEST_URI'] = str_replace( '&action=' . $new_action, '', self::get_server_value( 'REQUEST_URI' ) );
968 1332 }
969 1333 }
970 1334
@@ -971,21 +1335,23 @@
971 1335 /**
972 1336 * Check the WP query for a parameter
973 1337 *
974 1338 * @since 2.0
975 - * @return string|array
1339 + *
1340 + * @param array|string $value
1341 + * @param string $param
1342 + *
1343 + * @return array|string
976 1344 */
977 1345 public static function get_query_var( $value, $param ) {
1346 + // phpcs:ignore Universal.Operators.StrictComparisons
978 1347 if ( $value != '' ) {
979 1348 return $value;
980 1349 }
981 1350
982 1351 global $wp_query;
983 - if ( isset( $wp_query->query_vars[ $param ] ) ) {
984 - $value = $wp_query->query_vars[ $param ];
985 - }
986 1352
987 - return $value;
1353 + return $wp_query->query_vars[ $param ] ?? $value;
988 1354 }
989 1355
990 1356 /**
991 1357 * Try to show the SVG if possible. Otherwise, use the font icon.
@@ -990,48 +1356,82 @@
990 1356 /**
991 1357 * Try to show the SVG if possible. Otherwise, use the font icon.
992 1358 *
993 1359 * @since 4.0.02
1360 + *
994 1361 * @param string $class
995 1362 * @param array $atts
1363 + *
1364 + * @return string|null
996 1365 */
997 1366 public static function icon_by_class( $class, $atts = array() ) {
998 1367 $echo = ! isset( $atts['echo'] ) || $atts['echo'];
1368 +
999 1369 if ( isset( $atts['echo'] ) ) {
1000 1370 unset( $atts['echo'] );
1001 1371 }
1002 1372
1003 - $html_atts = self::array_to_html_params( $atts );
1004 -
1005 1373 $icon = trim( str_replace( array( 'frm_icon_font', 'frmfont ' ), '', $class ) );
1006 1374
1007 - // Replace icons that have been removed.
1375 + // Replace icons that have been removed or renamed.
1008 1376 $deprecated = array(
1009 - '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',
1010 1380 );
1381 +
1011 1382 if ( isset( $deprecated[ $icon ] ) ) {
1012 - $icon = $deprecated[ $icon ];
1383 + $icon = $deprecated[ $icon ];
1013 1384 $class = str_replace( $icon, $deprecated[ $icon ], $class );
1014 1385 }
1015 1386
1016 - if ( $icon === $class ) {
1017 - $icon = '<i class="' . esc_attr( $class ) . '"' . $html_atts . '></i>';
1018 - } else {
1019 - $class = strpos( $icon, ' ' ) === false ? '' : ' ' . $icon;
1020 - 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, ' ' ) ) {
1021 1393 $icon = explode( ' ', $icon );
1022 1394 $icon = reset( $icon );
1023 1395 }
1024 - $icon = '<svg class="frmsvg' . esc_attr( $class ) . '"' . $html_atts . '>
1025 - <use xlink:href="#' . esc_attr( $icon ) . '" />
1026 - </svg>';
1027 1396 }
1028 1397
1029 - if ( $echo ) {
1030 - echo self::kses_icon( $icon ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1031 - } else {
1032 - 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;
1033 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 );
1034 1434 }
1035 1435
1036 1436 /**
1037 1437 * Run kses for icons. It needs to add a few filters first in order to preserve some custom style values.
@@ -1038,8 +1438,9 @@
1038 1438 *
1039 1439 * @since 5.0.13
1040 1440 *
1041 1441 * @param string $icon
1442 + *
1042 1443 * @return string
1043 1444 */
1044 1445 public static function kses_icon( $icon ) {
1045 1446 add_filter( 'safe_style_css', 'FrmAppHelper::allow_vars_in_styles' );
@@ -1055,13 +1456,15 @@
1055 1456 /**
1056 1457 * @since 5.0.13.1
1057 1458 *
1058 1459 * @param array $allowed_html
1460 + *
1059 1461 * @return array
1060 1462 */
1061 1463 public static function add_allowed_icon_tags( $allowed_html ) {
1062 1464 $allowed_html['svg']['data-open'] = true;
1063 1465 $allowed_html['svg']['title'] = true;
1466 + $allowed_html['svg']['tabindex'] = true;
1064 1467 return $allowed_html;
1065 1468 }
1066 1469
1067 1470 /**
@@ -1067,8 +1470,9 @@
1067 1470 /**
1068 1471 * @since 5.0.13
1069 1472 *
1070 1473 * @param array $allowed_attr
1474 + *
1071 1475 * @return array
1072 1476 */
1073 1477 public static function allow_vars_in_styles( $allowed_attr ) {
1074 1478 $allowed_attr[] = '--primary-700';
@@ -1081,9 +1485,9 @@
1081 1485 * @param bool $allow_css
1082 1486 * @param string $css_string
1083 1487 */
1084 1488 public static function allow_style( $allow_css, $css_string ) {
1085 - if ( ! $allow_css && 0 === strpos( $css_string, '--primary-700:' ) ) {
1489 + if ( ! $allow_css && str_starts_with( $css_string, '--primary-700:' ) ) {
1086 1490 $split = explode( ':', $css_string, 2 );
1087 1491 $allow_css = 2 === count( $split ) && self::is_a_valid_color( $split[1] );
1088 1492 }
1089 1493 return $allow_css;
@@ -1092,19 +1496,22 @@
1092 1496 /**
1093 1497 * @since 5.0.13
1094 1498 *
1095 1499 * @param string $value
1500 + *
1096 1501 * @return bool
1097 1502 */
1098 1503 private static function is_a_valid_color( $value ) {
1099 1504 $match = 0;
1100 - if ( 0 === strpos( $value, 'rgba(' ) ) {
1505 +
1506 + if ( str_starts_with( $value, 'rgba(' ) ) {
1101 1507 $match = preg_match( '/^rgba\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3}),\s*(\d*(?:\.\d+)?)\)$/', $value );
1102 - } elseif ( 0 === strpos( $value, 'rgb(' ) ) {
1508 + } elseif ( str_starts_with( $value, 'rgb(' ) ) {
1103 1509 $match = preg_match( '/^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/', $value );
1104 - } elseif ( 0 === strpos( $value, '#' ) ) {
1510 + } elseif ( str_starts_with( $value, '#' ) ) {
1105 1511 $match = preg_match( '/^#([a-f0-9]{6}|[a-f0-9]{3})\b$/', $value );
1106 1512 }
1513 +
1107 1514 return (bool) $match;
1108 1515 }
1109 1516
1110 1517 /**
@@ -1110,11 +1517,19 @@
1110 1517 /**
1111 1518 * Include svg images.
1112 1519 *
1113 1520 * @since 4.0.02
1521 + *
1522 + * @return void
1114 1523 */
1115 1524 public static function include_svg() {
1116 - 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;
1117 1532 }
1118 1533
1119 1534 /**
1120 1535 * Convert an associative array to HTML values.
@@ -1123,17 +1538,20 @@
1123 1538 * @since 5.0.13 added $echo parameter.
1124 1539 *
1125 1540 * @param array $atts
1126 1541 * @param bool $echo
1542 + *
1127 1543 * @return string|void
1128 1544 */
1129 1545 public static function array_to_html_params( $atts, $echo = false ) {
1130 - $callback = function() use ( $atts ) {
1131 - if ( $atts ) {
1132 - foreach ( $atts as $key => $value ) {
1133 - echo ' ' . esc_attr( $key ) . '="' . esc_attr( $value ) . '"';
1134 - }
1546 + $callback = function () use ( $atts ) {
1547 + if ( ! $atts ) {
1548 + return;
1135 1549 }
1550 +
1551 + foreach ( $atts as $key => $value ) {
1552 + echo ' ' . esc_attr( $key ) . '="' . esc_attr( $value ) . '"';
1553 + }
1136 1554 };
1137 1555 return self::clip( $callback, $echo );
1138 1556 }
1139 1557
@@ -1143,9 +1561,12 @@
1143 1561 * @since 5.0.13
1144 1562 *
1145 1563 * @param Closure $echo_function
1146 1564 * @param bool $echo
1147 - * @return string|void
1565 + *
1566 + * @return string|null
1567 + *
1568 + * @psalm-return ($echo is true ? null : string)
1148 1569 */
1149 1570 public static function clip( $echo_function, $echo = false ) {
1150 1571 if ( ! $echo ) {
1151 1572 ob_start();
@@ -1154,28 +1575,30 @@
1154 1575 if ( is_callable( $echo_function ) ) {
1155 1576 $echo_function();
1156 1577 }
1157 1578
1158 - if ( ! $echo ) {
1159 - $return = ob_get_contents();
1160 - ob_end_clean();
1161 - return $return;
1162 - }
1579 + return $echo ? null : ob_get_clean();
1163 1580 }
1164 1581
1165 1582 /**
1166 1583 * @since 3.0
1584 + *
1585 + * @param array $atts
1586 + *
1587 + * @return void
1167 1588 */
1168 1589 public static function get_admin_header( $atts ) {
1169 - $has_nav = ( isset( $atts['form'] ) && ! empty( $atts['form'] ) && ( ! isset( $atts['is_template'] ) || ! $atts['is_template'] ) );
1170 - if ( ! isset( $atts['close'] ) || empty( $atts['close'] ) ) {
1590 + $has_nav = ! empty( $atts['form'] ) && empty( $atts['is_template'] );
1591 +
1592 + if ( empty( $atts['close'] ) ) {
1171 1593 $atts['close'] = admin_url( 'admin.php?page=formidable' );
1172 1594 }
1595 +
1173 1596 if ( ! isset( $atts['import_link'] ) ) {
1174 1597 $atts['import_link'] = false;
1175 1598 }
1176 1599
1177 - include( self::plugin_path() . '/classes/views/shared/admin-header.php' );
1600 + include self::plugin_path() . '/classes/views/shared/admin-header.php';
1178 1601 }
1179 1602
1180 1603 /**
1181 1604 * @since 6.0
@@ -1180,16 +1603,19 @@
1180 1603 /**
1181 1604 * @since 6.0
1182 1605 *
1183 1606 * @param string $type
1607 + *
1184 1608 * @return void
1185 1609 */
1186 1610 public static function import_link( $type = 'secondary' ) {
1611 + // phpcs:disable Generic.WhiteSpace.ScopeIndent
1187 1612 ?>
1188 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">
1189 1614 <?php esc_html_e( 'Import', 'formidable' ); ?>
1190 1615 </a>
1191 1616 <?php
1617 + // phpcs:enable Generic.WhiteSpace.ScopeIndent
1192 1618 }
1193 1619
1194 1620 /**
1195 1621 * Print applicable admin banner.
@@ -1196,8 +1622,9 @@
1196 1622 *
1197 1623 * @since 5.4.2
1198 1624 *
1199 1625 * @param bool $should_show_lite_upgrade
1626 + *
1200 1627 * @return void
1201 1628 */
1202 1629 public static function print_admin_banner( $should_show_lite_upgrade ) {
1203 1630 if ( ! current_user_can( 'administrator' ) ) {
@@ -1204,19 +1631,44 @@
1204 1631 FrmInbox::maybe_show_banner();
1205 1632 return;
1206 1633 }
1207 1634
1208 - 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() ) {
1209 1636 // Print license warning or inbox banner and exit if either prints.
1210 1637 // And exit before printing the upgrade bar if it shouldn't be shown.
1211 1638 return;
1212 1639 }
1640 + // phpcs:disable Generic.WhiteSpace.ScopeIndent
1213 1641 ?>
1214 1642 <div class="frm-upgrade-bar">
1215 - <span>You're using Formidable Forms Lite. To unlock more features consider</span>
1216 - <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>
1217 1668 </div>
1218 1669 <?php
1670 + // phpcs:enable Generic.WhiteSpace.ScopeIndent
1219 1671 }
1220 1672
1221 1673 /**
1222 1674 * @since 5.4.2
@@ -1230,14 +1682,18 @@
1230 1682 /**
1231 1683 * Render a button for a new item (Form, Application, etc).
1232 1684 *
1233 1685 * @since 3.0
1686 + *
1234 1687 * @param array $atts {
1688 + * Details about the button.
1689 + *
1235 1690 * @type array $link_hook Custom link hook, calls do_action and exits early.
1236 1691 * @type string $new_link Href value, default #.
1237 1692 * @type string $class Custom class names, space separated.
1238 1693 * @type string $button_text Button text. Default "Add New".
1239 1694 * }
1695 + *
1240 1696 * @return void
1241 1697 */
1242 1698 public static function add_new_item_link( $atts ) {
1243 1699 if ( isset( $atts['link_hook'] ) ) {
@@ -1244,9 +1700,9 @@
1244 1700 do_action( $atts['link_hook']['hook'], $atts['link_hook']['param'] );
1245 1701 return;
1246 1702 }
1247 1703
1248 - 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'] ) ) {
1249 1705 // Do not render a button if none of these attributes are set.
1250 1706 return;
1251 1707 }
1252 1708
@@ -1252,12 +1708,8 @@
1252 1708
1253 1709 $href = ! empty( $atts['new_link'] ) ? esc_url( $atts['new_link'] ) : '#';
1254 1710 $class = 'button button-primary frm-button-primary';
1255 1711
1256 - if ( ! empty( $atts['trigger_new_form_modal'] ) ) {
1257 - $class .= ' frm-trigger-new-form-modal';
1258 - }
1259 -
1260 1712 if ( ! empty( $atts['class'] ) ) {
1261 1713 $class .= ' ' . $atts['class'];
1262 1714 }
1263 1715
@@ -1267,8 +1719,12 @@
1267 1719 }
1268 1720
1269 1721 /**
1270 1722 * @since 3.06
1723 + *
1724 + * @param array $atts
1725 + *
1726 + * @return void
1271 1727 */
1272 1728 public static function show_search_box( $atts ) {
1273 1729 $defaults = array(
1274 1730 'placeholder' => '',
@@ -1274,10 +1730,12 @@
1274 1730 'placeholder' => '',
1275 1731 'tosearch' => '',
1276 1732 'text' => __( 'Search', 'formidable' ),
1277 1733 'input_id' => '',
1734 + 'value' => false,
1735 + 'class' => '',
1278 1736 );
1279 - $atts = array_merge( $defaults, $atts );
1737 + $atts = array_merge( $defaults, $atts );
1280 1738
1281 1739 if ( $atts['input_id'] === 'template' && empty( $atts['tosearch'] ) ) {
1282 1740 $atts['tosearch'] = 'frm-card';
1283 1741 }
@@ -1282,8 +1740,9 @@
1282 1740 $atts['tosearch'] = 'frm-card';
1283 1741 }
1284 1742
1285 1743 $class = 'frm-search-input';
1744 +
1286 1745 if ( ! empty( $atts['tosearch'] ) ) {
1287 1746 $class .= ' frm-auto-search';
1288 1747 }
1289 1748
@@ -1288,21 +1747,35 @@
1288 1747 }
1289 1748
1290 1749 $input_id = $atts['input_id'] . '-search-input';
1291 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
1292 1771 ?>
1293 - <p class="frm-search">
1772 + <p class="frm-search <?php echo esc_attr( $atts['class'] ); ?>">
1294 1773 <label class="screen-reader-text" for="<?php echo esc_attr( $input_id ); ?>">
1295 1774 <?php echo esc_html( $atts['text'] ); ?>:
1296 1775 </label>
1297 - <span class="frmfont frm_search_icon"></span>
1298 - <input type="search" id="<?php echo esc_attr( $input_id ); ?>" name="s"
1299 - value="<?php _admin_search_query(); ?>" placeholder="<?php echo esc_attr( $atts['placeholder'] ); ?>"
1300 - class="<?php echo esc_attr( $class ); ?>" data-tosearch="<?php echo esc_attr( $atts['tosearch'] ); ?>"
1301 - <?php if ( ! empty( $atts['tosearch'] ) ) { ?>
1302 - autocomplete="off"
1303 - <?php } ?>
1304 - />
1776 + <?php self::icon_by_class( 'frmfont frm_search_icon frm_svg20' ); ?>
1777 + <input <?php self::array_to_html_params( $input_atts, true ); ?> />
1305 1778 <?php
1306 1779 if ( empty( $atts['tosearch'] ) ) {
1307 1780 submit_button( $atts['text'], 'button-secondary', '', false, array( 'id' => 'search-submit' ) );
1308 1781 }
@@ -1308,16 +1781,21 @@
1308 1781 }
1309 1782 ?>
1310 1783 </p>
1311 1784 <?php
1785 + // phpcs:enable Generic.WhiteSpace.ScopeIndent
1312 1786 }
1313 1787
1314 1788 /**
1315 - * @param string $type
1789 + * @param string $type Hook type slug.
1790 + * @param object|null $object Optional related object.
1791 + *
1792 + * @return void
1316 1793 */
1317 1794 public static function trigger_hook_load( $type, $object = null ) {
1318 1795 // Only load the form hooks once.
1319 1796 $hooks_loaded = apply_filters( 'frm_' . $type . '_hooks_loaded', false, $object );
1797 +
1320 1798 if ( ! $hooks_loaded ) {
1321 1799 do_action( 'frm_load_' . $type . '_hooks' );
1322 1800 }
1323 1801 }
@@ -1322,11 +1800,14 @@
1322 1800 }
1323 1801 }
1324 1802
1325 1803 /**
1326 - * 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.
1327 1806 *
1328 1807 * @since 3.0
1808 + *
1809 + * @return void
1329 1810 */
1330 1811 public static function save_combined_js() {
1331 1812 $file_atts = apply_filters(
1332 1813 'frm_js_location',
@@ -1339,10 +1820,34 @@
1339 1820
1340 1821 $files = array(
1341 1822 self::plugin_path() . '/js/formidable.min.js',
1342 1823 );
1824 + /**
1825 + * @param array $files
1826 + */
1343 1827 $files = apply_filters( 'frm_combined_js_files', $files );
1344 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 );
1345 1850 }
1346 1851
1347 1852 /**
1348 1853 * Check a value from a shortcode to see if true or false.
@@ -1349,14 +1854,14 @@
1349 1854 * True when value is 1, true, 'true', 'yes'
1350 1855 *
1351 1856 * @since 1.07.10
1352 1857 *
1353 - * @param string $value The value to compare
1858 + * @param bool|int|string $value The value to compare.
1354 1859 *
1355 - * @return boolean True or False
1860 + * @return bool
1356 1861 */
1357 1862 public static function is_true( $value ) {
1358 - return ( true === $value || 1 == $value || 'true' == $value || 'yes' == $value );
1863 + return true === $value || '1' === (string) $value || 'true' === $value || 'yes' === $value;
1359 1864 }
1360 1865
1361 1866 /**
1362 1867 * Gets all post from a specific post type.
@@ -1364,8 +1869,9 @@
1364 1869 *
1365 1870 * @since 4.10.01 Add `$post_type` argument.
1366 1871 *
1367 1872 * @param string $post_type Post type to query. Default is `page`.
1873 + *
1368 1874 * @return WP_Post[]
1369 1875 */
1370 1876 public static function get_pages( $post_type = 'page' ) {
1371 1877 $query = array(
@@ -1384,8 +1890,9 @@
1384 1890 *
1385 1891 * @since 5.0.09
1386 1892 *
1387 1893 * @param string $post_type Post type to query. Default is `page`.
1894 + *
1388 1895 * @return array
1389 1896 */
1390 1897 public static function get_post_ids_and_titles( $post_type = 'page' ) {
1391 1898 return FrmDb::get_results(
@@ -1410,13 +1917,12 @@
1410 1917 *
1411 1918 * @param array $args Selection arguments.
1412 1919 */
1413 1920 public static function maybe_autocomplete_pages_options( $args ) {
1414 - $args = self::preformat_selection_args( $args );
1415 -
1921 + $args = self::preformat_selection_args( $args );
1416 1922 $pages_count = wp_count_posts( $args['post_type'] );
1417 1923
1418 - if ( $pages_count->publish <= 50 ) {
1924 + if ( ! isset( $pages_count->publish ) || $pages_count->publish <= 50 ) {
1419 1925 self::wp_pages_dropdown( $args );
1420 1926 return;
1421 1927 }
1422 1928
@@ -1422,9 +1928,9 @@
1422 1928
1423 1929 wp_enqueue_script( 'jquery-ui-autocomplete' );
1424 1930
1425 1931 $selected = self::get_post_param( $args['field_name'], $args['page_id'], 'absint' );
1426 - $title = '';
1932 + $title = '';
1427 1933
1428 1934 if ( $selected ) {
1429 1935 $title = get_the_title( $selected );
1430 1936 }
@@ -1440,18 +1946,119 @@
1440 1946 <?php
1441 1947 }
1442 1948
1443 1949 /**
1444 - * @param array $args
1445 - * @param string $page_id Deprecated.
1446 - * @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.
1447 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 + */
1448 2055 public static function wp_pages_dropdown( $args = array(), $page_id = '', $truncate = false ) {
1449 2056 self::prep_page_dropdown_params( $page_id, $truncate, $args );
1450 2057
1451 2058 $pages = self::get_post_ids_and_titles( $args['post_type'] );
1452 2059 $selected = self::get_post_param( $args['field_name'], $args['page_id'], 'absint' );
1453 -
2060 + // phpcs:disable Generic.WhiteSpace.ScopeIndent
1454 2061 ?>
1455 2062 <select name="<?php echo esc_attr( $args['field_name'] ); ?>" id="<?php echo esc_attr( $args['field_name'] ); ?>" class="frm-pages-dropdown">
1456 2063 <option value=""><?php echo esc_html( $args['placeholder'] ); ?></option>
1457 2064 <?php foreach ( $pages as $page ) { ?>
@@ -1460,8 +2067,9 @@
1460 2067 </option>
1461 2068 <?php } ?>
1462 2069 </select>
1463 2070 <?php
2071 + // phpcs:enable Generic.WhiteSpace.ScopeIndent
1464 2072 }
1465 2073
1466 2074 /**
1467 2075 * Fill in missing parameters passed to wp_pages_dropdown().
@@ -1467,8 +2075,14 @@
1467 2075 * Fill in missing parameters passed to wp_pages_dropdown().
1468 2076 * This is for reverse compatibility with switching 3 params to 1.
1469 2077 *
1470 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
1471 2085 */
1472 2086 private static function prep_page_dropdown_params( $page_id, $truncate, &$args ) {
1473 2087 if ( ! is_array( $args ) ) {
1474 2088 $args = array(
@@ -1485,16 +2099,20 @@
1485 2099 * Filter to format args for page dropdown or autocomplete
1486 2100 *
1487 2101 * @since 4.03.06
1488 2102 * @since 4.10.01 Added `post_type` and `autocomplete_placeholder` to the arguments array.
2103 + *
2104 + * @param array $args
2105 + *
2106 + * @return array
1489 2107 */
1490 2108 private static function preformat_selection_args( $args ) {
1491 2109 $defaults = array(
1492 - 'truncate' => false,
1493 - 'placeholder' => ' ',
1494 - 'field_name' => '',
1495 - 'page_id' => '',
1496 - 'post_type' => 'page',
2110 + 'truncate' => false,
2111 + 'placeholder' => ' ',
2112 + 'field_name' => '',
2113 + 'page_id' => '',
2114 + 'post_type' => 'page',
1497 2115 'autocomplete_placeholder' => __( 'Select a Page', 'formidable' ),
1498 2116 );
1499 2117
1500 2118 return array_merge( $defaults, $args );
@@ -1499,13 +2117,18 @@
1499 2117
1500 2118 return array_merge( $defaults, $args );
1501 2119 }
1502 2120
2121 + /**
2122 + * @param int $post_id
2123 + *
2124 + * @return string
2125 + */
1503 2126 public static function post_edit_link( $post_id ) {
1504 2127 $post = get_post( $post_id );
2128 +
1505 2129 if ( $post ) {
1506 2130 $post_url = admin_url( 'post.php?post=' . $post_id . '&action=edit' );
1507 -
1508 2131 return '<a href="' . esc_url( $post_url ) . '">' . self::truncate( $post->post_title, 50 ) . '</a>';
1509 2132 }
1510 2133
1511 2134 return '';
@@ -1518,11 +2141,9 @@
1518 2141 *
1519 2142 * @return bool
1520 2143 */
1521 2144 public static function is_full_screen() {
1522 - return self::is_form_builder_page() ||
1523 - self::is_style_editor_page() ||
1524 - 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();
1525 2146 }
1526 2147
1527 2148 /**
1528 2149 * Check if user is on the style editor or its alternative URL.
@@ -1532,8 +2153,9 @@
1532 2153 * @since 5.5.3
1533 2154 * @since 6.0 Added the $view parameter. Previously there was only a 'edit' view.
1534 2155 *
1535 2156 * @param string $view Supports 'edit', 'list', and ''. If '', both 'edit' and 'list' will match.
2157 + *
1536 2158 * @return bool
1537 2159 */
1538 2160 public static function is_style_editor_page( $view = '' ) {
1539 2161 if ( ! self::is_admin_page( 'formidable-styles' ) && ! self::is_admin_page( 'formidable-styles2' ) ) {
@@ -1565,35 +2187,29 @@
1565 2187 return self::is_admin_page( 'formidable-views-editor' );
1566 2188 }
1567 2189
1568 2190 /**
1569 - * @since 4.0
1570 - * @deprecated 5.5.3
2191 + * @param string $field_name
2192 + * @param array|string $capability
2193 + * @param string $multiple 'single' and 'multiple'.
1571 2194 */
1572 - public static function maybe_full_screen_link( $link ) {
1573 - _deprecated_function( __METHOD__, '5.5.3' );
1574 - return $link;
1575 - }
1576 -
1577 - /**
1578 - * @param string $field_name
1579 - * @param string|array $capability
1580 - * @param string $multiple 'single' and 'multiple'
1581 - */
1582 2195 public static function wp_roles_dropdown( $field_name, $capability, $multiple = 'single' ) {
2196 + // phpcs:disable Generic.WhiteSpace.ScopeIndent
1583 2197 ?>
1584 2198 <select name="<?php echo esc_attr( $field_name ); ?>" id="<?php echo esc_attr( $field_name ); ?>"
1585 - <?php echo ( 'multiple' === $multiple ) ? 'multiple="multiple"' : ''; ?>
2199 + <?php echo 'multiple' === $multiple ? 'multiple="multiple"' : ''; ?>
1586 2200 class="frm_multiselect">
1587 2201 <?php self::roles_options( $capability ); ?>
1588 2202 </select>
1589 2203 <?php
2204 + // phpcs:enable Generic.WhiteSpace.ScopeIndent
1590 2205 }
1591 2206
1592 2207 /**
1593 2208 * @since 4.07
2209 + *
1594 2210 * @param array|string $selected
1595 - * @param string $current
2211 + * @param string $current
1596 2212 */
1597 2213 private static function selected( $selected, $current ) {
1598 2214 if ( is_callable( 'FrmProAppHelper::selected' ) ) {
1599 2215 FrmProAppHelper::selected( $selected, $current );
@@ -1602,12 +2218,13 @@
1602 2218 }
1603 2219 }
1604 2220
1605 2221 /**
1606 - * @param string|array $capability
2222 + * @param array|string $capability
1607 2223 */
1608 2224 public static function roles_options( $capability ) {
1609 2225 global $frm_vars;
2226 +
1610 2227 if ( isset( $frm_vars['editable_roles'] ) ) {
1611 2228 $editable_roles = $frm_vars['editable_roles'];
1612 2229 } else {
1613 2230 $editable_roles = get_editable_roles();
@@ -1616,9 +2233,9 @@
1616 2233
1617 2234 foreach ( $editable_roles as $role => $details ) {
1618 2235 $name = translate_user_role( $details['name'] );
1619 2236 ?>
1620 - <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>
1621 2238 <?php
1622 2239 unset( $role, $details );
1623 2240 }
1624 2241 }
@@ -1628,8 +2245,9 @@
1628 2245 *
1629 2246 * @since 5.0 Parameter `$type` supports `pro_only` value.
1630 2247 *
1631 2248 * @param string $type Supports `auto`, `pro`, or `pro_only`.
2249 + *
1632 2250 * @return array
1633 2251 */
1634 2252 public static function frm_capabilities( $type = 'auto' ) {
1635 2253 if ( ! self::pro_is_installed() && ! in_array( $type, array( 'pro', 'pro_only' ), true ) ) {
@@ -1639,9 +2257,8 @@
1639 2257 $pro_cap = array(
1640 2258 'frm_create_entries' => __( 'Add Entries from Admin Area', 'formidable' ),
1641 2259 'frm_edit_entries' => __( 'Edit Entries from Admin Area', 'formidable' ),
1642 2260 'frm_view_reports' => __( 'View Reports', 'formidable' ),
1643 - 'frm_edit_displays' => __( 'Add/Edit Views', 'formidable' ),
1644 2261 );
1645 2262 /**
1646 2263 * @since 5.3.1
1647 2264 *
@@ -1664,9 +2281,9 @@
1664 2281 * @return array<string,string>
1665 2282 */
1666 2283 private static function get_lite_capabilities() {
1667 2284 return array(
1668 - 'frm_view_forms' => __( 'View Forms', 'formidable' ),
2285 + 'frm_view_forms' => __( 'View Forms List', 'formidable' ),
1669 2286 'frm_edit_forms' => __( 'Add and Edit Forms', 'formidable' ),
1670 2287 'frm_delete_forms' => __( 'Delete Forms', 'formidable' ),
1671 2288 'frm_change_settings' => __( 'Access this Settings Page', 'formidable' ),
1672 2289 'frm_view_entries' => __( 'View Entries from Admin Area', 'formidable' ),
@@ -1695,21 +2312,18 @@
1695 2312 if ( $role === 'loggedin' || ! $role ) {
1696 2313 return is_user_logged_in();
1697 2314 }
1698 2315
1699 - if ( $role == 1 ) {
2316 + if ( (int) $role === 1 ) {
1700 2317 $role = 'administrator';
1701 2318 }
1702 2319
1703 - if ( ! is_user_logged_in() ) {
1704 - return false;
1705 - }
1706 -
1707 - return current_user_can( $role );
2320 + return is_user_logged_in() ? current_user_can( $role ) : false;
1708 2321 }
1709 2322
1710 2323 /**
1711 - * @param string|array $needed_role
2324 + * @param array|string $needed_role
2325 + *
1712 2326 * @return bool
1713 2327 */
1714 2328 public static function user_has_permission( $needed_role ) {
1715 2329 if ( is_array( $needed_role ) ) {
@@ -1723,18 +2337,20 @@
1723 2337 }
1724 2338
1725 2339 $can = self::current_user_can( $needed_role );
1726 2340
1727 - if ( $can || in_array( $needed_role, array( '-1', 'loggedout' ) ) ) {
2341 + if ( $can || in_array( $needed_role, array( '-1', 'loggedout' ), true ) ) {
1728 2342 return $can;
1729 2343 }
1730 2344
1731 2345 $roles = array( 'administrator', 'editor', 'author', 'contributor', 'subscriber' );
2346 +
1732 2347 foreach ( $roles as $role ) {
1733 2348 if ( current_user_can( $role ) ) {
1734 2349 return true;
1735 2350 }
1736 - if ( $role == $needed_role ) {
2351 +
2352 + if ( $role === $needed_role ) {
1737 2353 break;
1738 2354 }
1739 2355 }
1740 2356
@@ -1752,11 +2368,11 @@
1752 2368 if ( ! current_user_can( 'administrator' ) || current_user_can( 'frm_view_forms' ) ) {
1753 2369 return;
1754 2370 }
1755 2371
1756 - $user_id = get_current_user_id();
1757 - $user = new WP_User( $user_id );
2372 + $user = new WP_User( get_current_user_id() );
1758 2373 $frm_roles = self::frm_capabilities();
2374 +
1759 2375 foreach ( $frm_roles as $frm_role => $frm_role_description ) {
1760 2376 $user->add_cap( $frm_role );
1761 2377 unset( $frm_role, $frm_role_description );
1762 2378 }
@@ -1765,35 +2381,47 @@
1765 2381 /**
1766 2382 * Make sure admins have permission to see the menu items
1767 2383 *
1768 2384 * @since 2.0.6
2385 + *
2386 + * @param string $cap
2387 + *
2388 + * @return void
1769 2389 */
1770 2390 public static function force_capability( $cap = 'frm_change_settings' ) {
1771 - if ( current_user_can( 'administrator' ) && ! current_user_can( $cap ) ) {
1772 - $role = get_role( 'administrator' );
1773 - $frm_roles = self::frm_capabilities();
1774 - foreach ( $frm_roles as $frm_role => $frm_role_description ) {
1775 - $role->add_cap( $frm_role );
1776 - }
2391 + if ( ! current_user_can( 'administrator' ) || current_user_can( $cap ) ) {
2392 + return;
1777 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 + }
1778 2401 }
1779 2402
1780 2403 /**
1781 - * Check if the user has permision for action.
2404 + * Check if the user has permission for action.
1782 2405 * Return permission message and stop the action if no permission
1783 2406 *
1784 2407 * @since 2.0
1785 2408 *
1786 2409 * @param string $permission
2410 + * @param string $show_message
1787 2411 */
1788 2412 public static function permission_check( $permission, $show_message = 'show' ) {
1789 2413 $permission_error = self::permission_nonce_error( $permission );
1790 - if ( $permission_error !== false ) {
1791 - if ( 'hide' == $show_message ) {
1792 - $permission_error = '';
1793 - }
1794 - wp_die( esc_html( $permission_error ) );
2414 +
2415 + if ( $permission_error === false ) {
2416 + return;
1795 2417 }
2418 +
2419 + if ( 'hide' === $show_message ) {
2420 + $permission_error = '';
2421 + }
2422 +
2423 + wp_die( esc_html( $permission_error ) );
1796 2424 }
1797 2425
1798 2426 /**
1799 2427 * Check user permission and nonce
@@ -1800,24 +2428,27 @@
1800 2428 *
1801 2429 * @since 2.0
1802 2430 *
1803 2431 * @param string $permission
2432 + * @param string $nonce_name
2433 + * @param string $nonce
1804 2434 *
1805 2435 * @return false|string The permission message or false if allowed
1806 2436 */
1807 2437 public static function permission_nonce_error( $permission, $nonce_name = '', $nonce = '' ) {
1808 - if ( ! empty( $permission ) && ! current_user_can( $permission ) && ! current_user_can( 'administrator' ) ) {
2438 + if ( $permission && ! current_user_can( $permission ) && ! current_user_can( 'administrator' ) ) {
1809 2439 $frm_settings = self::get_settings();
1810 -
1811 2440 return $frm_settings->admin_permission;
1812 2441 }
1813 2442
1814 2443 $error = false;
1815 - if ( empty( $nonce_name ) ) {
2444 +
2445 + if ( ! $nonce_name ) {
1816 2446 return $error;
1817 2447 }
1818 2448
1819 - $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 +
1820 2451 if ( $_REQUEST && ( ! isset( $_REQUEST[ $nonce_name ] ) || ! wp_verify_nonce( $nonce_value, $nonce ) ) ) {
1821 2452 $frm_settings = self::get_settings();
1822 2453 $error = $frm_settings->admin_permission;
1823 2454 }
@@ -1824,8 +2455,14 @@
1824 2455
1825 2456 return $error;
1826 2457 }
1827 2458
2459 + /**
2460 + * @param array|string $values
2461 + * @param string $current
2462 + *
2463 + * @return void
2464 + */
1828 2465 public static function checked( $values, $current ) {
1829 2466 if ( self::check_selected( $values, $current ) ) {
1830 2467 echo ' checked="checked"';
1831 2468 }
@@ -1830,40 +2467,74 @@
1830 2467 echo ' checked="checked"';
1831 2468 }
1832 2469 }
1833 2470
2471 + /**
2472 + * @param array|string $values
2473 + * @param string|null $current
2474 + *
2475 + * @return bool
2476 + */
1834 2477 public static function check_selected( $values, $current ) {
1835 2478 $values = self::recursive_function_map( $values, 'trim' );
1836 2479 $values = self::recursive_function_map( $values, 'htmlspecialchars_decode' );
1837 - $current = htmlspecialchars_decode( trim( $current ) );
2480 + $current = is_null( $current ) ? '' : htmlspecialchars_decode( trim( $current ) );
1838 2481
1839 - 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;
1840 2484 }
1841 2485
2486 + /**
2487 + * @param array|string $value
2488 + * @param callable|string $function
2489 + *
2490 + * @return array|string
2491 + */
1842 2492 public static function recursive_function_map( $value, $function ) {
1843 2493 if ( is_array( $value ) ) {
1844 2494 $original_function = $function;
1845 - if ( count( $value ) ) {
1846 - $function = explode( ', ', FrmDb::prepare_array_values( $value, $function ) );
1847 - } else {
1848 - $function = array( $function );
1849 - }
1850 - if ( ! self::is_assoc( $value ) ) {
1851 - $value = array_map( array( 'FrmAppHelper', 'recursive_function_map' ), $value, $function );
1852 - } else {
2495 + $function = count( $value ) ? explode( ', ', FrmDb::prepare_array_values( $value, $function ) ) : array( $function );
2496 +
2497 + if ( self::is_assoc( $value ) ) {
1853 2498 foreach ( $value as $k => $v ) {
1854 2499 if ( ! is_array( $v ) ) {
1855 2500 $value[ $k ] = call_user_func( $original_function, $v );
1856 2501 }
1857 2502 }
2503 + } else {
2504 + $value = array_map( array( 'FrmAppHelper', 'recursive_function_map' ), $value, $function );
1858 2505 }
1859 2506 } else {
2507 + $value = self::maybe_update_value_if_null( $value, $function );
1860 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 '';
1861 2527 }
1862 2528
1863 2529 return $value;
1864 2530 }
1865 2531
2532 + /**
2533 + * @param array $array
2534 + *
2535 + * @return bool
2536 + */
1866 2537 public static function is_assoc( $array ) {
1867 2538 return (bool) count( array_filter( array_keys( $array ), 'is_string' ) );
1868 2539 }
1869 2540
@@ -1868,20 +2539,24 @@
1868 2539 }
1869 2540
1870 2541 /**
1871 2542 * Flatten a multi-dimensional array
2543 + *
2544 + * @param array $array
2545 + * @param string $keys
2546 + *
2547 + * @return array
1872 2548 */
1873 2549 public static function array_flatten( $array, $keys = 'keep' ) {
1874 2550 $return = array();
2551 +
1875 2552 foreach ( $array as $key => $value ) {
1876 2553 if ( is_array( $value ) ) {
1877 2554 $return = array_merge( $return, self::array_flatten( $value, $keys ) );
2555 + } elseif ( $keys === 'keep' ) {
2556 + $return[ $key ] = $value;
1878 2557 } else {
1879 - if ( $keys == 'keep' ) {
1880 - $return[ $key ] = $value;
1881 - } else {
1882 - $return[] = $value;
1883 - }
2558 + $return[] = $value;
1884 2559 }
1885 2560 }
1886 2561
1887 2562 return $return;
@@ -1886,16 +2561,43 @@
1886 2561
1887 2562 return $return;
1888 2563 }
1889 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 + */
1890 2586 public static function esc_textarea( $text, $is_rich_text = false ) {
1891 2587 $safe_text = str_replace( '&quot;', '"', $text );
2588 +
1892 2589 if ( ! $is_rich_text ) {
1893 2590 $safe_text = htmlspecialchars( $safe_text, ENT_NOQUOTES );
1894 2591 }
2592 +
1895 2593 $safe_text = str_replace( '&amp; ', '& ', $safe_text );
1896 2594
1897 - 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 );
1898 2600 }
1899 2601
1900 2602 /**
1901 2603 * Add auto paragraphs to text areas
@@ -1900,44 +2602,59 @@
1900 2602 /**
1901 2603 * Add auto paragraphs to text areas
1902 2604 *
1903 2605 * @since 2.0
2606 + *
2607 + * @param mixed $content
2608 + *
2609 + * @return mixed
1904 2610 */
1905 2611 public static function use_wpautop( $content ) {
1906 - if ( apply_filters( 'frm_use_wpautop', true ) && ! is_array( $content ) ) {
1907 - $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 ) );
1908 2614 }
1909 2615
1910 2616 return $content;
1911 2617 }
1912 2618
2619 + /**
2620 + * Replace quotes with their HTML entities.
2621 + *
2622 + * @param string $val
2623 + *
2624 + * @return string
2625 + */
1913 2626 public static function replace_quotes( $val ) {
1914 2627 // Replace double quotes.
1915 2628 $val = str_replace( array( '&#8220;', '&#8221;', '&#8243;' ), '"', $val );
1916 2629
1917 2630 // Replace single quotes.
1918 - $val = str_replace( array( '&#8216;', '&#8217;', '&#8242;', '&prime;', '&rsquo;', '&lsquo;' ), "'", $val );
1919 -
1920 - return $val;
2631 + return str_replace( array( '&#8216;', '&#8217;', '&#8242;', '&prime;', '&rsquo;', '&lsquo;' ), "'", $val );
1921 2632 }
1922 2633
1923 2634 /**
1924 - * @param string $handle
2635 + * @param string $handle
2636 + * @param int|string $default
2637 + *
2638 + * @return int|string
1925 2639 */
1926 2640 public static function script_version( $handle, $default = 0 ) {
1927 2641 global $wp_scripts;
2642 +
1928 2643 if ( ! $wp_scripts ) {
1929 2644 return $default;
1930 2645 }
1931 2646
1932 2647 $ver = $default;
2648 +
1933 2649 if ( ! isset( $wp_scripts->registered[ $handle ] ) ) {
1934 2650 return $ver;
1935 2651 }
1936 2652
1937 2653 $query = $wp_scripts->registered[ $handle ];
2654 +
1938 2655 if ( is_object( $query ) && ! empty( $query->ver ) ) {
1939 - $ver = $query->ver;
2656 + return $query->ver;
1940 2657 }
1941 2658
1942 2659 return $ver;
1943 2660 }
@@ -1946,17 +2663,23 @@
1946 2663 * @since 5.0.13 added $echo param.
1947 2664 *
1948 2665 * @param string $url
1949 2666 * @param bool $echo
1950 - * @return string|void
2667 + *
2668 + * @return string|null
1951 2669 */
1952 2670 public static function js_redirect( $url, $echo = false ) {
1953 - $callback = function() use ( $url ) {
2671 + $callback = function () use ( $url ) {
1954 2672 echo '<script type="text/javascript">window.location="' . esc_url_raw( $url ) . '"</script>';
1955 2673 };
1956 2674 return self::clip( $callback, $echo );
1957 2675 }
1958 2676
2677 + /**
2678 + * @param int|string $user_id
2679 + *
2680 + * @return int|string
2681 + */
1959 2682 public static function get_user_id_param( $user_id ) {
1960 2683 if ( ! $user_id || is_numeric( $user_id ) ) {
1961 2684 return $user_id;
1962 2685 }
@@ -1961,16 +2684,13 @@
1961 2684 return $user_id;
1962 2685 }
1963 2686
1964 2687 $user_id = sanitize_text_field( $user_id );
1965 - if ( $user_id == 'current' ) {
2688 +
2689 + if ( $user_id === 'current' ) {
1966 2690 $user_id = get_current_user_id();
1967 2691 } else {
1968 - if ( is_email( $user_id ) ) {
1969 - $user = get_user_by( 'email', $user_id );
1970 - } else {
1971 - $user = get_user_by( 'login', $user_id );
1972 - }
2692 + $user = is_email( $user_id ) ? get_user_by( 'email', $user_id ) : get_user_by( 'login', $user_id );
1973 2693
1974 2694 if ( $user ) {
1975 2695 $user_id = $user->ID;
1976 2696 }
@@ -1979,8 +2699,14 @@
1979 2699
1980 2700 return $user_id;
1981 2701 }
1982 2702
2703 + /**
2704 + * @param string $filename
2705 + * @param array $atts
2706 + *
2707 + * @return false|string
2708 + */
1983 2709 public static function get_file_contents( $filename, $atts = array() ) {
1984 2710 if ( ! is_file( $filename ) ) {
1985 2711 return false;
1986 2712 }
@@ -1986,13 +2712,10 @@
1986 2712 }
1987 2713
1988 2714 extract( $atts ); // phpcs:ignore WordPress.PHP.DontExtract
1989 2715 ob_start();
1990 - include( $filename );
1991 - $contents = ob_get_contents();
1992 - ob_end_clean();
1993 -
1994 - return $contents;
2716 + include $filename;
2717 + return ob_get_clean();
1995 2718 }
1996 2719
1997 2720 /**
1998 2721 * @param string $name
@@ -2002,8 +2725,9 @@
2002 2725 * @param int $num_chars
2003 2726 */
2004 2727 public static function get_unique_key( $name, $table_name, $column, $id = 0, $num_chars = 5 ) {
2005 2728 $key = '';
2729 +
2006 2730 if ( $name ) {
2007 2731 $key = sanitize_key( $name );
2008 2732 $key = self::maybe_clear_long_key( $key, $column );
2009 2733 }
@@ -2023,31 +2747,31 @@
2023 2747 $column
2024 2748 );
2025 2749
2026 2750 // Create a unique field id if it has already been used.
2027 - if ( in_array( $key, $similar_keys, true ) ) {
2028 - $key = self::maybe_truncate_key_before_appending( $column, $key );
2751 + if ( ! in_array( $key, $similar_keys, true ) ) {
2752 + return $key;
2753 + }
2029 2754
2030 - /**
2031 - * Allow for a custom separator between the attempted key and the generated suffix.
2032 - *
2033 - * @since 5.2.03
2034 - *
2035 - * @param string $separator. Default empty.
2036 - * @param string $key the key without the added suffix.
2037 - */
2038 - $separator = apply_filters( 'frm_unique_' . $column . '_separator', '', $key );
2755 + $key = self::maybe_truncate_key_before_appending( $column, $key );
2039 2756
2040 - $suffix = 2;
2041 - do {
2042 - $key_check = $key . $separator . $suffix;
2043 - ++$suffix;
2044 - } 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 );
2045 2766
2046 - $key = $key_check;
2047 - }
2767 + $suffix = 2;
2768 + do {
2769 + $key_check = $key . $separator . $suffix;
2770 + ++$suffix;
2771 + } while ( in_array( $key_check, $similar_keys, true ) );
2048 2772
2049 - return $key;
2773 + return $key_check;
2050 2774 }
2051 2775
2052 2776 /**
2053 2777 * Avoid trying to append to a really long key,
@@ -2054,20 +2778,26 @@
2054 2778 * The database limit is 100 for form and field keys so we want to avoid getting too close.
2055 2779 *
2056 2780 * @param string $column
2057 2781 * @param string $key
2782 + *
2058 2783 * @return string
2059 2784 */
2060 2785 private static function maybe_truncate_key_before_appending( $column, $key ) {
2061 - if ( in_array( $column, array( 'form_key', 'field_key' ), true ) ) {
2062 - $max_key_length_before_truncating = 60;
2063 - if ( strlen( $key ) > $max_key_length_before_truncating ) {
2064 - $key = substr( $key, 0, $max_key_length_before_truncating );
2065 - if ( is_numeric( $key ) ) {
2066 - $key .= 'a';
2067 - }
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';
2068 2797 }
2069 2798 }
2799 +
2070 2800 return $key;
2071 2801 }
2072 2802
2073 2803 /**
@@ -2074,29 +2804,36 @@
2074 2804 * Possibly reset a key to avoid conflicts with column size limits.
2075 2805 *
2076 2806 * @param string $key
2077 2807 * @param string $column
2808 + *
2078 2809 * @return string either the original key value, or an empty string if the key was too long.
2079 2810 */
2080 2811 private static function maybe_clear_long_key( $key, $column ) {
2081 2812 if ( 'field_key' === $column && strlen( $key ) >= 70 ) {
2082 - $key = '';
2813 + return '';
2083 2814 }
2084 2815 return $key;
2085 2816 }
2086 2817
2087 2818 /**
2819 + * @since 6.21 This is changed from `private` to `public`.
2820 + *
2088 2821 * @param int $num_chars
2822 + *
2089 2823 * @return string
2090 2824 */
2091 - private static function generate_new_key( $num_chars ) {
2092 - $max_slug_value = pow( 36, $num_chars );
2093 - $min_slug_value = 37; // we want to have at least 2 characters in the slug
2094 - 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 );
2095 2831 }
2096 2832
2097 2833 /**
2098 2834 * @param string $key
2835 + *
2099 2836 * @return string
2100 2837 */
2101 2838 private static function prevent_numeric_and_reserved_keys( $key ) {
2102 2839 if ( is_numeric( $key ) ) {
@@ -2110,12 +2847,14 @@
2110 2847 'editlink',
2111 2848 'siteurl',
2112 2849 'evenodd',
2113 2850 );
2851 +
2114 2852 if ( in_array( $key, $not_allowed, true ) ) {
2115 2853 $key .= 'a';
2116 2854 }
2117 2855 }
2856 +
2118 2857 return $key;
2119 2858 }
2120 2859
2121 2860 /**
@@ -2120,11 +2859,16 @@
2120 2859
2121 2860 /**
2122 2861 * Editing a Form or Entry
2123 2862 *
2124 - * @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
2125 2869 *
2126 - * @return bool|array
2870 + * @return array|bool
2127 2871 */
2128 2872 public static function setup_edit_vars( $record, $table, $fields = '', $default = false, $post_values = array(), $args = array() ) {
2129 2873 if ( ! $record ) {
2130 2874 return false;
@@ -2129,9 +2873,9 @@
2129 2873 if ( ! $record ) {
2130 2874 return false;
2131 2875 }
2132 2876
2133 - if ( empty( $post_values ) ) {
2877 + if ( ! $post_values ) {
2134 2878 $post_values = wp_unslash( $_POST ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
2135 2879 }
2136 2880
2137 2881 $values = array(
@@ -2139,9 +2883,9 @@
2139 2883 'fields' => array(),
2140 2884 );
2141 2885
2142 2886 foreach ( array( 'name', 'description' ) as $var ) {
2143 - $default_val = isset( $record->{$var} ) ? $record->{$var} : '';
2887 + $default_val = $record->{$var} ?? '';
2144 2888 $values[ $var ] = self::get_param( $var, $default_val, 'get', 'wp_kses_post' );
2145 2889 unset( $var, $default_val );
2146 2890 }
2147 2891
@@ -2159,48 +2903,67 @@
2159 2903
2160 2904 return $values;
2161 2905 }
2162 2906
2907 + /**
2908 + * @param array|string $fields
2909 + * @param object $record
2910 + * @param array $values
2911 + * @param array $args
2912 + *
2913 + * @return void
2914 + */
2163 2915 private static function prepare_field_arrays( $fields, $record, array &$values, $args ) {
2164 - if ( ! empty( $fields ) ) {
2165 - foreach ( (array) $fields as $field ) {
2166 - if ( ! self::is_admin_page() ) {
2167 - // Don't prep default values on the form settings page.
2168 - $field->default_value = apply_filters( 'frm_get_default_value', $field->default_value, $field, true );
2169 - }
2170 - $args['parent_form_id'] = isset( $args['parent_form_id'] ) ? $args['parent_form_id'] : $field->form_id;
2171 - 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 );
2172 2924 }
2925 +
2926 + $args['parent_form_id'] = $args['parent_form_id'] ?? $field->form_id;
2927 + self::fill_field_defaults( $field, $record, $values, $args );
2173 2928 }
2174 2929 }
2175 2930
2931 + /**
2932 + * @param object $field
2933 + * @param object $record
2934 + * @param array $values
2935 + * @param array $args
2936 + *
2937 + * @return void
2938 + */
2176 2939 private static function fill_field_defaults( $field, $record, array &$values, $args ) {
2177 2940 $post_values = $args['post_values'];
2178 2941
2179 2942 if ( $args['default'] ) {
2180 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 + );
2181 2960 } else {
2182 - if ( $record->post_id && self::pro_is_installed() && isset( $field->field_options['post_field'] ) && $field->field_options['post_field'] ) {
2183 - if ( ! isset( $field->field_options['custom_field'] ) ) {
2184 - $field->field_options['custom_field'] = '';
2185 - }
2186 - $meta_value = FrmProEntryMetaHelper::get_post_value(
2187 - $record->post_id,
2188 - $field->field_options['post_field'],
2189 - $field->field_options['custom_field'],
2190 - array(
2191 - 'truncate' => false,
2192 - 'type' => $field->type,
2193 - 'form_id' => $field->form_id,
2194 - 'field' => $field,
2195 - )
2196 - );
2197 - } else {
2198 - $meta_value = FrmEntryMeta::get_meta_value( $record, $field->id );
2199 - }
2200 - }
2961 + $meta_value = FrmEntryMeta::get_meta_value( $record, $field->id );
2962 + }//end if
2201 2963
2202 - $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 +
2203 2966 if ( isset( $post_values['item_meta'][ $field->id ] ) ) {
2204 2967 $new_value = $post_values['item_meta'][ $field->id ];
2205 2968 self::unserialize_or_decode( $new_value );
2206 2969 } else {
@@ -2215,9 +2978,9 @@
2215 2978 $args['field_type'] = $field_type;
2216 2979
2217 2980 FrmFieldsHelper::prepare_edit_front_field( $field_array, $field, $values['id'], $args );
2218 2981
2219 - if ( ! isset( $field_array['unique'] ) || ! $field_array['unique'] ) {
2982 + if ( empty( $field_array['unique'] ) ) {
2220 2983 $field_array['unique_msg'] = '';
2221 2984 }
2222 2985
2223 2986 $field_array = array_merge( (array) $field->field_options, $field_array );
@@ -2246,12 +3009,15 @@
2246 3009 );
2247 3010 }
2248 3011
2249 3012 /**
3013 + * @param object $record
2250 3014 * @param string $table
3015 + * @param array $post_values
3016 + * @param array $values
2251 3017 */
2252 3018 private static function fill_form_opts( $record, $table, $post_values, array &$values ) {
2253 - if ( $table == 'entries' ) {
3019 + if ( $table === 'entries' ) {
2254 3020 $form = $record->form_id;
2255 3021 FrmForm::maybe_get_form( $form );
2256 3022 } else {
2257 3023 $form = $record;
@@ -2281,15 +3047,21 @@
2281 3047 }
2282 3048
2283 3049 /**
2284 3050 * Set to POST value or default
3051 + *
3052 + * @param array $post_values
3053 + * @param array $values
3054 + *
3055 + * @return void
2285 3056 */
2286 3057 private static function fill_form_defaults( $post_values, array &$values ) {
2287 3058 $form_defaults = FrmFormsHelper::get_default_opts();
2288 3059
2289 3060 foreach ( $form_defaults as $opt => $default ) {
3061 + // phpcs:ignore Universal.Operators.StrictComparisons
2290 3062 if ( ! isset( $values[ $opt ] ) || $values[ $opt ] == '' ) {
2291 - $values[ $opt ] = ( $post_values && isset( $post_values['options'][ $opt ] ) ) ? $post_values['options'][ $opt ] : $default;
3063 + $values[ $opt ] = $post_values['options'][ $opt ] ?? $default;
2292 3064 }
2293 3065
2294 3066 unset( $opt, $default );
2295 3067 }
@@ -2299,9 +3071,9 @@
2299 3071 }
2300 3072
2301 3073 foreach ( array( 'before', 'after', 'submit' ) as $h ) {
2302 3074 if ( ! isset( $values[ $h . '_html' ] ) ) {
2303 - $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 );
2304 3076 }
2305 3077 unset( $h );
2306 3078 }
2307 3079 }
@@ -2310,46 +3082,70 @@
2310 3082 * @since 2.2.10
2311 3083 *
2312 3084 * @param array $post_values
2313 3085 *
2314 - * @return boolean|int
3086 + * @return bool|int
2315 3087 */
2316 3088 public static function custom_style_value( $post_values ) {
2317 - if ( ! empty( $post_values ) && isset( $post_values['options']['custom_style'] ) ) {
2318 - $custom_style = absint( $post_values['options']['custom_style'] );
2319 - } else {
2320 - $frm_settings = self::get_settings();
2321 - $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'] );
2322 3091 }
2323 3092
2324 - return $custom_style;
3093 + $frm_settings = self::get_settings();
3094 + return $frm_settings->load_style !== 'none';
2325 3095 }
2326 3096
2327 - public static function truncate( $str, $length, $minword = 3, $continue = '...' ) {
2328 - 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 ) ) {
2329 3114 return '';
2330 3115 }
2331 3116
2332 - $length = (int) $length;
2333 - $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 );
2334 3124 $original_len = self::mb_function( array( 'mb_strlen', 'strlen' ), array( $str ) );
2335 3125
2336 - if ( $length == 0 ) {
2337 - return '';
2338 - } elseif ( $length <= 10 ) {
3126 + if ( $length <= 10 ) {
2339 3127 $sub = self::mb_function( array( 'mb_substr', 'substr' ), array( $str, 0, $length ) );
2340 3128
2341 - return $sub . ( ( $length < $original_len ) ? $continue : '' );
3129 + if ( $force_length_limit ) {
3130 + return $sub;
3131 + }
3132 +
3133 + return $sub . ( $length < $original_len ? $continue : '' );
2342 3134 }
2343 3135
2344 - $sub = '';
2345 - $len = 0;
3136 + $sub = '';
3137 + $len = 0;
3138 + $words = self::mb_function( array( 'mb_split', 'explode' ), array( ' ', $str ) );
2346 3139
2347 - $words = self::mb_function( array( 'mb_split', 'explode' ), array( ' ', $str ) );
3140 + if ( ! is_array( $words ) ) {
3141 + return $original_string;
3142 + }
2348 3143
2349 3144 foreach ( $words as $word ) {
2350 - $part = ( ( $sub != '' ) ? ' ' : '' ) . $word;
3145 + $part = ( $sub !== '' ? ' ' : '' ) . $word;
2351 3146 $total_len = self::mb_function( array( 'mb_strlen', 'strlen' ), array( $sub . $part ) );
3147 +
2352 3148 if ( $total_len > $length && substr_count( $sub, ' ' ) ) {
2353 3149 break;
2354 3150 }
2355 3151
@@ -2362,14 +3158,72 @@
2362 3158
2363 3159 unset( $total_len, $word );
2364 3160 }
2365 3161
2366 - 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 : '' );
2367 3176 }
2368 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 + */
2369 3222 public static function mb_function( $function_names, $args ) {
2370 3223 $mb_function_name = $function_names[0];
2371 3224 $function_name = $function_names[1];
3225 +
2372 3226 if ( function_exists( $mb_function_name ) ) {
2373 3227 $function_name = $mb_function_name;
2374 3228 }
2375 3229
@@ -2375,14 +3229,21 @@
2375 3229
2376 3230 return call_user_func_array( $function_name, $args );
2377 3231 }
2378 3232
3233 + /**
3234 + * @param string $date
3235 + * @param string $date_format
3236 + * @param string $time_format
3237 + *
3238 + * @return string
3239 + */
2379 3240 public static function get_formatted_time( $date, $date_format = '', $time_format = '' ) {
2380 - if ( empty( $date ) ) {
3241 + if ( ! $date ) {
2381 3242 return $date;
2382 3243 }
2383 3244
2384 - if ( empty( $date_format ) ) {
3245 + if ( ! $date_format ) {
2385 3246 $date_format = get_option( 'date_format' );
2386 3247 }
2387 3248
2388 3249 if ( preg_match( '/^\d{1-2}\/\d{1-2}\/\d{4}$/', $date ) && self::pro_is_installed() ) {
@@ -2390,10 +3251,10 @@
2390 3251 $date = FrmProAppHelper::convert_date( $date, $frmpro_settings->date_format, 'Y-m-d' );
2391 3252 }
2392 3253
2393 3254 $formatted = self::get_localized_date( $date_format, $date );
3255 + $do_time = gmdate( 'H:i:s', strtotime( $date ) ) !== '00:00:00';
2394 3256
2395 - $do_time = ( gmdate( 'H:i:s', strtotime( $date ) ) != '00:00:00' );
2396 3257 if ( $do_time ) {
2397 3258 $formatted .= self::add_time_to_date( $time_format, $date );
2398 3259 }
2399 3260
@@ -2399,45 +3260,52 @@
2399 3260
2400 3261 return $formatted;
2401 3262 }
2402 3263
3264 + /**
3265 + * @param string $time_format
3266 + * @param string $date
3267 + *
3268 + * @return string
3269 + */
2403 3270 private static function add_time_to_date( $time_format, $date ) {
2404 - if ( empty( $time_format ) ) {
3271 + if ( ! $time_format ) {
2405 3272 $time_format = get_option( 'time_format' );
2406 3273 }
2407 3274
2408 3275 $trimmed_format = trim( $time_format );
2409 - $time = '';
2410 - if ( $time_format && ! empty( $trimmed_format ) ) {
2411 - $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 );
2412 3279 }
2413 3280
2414 - return $time;
3281 + return '';
2415 3282 }
2416 3283
2417 3284 /**
2418 3285 * @since 2.0.8
3286 + *
3287 + * @param string $date_format
3288 + * @param string $date
3289 + *
3290 + * @return string
2419 3291 */
2420 3292 public static function get_localized_date( $date_format, $date ) {
2421 3293 $date = get_date_from_gmt( $date );
2422 -
2423 3294 return date_i18n( $date_format, strtotime( $date ) );
2424 3295 }
2425 3296
2426 3297 /**
2427 - * Gets the time ago in words
3298 + * Gets the time ago in words.
2428 3299 *
2429 - * @param int $from in seconds
2430 - * @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.
2431 3303 *
2432 - * @return string $time_ago
3304 + * @return string Time ago.
2433 3305 */
2434 3306 public static function human_time_diff( $from, $to = '', $levels = 1 ) {
2435 - if ( empty( $to ) && 0 !== $to ) {
2436 - $now = new DateTime();
2437 - } else {
2438 - $now = new DateTime( '@' . $to );
2439 - }
3307 + $now = ! $to && 0 !== $to ? new DateTime() : new DateTime( '@' . $to );
2440 3308 $ago = new DateTime( '@' . $from );
2441 3309
2442 3310 // Get the time difference
2443 3311 $diff_object = $now->diff( $ago );
@@ -2443,9 +3311,9 @@
2443 3311 $diff_object = $now->diff( $ago );
2444 3312 $diff = get_object_vars( $diff_object );
2445 3313
2446 3314 // Add week amount and update day amount
2447 - $diff['w'] = floor( $diff['d'] / 7 );
3315 + $diff['w'] = floor( $diff['d'] / 7 );
2448 3316 $diff['d'] -= $diff['w'] * 7;
2449 3317
2450 3318 $time_strings = self::get_time_strings();
2451 3319
@@ -2451,10 +3319,11 @@
2451 3319
2452 3320 if ( ! is_numeric( $levels ) ) {
2453 3321 // Show time in specified unit.
2454 3322 $levels = self::get_unit( $levels );
3323 +
2455 3324 if ( isset( $time_strings[ $levels ] ) ) {
2456 - $diff = array(
3325 + $diff = array(
2457 3326 $levels => self::time_format( $levels, $diff ),
2458 3327 );
2459 3328 $time_strings = array(
2460 3329 $levels => $time_strings[ $levels ],
@@ -2459,13 +3328,14 @@
2459 3328 $time_strings = array(
2460 3329 $levels => $time_strings[ $levels ],
2461 3330 );
2462 3331 }
3332 +
2463 3333 $levels = 1;
2464 3334 }
2465 3335
2466 3336 foreach ( $time_strings as $k => $v ) {
2467 - if ( isset( $diff[ $k ] ) && $diff[ $k ] ) {
3337 + if ( ! empty( $diff[ $k ] ) ) {
2468 3338 $time_strings[ $k ] = $diff[ $k ] . ' ' . ( $diff[ $k ] > 1 ? $v[1] : $v[0] );
2469 3339 } elseif ( isset( $diff[ $k ] ) && count( $time_strings ) === 1 ) {
2470 3340 // Account for 0.
2471 3341 $time_strings[ $k ] = $diff[ $k ] . ' ' . $v[1];
@@ -2473,17 +3343,21 @@
2473 3343 unset( $time_strings[ $k ] );
2474 3344 }
2475 3345 }
2476 3346
2477 - $levels_deep = apply_filters( 'frm_time_ago_levels', $levels, compact( 'time_strings', 'from', 'to' ) );
2478 - $time_strings = array_slice( $time_strings, 0, absint( $levels_deep ) );
2479 - $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 ) );
2480 3349
2481 - return $time_ago_string;
3350 + return implode( ' ', $time_strings );
2482 3351 }
2483 3352
2484 3353 /**
2485 3354 * @since 4.05.01
3355 + *
3356 + * @param string $unit
3357 + * @param array $diff
3358 + *
3359 + * @return int
2486 3360 */
2487 3361 private static function time_format( $unit, $diff ) {
2488 3362 $return = array(
2489 3363 'y' => 'y',
@@ -2488,14 +3362,14 @@
2488 3362 $return = array(
2489 3363 'y' => 'y',
2490 3364 'd' => 'days',
2491 3365 );
3366 +
2492 3367 if ( isset( $return[ $unit ] ) ) {
2493 3368 return $diff[ $return[ $unit ] ];
2494 3369 }
2495 3370
2496 3371 $total = $diff['days'] * self::convert_time( 'd', $unit );
2497 -
2498 3372 $times = array( 'h', 'i', 's' );
2499 3373
2500 3374 foreach ( $times as $time ) {
2501 3375 if ( ! isset( $diff[ $time ] ) ) {
@@ -2509,8 +3383,13 @@
2509 3383 }
2510 3384
2511 3385 /**
2512 3386 * @since 4.05.01
3387 + *
3388 + * @param string $from
3389 + * @param string $to
3390 + *
3391 + * @return int
2513 3392 */
2514 3393 private static function convert_time( $from, $to ) {
2515 3394 $convert = array(
2516 3395 's' => 1,
@@ -2526,28 +3405,35 @@
2526 3405 }
2527 3406
2528 3407 /**
2529 3408 * @since 4.05.01
3409 + *
3410 + * @param string $unit
3411 + *
3412 + * @return int|string
2530 3413 */
2531 3414 private static function get_unit( $unit ) {
2532 3415 $units = self::get_time_strings();
3416 +
2533 3417 if ( isset( $units[ $unit ] ) || is_numeric( $unit ) ) {
2534 3418 return $unit;
2535 3419 }
2536 3420
2537 3421 foreach ( $units as $u => $strings ) {
2538 - if ( in_array( $unit, $strings ) ) {
3422 + if ( in_array( $unit, $strings, true ) ) {
2539 3423 return $u;
2540 3424 }
2541 3425 }
3426 +
2542 3427 return 1;
2543 3428 }
2544 3429
2545 3430 /**
2546 3431 * Get the translatable time strings. The untranslated version is a failsafe
2547 - * 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.
2548 3433 *
2549 3434 * @since 2.0.20
3435 + *
2550 3436 * @return array
2551 3437 */
2552 3438 private static function get_time_strings() {
2553 3439 return array(
@@ -2591,35 +3477,48 @@
2591 3477
2592 3478 // Pagination Methods.
2593 3479
2594 3480 /**
2595 - * @param integer $current_p
3481 + * @param int $r_count
3482 + * @param int $current_p
3483 + * @param int $p_size
3484 + *
3485 + * @return int
2596 3486 */
2597 3487 public static function get_last_record_num( $r_count, $current_p, $p_size ) {
2598 - 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;
2599 3489 }
2600 3490
2601 3491 /**
2602 - * @param integer $current_p
3492 + * @param int $r_count
3493 + * @param int $current_p
3494 + * @param int $p_size
3495 + *
3496 + * @return int
2603 3497 */
2604 3498 public static function get_first_record_num( $r_count, $current_p, $p_size ) {
3499 + // phpcs:ignore Universal.Operators.StrictComparisons
2605 3500 if ( $current_p == 1 ) {
2606 3501 return 1;
2607 - } else {
2608 - return ( self::get_last_record_num( $r_count, ( $current_p - 1 ), $p_size ) + 1 );
2609 3502 }
3503 + return self::get_last_record_num( $r_count, $current_p - 1, $p_size ) + 1;
2610 3504 }
2611 3505
2612 3506 /**
3507 + * @param array $json_vars
3508 + *
2613 3509 * @return array
2614 3510 */
2615 3511 public static function json_to_array( $json_vars ) {
2616 3512 $vars = array();
3513 +
2617 3514 foreach ( $json_vars as $jv ) {
2618 3515 $jv_name = explode( '[', $jv['name'] );
2619 3516 $last = count( $jv_name ) - 1;
3517 +
2620 3518 foreach ( $jv_name as $p => $n ) {
2621 3519 $name = trim( $n, ']' );
3520 +
2622 3521 if ( ! isset( $l1 ) ) {
2623 3522 $l1 = $name;
2624 3523 }
2625 3524
@@ -2630,9 +3529,9 @@
2630 3529 if ( ! isset( $l3 ) ) {
2631 3530 $l3 = $name;
2632 3531 }
2633 3532
2634 - $this_val = ( $p == $last ) ? $jv['value'] : array();
3533 + $this_val = $p === $last ? $jv['value'] : array();
2635 3534
2636 3535 switch ( $p ) {
2637 3536 case 0:
2638 3537 $l1 = $name;
@@ -2654,12 +3553,12 @@
2654 3553 self::add_value_to_array( $name, $l4, $this_val, $vars[ $l1 ][ $l2 ][ $l3 ] );
2655 3554 }
2656 3555
2657 3556 unset( $this_val, $n );
2658 - }
3557 + }//end foreach
2659 3558
2660 3559 unset( $last, $jv );
2661 - }
3560 + }//end foreach
2662 3561
2663 3562 return $vars;
2664 3563 }
2665 3564
@@ -2665,10 +3564,15 @@
2665 3564
2666 3565 /**
2667 3566 * @param string $name
2668 3567 * @param string $l1
3568 + * @param mixed $val
3569 + * @param array $vars
3570 + *
3571 + * @return void
2669 3572 */
2670 3573 public static function add_value_to_array( $name, $l1, $val, &$vars ) {
3574 + // phpcs:ignore Universal.Operators.StrictComparisons
2671 3575 if ( $name == '' ) {
2672 3576 $vars[] = $val;
2673 3577 } elseif ( ! isset( $vars[ $l1 ] ) ) {
2674 3578 $vars[ $l1 ] = $val;
@@ -2674,19 +3578,26 @@
2674 3578 $vars[ $l1 ] = $val;
2675 3579 }
2676 3580 }
2677 3581
3582 + /**
3583 + * @param string $name
3584 + * @param string $class
3585 + * @param string $form_name
3586 + *
3587 + * @return void
3588 + */
2678 3589 public static function maybe_add_tooltip( $name, $class = 'closed', $form_name = '' ) {
2679 3590 $tooltips = array(
2680 3591 'action_title' => __( 'Give this action a label for easy reference.', 'formidable' ),
2681 - '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
2682 3593 'cc' => __( 'Add CC addresses separated by a ",". FORMAT: Name <[email protected]> or [email protected].', 'formidable' ),
2683 3594 'bcc' => __( 'Add BCC addresses separated by a ",". FORMAT: Name <[email protected]> or [email protected].', 'formidable' ),
2684 - '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
2685 3596 'from' => __( 'Enter the name and/or email address of the sender. FORMAT: John Bates <[email protected]> or [email protected].', 'formidable' ),
2686 3597 /* translators: %1$s: Form name, %2$s: Date */
2687 - '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() ) ),
2688 - '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
2689 3600 );
2690 3601
2691 3602 if ( ! isset( $tooltips[ $name ] ) ) {
2692 3603 return;
@@ -2691,9 +3602,9 @@
2691 3602 if ( ! isset( $tooltips[ $name ] ) ) {
2692 3603 return;
2693 3604 }
2694 3605
2695 - if ( 'open' == $class ) {
3606 + if ( 'open' === $class ) {
2696 3607 echo ' frm_help"';
2697 3608 } else {
2698 3609 echo ' class="frm_help"';
2699 3610 }
@@ -2699,9 +3610,9 @@
2699 3610 }
2700 3611
2701 3612 echo ' title="' . esc_attr( $tooltips[ $name ] );
2702 3613
2703 - if ( 'open' != $class ) {
3614 + if ( 'open' !== $class ) {
2704 3615 echo '"';
2705 3616 }
2706 3617 }
2707 3618
@@ -2706,20 +3617,28 @@
2706 3617 }
2707 3618
2708 3619 /**
2709 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
2710 3627 */
2711 3628 public static function select_current_page( $page, $current_page, $action = array() ) {
3629 + // phpcs:ignore Universal.Operators.StrictComparisons
2712 3630 if ( $current_page != $page ) {
2713 3631 return;
2714 3632 }
2715 3633
2716 3634 $frm_action = self::simple_get( 'frm_action', 'sanitize_title' );
3635 +
2717 3636 if ( 'lite-reports' === $frm_action ) {
2718 3637 $frm_action = 'reports';
2719 3638 }
2720 3639
2721 - if ( empty( $action ) || ( ! empty( $frm_action ) && in_array( $frm_action, $action ) ) ) {
3640 + if ( ! $action || ( $frm_action && in_array( $frm_action, $action, true ) ) ) {
2722 3641 echo ' class="current_page"';
2723 3642 }
2724 3643 }
2725 3644
@@ -2749,14 +3668,19 @@
2749 3668
2750 3669 // Add extra slashes for \r\n since WP strips them.
2751 3670 $post_content = str_replace( array( '\\r', '\\n', '\\u', '\\t' ), array( '\\\\r', '\\\\n', '\\\\u', '\\\\t' ), $post_content );
2752 3671
2753 - // allow for &quot
2754 - $post_content = str_replace( '&quot;', '\\"', $post_content );
2755 -
2756 - return $post_content;
3672 + // Allow for &quot
3673 + return str_replace( '&quot;', '\\"', $post_content );
2757 3674 }
2758 3675
3676 + /**
3677 + * @param array|string $val
3678 + * @param int|string $key
3679 + * @param array $post_content
3680 + *
3681 + * @return void
3682 + */
2759 3683 private static function prepare_action_slashes( $val, $key, &$post_content ) {
2760 3684 if ( ! isset( $post_content[ $key ] ) || is_numeric( $val ) ) {
2761 3685 return;
2762 3686 }
@@ -2765,15 +3689,17 @@
2765 3689 foreach ( $val as $k1 => $v1 ) {
2766 3690 self::prepare_action_slashes( $v1, $k1, $post_content[ $key ] );
2767 3691 unset( $k1, $v1 );
2768 3692 }
2769 - } else {
2770 - // Strip all slashes so everything is the same, no matter where the value is coming from
2771 - $val = stripslashes( $val );
2772 3693
2773 - // Add backslashes before double quotes and forward slashes only
2774 - $post_content[ $key ] = addcslashes( $val, '"\\/' );
3694 + return;
2775 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, '"\\/' );
2776 3702 }
2777 3703
2778 3704 /**
2779 3705 * Check for either json or serialized data. This is temporary while transitioning
@@ -2781,8 +3707,9 @@
2781 3707 *
2782 3708 * @since 4.02.03
2783 3709 *
2784 3710 * @param array|string $value
3711 + *
2785 3712 * @return void
2786 3713 */
2787 3714 public static function unserialize_or_decode( &$value ) {
2788 3715 if ( is_array( $value ) ) {
@@ -2788,13 +3715,9 @@
2788 3715 if ( is_array( $value ) ) {
2789 3716 return;
2790 3717 }
2791 3718
2792 - if ( is_serialized( $value ) ) {
2793 - $value = self::maybe_unserialize_array( $value );
2794 - } else {
2795 - $value = self::maybe_json_decode( $value, false );
2796 - }
3719 + $value = is_serialized( $value ) ? self::maybe_unserialize_array( $value ) : self::maybe_json_decode( $value, false );
2797 3720 }
2798 3721
2799 3722 /**
2800 3723 * Safely unserialize an array if necessary.
@@ -2802,8 +3725,9 @@
2802 3725 *
2803 3726 * @since 6.2
2804 3727 *
2805 3728 * @param mixed $value
3729 + *
2806 3730 * @return mixed
2807 3731 */
2808 3732 public static function maybe_unserialize_array( $value ) {
2809 3733 if ( ! is_string( $value ) ) {
@@ -2810,18 +3734,15 @@
2810 3734 return $value;
2811 3735 }
2812 3736
2813 3737 // Since we only expect an array, skip anything that doesn't start with a:.
2814 - if ( ! is_serialized( $value ) || 'a:' !== substr( $value, 0, 2 ) ) {
3738 + if ( ! is_serialized( $value ) || ! str_starts_with( $value, 'a:' ) ) {
2815 3739 return $value;
2816 3740 }
2817 3741
2818 3742 $parsed = FrmSerializedStringParserHelper::get()->parse( $value );
2819 - if ( is_array( $parsed ) ) {
2820 - $value = $parsed;
2821 - }
2822 3743
2823 - return $value;
3744 + return is_array( $parsed ) ? $parsed : $value;
2824 3745 }
2825 3746
2826 3747 /**
2827 3748 * Decode a JSON string.
@@ -2826,11 +3747,12 @@
2826 3747 /**
2827 3748 * Decode a JSON string.
2828 3749 * Do not switch shortcodes like [24] to array unless intentional ie XML values.
2829 3750 *
2830 - * @param mixed $string
2831 - * @param bool $single_to_array
2832 - * @return mixed
3751 + * @param array|string|null $string
3752 + * @param bool $single_to_array
3753 + *
3754 + * @return array|string|null
2833 3755 */
2834 3756 public static function maybe_json_decode( $string, $single_to_array = true ) {
2835 3757 if ( is_array( $string ) || is_null( $string ) ) {
2836 3758 return $string;
@@ -2835,20 +3757,19 @@
2835 3757 if ( is_array( $string ) || is_null( $string ) ) {
2836 3758 return $string;
2837 3759 }
2838 3760
2839 - $new_string = json_decode( $string, true );
2840 - if ( function_exists( 'json_last_error' ) ) {
2841 - // php 5.3+
2842 - $single_value = false;
2843 - if ( ! $single_to_array ) {
2844 - $single_value = is_array( $new_string ) && count( $new_string ) === 1 && isset( $new_string[0] );
2845 - }
2846 - if ( json_last_error() == JSON_ERROR_NONE && is_array( $new_string ) && ! $single_value ) {
2847 - $string = $new_string;
2848 - }
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] );
2849 3766 }
2850 3767
3768 + if ( json_last_error() === JSON_ERROR_NONE && is_array( $new_string ) && ! $single_value ) {
3769 + return $new_string;
3770 + }
3771 +
2851 3772 return $string;
2852 3773 }
2853 3774
2854 3775 /**
@@ -2854,8 +3775,9 @@
2854 3775 /**
2855 3776 * @since 6.2.3
2856 3777 *
2857 3778 * @param string $value
3779 + *
2858 3780 * @return string
2859 3781 */
2860 3782 public static function maybe_utf8_encode( $value ) {
2861 3783 $from_format = 'ISO-8859-1';
@@ -2864,13 +3786,15 @@
2864 3786 if ( function_exists( 'mb_check_encoding' ) && function_exists( 'mb_convert_encoding' ) ) {
2865 3787 if ( mb_check_encoding( $value, $from_format ) ) {
2866 3788 return mb_convert_encoding( $value, $to_format, $from_format );
2867 3789 }
3790 +
2868 3791 return $value;
2869 3792 }
2870 3793
2871 3794 if ( function_exists( 'iconv' ) ) {
2872 3795 $converted = iconv( $from_format, $to_format, $value );
3796 +
2873 3797 // Value is false if $value is not ISO-8859-1.
2874 3798 if ( false !== $converted ) {
2875 3799 return $converted;
2876 3800 }
@@ -2882,8 +3806,12 @@
2882 3806 /**
2883 3807 * Reformat the json serialized array in name => value array.
2884 3808 *
2885 3809 * @since 4.02.03
3810 + *
3811 + * @param array $form
3812 + *
3813 + * @return void
2886 3814 */
2887 3815 public static function format_form_data( &$form ) {
2888 3816 $formatted = array();
2889 3817
@@ -2890,17 +3818,20 @@
2890 3818 foreach ( $form as $input ) {
2891 3819 if ( ! isset( $input['name'] ) ) {
2892 3820 continue;
2893 3821 }
3822 +
2894 3823 $key = $input['name'];
2895 - if ( isset( $formatted[ $key ] ) ) {
2896 - if ( is_array( $formatted[ $key ] ) ) {
2897 - $formatted[ $key ][] = $input['value'];
2898 - } else {
2899 - $formatted[ $key ] = array( $formatted[ $key ], $input['value'] );
2900 - }
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'];
2901 3832 } else {
2902 - $formatted[ $key ] = $input['value'];
3833 + $formatted[ $key ] = array( $formatted[ $key ], $input['value'] );
2903 3834 }
2904 3835 }
2905 3836
2906 3837 parse_str( http_build_query( $formatted ), $form );
@@ -2907,30 +3838,34 @@
2907 3838 }
2908 3839
2909 3840 /**
2910 3841 * @since 4.02.03
3842 + *
3843 + * @param array|string $value
3844 + *
3845 + * @return string
2911 3846 */
2912 3847 public static function maybe_json_encode( $value ) {
2913 - if ( is_array( $value ) ) {
2914 - $value = wp_json_encode( $value );
2915 - }
2916 - return $value;
3848 + return is_array( $value ) ? wp_json_encode( $value ) : $value;
2917 3849 }
2918 3850
2919 3851 /**
3852 + * Echo The javascript to open and highlight the Formidable menu
3853 + *
2920 3854 * @since 1.07.10
2921 3855 *
2922 - * @param string $post_type The name of the post type that may need to be highlighted
2923 - * 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
2924 3859 */
2925 3860 public static function maybe_highlight_menu( $post_type ) {
2926 3861 global $post;
2927 3862
2928 - if ( isset( $_REQUEST['post_type'] ) && $_REQUEST['post_type'] != $post_type ) {
3863 + if ( isset( $_REQUEST['post_type'] ) && $_REQUEST['post_type'] !== $post_type ) {
2929 3864 return;
2930 3865 }
2931 3866
2932 - if ( is_object( $post ) && $post->post_type != $post_type ) {
3867 + if ( is_object( $post ) && $post->post_type !== $post_type ) {
2933 3868 return;
2934 3869 }
2935 3870
2936 3871 self::load_admin_wide_js();
@@ -2940,12 +3875,15 @@
2940 3875 /**
2941 3876 * Load the JS file on non-Formidable pages in the admin area
2942 3877 *
2943 3878 * @since 2.0
3879 + *
3880 + * @param bool $load
3881 + *
3882 + * @return void
2944 3883 */
2945 3884 public static function load_admin_wide_js( $load = true ) {
2946 - $version = self::plugin_version();
2947 - 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() );
2948 3886
2949 3887 $global_strings = array(
2950 3888 'updating_msg' => __( 'Please wait while your site updates.', 'formidable' ),
2951 3889 'deauthorize' => __( 'Are you sure you want to deauthorize Formidable Forms on this site?', 'formidable' ),
@@ -2951,12 +3889,13 @@
2951 3889 'deauthorize' => __( 'Are you sure you want to deauthorize Formidable Forms on this site?', 'formidable' ),
2952 3890 'url' => self::plugin_url(),
2953 3891 'app_url' => 'https://formidableforms.com/',
2954 3892 'applicationsUrl' => admin_url( 'admin.php?page=formidable-applications' ),
2955 - '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
2956 3894 'loading' => __( 'Loading&hellip;', 'formidable' ),
2957 3895 'nonce' => wp_create_nonce( 'frm_ajax' ),
2958 3896 'proIncludesSliderJs' => is_callable( 'FrmProFormsHelper::prepare_custom_currency' ),
3897 + 'inboxSlideIn' => FrmInbox::get_inbox_slide_in_value_for_js(),
2959 3898 );
2960 3899 wp_localize_script( 'formidable_admin_global', 'frmGlobal', $global_strings );
2961 3900
2962 3901 if ( $load ) {
@@ -2965,8 +3904,10 @@
2965 3904 }
2966 3905
2967 3906 /**
2968 3907 * @since 2.0.9
3908 + *
3909 + * @return void
2969 3910 */
2970 3911 public static function load_font_style() {
2971 3912 wp_enqueue_style( 'frm_fonts', self::plugin_url() . '/css/frm_fonts.css', array(), self::plugin_version() );
2972 3913 }
@@ -2972,99 +3913,224 @@
2972 3913 }
2973 3914
2974 3915 /**
2975 3916 * @param string $location
3917 + *
3918 + * @return void
2976 3919 */
2977 3920 public static function localize_script( $location ) {
2978 - global $wp_scripts;
3921 + global $wp_scripts, $wp_version;
2979 3922
2980 - $ajax_url = admin_url( 'admin-ajax.php', is_ssl() ? 'admin' : 'http' );
2981 - $ajax_url = apply_filters( 'frm_ajax_url', $ajax_url );
2982 -
2983 3923 $script_strings = array(
2984 - 'ajax_url' => $ajax_url,
2985 - 'images_url' => self::plugin_url() . '/images',
2986 - 'loading' => __( 'Loading&hellip;', 'formidable' ),
2987 - 'remove' => __( 'Remove', 'formidable' ),
2988 - 'offset' => apply_filters( 'frm_scroll_offset', 4 ),
2989 - 'nonce' => wp_create_nonce( 'frm_ajax' ),
2990 - 'id' => __( 'ID', 'formidable' ),
2991 - 'no_results' => __( 'No results match', 'formidable' ),
2992 - 'file_spam' => __( 'That file looks like Spam.', 'formidable' ),
2993 - 'calc_error' => __( 'There is an error in the calculation in the field with key', 'formidable' ),
2994 - 'empty_fields' => __( 'Please complete the preceding required fields before uploading a file.', 'formidable' ),
2995 - 'focus_first_error' => self::should_focus_first_error(),
2996 - '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,
2997 3939 );
2998 3940
2999 3941 $data = $wp_scripts->get_data( 'formidable', 'data' );
3000 - if ( empty( $data ) ) {
3942 +
3943 + if ( ! $data ) {
3001 3944 wp_localize_script( 'formidable', 'frm_js', $script_strings );
3002 3945 }
3003 3946
3004 - if ( $location == 'admin' ) {
3005 - $frm_settings = self::get_settings();
3947 + if ( $location === 'admin' ) {
3006 3948 $admin_script_strings = array(
3007 - 'desc' => __( '(Click to add description)', 'formidable' ),
3008 - 'blank' => __( '(Blank)', 'formidable' ),
3009 - 'no_label' => __( '(no label)', 'formidable' ),
3010 - 'saving' => '', // Deprecated in 6.0.
3011 - 'saved' => '', // Deprecated in 6.0.
3012 - 'ok' => __( 'OK', 'formidable' ),
3013 - 'cancel' => __( 'Cancel', 'formidable' ),
3014 - 'default_label' => __( 'Default', 'formidable' ),
3015 - 'clear_default' => __( 'Clear default value when typing', 'formidable' ),
3016 - 'no_clear_default' => __( 'Do not clear default value when typing', 'formidable' ),
3017 - 'valid_default' => __( 'Default value will pass form validation', 'formidable' ),
3018 - 'no_valid_default' => __( 'Default value will NOT pass form validation', 'formidable' ),
3019 - 'confirm' => __( 'Are you sure?', 'formidable' ),
3020 - 'conf_delete' => __( 'Are you sure you want to delete this field and all data associated with it?', 'formidable' ),
3021 - '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' ),
3022 - 'conf_no_repeat' => __( 'Warning: If you have entries with multiple rows, all but the first row will be lost.', 'formidable' ),
3023 - 'default_unique' => $frm_settings->unique_msg,
3024 - 'default_conf' => __( 'The entered values do not match', 'formidable' ),
3025 - 'enter_email' => __( 'Enter Email', 'formidable' ),
3026 - 'confirm_email' => __( 'Confirm Email', 'formidable' ),
3027 - 'conditional_text' => __( 'Conditional content here', 'formidable' ),
3028 - 'new_option' => __( 'New Option', 'formidable' ),
3029 - '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' ),
3030 - 'enter_password' => __( 'Enter Password', 'formidable' ),
3031 - 'confirm_password' => __( 'Confirm Password', 'formidable' ),
3032 - 'import_complete' => __( 'Import Complete', 'formidable' ),
3033 - 'updating' => __( 'Please wait while your site updates.', 'formidable' ),
3034 - 'no_save_warning' => __( 'Warning: There is no way to retrieve unsaved entries.', 'formidable' ),
3035 - 'private_label' => __( 'Private', 'formidable' ),
3036 - 'jquery_ui_url' => '',
3037 - 'pro_url' => is_callable( 'FrmProAppHelper::plugin_url' ) ? FrmProAppHelper::plugin_url() : '',
3038 - 'no_licenses' => __( 'No new licenses were found', 'formidable' ),
3039 - 'unmatched_parens' => __( 'This calculation has at least one unmatched ( ) { } [ ].', 'formidable' ),
3040 - 'view_shortcodes' => __( 'This calculation may have shortcodes that work in Views but not forms.', 'formidable' ),
3041 - 'text_shortcodes' => __( 'This calculation may have shortcodes that work in text calculations but not numeric calculations.', 'formidable' ),
3042 - 'only_one_action' => __( 'This form action is limited to one per form. Please edit the existing form action.', 'formidable' ),
3043 - '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(),
3044 3988 /* Translators: %s is the name of a Detail Page Slug that is a reserved word.*/
3045 - '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
3046 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. */
3047 - 'param_is_reserved' => sprintf( __( 'The parameter "%s" is reserved by WordPress. This may cause problems when included in the URL. Is this intentional? ', 'formidable' ), '****' ),
3048 - 'reserved_words' => __( 'See the list of reserved words in WordPress.', 'formidable' ),
3049 - 'repeat_limit_min' => __( 'Please enter a Repeat Limit that is greater than 1.', 'formidable' ),
3050 - 'checkbox_limit' => __( 'Please select a limit between 0 and 200.', 'formidable' ),
3051 - 'install' => __( 'Install', 'formidable' ),
3052 - 'active' => __( 'Active', 'formidable' ),
3053 - 'select_a_field' => __( 'Select a Field', 'formidable' ),
3054 - 'no_items_found' => __( 'No items found.', 'formidable' ),
3055 - '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', '>=' ),
3056 4016 );
4017 +
4018 + self::add_form_builder_modal_data( $admin_script_strings );
4019 +
4020 + /**
4021 + * @param array $admin_script_strings
4022 + */
3057 4023 $admin_script_strings = apply_filters( 'frm_admin_script_strings', $admin_script_strings );
3058 4024
3059 4025 $data = $wp_scripts->get_data( 'formidable_admin', 'data' );
3060 - if ( empty( $data ) ) {
4026 +
4027 + if ( ! $data ) {
3061 4028 wp_localize_script( 'formidable_admin', 'frm_admin_js', $admin_script_strings );
3062 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;
3063 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' );
3064 4103 }
3065 4104
3066 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 + /**
3067 4133 * Returns whether or not the first errored input should be auto-focused (default true).
3068 4134 *
3069 4135 * @since 5.2.05
3070 4136 *
@@ -3089,9 +4155,11 @@
3089 4155 * Echo the message on the plugins listing page
3090 4156 *
3091 4157 * @since 1.07.10
3092 4158 *
3093 - * @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
3094 4162 */
3095 4163 public static function min_version_notice( $min_version ) {
3096 4164 $frm_version = self::plugin_version();
3097 4165
@@ -3100,11 +4168,11 @@
3100 4168 return;
3101 4169 }
3102 4170
3103 4171 $wp_list_table = _get_list_table( 'WP_Plugins_List_Table' );
3104 - 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">' .
3105 - esc_html__( 'You are running an outdated version of Formidable. This plugin may not work correctly if you do not update Formidable.', 'formidable' ) .
3106 - '</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>';
3107 4175 }
3108 4176
3109 4177 /**
3110 4178 * If Pro is far outdated, show a message.
@@ -3110,8 +4178,10 @@
3110 4178 * If Pro is far outdated, show a message.
3111 4179 *
3112 4180 * @since 4.0.01
3113 4181 *
4182 + * @param string $min_version
4183 + *
3114 4184 * @return void
3115 4185 */
3116 4186 public static function min_pro_version_notice( $min_version ) {
3117 4187 if ( ! self::is_formidable_admin() ) {
@@ -3121,26 +4191,14 @@
3121 4191
3122 4192 self::php_version_notice();
3123 4193
3124 4194 $is_pro = self::pro_is_installed() && class_exists( 'FrmProDb' );
4195 +
3125 4196 if ( ! $is_pro || self::meets_min_pro_version( $min_version ) ) {
3126 4197 return;
3127 4198 }
3128 4199
3129 - $pro_version = FrmProDb::$plug_version;
3130 - $expired = FrmAddonsController::is_license_expired();
3131 - ?>
3132 - <div class="frm-banner-alert frm_error_style frm_previous_install">
3133 - <?php
3134 - esc_html_e( 'You are running a version of Formidable Forms that may not be compatible with your version of Formidable Forms Pro.', 'formidable' );
3135 - if ( empty( $expired ) ) {
3136 - echo ' Please <a href="' . esc_url( admin_url( 'plugins.php?s=formidable%20forms%20pro' ) ) . '">update now</a>.';
3137 - } else {
3138 - echo '<br/>Please <a href="https://formidableforms.com/account/downloads/?utm_source=WordPress&utm_medium=outdated">renew now</a> to get the latest version.';
3139 - }
3140 - ?>
3141 - </div>
3142 - <?php
4200 + include self::plugin_path() . '/classes/views/addons/min-version-notice.php';
3143 4201 }
3144 4202
3145 4203 /**
3146 4204 * If Pro is installed, check the version number.
@@ -3145,8 +4203,12 @@
3145 4203 /**
3146 4204 * If Pro is installed, check the version number.
3147 4205 *
3148 4206 * @since 4.0.01
4207 + *
4208 + * @param string $min_version
4209 + *
4210 + * @return bool
3149 4211 */
3150 4212 public static function meets_min_pro_version( $min_version ) {
3151 4213 return ! class_exists( 'FrmProDb' ) || version_compare( FrmProDb::$plug_version, $min_version, '>=' );
3152 4214 }
@@ -3151,24 +4213,22 @@
3151 4213 return ! class_exists( 'FrmProDb' ) || version_compare( FrmProDb::$plug_version, $min_version, '>=' );
3152 4214 }
3153 4215
3154 4216 /**
3155 - * 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.
3156 4218 *
3157 4219 * @since 4.0.02
4220 + *
4221 + * @return void
3158 4222 */
3159 4223 private static function php_version_notice() {
3160 4224 $message = array();
3161 - if ( version_compare( phpversion(), '5.6', '<' ) ) {
3162 - $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' );
3163 - }
3164 4225
3165 - $browser = self::get_server_value( 'HTTP_USER_AGENT' );
3166 - $is_ie = strpos( $browser, 'MSIE' ) !== false;
3167 - if ( $is_ie ) {
3168 - $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
3169 4228 }
3170 4229
4230 + // phpcs:disable Generic.WhiteSpace.ScopeIndent
3171 4231 foreach ( $message as $m ) {
3172 4232 ?>
3173 4233 <div class="frm-banner-alert frm_error_style frm_previous_install">
3174 4234 <?php echo esc_html( $m ); ?>
@@ -3174,12 +4234,14 @@
3174 4234 <?php echo esc_html( $m ); ?>
3175 4235 </div>
3176 4236 <?php
3177 4237 }
4238 + // phpcs:enable Generic.WhiteSpace.ScopeIndent
3178 4239 }
3179 4240
3180 4241 /**
3181 4242 * @param string $type
4243 + *
3182 4244 * @return array<string,string>
3183 4245 */
3184 4246 public static function locales( $type = 'date' ) {
3185 4247 $locales = array(
@@ -3273,12 +4335,12 @@
3273 4335 'zu' => __( 'Zulu', 'formidable' ),
3274 4336 );
3275 4337
3276 4338 if ( $type === 'captcha' ) {
3277 - // remove the languages unavailable for the captcha
4339 + // Remove the languages unavailable for the captcha
3278 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' );
3279 4341 } else {
3280 - // remove the languages unavailable for the datepicker
4342 + // Remove the languages unavailable for the datepicker
3281 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' );
3282 4344 }
3283 4345
3284 4346 $locales = array_diff_key( $locales, array_flip( $unset ) );
@@ -3289,32 +4351,27 @@
3289 4351 * @since 5.4.5 Added $args parameter with type.
3290 4352 *
3291 4353 * @param array<string,string> $locales
3292 4354 * @param array $args {
4355 + *
3293 4356 * @type string $type
3294 4357 * }
3295 4358 */
3296 - $locales = apply_filters( 'frm_locales', $locales, compact( 'type' ) );
3297 -
3298 - return $locales;
4359 + return apply_filters( 'frm_locales', $locales, compact( 'type' ) );
3299 4360 }
3300 4361
3301 4362 /**
3302 - * Output HTML containing reference text for accessibility
3303 - *
3304 - * @deprecated 5.1
4363 + * @return string
3305 4364 */
3306 - public static function multiselect_accessibility() {
3307 - _deprecated_function( __METHOD__, '5.1' );
3308 - }
3309 -
3310 4365 public static function get_menu_icon_class() {
3311 4366 if ( is_callable( 'FrmProAppHelper::get_settings' ) ) {
3312 4367 $settings = FrmProAppHelper::get_settings();
4368 +
3313 4369 if ( is_object( $settings ) && ! empty( $settings->menu_icon ) ) {
3314 4370 return $settings->menu_icon;
3315 4371 }
3316 4372 }
4373 +
3317 4374 return 'frmfont frm_logo_icon';
3318 4375 }
3319 4376
3320 4377 /**
@@ -3332,10 +4389,9 @@
3332 4389 * @type array $input_attrs Attributes of value input.
3333 4390 * }
3334 4391 */
3335 4392 public static function images_dropdown( $args ) {
3336 - $args = self::fill_default_images_dropdown_args( $args );
3337 -
4393 + $args = self::fill_default_images_dropdown_args( $args );
3338 4394 $input_attrs_str = self::get_images_dropdown_input_attrs( $args );
3339 4395 ob_start();
3340 4396 include self::plugin_path() . '/classes/views/shared/images-dropdown.php';
3341 4397 $output = ob_get_clean();
@@ -3356,8 +4412,9 @@
3356 4412 *
3357 4413 * @since 5.0.04
3358 4414 *
3359 4415 * @param array $args The arguments.
4416 + *
3360 4417 * @return array
3361 4418 */
3362 4419 private static function fill_default_images_dropdown_args( $args ) {
3363 4420 $defaults = array(
@@ -3370,14 +4427,8 @@
3370 4427
3371 4428 $new_args['options'] = (array) $new_args['options'];
3372 4429 $new_args['input_attrs'] = (array) $new_args['input_attrs'];
3373 4430
3374 - // Set the number of columns.
3375 - $new_args['col_class'] = ceil( 12 / count( $new_args['options'] ) );
3376 - if ( $new_args['col_class'] > 6 ) {
3377 - $new_args['col_class'] = ceil( $new_args['col_class'] / 2 );
3378 - }
3379 -
3380 4431 /**
3381 4432 * Allows modifying the arguments of images_dropdown() method.
3382 4433 *
3383 4434 * @since 5.0.04
@@ -3393,8 +4444,9 @@
3393 4444 *
3394 4445 * @since 5.0.04
3395 4446 *
3396 4447 * @param array $args The arguments.
4448 + *
3397 4449 * @return string
3398 4450 */
3399 4451 private static function get_images_dropdown_input_attrs( $args ) {
3400 4452 $input_attrs = $args['input_attrs'];
@@ -3401,8 +4453,9 @@
3401 4453 $input_attrs['type'] = 'radio';
3402 4454 $input_attrs['name'] = $args['name'];
3403 4455
3404 4456 $input_attrs_str = '';
4457 +
3405 4458 foreach ( $input_attrs as $key => $input_attr ) {
3406 4459 $input_attrs_str .= ' ' . sprintf( '%s="%s"', esc_attr( $key ), esc_attr( $input_attr ) );
3407 4460 }
3408 4461
@@ -3417,8 +4470,23 @@
3417 4470 return apply_filters( 'frm_images_dropdown_input_attrs', $input_attrs_str, $args );
3418 4471 }
3419 4472
3420 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 + /**
3421 4489 * Gets the image of each option in images_dropdown() method.
3422 4490 *
3423 4491 * @since 5.0.04
3424 4492 *
@@ -3423,8 +4491,9 @@
3423 4491 * @since 5.0.04
3424 4492 *
3425 4493 * @param array $option Option data.
3426 4494 * @param array $args The arguments of images_dropdown() method.
4495 + *
3427 4496 * @return string
3428 4497 */
3429 4498 private static function get_images_dropdown_option_image( $option, $args ) {
3430 4499 $image = self::icon_by_class(
@@ -3429,9 +4498,9 @@
3429 4498 private static function get_images_dropdown_option_image( $option, $args ) {
3430 4499 $image = self::icon_by_class(
3431 4500 'frmfont ' . $option['svg'],
3432 4501 array(
3433 - 'echo' => false,
4502 + 'echo' => false,
3434 4503 )
3435 4504 );
3436 4505
3437 4506 $args['option'] = $option;
@@ -3453,8 +4522,9 @@
3453 4522 * @since 5.0.04
3454 4523 *
3455 4524 * @param array $option Option data.
3456 4525 * @param array $args The arguments of images_dropdown() method.
4526 + *
3457 4527 * @return string
3458 4528 */
3459 4529 private static function get_images_dropdown_option_classes( $option, $args ) {
3460 4530 $classes = '';
@@ -3482,17 +4552,19 @@
3482 4552 * @since 5.0.04
3483 4553 *
3484 4554 * @param array $option Option data.
3485 4555 * @param array $args The arguments of images_dropdown() method.
4556 + *
3486 4557 * @return string
3487 4558 */
3488 4559 private static function get_images_dropdown_option_html_attrs( $option, $args ) {
3489 4560 $html_attrs = '';
4561 +
3490 4562 if ( ! empty( $option['custom_attrs'] ) && is_array( $option['custom_attrs'] ) ) {
3491 4563 $html_attrs_arr = array();
3492 4564
3493 4565 foreach ( $option['custom_attrs'] as $key => $value ) {
3494 - if ( in_array( $key, array( 'type', 'class', 'data-value' ) ) ) {
4566 + if ( in_array( $key, array( 'type', 'class', 'data-value' ), true ) ) {
3495 4567 continue;
3496 4568 }
3497 4569
3498 4570 $html_attrs_arr[] = sprintf( '%s="%s"', esc_attr( $key ), esc_attr( $value ) );
@@ -3550,14 +4622,13 @@
3550 4622 * @since 5.0.07
3551 4623 *
3552 4624 * @param array $values
3553 4625 * @param array $keys
4626 + *
3554 4627 * @return array
3555 4628 */
3556 4629 public static function maybe_filter_array( $values, $keys ) {
3557 - $allow_unfiltered_html = self::allow_unfiltered_html();
3558 -
3559 - if ( $allow_unfiltered_html ) {
4630 + if ( self::allow_unfiltered_html() ) {
3560 4631 return $values;
3561 4632 }
3562 4633
3563 4634 foreach ( $keys as $key ) {
@@ -3569,36 +4640,87 @@
3569 4640 return $values;
3570 4641 }
3571 4642
3572 4643 /**
3573 - * Some back end fields allow privleged users to add scripts.
4644 + * Some back end fields allow privileged users to add scripts.
3574 4645 * A site that uses the DISALLOW_UNFILTERED_HTML always remove scripts on echo.
3575 4646 *
3576 4647 * @since 5.0.13
3577 4648 *
3578 4649 * @param string $value
3579 - * @param array|string $allowed 'all' for everything included as defaults
4650 + * @param array|string $allowed 'all' for everything included as defaults.
4651 + *
3580 4652 * @return string
3581 4653 */
3582 4654 public static function maybe_kses( $value, $allowed = 'all' ) {
3583 4655 if ( self::should_never_allow_unfiltered_html() ) {
3584 - $value = self::kses( $value, $allowed );
4656 + return self::kses( $value, $allowed );
3585 4657 }
3586 4658 return $value;
3587 4659 }
3588 4660
3589 4661 /**
3590 - * @since 5.0.16
4662 + * Check if an option attribute used in an [input] shortcode is safe.
3591 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 + *
3592 4669 * @return bool
3593 4670 */
3594 - public static function show_landing_pages() {
3595 - 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 );
3596 4716 }
3597 4717
3598 4718 /**
3599 4719 * @since 5.0.16
3600 4720 *
4721 + * @param string $medium
4722 + *
3601 4723 * @return array
3602 4724 */
3603 4725 public static function get_landing_page_upgrade_data_params( $medium = 'landing' ) {
3604 4726 $params = array(
@@ -3605,8 +4727,9 @@
3605 4727 'medium' => $medium,
3606 4728 'upgrade' => __( 'Form Landing Pages', 'formidable' ),
3607 4729 'message' => __( 'Easily manage a landing page for your form. Upgrade to get form landing pages.', 'formidable' ),
3608 4730 'screenshot' => 'landing.png',
4731 + 'learn-more' => self::get_doc_url( 'landing-page-forms', 'form-landing-page-settings', false ),
3609 4732 );
3610 4733 return self::get_upgrade_data_params( 'landing', $params );
3611 4734 }
3612 4735
@@ -3612,20 +4735,10 @@
3612 4735
3613 4736 /**
3614 4737 * @since 5.0.17
3615 4738 *
3616 - * @param string $plugin
3617 - * @return string|false
3618 - */
3619 - private static function get_plan_required( $plugin ) {
3620 - $link = FrmAddonsController::install_link( $plugin );
3621 - return FrmFormsHelper::get_plan_required( $link );
3622 - }
3623 -
3624 - /**
3625 - * @since 5.0.17
4739 + * @param string $feature
3626 4740 *
3627 - * @param string
3628 4741 * @return bool
3629 4742 */
3630 4743 public static function show_new_feature( $feature ) {
3631 4744 $link = FrmAddonsController::install_link( $feature );
@@ -3632,16 +4745,21 @@
3632 4745 return array_key_exists( 'status', $link ) || array_key_exists( 'class', $link );
3633 4746 }
3634 4747
3635 4748 /**
4749 + * Enhances upgrade data parameters with installation link and plan requirement information.
4750 + *
3636 4751 * @since 5.0.17
3637 4752 *
3638 - * @param string $plugin
3639 - * @param array $params
3640 - * @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.
3641 4758 */
3642 - public static function get_upgrade_data_params( $plugin, $params ) {
4759 + public static function get_upgrade_data_params( $plugin, $params, $detailed = false ) {
3643 4760 $link = FrmAddonsController::install_link( $plugin );
4761 +
3644 4762 if ( ! $link ) {
3645 4763 return $params;
3646 4764 }
3647 4765
@@ -3647,15 +4765,20 @@
3647 4765
3648 4766 if ( ! empty( $link['url'] ) && self::pro_is_installed() ) {
3649 4767 $params['oneclick'] = json_encode( $link );
3650 4768 unset( $params['message'] );
4769 +
3651 4770 if ( ! isset( $params['medium'] ) ) {
3652 4771 $params['medium'] = $plugin;
3653 4772 }
3654 4773 } else {
3655 - $params['requires'] = FrmFormsHelper::get_plan_required( $link );
4774 + $params['requires'] = $params['requires'] ?? FrmFormsHelper::get_plan_required( $link );
3656 4775 }
3657 4776
4777 + if ( $detailed ) {
4778 + $params['plugin-status'] = $link['status'] ?? '';
4779 + }
4780 +
3658 4781 return $params;
3659 4782 }
3660 4783
3661 4784 /**
@@ -3664,8 +4787,9 @@
3664 4787 *
3665 4788 * @since 5.0.17
3666 4789 *
3667 4790 * @param string $text
4791 + *
3668 4792 * @return bool
3669 4793 */
3670 4794 public static function ctype_xdigit( $text ) {
3671 4795 if ( function_exists( 'ctype_xdigit' ) ) {
@@ -3680,12 +4804,14 @@
3680 4804 * @since 5.2.01
3681 4805 */
3682 4806 public static function set_current_screen_and_hook_suffix() {
3683 4807 global $hook_suffix;
4808 +
3684 4809 if ( is_null( $hook_suffix ) ) {
3685 4810 // $hook_suffix gets used in substr so make sure it's not null. PHP 8.1 deprecates null in substr.
3686 4811 $hook_suffix = ''; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
3687 4812 }
4813 +
3688 4814 set_current_screen();
3689 4815 }
3690 4816
3691 4817 /**
@@ -3692,15 +4818,15 @@
3692 4818 * Shows pill text.
3693 4819 *
3694 4820 * @since 5.2.02
3695 4821 *
3696 - * @param string $text Text in the pill. Default is NEW.
4822 + * @param string|null $text Text in the pill. Default is NEW.
3697 4823 */
3698 4824 public static function show_pill_text( $text = null ) {
3699 4825 if ( null === $text ) {
3700 4826 $text = __( 'NEW', 'formidable' );
3701 4827 }
3702 - echo '<span class="frm-new-pill">' . esc_html( $text ) . '</span>';
4828 + echo '<span class="frm-meta-tag frm-new-pill">' . esc_html( $text ) . '</span>';
3703 4829 }
3704 4830
3705 4831 /**
3706 4832 * Count the number of decimals digits.
@@ -3707,9 +4833,10 @@
3707 4833 *
3708 4834 * @since 5.2.07
3709 4835 *
3710 4836 * @param mixed $num Number.
3711 - * @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.
3712 4839 */
3713 4840 public static function count_decimals( $num ) {
3714 4841 if ( ! is_numeric( $num ) ) {
3715 4842 return false;
@@ -3716,8 +4843,9 @@
3716 4843 }
3717 4844
3718 4845 $num = (string) $num;
3719 4846 $parts = explode( '.', $num );
4847 +
3720 4848 if ( 1 === count( $parts ) ) {
3721 4849 return 0;
3722 4850 }
3723 4851
@@ -3740,9 +4868,9 @@
3740 4868 }
3741 4869
3742 4870 add_filter(
3743 4871 'option_gmt_offset',
3744 - function( $offset ) {
4872 + function ( $offset ) {
3745 4873 if ( ! is_string( $offset ) || is_numeric( $offset ) ) {
3746 4874 // Leave a valid value alone.
3747 4875 return $offset;
3748 4876 }
@@ -3799,17 +4927,20 @@
3799 4927 * @since 5.5.5
3800 4928 *
3801 4929 * @param string $url
3802 4930 * @param string $expected_extension
4931 + *
3803 4932 * @return bool
3804 4933 */
3805 4934 public static function validate_url_is_in_s3_bucket( $url, $expected_extension ) {
3806 - $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 +
3807 4937 if ( ! $file_is_in_expected_s3_bucket ) {
3808 4938 return false;
3809 4939 }
3810 4940
3811 4941 $parsed = parse_url( $url );
4942 +
3812 4943 if ( ! is_array( $parsed ) ) {
3813 4944 // URL is malformed.
3814 4945 return false;
3815 4946 }
@@ -3815,74 +4946,13 @@
3815 4946 }
3816 4947
3817 4948 $path = $parsed['path'];
3818 4949 $ext = pathinfo( $path, PATHINFO_EXTENSION );
3819 - if ( $expected_extension !== $ext ) {
3820 - // The URL isn't to an XML file.
3821 - return false;
3822 - }
3823 -
3824 - return true;
4950 + // The URL isn't to an XML file.
4951 + return $expected_extension === $ext;
3825 4952 }
3826 4953
3827 4954 /**
3828 - * @since 4.08
3829 - * @deprecated 4.09.01
3830 - */
3831 - public static function expiring_message() {
3832 - _deprecated_function( __METHOD__, '4.09.01', 'FrmProAddonsController::expiring_message' );
3833 - if ( is_callable( 'FrmProAddonsController::expiring_message' ) ) {
3834 - FrmProAddonsController::expiring_message();
3835 - }
3836 - }
3837 -
3838 - /**
3839 - * Use the WP 4.7 wp_doing_ajax function
3840 - *
3841 - * @since 2.05.07
3842 - * @deprecated 4.04.04
3843 - */
3844 - public static function wp_doing_ajax() {
3845 - _deprecated_function( __METHOD__, '4.04.04', 'wp_doing_ajax' );
3846 - return wp_doing_ajax();
3847 - }
3848 -
3849 - /**
3850 - * @deprecated 4.0
3851 - */
3852 - public static function insert_opt_html( $args ) {
3853 - _deprecated_function( __METHOD__, '4.0', 'FrmFormsHelper::insert_opt_html' );
3854 - FrmFormsHelper::insert_opt_html( $args );
3855 - }
3856 -
3857 - /**
3858 - * @deprecated 3.01
3859 - * @codeCoverageIgnore
3860 - */
3861 - public static function sanitize_array( &$values ) {
3862 - FrmDeprecated::sanitize_array( $values );
3863 - }
3864 -
3865 - /**
3866 - * @since 2.0
3867 - * @deprecated 5.0.13
3868 - *
3869 - * @return string The base Google APIS url for the current version of jQuery UI
3870 - */
3871 - public static function jquery_ui_base_url() {
3872 - _deprecated_function( __FUNCTION__, '5.0.13', 'FrmProAppHelper::jquery_ui_base_url' );
3873 - return is_callable( 'FrmProAppHelper::jquery_ui_base_url' ) ? FrmProAppHelper::jquery_ui_base_url() : '';
3874 - }
3875 -
3876 - /**
3877 - * @since 4.07
3878 - * @deprecated 6.0
3879 - */
3880 - public static function renewal_message() {
3881 - _deprecated_function( __METHOD__, '6.0', 'FrmProAddonsController::renewal_message' );
3882 - }
3883 -
3884 - /**
3885 4955 * Display a dismissable warning message and save its dismissal state.
3886 4956 *
3887 4957 * @since 6.3
3888 4958 *
@@ -3887,8 +4957,9 @@
3887 4957 * @since 6.3
3888 4958 *
3889 4959 * @param string $message The warning message to display.
3890 4960 * @param string $option The unique identifier for the dismissal state of the message and the WP Ajax action.
4961 + *
3891 4962 * @return void
3892 4963 */
3893 4964 public static function add_dismissable_warning_message( $message = '', $option = '' ) {
3894 4965 if ( ! $message || ! $option ) {
@@ -3894,9 +4965,9 @@
3894 4965 if ( ! $message || ! $option ) {
3895 4966 return;
3896 4967 }
3897 4968
3898 - $ajax_callback = function() use ( $option ) {
4969 + $ajax_callback = function () use ( $option ) {
3899 4970 self::dismiss_warning_message( $option );
3900 4971 };
3901 4972
3902 4973 // We're handling JS codes with `doJsonPost` and it adds 'frm_' to the beginning of the action.
@@ -3904,9 +4975,9 @@
3904 4975 add_action( 'wp_ajax_frm_' . $option, $ajax_callback );
3905 4976
3906 4977 add_filter(
3907 4978 'frm_message_list',
3908 - function( $show_messages ) use ( $message, $option ) {
4979 + function ( $show_messages ) use ( $message, $option ) {
3909 4980 if ( get_option( $option, false ) ) {
3910 4981 return $show_messages;
3911 4982 }
3912 4983
@@ -3913,9 +4984,9 @@
3913 4984 $dismiss_icon = self::icon_by_class(
3914 4985 'frmfont frm_close_icon',
3915 4986 array(
3916 4987 'aria-label' => _x( 'Dismiss', 'warning message: close icon label', 'formidable' ),
3917 - 'echo' => false,
4988 + 'echo' => false,
3918 4989 )
3919 4990 );
3920 4991
3921 4992 $show_messages[] = $message;
@@ -3931,8 +5002,9 @@
3931 5002 *
3932 5003 * @since 6.3
3933 5004 *
3934 5005 * @param string $option The unique identifier for the dismissal state of the message.
5006 + *
3935 5007 * @return void
3936 5008 */
3937 5009 public static function dismiss_warning_message( $option = '' ) {
3938 5010 self::permission_check( 'frm_change_settings' );
@@ -3938,10 +5010,182 @@
3938 5010 self::permission_check( 'frm_change_settings' );
3939 5011 check_ajax_referer( 'frm_ajax', 'nonce' );
3940 5012
3941 5013 if ( $option ) {
3942 - update_option( $option, true, 'no' );
5014 + update_option( $option, true, false );
3943 5015 }
3944 5016
3945 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;
3946 5190 }
3947 5191 }