__('Single line', 'ai-eshop-optimizer'), 'textarea' => __('Paragraph', 'ai-eshop-optimizer'), 'radio' => __('Choose one', 'ai-eshop-optimizer'), 'checkbox' => __('Choose any', 'ai-eshop-optimizer'), 'select' => __('Drop-down', 'ai-eshop-optimizer'), 'email' => __('E-mail', 'ai-eshop-optimizer'), 'url' => __('Web address', 'ai-eshop-optimizer'), 'tel' => __('Telephone', 'ai-eshop-optimizer'), 'consent' => __('Consent', 'ai-eshop-optimizer'), ); } public static function is_choice_type($type) { return in_array($type, array('radio', 'checkbox', 'select'), true); } public static function is_multi_type($type) { return 'checkbox' === $type; } public static function max_length($type) { switch ((string) $type) { case 'textarea': return 5000; case 'email': return 254; case 'url': return 500; case 'tel': return 40; default: return 200; } } const WIDTHS = array('full', 'half'); const PROPS = array('placeholder', 'help', 'missing', 'invalid', 'autocomplete', 'prompt', 'rows'); const MORE = array('help', 'missing', 'invalid', 'autocomplete', 'prompt', 'rows'); public static function normalise_field(array $f, $sanitise = false) { $types = self::field_types(); $type = isset($f['type']) ? (string) $f['type'] : ''; $key = isset($f['key']) ? sanitize_key((string) $f['key']) : ''; if ('' === $key || !isset($types[$type])) { return null; } $text = static function ($v) use ($sanitise) { $v = is_scalar($v) ? (string) $v : ''; return $sanitise ? sanitize_text_field($v) : $v; }; $field = array( 'key' => $key, 'type' => $type, 'label' => array_key_exists('label', $f) ? $text($f['label']) : $key, 'required' => !empty($f['required']), 'choices' => array(), 'width' => isset($f['width']) && in_array((string) $f['width'], self::WIDTHS, true) ? (string) $f['width'] : 'full', ); foreach (self::PROPS as $prop) { $field[$prop] = 'rows' === $prop ? max(0, min(30, (int) ($f['rows'] ?? 0))) : $text($f[$prop] ?? ''); } $field['autocomplete'] = (string) preg_replace('/[^a-z0-9 -]/', '', strtolower($field['autocomplete'])); if (self::is_choice_type($type)) { $choices = isset($f['choices']) ? $f['choices'] : array(); if (is_string($choices)) { $choices = preg_split('/\r\n|\r|\n/', $choices); } foreach ((array) $choices as $k => $c) { if (is_array($c)) { $ck = isset($c['key']) ? (string) $c['key'] : ''; $cl = isset($c['label']) ? (string) $c['label'] : $ck; } elseif (is_string($k)) { $ck = $k; $cl = (string) $c; } elseif (false !== strpos((string) $c, '|')) { list($ck, $cl) = array_map('trim', explode('|', (string) $c, 2)); } else { $ck = (string) $c; $cl = (string) $c; } $ck = sanitize_key($ck); if ('' === $ck || '' === trim($cl)) { continue; } $field['choices'][$ck] = $text($cl); } if (empty($field['choices'])) { return null; } } return $field; } public static function settings($form_id) { $raw = get_post_meta((int) $form_id, self::META_SETTINGS, true); $arr = is_string($raw) ? json_decode($raw, true) : $raw; $arr = is_array($arr) ? $arr : array(); $out = array(); foreach (self::setting_defaults() as $k => $default) { $v = isset($arr[$k]) && is_scalar($arr[$k]) ? trim((string) $arr[$k]) : ''; $out[$k] = '' !== $v ? $v : $default; } return $out; } public static function setting_defaults() { return array( 'button' => __('Send', 'ai-eshop-optimizer'), 'sending' => __('Sending…', 'ai-eshop-optimizer'), 'success' => __('Thank you. We have your message and will be in touch shortly.', 'ai-eshop-optimizer'), 'check' => __('Please check the fields marked above.', 'ai-eshop-optimizer'), 'notify_to' => '', 'subject' => '', 'ack_subject' => '', 'ack_body' => '', 'cta_label' => '', 'cta_opens' => 'modal', 'cta_page' => '', 'cta_contact' => '', 'cta_location' => '', 'cta_icons' => '', ); } const TRANSLATABLE = array('cta_label', 'button', 'sending', 'success', 'check'); const STRINGS_DOMAIN = 'Rooster One forms'; public static function words($form_id) { $s = self::settings($form_id); $raw = get_post_meta((int) $form_id, self::META_SETTINGS, true); $own = is_string($raw) ? json_decode($raw, true) : array(); $own = is_array($own) ? $own : array(); foreach (self::TRANSLATABLE as $k) { $v = isset($own[$k]) && is_scalar($own[$k]) ? trim((string) $own[$k]) : ''; if ('' === $v) { continue; } if (has_filter('wpml_translate_single_string')) { $s[$k] = (string) apply_filters('wpml_translate_single_string', $v, self::STRINGS_DOMAIN, 'form ' . (int) $form_id . ': ' . $k); } elseif (function_exists('pll__')) { $s[$k] = (string) pll__($v); } } return $s; } public static function register_strings($form_id) { $raw = get_post_meta((int) $form_id, self::META_SETTINGS, true); $own = is_string($raw) ? json_decode($raw, true) : array(); $own = is_array($own) ? $own : array(); foreach (self::TRANSLATABLE as $k) { $v = isset($own[$k]) && is_scalar($own[$k]) ? trim((string) $own[$k]) : ''; if ('' !== $v) { do_action('wpml_register_single_string', self::STRINGS_DOMAIN, 'form ' . (int) $form_id . ': ' . $k, $v); } } } public static function register_polylang_strings() { if (!function_exists('pll_register_string') || !isset($_GET['page']) || 'mlang_strings' !== sanitize_key(wp_unslash($_GET['page']))) { return; } foreach (get_posts(array('post_type' => self::POST_TYPE, 'post_status' => 'publish', 'numberposts' => 200, 'fields' => 'ids', 'no_found_rows' => true)) as $id) { $raw = get_post_meta((int) $id, self::META_SETTINGS, true); $own = is_string($raw) ? json_decode($raw, true) : array(); foreach (self::TRANSLATABLE as $k) { $v = is_array($own) && isset($own[$k]) && is_scalar($own[$k]) ? trim((string) $own[$k]) : ''; if ('' !== $v) { pll_register_string('form ' . (int) $id . ': ' . $k, $v, self::STRINGS_DOMAIN); } } } } public static function save_definition($post_id, array $fields, array $settings = array(), $notify = null) { $post_id = (int) $post_id; $clean = self::clean_fields($fields); $json = (string) wp_json_encode($clean, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); $changed = false; if ((string) get_post_meta($post_id, self::META_FIELDS, true) !== $json) { update_post_meta($post_id, self::META_FIELDS, wp_slash($json)); $changed = true; } $kept = array(); $defaults = self::setting_defaults(); foreach (array_keys($defaults) as $k) { $v = isset($settings[$k]) && is_scalar($settings[$k]) ? (string) $settings[$k] : ''; $v = in_array($k, array('ack_body'), true) ? sanitize_textarea_field($v) : sanitize_text_field($v); if ('cta_page' === $k) { $v = absint($v) > 0 ? (string) absint($v) : ''; } elseif ('cta_opens' === $k) { $v = 'page' === $v ? 'page' : ''; } elseif ('cta_contact' === $k) { $v = class_exists('AIEO_Location_Contact') ? implode(',', AIEO_Location_Contact::parse($v)) : ''; } elseif ('cta_location' === $k) { $v = sanitize_key($v); } elseif ('cta_icons' === $k) { $v = 'hide' === $v ? 'hide' : ''; } if ('' !== trim($v)) { $kept[$k] = trim($v); } } $sjson = $kept ? (string) wp_json_encode($kept, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : ''; if ((string) get_post_meta($post_id, self::META_SETTINGS, true) !== $sjson) { if ('' === $sjson) { delete_post_meta($post_id, self::META_SETTINGS); } else { update_post_meta($post_id, self::META_SETTINGS, wp_slash($sjson)); } $changed = true; } if (null !== $notify) { $want = $notify ? 'email' : ''; if ((string) get_post_meta($post_id, self::META_NOTIFY, true) !== $want) { if ('' === $want) { delete_post_meta($post_id, self::META_NOTIFY); } else { update_post_meta($post_id, self::META_NOTIFY, $want); } $changed = true; } } return $changed; } public static function clean_fields(array $fields) { $out = array(); $seen = array(); foreach ($fields as $row) { $f = is_array($row) ? self::normalise_field($row, true) : null; if (!$f || isset($seen[$f['key']])) { continue; } $seen[$f['key']] = true; $stored = array('key' => $f['key'], 'type' => $f['type'], 'label' => $f['label'], 'required' => $f['required']); if ($f['choices']) { $stored['choices'] = $f['choices']; } if ('full' !== $f['width']) { $stored['width'] = $f['width']; } foreach (self::PROPS as $prop) { if ('' !== (string) $f[$prop] && 0 !== $f[$prop]) { $stored[$prop] = $f[$prop]; } } $out[] = $stored; } return $out; } public static function register() { if (self::$registered) { return; } self::$registered = true; add_action('init', array(__CLASS__, 'register_cpt')); add_action('add_meta_boxes', array(__CLASS__, 'add_metabox')); add_action('admin_enqueue_scripts', array(__CLASS__, 'enqueue_editor_assets')); add_action('save_post_' . self::POST_TYPE, array(__CLASS__, 'save_metabox'), 10, 2); add_action('admin_init', array(__CLASS__, 'register_polylang_strings')); add_action('admin_init', array(__CLASS__, 'maybe_install_tables'), 5); } public static function register_cpt() { register_post_type(self::POST_TYPE, array( 'labels' => array( 'name' => __('Forms', 'ai-eshop-optimizer'), 'singular_name' => __('Form', 'ai-eshop-optimizer'), 'add_new_item' => __('Add form', 'ai-eshop-optimizer'), 'edit_item' => __('Edit form', 'ai-eshop-optimizer'), ), 'public' => false, 'show_ui' => true, 'show_in_menu' => false, 'show_in_rest' => false, 'exclude_from_search' => true, 'supports' => array('title'), 'capability_type' => 'post', 'map_meta_cap' => true, )); } public static function maybe_install_tables() { if (get_option(self::DB_VERSION_OPTION) === self::DB_VERSION) { return; } if (!AIEO_Schema_Lock::acquire(self::DB_VERSION_OPTION)) { return; } global $wpdb; $charset = aieo_get_charset_collate(); $e = $wpdb->prefix . 'aieo_form_entries'; $v = $wpdb->prefix . 'aieo_form_entry_values'; $failed = 0; $q = function ($sql) use ($wpdb, &$failed) { if (false === $wpdb->query($sql)) { $failed++; } }; $q("CREATE TABLE IF NOT EXISTS {$e} ( id bigint(20) unsigned NOT NULL AUTO_INCREMENT, form_id bigint(20) unsigned NOT NULL, user_id bigint(20) unsigned NOT NULL DEFAULT 0, order_id bigint(20) unsigned NOT NULL DEFAULT 0, source varchar(40) NOT NULL DEFAULT '', surface varchar(64) DEFAULT NULL, external_id bigint(20) unsigned DEFAULT NULL, eponymous_id bigint(20) unsigned NOT NULL DEFAULT 0, run_id bigint(20) unsigned NOT NULL DEFAULT 0, ip_hash char(64) NOT NULL DEFAULT '', status varchar(20) NOT NULL DEFAULT 'new', created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_external (surface, external_id), KEY idx_form_created (form_id, created_at), KEY idx_order (order_id), KEY idx_status (form_id, status), KEY idx_surface (surface, created_at), KEY idx_eponymous (eponymous_id, created_at), KEY idx_run (run_id) ) {$charset}"); self::add_column($q, $e, 'surface', "ALTER TABLE {$e} ADD COLUMN surface varchar(64) DEFAULT NULL AFTER source"); self::add_column($q, $e, 'external_id', "ALTER TABLE {$e} ADD COLUMN external_id bigint(20) unsigned DEFAULT NULL AFTER surface"); self::add_index($q, $e, 'uk_external', "ALTER TABLE {$e} ADD UNIQUE KEY uk_external (surface, external_id)"); self::add_index($q, $e, 'idx_surface', "ALTER TABLE {$e} ADD KEY idx_surface (surface, created_at)"); self::add_column($q, $e, 'eponymous_id', "ALTER TABLE {$e} ADD COLUMN eponymous_id bigint(20) unsigned NOT NULL DEFAULT 0 AFTER external_id"); self::add_column($q, $e, 'run_id', "ALTER TABLE {$e} ADD COLUMN run_id bigint(20) unsigned NOT NULL DEFAULT 0 AFTER eponymous_id"); self::add_index($q, $e, 'idx_eponymous', "ALTER TABLE {$e} ADD KEY idx_eponymous (eponymous_id, created_at)"); self::add_index($q, $e, 'idx_run', "ALTER TABLE {$e} ADD KEY idx_run (run_id)"); $q("CREATE TABLE IF NOT EXISTS {$v} ( id bigint(20) unsigned NOT NULL AUTO_INCREMENT, entry_id bigint(20) unsigned NOT NULL, field_key varchar(64) NOT NULL, disposition varchar(12) NOT NULL DEFAULT 'answered', choice_key varchar(64) DEFAULT NULL, value_num decimal(14,4) DEFAULT NULL, value_text longtext, PRIMARY KEY (id), KEY idx_entry (entry_id), KEY idx_field (field_key, choice_key), KEY idx_disposition (field_key, disposition) ) {$charset}"); self::add_column($q, $e, 'page_url', "ALTER TABLE {$e} ADD COLUMN page_url varchar(2048) DEFAULT NULL AFTER source"); self::add_column($q, $e, 'seen_before', "ALTER TABLE {$e} ADD COLUMN seen_before text DEFAULT NULL AFTER page_url"); self::add_column($q, $e, 'location', "ALTER TABLE {$e} ADD COLUMN location varchar(64) DEFAULT NULL AFTER seen_before"); if (0 === $failed) { update_option(self::DB_VERSION_OPTION, self::DB_VERSION, false); } } private static function add_column($q, $table, $column, $sql) { global $wpdb; $has = (int) $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s AND COLUMN_NAME = %s", $table, $column )); if (!$has) { $q($sql); } } private static function add_index($q, $table, $index, $sql) { global $wpdb; $has = (int) $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s AND INDEX_NAME = %s", $table, $index )); if (!$has) { $q($sql); } } public static function entries_table() { global $wpdb; return $wpdb->prefix . 'aieo_form_entries'; } public static function entry_values_table() { global $wpdb; return $wpdb->prefix . 'aieo_form_entry_values'; } public static function fields($form_id) { $raw = get_post_meta((int) $form_id, self::META_FIELDS, true); $arr = is_string($raw) ? json_decode($raw, true) : $raw; if (!is_array($arr)) { return array(); } $out = array(); foreach ($arr as $f) { $field = is_array($f) ? self::normalise_field($f) : null; if ($field) { $out[] = $field; } } return $out; } public static function add_metabox() { add_meta_box( 'aieo-form-fields', __('Fields', 'ai-eshop-optimizer'), array(__CLASS__, 'render_metabox'), self::POST_TYPE, 'normal', 'high' ); add_meta_box( 'aieo-form-settings', __('Words and notification', 'ai-eshop-optimizer'), array(__CLASS__, 'render_settings_box'), self::POST_TYPE, 'normal', 'high' ); add_meta_box( 'aieo-form-button', __('Button', 'ai-eshop-optimizer'), array(__CLASS__, 'render_button_box'), self::POST_TYPE, 'normal', 'high' ); } public static function render_button_box($post) { rooster_admin_builders(); $ui = '\Rooster\Admin\Ui'; $raw = get_post_meta($post->ID, self::META_SETTINGS, true); $stored = is_string($raw) ? json_decode($raw, true) : array(); $stored = is_array($stored) ? $stored : array(); $pages = array('' => __('— Choose a page —', 'ai-eshop-optimizer')); foreach (get_pages(array('sort_column' => 'post_title', 'post_status' => 'publish', 'number' => 300)) as $page) { $pages[(string) $page->ID] = '' !== $page->post_title ? $page->post_title : '#' . $page->ID; } $slug = '' !== (string) $post->post_name ? (string) $post->post_name : (string) $post->ID; $html = '
'; $html .= $ui::field_row(array( 'id' => 'aieo-form-cta-label', 'label' => __('Button words', 'ai-eshop-optimizer'), 'control' => $ui::input('aieo_form_settings[cta_label]', (string) ($stored['cta_label'] ?? ''), array('id' => 'aieo-form-cta-label', 'placeholder' => (string) $post->post_title)), 'help' => __('Empty: the form\'s title. With WPML or Polylang, translate it under String Translation (Rooster One forms).', 'ai-eshop-optimizer'), )); $html .= $ui::field_row(array( 'id' => 'aieo-form-cta-opens', 'label' => __('The button opens', 'ai-eshop-optimizer'), 'control' => $ui::select('aieo_form_settings[cta_opens]', array( 'modal' => __('The form in a window', 'ai-eshop-optimizer'), 'page' => __('The page that holds the form', 'ai-eshop-optimizer'), ), 'page' === (string) ($stored['cta_opens'] ?? '') ? 'page' : 'modal', array('id' => 'aieo-form-cta-opens')), )); $html .= $ui::field_row(array( 'id' => 'aieo-form-cta-page', 'label' => __('The page', 'ai-eshop-optimizer'), 'control' => $ui::combo('aieo_form_settings[cta_page]', $pages, (string) ($stored['cta_page'] ?? ''), array('id' => 'aieo-form-cta-page')), 'help' => __('For "the page": the page where the form is placed. Under WPML or Polylang the visitor goes to its translation. Without a page the button opens the window.', 'ai-eshop-optimizer'), )); $locations = class_exists('AIEO_Location_Contact') ? AIEO_Location_Contact::choices() : array(); $main = class_exists('AIEO_Location_Contact') ? AIEO_Location_Contact::location('') : null; $html .= $ui::field_row(array( 'id' => 'aieo-form-cta-contact', 'group' => true, 'label' => __('Beside it', 'ai-eshop-optimizer'), 'control' => $ui::checks('aieo_form_settings[cta_contact]', array( 'call' => __('Call', 'ai-eshop-optimizer'), 'whatsapp' => 'WhatsApp', 'viber' => 'Viber', 'messenger' => 'Messenger', ), class_exists('AIEO_Location_Contact') ? AIEO_Location_Contact::parse((string) ($stored['cta_contact'] ?? '')) : array(), array('inline' => true)), 'help' => $locations ? __('A button for each one the location has (its phone, and the chat channels set under Google Reviews → Locations). Centred beside the form\'s button; one under the other on a phone.', 'ai-eshop-optimizer') : __('The buttons come from a location\'s phone and chat channels: add your locations under Google Reviews → Locations first.', 'ai-eshop-optimizer'), )); $html .= $ui::field_row(array( 'id' => 'aieo-form-cta-icons', 'label' => __('Their look', 'ai-eshop-optimizer'), 'control' => $ui::select('aieo_form_settings[cta_icons]', array( '' => __('Icon and words', 'ai-eshop-optimizer'), 'hide' => __('Words only', 'ai-eshop-optimizer'), ), 'hide' === (string) ($stored['cta_icons'] ?? '') ? 'hide' : '', array('id' => 'aieo-form-cta-icons')), 'help' => __('The icons: a phone to call; WhatsApp\'s, Viber\'s and Messenger\'s own marks, in the button\'s colour.', 'ai-eshop-optimizer'), )); if ($locations) { $html .= $ui::field_row(array( 'id' => 'aieo-form-cta-location', 'label' => __('Location', 'ai-eshop-optimizer'), 'control' => $ui::select('aieo_form_settings[cta_location]', array( '' => sprintf(__('The main location (%s)', 'ai-eshop-optimizer'), $main ? (string) $main['label'] : '-'), ) + $locations, (string) ($stored['cta_location'] ?? ''), array('id' => 'aieo-form-cta-location')), 'help' => __('Whose phone and chat channels the buttons use. When the location has an email, the entries sent through this button are mailed there too. A placement may choose another location.', 'ai-eshop-optimizer'), )); } $html .= $ui::field_row(array( 'id' => 'aieo-form-cta-place', 'label' => __('Place it', 'ai-eshop-optimizer'), 'control' => '[aieo_form_button form="' . esc_html($slug) . '"]', 'help' => __('The shortcode, in any theme (a tagDiv theme too); or the block Form Button, which Elementor offers as a widget. A placement may change the words (label="…"), what it opens (opens="modal" or "page"), the buttons beside it (contact="call,whatsapp,viber,messenger", "all" or "none"), their look (icons="hide") and the location (location="its slug").', 'ai-eshop-optimizer'), )); \Rooster\Admin\Markup::out($html . '
'); } public static function enqueue_editor_assets($hook_suffix) { if (!in_array($hook_suffix, array('post.php', 'post-new.php'), true)) { return; } $screen = get_current_screen(); if ($screen && self::POST_TYPE === $screen->post_type) { rooster_admin_table_assets(array('cards')); } } private static function more_lines(array $f) { $lines = array(); foreach (self::MORE as $prop) { $v = $f[$prop] ?? ''; if ('' !== (string) $v && 0 !== $v) { $lines[] = $prop . ': ' . $v; } } return implode("\n", $lines); } private static function parse_more($text) { $out = array(); foreach (preg_split('/\r\n|\r|\n/', (string) $text) as $line) { if (!preg_match('/^\s*([a-z]+)\s*:\s*(.*)$/', $line, $m)) { continue; } if (in_array($m[1], self::MORE, true)) { $out[$m[1]] = trim($m[2]); } } return $out; } public static function render_metabox($post) { wp_nonce_field('aieo_form_fields_save', 'aieo_form_fields_nonce'); $fields = self::fields($post->ID); $types = self::field_types(); rooster_admin_builders(); $ui = '\Rooster\Admin\Ui'; $blank = self::normalise_field(array('key' => 'x', 'type' => 'text', 'label' => '')); $blank['key'] = ''; $rows = $fields ? $fields : array($blank); $table = array(); foreach ($rows as $i => $f) { $choices = ''; foreach ((array) $f['choices'] as $ck => $cl) { $choices .= $ck . '|' . $cl . "\n"; } $name = 'aieo_field[' . (int) $i . ']'; $id = 'aieo-field-' . (int) $i . '-'; $table[] = array( '_id' => (string) $i, '_name' => '' !== (string) $f['key'] ? (string) $f['key'] : sprintf( __('field %d', 'ai-eshop-optimizer'), $i + 1), 'key' => array('html' => $ui::input($name . '[key]', (string) $f['key'], array('id' => $id . 'key', 'code' => true, 'attrs' => array('aria-label' => __('Key', 'ai-eshop-optimizer'))))), 'type' => array('html' => $ui::select($name . '[type]', $types, (string) $f['type'], array('id' => $id . 'type', 'attrs' => array('aria-label' => __('Type', 'ai-eshop-optimizer'))))), 'label' => array('html' => $ui::input($name . '[label]', (string) $f['label'], array('id' => $id . 'label', 'attrs' => array('aria-label' => __('Label', 'ai-eshop-optimizer'))))), 'placeholder' => array('html' => $ui::input($name . '[placeholder]', (string) $f['placeholder'], array('id' => $id . 'placeholder', 'attrs' => array('aria-label' => __('Placeholder', 'ai-eshop-optimizer'))))), 'choices' => array('html' => $ui::textarea($name . '[choices]', trim($choices), array('id' => $id . 'choices', 'rows' => 3, 'placeholder' => __("yes|Yes\nno|No", 'ai-eshop-optimizer'), 'attrs' => array('aria-label' => __('Choices (one per line, key|Label)', 'ai-eshop-optimizer'))))), 'width' => array('html' => $ui::select($name . '[width]', array('full' => __('Full', 'ai-eshop-optimizer'), 'half' => __('Half', 'ai-eshop-optimizer')), (string) $f['width'], array('id' => $id . 'width', 'attrs' => array('aria-label' => __('Width', 'ai-eshop-optimizer'))))), 'more' => array('html' => $ui::textarea($name . '[more]', self::more_lines($f), array('id' => $id . 'more', 'rows' => 3, 'code' => true, 'placeholder' => 'missing: …' . "\n" . 'autocomplete: email', 'attrs' => array('aria-label' => __('More (help, missing, invalid, autocomplete, prompt, rows: one per line, name: value)', 'ai-eshop-optimizer'))))), 'required' => array('html' => ''), 'remove' => array(array('label' => __('Remove', 'ai-eshop-optimizer'), 'destructive' => true, 'icon' => 'x', 'attrs' => array('data-aieo-remove' => ''))), ); } $premade = class_exists('AIEO_Form_Premade') ? AIEO_Form_Premade::choices() : array(); ?>

' . esc_html('' !== (string) $post->post_name ? (string) $post->post_name : (string) $post->ID) . '' ); ?>

__('Blank: the fields below', 'ai-eshop-optimizer')) + $premade, '', array('id' => 'aieo-form-premade'))); ?>

