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

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