Conversions', 'kconv_view_entries', 'killerconversions', array( __CLASS__, 'screen' ), 'dashicons-feedback', 30 ); add_submenu_page( 'killerconversions', __( 'Forms', 'killerconversions' ), __( 'Forms', 'killerconversions' ), 'manage_options', 'killerconversions', array( __CLASS__, 'screen' ) ); add_submenu_page( 'killerconversions', __( 'Inbox', 'killerconversions' ), __( 'Inbox', 'killerconversions' ), 'kconv_view_entries', 'kconv-entries', array( __CLASS__, 'entries' ) ); do_action( 'kconv_admin_menu' ); } public static function assets( $hook ) { if ( ! KCONV_Admin_Scope::plugin() ) { return; } wp_enqueue_style( 'kconv-admin', KCONV_URL . 'assets/admin.css', array(), KCONV_VERSION ); wp_enqueue_style( 'kconv-workspace', KCONV_URL . 'assets/workspace.css', array( 'kconv-admin' ), KCONV_VERSION ); wp_enqueue_script( 'kconv-admin', KCONV_URL . 'assets/admin.js', array('wp-i18n'), KCONV_VERSION, true ); wp_set_script_translations( 'kconv-admin', 'killerconversions', KCONV_DIR . 'languages' ); wp_enqueue_script( 'kconv-workspace', KCONV_URL . 'assets/workspace.js', array( 'kconv-admin' , 'wp-i18n' ), KCONV_VERSION, true ); wp_set_script_translations( 'kconv-workspace', 'killerconversions', KCONV_DIR . 'languages' ); wp_enqueue_style( 'kconv-mobile-editor', KCONV_URL . 'assets/mobile-editor.css', array( 'kconv-workspace' ), KCONV_VERSION ); wp_enqueue_style( 'kconv-readability', KCONV_URL . 'assets/readability.css', array( 'kconv-workspace' ), KCONV_VERSION ); wp_enqueue_style( 'kconv-published-preview', KCONV_URL . 'assets/published-preview.css', array( 'kconv-workspace' ), KCONV_VERSION ); wp_enqueue_style( 'kconv-publish-next', KCONV_URL . 'assets/publish-next.css', array( 'kconv-workspace', 'kconv-readability' ), KCONV_VERSION ); wp_enqueue_script( 'kconv-publish-next', KCONV_URL . 'assets/publish-next.js', array( 'kconv-workspace' ), KCONV_VERSION, true ); wp_enqueue_script( 'kconv-published-preview', KCONV_URL . 'assets/published-preview.js', array( 'kconv-workspace' , 'wp-i18n' ), KCONV_VERSION, true ); wp_set_script_translations( 'kconv-published-preview', 'killerconversions', KCONV_DIR . 'languages' ); wp_enqueue_script( 'kconv-mobile-editor', KCONV_URL . 'assets/mobile-editor.js', array( 'kconv-workspace' , 'wp-i18n' ), KCONV_VERSION, true ); wp_set_script_translations( 'kconv-mobile-editor', 'killerconversions', KCONV_DIR . 'languages' ); wp_localize_script( 'kconv-published-preview', 'KCONV_Preview', array( 'endpoint' => rest_url( 'killerconversions/v1/admin/preview' ), 'nonce' => wp_create_nonce( 'wp_rest' ) ) ); } public static function navigation( $current = 'forms', $compact = false ) { KCONV_Workspace_Navigation::render( $current, $compact ); } public static function screen() { if ( ! current_user_can( 'manage_options' ) ) { self::entries(); return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only screen() navigation/filter input; values are normalized or allowlisted and escaped at output. No state change occurs from this read; mutations use separate capability/nonce guards. $edit = isset( $_GET['edit'] ) && is_scalar( $_GET['edit'] ) ? sanitize_text_field( wp_unslash( $_GET['edit'] ) ) : ''; echo '
'; self::navigation( 'forms', '' !== $edit ); if ( '' !== $edit ) { echo '

' . esc_html__( 'Form builder', 'killerconversions' ) . '