'key', 'label' => __('Key', 'ai-eshop-optimizer'), 'width' => 'sm'), array('key' => 'type', 'label' => __('Type', 'ai-eshop-optimizer'), 'width' => 'sm'), array('key' => 'label', 'label' => __('Label', 'ai-eshop-optimizer')), array('key' => 'placeholder', 'label' => __('Placeholder', 'ai-eshop-optimizer')), array('key' => 'choices', 'label' => __('Choices (one per line, key|Label)', 'ai-eshop-optimizer')), array('key' => 'width', 'label' => __('Width', 'ai-eshop-optimizer'), 'width' => 'sm'), array('key' => 'more', 'label' => __('More', 'ai-eshop-optimizer')), array('key' => 'required', 'label' => __('Required', 'ai-eshop-optimizer'), 'width' => 'min', 'align' => 'center'), array('key' => 'remove', 'label' => __('Actions', 'ai-eshop-optimizer'), 'type' => 'actions'), ), $table, array( 'id' => 'aieo-form-fields-table', 'caption' => __('The form\'s fields', 'ai-eshop-optimizer'), 'cards' => true, ) )); ?>

ID, self::META_SETTINGS, true); $stored = is_string($raw) ? json_decode($raw, true) : array(); $stored = is_array($stored) ? $stored : array(); $def = self::setting_defaults(); $labels = array( 'button' => __('Button', 'ai-eshop-optimizer'), 'sending' => __('Button while sending', 'ai-eshop-optimizer'), 'success' => __('Thank-you line on the page', 'ai-eshop-optimizer'), 'check' => __('Line when a field needs correcting', 'ai-eshop-optimizer'), 'notify_to' => __('Mail entries to (addresses; empty: the site\'s administration address)', 'ai-eshop-optimizer'), 'subject' => __('Subject of the entry\'s e-mail ({form} and {field key} are filled)', 'ai-eshop-optimizer'), 'ack_subject' => __('Thank-you e-mail to the visitor: subject (empty: none)', 'ai-eshop-optimizer'), 'ack_body' => __('Thank-you e-mail to the visitor: text', 'ai-eshop-optimizer'), ); $html = '
' . '

