gutenberg/block-preview.png', 'block_empty_url' => WPFORMS_PLUGIN_URL . 'assets/images/empty-states/no-forms.svg', 'route_namespace' => RestApi::ROUTE_NAMESPACE, 'wpnonce' => wp_create_nonce( 'wpforms-gutenberg-form-selector' ), 'urls' => [ 'form_url' => admin_url( 'admin.php?page=wpforms-builder&view=fields&form_id={ID}' ), 'entries_url' => admin_url( 'admin.php?view=list&page=wpforms-entries&form_id={ID}' ), ], 'forms' => $this->get_form_list(), 'strings' => $strings, 'isPro' => wpforms()->is_pro(), 'defaults' => self::DEFAULT_ATTRIBUTES, 'is_modern_markup' => $this->render_engine === 'modern', 'is_full_styling' => $this->disable_css_setting === 1, 'wpforms_guide' => esc_url( wpforms_utm_link( 'https://wpforms.com/docs/creating-first-form/', 'gutenberg', 'Create Your First Form Documentation' ) ), 'get_started_url' => esc_url( admin_url( 'admin.php?page=wpforms-builder' ) ), 'sizes' => [ 'field-size' => CSSVars::FIELD_SIZE, 'label-size' => CSSVars::LABEL_SIZE, 'button-size' => CSSVars::BUTTON_SIZE, 'container-shadow-size' => CSSVars::CONTAINER_SHADOW_SIZE, ], ]; } /** * Get the form list. * * @since 1.8.8 * * @return array * @noinspection NullPointerExceptionInspection */ public function get_form_list(): array { $forms = wpforms()->obj( 'form' )->get( '', [ 'order' => 'DESC' ] ); if ( empty( $forms ) ) { return []; } return array_map( static function ( $form ) { $form->post_title = htmlspecialchars_decode( $form->post_title, ENT_QUOTES ); $max_length = 47; $form->post_title = trim( mb_substr( trim( $form->post_title ), 0, $max_length ) ); $form->post_title = mb_strlen( $form->post_title ) === $max_length ? $form->post_title . '…' : $form->post_title; return $form; }, $forms ); } /** * Let's WP know that we have translation strings on our block script. * * @since 1.8.3 * @deprecated 1.8.5 */ public function enable_block_translations() { _deprecated_function( __METHOD__, '1.8.5' ); } /** * Filter form action. * * @since 1.8.8 * * @param string|mixed $action Form action. * @param array|mixed $form_data Form data. * * @return string */ public function form_action_filter( $action, $form_data ): string { if ( $this->is_gb_editor() ) { // Remove inappropriate form action URL that contains all the block attributes. $action = ''; } return (string) $action; } /** * Get form HTML to display in a WPForms Gutenberg block. * * @since 1.4.8 * * @param array|mixed $attr Attributes passed by WPForms Gutenberg block. * * @return string */ public function get_form_html( $attr ): string { $attr = (array) $attr; $id = ! empty( $attr['formId'] ) ? absint( $attr['formId'] ) : 0; $this->current_form_id = $id; if ( empty( $id ) ) { return ''; } if ( $this->is_gb_editor() ) { $this->disable_fields_in_gb_editor(); } $title = ! empty( $attr['displayTitle'] ); $desc = ! empty( $attr['displayDesc'] ); $this->add_class_callback( $id, $attr ); // Maybe override block attributes with the theme settings. $attr = $this->maybe_override_block_attributes( $attr ); // Get block content. $content = $this->get_content( $id, $title, $desc, $attr ); // phpcs:disable WPForms.PHP.ValidateHooks.InvalidHookName /** * Filter Gutenberg block content. * * @since 1.5.8.2 * * @param string $content Block content. * @param int $id Form id. */ return (string) apply_filters( 'wpforms_gutenberg_block_form_content', $content, $id ); // phpcs:enable WPForms.PHP.ValidateHooks.InvalidHookName } /** * Maybe override block attributes. * * This method is used to override block attributes with the theme settings. * * @since 1.8.8 * * @param array $attr Attributes passed by WPForms Gutenberg block. * * @return array */ private function maybe_override_block_attributes( array $attr ): array { $theme_slug = (string) ( $attr['theme'] ?? '' ); // Previously added blocks (FS 1.0) don't have the themeName attribute. // To preserve existing styling of such old blocks, we shouldn't override attributes. if ( ! isset( $attr['themeName'] ) || ( empty( $attr['themeName'] ) && $theme_slug === 'default' ) ) { return $attr; } $theme_data = $this->themes_data_obj->get_theme( $theme_slug ); // Theme doesn't exist, let's return. if ( ! $theme_data ) { return $attr; } // Override block attributes with the theme settings. return array_merge( $attr, $theme_data['settings'] ); } /** * Add class callback. * * @since 1.8.1 * * @param int $id Form id. * @param array $attr Form attributes. * * @return void */ private function add_class_callback( $id, $attr ) { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks $class_callback = static function ( $classes, $form_data ) use ( $id, $attr ) { if ( (int) $form_data['id'] !== $id ) { return $classes; } $cls = []; // Add custom class to form container. if ( ! empty( $attr['className'] ) ) { $cls = array_map( 'esc_attr', explode( ' ', $attr['className'] ) ); } // Add classes to identify that the form displays inside the block. $cls[] = 'wpforms-block'; if ( ! empty( $attr['clientId'] ) ) { $cls[] = 'wpforms-block-' . $attr['clientId']; } return array_unique( array_merge( $classes, $cls ) ); }; if ( empty( $this->callbacks[ $id ] ) ) { add_filter( 'wpforms_frontend_container_class', $class_callback, 10, 2 ); } $this->callbacks[ $id ][] = $class_callback; } /** * Get content. * * @since 1.8.1 * * @param int $id Form id. * @param bool $title Form title is not empty. * @param bool $desc Form desc is not empty. * @param array $attr Form attributes. * * @return string */ private function get_content( $id, $title, $desc, $attr ): string { /** * Filter allow render block content flag. * * @since 1.8.8 * * @param bool $allow_render Allow render flag. Defaults to `true`. */ $allow_render = (bool) apply_filters( 'wpforms_integrations_gutenberg_form_selector_allow_render', true ); if ( ! $allow_render ) { return ''; } ob_start(); // phpcs:disable WPForms.PHP.ValidateHooks.InvalidHookName /** * Fires before Gutenberg block output. * * @since 1.5.8.2 */ do_action( 'wpforms_gutenberg_block_before' ); /** * Filter block title display flag. * * @since 1.5.8.2 * * @param bool $title Title display flag. * @param int $id Form id. */ $title = (bool) apply_filters( 'wpforms_gutenberg_block_form_title', $title, $id ); /** * Filter block description display flag. * * @since 1.5.8.2 * * @param bool $desc Description display flag. * @param int $id Form id. */ $desc = (bool) apply_filters( 'wpforms_gutenberg_block_form_desc', $desc, $id ); $this->output_css_vars( $attr ); $this->output_custom_css( $attr ); wpforms_display( $id, $title, $desc ); /** * Fires after Gutenberg block output. * * @since 1.5.8.2 */ do_action( 'wpforms_gutenberg_block_after' ); // phpcs:enable WPForms.PHP.ValidateHooks.InvalidHookName $content = (string) ob_get_clean(); if ( ! $this->is_gb_editor() ) { return $content; } if ( empty( $content ) ) { return '
' . '
' . esc_html__( 'The form cannot be displayed.', 'wpforms-lite' ) . '
'; } /** * Unfortunately, the inline 'script' tag cannot be executed in the GB editor. * This is the hacky way to trigger custom event on form loaded in the Block Editor / GB / FSE. */ // phpcs:disable WordPress.PHP.DevelopmentFunctions.error_log_var_export $content .= sprintf( '', absint( $id ), var_export( (bool) $title, true ), var_export( (bool) $desc, true ) ); // phpcs:enable WordPress.PHP.DevelopmentFunctions.error_log_var_export return $content; } /** * Checking if is Gutenberg REST API call. * * @since 1.5.7 * * @return bool True if is Gutenberg REST API call. */ public function is_gb_editor(): bool { // TODO: Find a better way to check if is GB editor API call. // phpcs:ignore WordPress.Security.NonceVerification.Recommended return defined( 'REST_REQUEST' ) && REST_REQUEST && ! empty( $_REQUEST['context'] ) && $_REQUEST['context'] === 'edit'; } /** * Disable form fields if called from the Gutenberg editor. * * @since 1.7.5 * * @return void */ private function disable_fields_in_gb_editor() { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks add_filter( 'wpforms_frontend_container_class', static function ( $classes ) { $classes[] = 'wpforms-gutenberg-form-selector'; return $classes; } ); add_action( 'wpforms_frontend_output', static function () { echo '
'; }, 3 ); add_action( 'wpforms_frontend_output', static function () { echo '
'; }, 30 ); } /** * Output CSS variables for the particular form. * * @since 1.8.1 * * @param array $attr Attributes passed by WPForms Gutenberg block. */ private function output_css_vars( $attr ) { if ( empty( $this->css_vars_obj ) || ! method_exists( $this->css_vars_obj, 'get_vars' ) ) { return; } $this->css_vars_obj->output_root(); if ( $this->render_engine === 'classic' || $this->disable_css_setting !== 1 ) { return; } $css_vars = $this->css_vars_obj->get_customized_css_vars( $attr ); if ( empty( $css_vars ) ) { return; } $style_id = "#wpforms-css-vars-{$attr['formId']}-block-{$attr['clientId']}"; /** * Filter the CSS selector for output CSS variables for styling the GB block form. * * @since 1.8.1 * * @param string $selector The CSS selector for output CSS variables for styling the GB block form. * @param array $attr Attributes passed by WPForms Gutenberg block. * @param array $css_vars CSS variables data. */ $vars_selector = apply_filters( 'wpforms_integrations_gutenberg_form_selector_output_css_vars_selector', "#wpforms-{$attr['formId']}.wpforms-block-{$attr['clientId']}", $attr, $css_vars ); $this->css_vars_obj->output_selector_vars( $vars_selector, $css_vars, $style_id, $this->current_form_id ); } /** * Output custom CSS styles. * * @since 1.8.8 * * @param array $attr Attributes passed by WPForms Gutenberg block. */ private function output_custom_css( $attr ) { if ( wpforms_get_render_engine() === 'classic' ) { return; } $custom_css = trim( $attr['customCss'] ?? '' ); if ( empty( $custom_css ) ) { return; } $style_id = "#wpforms-custom-css-{$attr['formId']}-block-{$attr['clientId']}"; printf( '', sanitize_key( $style_id ), esc_html( $custom_css ) ); } /** * Disable loading media for the richtext editor for edit action to prevent script conflicts. * * @since 1.9.1 * * @param bool|mixed $media_enabled Whether to enable media. * @param array $field Field data. * * @return bool */ public function disable_richtext_media( $media_enabled, array $field ): bool { // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( ! empty( $_REQUEST['action'] ) && $_REQUEST['action'] === 'edit' && is_admin() ) { return false; } return (bool) $media_enabled; } }