'; } $published = KCONV_Authoring::published_result( absint( $edit ) ); if ( $published ) { self::publish_next( absint( $edit ), $published ); } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Existing read-only draft/unpublish feedback. Publish success above requires a nonce bound to the actual committed form revision. elseif ( isset( $_GET['saved'], $_GET['saved_as'] ) && '1' === $_GET['saved'] && in_array( $_GET['saved_as'], array( 'draft', 'unpublish' ), true ) ) { echo '

' . esc_html( 'unpublish' === $_GET['saved_as'] ? __( 'Form unpublished. Visitors can no longer submit it.', 'killerconversions' ) : __( 'Draft saved. Any published version is unchanged.', 'killerconversions' ) ) . '

'; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only screen() navigation/filter input; values are normalized or allowlisted and escaped at output. No state change occurs from this read; mutations use separate capability/nonce guards. if ( '' !== $edit ) { self::editor( absint( $edit ) ); } elseif ( isset( $_GET['create'] ) ) { self::task_picker(); } else { self::library(); } echo '
'; } /** Optional next steps after a verified publish; viewing this panel performs no setup action. */ private static function publish_next( $id, $kind ) { $shortcode = '[killerconversions id="' . $id . '"]'; echo '
' . esc_html( 'new' === $kind ? __( 'Form published.', 'killerconversions' ) : __( 'Form updates published', 'killerconversions' ) ) . ' ' . esc_html( 'new' === $kind ? __( 'Add it to a page and check a real submission', 'killerconversions' ) : __( '· Review published settings', 'killerconversions' ) ) . '

' . esc_html__( 'Publishing makes the form ready to use. It does not add it to a WordPress page or verify delivery.', 'killerconversions' ) . '

'; self::published_setup( $id ); echo '
  1. ' . esc_html__( 'Add to a page', 'killerconversions' ) . '

    ' . esc_html__( 'In your page editor, add the KillerConversions Form block and choose this form, or paste its shortcode into a Shortcode block.', 'killerconversions' ) . '

    ' . esc_html__( 'Open WordPress Pages', 'killerconversions' ) . ' ' . esc_html__( '(new tab)', 'killerconversions' ) . '

  2. ' . esc_html__( 'Open your page', 'killerconversions' ) . '

    ' . esc_html__( 'Save or publish the page you chose, then use its View page link. Check the form there as a visitor.', 'killerconversions' ) . '

  3. ' . esc_html__( 'Send a real test', 'killerconversions' ) . '

    ' . esc_html__( 'Use details you control and clearly mark the message as a test. This creates a real entry and can send any enabled emails or integrations.', 'killerconversions' ) . '

  4. ' . esc_html__( 'Check Inbox', 'killerconversions' ) . '

    ' . esc_html__( 'Find the test entry here. Check any expected email or connected destination separately.', 'killerconversions' ) . '

    ' . esc_html__( 'Open this form’s Inbox', 'killerconversions' ) . ' ' . esc_html__( '(new tab)', 'killerconversions' ) . '
' . esc_html__( 'Keep editing', 'killerconversions' ) . '
'; } /** Read the published form, never the working draft or unsaved browser values. */ private static function published_setup( $id ) { $form = KCONV_Core::get_form( $id ); if ( ! $form || 'publish' !== $form['status'] ) { return; } $mail = KCONV_Mail::config( $form ); $mail_invalid = is_wp_error( $mail ); $mail_off = ! $mail_invalid && empty( $mail['enabled'] ); $delivery_paused = ! KCONV_Delivery_Guard::allowed(); $mail_state = $mail_invalid ? 'invalid' : ( $mail_off ? 'off' : ( $delivery_paused ? 'paused' : 'configured' ) ); $spam = KCONV_Spam::config( $form ); $spam_state = is_wp_error( $spam ) ? 'invalid' : ( empty( $spam['enabled'] ) ? 'off' : 'configured' ); echo '

' . esc_html__( 'Published settings', 'killerconversions' ) . '

' . esc_html__( 'These are the saved settings used by your published form. Edits below and saved drafts take effect only when you publish updates.', 'killerconversions' ) . '

' . esc_html__( 'Staff notifications', 'killerconversions' ) . '

'; if ( $mail_invalid ) { echo '

' . esc_html__( 'Needs attention', 'killerconversions' ) . '

' . esc_html( $mail->get_error_message() ) . '

'; } elseif ( $mail_off ) { echo '

