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

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