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

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