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

1,491 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 $frm_style = new FrmStyle();
714 $post_id = FrmAppHelper::get_post_param( 'ID', false, 'sanitize_title' );
715 $style_nonce = FrmAppHelper::get_post_param( 'frm_style', '', 'sanitize_text_field' );
716
717 if ( $post_id === false || ! wp_verify_nonce( $style_nonce, 'frm_style_nonce' ) ) {
718 // Exit early if the request isn't valid.
719 // Since we're not dying, it should just reload the visual styler without any message.
720 return;
721 }
722
723 $id = $frm_style->update( $post_id );
724
725 if ( ! $post_id && $id ) {
726 self::maybe_redirect_after_save( $id );
727 // Set the post id to the new style so it will be loaded for editing.
728 $post_id = reset( $id );
729 }
730
731 /**
732 * @since 6.25.1
733 *
734 * @param int $post_id
735 */
736 do_action( 'frm_after_saved_style', absint( $post_id ) );
737
738 self::$message = __( 'Your styling settings have been saved.', 'formidable' );
739 }
740
741 /**
742 * Show the edit view after saving.
743 * The save event is triggered earlier, on admin init where self::save_style is called.
744 * This happens earlier because there is a possible redirect (to adjust the URL for a new or duplicated style).
745 *
746 * @return void
747 */
748 public static function save() {
749 self::edit();
750 }
751
752 /**
753 * Force a redirect after duplicating or creating a new style to avoid an old stale URL that could result in more styles than intended.
754 *
755 * @since 6.0
756 *
757 * @param array $ids
758 *
759 * @return void
760 */
761 private static function maybe_redirect_after_save( $ids ) {
762 $referer = FrmAppHelper::get_server_value( 'HTTP_REFERER' );
763 $parsed = parse_url( $referer );
764 $query = $parsed['query'];
765 $current_action = false;
766 $actions_to_redirect = array( 'duplicate', 'new_style' );
767
768 foreach ( $actions_to_redirect as $action ) {
769 if ( str_contains( $query, 'frm_action=' . $action ) ) {
770 $current_action = $action;
771 break;
772 }
773 }
774
775 if ( false === $current_action ) {
776 // Do not redirect as the referer URL did not match $actions_to_redirect.
777 return;
778 }
779
780 parse_str( $query, $parsed_query );
781 $form_id = ! empty( $parsed_query['form'] ) ? absint( $parsed_query['form'] ) : 0;
782 $style = new stdClass();
783 $style->ID = end( $ids );
784 wp_safe_redirect( esc_url_raw( FrmStylesHelper::get_edit_url( $style, $form_id ) ) );
785 die();
786 }
787
788 /**
789 * @since 6.0
790 *
791 * @param string $message
792 * @param array|object $forms
793 *
794 * @return void
795 */
796 public static function manage( $message = '', $forms = array() ) {
797 $frm_style = new FrmStyle();
798 $styles = $frm_style->get_all();
799 $default_style = $frm_style->get_default_style( $styles );
800 $frm_settings = FrmAppHelper::get_settings();
801
802 if ( ! $forms ) {
803 $forms = FrmForm::get_published_forms();
804 }
805
806 include FrmAppHelper::plugin_path() . '/classes/views/styles/manage.php';
807 }
808
809 /**
810 * Handle saving for the page rendered in self::manage which is included in Global Settings in the "Manage Styles" tab.
811 * This gets called from the frm_update_settings hook which is called after saving Global settings.
812 *
813 * @return void
814 */
815 private static function manage_styles() {
816 global $wpdb;
817
818 $forms = FrmForm::get_published_forms();
819
820 foreach ( $forms as $form ) {
821 if ( ! isset( $_POST['style'] ) || ! isset( $_POST['style'][ $form->id ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
822 continue;
823 }
824
825 $new_style = sanitize_text_field( wp_unslash( $_POST['style'][ $form->id ] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
826
827 $form->options['custom_style'] = $new_style;
828 $wpdb->update( $wpdb->prefix . 'frm_forms', array( 'options' => maybe_serialize( $form->options ) ), array( 'id' => $form->id ) );
829 unset( $form );
830 }
831 }
832
833 /**
834 * Echo content for the Custom CSS page.
835 *
836 * @param string $message
837 * @param array $extra_args An array of extra arguments.
838 *
839 * @return void
840 */
841 public static function custom_css( $message = '', $extra_args = array() ) {
842 $id = $extra_args['id'] ?? 'frm_codemirror_box';
843 $settings = self::enqueue_codemirror( $id, $extra_args['placeholder'] ?? '' );
844 $id = $settings ? $id : 'frm_custom_css_box';
845 $show_errors = $extra_args['show_errors'] ?? true;
846 $custom_css = $extra_args['custom_css'] ?? self::get_custom_css();
847 $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
848 $textarea_params = ! empty( $extra_args['textarea_params'] ) ? $extra_args['textarea_params'] : array(
849 'name' => 'frm_custom_css',
850 'id' => $id,
851 );
852
853 if ( $settings ) {
854 $textarea_params['class'] = 'hide-if-js';
855 }
856
857 include FrmAppHelper::plugin_path() . '/classes/views/styles/custom_css.php';
858 }
859
860 /**
861 * Get custom CSS code entered in the Custom CSS page.
862 *
863 * @since 6.0
864 *
865 * @param array|null $single_style_settings The single style settings.
866 *
867 * @return string
868 */
869 public static function get_custom_css( $single_style_settings = null ) {
870 if ( $single_style_settings ) {
871 // If the single style settings are passed, return the custom CSS from the single style settings.
872 if ( ! empty( $single_style_settings['single_style_custom_css'] ) && ! empty( $single_style_settings['enable_style_custom_css'] ) ) {
873 return $single_style_settings['single_style_custom_css'];
874 }
875
876 return '';
877 }
878
879 $settings = FrmAppHelper::get_settings();
880
881 if ( is_string( $settings->custom_css ) ) {
882 return $settings->custom_css;
883 }
884
885 // If it does not exist, check the default style as a fallback.
886 $frm_style = new FrmStyle();
887 $style = $frm_style->get_default_style();
888
889 return $style->post_content['custom_css'];
890 }
891
892 /**
893 * Enqueue assets for codemirror, built into WordPress since 4.9.
894 * The Custom CSS page uses codemirror.
895 *
896 * @since 6.0 Previously this code was embedded in self::custom_css.
897 *
898 * @param string $id The ID of the codemirror box.
899 * @param string $placeholder The placeholder text for the codemirror box.
900 *
901 * @return array|false
902 */
903 private static function enqueue_codemirror( $id = 'frm_codemirror_box', $placeholder = '' ) {
904 if ( ! function_exists( 'wp_enqueue_code_editor' ) ) {
905 // The WordPress version is likely older than 4.9.
906 return false;
907 }
908
909 $settings = wp_enqueue_code_editor(
910 array(
911 'type' => 'text/css',
912 'codemirror' => array(
913 'indentUnit' => 2,
914 'tabSize' => 2,
915 // As the codemirror box only appears once you click into the Custom CSS tab, we need to auto-refresh.
916 // Otherwise the line numbers all end up with a 1px width causing overlap issues with the text in the content.
917 'autoRefresh' => true,
918 'placeholder' => $placeholder,
919 ),
920 )
921 );
922
923 if ( $settings ) {
924 wp_add_inline_script(
925 'code-editor',
926 sprintf(
927 'jQuery( function() { window.%s_wp_editor = wp.codeEditor.initialize( \'%s\', %s ); } );',
928 $id,
929 $id,
930 wp_json_encode( $settings )
931 )
932 );
933 }
934
935 return $settings;
936 }
937
938 /**
939 * Handling routing for the visual styler.
940 *
941 * @return void
942 */
943 public static function route() {
944 $action = FrmAppHelper::get_param( 'frm_action', '', 'get', 'sanitize_title' );
945 FrmAppHelper::include_svg();
946
947 switch ( $action ) {
948 case 'edit':
949 case 'save':
950 self::$action();
951 return;
952 default:
953 do_action( 'frm_style_action_route', $action );
954
955 if ( apply_filters( 'frm_style_stop_action_route', false, $action ) ) {
956 return;
957 }
958
959 if ( in_array( $action, array( 'new_style', 'duplicate' ), true ) ) {
960 self::$action();
961 return;
962 }
963
964 self::edit();
965 return;
966 }
967 }
968
969 /**
970 * Handle AJAX routing for frm_settings_reset for resetting styles to the default settings.
971 * From the edit view, it will return default styles and not actually update the style.
972 * 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.
973 *
974 * @since 6.0 When a style_id is passed to this action, the style will actually be reset.
975 *
976 * @return void
977 */
978 public static function reset_styling() {
979 FrmAppHelper::permission_check( 'frm_change_settings' );
980 check_ajax_referer( 'frm_ajax', 'nonce' );
981
982 $style_id = FrmAppHelper::get_post_param( 'style_id', '', 'absint' );
983
984 if ( ! $style_id ) {
985 // A style ID is not sent when resetting on the edit page.
986 // Instead of resetting the style, send the defaults back so the inputs can be updated with JavaScript.
987 $frm_style = new FrmStyle();
988 $defaults = $frm_style->get_defaults();
989 echo json_encode( $defaults );
990 wp_die();
991 }
992
993 $frm_style = new FrmStyle();
994 $default_template_style = $frm_style->get_default_template_style( $style_id );
995 $where = array(
996 'ID' => $style_id,
997 'post_type' => self::$post_type,
998 );
999
1000 $style_object = $frm_style->get_new();
1001 $style_object->post_content = json_decode( $default_template_style, true );
1002
1003 global $wpdb;
1004 $wpdb->update( $wpdb->posts, array( 'post_content' => $default_template_style ), $where );
1005
1006 // Save the settings after resetting to default or the old style will still appear.
1007 $frm_style->save_settings();
1008
1009 $data = array(
1010 'style' => FrmStylesCardHelper::get_style_param_for_card( $style_object ),
1011 );
1012 wp_send_json_success( $data );
1013 wp_die();
1014 }
1015
1016 /**
1017 * Handle routing for the frm_change_styling AJAX action.
1018 * This doesn't actually change styling. It just handles the events when someone changes a style.
1019 * It responds with the new CSS required for the updated styler preview in the edit page.
1020 *
1021 * @return void
1022 */
1023 public static function change_styling() {
1024 FrmAppHelper::permission_check( 'frm_change_settings' );
1025 check_ajax_referer( 'frm_ajax', 'nonce' );
1026
1027 $is_loaded_via_ajax = true;
1028 $frm_style = new FrmStyle();
1029
1030 // Intentionally avoid defaults here so nothing gets removed from our style.
1031 $defaults = array();
1032 $style = '';
1033
1034 echo '<style type="text/css">';
1035 include FrmAppHelper::plugin_path() . '/css/_single_theme.css.php';
1036 echo '</style>';
1037 wp_die();
1038 }
1039
1040 /**
1041 * @return void
1042 */
1043 public static function add_meta_boxes() {
1044 // Setup meta boxes
1045 $meta_boxes = array(
1046 'general' => __( 'General', 'formidable' ),
1047 'form-title' => __( 'Form Title', 'formidable' ),
1048 'form-description' => __( 'Form Description', 'formidable' ),
1049 'field-labels' => __( 'Field Labels', 'formidable' ),
1050 'field-description' => __( 'Field Description', 'formidable' ),
1051 'field-colors' => __( 'Field Colors', 'formidable' ),
1052 'field-sizes' => __( 'Field Settings', 'formidable' ),
1053 'check-box-radio-fields' => __( 'Check Box & Radio Fields', 'formidable' ),
1054 'buttons' => __( 'Buttons', 'formidable' ),
1055 'form-messages' => __( 'Form Messages', 'formidable' ),
1056 );
1057
1058 /**
1059 * Add custom boxes to the styling settings
1060 *
1061 * @since 2.3
1062 *
1063 * @param array $meta_boxes
1064 */
1065 $meta_boxes = apply_filters( 'frm_style_boxes', $meta_boxes );
1066
1067 foreach ( $meta_boxes as $nicename => $name ) {
1068 add_meta_box( $nicename . '-style', $name, 'FrmStylesController::include_style_section', self::$screen, 'side', 'default', $nicename );
1069 unset( $nicename, $name );
1070 }
1071 }
1072
1073 /**
1074 * @param array $atts
1075 * @param array $sec
1076 *
1077 * @return void
1078 */
1079 public static function include_style_section( $atts, $sec ) {
1080 extract( $atts ); // phpcs:ignore WordPress.PHP.DontExtract
1081 $style = $atts['style'];
1082 FrmStylesHelper::prepare_color_output( $style->post_content, false );
1083
1084 $current_tab = FrmAppHelper::simple_get( 'page-tab', 'sanitize_title', 'default' );
1085 $file_name = FrmAppHelper::plugin_path() . '/classes/views/styles/_' . $sec['args'] . '.php';
1086
1087 /**
1088 * Set the location of custom styling settings right before
1089 * loading onto the page. If your style box was named "progress",
1090 * this hook name will be frm_style_settings_progress.
1091 *
1092 * @since 2.3
1093 */
1094 $file_name = apply_filters( 'frm_style_settings_' . $sec['args'], $file_name );
1095
1096 echo '<div class="frm_grid_container">';
1097 include $file_name;
1098 echo '</div>';
1099 }
1100
1101 public static function load_css() {
1102 header( 'Content-type: text/css' );
1103
1104 $frm_style = new FrmStyle();
1105 $defaults = $frm_style->get_defaults();
1106 $style = '';
1107
1108 include FrmAppHelper::plugin_path() . '/css/_single_theme.css.php';
1109 wp_die();
1110 }
1111
1112 /**
1113 * @return void
1114 */
1115 public static function load_saved_css() {
1116 $css = get_transient( 'frmpro_css' );
1117
1118 ob_start();
1119 include FrmAppHelper::plugin_path() . '/css/custom_theme.css.php';
1120 $output = ob_get_clean();
1121 $output = self::replace_relative_url( $output );
1122
1123 /**
1124 * The API needs to load font icons through a custom URL.
1125 *
1126 * @since 5.2
1127 *
1128 * @param string $output
1129 */
1130 $output = apply_filters( 'frm_saved_css', $output );
1131
1132 echo $output; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1133
1134 self::maybe_hide_sample_form_error_message();
1135
1136 wp_die();
1137 }
1138
1139 /**
1140 * Add an extra style rule to hide a broken style warning.
1141 * To avoid cluttering the front end with any unnecessary styles this is only added when the referer URL matches the styler.
1142 *
1143 * @since 6.2.3
1144 *
1145 * @return void
1146 */
1147 public static function maybe_hide_sample_form_error_message() {
1148 $referer = FrmAppHelper::get_server_value( 'HTTP_REFERER' );
1149
1150 if ( str_contains( $referer, 'admin.php?page=formidable-styles' ) ) {
1151 echo '#frm_broken_styles_warning { display: none; }';
1152 }
1153 }
1154
1155 /**
1156 * Replaces relative URL with absolute URL.
1157 *
1158 * @since 4.11.03
1159 *
1160 * @param string $css CSS content.
1161 *
1162 * @return string
1163 */
1164 public static function replace_relative_url( $css ) {
1165 $plugin_url = trailingslashit( FrmAppHelper::plugin_url() );
1166 return str_replace(
1167 array(
1168 'url(../',
1169 "url('../",
1170 'url("../',
1171 ),
1172 array(
1173 'url(' . $plugin_url,
1174 "url('" . $plugin_url,
1175 'url("' . $plugin_url,
1176 ),
1177 $css
1178 );
1179 }
1180
1181 /**
1182 * Check if the Formidable styling should be loaded,
1183 * then enqueue it for the footer
1184 *
1185 * @since 2.0
1186 *
1187 * @return void
1188 */
1189 public static function enqueue_style() {
1190 global $frm_vars;
1191
1192 if ( ! empty( $frm_vars['css_loaded'] ) ) {
1193 // The CSS has already been loaded.
1194 return;
1195 }
1196
1197 $frm_settings = FrmAppHelper::get_settings();
1198
1199 if ( $frm_settings->load_style === 'none' ) {
1200 return;
1201 }
1202
1203 wp_enqueue_style( 'formidable' );
1204 $frm_vars['css_loaded'] = true;
1205 }
1206
1207 /**
1208 * Get the stylesheets for the form settings page
1209 *
1210 * @return array<WP_Post>
1211 */
1212 public static function get_style_opts() {
1213 $frm_style = new FrmStyle();
1214 return $frm_style->get_all();
1215 }
1216
1217 /**
1218 * Get the style post object for a target form.
1219 *
1220 * @param bool|object|string $form
1221 *
1222 * @return WP_Post|null
1223 */
1224 public static function get_form_style( $form = 'default' ) {
1225 $style = FrmFormsHelper::get_form_style( $form );
1226
1227 // phpcs:ignore Universal.Operators.StrictComparisons
1228 if ( ! $style || 1 == $style ) {
1229 $style = 'default';
1230 }
1231
1232 $frm_style = new FrmStyle( $style );
1233 return $frm_style->get_one();
1234 }
1235
1236 /**
1237 * @param string $class
1238 * @param string $style
1239 *
1240 * @return string
1241 */
1242 public static function get_form_style_class( $class, $style ) {
1243 // phpcs:ignore Universal.Operators.StrictComparisons
1244 if ( 1 == $style ) {
1245 $style = 'default';
1246 }
1247
1248 $frm_style = new FrmStyle( $style );
1249 $style = $frm_style->get_one();
1250
1251 if ( $style ) {
1252 $class .= ' frm_style_' . $style->post_name;
1253 self::maybe_add_rtl_class( $style, $class );
1254 }
1255
1256 return $class;
1257 }
1258
1259 /**
1260 * @since 3.0
1261 *
1262 * @param object $style
1263 * @param string $class
1264 *
1265 * @return void
1266 */
1267 private static function maybe_add_rtl_class( $style, &$class ) {
1268 $is_rtl = isset( $style->post_content['direction'] ) && 'rtl' === $style->post_content['direction'];
1269
1270 if ( $is_rtl ) {
1271 $class .= ' frm_rtl';
1272 }
1273 }
1274
1275 /**
1276 * @param string $val Style setting key.
1277 * @param int|string $form Form ID or 'default'.
1278 *
1279 * @return mixed
1280 */
1281 public static function get_style_val( $val, $form = 'default' ) {
1282 $style = self::get_form_style( $form );
1283
1284 if ( $style && isset( $style->post_content[ $val ] ) ) {
1285 return $style->post_content[ $val ];
1286 }
1287
1288 return null;
1289 }
1290
1291 /**
1292 * @param array $default_styles
1293 *
1294 * @return array
1295 */
1296 public static function show_entry_styles( $default_styles ) {
1297 $frm_style = new FrmStyle( 'default' );
1298 $style = $frm_style->get_one();
1299
1300 if ( ! $style ) {
1301 return $default_styles;
1302 }
1303
1304 foreach ( $default_styles as $name => $val ) {
1305 $setting = $name;
1306
1307 if ( 'border_width' === $name ) {
1308 $setting = 'field_border_width';
1309 } elseif ( 'alt_bg_color' === $name ) {
1310 $setting = 'bg_color_active';
1311 }
1312
1313 $default_styles[ $name ] = $style->post_content[ $setting ];
1314 unset( $name, $val );
1315 }
1316
1317 return $default_styles;
1318 }
1319
1320 /**
1321 * @param string $important
1322 * @param array $field
1323 *
1324 * @return string
1325 */
1326 public static function important_style( $important, $field ) {
1327 return self::get_style_val( 'important_style', $field['form_id'] );
1328 }
1329
1330 /**
1331 * Duplicate of WordPress do_accordion_section function, it adds an additional svg icon support.
1332 *
1333 * @since 6.8.3
1334 *
1335 * @param string|WP_Screen $screen Screen ID or screen object.
1336 * @param string $context Meta box context.
1337 * @param mixed $data_object Data object passed to callbacks.
1338 *
1339 * @return int
1340 */
1341 public static function do_accordion_sections( $screen, $context, $data_object ) { // phpcs:ignore SlevomatCodingStandard.Complexity.Cognitive.ComplexityTooHigh
1342 global $wp_meta_boxes;
1343
1344 // The symbol id from icons.svg
1345 $icon_ids = array(
1346 'ranking-fields-style' => 'frm_chart_bar_icon',
1347 'section-fields-style' => 'frm-form-title-style',
1348 );
1349
1350 wp_enqueue_script( 'accordion' );
1351
1352 if ( ! $screen ) {
1353 $screen = get_current_screen();
1354 } elseif ( is_string( $screen ) ) {
1355 $screen = convert_to_screen( $screen );
1356 }
1357
1358 $page = $screen->id;
1359
1360 // phpcs:disable Generic.WhiteSpace.ScopeIndent
1361 ?>
1362 <div id="side-sortables" class="accordion-container">
1363 <ul class="outer-border">
1364 <?php
1365 $i = 0;
1366 $first_open = false;
1367
1368 if ( isset( $wp_meta_boxes[ $page ][ $context ] ) ) {
1369 foreach ( array( 'high', 'core', 'default', 'low' ) as $priority ) {
1370 if ( ! isset( $wp_meta_boxes[ $page ][ $context ][ $priority ] ) ) {
1371 continue;
1372 }
1373
1374 foreach ( $wp_meta_boxes[ $page ][ $context ][ $priority ] as $box ) {
1375 if ( false === $box || ! $box['title'] ) {
1376 continue;
1377 }
1378
1379 ++$i;
1380 $icon_id = array_key_exists( $box['id'], $icon_ids ) ? $icon_ids[ $box['id'] ] : 'frm-' . $box['id'];
1381 $open_class = '';
1382
1383 if ( ! $first_open ) {
1384 $first_open = true;
1385 $open_class = 'open';
1386 }
1387
1388 $accordion_content_id = 'frm_style_section_' . $box['id'];
1389 ?>
1390 <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 ?>
1391 <h3 class="accordion-section-title hndle">
1392 <?php
1393 FrmAppHelper::icon_by_class( 'frmfont ' . $icon_id . ' frm_svg24' );
1394 echo esc_html( $box['title'] );
1395 ?>
1396 <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 ?>
1397 <?php FrmAppHelper::icon_by_class( 'frmfont frm_arrowdown8_icon' ); ?>
1398 </button>
1399 </h3>
1400 <div class="accordion-section-content <?php postbox_classes( $box['id'], $page ); ?>" id="<?php echo esc_attr( $accordion_content_id ); ?>">
1401 <div class="inside">
1402 <?php call_user_func( $box['callback'], $data_object, $box ); ?>
1403 </div><!-- .inside -->
1404 </div><!-- .accordion-section-content -->
1405 </li><!-- .accordion-section -->
1406 <?php
1407 }//end foreach
1408 }//end foreach
1409 }//end if
1410 ?>
1411 </ul><!-- .outer-border -->
1412 </div><!-- .accordion-container -->
1413 <?php
1414 // phpcs:enable Generic.WhiteSpace.ScopeIndent
1415 return $i;
1416 }
1417
1418 /**
1419 * Rename a style via an AJAX action.
1420 *
1421 * @since 6.0
1422 *
1423 * @return void
1424 */
1425 public static function rename_style() {
1426 $permission_error = FrmAppHelper::permission_nonce_error( 'frm_edit_forms', 'nonce', 'frm_ajax' );
1427
1428 if ( $permission_error !== false ) {
1429 $data = array(
1430 'message' => __( 'Unable to rename style', 'formidable' ),
1431 );
1432 wp_send_json_error( $data, 403 );
1433 die();
1434 }
1435
1436 $style_id = FrmAppHelper::get_post_param( 'style_id', 0, 'absint' );
1437 $style_name = FrmAppHelper::get_post_param( 'style_name', '', 'sanitize_text_field' );
1438
1439 if ( ! $style_id || ! $style_name ) {
1440 $data = array(
1441 'message' => __( 'Invalid route', 'formidable' ),
1442 );
1443 wp_send_json_error( $data, 400 );
1444 die();
1445 }
1446
1447 $post = get_post( $style_id );
1448
1449 if ( ! $post || $post->post_type !== self::$post_type ) {
1450 $data = array(
1451 'message' => __( 'The style you are renaming either does not exist or it is not a style', 'formidable' ),
1452 );
1453 wp_send_json_error( $data, 404 );
1454 die();
1455 }
1456
1457 global $wpdb;
1458 $wpdb->update( $wpdb->posts, array( 'post_title' => $style_name ), array( 'ID' => $post->ID ) );
1459
1460 $data = array();
1461 wp_send_json_success( $data );
1462 }
1463
1464 /**
1465 * Prevent the WordPress edit.css file from loading on the visual styler page.
1466 * This way .form-field elements do not have border styles applied to them.
1467 *
1468 * @since 6.0
1469 *
1470 * @param WP_Styles $styles
1471 *
1472 * @return void
1473 */
1474 public static function disable_conflicting_wp_admin_css( $styles ) {
1475 if ( ! FrmAppHelper::is_style_editor_page() ) {
1476 return;
1477 }
1478
1479 FrmStylesPreviewHelper::disable_conflicting_wp_admin_css( $styles );
1480 }
1481
1482 /**
1483 * @deprecated 6.1 Saving custom CSS has been moved into Global Settings.
1484 *
1485 * @return void
1486 */
1487 public static function save_css() {
1488 _deprecated_function( __METHOD__, '6.1' );
1489 }
1490 }
1491