' . esc_html__( 'Off — enquiries still appear in Inbox.', 'killerconversions' ) . '

'; } else { echo '

' . esc_html( $delivery_paused ? __( 'Paused — review this site’s delivery settings.', 'killerconversions' ) : __( 'Configured — delivery is not verified.', 'killerconversions' ) ) . '

'; if ( ! empty( $mail['recipient'] ) ) { /* translators: %s: Saved default staff email recipient. */ echo '

' . esc_html( sprintf( __( 'Default recipient: %s', 'killerconversions' ), $mail['recipient'] ) ) . '

'; } if ( ! empty( $mail['routes'] ) ) { echo '

' . esc_html( empty( $mail['recipient'] ) ? __( 'Routing rules choose the recipient from submitted answers. With no default recipient, unmatched notifications are not sent.', 'killerconversions' ) : __( 'Routing rules can choose another recipient from submitted answers.', 'killerconversions' ) ) . '

'; } } if ( $delivery_paused && ! $mail_off ) { echo '

' . esc_html__( 'Review paused delivery', 'killerconversions' ) . ' ' . esc_html__( '(new tab)', 'killerconversions' ) . '

'; } echo '
' . esc_html( $mail_off ? __( 'Set up email notifications', 'killerconversions' ) : __( 'Review email settings', 'killerconversions' ) ) . ''; if ( $mail_off ) { echo '
' . esc_html__( 'Use Inbox only', 'killerconversions' ) . '

' . esc_html__( 'Staff notifications are already off. Check Inbox regularly for new enquiries; no extra setting is needed.', 'killerconversions' ) . '

' . esc_html__( 'Open this form’s Inbox', 'killerconversions' ) . ' ' . esc_html__( '(new tab)', 'killerconversions' ) . '
'; } echo '

' . esc_html__( 'Visitor confirmations and other connections have separate settings.', 'killerconversions' ) . '

' . esc_html__( 'Spam protection', 'killerconversions' ) . '

' . esc_html__( 'Honeypot and rate limits active.', 'killerconversions' ) . '

'; if ( 'invalid' === $spam_state ) { echo '

' . esc_html__( 'Turnstile needs attention. Submissions are blocked until its settings are corrected or protection is saved as disabled.', 'killerconversions' ) . '

'; } elseif ( 'configured' === $spam_state ) { echo '

' . esc_html__( 'Turnstile configured. Check the published page to verify it works for visitors.', 'killerconversions' ) . '

'; } else { echo '

' . esc_html__( 'Optional Cloudflare Turnstile is off. Enabling it requires a Cloudflare site key and secret key. Included in Free.', 'killerconversions' ) . '

'; } echo '' . esc_html__( 'Review protection', 'killerconversions' ) . '

' . esc_html__( 'Entry retention', 'killerconversions' ) . '

'; $days = (int) $form['settings']['retention_days']; /* translators: %s: Number of days before new entries are scheduled for removal. */ echo '

' . esc_html( sprintf( _n( 'New entries scheduled for removal after %s day.', 'New entries scheduled for removal after %s days.', $days, 'killerconversions' ), number_format_i18n( $days ) ) ) . '

' . esc_html__( 'Cleanup depends on WordPress cron. Changing this period affects new entries; email copies and connected services are separate.', 'killerconversions' ) . '

