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

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