<?php if ( ! defined( 'ABSPATH' ) ) { die( 'You are not allowed to call this page directly.' ); } class FrmStylesController { /** * @var string */ public static $post_type = 'frm_styles'; /** * @var string */ public static $screen = 'formidable_page_formidable-styles'; /** * @var string|null */ private static $message; /** * @return void */ public static function load_pro_hooks() { if ( FrmAppHelper::pro_is_installed() ) { FrmProStylesController::load_pro_hooks(); } } /** * @return void */ public static function register_post_types() { register_post_type( self::$post_type, array( 'label' => __( 'Styles', 'formidable' ), 'public' => false, 'show_ui' => false, 'capability_type' => 'page', 'capabilities' => array( 'edit_post' => 'frm_change_settings', 'edit_posts' => 'frm_change_settings', 'edit_others_posts' => 'frm_change_settings', 'publish_posts' => 'frm_change_settings', 'delete_post' => 'frm_change_settings', 'delete_posts' => 'frm_change_settings', 'read_private_posts' => 'read_private_posts', ), 'supports' => array( 'title', ), 'has_archive' => false, 'labels' => array( 'name' => __( 'Styles', 'formidable' ), 'singular_name' => __( 'Style', 'formidable' ), 'menu_name' => __( 'Style', 'formidable' ), 'edit' => __( 'Edit', 'formidable' ), 'add_new_item' => __( 'Create a New Style', 'formidable' ), 'edit_item' => __( 'Edit Style', 'formidable' ), ), ) ); } /** * Add two links for the visual styler. * There's a "Styles" submenu in the Formidable menu. * There's a second alternative "Forms" submenu in the Appearance menu. * This submenu links to a page to edit a form with the default style. * * @return void */ public static function menu() { add_submenu_page( 'formidable', 'Formidable | ' . __( 'Styles', 'formidable' ), __( 'Styles', 'formidable' ), 'frm_change_settings', 'formidable-styles', 'FrmStylesController::route' ); // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong add_submenu_page( 'themes.php', 'Formidable | ' . __( 'Styles', 'formidable' ), __( 'Forms', 'formidable' ), 'frm_change_settings', 'formidable-styles2', 'FrmStylesController::route' ); // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong } /** * Remove filters for the visual styler preview. * This triggers earlier than self::admin_init which is called too late. * * @return void */ public static function plugins_loaded() { if ( ! FrmAppHelper::is_style_editor_page() ) { return; } self::disable_form_css(); self::prevent_form_scripts_from_loading(); } /** * Avoid loading CSS the normal way with the preview in the styler. * It gets loaded instead with the frmpro_css action set to the #frm-custom-theme-css element. * * @since 6.0 * * @return void */ private static function disable_form_css() { add_filter( 'get_frm_stylesheet', '__return_empty_array' ); } /** * Removing this action prevents front end JavaScript from loading. * * @since 6.0 * * @return void */ private static function prevent_form_scripts_from_loading() { remove_action( 'init', 'FrmFormsController::front_head' ); } /** * @return void */ public static function admin_init() { self::maybe_hook_into_global_settings_save(); if ( ! FrmAppHelper::is_style_editor_page() ) { return; } FrmStyleComponent::register_assets(); self::load_pro_hooks(); $version = FrmAppHelper::plugin_version(); if ( FrmAppHelper::is_style_editor_page( 'edit' ) ) { wp_enqueue_style( 'wp-color-picker' ); } wp_enqueue_style( 'frm-custom-theme', admin_url( 'admin-ajax.php?action=frmpro_css' ), array(), $version ); $style = apply_filters( 'frm_style_head', false ); if ( $style ) { 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 } } /** * @since 6.0 * * @return void */ private static function maybe_hook_into_global_settings_save() { if ( empty( $_POST ) || ! isset( $_POST['style'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing // Avoid changing any style data if the style array is not sent in the request. return; } add_action( 'frm_update_settings', /** * Update the form data on the "Manage Styles" tab after global settings are saved. */ function () { self::manage_styles(); } ); } /** * @param string $register Either 'enqueue' or 'register'. * @param bool $force True to enqueue/register the style if a form has not been loaded. * * @return void */ public static function enqueue_css( $register = 'enqueue', $force = false ) { global $frm_vars; $register_css = $register === 'register'; $should_load = $force || ( ( $frm_vars['load_css'] || $register_css ) && ! FrmAppHelper::is_admin() ); if ( ! $should_load ) { return; } $frm_settings = FrmAppHelper::get_settings(); if ( $frm_settings->load_style === 'none' ) { return; } $css = apply_filters( 'get_frm_stylesheet', self::custom_stylesheet() ); if ( $css ) { $css = (array) $css; $version = FrmAppHelper::plugin_version(); foreach ( $css as $css_key => $file ) { if ( $register_css ) { $this_version = self::get_css_version( $css_key, $version ); wp_register_style( $css_key, $file, array(), $this_version ); } $load_on_all = ! FrmAppHelper::is_admin() && 'all' === $frm_settings->load_style; if ( $load_on_all || $register !== 'register' ) { wp_enqueue_style( $css_key ); } unset( $css_key, $file ); } if ( $frm_settings->load_style === 'all' ) { $frm_vars['css_loaded'] = true; } }//end if unset( $css ); add_filter( 'style_loader_tag', 'FrmStylesController::add_tags_to_css', 10, 2 ); } /** * @return array<string,string> */ public static function custom_stylesheet() { global $frm_vars; $stylesheet_urls = array(); if ( empty( $frm_vars['css_loaded'] ) ) { // Include css in head. self::get_url_to_custom_style( $stylesheet_urls ); } return $stylesheet_urls; } /** * @param array $stylesheet_urls * * @return void */ private static function get_url_to_custom_style( &$stylesheet_urls ) { $file_name = '/css/' . self::get_file_name(); if ( is_readable( FrmAppHelper::plugin_path() . $file_name ) ) { $url = FrmAppHelper::plugin_url() . $file_name; } else { $url = admin_url( 'admin-ajax.php?action=frmpro_css' ); } $stylesheet_urls['formidable'] = $url; } /** * Use a different stylesheet per site in a multisite install * * @since 3.0.03 * * @return string */ public static function get_file_name() { if ( is_multisite() ) { $blog_id = get_current_blog_id(); return 'formidableforms' . absint( $blog_id ) . '.css'; } return 'formidableforms.css'; } /** * @param string $css_key * @param string $version * * @return string */ private static function get_css_version( $css_key, $version ) { if ( 'formidable' === $css_key ) { $this_version = get_option( 'frm_last_style_update' ); if ( ! $this_version ) { $this_version = $version; } } else { $this_version = $version; } return $this_version; } /** * @param string $tag * @param string $handle * * @return string */ public static function add_tags_to_css( $tag, $handle ) { if ( ( 'formidable' === $handle || 'jquery-theme' === $handle ) && ! str_contains( $tag, ' property=' ) ) { $tag = str_replace( ' type="', ' property="stylesheet" type="', $tag ); } return $tag; } /** * Route the edit route to the style function. * * @since 6.0 this function no longer has any parameter values. * * @return void */ public static function edit() { self::load_styler(); } /** * 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. * * @return void */ public static function new_style() { self::load_styler(); } /** * 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. * * @return void */ public static function duplicate() { self::load_styler(); } /** * Render the style page for a form for assigning a style to a form, and for updating a target style. * * @since 6.0 this function no longer takes parameters. * * @return void */ public static function load_styler() { if ( 'assign_style' === FrmAppHelper::get_post_param( 'frm_action' ) ) { self::save_form_style(); } self::setup_styles_and_scripts_for_styler(); $style_id = self::get_style_id_for_styler(); if ( ! $style_id ) { $error_args = array( 'title' => __( 'No styles', 'formidable' ), 'body' => __( 'You must have a style to use the Visual Styler.', 'formidable' ), 'cancel_url' => admin_url( 'admin.php?page=formidable' ), ); FrmAppController::show_error_modal( $error_args ); return; } $form_id = FrmAppHelper::simple_get( 'form', 'absint', 0 ); if ( ! $form_id ) { $form_id = self::get_form_id_for_style( $style_id ); } $form = FrmForm::getOne( $form_id ); if ( ! is_object( $form ) ) { $error_args = array( 'title' => __( 'No forms', 'formidable' ), 'body' => __( 'You must have a form to use the Visual Styler.', 'formidable' ), 'cancel_url' => admin_url( 'admin.php?page=formidable' ), ); FrmAppController::show_error_modal( $error_args ); return; } $frm_style = new FrmStyle( $style_id ); $active_style = $frm_style->get_one(); $default_style = self::get_default_style(); self::disable_admin_page_styling_on_submit_buttons(); /** * @since 6.0 * * @param array{form:\stdClass} $data */ do_action( 'frm_before_render_style_page', compact( 'form' ) ); self::render_style_page( $active_style, $form, $default_style ); } /** * @since 6.0 * * @return int */ private static function get_style_id_for_styler() { $action = FrmAppHelper::simple_get( 'frm_action' ); if ( 'duplicate' === $action ) { // The duplicate action uses style_id instead of id for better backward compatibility. return FrmAppHelper::simple_get( 'style_id', 'absint', 0 ); } $style_id = FrmAppHelper::simple_get( 'id', 'absint', 0 ); if ( $style_id ) { // Always use the style ID from the URL if one is specified. return $style_id; } $request_form_id = FrmAppHelper::simple_get( 'form', 'absint', 0 ); if ( $request_form_id && is_callable( 'FrmProStylesController::get_active_style_for_form' ) ) { return FrmProStylesController::get_active_style_for_form( $request_form_id )->ID; } return self::get_default_style()->ID; } /** * If a form ID is not being passed in the URL, try to get the best match. * * @since 6.0 * * @param int $style_id * * @return int */ private static function get_form_id_for_style( $style_id ) { $check = serialize( array( 'custom_style' => (string) $style_id ) ); $check = substr( $check, 5, -1 ); $form_id = FrmDb::get_var( 'frm_forms', array( 'options LIKE' => $check, 'status' => 'published', ) ); if ( ! $form_id ) { // Fallback to any form. $where = array( 'status' => 'published', // Make sure it's not a repeater. 'parent_form_id' => array( null, 0 ), ); $form_id = FrmDb::get_var( 'frm_forms', $where, 'id' ); } return $form_id; } /** * Add a frm_no_style_button class to all buttons to avoid some style rules like border-radius: 30px. * * @return void */ private static function disable_admin_page_styling_on_submit_buttons() { add_filter( 'frm_submit_button_class', function ( $classes ) { $classes[] = 'frm_no_style_button'; return $classes; } ); } /** * @since 6.0 * * @return WP_Post */ private static function get_default_style() { $frm_style = new FrmStyle( 'default' ); return $frm_style->get_one(); } /** * Save style for form (from Styler list page) via a POST action. * * @since 6.0 * * @return void */ private static function save_form_style() { $permission_error = FrmAppHelper::permission_nonce_error( 'frm_edit_forms', 'frm_save_form_style', 'frm_save_form_style_nonce' ); if ( $permission_error !== false ) { wp_die( 'Unable to save form', '', 403 ); } $style_id = FrmAppHelper::get_post_param( 'style_id', 0, 'absint' ); if ( $style_id && ! self::confirm_style_exists_before_setting( $style_id ) ) { wp_die( esc_html__( 'Invalid target style', 'formidable' ), esc_html__( 'Invalid target style', 'formidable' ), 400 ); return; } /** * Hook into the saved style ID so Pro can import a style template by its key and return a new style ID. * * @since 6.0 * * @param int $style_id */ $style_id = apply_filters( 'frm_saved_form_style_id', $style_id ); if ( ! $style_id && '0' !== FrmAppHelper::get_post_param( 'style_id', '', 'sanitize_text_field' ) ) { // "0" is a special value used for the enable/disable toggle. wp_die( esc_html__( 'Invalid style value', 'formidable' ), esc_html__( 'Invalid style value', 'formidable' ), 400 ); return; } $form_id = FrmAppHelper::get_post_param( 'form_id', 0, 'absint' ); if ( ! $form_id ) { wp_die( esc_html__( 'No form specified', 'formidable' ), esc_html__( 'No form specified', 'formidable' ), 400 ); return; } $form = FrmForm::getOne( $form_id ); if ( ! $form ) { wp_die( esc_html__( 'Form does not exist', 'formidable' ), esc_html__( 'Form does not exist', 'formidable' ), 400 ); return; } $previous_style_id = $form->options['custom_style']; // If the default style is selected, use the "Always use default" legacy option instead of the default style. // There's also a check here for conversational forms. // Without the check it isn't possible to select "Default" because "Always use default" will convert to "Lines" dynamically. $default_style = self::get_default_style(); if ( $style_id === $default_style->ID && empty( $form->options['chat'] ) ) { $style_id = 1; } // We want to save a string for consistency. FrmStylesHelper::get_form_count_for_style expects the custom style ID is a string. $form->options['custom_style'] = (string) $style_id; if ( $previous_style_id === $form->options['custom_style'] ) { // Exit early before updating DB if nothing actually changed. self::$message = __( 'Successfully updated style.', 'formidable' ); return; } global $wpdb; $wpdb->update( $wpdb->prefix . 'frm_forms', array( 'options' => maybe_serialize( $form->options ) ), array( 'id' => $form->id ) ); FrmForm::clear_form_cache(); /** * @since 6.25.1 * * @param int $form_id * @param int $style_id */ do_action( 'frm_after_changed_form_style', absint( $form_id ), absint( $style_id ) ); self::$message = __( 'Successfully updated style.', 'formidable' ); } /** * Validate that we're assigning a form to a style that actually exists before assigning it to a form. * * @param int $style_id * * @return bool True if the style actually exists. */ private static function confirm_style_exists_before_setting( $style_id ) { global $wpdb; $post_type = FrmDb::get_var( $wpdb->posts, array( 'ID' => $style_id ), 'post_type' ); return self::$post_type === $post_type; } /** * Register and enqueue styles and scripts for the style tab page. * * @since 6.0 * * @return void */ private static function setup_styles_and_scripts_for_styler() { $plugin_url = FrmAppHelper::plugin_url(); $version = FrmAppHelper::plugin_version(); $js_dependencies = array( 'wp-i18n', 'wp-hooks', 'formidable_dom' ); if ( FrmAppHelper::pro_is_installed() && is_callable( 'FrmProAppHelper::use_jquery_datepicker' ) && FrmProAppHelper::use_jquery_datepicker() ) { $js_dependencies[] = 'jquery-ui-datepicker'; } wp_register_script( 'formidable_style', $plugin_url . '/js/admin/style.js', $js_dependencies, $version ); wp_register_style( 'formidable_style', $plugin_url . '/css/admin/style.css', array(), $version ); wp_print_styles( 'formidable_style' ); wp_print_styles( 'formidable' ); wp_enqueue_script( 'formidable_style' ); wp_set_script_translations( 'formidable_style', 'formidable' ); } /** * Render the style page (with a more limited and typed scope than calling it from self::style directly). * * @since 6.0 * * @param stdClass|WP_Post $active_style * @param stdClass $form * @param WP_Post $default_style * * @return void */ private static function render_style_page( $active_style, $form, $default_style ) { $style_views_path = self::get_views_path(); // Edit, list (default), new_style. $view = FrmAppHelper::simple_get( 'frm_action', 'sanitize_text_field', 'list' ); $frm_style = new FrmStyle( $active_style->ID ); if ( 'new_style' !== $view && ! FrmAppHelper::simple_get( 'form' ) && ! FrmAppHelper::simple_get( 'style_id' ) ) { // Have the Appearance > Forms link fallback to the edit view. Otherwise we want to use 'list' as the default. $view = 'edit'; } if ( in_array( $view, array( 'edit', 'new_style', 'duplicate' ), true ) ) { self::add_meta_boxes(); } switch ( $view ) { case 'edit': $style = $active_style; break; case 'duplicate': $style = clone $active_style; $new_style = $frm_style->get_new(); $style->ID = $new_style->ID; $style->post_name = $new_style->post_name; unset( $new_style ); break; case 'new_style': $style = $frm_style->get_new(); break; } if ( in_array( $view, array( 'duplicate', 'new_style' ), true ) ) { $style->post_title = FrmAppHelper::simple_get( 'style_name' ); $style->menu_order = 0; } if ( ! isset( $style ) ) { $style = $active_style; } self::force_form_style( $style ); if ( isset( self::$message ) ) { $message = self::$message; } $preview_helper = new FrmStylesPreviewHelper( $form->id ); $preview_helper->adjust_form_for_preview(); // Get form HTML before displaying warnings and notes so we can check global $frm_vars data without adding extra database calls. $target_form_preview_html = $preview_helper->get_html_for_form_preview(); $warnings = $preview_helper->get_warnings_for_styler_preview( $style, $default_style, $view ); $notes = $preview_helper->get_notes_for_styler_preview(); include $style_views_path . 'show.php'; } /** * @since 6.0 * * @return string */ private static function get_views_path() { return FrmAppHelper::plugin_path() . '/classes/views/styles/'; } /** * Filter form classes so the form uses the preview style, not the form's active style. * * @since 6.0 * * @param stdClass|WP_Post $style A new style is not a WP_Post object. * * @return void */ private static function force_form_style( $style ) { add_filter( 'frm_add_form_style_class', function ( $class ) use ( $style ) { $split = array_filter( explode( ' ', $class ), /** * @param string $class */ function ( $class ) { return $class && ! str_starts_with( $class, 'frm_style_' ); } ); $split[] = 'frm_style_' . $style->post_name; return implode( ' ', $split ); } ); } /** * Save style post object via a POST request submitted from the Visual styler "edit" page. * * @since 6.0 * * @return void */ public static function save_style() { $frm_style = new FrmStyle(); $post_id = FrmAppHelper::get_post_param( 'ID', false, 'sanitize_title' ); $style_nonce = FrmAppHelper::get_post_param( 'frm_style', '', 'sanitize_text_field' ); if ( $post_id === false || ! wp_verify_nonce( $style_nonce, 'frm_style_nonce' ) ) { // Exit early if the request isn't valid. // Since we're not dying, it should just reload the visual styler without any message. return; } $id = $frm_style->update( $post_id ); if ( ! $post_id && $id ) { self::maybe_redirect_after_save( $id ); // Set the post id to the new style so it will be loaded for editing. $post_id = reset( $id ); } /** * @since 6.25.1 * * @param int $post_id */ do_action( 'frm_after_saved_style', absint( $post_id ) ); self::$message = __( 'Your styling settings have been saved.', 'formidable' ); } /** * Show the edit view after saving. * The save event is triggered earlier, on admin init where self::save_style is called. * This happens earlier because there is a possible redirect (to adjust the URL for a new or duplicated style). * * @return void */ public static function save() { self::edit(); } /** * Force a redirect after duplicating or creating a new style to avoid an old stale URL that could result in more styles than intended. * * @since 6.0 * * @param array $ids * * @return void */ private static function maybe_redirect_after_save( $ids ) { $referer = FrmAppHelper::get_server_value( 'HTTP_REFERER' ); $parsed = parse_url( $referer ); $query = $parsed['query']; $current_action = false; $actions_to_redirect = array( 'duplicate', 'new_style' ); foreach ( $actions_to_redirect as $action ) { if ( str_contains( $query, 'frm_action=' . $action ) ) { $current_action = $action; break; } } if ( false === $current_action ) { // Do not redirect as the referer URL did not match $actions_to_redirect. return; } parse_str( $query, $parsed_query ); $form_id = ! empty( $parsed_query['form'] ) ? absint( $parsed_query['form'] ) : 0; $style = new stdClass(); $style->ID = end( $ids ); wp_safe_redirect( esc_url_raw( FrmStylesHelper::get_edit_url( $style, $form_id ) ) ); die(); } /** * @since 6.0 * * @param string $message * @param array|object $forms * * @return void */ public static function manage( $message = '', $forms = array() ) { $frm_style = new FrmStyle(); $styles = $frm_style->get_all(); $default_style = $frm_style->get_default_style( $styles ); $frm_settings = FrmAppHelper::get_settings(); if ( ! $forms ) { $forms = FrmForm::get_published_forms(); } include FrmAppHelper::plugin_path() . '/classes/views/styles/manage.php'; } /** * Handle saving for the page rendered in self::manage which is included in Global Settings in the "Manage Styles" tab. * This gets called from the frm_update_settings hook which is called after saving Global settings. * * @return void */ private static function manage_styles() { global $wpdb; $forms = FrmForm::get_published_forms(); foreach ( $forms as $form ) { if ( ! isset( $_POST['style'] ) || ! isset( $_POST['style'][ $form->id ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing continue; } $new_style = sanitize_text_field( wp_unslash( $_POST['style'][ $form->id ] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing $form->options['custom_style'] = $new_style; $wpdb->update( $wpdb->prefix . 'frm_forms', array( 'options' => maybe_serialize( $form->options ) ), array( 'id' => $form->id ) ); unset( $form ); } } /** * Echo content for the Custom CSS page. * * @param string $message * @param array $extra_args An array of extra arguments. * * @return void */ public static function custom_css( $message = '', $extra_args = array() ) { $id = $extra_args['id'] ?? 'frm_codemirror_box'; $settings = self::enqueue_codemirror( $id, $extra_args['placeholder'] ?? '' ); $id = $settings ? $id : 'frm_custom_css_box'; $show_errors = $extra_args['show_errors'] ?? true; $custom_css = $extra_args['custom_css'] ?? self::get_custom_css(); $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 $textarea_params = ! empty( $extra_args['textarea_params'] ) ? $extra_args['textarea_params'] : array( 'name' => 'frm_custom_css', 'id' => $id, ); if ( $settings ) { $textarea_params['class'] = 'hide-if-js'; } include FrmAppHelper::plugin_path() . '/classes/views/styles/custom_css.php'; } /** * Get custom CSS code entered in the Custom CSS page. * * @since 6.0 * * @param array|null $single_style_settings The single style settings. * * @return string */ public static function get_custom_css( $single_style_settings = null ) { if ( $single_style_settings ) { // If the single style settings are passed, return the custom CSS from the single style settings. if ( ! empty( $single_style_settings['single_style_custom_css'] ) && ! empty( $single_style_settings['enable_style_custom_css'] ) ) { return $single_style_settings['single_style_custom_css']; } return ''; } $settings = FrmAppHelper::get_settings(); if ( is_string( $settings->custom_css ) ) { return $settings->custom_css; } // If it does not exist, check the default style as a fallback. $frm_style = new FrmStyle(); $style = $frm_style->get_default_style(); return $style->post_content['custom_css']; } /** * Enqueue assets for codemirror, built into WordPress since 4.9. * The Custom CSS page uses codemirror. * * @since 6.0 Previously this code was embedded in self::custom_css. * * @param string $id The ID of the codemirror box. * @param string $placeholder The placeholder text for the codemirror box. * * @return array|false */ private static function enqueue_codemirror( $id = 'frm_codemirror_box', $placeholder = '' ) { if ( ! function_exists( 'wp_enqueue_code_editor' ) ) { // The WordPress version is likely older than 4.9. return false; } $settings = wp_enqueue_code_editor( array( 'type' => 'text/css', 'codemirror' => array( 'indentUnit' => 2, 'tabSize' => 2, // As the codemirror box only appears once you click into the Custom CSS tab, we need to auto-refresh. // Otherwise the line numbers all end up with a 1px width causing overlap issues with the text in the content. 'autoRefresh' => true, 'placeholder' => $placeholder, ), ) ); if ( $settings ) { wp_add_inline_script( 'code-editor', sprintf( 'jQuery( function() { window.%s_wp_editor = wp.codeEditor.initialize( \'%s\', %s ); } );', $id, $id, wp_json_encode( $settings ) ) ); } return $settings; } /** * Handling routing for the visual styler. * * @return void */ public static function route() { $action = FrmAppHelper::get_param( 'frm_action', '', 'get', 'sanitize_title' ); FrmAppHelper::include_svg(); switch ( $action ) { case 'edit': case 'save': self::$action(); return; default: do_action( 'frm_style_action_route', $action ); if ( apply_filters( 'frm_style_stop_action_route', false, $action ) ) { return; } if ( in_array( $action, array( 'new_style', 'duplicate' ), true ) ) { self::$action(); return; } self::edit(); return; } } /** * Handle AJAX routing for frm_settings_reset for resetting styles to the default settings. * From the edit view, it will return default styles and not actually update the style. * 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. * * @since 6.0 When a style_id is passed to this action, the style will actually be reset. * * @return void */ public static function reset_styling() { FrmAppHelper::permission_check( 'frm_change_settings' ); check_ajax_referer( 'frm_ajax', 'nonce' ); $style_id = FrmAppHelper::get_post_param( 'style_id', '', 'absint' ); if ( ! $style_id ) { // A style ID is not sent when resetting on the edit page. // Instead of resetting the style, send the defaults back so the inputs can be updated with JavaScript. $frm_style = new FrmStyle(); $defaults = $frm_style->get_defaults(); echo json_encode( $defaults ); wp_die(); } $frm_style = new FrmStyle(); $default_template_style = $frm_style->get_default_template_style( $style_id ); $where = array( 'ID' => $style_id, 'post_type' => self::$post_type, ); $style_object = $frm_style->get_new(); $style_object->post_content = json_decode( $default_template_style, true ); global $wpdb; $wpdb->update( $wpdb->posts, array( 'post_content' => $default_template_style ), $where ); // Save the settings after resetting to default or the old style will still appear. $frm_style->save_settings(); $data = array( 'style' => FrmStylesCardHelper::get_style_param_for_card( $style_object ), ); wp_send_json_success( $data ); wp_die(); } /** * Handle routing for the frm_change_styling AJAX action. * This doesn't actually change styling. It just handles the events when someone changes a style. * It responds with the new CSS required for the updated styler preview in the edit page. * * @return void */ public static function change_styling() { FrmAppHelper::permission_check( 'frm_change_settings' ); check_ajax_referer( 'frm_ajax', 'nonce' ); $is_loaded_via_ajax = true; $frm_style = new FrmStyle(); // Intentionally avoid defaults here so nothing gets removed from our style. $defaults = array(); $style = ''; echo '<style type="text/css">'; include FrmAppHelper::plugin_path() . '/css/_single_theme.css.php'; echo '</style>'; wp_die(); } /** * @return void */ public static function add_meta_boxes() { // setup meta boxes $meta_boxes = array( 'general' => __( 'General', 'formidable' ), 'form-title' => __( 'Form Title', 'formidable' ), 'form-description' => __( 'Form Description', 'formidable' ), 'field-labels' => __( 'Field Labels', 'formidable' ), 'field-description' => __( 'Field Description', 'formidable' ), 'field-colors' => __( 'Field Colors', 'formidable' ), 'field-sizes' => __( 'Field Settings', 'formidable' ), 'check-box-radio-fields' => __( 'Check Box & Radio Fields', 'formidable' ), 'buttons' => __( 'Buttons', 'formidable' ), 'form-messages' => __( 'Form Messages', 'formidable' ), ); /** * Add custom boxes to the styling settings * * @since 2.3 * * @param array $meta_boxes */ $meta_boxes = apply_filters( 'frm_style_boxes', $meta_boxes ); foreach ( $meta_boxes as $nicename => $name ) { add_meta_box( $nicename . '-style', $name, 'FrmStylesController::include_style_section', self::$screen, 'side', 'default', $nicename ); unset( $nicename, $name ); } } /** * @param array $atts * @param array $sec * * @return void */ public static function include_style_section( $atts, $sec ) { extract( $atts ); // phpcs:ignore WordPress.PHP.DontExtract $style = $atts['style']; FrmStylesHelper::prepare_color_output( $style->post_content, false ); $current_tab = FrmAppHelper::simple_get( 'page-tab', 'sanitize_title', 'default' ); $file_name = FrmAppHelper::plugin_path() . '/classes/views/styles/_' . $sec['args'] . '.php'; /** * Set the location of custom styling settings right before * loading onto the page. If your style box was named "progress", * this hook name will be frm_style_settings_progress. * * @since 2.3 */ $file_name = apply_filters( 'frm_style_settings_' . $sec['args'], $file_name ); echo '<div class="frm_grid_container">'; include $file_name; echo '</div>'; } public static function load_css() { header( 'Content-type: text/css' ); $frm_style = new FrmStyle(); $defaults = $frm_style->get_defaults(); $style = ''; include FrmAppHelper::plugin_path() . '/css/_single_theme.css.php'; wp_die(); } /** * @return void */ public static function load_saved_css() { $css = get_transient( 'frmpro_css' ); ob_start(); include FrmAppHelper::plugin_path() . '/css/custom_theme.css.php'; $output = ob_get_clean(); $output = self::replace_relative_url( $output ); /** * The API needs to load font icons through a custom URL. * * @since 5.2 * * @param string $output */ $output = apply_filters( 'frm_saved_css', $output ); echo $output; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped self::maybe_hide_sample_form_error_message(); wp_die(); } /** * Add an extra style rule to hide a broken style warning. * To avoid cluttering the front end with any unnecessary styles this is only added when the referer URL matches the styler. * * @since 6.2.3 * * @return void */ public static function maybe_hide_sample_form_error_message() { $referer = FrmAppHelper::get_server_value( 'HTTP_REFERER' ); if ( str_contains( $referer, 'admin.php?page=formidable-styles' ) ) { echo '#frm_broken_styles_warning { display: none; }'; } } /** * Replaces relative URL with absolute URL. * * @since 4.11.03 * * @param string $css CSS content. * * @return string */ public static function replace_relative_url( $css ) { $plugin_url = trailingslashit( FrmAppHelper::plugin_url() ); return str_replace( array( 'url(../', "url('../", 'url("../', ), array( 'url(' . $plugin_url, "url('" . $plugin_url, 'url("' . $plugin_url, ), $css ); } /** * Check if the Formidable styling should be loaded, * then enqueue it for the footer * * @since 2.0 * * @return void */ public static function enqueue_style() { global $frm_vars; if ( ! empty( $frm_vars['css_loaded'] ) ) { // The CSS has already been loaded. return; } $frm_settings = FrmAppHelper::get_settings(); if ( $frm_settings->load_style !== 'none' ) { wp_enqueue_style( 'formidable' ); $frm_vars['css_loaded'] = true; } } /** * Get the stylesheets for the form settings page * * @return array<WP_Post> */ public static function get_style_opts() { $frm_style = new FrmStyle(); return $frm_style->get_all(); } /** * Get the style post object for a target form. * * @param bool|object|string $form * * @return WP_Post|null */ public static function get_form_style( $form = 'default' ) { $style = FrmFormsHelper::get_form_style( $form ); // phpcs:ignore Universal.Operators.StrictComparisons if ( ! $style || 1 == $style ) { $style = 'default'; } $frm_style = new FrmStyle( $style ); return $frm_style->get_one(); } /** * @param string $class * @param string $style * * @return string */ public static function get_form_style_class( $class, $style ) { // phpcs:ignore Universal.Operators.StrictComparisons if ( 1 == $style ) { $style = 'default'; } $frm_style = new FrmStyle( $style ); $style = $frm_style->get_one(); if ( $style ) { $class .= ' frm_style_' . $style->post_name; self::maybe_add_rtl_class( $style, $class ); } return $class; } /** * @since 3.0 * * @param object $style * @param string $class * * @return void */ private static function maybe_add_rtl_class( $style, &$class ) { $is_rtl = isset( $style->post_content['direction'] ) && 'rtl' === $style->post_content['direction']; if ( $is_rtl ) { $class .= ' frm_rtl'; } } /** * @param string $val Style setting key. * @param int|string $form Form ID or 'default'. * * @return mixed */ public static function get_style_val( $val, $form = 'default' ) { $style = self::get_form_style( $form ); if ( $style && isset( $style->post_content[ $val ] ) ) { return $style->post_content[ $val ]; } return null; } /** * @param array $default_styles * * @return array */ public static function show_entry_styles( $default_styles ) { $frm_style = new FrmStyle( 'default' ); $style = $frm_style->get_one(); if ( ! $style ) { return $default_styles; } foreach ( $default_styles as $name => $val ) { $setting = $name; if ( 'border_width' === $name ) { $setting = 'field_border_width'; } elseif ( 'alt_bg_color' === $name ) { $setting = 'bg_color_active'; } $default_styles[ $name ] = $style->post_content[ $setting ]; unset( $name, $val ); } return $default_styles; } /** * @param string $important * @param array $field * * @return string */ public static function &important_style( $important, $field ) { $important = self::get_style_val( 'important_style', $field['form_id'] ); return $important; } /** * Duplicate of WordPress do_accordion_section function, it adds an additional svg icon support. * * @since 6.8.3 * * @param string|WP_Screen $screen Screen ID or screen object. * @param string $context Meta box context. * @param mixed $data_object Data object passed to callbacks. * * @return int */ public static function do_accordion_sections( $screen, $context, $data_object ) { // phpcs:ignore SlevomatCodingStandard.Complexity.Cognitive.ComplexityTooHigh global $wp_meta_boxes; // the symbol id from icons.svg $icon_ids = array( 'ranking-fields-style' => 'frm_chart_bar_icon', 'section-fields-style' => 'frm-form-title-style', ); wp_enqueue_script( 'accordion' ); if ( ! $screen ) { $screen = get_current_screen(); } elseif ( is_string( $screen ) ) { $screen = convert_to_screen( $screen ); } $page = $screen->id; ?> <div id="side-sortables" class="accordion-container"> <ul class="outer-border"> <?php $i = 0; $first_open = false; if ( isset( $wp_meta_boxes[ $page ][ $context ] ) ) { foreach ( array( 'high', 'core', 'default', 'low' ) as $priority ) { if ( isset( $wp_meta_boxes[ $page ][ $context ][ $priority ] ) ) { foreach ( $wp_meta_boxes[ $page ][ $context ][ $priority ] as $box ) { if ( false === $box || ! $box['title'] ) { continue; } ++$i; $icon_id = array_key_exists( $box['id'], $icon_ids ) ? $icon_ids[ $box['id'] ] : 'frm-' . $box['id']; $open_class = ''; if ( ! $first_open ) { $first_open = true; $open_class = 'open'; } $accordion_content_id = 'frm_style_section_' . $box['id']; ?> <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 ?> <h3 class="accordion-section-title hndle"> <?php FrmAppHelper::icon_by_class( 'frmfont ' . $icon_id . ' frm_svg24' ); echo esc_html( $box['title'] ); ?> <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 ?> <?php FrmAppHelper::icon_by_class( 'frmfont frm_arrowdown8_icon' ); ?> </button> </h3> <div class="accordion-section-content <?php postbox_classes( $box['id'], $page ); ?>" id="<?php echo esc_attr( $accordion_content_id ); ?>"> <div class="inside"> <?php call_user_func( $box['callback'], $data_object, $box ); ?> </div><!-- .inside --> </div><!-- .accordion-section-content --> </li><!-- .accordion-section --> <?php }//end foreach }//end if }//end foreach }//end if ?> </ul><!-- .outer-border --> </div><!-- .accordion-container --> <?php return $i; } /** * Rename a style via an AJAX action. * * @since 6.0 * * @return void */ public static function rename_style() { $permission_error = FrmAppHelper::permission_nonce_error( 'frm_edit_forms', 'nonce', 'frm_ajax' ); if ( $permission_error !== false ) { $data = array( 'message' => __( 'Unable to rename style', 'formidable' ), ); wp_send_json_error( $data, 403 ); die(); } $style_id = FrmAppHelper::get_post_param( 'style_id', 0, 'absint' ); $style_name = FrmAppHelper::get_post_param( 'style_name', '', 'sanitize_text_field' ); if ( ! $style_id || ! $style_name ) { $data = array( 'message' => __( 'Invalid route', 'formidable' ), ); wp_send_json_error( $data, 400 ); die(); } $post = get_post( $style_id ); if ( ! $post || $post->post_type !== self::$post_type ) { $data = array( 'message' => __( 'The style you are renaming either does not exist or it is not a style', 'formidable' ), ); wp_send_json_error( $data, 404 ); die(); } global $wpdb; $wpdb->update( $wpdb->posts, array( 'post_title' => $style_name ), array( 'ID' => $post->ID ) ); $data = array(); wp_send_json_success( $data ); } /** * Prevent the WordPress edit.css file from loading on the visual styler page. * This way .form-field elements do not have border styles applied to them. * * @since 6.0 * * @param WP_Styles $styles * * @return void */ public static function disable_conflicting_wp_admin_css( $styles ) { if ( ! FrmAppHelper::is_style_editor_page() ) { return; } FrmStylesPreviewHelper::disable_conflicting_wp_admin_css( $styles ); } /** * @deprecated 6.1 Saving custom CSS has been moved into Global Settings. * * @return void */ public static function save_css() { _deprecated_function( __METHOD__, '6.1' ); } }