' . esc_html__( 'Change retention', 'killerconversions' ) . '
'; } private static function library() { global $wpdb; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only library() navigation/filter input; values are normalized or allowlisted and escaped at output. No state change occurs from this read; mutations use separate capability/nonce guards. $search = isset( $_GET['s'] ) && is_string( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only library() navigation/filter input; values are normalized or allowlisted and escaped at output. No state change occurs from this read; mutations use separate capability/nonce guards. $state = isset( $_GET['state'] ) && is_string( $_GET['state'] ) && in_array( $_GET['state'], array( 'draft', 'publish' ), true ) ? sanitize_key( wp_unslash( $_GET['state'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only library() navigation/filter input; values are normalized or allowlisted and escaped at output. No state change occurs from this read; mutations use separate capability/nonce guards. $page = isset( $_GET['paged'] ) && is_scalar( $_GET['paged'] ) ? max( 1, absint( $_GET['paged'] ) ) : 1; $query = new WP_Query( array( 'post_type' => 'kconv_form', 'post_parent' => 0, 'post_status' => $state ? $state : array( 'publish', 'draft' ), 'posts_per_page' => 12, 'paged' => $page, 's' => $search, 'orderby' => 'modified', 'order' => 'DESC' ) ); $counts = (object) array( 'publish' => 0, 'draft' => 0 ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Administrator-only library counts and aggregates use the plugin form post type and private entries; paged IDs become absint values and one generated %d per ID. foreach ( $wpdb->get_results( $wpdb->prepare( 'SELECT post_status,COUNT(*) AS total FROM %i WHERE post_type=%s AND post_parent=0 GROUP BY post_status', $wpdb->posts, 'kconv_form' ) ) as $count ) { $counts->{$count->post_status} = (int) $count->total; } // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Administrator-only library counts and aggregates use the plugin form post type and private entries; paged IDs become absint values and one generated %d per ID. $entries = $wpdb->get_row( $wpdb->prepare( 'SELECT COUNT(*) AS total, SUM(CASE WHEN is_read=0 THEN 1 ELSE 0 END) AS unread FROM %i', KCONV_Core::table( 'entries' ) ), ARRAY_A ); echo '

' . esc_html__( 'Your forms', 'killerconversions' ) . '

' . esc_html__( 'Build forms, collect responses and follow up.', 'killerconversions' ) . '

+ ' . esc_html__( 'Create form', 'killerconversions' ) . '' . ( class_exists( 'KCONV_Pro_Blueprint_Templates' ) ? '' . esc_html__( 'Pro starters', 'killerconversions' ) . '' : '' ) . '
'; echo '
'; foreach ( array( array( __( 'Published forms', 'killerconversions' ), (int) $counts->publish ), array( __( 'Drafts', 'killerconversions' ), (int) $counts->draft ), array( __( 'Retained entries', 'killerconversions' ), (int) $entries['total'] ), array( __( 'Unread entries', 'killerconversions' ), (int) $entries['unread'] ) ) as $stat ) { echo '
' . esc_html( $stat[0] ) . '
' . esc_html( number_format_i18n( $stat[1] ) ) . '
'; } echo '
'; foreach ( array( '' => __( 'All forms', 'killerconversions' ), 'publish' => __( 'Published', 'killerconversions' ), 'draft' => __( 'Drafts', 'killerconversions' ) ) as $value => $label ) { echo '' . esc_html( $label ) . ''; } echo '
'; $totals = array(); if ( $query->posts ) { $ids = array_map( 'absint', wp_list_pluck( $query->posts, 'ID' ) ); $placeholders = implode( ',', array_fill( 0, count( $ids ), '%d' ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Administrator-only library counts and aggregates use the plugin form post type and private entries; paged IDs become absint values and one generated %d per ID. The immediately preceding statement assigns the result of prepare() to $sql. $sql = $wpdb->prepare( "SELECT form_id, COUNT(*) AS total FROM %i WHERE form_id IN ($placeholders) GROUP BY form_id", array_merge( array( KCONV_Core::table( 'entries' ) ), $ids ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared -- Administrator-only library counts and aggregates use the plugin form post type and private entries; paged IDs become absint values and one generated %d per ID. Administrator-only library counts and aggregates use the plugin form post type and private entries; paged IDs become absint values and one generated %d per ID. The immediately preceding statement assigns the result of prepare() to $sql. foreach ( $wpdb->get_results( $sql, ARRAY_A ) as $row ) { $totals[ $row['form_id'] ] = (int) $row['total']; } } foreach ( $query->posts as $post ) { $form = KCONV_Core::get_form( $post->ID ); $url = admin_url( 'admin.php?page=killerconversions&edit=' . $post->ID ); /* translators: 1: Number of form fields, 2: localized last-modified date. */ echo '
' . esc_html( 'publish' === $post->post_status ? __( 'Published', 'killerconversions' ) : __( 'Draft', 'killerconversions' ) ) . '' . ( KCONV_Authoring::working_id( $post->ID ) ? '' . esc_html__( 'Draft changes', 'killerconversions' ) . '' : '' ) . '

' . esc_html( $post->post_title ) . '

