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

1,262 lines 35.3 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 $post_type
10 */
11 public static $post_type = 'frm_styles';
12
13 /**
14 * @var string $screen
15 */
16 public static $screen = 'formidable_page_formidable-styles';
17
18 /**
19 * @var string|null $message
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_false' );
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 self::load_pro_hooks();
124
125 $version = FrmAppHelper::plugin_version();
126 wp_enqueue_script( 'jquery-ui-datepicker' );
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 }
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 public static function add_tags_to_css( $tag, $handle ) {
269 if ( ( 'formidable' == $handle || 'jquery-theme' == $handle ) && strpos( $tag, ' property=' ) === false ) {
270 $frm_settings = FrmAppHelper::get_settings();
271 if ( $frm_settings->use_html ) {
272 $tag = str_replace( ' type="', ' property="stylesheet" type="', $tag );
273 }
274 }
275
276 return $tag;
277 }
278
279 /**
280 * Route the edit route to the style function.
281 *
282 * @since 6.0 this function no longer has any parameter values.
283 *
284 * @return void
285 */
286 public static function edit() {
287 self::load_styler();
288 }
289
290 /**
291 * 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.
292 *
293 * @return void
294 */
295 public static function new_style() {
296 self::load_styler();
297 }
298
299 /**
300 * 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.
301 *
302 * @return void
303 */
304 public static function duplicate() {
305 self::load_styler();
306 }
307
308 /**
309 * Render the style page for a form for assigning a style to a form, and for updating a target style.
310 *
311 * @since 6.0 this function no longer takes parameters.
312 *
313 * @return void
314 */
315 public static function load_styler() {
316 if ( 'assign_style' === FrmAppHelper::get_post_param( 'frm_action' ) ) {
317 self::save_form_style();
318 }
319
320 self::setup_styles_and_scripts_for_styler();
321
322 $style_id = self::get_style_id_for_styler();
323 if ( ! $style_id ) {
324 wp_die( esc_html__( 'Invalid route', 'formidable' ), esc_html__( 'Invalid route', 'formidable' ), 400 );
325 }
326
327 $form_id = FrmAppHelper::simple_get( 'form', 'absint', 0 );
328 if ( ! $form_id ) {
329 $form_id = self::get_form_id_for_style( $style_id );
330 }
331
332 $form = FrmForm::getOne( $form_id );
333 if ( ! is_object( $form ) ) {
334 wp_die( esc_html__( 'Invalid route', 'formidable' ), esc_html__( 'Invalid route', 'formidable' ), 400 );
335 }
336
337 $frm_style = new FrmStyle( $style_id );
338 $active_style = $frm_style->get_one();
339 $default_style = self::get_default_style();
340
341 self::disable_admin_page_styling_on_submit_buttons();
342
343 /**
344 * @since 6.0
345 *
346 * @param array {
347 * @type stdClass $form
348 * }
349 */
350 do_action( 'frm_before_render_style_page', compact( 'form' ) );
351
352 self::render_style_page( $active_style, $form, $default_style );
353 }
354
355 /**
356 * @since 6.0
357 *
358 * @return int
359 */
360 private static function get_style_id_for_styler() {
361 $action = FrmAppHelper::simple_get( 'frm_action' );
362 if ( 'duplicate' === $action ) {
363 // The duplicate action uses style_id instead of id for better backward compatibility.
364 return FrmAppHelper::simple_get( 'style_id', 'absint', 0 );
365 }
366
367 $style_id = FrmAppHelper::simple_get( 'id', 'absint', 0 );
368 if ( $style_id ) {
369 // Always use the style ID from the URL if one is specified.
370 return $style_id;
371 }
372
373 $request_form_id = FrmAppHelper::simple_get( 'form', 'absint', 0 );
374 if ( $request_form_id && is_callable( 'FrmProStylesController::get_active_style_for_form' ) ) {
375 return FrmProStylesController::get_active_style_for_form( $request_form_id )->ID;
376 }
377
378 return self::get_default_style()->ID;
379 }
380
381 /**
382 * If a form ID is not being passed in the URL, try to get the best match.
383 *
384 * @since 6.0
385 *
386 * @param int $style_id
387 * @return int
388 */
389 private static function get_form_id_for_style( $style_id ) {
390 $check = serialize( array( 'custom_style' => (string) $style_id ) );
391 $check = substr( $check, 5, -1 );
392 $form_id = FrmDb::get_var(
393 'frm_forms',
394 array(
395 'options LIKE' => $check,
396 'status' => 'published',
397 )
398 );
399
400 if ( ! $form_id ) {
401 // TODO: Show a message why a random form is being shown (because no form is assigned to the style).
402 // Fallback to any form.
403 $where = array(
404 'status' => 'published',
405 'parent_form_id' => array( null, 0 ), // Make sure it's not a repeater.
406 );
407 $form_id = FrmDb::get_var( 'frm_forms', $where, 'id' );
408 }
409
410 return $form_id;
411 }
412
413 /**
414 * Add a frm_no_style_button class to all buttons to avoid some style rules like border-radius: 30px.
415 *
416 * @return void
417 */
418 private static function disable_admin_page_styling_on_submit_buttons() {
419 add_filter(
420 'frm_submit_button_class',
421 function( $classes ) {
422 $classes[] = 'frm_no_style_button';
423 return $classes;
424 }
425 );
426 }
427
428 /**
429 * @since 6.0
430 *
431 * @return WP_Post
432 */
433 private static function get_default_style() {
434 $frm_style = new FrmStyle( 'default' );
435 $default_style = $frm_style->get_one();
436 return $default_style;
437 }
438
439 /**
440 * Save style for form (from Styler list page) via a POST action.
441 *
442 * @since 6.0
443 *
444 * @return void
445 */
446 private static function save_form_style() {
447 $permission_error = FrmAppHelper::permission_nonce_error( 'frm_edit_forms', 'frm_save_form_style', 'frm_save_form_style_nonce' );
448 if ( $permission_error !== false ) {
449 wp_die( 'Unable to save form', '', 403 );
450 }
451
452 $style_id = FrmAppHelper::get_post_param( 'style_id', 0, 'absint' );
453 if ( $style_id && ! self::confirm_style_exists_before_setting( $style_id ) ) {
454 wp_die( esc_html__( 'Invalid target style', 'formidable' ), esc_html__( 'Invalid target style', 'formidable' ), 400 );
455 return;
456 }
457
458 /**
459 * Hook into the saved style ID so Pro can import a style template by its key and return a new style ID.
460 *
461 * @since 6.0
462 *
463 * @param int $style_id
464 */
465 $style_id = apply_filters( 'frm_saved_form_style_id', $style_id );
466
467 if ( ! $style_id && '0' !== FrmAppHelper::get_post_param( 'style_id', 'sanitize_text_field', '' ) ) {
468 // "0" is a special value used for the enable/disable toggle.
469 wp_die( esc_html__( 'Invalid style value', 'formidable' ), esc_html__( 'Invalid style value', 'formidable' ), 400 );
470 return;
471 }
472
473 $form_id = FrmAppHelper::get_post_param( 'form_id', 'absint', 0 );
474 if ( ! $form_id ) {
475 wp_die( esc_html__( 'No form specified', 'formidable' ), esc_html__( 'No form specified', 'formidable' ), 400 );
476 return;
477 }
478
479 $form = FrmForm::getOne( $form_id );
480 if ( ! $form ) {
481 wp_die( esc_html__( 'Form does not exist', 'formidable' ), esc_html__( 'Form does not exist', 'formidable' ), 400 );
482 return;
483 }
484
485 // If the default style is selected, use the "Always use default" legacy option instead of the default style.
486 // There's also a check here for conversational forms.
487 // Without the check it isn't possible to select "Default" because "Always use default" will convert to "Lines" dynamically.
488 $default_style = self::get_default_style();
489 if ( $style_id === $default_style->ID && empty( $form->options['chat'] ) ) {
490 $style_id = 1;
491 }
492
493 $form->options['custom_style'] = (string) $style_id; // We want to save a string for consistency. FrmStylesHelper::get_form_count_for_style expects the custom style ID is a string.
494
495 global $wpdb;
496 $wpdb->update( $wpdb->prefix . 'frm_forms', array( 'options' => maybe_serialize( $form->options ) ), array( 'id' => $form->id ) );
497
498 FrmForm::clear_form_cache();
499
500 self::$message = __( 'Successfully updated style.', 'formidable' );
501 }
502
503 /**
504 * Validate that we're assigning a form to a style that actually exists before assigning it to a form.
505 *
506 * @param int $style_id
507 * @return bool True if the style actually exists.
508 */
509 private static function confirm_style_exists_before_setting( $style_id ) {
510 global $wpdb;
511 $post_type = FrmDb::get_var( $wpdb->posts, array( 'ID' => $style_id ), 'post_type' );
512 return self::$post_type === $post_type;
513 }
514
515 /**
516 * Register and enqueue styles and scripts for the style tab page.
517 *
518 * @since 6.0
519 *
520 * @return void
521 */
522 private static function setup_styles_and_scripts_for_styler() {
523 $plugin_url = FrmAppHelper::plugin_url();
524 $version = FrmAppHelper::plugin_version();
525 $js_dependencies = array( 'wp-i18n', 'wp-hooks', 'formidable_dom' );
526
527 if ( FrmAppHelper::pro_is_installed() ) {
528 $js_dependencies[] = 'jquery-ui-datepicker';
529 }
530
531 wp_register_script( 'formidable_style', $plugin_url . '/js/admin/style.js', $js_dependencies, $version );
532 wp_register_style( 'formidable_style', $plugin_url . '/css/admin/style.css', array(), $version );
533 wp_print_styles( 'formidable_style' );
534
535 wp_print_styles( 'formidable' );
536 wp_enqueue_script( 'formidable_style' );
537 }
538
539 /**
540 * Render the style page (with a more limited and typed scope than calling it from self::style directly).
541 *
542 * @since 6.0
543 *
544 * @param stdClass|WP_Post $active_style
545 * @param array<WP_Post> $styles
546 * @param stdClass $form
547 * @param WP_Post $default_style
548 * @return void
549 */
550 private static function render_style_page( $active_style, $form, $default_style ) {
551 $style_views_path = self::get_views_path();
552 $view = FrmAppHelper::simple_get( 'frm_action', 'sanitize_text_field', 'list' ); // edit, list (default), new_style.
553 $frm_style = new FrmStyle( $active_style->ID );
554
555 if ( 'new_style' !== $view && ! FrmAppHelper::simple_get( 'form' ) && ! FrmAppHelper::simple_get( 'style_id' ) ) {
556 $view = 'edit'; // Have the Appearance > Forms link fallback to the edit view. Otherwise we want to use 'list' as the default.
557 }
558
559 if ( in_array( $view, array( 'edit', 'new_style', 'duplicate' ), true ) ) {
560 self::add_meta_boxes();
561 }
562
563 switch ( $view ) {
564 case 'edit':
565 $style = $active_style;
566 break;
567
568 case 'duplicate':
569 $style = clone $active_style;
570 $new_style = $frm_style->get_new();
571 $style->ID = $new_style->ID;
572 $style->post_name = $new_style->post_name;
573 unset( $new_style );
574 break;
575
576 case 'new_style':
577 $style = $frm_style->get_new();
578 break;
579 }
580
581 if ( in_array( $view, array( 'duplicate', 'new_style' ), true ) ) {
582 $style->post_title = FrmAppHelper::simple_get( 'style_name' );
583 $style->menu_order = 0;
584 }
585
586 if ( ! isset( $style ) ) {
587 $style = $active_style;
588 }
589
590 self::force_form_style( $style );
591
592 if ( isset( self::$message ) ) {
593 $message = self::$message;
594 }
595
596 $preview_helper = new FrmStylesPreviewHelper( $form->id );
597 $preview_helper->adjust_form_for_preview();
598
599 // Get form HTML before displaying warnings and notes so we can check global $frm_vars data without adding extra database calls.
600 $target_form_preview_html = $preview_helper->get_html_for_form_preview();
601 $warnings = $preview_helper->get_warnings_for_styler_preview( $style, $default_style, $view );
602 $notes = $preview_helper->get_notes_for_styler_preview();
603
604 include $style_views_path . 'show.php';
605 }
606
607 /**
608 * @since 6.0
609 *
610 * @return string
611 */
612 private static function get_views_path() {
613 return FrmAppHelper::plugin_path() . '/classes/views/styles/';
614 }
615
616 /**
617 * Filter form classes so the form uses the preview style, not the form's active style.
618 *
619 * @since 6.0
620 *
621 * @param WP_Post|stdClass $style A new style is not a WP_Post object.
622 * @return void
623 */
624 private static function force_form_style( $style ) {
625 add_filter(
626 'frm_add_form_style_class',
627 function( $class ) use ( $style ) {
628 $split = array_filter(
629 explode( ' ', $class ),
630 /**
631 * @param string $class
632 */
633 function( $class ) {
634 return $class && 0 !== strpos( $class, 'frm_style_' );
635 }
636 );
637 $split[] = 'frm_style_' . $style->post_name;
638 return implode( ' ', $split );
639 }
640 );
641 }
642
643 /**
644 * Save style post object via a POST request submitted from the Visual styler "edit" page.
645 *
646 * @since 6.0
647 *
648 * @return void
649 */
650 public static function save_style() {
651 $frm_style = new FrmStyle();
652 $message = '';
653 $post_id = FrmAppHelper::get_post_param( 'ID', false, 'sanitize_title' );
654 $style_nonce = FrmAppHelper::get_post_param( 'frm_style', '', 'sanitize_text_field' );
655
656 if ( $post_id === false || ! wp_verify_nonce( $style_nonce, 'frm_style_nonce' ) ) {
657 // Exit early if the request isn't valid.
658 // Since we're not dying, it should just reload the visual styler without any message.
659 return;
660 }
661
662 $id = $frm_style->update( $post_id );
663 if ( ! $post_id && $id ) {
664 self::maybe_redirect_after_save( $id );
665 $post_id = reset( $id ); // Set the post id to the new style so it will be loaded for editing.
666 }
667
668 self::$message = __( 'Your styling settings have been saved.', 'formidable' );
669 }
670
671 /**
672 * Show the edit view after saving.
673 * The save event is triggered earlier, on admin init where self::save_style is called.
674 * This happens earlier because there is a possible redirect (to adjust the URL for a new or duplicated style).
675 *
676 * @return void
677 */
678 public static function save() {
679 self::edit();
680 }
681
682 /**
683 * Force a redirect after duplicating or creating a new style to avoid an old stale URL that could result in more styles than intended.
684 *
685 * @since 6.0
686 *
687 * @param array $ids
688 * @return void
689 */
690 private static function maybe_redirect_after_save( $ids ) {
691 $referer = FrmAppHelper::get_server_value( 'HTTP_REFERER' );
692 $parsed = parse_url( $referer );
693 $query = $parsed['query'];
694
695 $current_action = false;
696 $actions_to_redirect = array( 'duplicate', 'new_style' );
697 foreach ( $actions_to_redirect as $action ) {
698 if ( false !== strpos( $query, 'frm_action=' . $action ) ) {
699 $current_action = $action;
700 break;
701 }
702 }
703
704 if ( false === $current_action ) {
705 // Do not redirect as the referer URL did not match $actions_to_redirect.
706 return;
707 }
708
709 parse_str( $query, $parsed_query );
710 $form_id = ! empty( $parsed_query['form'] ) ? absint( $parsed_query['form'] ) : 0;
711
712 $style = new stdClass();
713 $style->ID = end( $ids );
714 wp_safe_redirect( esc_url_raw( FrmStylesHelper::get_edit_url( $style, $form_id ) ) );
715 die();
716 }
717
718 /**
719 * @since 6.0
720 *
721 * @param string $message
722 * @param array|object $forms
723 * @return void
724 */
725 public static function manage( $message = '', $forms = array() ) {
726 $frm_style = new FrmStyle();
727 $styles = $frm_style->get_all();
728 $default_style = $frm_style->get_default_style( $styles );
729
730 if ( ! $forms ) {
731 $forms = FrmForm::get_published_forms();
732 }
733
734 include FrmAppHelper::plugin_path() . '/classes/views/styles/manage.php';
735 }
736
737 /**
738 * Handle saving for the page rendered in self::manage which is included in Global Settings in the "Manage Styles" tab.
739 * This gets called from the frm_update_settings hook which is called after saving Global settings.
740 *
741 * @return void
742 */
743 private static function manage_styles() {
744 global $wpdb;
745
746 $forms = FrmForm::get_published_forms();
747 foreach ( $forms as $form ) {
748 $new_style = ( isset( $_POST['style'] ) && isset( $_POST['style'][ $form->id ] ) ) ? sanitize_text_field( wp_unslash( $_POST['style'][ $form->id ] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
749 $previous_style = ( isset( $_POST['prev_style'] ) && isset( $_POST['prev_style'][ $form->id ] ) ) ? sanitize_text_field( wp_unslash( $_POST['prev_style'][ $form->id ] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
750 if ( $new_style == $previous_style ) {
751 continue;
752 }
753
754 $form->options['custom_style'] = $new_style;
755 $wpdb->update( $wpdb->prefix . 'frm_forms', array( 'options' => maybe_serialize( $form->options ) ), array( 'id' => $form->id ) );
756 unset( $form );
757 }
758 }
759
760 /**
761 * Echo content for the Custom CSS page.
762 *
763 * @param string $message
764 * @return void
765 */
766 public static function custom_css( $message = '' ) {
767 $settings = self::enqueue_codemirror();
768 $id = $settings ? 'frm_codemirror_box' : 'frm_custom_css_box';
769 $custom_css = self::get_custom_css();
770
771 include FrmAppHelper::plugin_path() . '/classes/views/styles/custom_css.php';
772 }
773
774 /**
775 * Get custom CSS code entered in the Custom CSS page.
776 *
777 * @since 6.0
778 *
779 * @return string
780 */
781 public static function get_custom_css() {
782 $settings = FrmAppHelper::get_settings();
783 if ( is_string( $settings->custom_css ) ) {
784 return $settings->custom_css;
785 }
786
787 // If it does not exist, check the default style as a fallback.
788 $frm_style = new FrmStyle();
789 $style = $frm_style->get_default_style();
790 $custom_css = $style->post_content['custom_css'];
791
792 return $custom_css;
793 }
794
795 /**
796 * Enqueue assets for codemirror, built into WordPress since 4.9.
797 * The Custom CSS page uses codemirror.
798 *
799 * @since 6.0 Previously this code was embedded in self::custom_css.
800 *
801 * @return array|false
802 */
803 private static function enqueue_codemirror() {
804 if ( ! function_exists( 'wp_enqueue_code_editor' ) ) {
805 // The WordPress version is likely older than 4.9.
806 return false;
807 }
808
809 $settings = wp_enqueue_code_editor(
810 array(
811 'type' => 'text/css',
812 'codemirror' => array(
813 'indentUnit' => 2,
814 'tabSize' => 2,
815 // As the codemirror box only appears once you click into the Custom CSS tab, we need to auto-refresh.
816 // Otherwise the line numbers all end up with a 1px width causing overlap issues with the text in the content.
817 'autoRefresh' => true,
818 ),
819 )
820 );
821
822 if ( $settings ) {
823 wp_add_inline_script(
824 'code-editor',
825 sprintf(
826 'jQuery( function() { wp.codeEditor.initialize( \'frm_codemirror_box\', %s ); } );',
827 wp_json_encode( $settings )
828 )
829 );
830 }
831
832 return $settings;
833 }
834
835 /**
836 * Handling routing for the visual styler.
837 *
838 * @return void
839 */
840 public static function route() {
841 $action = FrmAppHelper::get_param( 'frm_action', '', 'get', 'sanitize_title' );
842 FrmAppHelper::include_svg();
843
844 switch ( $action ) {
845 case 'edit':
846 case 'save':
847 self::$action();
848 return;
849 default:
850 do_action( 'frm_style_action_route', $action );
851
852 if ( apply_filters( 'frm_style_stop_action_route', false, $action ) ) {
853 return;
854 }
855
856 if ( in_array( $action, array( 'new_style', 'duplicate' ), true ) ) {
857 self::$action();
858 return;
859 }
860
861 self::edit();
862 return;
863 }
864 }
865
866 /**
867 * Handle AJAX routing for frm_settings_reset for resetting styles to the default settings.
868 * From the edit view, it will return default styles and not actually update the style.
869 * 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.
870 *
871 * @since 6.0 When a style_id is passed to this action, the style will actually be reset.
872 *
873 * @return void
874 */
875 public static function reset_styling() {
876 FrmAppHelper::permission_check( 'frm_change_settings' );
877 check_ajax_referer( 'frm_ajax', 'nonce' );
878
879 $style_id = FrmAppHelper::get_post_param( 'style_id', '', 'absint' );
880 if ( ! $style_id ) {
881 // A style ID is not sent when resetting on the edit page.
882 // Instead of resetting the style, send the defaults back so the inputs can be updated with JavaScript.
883 $frm_style = new FrmStyle();
884 $defaults = $frm_style->get_defaults();
885 echo json_encode( $defaults );
886 wp_die();
887 }
888
889 $frm_style = new FrmStyle();
890 $defaults = $frm_style->get_defaults();
891 $default_post_content = FrmAppHelper::prepare_and_encode( $defaults );
892 $where = array(
893 'ID' => $style_id,
894 'post_type' => self::$post_type,
895 );
896 global $wpdb;
897 $wpdb->update( $wpdb->posts, array( 'post_content' => $default_post_content ), $where );
898
899 $frm_style->save_settings(); // Save the settings after resetting to default or the old style will still appear.
900
901 $data = array(
902 'style' => FrmStylesCardHelper::get_style_param_for_card( $frm_style->get_new() ),
903 );
904 wp_send_json_success( $data );
905 wp_die();
906 }
907
908 /**
909 * Handle routing for the frm_change_styling AJAX action.
910 * This doesn't actually change styling. It just handles the events when someone changes a style.
911 * It responds with the new CSS required for the updated styler preview in the edit page.
912 *
913 * @return void
914 */
915 public static function change_styling() {
916 FrmAppHelper::permission_check( 'frm_change_settings' );
917 check_ajax_referer( 'frm_ajax', 'nonce' );
918
919 $frm_style = new FrmStyle();
920 $defaults = false; // Intentionally avoid defaults here so nothing gets removed from our style.
921 $style = '';
922
923 echo '<style type="text/css">';
924 include FrmAppHelper::plugin_path() . '/css/_single_theme.css.php';
925 echo '</style>';
926 wp_die();
927 }
928
929 /**
930 * @return void
931 */
932 public static function add_meta_boxes() {
933 // setup meta boxes
934 $meta_boxes = array(
935 'general' => __( 'General', 'formidable' ),
936 'form-title' => __( 'Form Title', 'formidable' ),
937 'form-description' => __( 'Form Description', 'formidable' ),
938 'field-labels' => __( 'Field Labels', 'formidable' ),
939 'field-description' => __( 'Field Description', 'formidable' ),
940 'field-colors' => __( 'Field Colors', 'formidable' ),
941 'field-sizes' => __( 'Field Settings', 'formidable' ),
942 'check-box-radio-fields' => __( 'Check Box & Radio Fields', 'formidable' ),
943 'buttons' => __( 'Buttons', 'formidable' ),
944 'form-messages' => __( 'Form Messages', 'formidable' ),
945 );
946
947 /**
948 * Add custom boxes to the styling settings
949 *
950 * @since 2.3
951 *
952 * @param array $meta_boxes
953 */
954 $meta_boxes = apply_filters( 'frm_style_boxes', $meta_boxes );
955
956 foreach ( $meta_boxes as $nicename => $name ) {
957 add_meta_box( $nicename . '-style', $name, 'FrmStylesController::include_style_section', self::$screen, 'side', 'default', $nicename );
958 unset( $nicename, $name );
959 }
960 }
961
962 /**
963 * @param array $atts
964 * @param array $sec
965 * @return void
966 */
967 public static function include_style_section( $atts, $sec ) {
968 extract( $atts ); // phpcs:ignore WordPress.PHP.DontExtract
969 $style = $atts['style'];
970 FrmStylesHelper::prepare_color_output( $style->post_content, false );
971
972 $current_tab = FrmAppHelper::simple_get( 'page-tab', 'sanitize_title', 'default' );
973 $file_name = FrmAppHelper::plugin_path() . '/classes/views/styles/_' . $sec['args'] . '.php';
974
975 /**
976 * Set the location of custom styling settings right before
977 * loading onto the page. If your style box was named "progress",
978 * this hook name will be frm_style_settings_progress.
979 *
980 * @since 2.3
981 */
982 $file_name = apply_filters( 'frm_style_settings_' . $sec['args'], $file_name );
983
984 echo '<div class="frm_grid_container">';
985 include $file_name;
986 echo '</div>';
987 }
988
989 public static function load_css() {
990 header( 'Content-type: text/css' );
991
992 $frm_style = new FrmStyle();
993 $defaults = $frm_style->get_defaults();
994 $style = '';
995
996 include FrmAppHelper::plugin_path() . '/css/_single_theme.css.php';
997 wp_die();
998 }
999
1000 /**
1001 * @return void
1002 */
1003 public static function load_saved_css() {
1004 $css = get_transient( 'frmpro_css' );
1005
1006 ob_start();
1007 include FrmAppHelper::plugin_path() . '/css/custom_theme.css.php';
1008 $output = ob_get_clean();
1009 $output = self::replace_relative_url( $output );
1010
1011 /**
1012 * The API needs to load font icons through a custom URL.
1013 *
1014 * @since 5.2
1015 *
1016 * @param string $output
1017 */
1018 $output = apply_filters( 'frm_saved_css', $output );
1019
1020 echo $output; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1021
1022 self::maybe_hide_sample_form_error_message();
1023
1024 wp_die();
1025 }
1026
1027 /**
1028 * Add an extra style rule to hide a broken style warning.
1029 * To avoid cluttering the front end with any unecessary styles this is only added when the referer URL matches the styler.
1030 *
1031 * @since 6.2.3
1032 *
1033 * @return void
1034 */
1035 public static function maybe_hide_sample_form_error_message() {
1036 $referer = FrmAppHelper::get_server_value( 'HTTP_REFERER' );
1037 if ( false !== strpos( $referer, 'admin.php?page=formidable-styles' ) ) {
1038 echo '#frm_broken_styles_warning { display: none; }';
1039 }
1040 }
1041
1042 /**
1043 * Replaces relative URL with absolute URL.
1044 *
1045 * @since 4.11.03
1046 *
1047 * @param string $css CSS content.
1048 * @return string
1049 */
1050 public static function replace_relative_url( $css ) {
1051 $plugin_url = trailingslashit( FrmAppHelper::plugin_url() );
1052 return str_replace(
1053 array(
1054 'url(../',
1055 "url('../",
1056 'url("../',
1057 ),
1058 array(
1059 'url(' . $plugin_url,
1060 "url('" . $plugin_url,
1061 'url("' . $plugin_url,
1062 ),
1063 $css
1064 );
1065 }
1066
1067 /**
1068 * Check if the Formidable styling should be loaded,
1069 * then enqueue it for the footer
1070 *
1071 * @since 2.0
1072 */
1073 public static function enqueue_style() {
1074 global $frm_vars;
1075
1076 if ( isset( $frm_vars['css_loaded'] ) && $frm_vars['css_loaded'] ) {
1077 // The CSS has already been loaded.
1078 return;
1079 }
1080
1081 $frm_settings = FrmAppHelper::get_settings();
1082 if ( $frm_settings->load_style != 'none' ) {
1083 wp_enqueue_style( 'formidable' );
1084 $frm_vars['css_loaded'] = true;
1085 }
1086 }
1087
1088 /**
1089 * Get the stylesheets for the form settings page
1090 *
1091 * @return array<WP_Post>
1092 */
1093 public static function get_style_opts() {
1094 $frm_style = new FrmStyle();
1095 $styles = $frm_style->get_all();
1096
1097 return $styles;
1098 }
1099
1100 /**
1101 * Get the style post object for a target form.
1102 *
1103 * @param object|string|boolean $form
1104 * @return WP_Post|null
1105 */
1106 public static function get_form_style( $form = 'default' ) {
1107 $style = FrmFormsHelper::get_form_style( $form );
1108
1109 if ( empty( $style ) || 1 == $style ) {
1110 $style = 'default';
1111 }
1112
1113 $frm_style = new FrmStyle( $style );
1114 return $frm_style->get_one();
1115 }
1116
1117 /**
1118 * @param string $class
1119 * @param string $style
1120 */
1121 public static function get_form_style_class( $class, $style ) {
1122 if ( 1 == $style ) {
1123 $style = 'default';
1124 }
1125
1126 $frm_style = new FrmStyle( $style );
1127 $style = $frm_style->get_one();
1128
1129 if ( $style ) {
1130 $class .= ' frm_style_' . $style->post_name;
1131 self::maybe_add_rtl_class( $style, $class );
1132 }
1133
1134 return $class;
1135 }
1136
1137 /**
1138 * @param object $style
1139 * @param string $class
1140 *
1141 * @since 3.0
1142 */
1143 private static function maybe_add_rtl_class( $style, &$class ) {
1144 $is_rtl = isset( $style->post_content['direction'] ) && 'rtl' === $style->post_content['direction'];
1145 if ( $is_rtl ) {
1146 $class .= ' frm_rtl';
1147 }
1148 }
1149
1150 /**
1151 * @param string $val
1152 */
1153 public static function get_style_val( $val, $form = 'default' ) {
1154 $style = self::get_form_style( $form );
1155 if ( $style && isset( $style->post_content[ $val ] ) ) {
1156 return $style->post_content[ $val ];
1157 }
1158 }
1159
1160 public static function show_entry_styles( $default_styles ) {
1161 $frm_style = new FrmStyle( 'default' );
1162 $style = $frm_style->get_one();
1163
1164 if ( ! $style ) {
1165 return $default_styles;
1166 }
1167
1168 foreach ( $default_styles as $name => $val ) {
1169 $setting = $name;
1170 if ( 'border_width' == $name ) {
1171 $setting = 'field_border_width';
1172 } elseif ( 'alt_bg_color' == $name ) {
1173 $setting = 'bg_color_active';
1174 }
1175 $default_styles[ $name ] = $style->post_content[ $setting ];
1176 unset( $name, $val );
1177 }
1178
1179 return $default_styles;
1180 }
1181
1182 public static function &important_style( $important, $field ) {
1183 $important = self::get_style_val( 'important_style', $field['form_id'] );
1184
1185 return $important;
1186 }
1187
1188 public static function do_accordion_sections( $screen, $context, $object ) {
1189 return do_accordion_sections( $screen, $context, $object );
1190 }
1191
1192 /**
1193 * Rename a style via an AJAX action.
1194 *
1195 * @since 6.0
1196 *
1197 * @return void
1198 */
1199 public static function rename_style() {
1200 $permission_error = FrmAppHelper::permission_nonce_error( 'frm_edit_forms', 'nonce', 'frm_ajax' );
1201 if ( $permission_error !== false ) {
1202 $data = array(
1203 'message' => __( 'Unable to rename style', 'formidable' ),
1204 );
1205 wp_send_json_error( $data, 403 );
1206 die();
1207 }
1208
1209 $style_id = FrmAppHelper::get_post_param( 'style_id', 0, 'absint' );
1210 $style_name = FrmAppHelper::get_post_param( 'style_name', '', 'sanitize_text_field' );
1211
1212 if ( ! $style_id || ! $style_name ) {
1213 $data = array(
1214 'message' => __( 'Invalid route', 'formidable' ),
1215 );
1216 wp_send_json_error( $data, 400 );
1217 die();
1218 }
1219
1220 $post = get_post( $style_id );
1221 if ( ! $post || $post->post_type !== self::$post_type ) {
1222 $data = array(
1223 'message' => __( 'The style you are renaming either does not exist or it is not a style', 'formidable' ),
1224 );
1225 wp_send_json_error( $data, 404 );
1226 die();
1227 }
1228
1229 global $wpdb;
1230 $wpdb->update( $wpdb->posts, array( 'post_title' => $style_name ), array( 'ID' => $post->ID ) );
1231
1232 $data = array();
1233 wp_send_json_success( $data );
1234 }
1235
1236 /**
1237 * Prevent the WordPress edit.css file from loading on the visual styler page.
1238 * This way .form-field elements do not have border styles applied to them.
1239 *
1240 * @since 6.0
1241 *
1242 * @param WP_Styles $styles
1243 * @return void
1244 */
1245 public static function disable_conflicting_wp_admin_css( $styles ) {
1246 if ( ! FrmAppHelper::is_style_editor_page() ) {
1247 return;
1248 }
1249
1250 FrmStylesPreviewHelper::disable_conflicting_wp_admin_css( $styles );
1251 }
1252
1253 /**
1254 * @deprecated 6.1 Saving custom CSS has been moved into Global Settings.
1255 *
1256 * @return void
1257 */
1258 public static function save_css() {
1259 _deprecated_function( __METHOD__, '6.1' );
1260 }
1261 }
1262