and keeps "Form Builder" * highlighted in the sidebar (the hook suffix still resolves under that * parent), without adding a visible, redundant submenu entry. */ class Form_Builder_Builder_Page { const PAGE_SLUG = 'adminoptim-form-builder'; const PARENT_SLUG = 'edit.php?post_type=' . Form_Builder::ADMIN_POST_TYPE; /** * @var Form_Builder_Forms */ private $forms; /** * The hook suffix add_submenu_page() returns for this page, needed to * scope admin_enqueue_scripts() even though the menu entry itself is * removed right after registration. * * @var string */ private $page_hook = ''; /** * Constructor. * * @param Form_Builder_Forms $forms Forms service instance. */ public function __construct( Form_Builder_Forms $forms ) { $this->forms = $forms; add_action( 'admin_menu', [ $this, 'register_page' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] ); add_action( 'load-post.php', [ $this, 'maybe_redirect_edit_screen' ] ); add_action( 'load-post-new.php', [ $this, 'maybe_redirect_new_screen' ] ); add_filter( 'get_edit_post_link', [ $this, 'filter_edit_post_link' ], 10, 2 ); } /** * Register the hidden builder page under the CPT's parent slug. * * @return void */ public function register_page() { $this->page_hook = (string) add_submenu_page( self::PARENT_SLUG, __( 'Edit Form', 'admin-optimizer' ), __( 'Edit Form', 'admin-optimizer' ), Form_Builder::management_capability(), self::PAGE_SLUG, [ $this, 'render_page' ] ); remove_submenu_page( self::PARENT_SLUG, self::PAGE_SLUG ); /* * Because the submenu entry is removed immediately above, * get_admin_page_title() can no longer find it in $submenu, so * wp-admin's own admin-header.php ends up with a null $title * (triggering a strip_tags(null) deprecation notice on PHP 8.1+). * Set the global explicitly, early enough (load-$hook fires before * admin-header.php is required) to pre-empt that lookup. */ add_action( 'load-' . $this->page_hook, [ $this, 'set_admin_title' ] ); /* * The "Add New" flow (no form_id) creates a draft and redirects to * attach it. That redirect must happen here, on load-$hook — which * fires before admin-header.php requires/prints anything — not from * inside render_page(), which WordPress only calls after * admin-header.php has already sent headers and output the page's * (styles included). A wp_safe_redirect() from render_page() * would reliably warn "headers already sent" and leave the page * blank, since enqueued admin styles always print before any * submenu page callback runs. */ add_action( 'load-' . $this->page_hook, [ $this, 'maybe_create_draft_and_redirect' ] ); } /** * Create a new draft and redirect to it when the builder page is visited * with no form_id (the "Add New" flow) — see the comment in * register_page() for why this must run on load-$hook. * * @return void */ public function maybe_create_draft_and_redirect() { if ( ! Form_Builder::can_manage_forms() ) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only screen routing, no state change. $form_id = isset( $_GET['form_id'] ) ? absint( wp_unslash( $_GET['form_id'] ) ) : 0; if ( $form_id ) { return; } $this->create_draft_and_redirect(); } /** * Explicitly set the admin page title global — see the comment in * register_page() for why this is necessary for a hidden submenu page. * * @return void */ public function set_admin_title() { $GLOBALS['title'] = __( 'Edit Form', 'admin-optimizer' ); } /** * Redirect the CPT's native post.php edit screen to the builder page. * * @return void */ public function maybe_redirect_edit_screen() { global $typenow; if ( Form_Builder::ADMIN_POST_TYPE !== $typenow || ! Form_Builder::can_manage_forms() ) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only screen routing, no state change. $action = isset( $_GET['action'] ) ? sanitize_key( wp_unslash( $_GET['action'] ) ) : ''; if ( '' !== $action && 'edit' !== $action ) { // Anything other than a plain edit view — delete, trash, untrash, // restore from autosave, etc. — must reach post.php's own // handling untouched. Row actions like "Delete Permanently" link // here with action=delete, and hijacking that to the builder page // silently turns every such action into a no-op edit-screen visit. return; } $form_id = isset( $_GET['post'] ) ? absint( wp_unslash( $_GET['post'] ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only redirect target, no state change. if ( ! $form_id ) { return; } wp_safe_redirect( $this->builder_url( $form_id ) ); exit; } /** * Redirect the CPT's native post-new.php screen to the builder page, * which creates the new draft itself (see maybe_create_draft()). * * @return void */ public function maybe_redirect_new_screen() { global $typenow; if ( Form_Builder::ADMIN_POST_TYPE !== $typenow || ! Form_Builder::can_manage_forms() ) { return; } wp_safe_redirect( $this->builder_url() ); exit; } /** * Point every get_edit_post_link() call for a form at the builder page * instead of post.php — covers the CPT list table's own title/edit link, * the entries list table and single-entry view's form links, and the AI * generator's `editUrl`, with no changes needed in any of those files. * * @param string $link Default edit link. * @param int $post_id Post ID. * * @return string */ public function filter_edit_post_link( $link, $post_id ) { $post = get_post( $post_id ); if ( ! $post || Form_Builder::ADMIN_POST_TYPE !== $post->post_type ) { return $link; } return $this->builder_url( (int) $post->ID ); } /** * Build the builder page's URL, with or without a form ID. * * @param int $form_id Form post ID, or `0` for the "create a new form" entry point. * * @return string */ private function builder_url( $form_id = 0 ) { $url = admin_url( 'admin.php?page=' . self::PAGE_SLUG ); return $form_id ? add_query_arg( [ 'form_id' => absint( $form_id ) ], $url ) : $url; } /** * Render the builder page: create a draft and self-redirect when no * form_id is present (the "Add New" entry point), otherwise render the * page shell the JS app mounts into. * * @return void */ public function render_page() { if ( ! Form_Builder::can_manage_forms() ) { wp_die( esc_html__( 'You do not have permission to access this page.', 'admin-optimizer' ) ); } $form_id = isset( $_GET['form_id'] ) ? absint( wp_unslash( $_GET['form_id'] ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only screen routing, no state change. if ( ! $form_id ) { $this->create_draft_and_redirect(); return; } $form = get_post( $form_id ); if ( ! $form || ! Form_Builder::can_edit_form( $form->ID ) ) { wp_die( esc_html__( 'This form could not be found.', 'admin-optimizer' ) ); } $is_pro_active = Module_Registry::is_pro_active(); ?>

Form_Builder::ADMIN_POST_TYPE, 'post_title' => __( 'Untitled form', 'admin-optimizer' ), 'post_status' => 'draft', 'post_author' => get_current_user_id(), ], true ); if ( is_wp_error( $post_id ) || ! $post_id ) { wp_die( esc_html__( 'The form could not be created.', 'admin-optimizer' ) ); } wp_safe_redirect( $this->builder_url( (int) $post_id ) ); exit; } /** * Enqueue the builder app's assets and bootstrap it with JSON, only on * this page (matched by hook suffix, since the menu entry itself is * removed and so is never the current-screen-by-slug in the usual way). * * @param string $hook_suffix Current admin page hook suffix. * * @return void */ public function enqueue_assets( $hook_suffix ) { if ( $hook_suffix !== $this->page_hook || ! Form_Builder::can_manage_forms() ) { return; } $form_id = isset( $_GET['form_id'] ) ? absint( wp_unslash( $_GET['form_id'] ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only bootstrap data, no state change. if ( ! $form_id ) { return; } $form = get_post( $form_id ); if ( ! $form || ! Form_Builder::can_edit_form( $form->ID ) ) { return; } wp_enqueue_style( 'wp-color-picker' ); wp_enqueue_style( 'dashicons' ); wp_enqueue_style( 'adminoptimizer-form-builder-settings', Form_Builder::MODULE_URL . 'assets/css/form-builder-settings.min.css', [ 'wp-color-picker', 'dashicons' ], ADMINOPTIMIZER_VERSION ); wp_enqueue_script( 'adminoptimizer-form-builder-settings', Form_Builder::MODULE_URL . 'assets/js/form-builder-settings.min.js', [ 'wp-color-picker' ], ADMINOPTIMIZER_VERSION, true ); wp_localize_script( 'adminoptimizer-form-builder-settings', 'aofbBuilder', $this->bootstrap_data( $form ) ); /** * Fires at the end of the builder page's asset enqueue. Pro attaches * preset data/assets and inline-appends its JS onto this script * handle here. */ do_action( 'adminoptim_form_builder_enqueue_settings_scripts' ); } /** * Build the JSON payload the builder app boots from. * * @param \WP_Post $form Form post. * * @return array */ private function bootstrap_data( \WP_Post $form ) { $is_pro_active = Module_Registry::is_pro_active(); return [ 'form' => [ 'id' => (int) $form->ID, 'title' => (string) $form->post_title, 'status' => (string) $form->post_status, 'fields' => Form_Builder_Forms::get_form_fields( $form->ID ), 'settings' => Form_Builder_Forms::get_form_settings( $form->ID ), ], 'fieldTypes' => $this->forms->get_supported_field_types(), 'fieldCapabilities' => $this->forms->get_supported_field_capabilities(), 'defaultSettings' => Form_Builder_Forms::get_default_form_settings(), 'hasSmtpProfiles' => $is_pro_active && $this->forms->has_smtp_profile_choices(), 'smtpProfiles' => $is_pro_active ? $this->forms->get_smtp_profile_choices() : [], 'fileTypeOptions' => $is_pro_active ? $this->file_type_options() : [], 'emailFields' => $this->forms->get_email_field_choices( Form_Builder_Forms::get_form_fields( $form->ID ) ), 'isPro' => $is_pro_active, 'shortcode' => sprintf( '[ao_form id="%d"]', (int) $form->ID ), 'formsListUrl' => admin_url( 'edit.php?post_type=' . Form_Builder::ADMIN_POST_TYPE ), 'formBuilderSettingsUrl' => current_user_can( 'manage_options' ) ? admin_url( 'edit.php?post_type=' . Form_Builder::ADMIN_POST_TYPE . '&page=adminoptim-form-settings' ) : '', 'entriesUrl' => add_query_arg( [ 'post_type' => Form_Builder::ADMIN_POST_TYPE, 'page' => 'adminoptim-form-entries', 'form_id' => (int) $form->ID, ], admin_url( 'edit.php' ) ), 'upgradeUrl' => esc_url_raw( ao_get_upgrade_url( 'form-builder-template-lock' ) ), 'restUrl' => esc_url_raw( rest_url( Form_Builder_Rest::REST_NAMESPACE . '/form-builder/' . (int) $form->ID ) ), 'restNonce' => wp_create_nonce( 'wp_rest' ), 'untitledLabel' => __( 'Untitled field', 'admin-optimizer' ), 'i18n' => $this->i18n_strings(), 'templates' => Form_Builder_Templates::get_registered_templates(), 'lockedTemplates' => $is_pro_active ? [] : Form_Builder_Templates::get_locked_template_teasers(), 'templateCategories' => Form_Builder_Templates::get_categories(), 'inertSettings' => Form_Builder_Forms::get_inert_settings_labels( $form->ID ), 'integrationsInert' => Form_Builder_Forms::has_inert_integrations( $form->ID ), 'customCssInert' => Form_Builder_Forms::has_inert_custom_css( $form->ID ), ]; } /** * The upload types this site permits, grouped for the File field's picker. * Each item lists the extensions WordPress treats as one type (jpg, jpeg, jpe). * * @return array>}> */ private function file_type_options() { $labels = [ 'image' => __( 'Images', 'admin-optimizer' ), 'document' => __( 'Documents', 'admin-optimizer' ), 'spreadsheet' => __( 'Spreadsheets', 'admin-optimizer' ), 'interactive' => __( 'Presentations', 'admin-optimizer' ), 'audio' => __( 'Audio', 'admin-optimizer' ), 'video' => __( 'Video', 'admin-optimizer' ), 'text' => __( 'Text', 'admin-optimizer' ), 'archive' => __( 'Archives', 'admin-optimizer' ), 'other' => __( 'Other', 'admin-optimizer' ), ]; $items = array_fill_keys( array_keys( $labels ), [] ); foreach ( get_allowed_mime_types() as $extensions => $mime_type ) { $extensions = explode( '|', strtolower( (string) $extensions ) ); // Admins hold unfiltered_html and see these, but WordPress refuses // them for the visitors who actually submit the form. if ( array_intersect( $extensions, [ 'htm', 'html', 'js' ] ) ) { continue; } $items[ $this->file_type_group( $extensions, (string) $mime_type, $labels ) ][] = $extensions; } $groups = []; foreach ( $items as $group => $group_items ) { if ( ! $group_items ) { continue; } usort( $group_items, static function ( $a, $b ) { return strcmp( $a[0], $b[0] ); } ); $groups[] = [ 'id' => $group, 'label' => $labels[ $group ], 'items' => $group_items, ]; } return $groups; } /** * Pick the picker group for one WordPress file type. * * wp_ext2type() knows xls but not xla, and none of the Office templates, so * every extension is tried before falling back to the MIME type. * * @param array $extensions Extensions of this file type. * @param string $mime_type MIME type of this file type. * @param array $groups Known groups, keyed by group. * * @return string */ private function file_type_group( array $extensions, $mime_type, array $groups ) { foreach ( $extensions as $extension ) { $group = (string) wp_ext2type( $extension ); if ( isset( $groups[ $group ] ) ) { return $group; } } $office_groups = [ 'wordprocessingml' => 'document', 'spreadsheetml' => 'spreadsheet', 'ms-excel' => 'spreadsheet', 'presentationml' => 'interactive', 'ms-powerpoint' => 'interactive', ]; foreach ( $office_groups as $needle => $group ) { if ( false !== strpos( $mime_type, $needle ) ) { return $group; } } $group = strtok( $mime_type, '/' ); return isset( $groups[ $group ] ) ? $group : 'other'; } /** * UI strings the JS app needs, kept in one place so every string is * translatable through this module's text domain. * * @return array */ private function i18n_strings() { return [ 'formsLabel' => __( 'Forms', 'admin-optimizer' ), 'publishedLabel' => __( 'Published', 'admin-optimizer' ), 'draftLabel' => __( 'Draft', 'admin-optimizer' ), 'unsavedChanges' => __( 'Unsaved changes', 'admin-optimizer' ), 'previewLabel' => __( 'Preview', 'admin-optimizer' ), 'saveLabel' => __( 'Save', 'admin-optimizer' ), 'savedLabel' => __( 'Saved', 'admin-optimizer' ), 'savingLabel' => __( 'Saving…', 'admin-optimizer' ), 'updateLabel' => __( 'Update', 'admin-optimizer' ), 'saveDraftLabel' => __( 'Save draft', 'admin-optimizer' ), 'copyLabel' => __( 'Copy', 'admin-optimizer' ), 'viewEntries' => __( 'View entries', 'admin-optimizer' ), 'switchToDraft' => __( 'Switch to draft', 'admin-optimizer' ), 'allForms' => __( 'All forms', 'admin-optimizer' ), 'formUpdated' => __( 'Form updated.', 'admin-optimizer' ), 'switchedToDraft' => __( 'Switched to draft.', 'admin-optimizer' ), 'untitledFormTitle' => __( 'Untitled form', 'admin-optimizer' ), 'templatesTitle' => __( 'Templates', 'admin-optimizer' ), 'templatesDesc' => __( 'Start from a ready-made form, then change anything in the Builder.', 'admin-optimizer' ), 'searchTemplates' => __( 'Search templates', 'admin-optimizer' ), 'allCategory' => __( 'All', 'admin-optimizer' ), 'blankFormTitle' => __( 'Blank form', 'admin-optimizer' ), 'blankFormDesc' => __( 'Start from scratch', 'admin-optimizer' ), 'generateAiTitle' => __( 'Generate with AI', 'admin-optimizer' ), 'generateAiDesc' => __( 'Describe your form and we build it', 'admin-optimizer' ), 'noTemplatesFound' => __( 'No templates match that search.', 'admin-optimizer' ), /* translators: %d: number of fields in a template. */ 'fieldsCountLabel' => __( '%d fields', 'admin-optimizer' ), /* translators: %s: template name. */ 'useTemplateReplace' => __( 'Use the "%s" template?', 'admin-optimizer' ), 'useTemplateBlank' => __( 'Start a blank form?', 'admin-optimizer' ), /* translators: %d: number of fields already in the form. */ 'templateHasFields' => __( 'Your form already has %d field(s). You can keep them and add the template\'s fields, or replace them. You can undo this right after.', 'admin-optimizer' ), 'templateBlankWarning' => __( 'Starting blank removes your current fields. You can undo this right after.', 'admin-optimizer' ), 'addToMyFields' => __( 'Add to my fields', 'admin-optimizer' ), 'replaceMyFields' => __( 'Replace my fields', 'admin-optimizer' ), 'removeMyFields' => __( 'Remove my fields', 'admin-optimizer' ), /* translators: %s: template name. */ 'templateApplied' => __( '"%s" applied.', 'admin-optimizer' ), /* translators: %s: template name. */ 'templateAdded' => __( '"%s" added to your form.', 'admin-optimizer' ), 'startedBlank' => __( 'Started a blank form.', 'admin-optimizer' ), 'undone' => __( 'Undone.', 'admin-optimizer' ), 'browseTemplates' => __( 'Browse templates', 'admin-optimizer' ), 'aiNotPro' => __( 'Generate-with-AI is part of Admin Optimizer Pro.', 'admin-optimizer' ), 'copyShortcode' => __( 'Copy shortcode', 'admin-optimizer' ), 'shortcodeCopied' => __( 'Shortcode copied.', 'admin-optimizer' ), 'titlePlaceholder' => __( 'Enter form title', 'admin-optimizer' ), 'presetsLabel' => __( 'Presets', 'admin-optimizer' ), 'presetsHint' => __( 'Start from a ready-made form', 'admin-optimizer' ), 'proTag' => __( 'Pro', 'admin-optimizer' ), 'upgradeCta' => __( 'See what Pro adds', 'admin-optimizer' ), 'upgradeToProPrefix' => __( 'Upgrade to the', 'admin-optimizer' ), 'upgradeToProLinkText' => __( 'Pro version', 'admin-optimizer' ), 'upgradeToProSuffix' => __( 'to access these features', 'admin-optimizer' ), 'tabBuilder' => __( 'Builder', 'admin-optimizer' ), 'tabSettings' => __( 'Settings', 'admin-optimizer' ), 'tabStyle' => __( 'Style', 'admin-optimizer' ), 'tabEmails' => __( 'Emails', 'admin-optimizer' ), 'tabAuto' => __( 'Autoresponder', 'admin-optimizer' ), 'fieldsHeading' => __( 'Fields', 'admin-optimizer' ), 'searchFields' => __( 'Search fields', 'admin-optimizer' ), 'canvasHeading' => __( 'Form Editor', 'admin-optimizer' ), 'canvasSubtitle' => __( 'Create the form that visitors will see on the frontend.', 'admin-optimizer' ), 'emptyCanvasTitle' => __( 'Your form has no fields yet.', 'admin-optimizer' ), 'emptyCanvasBody' => __( 'Pick a template, or add your first field.', 'admin-optimizer' ), 'addTextField' => __( 'Add a text field', 'admin-optimizer' ), 'addField' => __( 'Add field', 'admin-optimizer' ), 'moveUp' => __( 'Move up', 'admin-optimizer' ), 'moveDown' => __( 'Move down', 'admin-optimizer' ), 'removeField' => __( 'Remove field', 'admin-optimizer' ), 'removeThisField' => __( 'Remove this field', 'admin-optimizer' ), 'fieldRemoved' => __( 'Field removed.', 'admin-optimizer' ), 'undo' => __( 'Undo', 'admin-optimizer' ), 'requiredLabel' => __( 'Required', 'admin-optimizer' ), 'pinnedHeading' => __( 'Pinned to the end', 'admin-optimizer' ), 'pinnedHint' => __( 'reCAPTCHA and Turnstile always run last. Add one from the Anti-spam group.', 'admin-optimizer' ), 'buttonLabelField' => __( 'Button label', 'admin-optimizer' ), 'widthField' => __( 'Width', 'admin-optimizer' ), 'widthFull' => __( 'Fill available space', 'admin-optimizer' ), 'widthThreeQuarter' => __( 'Three quarters', 'admin-optimizer' ), 'widthHalf' => __( 'Half', 'admin-optimizer' ), 'widthQuarter' => __( 'Quarter', 'admin-optimizer' ), 'noSubmitWarning' => __( 'This form has no submit button.', 'admin-optimizer' ), 'submitAlreadyAdded' => __( 'This form already has a submit button.', 'admin-optimizer' ), 'rowInRowError' => __( 'A Row cannot contain another Row.', 'admin-optimizer' ), 'conditionalLogic' => __( 'Conditional logic', 'admin-optimizer' ), 'showWhen' => __( 'Only show this field when', 'admin-optimizer' ), 'matchAll' => __( 'ALL of the following rules match', 'admin-optimizer' ), 'matchAny' => __( 'ANY of the following rules match', 'admin-optimizer' ), 'addRule' => __( 'Add rule', 'admin-optimizer' ), 'conditionHint' => __( 'A rule can only use a field that has no conditional logic of its own.', 'admin-optimizer' ), 'inactiveConditions' => __( 'These rules are saved but not applied on your site without Pro.', 'admin-optimizer' ), 'saveError' => __( 'Fix the problems above to save.', 'admin-optimizer' ), 'leaveTitle' => __( 'Leave without saving?', 'admin-optimizer' ), 'leaveBody' => __( 'You have unsaved changes to this form.', 'admin-optimizer' ), 'keepEditing' => __( 'Keep editing', 'admin-optimizer' ), 'leaveAnyway' => __( 'Leave without saving', 'admin-optimizer' ), 'upgradeTitle' => __( 'Pro feature', 'admin-optimizer' ), 'upgradeBody' => __( 'Presets, star ratings, conditional logic, redirects, submission limits, and access rules are part of Admin Optimizer Pro.', 'admin-optimizer' ), 'notNow' => __( 'Not now', 'admin-optimizer' ), 'seeProPlans' => __( 'See Pro plans', 'admin-optimizer' ), 'confirmReplaceTitle' => __( 'Replace your current fields?', 'admin-optimizer' ), 'confirmReplaceButton' => __( 'Replace fields', 'admin-optimizer' ), 'cancel' => __( 'Cancel', 'admin-optimizer' ), 'close' => __( 'Close', 'admin-optimizer' ), 'savedAndPublished' => __( 'Form saved and published.', 'admin-optimizer' ), 'draftSaved' => __( 'Draft saved.', 'admin-optimizer' ), 'previewUnsavedNote' => __( 'This preview includes your unsaved changes.', 'admin-optimizer' ), 'previewSavedNote' => __( 'This is your saved form.', 'admin-optimizer' ), 'inertNotice' => __( 'Saved but not enforced. Your form is open to everyone until Pro is active.', 'admin-optimizer' ), 'accessSectionLabel' => __( 'Access and limits', 'admin-optimizer' ), 'settingsSectionTitle' => __( 'Submission Settings', 'admin-optimizer' ), 'settingsSectionDesc' => __( 'Choose what happens when someone submits this form.', 'admin-optimizer' ), 'styleSectionTitle' => __( 'Form Style', 'admin-optimizer' ), 'styleSectionDesc' => __( 'Colors and styling for this form only.', 'admin-optimizer' ), 'emailsSectionTitle' => __( 'Notification Email', 'admin-optimizer' ), 'emailsSectionDesc' => __( 'Sent to you each time this form is submitted.', 'admin-optimizer' ), 'autoSectionDesc' => __( 'Send an automatic thank-you email right after someone submits.', 'admin-optimizer' ), 'onLabel' => __( 'On', 'admin-optimizer' ), 'offLabel' => __( 'Off', 'admin-optimizer' ), 'deliveryLabel' => __( 'What should happen to submissions?', 'admin-optimizer' ), 'deliveryBoth' => __( 'Save entries and email me', 'admin-optimizer' ), 'deliveryEmail' => __( 'Email me only', 'admin-optimizer' ), 'deliveryStore' => __( 'Save entries only', 'admin-optimizer' ), 'successMessageLabel' => __( 'Success message', 'admin-optimizer' ), 'successBehaviorLabel' => __( 'Show success message', 'admin-optimizer' ), 'successBehaviorMessage' => __( 'After the last form field', 'admin-optimizer' ), 'successBehaviorReplace' => __( 'Replace form with success message', 'admin-optimizer' ), 'redirectLabel' => __( 'Redirect after submit', 'admin-optimizer' ), 'whoCanSubmit' => __( 'Who can submit', 'admin-optimizer' ), 'everyone' => __( 'Everyone', 'admin-optimizer' ), 'loggedInUsers' => __( 'Logged-in users', 'admin-optimizer' ), 'specificRoles' => __( 'Specific roles', 'admin-optimizer' ), 'maxEntries' => __( 'Maximum entries', 'admin-optimizer' ), 'closeOn' => __( 'Close form on', 'admin-optimizer' ), 'colorsHeading' => __( 'Style and Color', 'admin-optimizer' ), 'clearAll' => __( 'Clear all', 'admin-optimizer' ), 'primaryColor' => __( 'Primary color', 'admin-optimizer' ), 'textColor' => __( 'Text color', 'admin-optimizer' ), 'inputBgColor' => __( 'Input background', 'admin-optimizer' ), 'borderColor' => __( 'Input border', 'admin-optimizer' ), 'borderRadius' => __( 'Border radius (px)', 'admin-optimizer' ), 'previewHeading' => __( 'How it looks', 'admin-optimizer' ), 'notifyHeading' => __( 'Notification to you', 'admin-optimizer' ), 'mailEnable' => __( 'Email me when someone submits', 'admin-optimizer' ), 'mailStoreOnlyNote' => __( 'Submissions are set to save entries only, so no notification is sent. Change this under Settings.', 'admin-optimizer' ), 'sendTo' => __( 'Send to', 'admin-optimizer' ), 'smtpProfile' => __( 'Send using SMTP profile', 'admin-optimizer' ), 'subjectLabel' => __( 'Subject', 'admin-optimizer' ), 'messageLabel' => __( 'Message', 'admin-optimizer' ), 'insertValue' => __( 'Insert a value from the form:', 'admin-optimizer' ), 'autoHeading' => __( 'Confirmation to the submitter', 'admin-optimizer' ), 'autoEnable' => __( 'Send a confirmation email to the submitter', 'admin-optimizer' ), 'autoOffHint' => __( 'Turn this on to configure the confirmation email.', 'admin-optimizer' ), 'sendConfirmationTo' => __( 'Send confirmation to', 'admin-optimizer' ), 'firstEmailField' => __( 'First Email field submitted', 'admin-optimizer' ), 'sendAsHtml' => __( 'Send this message as HTML email', 'admin-optimizer' ), 'basicField' => __( 'Field', 'admin-optimizer' ), 'choiceField' => __( 'Choice', 'admin-optimizer' ), 'advancedField' => __( 'Advanced', 'admin-optimizer' ), 'layoutField' => __( 'Layout', 'admin-optimizer' ), 'layoutKind' => __( 'Layout field', 'admin-optimizer' ), 'choiceKind' => __( 'Choice field', 'admin-optimizer' ), 'displayKind' => __( 'Display field', 'admin-optimizer' ), 'antispamKind' => __( 'Anti-spam field', 'admin-optimizer' ), 'inputKind' => __( 'Input field', 'admin-optimizer' ), 'antispamField' => __( 'Anti-spam', 'admin-optimizer' ), 'labelField' => __( 'Label', 'admin-optimizer' ), 'nameField' => __( 'Name', 'admin-optimizer' ), 'placeholderField' => __( 'Placeholder', 'admin-optimizer' ), 'defaultValueField' => __( 'Default value', 'admin-optimizer' ), 'helpTextField' => __( 'Help text', 'admin-optimizer' ), 'requiredField' => __( 'Required field', 'admin-optimizer' ), 'hideLabelField' => __( 'Hide label (kept for screen readers)', 'admin-optimizer' ), 'allowPrepopulationField' => __( 'Allow value override', 'admin-optimizer' ), 'allowPrepopulationHint' => __( 'Allow this field’s initial value to be overridden through shortcode attributes or Gutenberg Form Block settings.', 'admin-optimizer' ), 'choicesField' => __( 'Choices', 'admin-optimizer' ), 'choicesHint' => __( 'One per line. Use value|Label to set them separately.', 'admin-optimizer' ), 'columnsField' => __( 'Columns', 'admin-optimizer' ), 'fileTypesField' => __( 'Allowed file types', 'admin-optimizer' ), 'fileTypesPlaceholder' => __( 'Type to search, e.g. pdf', 'admin-optimizer' ), 'fileTypesHint' => __( 'Leave empty to allow every file type this site permits.', 'admin-optimizer' ), 'fileTypesNoMatch' => __( 'No matching file types. Only types this site permits are listed.', 'admin-optimizer' ), 'fileTypesSelectAll' => __( 'Select all', 'admin-optimizer' ), 'fileTypesClearAll' => __( 'Clear all', 'admin-optimizer' ), /* translators: %s: file type, e.g. pdf. */ 'fileTypesRemove' => _x( 'Remove %s', 'file type chip', 'admin-optimizer' ), 'fileTypesUnlisted' => __( 'Saved earlier, but not in the list of types this site permits.', 'admin-optimizer' ), 'fileSizeField' => __( 'Max size (MB)', 'admin-optimizer' ), 'imageMinWidth' => __( 'Min width (px)', 'admin-optimizer' ), 'imageMaxWidth' => __( 'Max width (px)', 'admin-optimizer' ), 'imageMinHeight' => __( 'Min height (px)', 'admin-optimizer' ), 'imageMaxHeight' => __( 'Max height (px)', 'admin-optimizer' ), 'imageDimensionsHint' => __( 'For an exact size, enter the same minimum and maximum.', 'admin-optimizer' ), 'fileUploadNote' => __( 'Files are saved to the Uploads folder by default.', 'admin-optimizer' ), 'noFieldsToPreview' => __( 'Add fields to see them here.', 'admin-optimizer' ), 'selectPlaceholder' => __( 'Select…', 'admin-optimizer' ), ]; } }