' . esc_html( sprintf( __( '%1$d fields · Updated %2$s', 'killerconversions' ), $form ? count( $form['fields'] ) : 0, get_the_modified_date( 'M j', $post ) ) ) . '

' . esc_html( number_format_i18n( isset( $totals[ $post->ID ] ) ? $totals[ $post->ID ] : 0 ) ) . '' . esc_html__( 'retained entries', 'killerconversions' ) . '
' . esc_html__( 'Embed & duplicate', 'killerconversions' ) . '[killerconversions id="' . esc_html( $post->ID ) . '"]
'; wp_nonce_field( 'kconv_clone_form' ); echo '' . esc_html__( 'Copies fields and flow. Set up destinations separately.', 'killerconversions' ) . '
'; } if ( ! $query->posts ) { echo '

' . esc_html__( 'Room for your next great question.', 'killerconversions' ) . '

' . esc_html__( 'Create a form or try a different search.', 'killerconversions' ) . '

'; } echo '
' . wp_kses_post( (string) paginate_links( array( 'base' => add_query_arg( 'paged', '%#%' ), 'current' => $page, 'total' => $query->max_num_pages ) ) ) . '
'; } private static function task_picker() { echo '
' . esc_html__( 'Back to forms', 'killerconversions' ) . '

' . esc_html__( 'What should this form help you do?', 'killerconversions' ) . '

' . esc_html__( 'Choose a starting point. You can change every question before publishing.', 'killerconversions' ) . '

'; $tasks = array( 'contact' => array( __( 'Collect an enquiry', 'killerconversions' ), __( 'Start with name, email and a message.', 'killerconversions' ) ), 'estimate' => array( __( 'Create an estimate', 'killerconversions' ), __( 'Start with your service, quantity and price.', 'killerconversions' ) ), 'application' => array( __( 'Accept an application', 'killerconversions' ), __( 'Ask the right questions and choose supporting files.', 'killerconversions' ) ), 'crm' => array( __( 'Connect your tools', 'killerconversions' ), __( 'Send selected answers to HubSpot, Slack or a signed webhook.', 'killerconversions' ) ) ); foreach ( $tasks as $key => $task ) { $pro = 'crm' === $key; $starter = array( 'contact' => 'contact', 'estimate' => 'simple_estimate', 'application' => 'document_application' ); echo ''; } echo '

' . esc_html__( 'Browse the Free templates', 'killerconversions' ) . '

'; } private static function editor( $id ) { $context = KCONV_Authoring::context( $id ); $form = $context['form']; $public_id = $context['root']; if ( $id && ! $form ) { echo '

' . esc_html__( 'Form not found.', 'killerconversions' ) . '

