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

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