PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.25.1
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.25.1
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 / controllers / FrmStylesController.php

FrmStylesController.php in Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More 6.25.1, at classes/controllers/FrmStylesController.php

1,386 lines 38.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 FrmStylesController {
7
8 /**
9 * @var string
10 */
11 public static $post_type = 'frm_styles';
12
13 /**
14 * @var string
15 */
16 public static $screen = 'formidable_page_formidable-styles';
17
18 /**
19 * @var string|null
20 */
21 private static $message;
22
23 public static function load_pro_hooks() {
24 if ( FrmAppHelper::pro_is_installed() ) {
25 FrmProStylesController::load_pro_hooks();
26 }
27 }
28
29 public static function register_post_types() {
30 register_post_type(
31 self::$post_type,
32 array(
33 'label' => __( 'Styles', 'formidable' ),
34 'public' => false,
35 'show_ui' => false,
36 'capability_type' => 'page',
37 'capabilities' => array(
38 'edit_post' => 'frm_change_settings',
39 'edit_posts' => 'frm_change_settings',
40 'edit_others_posts' => 'frm_change_settings',
41 'publish_posts' => 'frm_change_settings',
42 'delete_post' => 'frm_change_settings',
43 'delete_posts' => 'frm_change_settings',
44 'read_private_posts' => 'read_private_posts',
45 ),
46 'supports' => array(
47 'title',
48 ),
49 'has_archive' => false,
50 'labels' => array(
51 'name' => __( 'Styles', 'formidable' ),
52 'singular_name' => __( 'Style', 'formidable' ),
53 'menu_name' => __( 'Style', 'formidable' ),
54 'edit' => __( 'Edit', 'formidable' ),
55 'add_new_item' => __( 'Create a New Style', 'formidable' ),
56 'edit_item' => __( 'Edit Style', 'formidable' ),
57 ),
58 )
59 );
60 }
61
62 /**
63 * Add two links for the visual styler.
64 * There's a "Styles" submenu in the Formidable menu.
65 * There's a second alternative "Forms" submenu in the Appearance menu.
66 * This submenu links to a page to edit a form with the default style.
67 *
68 * @return void
69 */
70 public static function menu() {
71 add_submenu_page( 'formidable', 'Formidable | ' . __( 'Styles', 'formidable' ), __( 'Styles', 'formidable' ), 'frm_change_settings', 'formidable-styles', 'FrmStylesController::route' );
72 add_submenu_page( 'themes.php', 'Formidable | ' . __( 'Styles', 'formidable' ), __( 'Forms', 'formidable' ), 'frm_change_settings', 'formidable-styles2', 'FrmStylesController::route' );
73 }
74
75 /**
76 * Remove filters for the visual styler preview.
77 * This triggers earlier than self::admin_init which is called too late.
78 *
79 * @return void
80 */
81 public static function plugins_loaded() {
82 if ( ! FrmAppHelper::is_style_editor_page() ) {
83 return;
84 }
85
86 self::disable_form_css();
87 self::prevent_form_scripts_from_loading();
88 }
89
90 /**
91 * Avoid loading CSS the normal way with the preview in the styler.
92 * It gets loaded instead with the frmpro_css action set to the #frm-custom-theme-css element.
93 *
94 * @since 6.0
95 *
96 * @return void
97 */
98 private static function disable_form_css() {
99 add_filter( 'get_frm_stylesheet', '__return_empty_array' );
100 }
101
102 /**
103 * Removing this action prevents front end JavaScript from loading.
104 *
105 * @since 6.0
106 *
107 * @return void
108 */
109 private static function prevent_form_scripts_from_loading() {
110 remove_action( 'init', 'FrmFormsController::front_head' );
111 }
112
113 /**
114 * @return void
115 */
116 public static function admin_init() {
117 self::maybe_hook_into_global_settings_save();
118
119 if ( ! FrmAppHelper::is_style_editor_page() ) {
120 return;
121 }
122
123 FrmStyleComponent::register_assets();
124 self::load_pro_hooks();
125
126 $version = FrmAppHelper::plugin_version();
127
128 if ( FrmAppHelper::is_style_editor_page( 'edit' ) ) {
129 wp_enqueue_style( 'wp-color-picker' );
130 }
131
132 wp_enqueue_style( 'frm-custom-theme', admin_url( 'admin-ajax.php?action=frmpro_css' ), array(), $version );
133
134 $style = apply_filters( 'frm_style_head', false );
135 if ( $style ) {
136 wp_enqueue_style( 'frm-single-custom-theme', admin_url( 'admin-ajax.php?action=frmpro_load_css&flat=1' ) . '&' . http_build_query( $style->post_content ), array(), $version );
137 }
138 }
139
140 /**
141 * @since 6.0
142 *
143 * @return void
144 */
145 private static function maybe_hook_into_global_settings_save() {
146 if ( empty( $_POST ) || ! isset( $_POST['style'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
147 // Avoid changing any style data if the style array is not sent in the request.
148 return;
149 }
150
151 add_action(
152 'frm_update_settings',
153 /**
154 * Update the form data on the "Manage Styles" tab after global settings are saved.
155 */
156 function () {
157 self::manage_styles();
158 }
159 );
160 }
161
162 /**
163 * @param string $register Either 'enqueue' or 'register'.
164 * @param bool $force True to enqueue/register the style if a form has not been loaded.
165 * @return void
166 */
167 public static function enqueue_css( $register = 'enqueue', $force = false ) {
168 global $frm_vars;
169
170 $register_css = $register === 'register';
171 $should_load = $force || ( ( $frm_vars['load_css'] || $register_css ) && ! FrmAppHelper::is_admin() );
172
173 if ( ! $should_load ) {
174 return;
175 }
176
177 $frm_settings = FrmAppHelper::get_settings();
178 if ( $frm_settings->load_style === 'none' ) {
179 return;
180 }
181
182 $css = apply_filters( 'get_frm_stylesheet', self::custom_stylesheet() );
183
184 if ( $css ) {
185 $css = (array) $css;
186 $version = FrmAppHelper::plugin_version();
187
188 foreach ( $css as $css_key => $file ) {
189 if ( $register_css ) {
190 $this_version = self::get_css_version( $css_key, $version );
191 wp_register_style( $css_key, $file, array(), $this_version );
192 }
193
194 $load_on_all = ! FrmAppHelper::is_admin() && 'all' === $frm_settings->load_style;
195 if ( $load_on_all || $register !== 'register' ) {
196 wp_enqueue_style( $css_key );
197 }
198 unset( $css_key, $file );
199 }
200
201 if ( $frm_settings->load_style === 'all' ) {
202 $frm_vars['css_loaded'] = true;
203 }
204 }//end if
205 unset( $css );
206
207 add_filter( 'style_loader_tag', 'FrmStylesController::add_tags_to_css', 10, 2 );
208 }
209
210 /**
211 * @return array<string,string>
212 */
213 public static function custom_stylesheet() {
214 global $frm_vars;
215 $stylesheet_urls = array();
216
217 if ( empty( $frm_vars['css_loaded'] ) ) {
218 // Include css in head.
219 self::get_url_to_custom_style( $stylesheet_urls );
220 }
221
222 return $stylesheet_urls;
223 }
224
225 /**
226 * @param array $stylesheet_urls
227 * @return void
228 */
229 private static function get_url_to_custom_style( &$stylesheet_urls ) {
230 $file_name = '/css/' . self::get_file_name();
231 if ( is_readable( FrmAppHelper::plugin_path() . $file_name ) ) {
232 $url = FrmAppHelper::plugin_url() . $file_name;
233 } else {
234 $url = admin_url( 'admin-ajax.php?action=frmpro_css' );
235 }
236 $stylesheet_urls['formidable'] = $url;
237 }
238
239 /**
240 * Use a different stylesheet per site in a multisite install
241 *
242 * @since 3.0.03
243 */
244 public static function get_file_name() {
245 if ( is_multisite() ) {
246 $blog_id = get_current_blog_id();
247 $name = 'formidableforms' . absint( $blog_id ) . '.css';
248 } else {
249 $name = 'formidableforms.css';
250 }
251
252 return $name;
253 }
254
255 private static function get_css_version( $css_key, $version ) {
256 if ( 'formidable' == $css_key ) {
257 $this_version = get_option( 'frm_last_style_update' );
258 if ( ! $this_version ) {
259 $this_version = $version;
260 }
261 } else {
262 $this_version = $version;
263 }
264
265 return $this_version;
266 }
267
268 /**
269 * @param string $tag
270 * @param string $handle
271 * @return string
272 */
273 public static function add_tags_to_css( $tag, $handle ) {
274 if ( ( 'formidable' === $handle || 'jquery-theme' === $handle ) && strpos( $tag, ' property=' ) === false ) {
275 $frm_settings = FrmAppHelper::get_settings();
276 $tag = str_replace( ' type="', ' property="stylesheet" type="', $tag );
277 }
278
279 return $tag;
280 }
281
282 /**
283 * Route the edit route to the style function.
284 *
285 * @since 6.0 this function no longer has any parameter values.
286 *
287 * @return void
288 */
289 public static function edit() {
290 self::load_styler();
291 }
292
293 /**
294 * When a new style route is hit, show a new style with the defaults. There is no ID yet, it is created after the first save event.
295 *
296 * @return void
297 */
298 public static function new_style() {
299 self::load_styler();
300 }
301
302 /**
303 * When the duplicate route is hit, it just shows the visual styler with a copy of the target style. There is no ID yet, it is created after the first save event.
304 *
305 * @return void
306 */
307 public static function duplicate() {
308 self::load_styler();
309 }
310
311 /**
312 * Render the style page for a form for assigning a style to a form, and for updating a target style.
313 *
314 * @since 6.0 this function no longer takes parameters.
315 *
316 * @return void
317 */
318 public static function load_styler() {
319 if ( 'assign_style' === FrmAppHelper::get_post_param( 'frm_action' ) ) {
320 self::save_form_style();
321 }
322
323 self::setup_styles_and_scripts_for_styler();
324
325 $style_id = self::get_style_id_for_styler();
326 if ( ! $style_id ) {
327 $error_args = array(
328 'title' => __( 'No styles', 'formidable' ),
329 'body' => __( 'You must have a style to use the Visual Styler.', 'formidable' ),
330 'cancel_url' => admin_url( 'admin.php?page=formidable' ),
331 );
332 FrmAppController::show_error_modal( $error_args );
333 return;
334 }
335
336 $form_id = FrmAppHelper::simple_get( 'form', 'absint', 0 );
337 if ( ! $form_id ) {
338 $form_id = self::get_form_id_for_style( $style_id );
339 }
340
341 $form = FrmForm::getOne( $form_id );
342
343 if ( ! is_object( $form ) ) {
344 $error_args = array(
345 'title' => __( 'No forms', 'formidable' ),
346 'body' => __( 'You must have a form to use the Visual Styler.', 'formidable' ),
347 'cancel_url' => admin_url( 'admin.php?page=formidable' ),
348 );
349 FrmAppController::show_error_modal( $error_args );
350 return;
351 }
352
353 $frm_style = new FrmStyle( $style_id );
354 $active_style = $frm_style->get_one();
355 $default_style = self::get_default_style();
356
357 self::disable_admin_page_styling_on_submit_buttons();
358
359 /**
360 * @since 6.0
361 *
362 * @param array{form:\stdClass} $data
363 */
364 do_action( 'frm_before_render_style_page', compact( 'form' ) );
365
366 self::render_style_page( $active_style, $form, $default_style );
367 }
368
369 /**
370 * @since 6.0
371 *
372 * @return int
373 */
374 private static function get_style_id_for_styler() {
375 $action = FrmAppHelper::simple_get( 'frm_action' );
376 if ( 'duplicate' === $action ) {
377 // The duplicate action uses style_id instead of id for better backward compatibility.
378 return FrmAppHelper::simple_get( 'style_id', 'absint', 0 );
379 }
380
381 $style_id = FrmAppHelper::simple_get( 'id', 'absint', 0 );
382 if ( $style_id ) {
383 // Always use the style ID from the URL if one is specified.
384 return $style_id;
385 }
386
387 $request_form_id = FrmAppHelper::simple_get( 'form', 'absint', 0 );
388 if ( $request_form_id && is_callable( 'FrmProStylesController::get_active_style_for_form' ) ) {
389 return FrmProStylesController::get_active_style_for_form( $request_form_id )->ID;
390 }
391
392 return self::get_default_style()->ID;
393 }
394
395 /**
396 * If a form ID is not being passed in the URL, try to get the best match.
397 *
398 * @since 6.0
399 *
400 * @param int $style_id
401 * @return int
402 */
403 private static function get_form_id_for_style( $style_id ) {
404 $check = serialize( array( 'custom_style' => (string) $style_id ) );
405 $check = substr( $check, 5, -1 );
406 $form_id = FrmDb::get_var(
407 'frm_forms',
408 array(
409 'options LIKE' => $check,
410 'status' => 'published',
411 )
412 );
413
414 if ( ! $form_id ) {
415 // TODO: Show a message why a random form is being shown (because no form is assigned to the style).
416 // Fallback to any form.
417 $where = array(
418 'status' => 'published',
419 // Make sure it's not a repeater.
420 'parent_form_id' => array( null, 0 ),
421 );
422 $form_id = FrmDb::get_var( 'frm_forms', $where, 'id' );
423 }
424
425 return $form_id;
426 }
427
428 /**
429 * Add a frm_no_style_button class to all buttons to avoid some style rules like border-radius: 30px.
430 *
431 * @return void
432 */
433 private static function disable_admin_page_styling_on_submit_buttons() {
434 add_filter(
435 'frm_submit_button_class',
436 function ( $classes ) {
437 $classes[] = 'frm_no_style_button';
438 return $classes;
439 }
440 );
441 }
442
443 /**
444 * @since 6.0
445 *
446 * @return WP_Post
447 */
448 private static function get_default_style() {
449 $frm_style = new FrmStyle( 'default' );
450 $default_style = $frm_style->get_one();
451 return $default_style;
452 }
453
454 /**
455 * Save style for form (from Styler list page) via a POST action.
456 *
457 * @since 6.0
458 *
459 * @return void
460 */
461 private static function save_form_style() {
462 $permission_error = FrmAppHelper::permission_nonce_error( 'frm_edit_forms', 'frm_save_form_style', 'frm_save_form_style_nonce' );
463 if ( $permission_error !== false ) {
464 wp_die( 'Unable to save form', '', 403 );
465 }
466
467 $style_id = FrmAppHelper::get_post_param( 'style_id', 0, 'absint' );
468 if ( $style_id && ! self::confirm_style_exists_before_setting( $style_id ) ) {
469 wp_die( esc_html__( 'Invalid target style', 'formidable' ), esc_html__( 'Invalid target style', 'formidable' ), 400 );
470 return;
471 }
472
473 /**
474 * Hook into the saved style ID so Pro can import a style template by its key and return a new style ID.
475 *
476 * @since 6.0
477 *
478 * @param int $style_id
479 */
480 $style_id = apply_filters( 'frm_saved_form_style_id', $style_id );
481
482 if ( ! $style_id && '0' !== FrmAppHelper::get_post_param( 'style_id', '', 'sanitize_text_field' ) ) {
483 // "0" is a special value used for the enable/disable toggle.
484 wp_die( esc_html__( 'Invalid style value', 'formidable' ), esc_html__( 'Invalid style value', 'formidable' ), 400 );
485 return;
486 }
487
488 $form_id = FrmAppHelper::get_post_param( 'form_id', 0, 'absint' );
489 if ( ! $form_id ) {
490 wp_die( esc_html__( 'No form specified', 'formidable' ), esc_html__( 'No form specified', 'formidable' ), 400 );
491 return;
492 }
493
494 $form = FrmForm::getOne( $form_id );
495 if ( ! $form ) {
496 wp_die( esc_html__( 'Form does not exist', 'formidable' ), esc_html__( 'Form does not exist', 'formidable' ), 400 );
497 return;
498 }
499
500 $previous_style_id = $form->options['custom_style'];
501
502 // If the default style is selected, use the "Always use default" legacy option instead of the default style.
503 // There's also a check here for conversational forms.
504 // Without the check it isn't possible to select "Default" because "Always use default" will convert to "Lines" dynamically.
505 $default_style = self::get_default_style();
506 if ( $style_id === $default_style->ID && empty( $form->options['chat'] ) ) {
507 $style_id = 1;
508 }
509
510 // We want to save a string for consistency. FrmStylesHelper::get_form_count_for_style expects the custom style ID is a string.
511 $form->options['custom_style'] = (string) $style_id;
512
513 if ( $previous_style_id === $form->options['custom_style'] ) {
514 // Exit early before updating DB if nothing actually changed.
515 self::$message = __( 'Successfully updated style.', 'formidable' );
516 return;
517 }
518
519 global $wpdb;
520 $wpdb->update( $wpdb->prefix . 'frm_forms', array( 'options' => maybe_serialize( $form->options ) ), array( 'id' => $form->id ) );
521
522 FrmForm::clear_form_cache();
523
524 /**
525 * @since 6.25.1
526 *
527 * @param int $form_id
528 * @param int $style_id
529 */
530 do_action( 'frm_after_changed_form_style', absint( $form_id ), absint( $style_id ) );
531
532 self::$message = __( 'Successfully updated style.', 'formidable' );
533 }
534
535 /**
536 * Validate that we're assigning a form to a style that actually exists before assigning it to a form.
537 *
538 * @param int $style_id
539 * @return bool True if the style actually exists.
540 */
541 private static function confirm_style_exists_before_setting( $style_id ) {
542 global $wpdb;
543 $post_type = FrmDb::get_var( $wpdb->posts, array( 'ID' => $style_id ), 'post_type' );
544 return self::$post_type === $post_type;
545 }
546
547 /**
548 * Register and enqueue styles and scripts for the style tab page.
549 *
550 * @since 6.0
551 *
552 * @return void
553 */
554 private static function setup_styles_and_scripts_for_styler() {
555 $plugin_url = FrmAppHelper::plugin_url();
556 $version = FrmAppHelper::plugin_version();
557 $js_dependencies = array( 'wp-i18n', 'wp-hooks', 'formidable_dom' );
558
559 if ( FrmAppHelper::pro_is_installed() && is_callable( 'FrmProAppHelper::use_jquery_datepicker' ) && FrmProAppHelper::use_jquery_datepicker() ) {
560 $js_dependencies[] = 'jquery-ui-datepicker';
561 }
562
563 wp_register_script( 'formidable_style', $plugin_url . '/js/admin/style.js', $js_dependencies, $version );
564 wp_register_style( 'formidable_style', $plugin_url . '/css/admin/style.css', array(), $version );
565 wp_print_styles( 'formidable_style' );
566
567 wp_print_styles( 'formidable' );
568 wp_enqueue_script( 'formidable_style' );
569 wp_set_script_translations( 'formidable_style', 'formidable' );
570 }
571
572 /**
573 * Render the style page (with a more limited and typed scope than calling it from self::style directly).
574 *
575 * @since 6.0
576 *
577 * @param stdClass|WP_Post $active_style
578 * @param stdClass $form
579 * @param WP_Post $default_style
580 * @return void
581 */
582 private static function render_style_page( $active_style, $form, $default_style ) {
583 $style_views_path = self::get_views_path();
584 // Edit, list (default), new_style.
585 $view = FrmAppHelper::simple_get( 'frm_action', 'sanitize_text_field', 'list' );
586 $frm_style = new FrmStyle( $active_style->ID );
587
588 if ( 'new_style' !== $view && ! FrmAppHelper::simple_get( 'form' ) && ! FrmAppHelper::simple_get( 'style_id' ) ) {
589 // Have the Appearance > Forms link fallback to the edit view. Otherwise we want to use 'list' as the default.
590 $view = 'edit';
591 }
592
593 if ( in_array( $view, array( 'edit', 'new_style', 'duplicate' ), true ) ) {
594 self::add_meta_boxes();
595 }
596
597 switch ( $view ) {
598 case 'edit':
599 $style = $active_style;
600 break;
601
602 case 'duplicate':
603 $style = clone $active_style;
604 $new_style = $frm_style->get_new();
605 $style->ID = $new_style->ID;
606 $style->post_name = $new_style->post_name;
607 unset( $new_style );
608 break;
609
610 case 'new_style':
611 $style = $frm_style->get_new();
612 break;
613 }
614
615 if ( in_array( $view, array( 'duplicate', 'new_style' ), true ) ) {
616 $style->post_title = FrmAppHelper::simple_get( 'style_name' );
617 $style->menu_order = 0;
618 }
619
620 if ( ! isset( $style ) ) {
621 $style = $active_style;
622 }
623
624 self::force_form_style( $style );
625
626 if ( isset( self::$message ) ) {
627 $message = self::$message;
628 }
629
630 $preview_helper = new FrmStylesPreviewHelper( $form->id );
631 $preview_helper->adjust_form_for_preview();
632
633 // Get form HTML before displaying warnings and notes so we can check global $frm_vars data without adding extra database calls.
634 $target_form_preview_html = $preview_helper->get_html_for_form_preview();
635 $warnings = $preview_helper->get_warnings_for_styler_preview( $style, $default_style, $view );
636 $notes = $preview_helper->get_notes_for_styler_preview();
637
638 include $style_views_path . 'show.php';
639 }
640
641 /**
642 * @since 6.0
643 *
644 * @return string
645 */
646 private static function get_views_path() {
647 return FrmAppHelper::plugin_path() . '/classes/views/styles/';
648 }
649
650 /**
651 * Filter form classes so the form uses the preview style, not the form's active style.
652 *
653 * @since 6.0
654 *
655 * @param stdClass|WP_Post $style A new style is not a WP_Post object.
656 * @return void
657 */
658 private static function force_form_style( $style ) {
659 add_filter(
660 'frm_add_form_style_class',
661 function ( $class ) use ( $style ) {
662 $split = array_filter(
663 explode( ' ', $class ),
664 /**
665 * @param string $class
666 */
667 function ( $class ) {
668 return $class && 0 !== strpos( $class, 'frm_style_' );
669 }
670 );
671 $split[] = 'frm_style_' . $style->post_name;
672 return implode( ' ', $split );
673 }
674 );
675 }
676
677 /**
678 * Save style post object via a POST request submitted from the Visual styler "edit" page.
679 *
680 * @since 6.0
681 *
682 * @return void
683 */
684 public static function save_style() {
685 $frm_style = new FrmStyle();
686 $post_id = FrmAppHelper::get_post_param( 'ID', false, 'sanitize_title' );
687 $style_nonce = FrmAppHelper::get_post_param( 'frm_style', '', 'sanitize_text_field' );
688
689 if ( $post_id === false || ! wp_verify_nonce( $style_nonce, 'frm_style_nonce' ) ) {
690 // Exit early if the request isn't valid.
691 // Since we're not dying, it should just reload the visual styler without any message.
692 return;
693 }
694
695 $id = $frm_style->update( $post_id );
696 if ( ! $post_id && $id ) {
697 self::maybe_redirect_after_save( $id );
698 // Set the post id to the new style so it will be loaded for editing.
699 $post_id = reset( $id );
700 }
701
702 /**
703 * @since 6.25.1
704 *
705 * @param int $post_id
706 */
707 do_action( 'frm_after_saved_style', absint( $post_id ) );
708
709 self::$message = __( 'Your styling settings have been saved.', 'formidable' );
710 }
711
712 /**
713 * Show the edit view after saving.
714 * The save event is triggered earlier, on admin init where self::save_style is called.
715 * This happens earlier because there is a possible redirect (to adjust the URL for a new or duplicated style).
716 *
717 * @return void
718 */
719 public static function save() {
720 self::edit();
721 }
722
723 /**
724 * Force a redirect after duplicating or creating a new style to avoid an old stale URL that could result in more styles than intended.
725 *
726 * @since 6.0
727 *
728 * @param array $ids
729 * @return void
730 */
731 private static function maybe_redirect_after_save( $ids ) {
732 $referer = FrmAppHelper::get_server_value( 'HTTP_REFERER' );
733 $parsed = parse_url( $referer );
734 $query = $parsed['query'];
735
736 $current_action = false;
737 $actions_to_redirect = array( 'duplicate', 'new_style' );
738 foreach ( $actions_to_redirect as $action ) {
739 if ( false !== strpos( $query, 'frm_action=' . $action ) ) {
740 $current_action = $action;
741 break;
742 }
743 }
744
745 if ( false === $current_action ) {
746 // Do not redirect as the referer URL did not match $actions_to_redirect.
747 return;
748 }
749
750 parse_str( $query, $parsed_query );
751 $form_id = ! empty( $parsed_query['form'] ) ? absint( $parsed_query['form'] ) : 0;
752
753 $style = new stdClass();
754 $style->ID = end( $ids );
755 wp_safe_redirect( esc_url_raw( FrmStylesHelper::get_edit_url( $style, $form_id ) ) );
756 die();
757 }
758
759 /**
760 * @since 6.0
761 *
762 * @param string $message
763 * @param array|object $forms
764 * @return void
765 */
766 public static function manage( $message = '', $forms = array() ) {
767 $frm_style = new FrmStyle();
768 $styles = $frm_style->get_all();
769 $default_style = $frm_style->get_default_style( $styles );
770 $frm_settings = FrmAppHelper::get_settings();
771
772 if ( ! $forms ) {
773 $forms = FrmForm::get_published_forms();
774 }
775
776 include FrmAppHelper::plugin_path() . '/classes/views/styles/manage.php';
777 }
778
779 /**
780 * Handle saving for the page rendered in self::manage which is included in Global Settings in the "Manage Styles" tab.
781 * This gets called from the frm_update_settings hook which is called after saving Global settings.
782 *
783 * @return void
784 */
785 private static function manage_styles() {
786 global $wpdb;
787
788 $forms = FrmForm::get_published_forms();
789 foreach ( $forms as $form ) {
790 if ( ! isset( $_POST['style'] ) || ! isset( $_POST['style'][ $form->id ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
791 continue;
792 }
793
794 $new_style = sanitize_text_field( wp_unslash( $_POST['style'][ $form->id ] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
795
796 $form->options['custom_style'] = $new_style;
797 $wpdb->update( $wpdb->prefix . 'frm_forms', array( 'options' => maybe_serialize( $form->options ) ), array( 'id' => $form->id ) );
798 unset( $form );
799 }
800 }
801
802 /**
803 * Echo content for the Custom CSS page.
804 *
805 * @param string $message
806 * @return void
807 */
808 public static function custom_css( $message = '' ) {
809 $settings = self::enqueue_codemirror();
810 $id = $settings ? 'frm_codemirror_box' : 'frm_custom_css_box';
811 $custom_css = self::get_custom_css();
812
813 include FrmAppHelper::plugin_path() . '/classes/views/styles/custom_css.php';
814 }
815
816 /**
817 * Get custom CSS code entered in the Custom CSS page.
818 *
819 * @since 6.0
820 *
821 * @return string
822 */
823 public static function get_custom_css() {
824 $settings = FrmAppHelper::get_settings();
825 if ( is_string( $settings->custom_css ) ) {
826 return $settings->custom_css;
827 }
828
829 // If it does not exist, check the default style as a fallback.
830 $frm_style = new FrmStyle();
831 $style = $frm_style->get_default_style();
832 $custom_css = $style->post_content['custom_css'];
833
834 return $custom_css;
835 }
836
837 /**
838 * Enqueue assets for codemirror, built into WordPress since 4.9.
839 * The Custom CSS page uses codemirror.
840 *
841 * @since 6.0 Previously this code was embedded in self::custom_css.
842 *
843 * @return array|false
844 */
845 private static function enqueue_codemirror() {
846 if ( ! function_exists( 'wp_enqueue_code_editor' ) ) {
847 // The WordPress version is likely older than 4.9.
848 return false;
849 }
850
851 $settings = wp_enqueue_code_editor(
852 array(
853 'type' => 'text/css',
854 'codemirror' => array(
855 'indentUnit' => 2,
856 'tabSize' => 2,
857 // As the codemirror box only appears once you click into the Custom CSS tab, we need to auto-refresh.
858 // Otherwise the line numbers all end up with a 1px width causing overlap issues with the text in the content.
859 'autoRefresh' => true,
860 ),
861 )
862 );
863
864 if ( $settings ) {
865 wp_add_inline_script(
866 'code-editor',
867 sprintf(
868 'jQuery( function() { wp.codeEditor.initialize( \'frm_codemirror_box\', %s ); } );',
869 wp_json_encode( $settings )
870 )
871 );
872 }
873
874 return $settings;
875 }
876
877 /**
878 * Handling routing for the visual styler.
879 *
880 * @return void
881 */
882 public static function route() {
883 $action = FrmAppHelper::get_param( 'frm_action', '', 'get', 'sanitize_title' );
884 FrmAppHelper::include_svg();
885
886 switch ( $action ) {
887 case 'edit':
888 case 'save':
889 self::$action();
890 return;
891 default:
892 do_action( 'frm_style_action_route', $action );
893
894 if ( apply_filters( 'frm_style_stop_action_route', false, $action ) ) {
895 return;
896 }
897
898 if ( in_array( $action, array( 'new_style', 'duplicate' ), true ) ) {
899 self::$action();
900 return;
901 }
902
903 self::edit();
904 return;
905 }
906 }
907
908 /**
909 * Handle AJAX routing for frm_settings_reset for resetting styles to the default settings.
910 * From the edit view, it will return default styles and not actually update the style.
911 * On the list view, it does update the style immediately, and returns the default card style attributes so the style card can be reset as well.
912 *
913 * @since 6.0 When a style_id is passed to this action, the style will actually be reset.
914 *
915 * @return void
916 */
917 public static function reset_styling() {
918 FrmAppHelper::permission_check( 'frm_change_settings' );
919 check_ajax_referer( 'frm_ajax', 'nonce' );
920
921 $style_id = FrmAppHelper::get_post_param( 'style_id', '', 'absint' );
922 if ( ! $style_id ) {
923 // A style ID is not sent when resetting on the edit page.
924 // Instead of resetting the style, send the defaults back so the inputs can be updated with JavaScript.
925 $frm_style = new FrmStyle();
926 $defaults = $frm_style->get_defaults();
927 echo json_encode( $defaults );
928 wp_die();
929 }
930
931 $frm_style = new FrmStyle();
932 $default_template_style = $frm_style->get_default_template_style( $style_id );
933 $where = array(
934 'ID' => $style_id,
935 'post_type' => self::$post_type,
936 );
937
938 $style_object = $frm_style->get_new();
939 $style_object->post_content = json_decode( $default_template_style, true );
940
941 global $wpdb;
942 $wpdb->update( $wpdb->posts, array( 'post_content' => $default_template_style ), $where );
943
944 // Save the settings after resetting to default or the old style will still appear.
945 $frm_style->save_settings();
946
947 $data = array(
948 'style' => FrmStylesCardHelper::get_style_param_for_card( $style_object ),
949 );
950 wp_send_json_success( $data );
951 wp_die();
952 }
953
954 /**
955 * Handle routing for the frm_change_styling AJAX action.
956 * This doesn't actually change styling. It just handles the events when someone changes a style.
957 * It responds with the new CSS required for the updated styler preview in the edit page.
958 *
959 * @return void
960 */
961 public static function change_styling() {
962 FrmAppHelper::permission_check( 'frm_change_settings' );
963 check_ajax_referer( 'frm_ajax', 'nonce' );
964
965 $is_loaded_via_ajax = true;
966 $frm_style = new FrmStyle();
967
968 // Intentionally avoid defaults here so nothing gets removed from our style.
969 $defaults = array();
970 $style = '';
971
972 echo '<style type="text/css">';
973 include FrmAppHelper::plugin_path() . '/css/_single_theme.css.php';
974 echo '</style>';
975 wp_die();
976 }
977
978 /**
979 * @return void
980 */
981 public static function add_meta_boxes() {
982 // setup meta boxes
983 $meta_boxes = array(
984 'general' => __( 'General', 'formidable' ),
985 'form-title' => __( 'Form Title', 'formidable' ),
986 'form-description' => __( 'Form Description', 'formidable' ),
987 'field-labels' => __( 'Field Labels', 'formidable' ),
988 'field-description' => __( 'Field Description', 'formidable' ),
989 'field-colors' => __( 'Field Colors', 'formidable' ),
990 'field-sizes' => __( 'Field Settings', 'formidable' ),
991 'check-box-radio-fields' => __( 'Check Box & Radio Fields', 'formidable' ),
992 'buttons' => __( 'Buttons', 'formidable' ),
993 'form-messages' => __( 'Form Messages', 'formidable' ),
994 );
995
996 /**
997 * Add custom boxes to the styling settings
998 *
999 * @since 2.3
1000 *
1001 * @param array $meta_boxes
1002 */
1003 $meta_boxes = apply_filters( 'frm_style_boxes', $meta_boxes );
1004
1005 foreach ( $meta_boxes as $nicename => $name ) {
1006 add_meta_box( $nicename . '-style', $name, 'FrmStylesController::include_style_section', self::$screen, 'side', 'default', $nicename );
1007 unset( $nicename, $name );
1008 }
1009 }
1010
1011 /**
1012 * @param array $atts
1013 * @param array $sec
1014 * @return void
1015 */
1016 public static function include_style_section( $atts, $sec ) {
1017 extract( $atts ); // phpcs:ignore WordPress.PHP.DontExtract
1018 $style = $atts['style'];
1019 FrmStylesHelper::prepare_color_output( $style->post_content, false );
1020
1021 $current_tab = FrmAppHelper::simple_get( 'page-tab', 'sanitize_title', 'default' );
1022 $file_name = FrmAppHelper::plugin_path() . '/classes/views/styles/_' . $sec['args'] . '.php';
1023
1024 /**
1025 * Set the location of custom styling settings right before
1026 * loading onto the page. If your style box was named "progress",
1027 * this hook name will be frm_style_settings_progress.
1028 *
1029 * @since 2.3
1030 */
1031 $file_name = apply_filters( 'frm_style_settings_' . $sec['args'], $file_name );
1032
1033 echo '<div class="frm_grid_container">';
1034 include $file_name;
1035 echo '</div>';
1036 }
1037
1038 public static function load_css() {
1039 header( 'Content-type: text/css' );
1040
1041 $frm_style = new FrmStyle();
1042 $defaults = $frm_style->get_defaults();
1043 $style = '';
1044
1045 include FrmAppHelper::plugin_path() . '/css/_single_theme.css.php';
1046 wp_die();
1047 }
1048
1049 /**
1050 * @return void
1051 */
1052 public static function load_saved_css() {
1053 $css = get_transient( 'frmpro_css' );
1054
1055 ob_start();
1056 include FrmAppHelper::plugin_path() . '/css/custom_theme.css.php';
1057 $output = ob_get_clean();
1058 $output = self::replace_relative_url( $output );
1059
1060 /**
1061 * The API needs to load font icons through a custom URL.
1062 *
1063 * @since 5.2
1064 *
1065 * @param string $output
1066 */
1067 $output = apply_filters( 'frm_saved_css', $output );
1068
1069 echo $output; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1070
1071 self::maybe_hide_sample_form_error_message();
1072
1073 wp_die();
1074 }
1075
1076 /**
1077 * Add an extra style rule to hide a broken style warning.
1078 * To avoid cluttering the front end with any unnecessary styles this is only added when the referer URL matches the styler.
1079 *
1080 * @since 6.2.3
1081 *
1082 * @return void
1083 */
1084 public static function maybe_hide_sample_form_error_message() {
1085 $referer = FrmAppHelper::get_server_value( 'HTTP_REFERER' );
1086 if ( false !== strpos( $referer, 'admin.php?page=formidable-styles' ) ) {
1087 echo '#frm_broken_styles_warning { display: none; }';
1088 }
1089 }
1090
1091 /**
1092 * Replaces relative URL with absolute URL.
1093 *
1094 * @since 4.11.03
1095 *
1096 * @param string $css CSS content.
1097 * @return string
1098 */
1099 public static function replace_relative_url( $css ) {
1100 $plugin_url = trailingslashit( FrmAppHelper::plugin_url() );
1101 return str_replace(
1102 array(
1103 'url(../',
1104 "url('../",
1105 'url("../',
1106 ),
1107 array(
1108 'url(' . $plugin_url,
1109 "url('" . $plugin_url,
1110 'url("' . $plugin_url,
1111 ),
1112 $css
1113 );
1114 }
1115
1116 /**
1117 * Check if the Formidable styling should be loaded,
1118 * then enqueue it for the footer
1119 *
1120 * @since 2.0
1121 */
1122 public static function enqueue_style() {
1123 global $frm_vars;
1124
1125 if ( isset( $frm_vars['css_loaded'] ) && $frm_vars['css_loaded'] ) {
1126 // The CSS has already been loaded.
1127 return;
1128 }
1129
1130 $frm_settings = FrmAppHelper::get_settings();
1131 if ( $frm_settings->load_style !== 'none' ) {
1132 wp_enqueue_style( 'formidable' );
1133 $frm_vars['css_loaded'] = true;
1134 }
1135 }
1136
1137 /**
1138 * Get the stylesheets for the form settings page
1139 *
1140 * @return array<WP_Post>
1141 */
1142 public static function get_style_opts() {
1143 $frm_style = new FrmStyle();
1144 $styles = $frm_style->get_all();
1145
1146 return $styles;
1147 }
1148
1149 /**
1150 * Get the style post object for a target form.
1151 *
1152 * @param bool|object|string $form
1153 * @return WP_Post|null
1154 */
1155 public static function get_form_style( $form = 'default' ) {
1156 $style = FrmFormsHelper::get_form_style( $form );
1157
1158 if ( empty( $style ) || 1 == $style ) {
1159 $style = 'default';
1160 }
1161
1162 $frm_style = new FrmStyle( $style );
1163 return $frm_style->get_one();
1164 }
1165
1166 /**
1167 * @param string $class
1168 * @param string $style
1169 */
1170 public static function get_form_style_class( $class, $style ) {
1171 if ( 1 == $style ) {
1172 $style = 'default';
1173 }
1174
1175 $frm_style = new FrmStyle( $style );
1176 $style = $frm_style->get_one();
1177
1178 if ( $style ) {
1179 $class .= ' frm_style_' . $style->post_name;
1180 self::maybe_add_rtl_class( $style, $class );
1181 }
1182
1183 return $class;
1184 }
1185
1186 /**
1187 * @since 3.0
1188 * @param object $style
1189 * @param string $class
1190 */
1191 private static function maybe_add_rtl_class( $style, &$class ) {
1192 $is_rtl = isset( $style->post_content['direction'] ) && 'rtl' === $style->post_content['direction'];
1193 if ( $is_rtl ) {
1194 $class .= ' frm_rtl';
1195 }
1196 }
1197
1198 /**
1199 * @param string $val
1200 */
1201 public static function get_style_val( $val, $form = 'default' ) {
1202 $style = self::get_form_style( $form );
1203 if ( $style && isset( $style->post_content[ $val ] ) ) {
1204 return $style->post_content[ $val ];
1205 }
1206 }
1207
1208 public static function show_entry_styles( $default_styles ) {
1209 $frm_style = new FrmStyle( 'default' );
1210 $style = $frm_style->get_one();
1211
1212 if ( ! $style ) {
1213 return $default_styles;
1214 }
1215
1216 foreach ( $default_styles as $name => $val ) {
1217 $setting = $name;
1218 if ( 'border_width' == $name ) {
1219 $setting = 'field_border_width';
1220 } elseif ( 'alt_bg_color' == $name ) {
1221 $setting = 'bg_color_active';
1222 }
1223 $default_styles[ $name ] = $style->post_content[ $setting ];
1224 unset( $name, $val );
1225 }
1226
1227 return $default_styles;
1228 }
1229
1230 public static function &important_style( $important, $field ) {
1231 $important = self::get_style_val( 'important_style', $field['form_id'] );
1232
1233 return $important;
1234 }
1235
1236 /**
1237 * Duplicate of WordPress do_accordion_section function, it adds an additional svg icon support.
1238 *
1239 * @since 6.8.3
1240 *
1241 * @return int
1242 */
1243 public static function do_accordion_sections( $screen, $context, $data_object ) {
1244 global $wp_meta_boxes;
1245
1246 // the symbol id from icons.svg
1247 $icon_ids = array(
1248 'ranking-fields-style' => 'frm_chart_bar_icon',
1249 'section-fields-style' => 'frm-form-title-style',
1250 );
1251
1252 wp_enqueue_script( 'accordion' );
1253
1254 if ( empty( $screen ) ) {
1255 $screen = get_current_screen();
1256 } elseif ( is_string( $screen ) ) {
1257 $screen = convert_to_screen( $screen );
1258 }
1259
1260 $page = $screen->id;
1261
1262 ?>
1263 <div id="side-sortables" class="accordion-container">
1264 <ul class="outer-border">
1265 <?php
1266 $i = 0;
1267 $first_open = false;
1268
1269 if ( isset( $wp_meta_boxes[ $page ][ $context ] ) ) {
1270 foreach ( array( 'high', 'core', 'default', 'low' ) as $priority ) {
1271 if ( isset( $wp_meta_boxes[ $page ][ $context ][ $priority ] ) ) {
1272 foreach ( $wp_meta_boxes[ $page ][ $context ][ $priority ] as $box ) {
1273 if ( false === $box || ! $box['title'] ) {
1274 continue;
1275 }
1276
1277 ++$i;
1278 $icon_id = array_key_exists( $box['id'], $icon_ids ) ? $icon_ids[ $box['id'] ] : 'frm-' . $box['id'];
1279
1280 $open_class = '';
1281 if ( ! $first_open ) {
1282 $first_open = true;
1283 $open_class = 'open';
1284 }
1285
1286 $accordion_content_id = 'frm_style_section_' . $box['id'];
1287 ?>
1288 <li class="control-section accordion-section <?php echo esc_attr( $open_class ); ?> <?php echo esc_attr( $box['id'] ); ?>" id="<?php echo esc_attr( $box['id'] ); ?>">
1289 <h3 class="accordion-section-title hndle">
1290 <?php
1291 FrmAppHelper::icon_by_class( 'frmfont ' . $icon_id . ' frm_svg24' );
1292 echo esc_html( $box['title'] );
1293 ?>
1294 <button type="button" aria-expanded="<?php echo esc_attr( 'open' === $open_class ? 'true' : 'false' ); ?>" aria-controls="<?php echo esc_attr( $accordion_content_id ); ?>" aria-label="<?php echo esc_attr( $box['title'] ); ?>">
1295 <?php FrmAppHelper::icon_by_class( 'frmfont frm_arrowdown8_icon' ); ?>
1296 </button>
1297 </h3>
1298 <div class="accordion-section-content <?php postbox_classes( $box['id'], $page ); ?>" id="<?php echo esc_attr( $accordion_content_id ); ?>">
1299 <div class="inside">
1300 <?php call_user_func( $box['callback'], $data_object, $box ); ?>
1301 </div><!-- .inside -->
1302 </div><!-- .accordion-section-content -->
1303 </li><!-- .accordion-section -->
1304 <?php
1305 }//end foreach
1306 }//end if
1307 }//end foreach
1308 }//end if
1309 ?>
1310 </ul><!-- .outer-border -->
1311 </div><!-- .accordion-container -->
1312 <?php
1313 return $i;
1314 }
1315
1316 /**
1317 * Rename a style via an AJAX action.
1318 *
1319 * @since 6.0
1320 *
1321 * @return void
1322 */
1323 public static function rename_style() {
1324 $permission_error = FrmAppHelper::permission_nonce_error( 'frm_edit_forms', 'nonce', 'frm_ajax' );
1325 if ( $permission_error !== false ) {
1326 $data = array(
1327 'message' => __( 'Unable to rename style', 'formidable' ),
1328 );
1329 wp_send_json_error( $data, 403 );
1330 die();
1331 }
1332
1333 $style_id = FrmAppHelper::get_post_param( 'style_id', 0, 'absint' );
1334 $style_name = FrmAppHelper::get_post_param( 'style_name', '', 'sanitize_text_field' );
1335
1336 if ( ! $style_id || ! $style_name ) {
1337 $data = array(
1338 'message' => __( 'Invalid route', 'formidable' ),
1339 );
1340 wp_send_json_error( $data, 400 );
1341 die();
1342 }
1343
1344 $post = get_post( $style_id );
1345 if ( ! $post || $post->post_type !== self::$post_type ) {
1346 $data = array(
1347 'message' => __( 'The style you are renaming either does not exist or it is not a style', 'formidable' ),
1348 );
1349 wp_send_json_error( $data, 404 );
1350 die();
1351 }
1352
1353 global $wpdb;
1354 $wpdb->update( $wpdb->posts, array( 'post_title' => $style_name ), array( 'ID' => $post->ID ) );
1355
1356 $data = array();
1357 wp_send_json_success( $data );
1358 }
1359
1360 /**
1361 * Prevent the WordPress edit.css file from loading on the visual styler page.
1362 * This way .form-field elements do not have border styles applied to them.
1363 *
1364 * @since 6.0
1365 *
1366 * @param WP_Styles $styles
1367 * @return void
1368 */
1369 public static function disable_conflicting_wp_admin_css( $styles ) {
1370 if ( ! FrmAppHelper::is_style_editor_page() ) {
1371 return;
1372 }
1373
1374 FrmStylesPreviewHelper::disable_conflicting_wp_admin_css( $styles );
1375 }
1376
1377 /**
1378 * @deprecated 6.1 Saving custom CSS has been moved into Global Settings.
1379 *
1380 * @return void
1381 */
1382 public static function save_css() {
1383 _deprecated_function( __METHOD__, '6.1' );
1384 }
1385 }
1386