'; return; } $templates = KCONV_Templates::all(); if ( ! $form ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only editor() navigation/filter input; values are normalized or allowlisted and escaped at output. No state change occurs from this read; mutations use separate capability/nonce guards. $key = isset( $_GET['template'] ) && is_string( $_GET['template'] ) ? sanitize_key( wp_unslash( $_GET['template'] ) ) : 'contact'; $template = isset( $templates[ $key ] ) ? $templates[ $key ] : $templates['contact']; $form = array_merge( array( 'id' => 0, 'title' => $template['name'], 'status' => 'draft', 'settings' => array_merge( KCONV_Core::defaults(), array_intersect_key( $template, array_flip( array( 'submit_label', 'success_message' ) ) ) ), 'fields' => $template['fields'] ), array_intersect_key( $template, array_flip( array( 'estimate', 'steps' ) ) ) ); } $types = array( 'repeater' => array( __( 'Repeatable group', 'killerconversions' ), '+', __( 'Add another person or item', 'killerconversions' ) ), 'text' => array( __( 'Short text', 'killerconversions' ), 'Aa', __( 'A name or a short answer', 'killerconversions' ) ), 'email' => array( __( 'Email', 'killerconversions' ), '@', __( 'Make it easy to follow up', 'killerconversions' ) ), 'textarea' => array( __( 'Long text', 'killerconversions' ), '¶', __( 'A message or detailed answer', 'killerconversions' ) ), 'select' => array( __( 'Dropdown', 'killerconversions' ), '⌄', __( 'Keep a list of choices compact', 'killerconversions' ) ), 'radio' => array( __( 'Single choice', 'killerconversions' ), '◉', __( 'Choose exactly one answer', 'killerconversions' ) ), 'checkbox' => array( __( 'Checkbox', 'killerconversions' ), '✓', __( 'An option or consent choice', 'killerconversions' ) ), 'number' => array( __( 'Number', 'killerconversions' ), '#', __( 'A quantity or a budget', 'killerconversions' ) ), 'tel' => array( __( 'Phone', 'killerconversions' ), 'Tel', __( 'A contact number', 'killerconversions' ) ), 'url' => array( __( 'Website', 'killerconversions' ), 'URL', __( 'A link to a website', 'killerconversions' ) ), 'checkbox_group' => array( __( 'Multiple choice', 'killerconversions' ), '☑', __( 'Choose more than one answer', 'killerconversions' ) ), 'date' => array( __( 'Date', 'killerconversions' ), '▦', __( 'Pick a calendar date', 'killerconversions' ) ), 'time' => array( __( 'Time', 'killerconversions' ), '◷', __( 'Pick a time of day', 'killerconversions' ) ), 'hidden' => array( __( 'Hidden value', 'killerconversions' ), '{ }', __( 'A fixed value saved with entries', 'killerconversions' ) ) ); if ( class_exists( 'KCONV_Calculations' ) ) { $types['calculated'] = array( __( 'Calculated estimate', 'killerconversions' ), '=', __( 'A formula or quote total', 'killerconversions' ) ); } if ( class_exists( 'KCONV_Uploads' ) ) { $types['file'] = array( __( 'File upload', 'killerconversions' ), '↑', __( 'Private attachments · Pro', 'killerconversions' ) ); } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only editor() navigation/filter input; values are normalized or allowlisted and escaped at output. No state change occurs from this read; mutations use separate capability/nonce guards. echo '
'; wp_nonce_field( 'kconv_save' ); KCONV_Authoring::hidden( $context, $form ); echo '
' . esc_html( is_rtl() ? '→' : '←' ) . '
'; if ( $context['live'] ) { echo '

' . esc_html( $context['working'] ? __( 'Editing a saved draft. Only Publish updates changes the live form.', 'killerconversions' ) : __( 'Live form. Save draft keeps it unchanged; Publish updates changes it.', 'killerconversions' ) ) . '

' . ( $context['working'] ? '' : '' ) . '
'; } echo '

'; echo '
' . esc_html__( 'Select a question to edit', 'killerconversions' ) . '

' . esc_html__( 'Edit form ·', 'killerconversions' ) . ' ' . esc_html__( 'Your website theme may affect the published appearance.', 'killerconversions' ) . '

'; foreach ( $tabs as $key => $label ) { if ( 'build' !== $key ) { echo '
' . esc_html( strtoupper( $label ) ) . '

' . esc_html( array( 'logic' => __( 'Advanced logic', 'killerconversions' ), 'connect' => __( 'Notifications & integrations', 'killerconversions' ), 'measure' => __( 'Measurement & outcomes', 'killerconversions' ), 'style' => __( 'Appearance & layout', 'killerconversions' ), 'settings' => __( 'Form settings', 'killerconversions' ) )[ $key ] ) . '

'; } } echo '

' . esc_html__( 'Email notifications', 'killerconversions' ) . '

' . esc_html__( 'Choose who hears about a new response.', 'killerconversions' ) . '

' . esc_html__( 'Leave blank to disable the default staff notification. Entries still appear in Inbox. Visitor confirmations, Pro routing and other connections have separate settings. Mail acceptance does not confirm delivery to the recipient’s inbox.', 'killerconversions' ) . '

' . esc_html__( 'Completion & privacy', 'killerconversions' ) . '

'; foreach ( array( 'submit_label' => __( 'Submit button', 'killerconversions' ), 'success_message' => __( 'Success message', 'killerconversions' ), 'retention_days' => __( 'Keep entries for this many days', 'killerconversions' ) ) as $key => $label ) { echo ''; } echo '

' . esc_html__( 'Retention changes apply to new entries. Scheduled cleanup depends on WordPress cron.', 'killerconversions' ) . '

' . esc_html__( 'Form quality check', 'killerconversions' ) . '

