PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.28
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.28
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
formidable / classes / helpers / FrmStylesHelper.php

FrmStylesHelper.php in Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More 6.28, at classes/helpers/FrmStylesHelper.php

1,166 lines 30.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if ( ! defined( 'ABSPATH' ) ) {
3 die( 'You are not allowed to call this page directly.' );
4 }
5
6 class FrmStylesHelper {
7
8 /**
9 * @return array
10 */
11 public static function get_upload_base() {
12 $uploads = wp_upload_dir();
13
14 if ( is_ssl() && ! preg_match( '/^https:\/\/.*\..*$/', $uploads['baseurl'] ) ) {
15 $uploads['baseurl'] = str_replace( 'http://', 'https://', $uploads['baseurl'] );
16 }
17
18 return $uploads;
19 }
20
21 /**
22 * Called from the admin header.
23 *
24 * @since 4.0
25 *
26 * @return void
27 */
28 public static function save_button() {
29 // phpcs:disable Generic.WhiteSpace.ScopeIndent
30 ?>
31 <input type="submit" name="submit" class="button button-primary frm-button-primary" value="<?php esc_attr_e( 'Update', 'formidable' ); ?>" />
32 <?php
33 // phpcs:enable Generic.WhiteSpace.ScopeIndent
34 }
35
36 /**
37 * @since 2.05
38 *
39 * @return array
40 */
41 public static function get_css_label_positions() {
42 return array(
43 'none' => __( 'top', 'formidable' ),
44 'left' => __( 'left', 'formidable' ),
45 'right' => __( 'right', 'formidable' ),
46 'no_label' => __( 'none', 'formidable' ),
47 'inside' => __( 'inside', 'formidable' ),
48 );
49 }
50
51 /**
52 * @since 6.11 Added $field param.
53 *
54 * @param array|object $field
55 *
56 * @return array
57 */
58 public static function get_single_label_positions( $field = array() ) {
59 $label_positions = array(
60 'top' => __( 'Top', 'formidable' ),
61 'left' => __( 'Left', 'formidable' ),
62 'right' => __( 'Right', 'formidable' ),
63 'inline' => __( 'Inline (left without a set width)', 'formidable' ),
64 'none' => __( 'None', 'formidable' ),
65 'hidden' => __( 'Hidden (but leave the space)', 'formidable' ),
66 'inside' => __( 'Placeholder inside the field', 'formidable' ),
67 );
68
69 /**
70 * Allows updating label positions in field settings.
71 *
72 * @since 6.11
73 *
74 * @param array $label_positions
75 * @param array|object $field
76 */
77 return apply_filters( 'frm_single_label_positions', $label_positions, $field );
78 }
79
80 /**
81 * @return array
82 */
83 public static function minus_icons() {
84 return array(
85 0 => array(
86 '-' => '62e',
87 '+' => '62f',
88 ),
89 1 => array(
90 '-' => '600',
91 '+' => '602',
92 ),
93 2 => array(
94 '-' => '604',
95 '+' => '603',
96 ),
97 3 => array(
98 '-' => '633',
99 '+' => '632',
100 ),
101 4 => array(
102 '-' => '613',
103 '+' => '60f',
104 ),
105 );
106 }
107
108 /**
109 * @return array
110 */
111 public static function arrow_icons() {
112 $minus_icons = self::minus_icons();
113
114 return array(
115 6 => array(
116 '-' => '62d',
117 '+' => '62a',
118 ),
119 0 => array(
120 '-' => '60d',
121 '+' => '609',
122 ),
123 1 => array(
124 '-' => '60e',
125 '+' => '60c',
126 ),
127 2 => array(
128 '-' => '630',
129 '+' => '631',
130 ),
131 3 => array(
132 '-' => '62b',
133 '+' => '628',
134 ),
135 4 => array(
136 '-' => '62c',
137 '+' => '629',
138 ),
139 5 => array(
140 '-' => '635',
141 '+' => '634',
142 ),
143 'p0' => $minus_icons[0],
144 'p1' => $minus_icons[1],
145 'p2' => $minus_icons[2],
146 'p3' => $minus_icons[3],
147 'p4' => $minus_icons[4],
148 );
149 }
150
151 /**
152 * @since 2.0
153 *
154 * @param int|string $key Icon key.
155 * @param string $icon Icon state, `+` or `-`.
156 * @param string $type Icon type, such as `arrow`.
157 *
158 * @return string The class for this icon.
159 */
160 public static function icon_key_to_class( $key, $icon = '+', $type = 'arrow' ) {
161 if ( 'arrow' === $type && is_numeric( $key ) ) {
162 // frm_arrowup6_icon.
163 $arrow = array(
164 '-' => 'down',
165 '+' => 'up',
166 );
167 $class = 'frm_arrow' . $arrow[ $icon ];
168 } else {
169 // frm_minus1_icon.
170 $key = str_replace( 'p', '', $key );
171 $plus = array(
172 '-' => 'minus',
173 '+' => 'plus',
174 );
175 $class = 'frm_' . $plus[ $icon ];
176 }
177
178 if ( $key ) {
179 $class .= $key;
180 }
181
182 return $class . '_icon';
183 }
184
185 /**
186 * @param WP_Post $style
187 * @param FrmStyle $frm_style
188 * @param string $type
189 *
190 * @return void
191 */
192 public static function bs_icon_select( $style, $frm_style, $type = 'arrow' ) {
193 $function_name = $type . '_icons';
194 $icons = self::$function_name();
195 unset( $function_name );
196
197 $name = 'arrow' === $type ? 'collapse_icon' : 'repeat_icon';
198 // phpcs:disable Generic.WhiteSpace.ScopeIndent
199 ?>
200 <div class="btn-group" id="frm_<?php echo esc_attr( $name ); ?>_select">
201 <button class="multiselect dropdown-toggle btn btn-default" data-toggle="dropdown" type="button">
202 <?php FrmAppHelper::icon_by_class( 'frmfont ' . self::icon_key_to_class( $style->post_content[ $name ], '+', $type ) ); ?>
203 <?php FrmAppHelper::icon_by_class( 'frmfont ' . self::icon_key_to_class( $style->post_content[ $name ], '-', $type ) ); ?>
204 <b class="caret"></b>
205 </button>
206 <ul class="multiselect-container frm-dropdown-menu">
207 <?php foreach ( $icons as $key => $icon ) { ?>
208 <li <?php echo $style->post_content['collapse_icon'] == $key ? 'class="active"' : ''; // phpcs:ignore Universal.Operators.StrictComparisons ?>>
209 <a href="javascript:void(0);">
210 <label>
211 <input type="radio" value="<?php echo esc_attr( $key ); ?>" name="<?php echo esc_attr( $frm_style->get_field_name( $name ) ); ?>" <?php checked( $style->post_content[ $name ], $key ); ?> /><?php // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong ?>
212 <span>
213 <?php
214 FrmAppHelper::icon_by_class( 'frmfont ' . self::icon_key_to_class( $key, '+', $type ) );
215 FrmAppHelper::icon_by_class( 'frmfont ' . self::icon_key_to_class( $key, '-', $type ) );
216 ?>
217 </span>
218 </label>
219 </a>
220 </li>
221 <?php } ?>
222 </ul>
223 </div>
224 <?php
225 // phpcs:enable Generic.WhiteSpace.ScopeIndent
226 }
227
228 /**
229 * Convert a color setting to a RGB CSV (without the rgb()/rgba() wrapper).
230 *
231 * @since 2.0
232 *
233 * @param string $color Color setting value. This could be hex or rgb.
234 *
235 * @return string RGB value without the rgb() wrapper.
236 */
237 public static function hex2rgb( $color ) {
238 $rgb = str_starts_with( $color, 'rgb' ) ? self::get_rgb_array_from_rgb( $color ) : self::get_rgb_array_from_hex( $color );
239 return implode( ',', $rgb );
240 }
241
242 /**
243 * Remove the rgb()/rgba() wrapper from a RGB color and return its R, G and B values as an array.
244 *
245 * @since 6.8.3
246 *
247 * @param string $rgb RGB value including the rgb() or rgba() wrapper.
248 *
249 * @return array<string> including three numeric values for R, G, and B.
250 */
251 private static function get_rgb_array_from_rgb( $rgb ) {
252 $rgb = str_replace( array( 'rgb(', 'rgba(', ')' ), '', $rgb );
253 $rgb = explode( ',', $rgb );
254
255 if ( 4 === count( $rgb ) ) {
256 // Drop the alpha. The function is expected to only return r,g,b with no alpha.
257 array_pop( $rgb );
258 }
259
260 return $rgb;
261 }
262
263 /**
264 * Get the R, G, and B array values from a Hex color code.
265 *
266 * @since 6.8.3
267 *
268 * @param string $hex A hex color string.
269 *
270 * @return array<string> Including three numeric values for R, G, and B.
271 */
272 private static function get_rgb_array_from_hex( $hex ) {
273 $hex = str_replace( '#', '', $hex );
274 list( $r, $g, $b ) = sscanf( $hex, '%02x%02x%02x' );
275 return array( $r, $g, $b );
276 }
277
278 /**
279 * @since 4.0
280 *
281 * @param string $hex Hex color string.
282 * @param float|int|string $a Alpha channel value.
283 *
284 * @return string
285 */
286 public static function hex2rgba( $hex, $a ) {
287 $rgb = self::hex2rgb( $hex );
288 return 'rgba(' . $rgb . ',' . $a . ')';
289 }
290
291 /**
292 * @since 6.0
293 *
294 * @param string $rgba Color setting value. This could be hex or rgb.
295 *
296 * @return string Hex color value.
297 */
298 private static function rgb_to_hex( $rgba ) {
299 if ( str_starts_with( $rgba, '#' ) ) {
300 // Color is already hex.
301 return $rgba;
302 }
303 preg_match( '/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i', $rgba, $by_color );
304 return sprintf( '%02x%02x%02x', $by_color[1], $by_color[2], $by_color[3] );
305 }
306
307 /**
308 * @since 6.8
309 *
310 * @param string $hsl
311 *
312 * @return string|null Null if it fails to parse the HSL string.
313 */
314 private static function hsl_to_hex( $hsl ) {
315 // Convert hsla to hsl.
316 $hsl = preg_replace( '/hsla\((\d+),\s*([\d.]+)%,\s*([\d.]+)%,\s*([\d.]+)\)/', 'hsl($1, $2%, $3%)', $hsl );
317
318 // Extract HSL components from the color string.
319 preg_match( '/hsl\((\d+),\s*(\d+)%,\s*(\d+)%\)/', $hsl, $matches );
320
321 if ( count( $matches ) !== 4 ) {
322 // Invalid HSL string format.
323 return null;
324 }
325
326 // Extract HSL values.
327 $h = (int) $matches[1];
328 $s = (int) $matches[2] / 100;
329 $l = (int) $matches[3] / 100;
330
331 // Calculate RGB values.
332 $c = ( 1 - abs( 2 * $l - 1 ) ) * $s;
333 $x = $c * ( 1 - abs( ( (int) ( $h / 60 ) % 2 ) - 1 ) );
334 $m = $l - $c / 2;
335 $r = 0;
336 $g = 0;
337 $b = 0;
338
339 if ( $h >= 0 && $h < 60 ) {
340 $r = $c;
341 $g = $x;
342 } elseif ( $h >= 60 && $h < 120 ) {
343 $r = $x;
344 $g = $c;
345 } elseif ( $h >= 120 && $h < 180 ) {
346 $g = $c;
347 $b = $x;
348 } elseif ( $h >= 180 && $h < 240 ) {
349 $g = $x;
350 $b = $c;
351 } elseif ( $h >= 240 && $h < 300 ) {
352 $r = $x;
353 $b = $c;
354 } elseif ( $h >= 300 && $h < 360 ) {
355 $r = $c;
356 $b = $x;
357 }//end if
358
359 // Convert RGB to 8-bit values
360 $r = round( ( $r + $m ) * 255 );
361 $g = round( ( $g + $m ) * 255 );
362 $b = round( ( $b + $m ) * 255 );
363
364 // Convert RGB to hex
365 return sprintf( '%02x%02x%02x', $r, $g, $b );
366 }
367
368 /**
369 * @since 2.3
370 *
371 * @param string $hex string The original color in hex format #ffffff.
372 * @param int $steps integer Should be between -255 and 255. Negative = darker, positive = lighter.
373 *
374 * @return string
375 */
376 public static function adjust_brightness( $hex, $steps ) {
377 $steps = max( - 255, min( 255, $steps ) );
378
379 if ( str_starts_with( $hex, 'rgba(' ) ) {
380 $rgba = str_replace( ')', '', str_replace( 'rgba(', '', $hex ) );
381 list ( $r, $g, $b, $a ) = array_map( 'trim', explode( ',', $rgba ) );
382 $r = max( 0, min( 255, $r + $steps ) );
383 $g = max( 0, min( 255, $g + $steps ) );
384 $b = max( 0, min( 255, $b + $steps ) );
385 return 'rgba(' . $r . ',' . $g . ',' . $b . ',' . $a . ')';
386 }
387
388 // Normalize into a six character long hex string
389 $hex = str_replace( '#', '', $hex );
390 self::fill_hex( $hex );
391
392 // Split into three parts: R, G and B
393 $color_parts = str_split( $hex, 2 );
394 $return = '#';
395
396 foreach ( $color_parts as $color ) {
397 // Convert to decimal.
398 $color = hexdec( $color );
399 // Adjust color.
400 $color = max( 0, min( 255, $color + $steps ) );
401
402 // Make two char hex code.
403 $return .= str_pad( dechex( $color ), 2, '0', STR_PAD_LEFT );
404 }
405
406 return $return;
407 }
408
409 /**
410 * @since 6.0
411 *
412 * @param string $color
413 *
414 * @return int
415 */
416 public static function get_color_brightness( $color ) {
417 if ( str_starts_with( $color, 'rgb' ) ) {
418 $color = self::rgb_to_hex( $color );
419 }
420
421 if ( str_starts_with( $color, 'hsl' ) ) {
422 $hsl_to_hex = self::hsl_to_hex( $color );
423
424 if ( is_null( $hsl_to_hex ) ) {
425 // Fallback if we cannot convert the HSL value.
426 return 0;
427 }
428
429 $color = $hsl_to_hex;
430 }
431
432 self::fill_hex( $color );
433
434 $c_r = hexdec( substr( $color, 0, 2 ) );
435 $c_g = hexdec( substr( $color, 2, 2 ) );
436 $c_b = hexdec( substr( $color, 4, 2 ) );
437
438 return ( ( $c_r * 299 ) + ( $c_g * 587 ) + ( $c_b * 114 ) ) / 1000;
439 }
440
441 /**
442 * Change a 3 character hex color to a 6 character one.
443 *
444 * @since 6.0
445 *
446 * @param string $color Color value, passed by reference.
447 *
448 * @return void
449 */
450 private static function fill_hex( &$color ) {
451 if ( 3 === strlen( $color ) ) {
452 $color = $color[0] . $color[0] . $color[1] . $color[1] . $color[2] . $color[2];
453 }
454 }
455
456 /**
457 * @since 4.05.02
458 *
459 * @param array $vars CSS variable keys.
460 *
461 * @return array
462 */
463 public static function get_css_vars( $vars = array() ) {
464 $vars = apply_filters( 'frm_css_vars', $vars );
465 return array_unique( $vars );
466 }
467
468 /**
469 * @since 4.05.02
470 *
471 * @param array $settings Style settings.
472 * @param array $defaults Default style settings.
473 * @param array $vars CSS variable keys to output.
474 *
475 * @return void
476 */
477 public static function output_vars( $settings, $defaults = array(), $vars = array() ) {
478 if ( ! $vars ) {
479 $vars = self::get_css_vars( array_keys( $settings ) );
480 }
481
482 $vars = array_diff( $vars, self::get_style_keys_to_remove_from_output_vars() );
483
484 foreach ( $vars as $var ) {
485 if ( ! isset( $settings[ $var ] ) || ! self::css_key_is_valid( $var ) ) {
486 continue;
487 }
488
489 if ( ! isset( $defaults[ $var ] ) ) {
490 $defaults[ $var ] = '';
491 }
492
493 $prepared_value = '';
494
495 if ( self::should_add_css_var( $settings, $defaults, $var, $prepared_value ) ) {
496 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
497 echo '--' . esc_html( self::clean_var_name( str_replace( '_', '-', $var ) ) ) . ':' . $prepared_value . ';';
498 }
499 }
500 }
501
502 /**
503 * None of these style settings are used as CSS variables, so we want to exclude them to keep the CSS output clean.
504 *
505 * @since 6.26.1
506 *
507 * @return array<string>
508 */
509 private static function get_style_keys_to_remove_from_output_vars() {
510 return array(
511 'remove_box_shadow',
512 'remove_box_shadow_active',
513 'theme_css',
514 'theme_name',
515 'theme_selector',
516 'important_style',
517 'submit_style',
518 'collapse_icon',
519 'center_form',
520 'custom_css',
521 'style_class',
522 'submit_bg_img',
523 'change_margin',
524 'repeat_icon',
525 'use_base_font_size',
526 'field_shape_type',
527 'bg_image_id',
528 'single_style_custom_css',
529 'enable_style_custom_css',
530 );
531 }
532
533 /**
534 * Check if a CSS variable setting is not blank, doesn't match the default, and doesn't include invalid substrings.
535 *
536 * @since 6.24
537 *
538 * @param array $settings Array of setting values.
539 * @param array $defaults Array of default values.
540 * @param string $var The setting key name.
541 * @param string $prepared_value The value from calling css_var_prepare_value. This is set by reference so it can be used after this function is called.
542 *
543 * @return bool True if the CSS value should be printed.
544 */
545 private static function should_add_css_var( $settings, $defaults, $var, &$prepared_value ) {
546 $prepared_value = self::css_var_prepare_value( $settings, $var );
547
548 if ( $prepared_value === '' ) {
549 return false;
550 }
551
552 if ( $defaults && $defaults[ $var ] === $prepared_value ) {
553 return false;
554 }
555
556 return self::css_value_is_valid( $prepared_value );
557 }
558
559 /**
560 * Prevent invalid CSS keys from getting added to the generated CSS.
561 *
562 * @since 6.20
563 *
564 * @param string $key
565 *
566 * @return bool
567 */
568 private static function css_key_is_valid( $key ) {
569 // Any key that is abnormally large is not valid.
570 // Any key that contains a '{' is not valid.
571 return strlen( $key ) < 100 && ! str_contains( $key, '{' );
572 }
573
574 /**
575 * Confirm a CSS value is valid.
576 * If it appears to contain JavaScript, it will not be added.
577 *
578 * @since 6.20
579 *
580 * @param string $var
581 *
582 * @return bool
583 */
584 private static function css_value_is_valid( $var ) {
585 if ( is_numeric( $var ) ) {
586 return true;
587 }
588
589 // None of these substrings should be present in any CSS value.
590 $invalid_substrings = array(
591 'function(',
592 ';userAgent',
593 ';stopPropagation',
594 '{const',
595 'window[',
596 'navigator[',
597 'Array;',
598 );
599
600 foreach ( $invalid_substrings as $substring ) {
601 if ( str_contains( $var, $substring ) ) {
602 return false;
603 }
604 }
605
606 return true;
607 }
608
609 /**
610 * Remove anything that isn't used as a CSS variable name.
611 *
612 * @param string $var_name
613 *
614 * @return string
615 */
616 private static function clean_var_name( $var_name ) {
617 return preg_replace( '/[^a-zA-Z0-9_-]/', '', $var_name );
618 }
619
620 /**
621 * Prepare the value for a CSS variable.
622 *
623 * @since 6.14
624 *
625 * @param array $settings An array of css style.
626 * @param string $key
627 *
628 * @return string
629 */
630 private static function css_var_prepare_value( $settings, $key ) {
631 $value = $settings[ $key ];
632
633 if ( ! is_string( $value ) && ! is_numeric( $value ) ) {
634 return '';
635 }
636
637 switch ( $key ) {
638 case 'font':
639 return safecss_filter_attr( $value );
640
641 case 'border_width_error':
642 case 'field_border_width':
643 if ( ! empty( $settings['field_shape_type'] ) && 'underline' === $settings['field_shape_type'] ) {
644 return safecss_filter_attr( '0px 0px ' . $value . ' 0px' );
645 }
646 break;
647
648 case 'box_shadow':
649 if ( ! empty( $settings['field_shape_type'] ) && 'underline' === $settings['field_shape_type'] ) {
650 return safecss_filter_attr( 'none' );
651 }
652 break;
653
654 case 'border_radius':
655 if ( ! empty( $settings['field_shape_type'] ) ) {
656 switch ( $settings['field_shape_type'] ) {
657 case 'underline':
658 case 'regular':
659 return safecss_filter_attr( '0px' );
660 case 'circle':
661 return safecss_filter_attr( '30px' );
662 }
663 }
664 break;
665 }//end switch
666
667 return esc_html( $value );
668 }
669
670 /**
671 * @since 2.3
672 *
673 * @param WP_Post $style
674 *
675 * @return array
676 */
677 public static function get_settings_for_output( $style ) {
678 if ( self::previewing_style() ) {
679 $frm_style = new FrmStyle();
680
681 if ( isset( $_POST['frm_style_setting'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
682
683 // Sanitizing is done later.
684 //phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Missing
685 $posted = wp_unslash( $_POST['frm_style_setting'] );
686
687 if ( ! is_array( $posted ) ) {
688 $posted = json_decode( $posted, true );
689 FrmAppHelper::format_form_data( $posted );
690 $settings = $frm_style->sanitize_post_content( $posted['frm_style_setting']['post_content'] );
691 $style_name = sanitize_title( $posted['style_name'] );
692 } else {
693 $settings = $frm_style->sanitize_post_content( $posted['post_content'] );
694 $style_name = FrmAppHelper::get_post_param( 'style_name', '', 'sanitize_title' );
695 }
696 } else {
697 $settings = $frm_style->sanitize_post_content( wp_unslash( $_GET ) );
698 $style_name = FrmAppHelper::get_param( 'style_name', '', 'get', 'sanitize_title' );
699 }
700
701 $settings = self::update_base_font_size( $settings, $frm_style->get_defaults() );
702
703 FrmAppHelper::sanitize_value( 'sanitize_text_field', $settings );
704
705 $settings['style_class'] = '';
706
707 if ( ! empty( $style_name ) ) {
708 $settings['style_class'] = $style_name . '.';
709 }
710 } else {
711 $settings = $style->post_content;
712 $settings['style_class'] = 'frm_style_' . $style->post_name . '.';
713 }//end if
714
715 $settings['style_class'] .= 'with_frm_style';
716 $settings['font'] = stripslashes( $settings['font'] );
717 $settings['change_margin'] = self::description_margin_for_screensize( $settings['width'] );
718
719 $checkbox_opts = array( 'important_style', 'auto_width', 'submit_style', 'collapse_icon', 'center_form' );
720
721 foreach ( $checkbox_opts as $opt ) {
722 if ( ! isset( $settings[ $opt ] ) ) {
723 $settings[ $opt ] = 0;
724 }
725 }
726
727 self::prepare_color_output( $settings );
728
729 $settings['field_height'] = $settings['field_height'] === '' ? 'auto' : $settings['field_height'];
730 $settings['field_width'] = $settings['field_width'] === '' ? 'auto' : $settings['field_width'];
731 $settings['auto_width'] = $settings['auto_width'] ? 'auto' : $settings['field_width'];
732 $settings['box_shadow'] = ! empty( $settings['remove_box_shadow'] ) ? 'none' : '0 1px 2px 0 rgba(18, 18, 23, 0.05)';
733
734 if ( ! isset( $settings['repeat_icon'] ) ) {
735 $settings['repeat_icon'] = 1;
736 }
737
738 return $settings;
739 }
740
741 /**
742 * Update the "Base Font Size" value from "Quick Settings across multiple settings values".
743 *
744 * @since 6.14
745 *
746 * @param array $settings An array of css style.
747 * @param array $defaults Default style settings.
748 *
749 * @return array
750 */
751 public static function update_base_font_size( $settings, $defaults ) {
752 if ( empty( $settings['base_font_size'] ) || empty( $settings['use_base_font_size'] ) || 'false' === $settings['use_base_font_size'] ) {
753 return $settings;
754 }
755
756 $base_font_size = (int) $settings['base_font_size'];
757 $font_size = $defaults['font_size'];
758 $font_sizes_to_update = array(
759 'font_size',
760 'field_font_size',
761 'check_font_size',
762 'title_size',
763 'form_desc_size',
764 'description_font_size',
765 'section_font_size',
766 'submit_font_size',
767 'success_font_size',
768 'error_font_size',
769 'progress_size',
770 );
771
772 array_map(
773 function ( $key ) use ( $defaults, $font_size, $base_font_size, &$settings ) {
774 if ( isset( $settings[ $key ] ) ) {
775 $settings[ $key ] = round( self::get_base_font_size_scale( $key, $font_size, $defaults ) * $base_font_size ) . 'px';
776 }
777 },
778 $font_sizes_to_update
779 );
780
781 return $settings;
782 }
783
784 /**
785 * Get style font size scale value.
786 *
787 * @since 6.14
788 *
789 * @param string $key Setting key.
790 * @param int|string $value Base font size value.
791 * @param array $defaults Default style settings.
792 *
793 * @return float
794 */
795 private static function get_base_font_size_scale( $key, $value, $defaults ) {
796 if ( empty( $defaults[ $key ] ) || ! is_numeric( (int) $defaults[ $key ] ) || ! is_numeric( (int) $value ) || 0 === (int) $value ) {
797 return 1;
798 }
799
800 return round( (int) $defaults[ $key ] / (int) $value, 2 );
801 }
802
803 /**
804 * @since 2.3
805 *
806 * @param array $settings Style settings, passed by reference.
807 * @param bool $allow_transparent Whether transparent colors are allowed.
808 *
809 * @return void
810 */
811 public static function prepare_color_output( &$settings, $allow_transparent = true ) {
812 $colors = self::allow_color_override();
813
814 foreach ( $colors as $css => $opts ) {
815 if ( $css === 'transparent' && ! $allow_transparent ) {
816 $css = '';
817 }
818
819 foreach ( $opts as $opt ) {
820 self::get_color_output( $css, $settings[ $opt ] );
821 }
822 }
823 }
824
825 /**
826 * @since 2.3
827 *
828 * @return array
829 */
830 private static function allow_color_override() {
831 $frm_style = new FrmStyle();
832 $colors = $frm_style->get_color_settings();
833
834 $transparent = array(
835 'fieldset_color',
836 'fieldset_bg_color',
837 'bg_color',
838 'bg_color_active',
839 'bg_color_disabled',
840 'section_bg_color',
841 'error_bg',
842 'success_bg_color',
843 'progress_bg_color',
844 'progress_active_bg_color',
845 'submit_border_color',
846 'submit_hover_border_color',
847 'submit_active_border_color',
848 'submit_hover_bg_color',
849 'submit_active_bg_color',
850 );
851
852 return array(
853 'transparent' => $transparent,
854 '' => array_diff( $colors, $transparent ),
855 );
856 }
857
858 /**
859 * @since 2.3
860 *
861 * @param string $default Default color value.
862 * @param string $color Color value, passed by reference.
863 *
864 * @return void
865 */
866 private static function get_color_output( $default, &$color ) {
867 $color = trim( $color );
868
869 if ( ! $color ) {
870 $color = $default;
871 } elseif ( str_contains( $color, 'rgb(' ) ) {
872 $color = str_replace( 'rgb(', 'rgba(', $color );
873 $color = str_replace( ')', ',1)', $color );
874 } elseif ( ! str_contains( $color, '#' ) && self::is_hex( $color ) ) {
875 $color = '#' . $color;
876 }
877 }
878
879 /**
880 * If a color looks like a hex code without the #, prepend the #.
881 * A color looks like a hex code if it does not contain the substrings "rgb", "rgba", "hsl", "hsla", or "hwb".
882 *
883 * @since 6.8
884 *
885 * @param string $color
886 *
887 * @return bool
888 */
889 private static function is_hex( $color ) {
890 $non_hex_substrings = array(
891 'rgba(',
892 'hsl(',
893 'hsla(',
894 'hwb(',
895 );
896
897 foreach ( $non_hex_substrings as $substring ) {
898 if ( str_contains( $color, $substring ) ) {
899 return false;
900 }
901 }
902
903 return true;
904 }
905
906 /**
907 * If left/right label is over a certain size,
908 * adjust the field description margin at a different screen size
909 *
910 * @since 2.3
911 *
912 * @param string $width Label width value.
913 *
914 * @return false|string
915 */
916 private static function description_margin_for_screensize( $width ) {
917 $temp_label_width = str_replace( 'px', '', $width );
918 $change_margin = false;
919
920 if ( $temp_label_width >= 230 ) {
921 $change_margin = '800px';
922 } elseif ( $width >= 215 ) {
923 $change_margin = '700px';
924 } elseif ( $width >= 180 ) {
925 $change_margin = '650px';
926 }
927
928 return $change_margin;
929 }
930
931 /**
932 * @since 2.3
933 *
934 * @return bool
935 */
936 public static function previewing_style() {
937 // phpcs:ignore WordPress.Security.NonceVerification.Missing
938 $ajax_change = isset( $_POST['action'] ) && $_POST['action'] === 'frm_change_styling' && isset( $_POST['frm_style_setting'] );
939
940 return $ajax_change || isset( $_GET['flat'] );
941 }
942
943 /**
944 * Get the URL for the Styler page list view (where you can assign styles to a form and view style templates) for a target form.
945 *
946 * @since 6.0
947 *
948 * @param int|string $form_id
949 *
950 * @return string
951 */
952 public static function get_list_url( $form_id ) {
953 return admin_url( 'admin.php?page=formidable-styles&form=' . absint( $form_id ) );
954 }
955
956 /**
957 * Get the back button args from Style settings.
958 *
959 * @since 6.14
960 *
961 * @param stdClass|WP_Post $style
962 * @param int $form_id
963 *
964 * @return array
965 */
966 public static function get_style_options_back_button_args( $style, $form_id ) {
967 if ( self::is_advanced_settings() ) {
968 return array(
969 'title' => __( 'Quick Settings', 'formidable' ),
970 'id' => 'frm_style_back_to_quick_settings',
971 );
972 }
973 return array(
974 'href' => self::get_list_url( $form_id ),
975 'title' => $style->post_title,
976 );
977 }
978
979 /**
980 * Get a link to edit a target style post object in the visual styler.
981 *
982 * @param stdClass|WP_Post $style
983 * @param int|string $form_id Used for the back button and preview form target.
984 * @param string $section The url param section.
985 *
986 * @return string
987 */
988 public static function get_edit_url( $style, $form_id = 0, $section = '' ) {
989 $query_args = array(
990 'page' => 'formidable-styles',
991 'frm_action' => 'edit',
992 'id' => $style->ID,
993 'section' => $section,
994 );
995
996 if ( $form_id ) {
997 // We include &form_id for the back button to know where to point to.
998 $query_args['form'] = $form_id;
999 }
1000
1001 return add_query_arg( $query_args, admin_url( 'admin.php' ) );
1002 }
1003
1004 /**
1005 * Get a count of the number of forms assigned to a target style ID.
1006 * All unassigned forms are included in the count for the default style.
1007 *
1008 * @since 6.0
1009 *
1010 * @param int|string $style_id
1011 * @param bool $is_default
1012 *
1013 * @return int
1014 */
1015 public static function get_form_count_for_style( $style_id, $is_default ) {
1016 $serialized = serialize( array( 'custom_style' => (string) $style_id ) );
1017 // Chop off the "a:1:{" from the front and the "}" from the back.
1018 $substring = substr( $serialized, 5, -1 );
1019 $number_of_forms = FrmDb::get_count(
1020 'frm_forms',
1021 array(
1022 'status' => 'published',
1023 'options LIKE' => $substring,
1024 )
1025 );
1026
1027 if ( ! $is_default ) {
1028 // Exit early as the rest of the code is about including the default count.
1029 return $number_of_forms;
1030 }
1031
1032 $conversational_style_id = FrmDb::get_var( 'posts', array( 'post_name' => 'lines-no-boxes' ), 'ID' );
1033
1034 return $number_of_forms + self::get_default_style_count( $style_id, $conversational_style_id );
1035 }
1036
1037 /**
1038 * Get the number of forms that use the default style.
1039 *
1040 * @since 6.0
1041 *
1042 * @param int|string $style_id
1043 * @param mixed $conversational_style_id
1044 *
1045 * @return int
1046 */
1047 private static function get_default_style_count( $style_id, $conversational_style_id ) {
1048 $substrings = array_map(
1049 function ( $value ) {
1050 $substring = serialize( array( 'custom_style' => $value ) );
1051 return substr( $substring, 5, -1 );
1052 },
1053 array( '1', 1 )
1054 );
1055 $where = array(
1056 'status' => 'published',
1057 0 => array(
1058 'options NOT LIKE' => 'custom_style',
1059 'or' => 1,
1060 'options LIKE' => $substrings,
1061 ),
1062 );
1063
1064 if ( $conversational_style_id ) {
1065 // When a conversational style is set, check for it in the query by wrapping the where and adding a conversational option check.
1066 $is_conversational_style = (int) $style_id === (int) $conversational_style_id;
1067 $where[0] = array(
1068 // The chat option doesn't exist if it isn't on.
1069 ( $is_conversational_style ? 'options LIKE' : 'options NOT LIKE' ) => ';s:4:"chat";',
1070 $where[0],
1071 );
1072 }
1073
1074 return FrmDb::get_count( 'frm_forms', $where );
1075 }
1076
1077 /**
1078 * Check if the current page is the advanced settings page.
1079 *
1080 * @since 6.14
1081 *
1082 * @return bool True if is advanced settings, false otherwise.
1083 */
1084 public static function is_advanced_settings() {
1085 return FrmAppHelper::get_param( 'section' ) === 'advanced-settings' && FrmAppHelper::get_param( 'page' ) === 'formidable-styles';
1086 }
1087
1088 /**
1089 * Get wrapper classname for style editor sections.
1090 *
1091 * @since 6.16
1092 *
1093 * @param string $section_type
1094 *
1095 * @return string The style editor wrapper classname.
1096 */
1097 public static function style_editor_get_wrapper_classname( $section_type ) {
1098 $is_quick_settings = 'quick-settings' === $section_type;
1099 $classname = 'frm-style-editor-form';
1100 $classname .= ( ! self::is_advanced_settings() xor $is_quick_settings ) ? ' frm_hidden' : '';
1101
1102 return $classname . ( FrmAppHelper::pro_is_installed() ? ' frm-pro' : '' );
1103 }
1104
1105 /**
1106 * Retrieve the background image URL of the submit button.
1107 * It may be either a full URL string (used in versions prior to 6.14) or a numeric attachment ID (introduced in version 6.14).
1108 *
1109 * @since 6.14
1110 *
1111 * @param array $settings
1112 *
1113 * @return false|string Return image url or false.
1114 */
1115 public static function get_submit_image_bg_url( $settings ) {
1116 $background_image = $settings['submit_bg_img'];
1117
1118 if ( ! $background_image ) {
1119 return false;
1120 }
1121
1122 // Handle the case where the submit_bg_img is a full URL string. If the
1123 // Settings were saved with the older styler version prior to 6.14, the
1124 // submit_bg_img will be a full URL string.
1125 if ( ! is_numeric( $background_image ) ) {
1126 return $background_image;
1127 }
1128
1129 return wp_get_attachment_url( (int) $background_image );
1130 }
1131
1132 /**
1133 * Determines if the chosen JavaScript library should be used.
1134 *
1135 * @since 6.13
1136 *
1137 * @return bool
1138 */
1139 public static function use_chosen_js() {
1140 if ( ! FrmAppHelper::pro_is_installed() ) {
1141 return false;
1142 }
1143
1144 return is_callable( 'FrmProAppHelper::use_chosen_js' ) ? FrmProAppHelper::use_chosen_js() : true;
1145 }
1146
1147 /**
1148 * Returns the bottom part from a margin/padding value.
1149 *
1150 * @since 6.17
1151 *
1152 * @param string $value The margin/padding value.
1153 *
1154 * @return string
1155 */
1156 public static function get_bottom_value( $value ) {
1157 if ( ! $value ) {
1158 return $value;
1159 }
1160
1161 $parts = explode( ' ', $value );
1162
1163 return count( $parts ) < 3 ? $parts[0] : $parts[2];
1164 }
1165 }
1166