// phpcs:disable Generic.Commenting.DocComment.MissingShort
/** @noinspection PhpIllegalPsrClassPathInspection */
/** @noinspection AutoloadingIssuesInspection */
// phpcs:enable Generic.Commenting.DocComment.MissingShort
if ( ! defined( 'ABSPATH' ) ) {
use WPForms\Integrations\AI\Helpers as AIHelpers;
* Form builder that contains magic.
* @since 1.6.8 Form Builder Refresh.
* - Added `deregister_common_wp_admin_styles()` method.
* - Changed logic of enqueue styles.
* Abort. Bail on proceeding to process the page.
* The human-readable error message.
* One is the loneliest number that you'll ever do.
private static $instance;
* Form data and settings.
* Current template information.
* @return WPForms_Builder
public static function instance() { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks
if ( ! isset( self::$instance ) && ! ( self::$instance instanceof self ) ) {
self::$instance = new self();
add_action( 'admin_init', [ self::$instance, 'init' ] );
add_action( 'admin_init', [ self::$instance, 'deregister_common_wp_admin_styles' ], PHP_INT_MAX );
add_action( 'load-wpforms_page_wpforms-builder', [ self::$instance, 'process_actions' ] );
* Determine if the user is viewing the builder, if so, party on.
public function init() { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks, Generic.Metrics.CyclomaticComplexity.TooHigh
// Only load if we are actually on the builder.
if ( ! wpforms_is_admin_page( 'builder' ) ) {
$form_id = isset( $_GET['form_id'] ) ? absint( $_GET['form_id'] ) : false; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
// Abort early if form ID is set, but the value is empty, 0 or any non-numeric value.
wp_die( esc_html__( 'It looks like the form you are trying to access is no longer available.', 'wpforms-lite' ), 403 );
// Default view for with an existing form is fields panel.
$this->view = isset( $_GET['view'] ) ? sanitize_key( $_GET['view'] ) : 'fields'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
// The default view for new form is the setup panel.
$this->view = isset( $_GET['view'] ) ? sanitize_key( $_GET['view'] ) : 'setup'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( $this->view === 'setup' && ! wpforms_current_user_can( 'create_forms' ) ) {
wp_die( esc_html__( 'Sorry, you are not allowed to create new forms.', 'wpforms-lite' ), 403 );
if ( $this->view === 'fields' && ! wpforms_current_user_can( 'edit_form_single', $form_id ) ) {
wp_die( esc_html__( 'Sorry, you are not allowed to edit this form.', 'wpforms-lite' ), 403 );
$this->form = wpforms()->obj( 'form' )->get( $form_id );
if ( ! empty( $form_id ) && empty( $this->form ) ) {
$this->abort_message = esc_html__( 'It looks like the form you are trying to access is no longer available.', 'wpforms-lite' );
if ( ! empty( $this->form->post_status ) && $this->form->post_status === 'trash' ) {
$this->abort_message = esc_html__( 'You can\'t edit this form because it\'s in the trash.', 'wpforms-lite' );
$this->form_data = $this->form ? wpforms_decode( $this->form->post_content ) : false;
* Active form template data filter.
* Allows developers to modify fields' structure and form settings in the template of the current form.
* @param array $template Template data.
* @param array $form_id Form ID.
$this->template = apply_filters( 'wpforms_builder_template_active', [], $this->form );
// Modify meta viewport tag if desktop view is forced.
add_filter( 'admin_viewport_meta', [ $this, 'viewport_meta' ] );
add_action( 'admin_head', [ $this, 'admin_head' ] );
add_action( 'admin_enqueue_scripts', [ $this, 'enqueues' ], PHP_INT_MAX );
add_action( 'admin_print_footer_scripts', [ $this, 'footer_scripts' ] );
add_action( 'wpforms_admin_page', [ $this, 'output' ] );
// Display Abort Message screen.
add_action( 'wpforms_admin_page', [ $this, 'display_abort_message' ] );
// Save the timestamp when the Builder has been opened for the first time.
add_option( 'wpforms_builder_opened_date', time(), '', 'no' );
* Form Builder init action.
* Executes after all the form builder UI output.
* Intended to use in addons.
* @param string $view Current view.
do_action( 'wpforms_builder_init', $this->view );
add_filter( 'teeny_mce_plugins', [ $this, 'tinymce_buttons' ] );
* Clear common wp-admin styles, keep only allowed.
public function deregister_common_wp_admin_styles() {
if ( ! wpforms_is_admin_page( 'builder' ) ) {
* Filter the allowed common wp-admin styles.
* @param array $allowed_styles Styles allowed in the Form Builder.
$allowed_styles = (array) apply_filters( // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
'wpforms_admin_builder_allowed_common_wp_admin_styles',
wp_styles()->registered = array_intersect_key( wp_styles()->registered, array_flip( $allowed_styles ) );
public function process_actions() {
$form_id = isset( $_GET['form_id'] ) ? (int) $_GET['form_id'] : 0;
$action = isset( $_REQUEST['action'] ) ? sanitize_key( $_REQUEST['action'] ) : false;
$nonce = isset( $_GET['_wpnonce'] ) ? sanitize_key( $_GET['_wpnonce'] ) : false;
if ( ! $this->is_allowed_action( $form_id, $action ) ) {
if ( ! wp_verify_nonce( $nonce, 'wpforms_' . $action . '_form_nonce' ) ) {
$this->process_action( $form_id, $action );
* Check whether the form action is valid and allowed.
* @param int $form_id Form ID.
* @param string $action Action name.
private function is_allowed_action( int $form_id, string $action ): bool {
if ( empty( $form_id ) || empty( $action ) ) {
return in_array( $action, [ 'save_as_template', 'template_to_form', 'duplicate' ], true );
* Process a single form action.
* The action can be triggered via URL:
* add_query_arg( [ 'action' => '<action>', 'form_id' => $form_id ] ),
* 'wpforms_save_as_template_form_nonce'
* @param int $form_id Form ID.
* @param string $action Action name.
private function process_action( int $form_id, string $action ) {
$form_handler = wpforms()->obj( 'form' );
if ( $action === 'save_as_template' ) {
$id = $form_handler->convert( $form_id, 'template' );
if ( $action === 'template_to_form' ) {
$id = $form_handler->convert( $form_id, 'form' );
if ( $action === 'duplicate' ) {
$ids = $form_handler->duplicate( $form_id );
$id = ! empty( $ids ) ? current( $ids ) : false;
// Reload the form builder with the target object.
wp_safe_redirect( $this->get_edit_url( $id, $this->view ) );
* @param string|int $form_id Form ID.
* @param string $view View name.
private function get_edit_url( $form_id, string $view = '' ): string {
if ( empty( $view ) || ! in_array( $view, $this->panels, true ) ) {
admin_url( 'admin.php?page=wpforms-builder' )
* Define TinyMCE buttons to use with our fancy editor instances.
* @param array $buttons List of default buttons.
* @noinspection PhpMissingParamTypeInspection
* @noinspection PhpUnusedParameterInspection
public function tinymce_buttons( $buttons ): array { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
return [ 'colorpicker', 'lists', 'wordpress', 'wpeditimage', 'wplink' ];
public function load_panels() {
// Base class and functions.
require_once WPFORMS_PLUGIN_DIR . 'includes/admin/builder/panels/class-base.php';
* Form Builder panels slugs array filter.
* Allows developers to disable loading of some builder panels.
* @param array $panels Panels slugs array.
$this->panels = apply_filters(
'wpforms_builder_panels',
foreach ( $this->panels as $panel ) {
$panel = sanitize_file_name( $panel );
$file = WPFORMS_PLUGIN_DIR . "includes/admin/builder/panels/class-{$panel}.php";
$file_pro = WPFORMS_PLUGIN_DIR . "pro/includes/admin/builder/panels/class-{$panel}.php";
if ( file_exists( $file ) ) {
} elseif ( file_exists( $file_pro ) ) {
* Admin head area inside the form builder.
public function admin_head() {
// Force hide an admin side menu.
echo '<style>#adminmenumain { display: none !important }</style>';
* Form Builder admin head action.
* @param string $view Current view.
do_action( 'wpforms_builder_admin_head', $this->view );
* Enqueue assets for the builder.
* @since 1.6.8 All the panel's stylesheets restructured and moved here.
public function enqueues() {
$this->suppress_conflicts();
* Form Builder enqueues before action.
* @param string $view Current view.
do_action( 'wpforms_builder_enqueues_before', $this->view );
$min = wpforms_get_min_suffix();
foreach ( $builder_styles as $style ) {
$style === 'basic' ? 'wpforms-builder' : 'wpforms-builder-' . $style,
WPFORMS_PLUGIN_URL . "assets/css/builder/builder-{$style}{$min}.css",
WPFORMS_PLUGIN_URL . 'assets/lib/font-awesome/font-awesome.min.css',
WPFORMS_PLUGIN_URL . 'assets/lib/jquery.tooltipster/jquery.tooltipster.min.css',