' . esc_html__( 'Practical suggestions to improve clarity.', 'killerconversions' ) . '

' . esc_html__( 'Draft recovery on this device', 'killerconversions' ) . '
'; echo '

' . esc_html__( 'Form appearance', 'killerconversions' ) . '

' . esc_html__( 'Set the style visitors see on your website. Existing forms keep their original appearance until you choose a different preset.', 'killerconversions' ) . '

'; foreach ( array( 'appearance_style' => array( __( 'Form style', 'killerconversions' ), array( 'classic' => __( 'Classic — original form styling', 'killerconversions' ), 'studio' => __( 'Studio — a framed form with a soft surface', 'killerconversions' ), 'minimal' => __( 'Minimal — clean lines and open space', 'killerconversions' ) ) ), 'appearance_accent' => array( __( 'Accent color', 'killerconversions' ), array( 'forest' => __( 'Forest green', 'killerconversions' ), 'indigo' => __( 'Indigo', 'killerconversions' ), 'ink' => __( 'Ink', 'killerconversions' ) ) ), 'appearance_density' => array( __( 'Spacing', 'killerconversions' ), array( 'comfortable' => __( 'Comfortable', 'killerconversions' ), 'compact' => __( 'Compact', 'killerconversions' ) ) ), 'appearance_font' => array( __( 'Typography', 'killerconversions' ), array( 'inherit' => __( 'Use website font', 'killerconversions' ), 'system' => __( 'System sans serif', 'killerconversions' ), 'serif' => __( 'Serif', 'killerconversions' ) ) ), 'appearance_width' => array( __( 'Form width', 'killerconversions' ), array( 'standard' => __( 'Standard · 700px', 'killerconversions' ), 'wide' => __( 'Wide · 960px', 'killerconversions' ), 'full' => __( 'Fill available space', 'killerconversions' ) ) ), 'appearance_radius' => array( __( 'Corners', 'killerconversions' ), array( 'default' => __( 'Follow style preset', 'killerconversions' ), 'square' => __( 'Square', 'killerconversions' ), 'rounded' => __( 'Rounded', 'killerconversions' ) ) ) ) as $key => $setting ) { $value = isset( $form['settings'][ $key ] ) ? $form['settings'][ $key ] : key( $setting[1] ); echo ''; } echo '
'; foreach ( array( 'public_heading' => array( __( 'Public form heading (optional)', 'killerconversions' ), 160, __( 'Leave empty for no heading', 'killerconversions' ) ), 'public_description' => array( __( 'Introduction (optional)', 'killerconversions' ), 600, __( 'Help visitors understand what happens next', 'killerconversions' ) ), 'appearance_color' => array( __( 'Custom accent color (optional)', 'killerconversions' ), 7, '#155c48' ) ) as $key => $info ) { $value = isset( $form['settings'][ $key ] ) ? $form['settings'][ $key ] : ''; echo ''; } echo '

' . esc_html__( 'Use a six-digit hex color or leave blank to use the selected accent. Button text automatically uses black or white for readable contrast.', 'killerconversions' ) . '

' . esc_html__( 'Forest accent', 'killerconversions' ) . '

' . esc_html__( 'Arrange questions into columns', 'killerconversions' ) . '

' . esc_html__( 'Select a field in Build and choose Full width or Half width in its properties. Half-width questions sit side by side when space allows, then stack on narrow screens.', 'killerconversions' ) . '

' . esc_html__( 'Try form uses your current questions. Save draft keeps edits private; Publish form or Publish updates changes the visitor version.', 'killerconversions' ) . '

'; do_action( 'kconv_admin_form_after', $form ); echo '
' . esc_html__( 'MAKE IT YOURS', 'killerconversions' ) . '

' . esc_html__( 'Skip the blank canvas.', 'killerconversions' ) . '

' . esc_html__( 'Start with a useful structure. Replacing fields keeps your entries and delivery settings; review any existing logic and mappings.', 'killerconversions' ) . '

'; $n = 0; foreach ( $templates as $key => $template ) { $n++; echo '
0' . esc_html( $n ) . '

' . esc_html( $template['name'] ) . '

' . esc_html( $template['description'] ) . '

'; } echo '

' . esc_html__( 'Give your form a home.', 'killerconversions' ) . '

