Changeset 3789028
- Timestamp:
- 11/07/2026 06:07:59 AM (less than one hour ago)
- Location:
- submitrelay
- Files:
-
- 9 added
- 5 edited
-
assets/screenshot-1.png (modified) ( previous)
-
tags/1.2.8 (added)
-
tags/1.2.8/assets (added)
-
tags/1.2.8/assets/admin.js (added)
-
tags/1.2.8/assets/public.js (added)
-
tags/1.2.8/languages (added)
-
tags/1.2.8/languages/index.php (added)
-
tags/1.2.8/readme.txt (added)
-
tags/1.2.8/submitrelay.php (added)
-
tags/1.2.8/uninstall.php (added)
-
trunk/assets/admin.js (modified) (1 diff)
-
trunk/assets/public.js (modified) (1 diff)
-
trunk/readme.txt (modified) (3 diffs)
-
trunk/submitrelay.php (modified) (35 diffs)
Legend:
- Unmodified
- Added
- Removed
-
submitrelay/trunk/assets/admin.js
r3694551 r3789028 1 /* SubmitRelay admin asset handle. Dynamic admin behavior is attached with wp_add_inline_script(). */ 1 /* SubmitRelay admin asset handle. Other admin behavior is attached with wp_add_inline_script(). */ 2 (function () { 3 'use strict'; 4 5 document.addEventListener('DOMContentLoaded', function () { 6 var input = document.getElementById('submitrelay-button-icon-image'); 7 var chooseBtn = document.getElementById('submitrelay-icon-choose'); 8 var resetBtn = document.getElementById('submitrelay-icon-reset'); 9 var preview = document.getElementById('submitrelay-icon-preview'); 10 var frame = null; 11 12 if (!input || !chooseBtn || !resetBtn || !preview) { 13 return; 14 } 15 16 function refresh() { 17 var url = input.value.trim(); 18 if (url) { 19 preview.setAttribute('src', url); 20 preview.hidden = false; 21 } else { 22 preview.removeAttribute('src'); 23 preview.hidden = true; 24 } 25 } 26 27 chooseBtn.addEventListener('click', function (event) { 28 event.preventDefault(); 29 if (!window.wp || !window.wp.media) { 30 return; 31 } 32 if (!frame) { 33 frame = window.wp.media({ 34 title: chooseBtn.getAttribute('data-title') || '', 35 button: { text: chooseBtn.getAttribute('data-button') || '' }, 36 library: { type: 'image' }, 37 multiple: false 38 }); 39 frame.on('select', function () { 40 var attachment = frame.state().get('selection').first().toJSON(); 41 if (attachment && attachment.url) { 42 input.value = attachment.url; 43 refresh(); 44 } 45 }); 46 } 47 frame.open(); 48 }); 49 50 resetBtn.addEventListener('click', function (event) { 51 event.preventDefault(); 52 input.value = ''; 53 refresh(); 54 }); 55 56 input.addEventListener('input', refresh); 57 refresh(); 58 }); 59 }()); -
submitrelay/trunk/assets/public.js
r3694551 r3789028 1 /* SubmitRelay public asset handle. Dynamic public behavior is attached with wp_add_inline_script(). */ 1 /* SubmitRelay public assets. Form submission behavior is attached with wp_add_inline_script(). */ 2 (function () { 3 'use strict'; 4 5 // Load once, even if the script is printed or combined more than once. 6 if (window.submitrelayStepperLoaded) { return; } 7 window.submitrelayStepperLoaded = true; 8 9 // Number (- / +) stepper fields used in the popup and the shortcode form. 10 // The - and + buttons are only shown after this script has run (the 11 // "submitrelay-stepper-ready" class), so they are never visible but dead. 12 function clamp(input) { 13 var min = parseInt(input.getAttribute('min'), 10); 14 var max = parseInt(input.getAttribute('max'), 10); 15 var value = parseInt(input.value, 10); 16 if (isNaN(min)) { min = 0; } 17 if (isNaN(max)) { max = 999; } 18 if (isNaN(value)) { value = min; } 19 value = Math.max(min, Math.min(max, value)); 20 input.value = String(value); 21 return { value: value, min: min, max: max }; 22 } 23 24 function syncStepper(wrap) { 25 if (!wrap) { return; } 26 var input = wrap.querySelector('.submitrelay-step-input'); 27 var minus = wrap.querySelector('.submitrelay-step-minus'); 28 var plus = wrap.querySelector('.submitrelay-step-plus'); 29 if (!input) { return; } 30 var state = clamp(input); 31 if (minus) { minus.disabled = state.value <= state.min; } 32 if (plus) { plus.disabled = state.value >= state.max; } 33 wrap.classList.add('submitrelay-stepper-ready'); 34 } 35 36 function syncAll(onlyNew) { 37 var selector = onlyNew ? '.submitrelay-stepper:not(.submitrelay-stepper-ready)' : '.submitrelay-stepper'; 38 var steppers = document.querySelectorAll(selector); 39 for (var i = 0; i < steppers.length; i++) { syncStepper(steppers[i]); } 40 } 41 42 // Capture phase, so themes or page builders that stop click events cannot block the buttons. 43 document.addEventListener('click', function (event) { 44 var target = event.target; 45 if (target && target.nodeType === 3) { target = target.parentNode; } 46 var button = (target && target.closest) ? target.closest('.submitrelay-step-btn') : null; 47 if (!button) { return; } 48 var wrap = button.closest('.submitrelay-stepper'); 49 var input = wrap ? wrap.querySelector('.submitrelay-step-input') : null; 50 if (!input) { return; } 51 event.preventDefault(); 52 var state = clamp(input); 53 var delta = button.classList.contains('submitrelay-step-plus') ? 1 : -1; 54 input.value = String(Math.max(state.min, Math.min(state.max, state.value + delta))); 55 syncStepper(wrap); 56 }, true); 57 58 document.addEventListener('change', function (event) { 59 var input = event.target; 60 if (input && input.classList && input.classList.contains('submitrelay-step-input')) { 61 syncStepper(input.closest('.submitrelay-stepper')); 62 } 63 }); 64 65 // After a form reset the field returns to its starting value. 66 document.addEventListener('reset', function () { 67 window.setTimeout(function () { syncAll(false); }, 0); 68 }); 69 70 if (document.readyState === 'loading') { 71 document.addEventListener('DOMContentLoaded', function () { syncAll(false); }); 72 } else { 73 syncAll(false); 74 } 75 76 // Forms added to the page later (page builders, AJAX popups). 77 if (window.MutationObserver) { 78 var scheduled = false; 79 new MutationObserver(function () { 80 if (scheduled) { return; } 81 scheduled = true; 82 (window.requestAnimationFrame || window.setTimeout)(function () { 83 scheduled = false; 84 syncAll(true); 85 }); 86 }).observe(document.documentElement, { childList: true, subtree: true }); 87 } 88 }()); -
submitrelay/trunk/readme.txt
r3781257 r3789028 5 5 Tested up to: 7.1 6 6 Requires PHP: 7.4 7 Stable tag: 1.2. 77 Stable tag: 1.2.8 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 193 193 == Changelog == 194 194 195 = 1.2.8 = 196 * Fix: the number (- / +) buttons are shown only once their script has loaded, and scripts are cache-busted, so the buttons are never visible but inactive on sites with caching or optimization plugins. 197 * Fix: saving the main settings no longer resets the Submission Storage choices (for example IP Address switching back off). 198 * New: "Number (- / +)" field type for the form builder, for things like Adults and Child. Set a minimum, maximum and starting value. Works in the popup and the shortcode form. 199 * Change: form fields in both the popup and the shortcode form now use an outlined style, with the label sitting on the field border. A "Field Label Background" colour setting matches the label to your page background. 200 * New: optional Button Icon Image for the floating button. Choose an image from the Media Library, or click "Use Default Icon" to return to the text/emoji icon. The default icon is unchanged. 201 195 202 = 1.2.7 = 196 203 * Fix: dropdown text no longer gets cut off in the shortcode form on themes that set a fixed select height. … … 334 341 == Upgrade Notice == 335 342 343 = 1.2.8 = 344 Form fields now use an outlined style (label on the field border), and the floating button icon can be an uploaded image. Forms will look different after updating. 345 336 346 = 1.2.7 = 337 347 Fixes dropdown text being cut off in the shortcode form on some themes. -
submitrelay/trunk/submitrelay.php
r3781257 r3789028 3 3 * Plugin Name: SubmitRelay – Form & Chat 4 4 * Description: WordPress contact form with WhatsApp chat and pre-filled messages, email notifications, submission storage, popup and shortcode forms. 5 * Version: 1.2. 75 * Version: 1.2.8 6 6 * Author: Muzaffer Ahmad Pakhtoon 7 7 * Author URI: https://wp.apharwat.net/ … … 32 32 * database migration. 33 33 */ 34 private const PLUGIN_VERSION = '1.2. 7';34 private const PLUGIN_VERSION = '1.2.8'; 35 35 private const DB_VERSION = '1.2.0'; 36 36 … … 337 337 'scroll_percent' => 0, 338 338 'popup_position' => 'center', 339 'button_icon_image' => '', 340 'outlined_label_bg' => '#ffffff', 339 341 'schedule_enabled' => 0, 340 342 'schedule_start' => '', … … 423 425 'field_text_color' => $defaults['field_text_color'], 424 426 'placeholder_text_color' => $defaults['placeholder_text_color'], 427 'outlined_label_bg' => $defaults['outlined_label_bg'], 425 428 ]; 426 429 foreach ($color_defaults as $key => $fallback) { … … 438 441 $settings['button_position'] = in_array($settings['button_position'], $this->button_positions, true) ? $settings['button_position'] : $defaults['button_position']; 439 442 $settings['popup_position'] = in_array($settings['popup_position'], $this->popup_positions, true) ? $settings['popup_position'] : $defaults['popup_position']; 443 $settings['button_icon_image'] = esc_url_raw((string) ($settings['button_icon_image'] ?? '')); 440 444 $settings['mobile_button_position'] = in_array($settings['mobile_button_position'], ['left', 'center', 'right'], true) ? $settings['mobile_button_position'] : $defaults['mobile_button_position']; 441 445 $settings['mobile_button_style'] = in_array($settings['mobile_button_style'], ['icon_text', 'icon'], true) ? $settings['mobile_button_style'] : $defaults['mobile_button_style']; … … 458 462 $settings['form_fields'] = $this->sanitize_form_fields($settings['form_fields']); 459 463 return $settings; 464 } 465 466 /** 467 * Script version used for cache busting: plugin version plus the file's 468 * modification time, so caches and CDNs never serve a stale script. 469 */ 470 private function asset_version($relative_path) { 471 $file = plugin_dir_path(__FILE__) . $relative_path; 472 $mtime = file_exists($file) ? filemtime($file) : false; 473 return $mtime ? self::PLUGIN_VERSION . '.' . $mtime : self::PLUGIN_VERSION; 460 474 } 461 475 … … 572 586 } 573 587 wp_register_style('submitrelay-public-css', false, [], self::PLUGIN_VERSION); 574 wp_register_script('submitrelay-public-js', plugin_dir_url(__FILE__) . 'assets/public.js', [], self::PLUGIN_VERSION, true);588 wp_register_script('submitrelay-public-js', plugin_dir_url(__FILE__) . 'assets/public.js', [], $this->asset_version('assets/public.js'), true); 575 589 } 576 590 … … 582 596 if ($hook === 'toplevel_page_submitrelay') { 583 597 wp_enqueue_editor(); 598 wp_enqueue_media(); 584 599 } 585 600 … … 587 602 wp_enqueue_style('submitrelay-admin-css'); 588 603 589 wp_enqueue_script('submitrelay-admin-js', plugin_dir_url(__FILE__) . 'assets/admin.js', [], self::PLUGIN_VERSION, true);604 wp_enqueue_script('submitrelay-admin-js', plugin_dir_url(__FILE__) . 'assets/admin.js', [], $this->asset_version('assets/admin.js'), true); 590 605 wp_localize_script('submitrelay-admin-js', 'SubmitRelayAdmin', [ 591 606 'settingsKey' => $this->settings_key, … … 599 614 'select' => __('Select / Dropdown', 'submitrelay'), 600 615 'radio' => __('Radio', 'submitrelay'), 616 'number' => __('Number (- / +)', 'submitrelay'), 617 'numberMin' => __('Minimum', 'submitrelay'), 618 'numberMax' => __('Maximum', 'submitrelay'), 619 'numberStart' => __('Starting value', 'submitrelay'), 601 620 'newField' => __('New Field', 'submitrelay'), 602 621 'required' => __('Required', 'submitrelay'), … … 612 631 ]); 613 632 633 wp_add_inline_style('submitrelay-admin-css', '.submitrelay-icon-preview { width: 32px; height: 32px; object-fit: contain; vertical-align: middle; margin-left: 8px; padding: 4px; border-radius: 6px; background: #2563eb; } .submitrelay-icon-preview[hidden] { display: none; } .submitrelay-number-options { display: none; } .submitrelay-number-options label { margin-right: 14px; }'); 614 634 wp_add_inline_style('submitrelay-admin-css', ' 615 635 .submitrelay-tab-content { … … 936 956 } 937 957 $base = wp_parse_args(is_array($existing) ? $existing : [], $defaults); 958 // Keep the effective storage choices (including the legacy-install behaviour) 959 // whenever any other settings page is saved. 960 $current_settings = $this->settings(); 961 $base['submission_storage'] = $current_settings['submission_storage']; 938 962 foreach (['custom_css', 'button_border_radius', 'button_padding', 'popup_width', 'popup_height', 'popup_border_radius', 'close_size', 'close_position', 'close_bg_color', 'close_text_color'] as $obsolete_key) { 939 963 unset($base[$obsolete_key]); … … 961 985 } 962 986 987 if (!$storage_only) { 988 $output['submission_storage'] = $base['submission_storage']; 989 } 963 990 if ($storage_only) { 964 991 $storage_input = is_array($input['submission_storage']) ? $input['submission_storage'] : []; … … 1039 1066 $output['bottom_text'] = sanitize_textarea_field($input['bottom_text'] ?? $base['bottom_text']); 1040 1067 1041 $color_fields = ['button_color', 'button_text_color', 'popup_bg_color', 'overlay_color', 'field_text_color', 'placeholder_text_color' ];1068 $color_fields = ['button_color', 'button_text_color', 'popup_bg_color', 'overlay_color', 'field_text_color', 'placeholder_text_color', 'outlined_label_bg']; 1042 1069 foreach ($color_fields as $color) { 1043 1070 $output[$color] = sanitize_hex_color($input[$color] ?? $base[$color]); … … 1059 1086 $output['animation_type'] = in_array($input['animation_type'] ?? '', $this->animation_types, true) ? $input['animation_type'] : $base['animation_type']; 1060 1087 $output['popup_position'] = in_array($input['popup_position'] ?? '', $this->popup_positions, true) ? $input['popup_position'] : $base['popup_position']; 1088 $output['button_icon_image'] = esc_url_raw((string) ($input['button_icon_image'] ?? ($base['button_icon_image'] ?? ''))); 1061 1089 $output['device_visibility'] = in_array($input['device_visibility'] ?? '', $this->device_visibility_opts, true) ? $input['device_visibility'] : $base['device_visibility']; 1062 1090 $output['visibility_mode'] = in_array($input['visibility_mode'] ?? '', $this->visibility_modes, true) ? $input['visibility_mode'] : $base['visibility_mode']; … … 1654 1682 <?php $this->render_checkbox('enabled', __('Enable Floating Button', 'submitrelay')); ?> 1655 1683 <?php $this->render_text_field('button_text', __('Button Text', 'submitrelay')); ?> 1656 <?php $this->render_text_field('button_icon', __('Button Icon', 'submitrelay')); ?> 1684 <?php 1685 $submitrelay_icon_settings = $this->settings(); 1686 $submitrelay_icon_image = (string) ($submitrelay_icon_settings['button_icon_image'] ?? ''); 1687 ?> 1688 <tr> 1689 <th scope="row"><label for="submitrelay-button-icon"><?php esc_html_e('Button Icon', 'submitrelay'); ?></label></th> 1690 <td> 1691 <input type="text" class="regular-text" id="submitrelay-button-icon" name="<?php echo esc_attr($this->settings_key); ?>[button_icon]" value="<?php echo esc_attr($submitrelay_icon_settings['button_icon'] ?? ''); ?>" maxlength="255"> 1692 <input type="hidden" id="submitrelay-button-icon-image" name="<?php echo esc_attr($this->settings_key); ?>[button_icon_image]" value="<?php echo esc_url($submitrelay_icon_image); ?>"> 1693 <button type="button" class="button" id="submitrelay-icon-choose" data-title="<?php esc_attr_e('Choose Button Icon', 'submitrelay'); ?>" data-button="<?php esc_attr_e('Use this image', 'submitrelay'); ?>"><?php esc_html_e('Upload Image', 'submitrelay'); ?></button> 1694 <button type="button" class="button" id="submitrelay-icon-reset"><?php esc_html_e('Use Default Icon', 'submitrelay'); ?></button> 1695 <img id="submitrelay-icon-preview" class="submitrelay-icon-preview" alt=""<?php if ($submitrelay_icon_image !== '') : ?> src="<?php echo esc_url($submitrelay_icon_image); ?>"<?php else : ?> hidden<?php endif; ?>> 1696 <p class="description"><?php esc_html_e('Type text or an emoji, or upload an image to use instead. Click "Use Default Icon" to remove the image and show the text icon again.', 'submitrelay'); ?></p> 1697 </td> 1698 </tr> 1657 1699 <?php $this->render_color('button_color', __('Floating Button Color', 'submitrelay')); ?> 1658 1700 <?php $this->render_color('popup_bg_color', __('Background Color', 'submitrelay')); ?> … … 1706 1748 if (!is_array($field)) continue; 1707 1749 $raw_type = $field['type'] ?? 'text'; 1708 $type = in_array($raw_type, ['text','email','phone','date','time','textarea','select','radio' ], true) ? $raw_type : 'text';1750 $type = in_array($raw_type, ['text','email','phone','date','time','textarea','select','radio','number'], true) ? $raw_type : 'text'; 1709 1751 $label = sanitize_text_field($field['label'] ?? ''); 1710 1752 $id = sanitize_key($field['id'] ?? ''); … … 1733 1775 } 1734 1776 } 1777 $number = []; 1778 if ($type === 'number') { 1779 $num_min = (isset($field['min']) && is_numeric($field['min'])) ? (int) $field['min'] : 0; 1780 $num_max = (isset($field['max']) && is_numeric($field['max'])) ? (int) $field['max'] : 10; 1781 $num_min = max(0, min(999, $num_min)); 1782 $num_max = max($num_min, min(999, $num_max)); 1783 $num_start = (isset($field['start']) && is_numeric($field['start'])) ? (int) $field['start'] : $num_min; 1784 $num_start = max($num_min, min($num_max, $num_start)); 1785 $number = ['min' => $num_min, 'max' => $num_max, 'start' => $num_start]; 1786 } 1735 1787 // Backward compatibility: older installations used one `enabled` 1736 1788 // flag for both forms. If the new per-form flags are absent, map … … 1751 1803 'width'=>$width, 1752 1804 'options'=>$options, 1753 ] ;1805 ] + $number; 1754 1806 $seen[$id]=true; 1755 1807 } … … 1774 1826 <span class="submitrelay-drag" title="<?php esc_attr_e('Drag to reorder','submitrelay'); ?>">☰</span> 1775 1827 <select name="<?php echo esc_attr($this->settings_key); ?>[form_fields][<?php echo (int)$i; ?>][type]" class="submitrelay-field-type"> 1776 <?php foreach (['text'=>__('Text','submitrelay'),'email'=>__('Email','submitrelay'),'phone'=>__('Phone','submitrelay'),'date'=>__('Date','submitrelay'),'time'=>__('Time','submitrelay'),'textarea'=>__('Textarea','submitrelay'),'select'=>__('Select / Dropdown','submitrelay'),'radio'=>__('Radio','submitrelay') ] as $type=>$label): ?><option value="<?php echo esc_attr($type); ?>" <?php selected($field['type'],$type); ?>><?php echo esc_html($label); ?></option><?php endforeach; ?>1828 <?php foreach (['text'=>__('Text','submitrelay'),'email'=>__('Email','submitrelay'),'phone'=>__('Phone','submitrelay'),'date'=>__('Date','submitrelay'),'time'=>__('Time','submitrelay'),'textarea'=>__('Textarea','submitrelay'),'select'=>__('Select / Dropdown','submitrelay'),'radio'=>__('Radio','submitrelay'),'number'=>__('Number (- / +)','submitrelay')] as $type=>$label): ?><option value="<?php echo esc_attr($type); ?>" <?php selected($field['type'],$type); ?>><?php echo esc_html($label); ?></option><?php endforeach; ?> 1777 1829 </select> 1778 1830 <input type="text" name="<?php echo esc_attr($this->settings_key); ?>[form_fields][<?php echo (int)$i; ?>][label]" value="<?php echo esc_attr($field['label']); ?>" aria-label="<?php esc_attr_e('Label','submitrelay'); ?>"> … … 1799 1851 </select> 1800 1852 <button type="button" class="button-link-delete submitrelay-remove-field" title="<?php echo esc_attr__('Remove field','submitrelay'); ?>" aria-label="<?php echo esc_attr__('Remove field','submitrelay'); ?>" <?php echo $is_core?'disabled':''; ?>><span class="dashicons dashicons-trash" aria-hidden="true"></span></button> 1853 <div class="submitrelay-number-options" style="grid-column:2 / -1; <?php echo ($field['type'] === 'number') ? 'display:block;' : ''; ?>"> 1854 <label><?php esc_html_e('Minimum','submitrelay'); ?> <input type="number" class="small-text" min="0" max="999" name="<?php echo esc_attr($this->settings_key); ?>[form_fields][<?php echo (int)$i; ?>][min]" value="<?php echo esc_attr((string) ($field['min'] ?? 0)); ?>"></label> 1855 <label><?php esc_html_e('Maximum','submitrelay'); ?> <input type="number" class="small-text" min="0" max="999" name="<?php echo esc_attr($this->settings_key); ?>[form_fields][<?php echo (int)$i; ?>][max]" value="<?php echo esc_attr((string) ($field['max'] ?? 10)); ?>"></label> 1856 <label><?php esc_html_e('Starting value','submitrelay'); ?> <input type="number" class="small-text" min="0" max="999" name="<?php echo esc_attr($this->settings_key); ?>[form_fields][<?php echo (int)$i; ?>][start]" value="<?php echo esc_attr((string) ($field['start'] ?? 0)); ?>"></label> 1857 </div> 1801 1858 <div class="submitrelay-custom-options" style="grid-column:2 / -1; <?php echo in_array($field['type'], ['select','radio'], true) ? 'display:block;' : ''; ?>"> 1802 1859 <label><?php esc_html_e('Options (one per line)','submitrelay'); ?><br><textarea rows="3" name="<?php echo esc_attr($this->settings_key); ?>[form_fields][<?php echo (int)$i; ?>][options_text]"><?php echo esc_textarea(implode("\n", $field['options'] ?? [])); ?></textarea></label> … … 1816 1873 function uniqueId(row,base){ const used=new Set([...rows.querySelectorAll('.submitrelay-field-row')].filter(r=>r!==row).map(r=>r.querySelector('input[name$="[id]"]')?.value.trim()).filter(Boolean)); let id=base||'field', n=2; while(used.has(id)){ id=base+'_'+n++; } return id; } 1817 1874 function syncGeneratedId(row){ const label=row.querySelector('input[name$="[label]"]'); const idInput=row.querySelector('input[name$="[id]"]'); if(!label||!idInput||idInput.readOnly)return; const current=idInput.value.trim(); if(!current || row.dataset.autoId==='1'){ const id=uniqueId(row,slugifyLabel(label.value)); idInput.value=id; row.dataset.generatedId=id; row.dataset.autoId='1'; } } 1818 function toggleOptions(row){ const type=row.querySelector('.submitrelay-field-type'); const box=row.querySelector('.submitrelay-custom-options'); if(type&&box) box.style.display=(type.value==='select'||type.value==='radio')?'block':'none'; }1875 function toggleOptions(row){ const type=row.querySelector('.submitrelay-field-type'); const box=row.querySelector('.submitrelay-custom-options'); const numBox=row.querySelector('.submitrelay-number-options'); if(type&&box) box.style.display=(type.value==='select'||type.value==='radio')?'block':'none'; if(type&&numBox) numBox.style.display=(type.value==='number')?'block':'none'; } 1819 1876 function clearDropIndicators(){ rows.querySelectorAll('.submitrelay-field-row').forEach(r=>r.classList.remove('drop-before','drop-after','drop-side')); } 1820 1877 function setWidth(row,value){ const select=row?.querySelector('select[name$="[width]"]'); if(select){ select.value=value; } } … … 1848 1905 reindex(); 1849 1906 }); const i18n = (window.SubmitRelayAdmin && window.SubmitRelayAdmin.i18n) || {}; 1850 add?.addEventListener('click',()=>{const i=rows.children.length; const wrap=document.createElement('div'); wrap.className='submitrelay-field-row'; wrap.draggable=true; wrap.dataset.autoId='1'; const base=window.SubmitRelayAdmin.settingsKey+'[form_fields]['+i+']'; const initialId=uniqueId(wrap,slugifyLabel(i18n.newField)); wrap.dataset.generatedId=initialId; wrap.innerHTML='<span class="submitrelay-drag">☰</span><select name="'+base+'[type]" class="submitrelay-field-type"><option value="text">'+i18n.text+'</option><option value="email">'+i18n.email+'</option><option value="phone">'+i18n.phone+'</option><option value="date">'+i18n.date+'</option><option value="time">'+i18n.time+'</option><option value="textarea">'+i18n.textarea+'</option><option value="select">'+i18n.select+'</option><option value="radio">'+i18n.radio+'</option>< /select><input type="text" name="'+base+'[label]" value="'+i18n.newField+'"><input type="text" name="'+base+'[id]" value="'+initialId+'"><input type="text" name="'+base+'[placeholder]" value=""><label class="submitrelay-switch" title="'+i18n.required+'"><input type="hidden" name="'+base+'[required]" value="0"><input type="checkbox" name="'+base+'[required]" value="1" aria-label="'+i18n.required+'"><span class="submitrelay-switch-track" aria-hidden="true"></span></label><label class="submitrelay-switch" title="'+i18n.popup+'"><input type="hidden" name="'+base+'[popup]" value="0"><input type="checkbox" name="'+base+'[popup]" value="1" checked aria-label="'+i18n.popup+'"><span class="submitrelay-switch-track" aria-hidden="true"></span></label><label class="submitrelay-switch" title="'+i18n.form+'"><input type="hidden" name="'+base+'[form]" value="0"><input type="checkbox" name="'+base+'[form]" value="1" checked aria-label="'+i18n.form+'"><span class="submitrelay-switch-track" aria-hidden="true"></span></label><select name="'+base+'[width]" aria-label="'+i18n.width+'"><option value="full">'+i18n.fullWidth+'</option><option value="half">'+i18n.halfWidth+'</option></select><button type="button" class="button-link-delete submitrelay-remove-field" title="'+i18n.removeField+'" aria-label="'+i18n.removeField+'"><span class="dashicons dashicons-trash" aria-hidden="true"></span></button><div class="submitrelay-custom-options" style="grid-column:2 / -1"><label>'+i18n.options+'<br><textarea rows="3" name="'+base+'[options_text]"></textarea></label></div>'; rows.appendChild(wrap); bind(wrap); reindex(); });1907 add?.addEventListener('click',()=>{const i=rows.children.length; const wrap=document.createElement('div'); wrap.className='submitrelay-field-row'; wrap.draggable=true; wrap.dataset.autoId='1'; const base=window.SubmitRelayAdmin.settingsKey+'[form_fields]['+i+']'; const initialId=uniqueId(wrap,slugifyLabel(i18n.newField)); wrap.dataset.generatedId=initialId; wrap.innerHTML='<span class="submitrelay-drag">☰</span><select name="'+base+'[type]" class="submitrelay-field-type"><option value="text">'+i18n.text+'</option><option value="email">'+i18n.email+'</option><option value="phone">'+i18n.phone+'</option><option value="date">'+i18n.date+'</option><option value="time">'+i18n.time+'</option><option value="textarea">'+i18n.textarea+'</option><option value="select">'+i18n.select+'</option><option value="radio">'+i18n.radio+'</option><option value="number">'+i18n.number+'</option></select><input type="text" name="'+base+'[label]" value="'+i18n.newField+'"><input type="text" name="'+base+'[id]" value="'+initialId+'"><input type="text" name="'+base+'[placeholder]" value=""><label class="submitrelay-switch" title="'+i18n.required+'"><input type="hidden" name="'+base+'[required]" value="0"><input type="checkbox" name="'+base+'[required]" value="1" aria-label="'+i18n.required+'"><span class="submitrelay-switch-track" aria-hidden="true"></span></label><label class="submitrelay-switch" title="'+i18n.popup+'"><input type="hidden" name="'+base+'[popup]" value="0"><input type="checkbox" name="'+base+'[popup]" value="1" checked aria-label="'+i18n.popup+'"><span class="submitrelay-switch-track" aria-hidden="true"></span></label><label class="submitrelay-switch" title="'+i18n.form+'"><input type="hidden" name="'+base+'[form]" value="0"><input type="checkbox" name="'+base+'[form]" value="1" checked aria-label="'+i18n.form+'"><span class="submitrelay-switch-track" aria-hidden="true"></span></label><select name="'+base+'[width]" aria-label="'+i18n.width+'"><option value="full">'+i18n.fullWidth+'</option><option value="half">'+i18n.halfWidth+'</option></select><button type="button" class="button-link-delete submitrelay-remove-field" title="'+i18n.removeField+'" aria-label="'+i18n.removeField+'"><span class="dashicons dashicons-trash" aria-hidden="true"></span></button><div class="submitrelay-custom-options" style="grid-column:2 / -1"><label>'+i18n.options+'<br><textarea rows="3" name="'+base+'[options_text]"></textarea></label></div><div class="submitrelay-number-options" style="grid-column:2 / -1"><label>'+i18n.numberMin+' <input type="number" class="small-text" min="0" max="999" name="'+base+'[min]" value="0"></label> <label>'+i18n.numberMax+' <input type="number" class="small-text" min="0" max="999" name="'+base+'[max]" value="10"></label> <label>'+i18n.numberStart+' <input type="number" class="small-text" min="0" max="999" name="'+base+'[start]" value="0"></label></div>'; rows.appendChild(wrap); bind(wrap); reindex(); }); 1851 1908 })(); 1852 1909 … … 1868 1925 <div style="margin-top:18px;"> 1869 1926 <table class="form-table" role="presentation"> 1927 <?php $this->render_color('outlined_label_bg', __('Field Label Background (shortcode form)', 'submitrelay')); ?> 1870 1928 <?php $this->render_color('field_text_color', __('Field Text Color', 'submitrelay')); ?> 1871 1929 <?php $this->render_color('placeholder_text_color', __('Placeholder Text Color', 'submitrelay')); ?> … … 2009 2067 <?php $this->render_select('popup_position', __('Popup Position', 'submitrelay'), $this->get_position_option_labels('popup')); ?> 2010 2068 <?php $this->render_text_field('auto_open_delay', __('Auto-open Delay (sec)', 'submitrelay'), 'number', 'small-text'); ?> 2011 </table>2012 <?php2013 }2014 2015 private function render_overlay_tab() {2016 $settings = $this->settings();2017 ?>2018 <table class="form-table">2019 <?php $this->render_color('overlay_color', __('Overlay Color', 'submitrelay')); ?>2020 <tr>2021 <th scope="row"><?php esc_html_e('Overlay Opacity', 'submitrelay'); ?></th>2022 <td>2023 <div class="submitrelay-opacity-wrapper">2024 <input type="range" name="<?php echo esc_attr($this->settings_key); ?>[overlay_opacity]" min="0" max="1" step="0.05" value="<?php echo esc_attr($settings['overlay_opacity']); ?>">2025 <span id="opacityValue"><?php echo esc_html($settings['overlay_opacity']); ?></span>2026 </div>2027 </td>2028 </tr>2029 <?php $this->render_select('animation_type', __('Animation Type', 'submitrelay'), $this->get_position_option_labels('animation')); ?>2030 2069 </table> 2031 2070 <?php … … 2400 2439 foreach (($field['options'] ?? []) as $option) $html .= '<option value="' . esc_attr($option) . '">' . esc_html($option) . '</option>'; 2401 2440 $html .= '</select>'; 2441 } elseif ($type === 'number') { 2442 $num_min = isset($field['min']) ? (int) $field['min'] : 0; 2443 $num_max = isset($field['max']) ? (int) $field['max'] : 10; 2444 $num_start = isset($field['start']) ? (int) $field['start'] : $num_min; 2445 $html .= '<div class="submitrelay-stepper">'; 2446 $html .= '<button type="button" class="submitrelay-step-btn submitrelay-step-minus" aria-label="' . esc_attr__('Decrease', 'submitrelay') . '">−</button>'; 2447 $html .= '<input type="number" id="' . esc_attr($input_id) . '" name="' . esc_attr($name) . '" value="' . esc_attr((string) $num_start) . '" min="' . esc_attr((string) $num_min) . '" max="' . esc_attr((string) $num_max) . '" step="1" inputmode="numeric" class="submitrelay-step-input">'; 2448 $html .= '<button type="button" class="submitrelay-step-btn submitrelay-step-plus" aria-label="' . esc_attr__('Increase', 'submitrelay') . '">+</button>'; 2449 $html .= '</div>'; 2402 2450 } elseif ($type === 'radio') { 2403 2451 $radio_legend_id = $input_id . '-legend'; … … 2524 2572 echo $compact_css; ?> 2525 2573 <?php 2526 return ob_get_clean(); 2574 $popup_css = ob_get_clean(); 2575 if (!empty($settings['button_icon_image'])) { 2576 $popup_css .= '.fsp-btn-icon-img { display: block; width: 22px; height: 22px; object-fit: contain; }'; 2577 } 2578 $popup_css .= $this->get_outlined_field_css('.fsp-form-wrap', $settings['popup_bg_color'] ?? '#ffffff'); 2579 return $popup_css; 2580 } 2581 2582 /** 2583 * Built-in "outlined labels" field style. This is a fixed preset (not custom 2584 * CSS input); the only configurable value is a validated hex colour. 2585 */ 2586 private function get_outlined_field_css($scope, $label_bg) { 2587 $s = $scope . '.submitrelay-outlined'; 2588 $bg = sanitize_hex_color($label_bg); 2589 if (!$bg) { 2590 $bg = '#ffffff'; 2591 } 2592 $css = $s . ' .submitrelay-field { position: relative; margin: 10px 0 12px !important; }'; 2593 $css .= $s . ' .submitrelay-field > .submitrelay-field-label { position: absolute !important; top: 0 !important; left: 14px !important; width: auto !important; height: auto !important; max-width: calc(100% - 28px) !important; margin: 0 !important; padding: 0 6px !important; transform: translateY(-50%); clip: auto !important; overflow: hidden !important; text-overflow: ellipsis; white-space: nowrap !important; border: 0 !important; background: ' . $bg . ' !important; font-size: 12px !important; font-weight: 500 !important; line-height: 1.2 !important; letter-spacing: 0.04em; text-transform: uppercase; z-index: 2; pointer-events: none; }'; 2594 $css .= $s . ' input, ' . $s . ' textarea, ' . $s . ' select { background-color: transparent !important; border: 1px solid #9ca3af !important; }'; 2595 $css .= $s . ' input::placeholder, ' . $s . ' textarea::placeholder { color: transparent !important; opacity: 0; }'; 2596 $css .= $s . ' input:focus, ' . $s . ' textarea:focus, ' . $s . ' select:focus { background-color: transparent !important; border-color: #2563eb !important; box-shadow: 0 0 0 1px #2563eb !important; outline: none !important; }'; 2597 $css .= $s . ' .submitrelay-stepper { display: flex; align-items: center; justify-content: space-between; gap: 8px; box-sizing: border-box; width: 100%; margin: 0; padding: 0 10px; border: 1px solid #9ca3af; border-radius: 12px; }'; 2598 $css .= $s . ' .submitrelay-stepper:focus-within { border-color: #2563eb; box-shadow: 0 0 0 1px #2563eb; }'; 2599 $css .= $s . ' .submitrelay-stepper .submitrelay-step-input { flex: 1 1 auto; min-width: 0; width: 100% !important; height: auto !important; min-height: 0 !important; margin: 0 !important; padding: 11px 0 !important; border: 0 !important; box-shadow: none !important; background: transparent !important; text-align: center; }'; 2600 $css .= $s . ' .submitrelay-stepper-ready .submitrelay-step-input { -moz-appearance: textfield; appearance: textfield; }'; 2601 $css .= $s . ' .submitrelay-stepper-ready .submitrelay-step-input::-webkit-outer-spin-button, ' . $s . ' .submitrelay-stepper-ready .submitrelay-step-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }'; 2602 $css .= $s . ' .submitrelay-stepper:not(.submitrelay-stepper-ready) .submitrelay-step-btn { display: none !important; }'; 2603 $css .= $s . ' .submitrelay-stepper .submitrelay-step-btn { flex: 0 0 auto !important; display: inline-flex !important; align-items: center !important; justify-content: center !important; position: relative !important; width: 24px !important; min-width: 24px !important; height: 24px !important; min-height: 24px !important; margin: 0 !important; padding: 0 !important; border: 1.5px solid #1f2937 !important; border-radius: 50% !important; background: transparent !important; color: #1f2937 !important; box-shadow: none !important; filter: none !important; transform: none !important; font-size: 0 !important; line-height: 0 !important; overflow: hidden; cursor: pointer; }'; 2604 $css .= $s . ' .submitrelay-stepper .submitrelay-step-btn::before { content: ""; position: absolute; top: 50%; left: 50%; width: 10px; height: 1.5px; background: #1f2937; transform: translate(-50%, -50%); }'; 2605 $css .= $s . ' .submitrelay-stepper .submitrelay-step-plus::after { content: ""; position: absolute; top: 50%; left: 50%; width: 1.5px; height: 10px; background: #1f2937; transform: translate(-50%, -50%); }'; 2606 $css .= $s . ' .submitrelay-stepper .submitrelay-step-btn:hover:not(:disabled) { background: rgba(31, 41, 55, 0.08) !important; filter: none !important; transform: none !important; }'; 2607 $css .= $s . ' .submitrelay-stepper .submitrelay-step-btn:disabled { opacity: 0.35 !important; cursor: not-allowed; }'; 2608 $css .= $s . ' input[type="radio"], ' . $s . ' input[type="checkbox"] { border: revert !important; box-shadow: none !important; }'; 2609 return $css; 2527 2610 } 2528 2611 … … 2546 2629 'id' => true, 'class' => true, 'style' => true, 'aria-hidden' => true, 2547 2630 ], 2631 'img' => [ 2632 'class' => true, 'src' => true, 'alt' => true, 'width' => true, 'height' => true, 2633 ], 2548 2634 'fieldset' => [ 2549 2635 'id' => true, 'class' => true, 'aria-labelledby' => true, … … 2560 2646 'class' => true, 'placeholder' => true, 'autocomplete' => true, 2561 2647 'required' => true, 'checked' => true, 'disabled' => true, 2562 'aria-label' => true, 'aria-required' => true, 'aria-hidden' => true, 'inputmode' => true, 'title' => true, 'data-*' => true, 'maxlength' => true, 'pattern' => true, 2648 'aria-label' => true, 'aria-required' => true, 'aria-hidden' => true, 'inputmode' => true, 'title' => true, 'data-*' => true, 'maxlength' => true, 'pattern' => true, 'min' => true, 'max' => true, 'step' => true, 2563 2649 ], 2564 2650 'textarea' => [ … … 2585 2671 private function render_popup_html($settings) { 2586 2672 $button_html = ''; 2587 if (!empty($settings['button_icon'])) $button_html .= '<span class="fsp-btn-icon">' . wp_kses_post($settings['button_icon']) . '</span> '; 2673 if (!empty($settings['button_icon_image'])) { 2674 $button_html .= '<span class="fsp-btn-icon"><img class="fsp-btn-icon-img" src="' . esc_url($settings['button_icon_image']) . '" alt="" width="22" height="22"></span> '; 2675 } elseif (!empty($settings['button_icon'])) { 2676 $button_html .= '<span class="fsp-btn-icon">' . wp_kses_post($settings['button_icon']) . '</span> '; 2677 } 2588 2678 $button_html .= '<span class="fsp-btn-text">' . esc_html($settings['button_text']) . '</span>'; 2589 2679 … … 2594 2684 ?> 2595 2685 <button id="fsp-float-btn"><?php echo wp_kses_post($button_html); ?></button> 2686 2596 2687 <div id="fsp-overlay"></div> 2597 2688 <div id="fsp-popup" role="dialog" aria-modal="true" aria-label="<?php esc_attr_e('Contact form', 'submitrelay'); ?>"> … … 2603 2694 <div class="fsp-description"><?php echo nl2br(esc_html($settings['popup_description'])); ?></div> 2604 2695 <?php endif; ?> 2605 <div class="fsp-form-wrap "><?php echo wp_kses($contact_form, $this->get_popup_allowed_html()); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Form markup is generated internally with escaped attributes and a WordPress-generated nonce. ?></div>2696 <div class="fsp-form-wrap submitrelay-outlined"><?php echo wp_kses($contact_form, $this->get_popup_allowed_html()); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Form markup is generated internally with escaped attributes and a WordPress-generated nonce. ?></div> 2606 2697 <?php if (!empty($settings['show_bottom_text']) && !empty($settings['bottom_text'])): ?> 2607 2698 <div class="fsp-bottom-text"><?php echo wp_kses_post($settings['bottom_text']); ?></div> … … 3154 3245 wp_send_json_error(['message' => sprintf(__('Please choose a valid option for %s.', 'submitrelay'), sanitize_text_field($field['label'] ?? $id))]); 3155 3246 } 3247 if (($field['type'] ?? '') === 'number') { 3248 $num_min = isset($field['min']) ? (int) $field['min'] : 0; 3249 $num_max = isset($field['max']) ? (int) $field['max'] : 10; 3250 if ($value !== '' && (!preg_match('/^\d{1,3}$/', $value) || (int) $value < $num_min || (int) $value > $num_max)) { 3251 /* translators: %s is the form field label. */ 3252 wp_send_json_error(['message' => sprintf(__('Please enter a valid number for %s.', 'submitrelay'), sanitize_text_field($field['label'] ?? $id))]); 3253 } 3254 } 3156 3255 if (($field['type'] ?? '') === 'phone' && $value !== '' && !$this->is_valid_phone_number($value)) { 3157 3256 /* translators: %s is the form field label. */ … … 3335 3434 $form_fields = $this->render_form_fields($settings, 'shortcode'); 3336 3435 3337 $form_html = '<div class="submitrelay-shortcode-wrapper " style="display:block;width:100%;max-width:100%;min-width:0;margin:0 auto;box-sizing:border-box;">3436 $form_html = '<div class="submitrelay-shortcode-wrapper submitrelay-outlined" style="display:block;width:100%;max-width:100%;min-width:0;margin:0 auto;box-sizing:border-box;"> 3338 3437 <form method="post" class="submitrelay-shortcode-form" style="display:block;width:100%;max-width:100%;min-width:0;margin:0;box-sizing:border-box;" data-wa-enabled="' . ($whatsapp_enabled ? '1' : '0') . '" data-wa-number="' . esc_attr($wa_number) . '" data-wa-template="' . esc_attr($wa_template) . '" data-wa-delay="' . $wa_delay . '" data-wa-target="' . esc_attr($settings['whatsapp_redirect_target'] ?? 'new_tab') . '" data-success-msg="' . $success_msg . '" data-error-msg="' . $error_msg . '" data-site-title="' . $site_title_js . '" data-site-url="' . $site_url_js . '"> 3339 3438 ' . $form_fields . ' … … 3594 3693 }()); 3595 3694 '; 3695 $inline_css .= $this->get_outlined_field_css('.submitrelay-shortcode-wrapper', $settings['outlined_label_bg'] ?? '#ffffff'); 3596 3696 wp_enqueue_style('submitrelay-public-css'); 3597 3697 wp_enqueue_script('submitrelay-public-js');
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)