'; foreach ($labels as $k => $label) { $value = isset($stored[$k]) ? (string) $stored[$k] : ''; $opts = array('id' => 'aieo-form-setting-' . $k, 'placeholder' => (string) $def[$k]); $html .= '


' . ('ack_body' === $k ? $ui::textarea('aieo_form_settings[' . $k . ']', $value, $opts + array('rows' => 4)) : $ui::input('aieo_form_settings[' . $k . ']', $value, $opts)) . '

'; } \Rooster\Admin\Markup::out($html . '
'); } public static function save_metabox($post_id, $post) { if (!isset($_POST['aieo_form_fields_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['aieo_form_fields_nonce'])), 'aieo_form_fields_save')) { return; } if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) { return; } if (!current_user_can('edit_post', $post_id)) { return; } $in = isset($_POST['aieo_field']) && is_array($_POST['aieo_field']) ? map_deep(wp_unslash($_POST['aieo_field']), 'sanitize_textarea_field') : array(); $rows = array(); foreach ($in as $row) { if (!is_array($row)) { continue; } $more = self::parse_more(isset($row['more']) ? (string) $row['more'] : ''); unset($row['more']); $rows[] = array_merge($row, $more); } $settings = isset($_POST['aieo_form_settings']) && is_array($_POST['aieo_form_settings']) ? map_deep(wp_unslash($_POST['aieo_form_settings']), 'sanitize_textarea_field') : array(); if (isset($settings['cta_contact']) && is_array($settings['cta_contact'])) { $settings['cta_contact'] = implode(',', array_map('strval', $settings['cta_contact'])); } $notify = isset($_POST['aieo_form_notify']) && 'email' === sanitize_key(wp_unslash($_POST['aieo_form_notify'])); $premade = isset($_POST['aieo_form_premade']) ? sanitize_key(wp_unslash($_POST['aieo_form_premade'])) : ''; if ('' !== $premade && !self::clean_fields($rows) && class_exists('AIEO_Form_Premade') && AIEO_Form_Premade::get($premade)) { $def = AIEO_Form_Premade::get($premade); $rows = $def['fields']; $settings = array_merge($def['settings'], array_filter(array_map('strval', (array) $settings), 'strlen')); $notify = !empty($def['notify']); } self::save_definition($post_id, $rows, (array) $settings, $notify); self::register_strings($post_id); } } }