'; if ( $public_id ) { echo '

' . esc_html__( 'In the WordPress page editor, add the', 'killerconversions' ) . ' ' . esc_html__( 'KillerConversions Form', 'killerconversions' ) . ' ' . esc_html__( 'block and choose this form. Or paste this shortcode into a Shortcode block.', 'killerconversions' ) . '

' . esc_html__( 'Publish your form before collecting responses. Saving draft changes does not update the published form.', 'killerconversions' ) . '

'; } else { echo '

' . esc_html__( 'Save a draft to get its shortcode. Publish the form before collecting responses on your page.', 'killerconversions' ) . '

'; } echo '

' . esc_html__( 'Quick actions', 'killerconversions' ) . '

' . esc_html__( '↑ ↓ to move · Enter to choose · Esc to close', 'killerconversions' ) . '

'; } public static function save() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You cannot manage forms.', 'killerconversions' ), '', array( 'response' => 403 ) ); } if ( ! isset( $_POST['_wpnonce'] ) || ! is_string( $_POST['_wpnonce'] ) ) { wp_die( esc_html__( 'You cannot manage forms.', 'killerconversions' ), '', array( 'response' => 403 ) ); } $nonce = sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ); if ( ! wp_verify_nonce( $nonce, 'kconv_save' ) ) { wp_die( esc_html__( 'You cannot manage forms.', 'killerconversions' ), '', array( 'response' => 403 ) ); } $result = KCONV_Authoring::save( wp_unslash( $_POST ) ); if ( is_wp_error( $result ) ) { wp_die( esc_html( implode( ' ', $result->get_error_messages() ) ), '', array( 'response' => 400, 'back_link' => true ) ); } wp_safe_redirect( $result['url'] ); exit; } public static function entries() { KCONV_Entries::screen(); } public static function delete_entry() { $id = isset( $_POST['entry_id'] ) && is_scalar( $_POST['entry_id'] ) ? absint( $_POST['entry_id'] ) : 0; if ( ! KCONV_Core::can( 'kconv_delete_entries' ) ) { wp_die( esc_html__( 'You do not have permission for this action.', 'killerconversions' ), '', array( 'response' => 403 ) ); } if ( ! isset( $_POST['_wpnonce'] ) || ! is_string( $_POST['_wpnonce'] ) ) { wp_die( esc_html__( 'You do not have permission for this action.', 'killerconversions' ), '', array( 'response' => 403 ) ); } $nonce = sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ); if ( ! wp_verify_nonce( $nonce, 'kconv_delete_entry_' . $id ) ) { wp_die( esc_html__( 'You do not have permission for this action.', 'killerconversions' ), '', array( 'response' => 403 ) ); } if ( ! KCONV_Entry_Access::assert_ids( array( $id ), 'delete' ) ) { wp_die( esc_html__( 'You do not have access to this entry.', 'killerconversions' ), '', array( 'response' => 403 ) ); } if ( ! KCONV_Core::delete_entries( array( $id ), true ) ) { wp_die( esc_html__( 'Entry deletion failed and was rolled back. Please retry after storage is repaired.', 'killerconversions' ), '', array( 'response' => 503 ) ); } wp_safe_redirect( admin_url( 'admin.php?page=kconv-entries' ) ); exit; } public static function csv_value( $value ) { $value = KCONV_Core::value_text( $value ); return preg_match( '/^[\s]*[=+@\-]/u', $value ) ? "'" . $value : $value; } public static function export() { if ( ! KCONV_Core::can( 'kconv_export_entries' ) ) { wp_die( esc_html__( 'You do not have permission for this action.', 'killerconversions' ), '', array( 'response' => 403 ) ); } if ( ! isset( $_POST['_wpnonce'] ) || ! is_string( $_POST['_wpnonce'] ) ) { wp_die( esc_html__( 'You do not have permission for this action.', 'killerconversions' ), '', array( 'response' => 403 ) ); } $nonce = sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ); if ( ! wp_verify_nonce( $nonce, 'kconv_export' ) ) { wp_die( esc_html__( 'You do not have permission for this action.', 'killerconversions' ), '', array( 'response' => 403 ) ); } KCONV_Entries::export( wp_unslash( $_POST ) ); } }