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

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