{"id":248360,"date":"2025-09-04T11:39:51","date_gmt":"2025-09-04T11:39:51","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/mksddn-forms-handler\/"},"modified":"2026-08-31T14:51:53","modified_gmt":"2026-08-31T14:51:53","slug":"mksddn-forms-handler","status":"publish","type":"plugin","link":"https:\/\/ta.wordpress.org\/plugins\/mksddn-forms-handler\/","author":23351592,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"2.7.0","stable_tag":"trunk","tested":"7.1","requires":"5.3","requires_php":"8.0","requires_plugins":null,"header_name":"MksDdn Forms Handler","header_author":"mksddn","header_description":"Advanced form processing system with REST API support, Telegram notifications, and Google Sheets integration. Create and manage forms with multiple delivery methods including email, Telegram, Google Sheets, and admin storage.","assets_banners_color":"","last_updated":"2026-08-31 14:51:53","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"https:\/\/github.com\/mksddn\/WP-MksDdn-Forms-Handler","header_author_uri":"https:\/\/github.com\/mksddn","rating":0,"author_block_rating":0,"active_installs":10,"downloads":1259,"num_ratings":0,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"1.0.5":{"tag":"1.0.5","author":"mksddn","date":"2025-10-12 16:23:33","revision":3377029},"1.1.0":{"tag":"1.1.0","author":"mksddn","date":"2025-10-12 16:23:33","revision":3377029},"1.1.1":{"tag":"1.1.1","author":"mksddn","date":"2025-10-12 16:35:57","revision":3377033},"1.2.0":{"tag":"1.2.0","author":"mksddn","date":"2025-11-14 10:46:49","revision":3395669},"1.3.0":{"tag":"1.3.0","author":"mksddn","date":"2025-11-17 11:15:11","revision":3397141},"1.3.1":{"tag":"1.3.1","author":"mksddn","date":"2025-12-04 07:41:32","revision":3410534},"2.0.0":{"tag":"2.0.0","author":"mksddn","date":"2026-01-11 14:25:37","revision":3437083},"2.1.0":{"tag":"2.1.0","author":"mksddn","date":"2026-01-15 06:59:07","revision":3440064},"2.1.1":{"tag":"2.1.1","author":"mksddn","date":"2026-01-20 07:12:35","revision":3442989},"2.2.0":{"tag":"2.2.0","author":"mksddn","date":"2026-01-24 11:09:44","revision":3446076},"2.3.0":{"tag":"2.3.0","author":"mksddn","date":"2026-02-01 18:38:04","revision":3451535},"2.4.0":{"tag":"2.4.0","author":"mksddn","date":"2026-02-20 12:32:15","revision":3465792},"2.4.1":{"tag":"2.4.1","author":"mksddn","date":"2026-04-07 07:18:54","revision":3500413},"2.5.0":{"tag":"2.5.0","author":"mksddn","date":"2026-05-26 13:58:42","revision":3549229},"2.5.1":{"tag":"2.5.1","author":"mksddn","date":"2026-06-01 15:15:36","revision":3556946},"2.5.2":{"tag":"2.5.2","author":"mksddn","date":"2026-06-01 16:08:04","revision":3557017},"2.6.0":{"tag":"2.6.0","author":"mksddn","date":"2026-06-05 10:56:17","revision":3562247},"2.6.1":{"tag":"2.6.1","author":"mksddn","date":"2026-08-31 14:51:53","revision":3674492}},"upgrade_notice":{"2.7.0":"<p>New feature: Spam Protection \u2014 optional global rate limit, bot heuristics, and Cloudflare Turnstile. All opt-in by default; enable under Forms \u2192 Spam Protection and per-form Advanced settings. Recommended update if your forms receive bot spam.<\/p>","2.6.0":"<p>New feature: Trusted origins \u2014 optional per-form Origin\/Referer protection in Advanced settings. Opt-in only; default is off, so existing forms keep working unchanged.<\/p>","2.5.2":"<p>Bug fix: Google Sheets form submissions and tab name handling. Form title is now written to the sheet. Recommended update if you use Google Sheets integration.<\/p>","2.5.1":"<p>Bug fix: Google Sheets OAuth &quot;Connect to Google&quot; used an incorrect redirect URI after the settings page move in 2.5.0. Recommended update if you use Google Sheets integration.<\/p>","2.5.0":"<p>New feature: User reply email \u2014 optional auto-reply to the submitter with text template or uploaded HTML file. Recommended update.<\/p>","2.4.1":"<p>Improvement: Loading state and accessibility for the AJAX submit button (spinner, aria-busy, label restored after response). Optional update.<\/p>","2.4.0":"<p>New feature: Redirect URL after form submission and custom Telegram templates with placeholders. Email notification settings migration. Enhanced error logging and localization. Recommended update.<\/p>","2.3.0":"<p>Improvement: Localization for error messages and Telegram\/email\/Google Sheets UI. Telegram and email notifications use field labels from form config. Regex pattern validation fix. Attachment metadata generated only for images (faster uploads). Recommended update.<\/p>","2.2.0":"<p>New feature: Tabbed interface for form settings with better organization. Improved localization for Google Sheets settings page. Admin structure improvements for better usability. Recommended update.<\/p>","2.1.1":"<p>Improvement: Enhanced form accessibility with aria-label attributes for required fields without labels. Improved label management and form field handling. Recommended update for better accessibility compliance.<\/p>","2.1.0":"<p>Improvement: Page URL field moved from submission info to submission data section. Page URL is now included in all notifications (email, Telegram, Google Sheets). Recommended update for better notification tracking.<\/p>","2.0.0":"<p>Major update: Russian language support, form customization (custom submit button text, HTML after button, custom success messages), improved AJAX file uploads, enhanced security. Recommended update.<\/p>","1.3.1":"<p>Compatibility update: Tested with WordPress 6.9. Recommended update for users upgrading to WordPress 6.9.<\/p>","1.3.0":"<p>New <code>array_of_objects<\/code> field type with nested validation. Security: Arrays restricted to <code>array_of_objects<\/code> only. Update forms using <code>text<\/code> for arrays. Recommended for better security.<\/p>","1.2.0":"<p>New feature: Support for nested arrays and objects in form submissions (e.g., product arrays). Improved display of complex data structures in admin, email, and Telegram. Recommended update for e-commerce and complex form integrations.<\/p>","1.1.1":"<p>Security update: Fixed URL escaping in template examples. Recommended update for WordPress Coding Standards compliance.<\/p>","1.1.0":"<p>New feature: Template functions for custom forms integration. Bug fix: Improved Telegram message formatting. Fully backward compatible.<\/p>"},"ratings":[],"assets_icons":[],"assets_banners":[],"assets_blueprints":{},"all_blocks":[],"tagged_versions":["1.0.5","1.1.0","1.1.1","1.2.0","1.3.0","1.3.1","2.0.0","2.1.0","2.1.1","2.2.0","2.3.0","2.4.0","2.4.1","2.5.0","2.5.1","2.5.2","2.6.0","2.6.1"],"block_files":[],"assets_screenshots":[],"screenshots":[]},"plugin_section":[],"plugin_tags":[247395,601,25296,23853,9171],"plugin_category":[42],"plugin_contributors":[247396],"plugin_business_model":[],"class_list":["post-248360","plugin","type-plugin","status-publish","hentry","plugin_tags-form-handler","plugin_tags-forms","plugin_tags-google-sheets","plugin_tags-rest-api","plugin_tags-telegram","plugin_category-contact-forms","plugin_contributors-mksddn","plugin_committers-mksddn"],"banners":[],"icons":{"svg":false,"icon":"https:\/\/s.w.org\/plugins\/geopattern-icon\/mksddn-forms-handler.svg","icon_2x":false,"generated":true},"screenshots":[],"raw_content":"<!--section=description-->\n<p>MksDdn Forms Handler is a powerful and flexible form processing plugin that allows you to create and manage forms with multiple delivery methods. Perfect for websites that need reliable form handling with modern integrations.<\/p>\n\n<h4>Key Features<\/h4>\n\n<ul>\n<li><strong>Multiple Delivery Methods<\/strong>: Send form submissions via email, Telegram, Google Sheets, or store in WordPress admin<\/li>\n<li><strong>REST API Support<\/strong>: Submit forms via AJAX or REST API endpoints<\/li>\n<li><strong>Telegram Integration<\/strong>: Instant notifications to Telegram channels<\/li>\n<li><strong>Google Sheets Integration<\/strong>: Automatically save submissions to Google Sheets<\/li>\n<li><strong>Custom Post Types<\/strong>: Dedicated forms and submissions management<\/li>\n<li><strong>Security First<\/strong>: Built-in validation, sanitization, and security measures<\/li>\n<li><strong>Developer Friendly<\/strong>: Clean code structure with proper namespacing<\/li>\n<\/ul>\n\n<h4>Use Cases<\/h4>\n\n<ul>\n<li>Contact forms with multiple delivery options<\/li>\n<li>Lead generation forms with instant notifications<\/li>\n<li>Data collection forms with Google Sheets backup<\/li>\n<li>Custom forms with REST API integration<\/li>\n<\/ul>\n\n<h4>Technical Features<\/h4>\n\n<ul>\n<li>WordPress 5.3+ compatible (tested up to 7.1)<\/li>\n<li>PHP 8.0+ required<\/li>\n<li>GPL v2+ licensed<\/li>\n<li>Clean, maintainable code<\/li>\n<li>Proper error handling<\/li>\n<li>Extensible logging via WordPress action hooks<\/li>\n<\/ul>\n\n<p>Developer documentation, REST API reference, field types, and external services are in the FAQ section below.<\/p>\n\n<!--section=installation-->\n<ol>\n<li>Upload the plugin files to the <code>\/wp-content\/plugins\/mksddn-forms-handler<\/code> directory, or install the plugin through the WordPress plugins screen directly.<\/li>\n<li>Activate the plugin through the 'Plugins' screen in WordPress<\/li>\n<li>Use the <strong>Forms<\/strong> and <strong>Submissions<\/strong> admin menus to create forms and review submissions<\/li>\n<li>Use the shortcode <code>[mksddn_fh_form id=\"form_id\"]<\/code> or <code>[mksddn_fh_form slug=\"form-slug\"]<\/code> to display forms on your pages<\/li>\n<\/ol>\n\n<!--section=faq-->\n<dl>\n<dt id=\"how%20do%20i%20create%20my%20first%20form%3F\"><h3>How do I create my first form?<\/h3><\/dt>\n<dd><ol>\n<li>Go to Forms &gt; Add New in your WordPress admin<\/li>\n<li>Fill in the form title and description<\/li>\n<li>Configure the form settings in the meta box<\/li>\n<li>Add form fields in JSON format<\/li>\n<li>Set up delivery methods (email, Telegram, Google Sheets)<\/li>\n<li>Publish the form and use the shortcode to display it<\/li>\n<\/ol><\/dd>\n<dt id=\"how%20do%20i%20set%20up%20telegram%20notifications%3F\"><h3>How do I set up Telegram notifications?<\/h3><\/dt>\n<dd><ol>\n<li>Create a Telegram bot using @BotFather<\/li>\n<li>Get your bot token<\/li>\n<li>Find your chat ID using one of these methods:\n\n<ul>\n<li><strong>Method 1 - Using getUpdates API<\/strong>: Send a message to your bot (e.g., <code>\/start<\/code>), then call:\n <code>https:\/\/api.telegram.org\/bot{your_bot_token}\/getUpdates<\/code>\nLook for the <code>\"chat\"<\/code> object in the JSON response and find the <code>\"id\"<\/code> field. Note: Group chat IDs are typically negative numbers.<\/li>\n<li><strong>Method 2 - Using helper bots<\/strong>: Add @userinfobot, @ShowJsonBot, or @getidsbot to your chat - they will display the chat ID automatically<\/li>\n<li><strong>Method 3 - For groups<\/strong>: Add your bot to the group, send a message, then use getUpdates API to retrieve the group chat ID<\/li>\n<\/ul><\/li>\n<li>Add the bot token and chat IDs in the form settings<\/li>\n<li>Enable \"Send to Telegram\" option<\/li>\n<\/ol>\n\n<p><strong>Custom Telegram Templates:<\/strong>\nYou can create custom templates for Telegram notifications with placeholders:\n* System placeholders: <code>{form_title}<\/code>, <code>{date}<\/code>, <code>{time}<\/code>, <code>{datetime}<\/code>, <code>{page_url}<\/code>\n* Field placeholders: <code>{field:field_name}<\/code> for field value, <code>{field_label:field_name}<\/code> for field label\n* Supports HTML formatting: <code>b<\/code>, <code>i<\/code>, <code>u<\/code>, <code>s<\/code>, <code>code<\/code>, <code>pre<\/code>, <code>a<\/code>\nEnable \"Use Custom Template\" in Telegram settings and enter your template in the textarea.<\/p><\/dd>\n<dt id=\"how%20do%20i%20integrate%20with%20google%20sheets%3F\"><h3>How do I integrate with Google Sheets?<\/h3><\/dt>\n<dd><ol>\n<li>Set up Google Sheets API credentials<\/li>\n<li>Create a spreadsheet and get the ID from the URL<\/li>\n<li>Configure the sheet name and API credentials<\/li>\n<li>Enable \"Send to Google Sheets\" option<\/li>\n<\/ol><\/dd>\n<dt id=\"can%20i%20use%20this%20with%20custom%20themes%3F\"><h3>Can I use this with custom themes?<\/h3><\/dt>\n<dd><p>Yes! The plugin is designed to work with any WordPress theme. Forms are displayed using shortcodes and can be styled with CSS.<\/p><\/dd>\n<dt id=\"is%20the%20plugin%20secure%3F\"><h3>Is the plugin secure?<\/h3><\/dt>\n<dd><p>Yes, the plugin includes comprehensive security measures:\n* Input validation and sanitization\n* Nonce verification\n* Capability checks\n* Rate limiting protection\n* SQL injection prevention<\/p><\/dd>\n<dt id=\"can%20i%20submit%20forms%20via%20ajax%3F\"><h3>Can I submit forms via AJAX?<\/h3><\/dt>\n<dd><p>Yes! The plugin provides REST API endpoints for AJAX form submissions. Check the REST API section for details.<\/p><\/dd>\n<dt id=\"can%20i%20use%20custom%20forms%20from%20my%20theme%20without%20configuring%20fields%3F\"><h3>Can I use custom forms from my theme without configuring fields?<\/h3><\/dt>\n<dd><p>Yes! Enable \"Accept any fields from frontend\" in form settings (Advanced Settings section). This allows submitting any field names without defining them in Fields Configuration - perfect for custom forms where you control the HTML. All fields are still sanitized, but type validation is skipped. You can also use the <code>mksddn_fh_allowed_fields<\/code> filter in your theme's functions.php to dynamically allow specific fields or all fields (<code>return ['*']<\/code>).<\/p><\/dd>\n<dt id=\"can%20i%20redirect%20users%20after%20form%20submission%3F\"><h3>Can I redirect users after form submission?<\/h3><\/dt>\n<dd><p>Yes! Configure a redirect URL in form settings (Display tab). You can use:\n* Absolute URLs (same domain only for security)\n* Relative paths (e.g., <code>\/thank-you<\/code>)\nExternal domains are blocked by default for security. To allow external redirects, use the <code>mksddn_fh_allowed_redirect_hosts<\/code> filter to whitelist specific domains.<\/p><\/dd>\n<dt id=\"for%20developers\"><h3>For Developers<\/h3><\/dt>\n<dd><\/dd>\n<dt id=\"architecture\"><h3>Architecture<\/h3><\/dt>\n<dd><p><strong>Component-based structure<\/strong> following SOLID principles with clear separation of concerns:<\/p>\n\n<p><strong>Core Components (includes\/)<\/strong>\n* <code>PostTypes<\/code> - custom post types registration (<code>mksddn_fh_forms<\/code>, <code>mksddn_fh_submits<\/code>)\n* <code>MetaBoxes<\/code> - form settings and submission data management\n* <code>FormsHandler<\/code> - main processing logic, REST API, per-form rate limiting, delivery orchestration\n* <code>Shortcodes<\/code> - form rendering with AJAX functionality\n* <code>AdminColumns<\/code> - admin interface customization\n* <code>ExportHandler<\/code> - CSV export with filtering\n* <code>Security<\/code> - admin restrictions for submissions (blocks manual create\/edit in wp-admin)\n* <code>SpamProtection<\/code> - global rate limit, heuristics, Turnstile verification\n* <code>SpamSettingsAdmin<\/code> - global spam protection settings page\n* <code>Utilities<\/code> - helper functions and form creation utilities\n* <code>GoogleSheetsAdmin<\/code> - Google Sheets settings page and OAuth\n* <code>Assets<\/code> - asset registration and conditional enqueuing\n* <code>Template Functions<\/code> - global functions for PHP template integration<\/p>\n\n<p><strong>Traits (includes\/traits\/)<\/strong>\n* <code>TelegramFormatterTrait<\/code> - HTML escaping and formatting for Telegram messages<\/p>\n\n<p><strong>Handlers (handlers\/)<\/strong>\n* <code>TelegramHandler<\/code> - Telegram Bot API integration\n* <code>GoogleSheetsHandler<\/code> - Google Sheets API integration\n* <code>TemplateParser<\/code> - placeholder parsing for Telegram and user reply emails<\/p>\n\n<p><strong>Assets (assets\/)<\/strong>\n* <code>css\/admin.css<\/code> - Admin styles (form settings, export, Google Sheets and spam settings pages)\n* <code>js\/admin.js<\/code> - Admin scripts (tabs, previews, user reply and trusted origins UI)\n* <code>js\/form.js<\/code> - Frontend AJAX form submission (enqueued by shortcode or template helpers)\n* <code>js\/turnstile-loader.js<\/code> - Local loader for Cloudflare Turnstile widget script<\/p><\/dd>\n<dt id=\"technology%20stack\"><h3>Technology Stack<\/h3><\/dt>\n<dd><ul>\n<li>WordPress 5.3+ - core platform<\/li>\n<li>PHP 8.0+ - server-side logic<\/li>\n<li>jQuery - client-side form handling<\/li>\n<li>REST API - form submission API<\/li>\n<li>Google Sheets API - spreadsheet integration<\/li>\n<li>Telegram Bot API - notifications<\/li>\n<\/ul><\/dd>\n<dt id=\"file%20structure\"><h3>File Structure<\/h3><\/dt>\n<dd><p>mksddn-forms-handler\/\n    \u251c\u2500\u2500 mksddn-forms-handler.php     # Main plugin file\n    \u251c\u2500\u2500 includes\/                     # Core components\n    \u2502   \u251c\u2500\u2500 class-post-types.php\n    \u2502   \u251c\u2500\u2500 class-meta-boxes.php\n    \u2502   \u251c\u2500\u2500 class-forms-handler.php\n    \u2502   \u251c\u2500\u2500 class-shortcodes.php\n    \u2502   \u251c\u2500\u2500 class-admin-columns.php\n    \u2502   \u251c\u2500\u2500 class-export-handler.php\n    \u2502   \u251c\u2500\u2500 class-security.php\n    \u2502   \u251c\u2500\u2500 class-utilities.php\n    \u2502   \u251c\u2500\u2500 class-spam-protection.php\n    \u2502   \u251c\u2500\u2500 class-spam-settings-admin.php\n    \u2502   \u251c\u2500\u2500 class-google-sheets-admin.php\n    \u2502   \u251c\u2500\u2500 class-assets.php\n    \u2502   \u251c\u2500\u2500 template-functions.php\n    \u2502   \u2514\u2500\u2500 traits\/\n    \u2502       \u2514\u2500\u2500 trait-telegram-formatter.php\n    \u251c\u2500\u2500 handlers\/                     # External service handlers\n    \u2502   \u251c\u2500\u2500 class-telegram-handler.php\n    \u2502   \u251c\u2500\u2500 class-google-sheets-handler.php\n    \u2502   \u2514\u2500\u2500 class-template-parser.php\n    \u251c\u2500\u2500 templates\/                    # Template files\n    \u2502   \u251c\u2500\u2500 form-settings-meta-box.php\n    \u2502   \u2514\u2500\u2500 custom-form-examples.php\n    \u251c\u2500\u2500 assets\/                       # Static resources\n    \u2502   \u251c\u2500\u2500 css\/\n    \u2502   \u2502   \u2514\u2500\u2500 admin.css\n    \u2502   \u2514\u2500\u2500 js\/\n    \u2502       \u251c\u2500\u2500 admin.js\n    \u2502       \u251c\u2500\u2500 form.js\n    \u2502       \u2514\u2500\u2500 turnstile-loader.js\n    \u251c\u2500\u2500 languages\/                    # Translations\n    \u2514\u2500\u2500 uninstall.php                # Cleanup script<\/p><\/dd>\n<dt id=\"integration%20methods\"><h3>Integration Methods<\/h3><\/dt>\n<dd><p><strong>1. Shortcode (Standard)<\/strong><\/p>\n\n<pre><code>[mksddn_fh_form slug=\"contact-form\"]\n<\/code><\/pre>\n\n<p>Plugin automatically generates HTML form based on configuration.<\/p>\n\n<p><strong>2. PHP Templates (Custom Forms)<\/strong>\nIntegrate pre-built forms in theme templates:<\/p>\n\n<pre><code>&lt;form method=\"post\" action=\"&lt;?php echo mksddn_fh_get_form_action(); ?&gt;\"&gt;\n    &lt;?php mksddn_fh_form_fields('contact-form'); ?&gt;\n    &lt;!-- Your custom fields --&gt;\n    &lt;input type=\"text\" name=\"name\" required&gt;\n    &lt;input type=\"email\" name=\"email\" required&gt;\n    &lt;button type=\"submit\"&gt;Send&lt;\/button&gt;\n&lt;\/form&gt;\n<\/code><\/pre>\n\n<p><strong>Available Functions:<\/strong>\n* <code>mksddn_fh_get_form_action()<\/code> - get form action URL\n* <code>mksddn_fh_form_fields($slug)<\/code> - output hidden fields (nonce, form_id, honeypot)\n* <code>mksddn_fh_get_form_config($slug)<\/code> - get form configuration\n* <code>mksddn_fh_get_rest_endpoint($slug)<\/code> - get REST API endpoint for AJAX\n* <code>mksddn_fh_form_has_files($slug)<\/code> - check for file fields\n* <code>mksddn_fh_enqueue_form_script()<\/code> - enqueue AJAX script\n* <code>mksddn_fh_render_turnstile($slug)<\/code> - output Turnstile widget markup (when required)\n* <code>mksddn_fh_enqueue_turnstile()<\/code> - enqueue Turnstile loader script\n* <code>mksddn_fh_form_requires_turnstile($slug)<\/code> - check if form requires Turnstile<\/p>\n\n<p><strong>Accept Any Fields (Advanced):<\/strong>\nFor custom forms where you control field names in templates, enable \"Accept any fields from frontend\" in form settings (Advanced tab) to skip field validation. Stored as post meta <code>_allow_any_fields<\/code> (<code>0<\/code> \/ <code>1<\/code>). This allows submitting ANY field names without defining them in Fields Configuration. All fields are still sanitized but type validation is skipped.<\/p>\n\n<p>See <code>\/templates\/custom-form-examples.php<\/code> for detailed examples.<\/p>\n\n<p><strong>User Reply Email (Email Settings tab):<\/strong>\nOptional auto-reply to the user who submitted the form. Configure in the form Email Settings tab:<\/p>\n\n<ul>\n<li><code>_send_user_reply<\/code> \u2014 enable\/disable auto-reply (<code>0<\/code> \/ <code>1<\/code>)<\/li>\n<li><code>_user_reply_email_field<\/code> \u2014 field name from Fields Configuration (<code>type: email<\/code>)<\/li>\n<li><code>_user_reply_type<\/code> \u2014 <code>text<\/code> (template with placeholders) or <code>html<\/code> (uploaded HTML file)<\/li>\n<li><code>_user_reply_subject<\/code> \u2014 reply subject with placeholders<\/li>\n<li><code>_user_reply_message<\/code> \u2014 text template body<\/li>\n<li><code>_user_reply_html_template<\/code> \u2014 HTML file content stored in post meta<\/li>\n<\/ul>\n\n<p>Placeholders: <code>{form_title}<\/code>, <code>{date}<\/code>, <code>{time}<\/code>, <code>{datetime}<\/code>, <code>{page_url}<\/code>, <code>{field:field_name}<\/code>, <code>{field_label:field_name}<\/code>.<\/p>\n\n<p>HTML template upload: <code>.html<\/code>\/<code>.htm<\/code> only, max 100 KB (filter <code>mksddn_fh_max_html_template_size<\/code>). PHP code and script tags in templates are rejected.<\/p>\n\n<p>User reply can be the only enabled delivery channel \u2014 a successful auto-reply counts as a successful form submission. Auto-reply failure alone does not block submission when another channel succeeds; result is reported in <code>delivery_results.user_reply_email<\/code>.<\/p>\n\n<p><strong>3. REST API (AJAX)<\/strong>\nSubmit forms via REST API without page reload:<\/p>\n\n<pre><code>fetch('&lt;?php echo mksddn_fh_get_rest_endpoint(\"contact-form\"); ?&gt;', {\n    method: 'POST',\n    headers: { 'Content-Type': 'application\/json' },\n    body: JSON.stringify(formData)\n});\n<\/code><\/pre><\/dd>\n<dt id=\"development%20standards\"><h3>Development Standards<\/h3><\/dt>\n<dd><p><strong>Coding<\/strong>\n* WordPress Coding Standards compliance\n* SOLID principles\n* DRY (Don't Repeat Yourself)\n* KISS (Keep It Simple)<\/p>\n\n<p><strong>Security<\/strong>\n* Input validation for all data\n* Output sanitization\n* Nonce verification (CSRF protection)\n* Capability checks\n* Rate limiting (1 request per 10 seconds per IP per form)\n* Optional global rate limit across all forms (Forms \u2192 Spam Protection)\n* Optional spam heuristics and Cloudflare Turnstile (global defaults + per-form inherit \/ on \/ off)\n* Optional trusted origins check (Origin\/Referer) per form \u2014 off by default<\/p>\n\n<p><strong>Performance<\/strong>\n* Minimal database queries\n* Data caching\n* Lazy loading of resources\n* Conditional script enqueuing<\/p>\n\n<p><strong>Compatibility<\/strong>\n* WordPress 5.3+ minimum\n* PHP 8.0+ minimum\n* Works on WordPress multisite (no network-specific settings UI)\n* Frontend forms use semantic HTML and ARIA attributes (aria-label, aria-busy, role=\"radiogroup\"); theme styling and RTL layout depend on the active theme<\/p><\/dd>\n<dt id=\"wordpress%20hooks%20%26%20filters\"><h3>WordPress Hooks &amp; Filters<\/h3><\/dt>\n<dd><p><strong>Filters:<\/strong><\/p>\n\n<pre><code>mksddn_fh_allowed_fields - Modify allowed field names for a form\n\nadd_filter('mksddn_fh_allowed_fields', function($allowed_fields, $form_id, $form_slug) {\n    \/\/ Allow all fields for specific form\n    if ($form_slug === 'my-custom-form') {\n        return ['*'];\n    }\n    \/\/ Add specific fields\n    return array_merge($allowed_fields, ['custom_field_1', 'custom_field_2']);\n}, 10, 3);\n\nmksddn_fh_allowed_redirect_hosts - Whitelist external domains for redirect URLs\n\nadd_filter('mksddn_fh_allowed_redirect_hosts', function($hosts) {\n    return array_merge($hosts, ['example.com', 'trusted-partner.com']);\n});\n\nmksddn_fh_max_html_template_size - Maximum size in bytes for uploaded user reply HTML templates (default: 102400)\n\nmksddn_fh_max_template_size - Maximum Telegram custom template size in characters (default: 10000)\n\nmksddn_forms_telegram_message - Modify the Telegram message before sending\n\nadd_filter('mksddn_forms_telegram_message', function($message, $form_data, $form_title, $fields_config) {\n    return $message . \"\\n\u2014 Sent via site\";\n}, 10, 4);\n\nmksddn_fh_turnstile_verify_url - Override the Cloudflare Turnstile siteverify endpoint URL\n\nmksddn_fh_trusted_origins_bypass - Skip trusted origins validation for a request (default: false)\n\nadd_filter('mksddn_fh_trusted_origins_bypass', function($bypass, $form_id, $mode) {\n    \/\/ Allow server-side proxy submissions for a specific form\n    if ($form_id === 123 &amp;&amp; $mode === 'allowlist') {\n        return true;\n    }\n    return $bypass;\n}, 10, 3);\n\nmksddn_fh_before_submit - Block or allow submission after validation, before delivery\n\nadd_filter('mksddn_fh_before_submit', function($allowed, $form_data, $form_config) {\n    if (isset($form_data['email']) &amp;&amp; str_contains($form_data['email'], 'spam.example')) {\n        return new WP_Error('blocked', 'Blocked');\n    }\n    return $allowed;\n}, 10, 3);\n\nmksddn_fh_is_spam - Custom spam decision when built-in heuristics are enabled (default: false)\n\nmksddn_fh_spam_multi_select_threshold - Minimum selected options count to treat as spam (default: 7)\n\nmksddn_fh_client_ip - Override client IP used for rate limiting and Turnstile (default: `REMOTE_ADDR`). Use this behind Cloudflare\/a reverse proxy after you restore the real visitor IP; do not trust `X-Forwarded-For` from the client.\n<\/code><\/pre>\n\n<p><strong>Actions:<\/strong><\/p>\n\n<pre><code>mksddn_forms_handler_log_security - Fired when unauthorized fields are detected (hook for custom logging; no built-in log storage)\nmksddn_forms_handler_log_submission - Fired when form submission is processed (hook for custom logging)\n<\/code><\/pre><\/dd>\n<dt id=\"rest%20api\"><h3>REST API<\/h3><\/dt>\n<dd><p>Namespace: <code>mksddn-forms-handler\/v1<\/code><\/p><\/dd>\n<dt id=\"list%20forms\"><h3>List Forms<\/h3><\/dt>\n<dd><ul>\n<li><strong>Method<\/strong>: GET<\/li>\n<li><strong>Path<\/strong>: <code>\/wp-json\/mksddn-forms-handler\/v1\/forms<\/code><\/li>\n<li><strong>Query Parameters<\/strong>:\n\n<ul>\n<li><code>per_page<\/code> (1\u2013100, default: 10)<\/li>\n<li><code>page<\/code> (&gt;=1, default: 1)<\/li>\n<li><code>search<\/code> (string, optional)<\/li>\n<\/ul><\/li>\n<li><strong>Response Headers<\/strong>: <code>X-WP-Total<\/code>, <code>X-WP-TotalPages<\/code><\/li>\n<\/ul><\/dd>\n<dt id=\"get%20single%20form\"><h3>Get Single Form<\/h3><\/dt>\n<dd><ul>\n<li><strong>Method<\/strong>: GET<\/li>\n<li><strong>Path<\/strong>: <code>\/wp-json\/mksddn-forms-handler\/v1\/forms\/{slug}<\/code><\/li>\n<li><strong>Response<\/strong>: Includes <code>id<\/code>, <code>slug<\/code>, <code>title<\/code>, <code>submit_url<\/code>, <code>fields<\/code> (sanitized config), <code>require_turnstile<\/code>, and <code>turnstile_site_key<\/code> when Turnstile is required (never the secret key)<\/li>\n<\/ul><\/dd>\n<dt id=\"submit%20form\"><h3>Submit Form<\/h3><\/dt>\n<dd><ul>\n<li><strong>Method<\/strong>: POST<\/li>\n<li><strong>Path<\/strong>: <code>\/wp-json\/mksddn-forms-handler\/v1\/forms\/{slug}\/submit<\/code><\/li>\n<li><strong>Content Types<\/strong>: JSON or multipart\/form-data<\/li>\n<li><strong>Body (JSON)<\/strong>: Key\/value pairs according to field configuration. The <code>mksddn_fh_hp<\/code> honeypot field may be present and must be empty (spam protection).<\/li>\n<li><strong>Body (Multipart)<\/strong>: Fields and file uploads supported. For multiple files, use <code>name[]<\/code>.<\/li>\n<\/ul><\/dd>\n<dt id=\"validation%20%26%20limits\"><h3>Validation &amp; Limits<\/h3><\/dt>\n<dd><ul>\n<li>Only configured fields accepted; unauthorized fields return <code>unauthorized_fields<\/code> error<\/li>\n<li>Required fields, email, URL, number (min\/max\/step), tel (pattern), date, time, datetime-local are validated<\/li>\n<li>Maximum 50 fields; total payload size \u2264 100 KB<\/li>\n<li>Rate limiting: 1 request per 10 seconds per IP per form<\/li>\n<li>Optional global rate limit (all forms combined) \u2014 configure under Forms \u2192 Spam Protection<\/li>\n<li>Optional trusted origins check per form (see below); disabled by default (<code>off<\/code>)<\/li>\n<\/ul><\/dd>\n<dt id=\"spam%20protection%20%28opt-in%29\"><h3>Spam Protection (opt-in)<\/h3><\/dt>\n<dd><p>Global settings: <strong>Forms \u2192 Spam Protection<\/strong>. Per-form overrides: <strong>Advanced<\/strong> tab on each form.<\/p>\n\n<p><strong>Global rate limit<\/strong>\n* Off by default for backward compatibility\n* Limits total submissions per IP across all forms within a time window (default: 20 per hour)\n* Works together with the per-form 10-second limit\n* Blocked requests return <code>global_rate_limited<\/code> (HTTP 429)<\/p>\n\n<p><strong>Spam heuristics<\/strong>\n* Global master switch + per-form mode: inherit \/ on \/ off\n* Detects common bot patterns: Latin-only name-like strings (15+ chars) with low vowel ratio or long consonant runs; selecting too many options on a multi-select or checkbox group (threshold filterable, default 7)\n* Does not flag <code>array_of_objects<\/code>, file fields, or free-text values outside configured name fields\n* Blocked requests return <code>spam_detected<\/code> (HTTP 400)<\/p>\n\n<p><strong>Cloudflare Turnstile<\/strong>\n* Configure both site key and secret key globally \u2014 captcha is not enforced until both are saved\n* Global default: <strong>Require Turnstile by default<\/strong> on the Spam Protection page\n* Per-form mode in Advanced settings: inherit \/ on \/ off (legacy per-form checkbox values <code>1<\/code>\/<code>0<\/code> map to on\/inherit)\n* Shortcode forms render the widget automatically\n* Custom REST\/AJAX forms must send <code>cf-turnstile-response<\/code> or <code>mksddn_fh_turnstile_response<\/code> in the request body\n* GET form meta includes <code>require_turnstile<\/code> and <code>turnstile_site_key<\/code> when enabled\n* Template helpers: <code>mksddn_fh_render_turnstile()<\/code>, <code>mksddn_fh_enqueue_turnstile()<\/code>, <code>mksddn_fh_form_requires_turnstile()<\/code>\n* Errors: <code>turnstile_required<\/code>, <code>turnstile_failed<\/code>, <code>turnstile_not_configured<\/code>\n* Rate limiting runs before Turnstile verification so failed captcha attempts cannot hammer Cloudflare\n* Behind a reverse proxy, restore the visitor IP (or use <code>mksddn_fh_client_ip<\/code>); <code>REMOTE_ADDR<\/code> alone may be the proxy IP<\/p><\/dd>\n<dt id=\"trusted%20origins%20%28opt-in%29\"><h3>Trusted Origins (opt-in)<\/h3><\/dt>\n<dd><p>Additional layer on top of nonce, honeypot, and rate limiting. Configure per form in <strong>Advanced<\/strong> tab.<\/p>\n\n<p><strong>Modes<\/strong>\n* <code>off<\/code> (default) \u2014 no origin check; existing forms behave unchanged after plugin update\n* <code>same_site<\/code> \u2014 accept submissions only from the WordPress site origin (<code>home_url<\/code> \/ <code>site_url<\/code>)\n* <code>allowlist<\/code> \u2014 accept only origins listed in the form settings (one per line, e.g. <code>https:\/\/www.example.com<\/code>)<\/p>\n\n<p><strong>Headers<\/strong>\n* Primary: <code>Origin<\/code>\n* Fallback: <code>Referer<\/code> when enabled (default on) for browser form posts without <code>Origin<\/code><\/p>\n\n<p><strong>Errors<\/strong>\n* Blocked requests return <code>origin_not_allowed<\/code> (HTTP 403) in REST and admin-post JSON responses<\/p>\n\n<p><strong>Notes<\/strong>\n* Subdomains, <code>www<\/code>, and ports are distinct origins \u2014 list each explicitly in allowlist mode\n* No database migration: missing meta uses runtime defaults (<code>off<\/code>, referer fallback on)\n* Filter <code>mksddn_fh_trusted_origins_bypass<\/code> for infrastructure edge cases<\/p><\/dd>\n<dt id=\"examples\"><h3>Examples<\/h3><\/dt>\n<dd><p><strong>List forms:<\/strong><\/p>\n\n<pre><code>curl -s 'https:\/\/example.com\/wp-json\/mksddn-forms-handler\/v1\/forms'\n<\/code><\/pre>\n\n<p><strong>Get single form:<\/strong><\/p>\n\n<pre><code>curl -s 'https:\/\/example.com\/wp-json\/mksddn-forms-handler\/v1\/forms\/contact'\n<\/code><\/pre>\n\n<p><strong>Submit form (JSON):<\/strong><\/p>\n\n<pre><code>curl -s -X POST \\\n  -H 'Content-Type: application\/json' \\\n  -d '{\"name\":\"John\",\"email\":\"john@example.com\",\"message\":\"Hi\",\"mksddn_fh_hp\":\"\"}' \\\n  'https:\/\/example.com\/wp-json\/mksddn-forms-handler\/v1\/forms\/contact\/submit'\n<\/code><\/pre>\n\n<p><strong>Submit form with files (multipart):<\/strong><\/p>\n\n<pre><code>curl -s -X POST \\\n  -F 'name=John' \\\n  -F 'email=john@example.com' \\\n  -F 'attachments[]=@\/path\/to\/file1.pdf' \\\n  -F 'attachments[]=@\/path\/to\/file2.png' \\\n  'https:\/\/example.com\/wp-json\/mksddn-forms-handler\/v1\/forms\/contact\/submit'\n<\/code><\/pre><\/dd>\n<dt id=\"supported%20field%20types\"><h3>Supported Field Types<\/h3><\/dt>\n<dd><p>Fields are configured as JSON in the form settings. Supported types:<\/p>\n\n<ul>\n<li><strong>Basic<\/strong>: text, email, password<\/li>\n<li><strong>Input<\/strong>: tel, url, number, date, time, datetime-local<\/li>\n<li><strong>Text<\/strong>: textarea<\/li>\n<li><strong>Choice<\/strong>: checkbox, select (supports multiple), radio<\/li>\n<li><strong>File<\/strong>: file uploads (form and REST multipart)<\/li>\n<li><strong>Array<\/strong>: array_of_objects - array of objects with nested field validation<\/li>\n<\/ul><\/dd>\n<dt id=\"field%20configuration%20notes\"><h3>Field Configuration Notes<\/h3><\/dt>\n<dd><ul>\n<li><code>name<\/code> - field name (required, used as form input name)<\/li>\n<li><code>label<\/code> - field label displayed in forms and admin (optional, falls back to <code>name<\/code>)<\/li>\n<li><code>notification_label<\/code> - custom label for Telegram\/email notifications (optional, priority: notification_label \u2192 label \u2192 name)<\/li>\n<li><code>type<\/code> - field type (required)<\/li>\n<li><code>required<\/code> - whether field is required (boolean, default: false)<\/li>\n<li><code>options<\/code> can be an array of strings or objects <code>{ \"value\": \"...\", \"label\": \"...\" }<\/code><\/li>\n<li>For <code>select<\/code> with multiple choice, set <code>multiple: true<\/code> (shortcode renders <code>name[]<\/code>)<\/li>\n<li>For <code>number<\/code>, optional attributes: <code>min<\/code>, <code>max<\/code>, <code>step<\/code><\/li>\n<li>For <code>tel<\/code>, optional <code>pattern<\/code> (default server validation uses <code>^\\+?\\d{7,15}$<\/code>)<\/li>\n<li>For <code>date\/time\/datetime-local<\/code>, server validates formats: <code>YYYY-MM-DD<\/code>, <code>HH:MM<\/code>, <code>YYYY-MM-DDTHH:MM<\/code><\/li>\n<li>For REST submissions, send arrays for multiple selects<\/li>\n<li>Pattern validation: use standard regex syntax (backslashes are preserved, invalid patterns are rejected)<\/li>\n<\/ul><\/dd>\n<dt id=\"file%20field%20options\"><h3>File Field Options<\/h3><\/dt>\n<dd><ul>\n<li><code>allowed_extensions<\/code>: Array of extensions, e.g. <code>[\"pdf\",\"png\",\"jpg\"]<\/code><\/li>\n<li><code>max_size_mb<\/code>: Maximum size per file (default: 10)<\/li>\n<li><code>max_files<\/code>: Maximum files per field (default: 5)<\/li>\n<li><code>multiple<\/code>: Allow multiple files<\/li>\n<\/ul><\/dd>\n<dt id=\"example%20json%20configuration\"><h3>Example JSON Configuration<\/h3><\/dt>\n<dd><p>[\n      {\"name\":\"name\",\"label\":\"Name\",\"type\":\"text\",\"required\":true,\"placeholder\":\"Your name\"},\n      {\"name\":\"email\",\"label\":\"Email\",\"notification_label\":\"Email Address\",\"type\":\"email\",\"required\":true},\n      {\"name\":\"phone\",\"label\":\"Phone\",\"type\":\"tel\",\"pattern\":\"^&#092;&#092;+?&#092;&#092;d{7,15}$\"},\n      {\"name\":\"website\",\"label\":\"Website\",\"type\":\"url\"},\n      {\"name\":\"age\",\"label\":\"Age\",\"type\":\"number\",\"min\":1,\"max\":120,\"step\":1},\n      {\"name\":\"birth\",\"label\":\"Birth date\",\"type\":\"date\"},\n      {\"name\":\"message\",\"label\":\"Message\",\"type\":\"textarea\",\"required\":true},\n      {\"name\":\"agree\",\"label\":\"I agree to Terms\",\"type\":\"checkbox\",\"required\":true},\n      {\n        \"name\":\"services\",\n        \"label\":\"Choose services\",\n        \"type\":\"select\",\n        \"multiple\":true,\n        \"options\":[\"seo\",\"smm\",\"ads\"]\n      },\n      {\n        \"name\":\"attachments\",\n        \"label\":\"Attach files\",\n        \"type\":\"file\",\n        \"multiple\":true,\n        \"allowed_extensions\":[\"pdf\",\"png\",\"jpg\"],\n        \"max_size_mb\":10,\n        \"max_files\":3\n      },\n      {\n        \"name\":\"products\",\n        \"label\":\"Products\",\n        \"type\":\"array_of_objects\",\n        \"required\":true,\n        \"fields\":[\n          {\"name\":\"name\",\"label\":\"Product Name\",\"type\":\"text\",\"required\":true},\n          {\"name\":\"size\",\"label\":\"Size\",\"type\":\"text\",\"required\":true},\n          {\"name\":\"color\",\"label\":\"Color\",\"type\":\"text\",\"required\":true},\n          {\"name\":\"quantity\",\"label\":\"Quantity\",\"type\":\"number\",\"required\":true,\"min\":1},\n          {\"name\":\"price\",\"label\":\"Price\",\"type\":\"number\",\"required\":true,\"min\":0}\n        ]\n      }\n    ]<\/p><\/dd>\n<dt id=\"pattern%20validation%20examples\"><h3>Pattern Validation Examples<\/h3><\/dt>\n<dd><p>Common regex patterns for validation (use in JSON with double backslashes):<\/p>\n\n<ul>\n<li>Phone (international): <code>\"pattern\": \"^\\\\+?\\\\d{7,15}$\"<\/code><\/li>\n<li>Phone (US): <code>\"pattern\": \"^\\\\(\\\\d{3}\\\\)\\\\s?\\\\d{3}-\\\\d{4}$\"<\/code><\/li>\n<li>Postal code (US): <code>\"pattern\": \"^\\\\d{5}(-\\\\d{4})?$\"<\/code><\/li>\n<li>Postal code (RU): <code>\"pattern\": \"^\\\\d{6}$\"<\/code><\/li>\n<li>Only letters: <code>\"pattern\": \"^[a-zA-Z]+$\"<\/code><\/li>\n<li>Alphanumeric: <code>\"pattern\": \"^[a-zA-Z0-9]+$\"<\/code><\/li>\n<li>URL slug: <code>\"pattern\": \"^[a-z0-9-]+$\"<\/code><\/li>\n<\/ul>\n\n<p><strong>Important notes:<\/strong>\n* In JSON, backslashes must be doubled (e.g., <code>\\\\d<\/code> instead of <code>\\d<\/code>, <code>\\\\+<\/code> instead of <code>\\+<\/code>)\n* HTML tags in patterns will be automatically removed for security\n* Invalid regex patterns will be rejected silently (check debug.log if WP_DEBUG is enabled)<\/p><\/dd>\n<dt id=\"array%20of%20objects%20field%20type\"><h3>Array of Objects Field Type<\/h3><\/dt>\n<dd><p>The <code>array_of_objects<\/code> type allows you to define arrays with nested field validation. Each item in the array is validated according to the nested <code>fields<\/code> configuration.<\/p>\n\n<p><strong>Configuration:<\/strong>\n* <code>name<\/code>: Field name (required)\n* <code>label<\/code>: Field label (required)\n* <code>notification_label<\/code>: Custom label for notifications (optional, priority: notification_label \u2192 label \u2192 name)\n* <code>type<\/code>: Must be <code>\"array_of_objects\"<\/code> (required)\n* <code>required<\/code>: Whether the array is required (default: false)\n* <code>fields<\/code>: Array of field configurations for each object in the array (required)<\/p>\n\n<p><strong>Nested fields support all standard field types<\/strong> (text, email, tel, url, number, textarea, etc.) with full validation. Nested fields also support <code>notification_label<\/code> for custom labels in Telegram\/email notifications.<\/p>\n\n<p><strong>Example REST API submission:<\/strong><\/p>\n\n<pre><code>{\n  \"email\": \"user@example.com\",\n  \"phone\": \"+1234567890\",\n  \"products\": [\n    {\n      \"name\": \"T-Shirt\",\n      \"size\": \"M\",\n      \"color\": \"Red\",\n      \"quantity\": 2,\n      \"price\": 1500\n    },\n    {\n      \"name\": \"Jeans\",\n      \"size\": \"L\",\n      \"color\": \"Blue\",\n      \"quantity\": 1,\n      \"price\": 3000\n    }\n  ]\n}\n<\/code><\/pre><\/dd>\n<dt id=\"external%20services\"><h3>External Services<\/h3><\/dt>\n<dd><p>This plugin can connect to external services when explicitly enabled in a form's settings:<\/p><\/dd>\n<dt id=\"google%20oauth2%20and%20google%20sheets%20api\"><h3>Google OAuth2 and Google Sheets API<\/h3><\/dt>\n<dd><ul>\n<li><strong>Purpose<\/strong>: Authenticate and append rows to a spreadsheet<\/li>\n<li><strong>When<\/strong>: Only if \"Send to Google Sheets\" is enabled for a form and valid credentials are provided<\/li>\n<li><strong>Data sent<\/strong>: Form fields configured for the form, form title, timestamp<\/li>\n<li><strong>Endpoints used<\/strong>: <code>https:\/\/oauth2.googleapis.com\/token<\/code>, <code>https:\/\/sheets.googleapis.com\/v4\/spreadsheets\/...<\/code><\/li>\n<li><strong>Terms<\/strong>: https:\/\/policies.google.com\/terms<\/li>\n<li><strong>Privacy<\/strong>: https:\/\/policies.google.com\/privacy<\/li>\n<\/ul><\/dd>\n<dt id=\"telegram%20bot%20api\"><h3>Telegram Bot API<\/h3><\/dt>\n<dd><ul>\n<li><strong>Purpose<\/strong>: Send a message with submission content to specified chat(s)<\/li>\n<li><strong>When<\/strong>: Only if \"Send to Telegram\" is enabled for a form and bot token + chat IDs are configured<\/li>\n<li><strong>Data sent<\/strong>: Form fields configured for the form, form title<\/li>\n<li><strong>Endpoint used<\/strong>: <code>https:\/\/api.telegram.org\/bot&lt;token&gt;\/sendMessage<\/code><\/li>\n<li><strong>Terms\/Privacy<\/strong>: https:\/\/telegram.org\/privacy<\/li>\n<\/ul><\/dd>\n<dt id=\"privacy%20notes\"><h3>Privacy Notes<\/h3><\/dt>\n<dd><ul>\n<li>No IP address or user agent is transmitted to external services; only form field values are sent<\/li>\n<li>External delivery is opt-in per form and disabled by default<\/li>\n<\/ul><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>2.7.0<\/h4>\n\n<ul>\n<li>Feature: Spam Protection settings page (Forms \u2192 Spam Protection) \u2014 global rate limit, heuristics master switch, Turnstile keys<\/li>\n<li>Feature: Per-form Advanced options \u2014 Require Turnstile, spam heuristics (inherit \/ on \/ off)<\/li>\n<li>Feature: Cloudflare Turnstile verification for REST and admin-post submissions<\/li>\n<li>Feature: Built-in spam heuristics (gibberish Latin name detection, excessive multi-select)<\/li>\n<li>Feature: Global rate limit across all forms per IP (default off)<\/li>\n<li>Filter: <code>mksddn_fh_before_submit<\/code> \u2014 block submission before delivery channels run<\/li>\n<li>Filter: <code>mksddn_fh_is_spam<\/code> \u2014 custom spam rules when heuristics are enabled<\/li>\n<li>Filter: <code>mksddn_fh_spam_multi_select_threshold<\/code> \u2014 adjust multi-select spam threshold<\/li>\n<li>Helpers: <code>mksddn_fh_render_turnstile()<\/code>, <code>mksddn_fh_enqueue_turnstile()<\/code>, <code>mksddn_fh_form_requires_turnstile()<\/code><\/li>\n<li>Improved: Internal fields (honeypot, Turnstile token) stripped before field validation<\/li>\n<li>Compatibility: All spam features are opt-in; existing forms behave unchanged after update<\/li>\n<\/ul>\n\n<h4>2.6.0<\/h4>\n\n<ul>\n<li>Feature: Trusted origins \u2014 optional per-form Origin\/Referer allowlist (<code>off<\/code> | <code>same_site<\/code> | <code>allowlist<\/code>) in Advanced settings<\/li>\n<li>Feature: Referer fallback toggle for browser compatibility when Origin header is absent<\/li>\n<li>Security: Submissions blocked with <code>origin_not_allowed<\/code> before file processing and delivery integrations<\/li>\n<li>Security: Origin\/Referer headers sanitized with <code>sanitize_url<\/code> (WordPress convention)<\/li>\n<li>Filter: <code>mksddn_fh_trusted_origins_bypass<\/code> for edge-case infrastructure bypass<\/li>\n<li>Improved: Trusted origins settings cached in form config to avoid extra meta queries<\/li>\n<li>Improved: Rate limit is not consumed when a request is blocked by origin validation<\/li>\n<li>Improved: Admin error notice when allowlist mode is saved without valid origins<\/li>\n<li>Improved: Invalid <code>trusted_origins_mode<\/code> values are treated as <code>off<\/code> (logged, request allowed)<\/li>\n<li>Compatibility: Default mode is <code>off<\/code>; explicit runtime defaults; no migration required for existing forms<\/li>\n<\/ul>\n\n<h4>2.5.2<\/h4>\n\n<ul>\n<li>Fixed: Google Sheets submissions failed on PHP 8+ because form title was not passed to the API handler<\/li>\n<li>Fixed: Sheet tab targeting \u2014 empty tab name appends to the first tab; named tabs use correct A1 notation and URL encoding<\/li>\n<li>Improved: Form title is included as the second column in Google Sheets rows<\/li>\n<li>Improved: Google Sheets tab name field label and help text<\/li>\n<\/ul>\n\n<h4>2.5.1<\/h4>\n\n<ul>\n<li>Fixed: Google Sheets OAuth redirect URI mismatch \u2014 \"Connect to Google\" now uses the same redirect URI as the token exchange and setup instructions<\/li>\n<li>Improved: Single source of truth for OAuth redirect URI via <code>GoogleSheetsHandler::get_oauth_redirect_uri()<\/code><\/li>\n<li>Fixed: Admin assets enqueue on the Google Sheets settings submenu page<\/li>\n<\/ul>\n\n<h4>2.5.0<\/h4>\n\n<ul>\n<li>Feature: User reply email \u2014 optional auto-reply to the submitter in Email Settings (text template with placeholders or uploaded HTML file)<\/li>\n<li>Feature: <code>TemplateParser::parse_for_email()<\/code> for HTML-safe placeholder replacement in user reply emails<\/li>\n<li>Filter: <code>mksddn_fh_max_html_template_size<\/code> for HTML template upload size limit<\/li>\n<\/ul>\n\n<h4>2.4.1<\/h4>\n\n<ul>\n<li>Improved: Submit button shows a loading spinner during AJAX submission; original button markup is restored on complete (custom labels\/HTML preserved)<\/li>\n<li>Improved: Accessibility \u2014 disabled state, aria-busy, and aria-label on the submit button while the request is in progress<\/li>\n<\/ul>\n\n<h4>2.4.0<\/h4>\n\n<ul>\n<li>Feature: Redirect URL after form submission - configure absolute URLs (same domain) or relative paths; external domains require whitelist filter<\/li>\n<li>Feature: Custom Telegram templates with placeholders for form fields, system data (date, time, page URL), and field labels; supports HTML formatting<\/li>\n<li>Improved: Email notification settings migration for existing forms<\/li>\n<li>Improved: Enhanced error logging and input sanitization<\/li>\n<li>Improved: Localization support for admin confirmation and error messages<\/li>\n<li>Technical: TemplateParser class and TelegramFormatterTrait for template handling<\/li>\n<\/ul>\n\n<h4>2.3.0<\/h4>\n\n<ul>\n<li>Improved: Localization \u2014 error messages (spam, rate limit, invalid data), notification labels (Telegram\/email), default email subject, and Google Sheets connection messages are now translatable<\/li>\n<li>Improved: Telegram and email notifications use field labels from form configuration (notification_label \u2192 label \u2192 name), including nested fields in array_of_objects; Google Sheets continues to receive values only (no label mapping)<\/li>\n<li>Improved: Regex pattern validation on form config save \u2014 preserves backslashes, validates syntax; invalid patterns rejected with debug log<\/li>\n<li>Improved: Fields config JSON stored with wp_slash to prevent backslash stripping; pattern note added in Fields Configuration UI<\/li>\n<li>Improved: Attachment metadata (thumbnails, etc.) generated only for image MIME types \u2014 faster handling of large non-image uploads<\/li>\n<li>Technical: TelegramHandler and FormsHandler::build_email_body() accept optional fields_config for label mapping; Utilities::sanitize_pattern_for_storage() for pattern sanitization<\/li>\n<\/ul>\n\n<h4>2.2.0<\/h4>\n\n<ul>\n<li>Feature: Tabbed interface for form settings - improved organization with separate tabs for Form Fields, Email Settings, Telegram, Google Sheets, Admin Storage, Display, and Advanced options<\/li>\n<li>Improved: Google Sheets settings page moved to a submenu under Forms section for better admin structure<\/li>\n<li>Improved: Enhanced localization support for Google Sheets settings page with Russian and English translations<\/li>\n<li>Technical: Updated redirect URIs to point to the correct admin page with improved consistency<\/li>\n<li>Technical: Added tab navigation functionality with JavaScript for seamless settings management<\/li>\n<\/ul>\n\n<h4>2.1.1<\/h4>\n\n<ul>\n<li>Improved: Enhanced form accessibility with aria-label attributes for required fields without labels<\/li>\n<li>Improved: Better label management - removed unnecessary label display for fields without labels<\/li>\n<li>Improved: Enhanced form examples for consistency in required field handling<\/li>\n<li>Technical: Refactored form field handling to improve accessibility compliance<\/li>\n<\/ul>\n\n<h4>2.1.0<\/h4>\n\n<ul>\n<li>Improved: Page URL field moved from \"Submission Info\" meta box to \"Submission Data\" section<\/li>\n<li>Improved: Page URL is now included in all notifications (email, Telegram, Google Sheets)<\/li>\n<li>Technical: Added <code>get_page_url()<\/code> method to extract page URL from referer<\/li>\n<li>Improved: Page URL automatically displayed as clickable link in submission data<\/li>\n<\/ul>\n\n<h4>2.0.0<\/h4>\n\n<ul>\n<li>Feature: Added Russian language support with complete translation files (.po and .mo)<\/li>\n<li>Feature: Form customization - custom submit button text, HTML after button, and custom success messages<\/li>\n<li>Feature: Improved file upload handling via AJAX with FormData support<\/li>\n<li>Security: Added direct access checks in multiple classes for better security<\/li>\n<li>Improved: Enhanced error handling with JSON responses for nonce verification failures<\/li>\n<li>Improved: Updated documentation for shortcode usage and asset registration<\/li>\n<\/ul>\n\n<h4>1.3.1<\/h4>\n\n<ul>\n<li>Compatibility: Tested with WordPress 6.9<\/li>\n<\/ul>\n\n<h4>1.3.0<\/h4>\n\n<ul>\n<li>Feature: New <code>array_of_objects<\/code> field type with full nested field validation<\/li>\n<li>Feature: Type-specific validation for nested fields (email, number, tel, url, etc.)<\/li>\n<li>Feature: Type-specific sanitization for nested fields in arrays<\/li>\n<li>Security: Arrays are now restricted to <code>array_of_objects<\/code> type only - prevents validation bypass<\/li>\n<li>Security: Simple field types (text, email, number, etc.) now reject arrays for better security<\/li>\n<li>Improved: Each array item is validated according to nested field configuration<\/li>\n<li>Improved: Better error messages for array validation with item index<\/li>\n<li>Technical: Added <code>validate_array_of_objects()<\/code> method for comprehensive array validation<\/li>\n<li>Technical: Added <code>sanitize_array_of_objects()<\/code> method for type-based sanitization<\/li>\n<li>Technical: Updated field configuration sanitization to support nested <code>fields<\/code> property<\/li>\n<li>Breaking: Forms using <code>text<\/code> type for arrays must be updated to <code>array_of_objects<\/code> type<\/li>\n<li>Full backward compatibility for existing form submissions (data format unchanged)<\/li>\n<\/ul>\n\n<h4>1.2.0<\/h4>\n\n<ul>\n<li>Feature: Added support for nested arrays and objects in REST API submissions<\/li>\n<li>Feature: Recursive sanitization for complex data structures (arrays of objects)<\/li>\n<li>Improved: Array of objects now displays as formatted tables in email notifications<\/li>\n<li>Improved: Array of objects formatted in Telegram messages with proper structure<\/li>\n<li>Improved: Array of objects saved as JSON in Google Sheets for better data handling<\/li>\n<li>Improved: Admin submission detail view now shows arrays of objects as HTML tables<\/li>\n<li>Improved: Admin submissions list shows item count for arrays of objects instead of \"Array\"<\/li>\n<li>Improved: Better handling of nested data structures in all delivery methods<\/li>\n<li>Technical: Added recursive value sanitization method (sanitize_value_recursive)<\/li>\n<li>Technical: Added recursive data size calculation for nested structures<\/li>\n<li>Technical: Added helper methods for detecting and rendering arrays of objects<\/li>\n<li>Full backward compatibility - all existing forms continue to work<\/li>\n<\/ul>\n\n<h4>1.1.1<\/h4>\n\n<ul>\n<li>Security: Added esc_url() escaping for all URL outputs in custom-form-examples.php template<\/li>\n<li>Fixed: WordPress Coding Standards compliance for template output functions<\/li>\n<\/ul>\n\n<h4>1.1.0<\/h4>\n\n<ul>\n<li>Added support for custom forms in PHP templates<\/li>\n<li>New template functions for easy integration: mksddn_fh_get_form_action(), mksddn_fh_form_fields(), mksddn_fh_get_form_config()<\/li>\n<li>New helper functions: mksddn_fh_get_rest_endpoint(), mksddn_fh_form_has_files(), mksddn_fh_enqueue_form_script()<\/li>\n<li>Added \"Accept any fields from frontend\" option - skip field validation for custom forms (Advanced Settings)<\/li>\n<li>New filter: mksddn_fh_allowed_fields - dynamically control allowed field names (supports wildcard '*')<\/li>\n<li>Added comprehensive examples in templates\/custom-form-examples.php<\/li>\n<li>Extended Utilities class with methods for template integration<\/li>\n<li>Fixed: Telegram message formatting switched from Markdown to HTML to prevent parsing errors<\/li>\n<li>Improved: Better handling of special characters in Cyrillic text (dashes, dots, brackets)<\/li>\n<li>Added: escape_html_for_telegram() method for proper HTML escaping<\/li>\n<li>Full backward compatibility - all existing shortcodes continue to work<\/li>\n<\/ul>\n\n<h4>1.0.5<\/h4>\n\n<ul>\n<li>REST: Fixed warnings caused by implicit array-to-string conversions (multipart submissions)<\/li>\n<li>Validation: Hardened guards for email\/url\/tel\/number; refined date\/time\/datetime-local handling<\/li>\n<li>REST: Correct total payload size calculation for array values<\/li>\n<\/ul>\n\n<h4>1.0.4<\/h4>\n\n<ul>\n<li>Fields: Added select (with multiple) and radio support in shortcode<\/li>\n<li>Config: <code>options<\/code> and <code>multiple<\/code> support in fields JSON<\/li>\n<li>Validation: Ensured submitted values match configured options<\/li>\n<li>Emails\/Admin: Proper rendering of array values (comma-separated)<\/li>\n<li>Docs: README and readme.txt updated with field types and examples<\/li>\n<\/ul>\n\n<h4>1.0.3<\/h4>\n\n<ul>\n<li>REST: Removed legacy <code>\/wp\/v2\/forms<\/code> route; unified custom namespace<\/li>\n<li>REST: Added GET endpoints in custom namespace:\n\n<ul>\n<li><code>GET \/wp-json\/mksddn-forms-handler\/v1\/forms<\/code><\/li>\n<li><code>GET \/wp-json\/mksddn-forms-handler\/v1\/forms\/{slug}<\/code><\/li>\n<\/ul><\/li>\n<li>Docs: Updated README and user guides (FAQ, Integrations)<\/li>\n<li>Meta: Bumped plugin version to 1.0.3<\/li>\n<\/ul>\n\n<h4>1.0.2<\/h4>\n\n<ul>\n<li>Enqueued scripts\/styles properly, removed inline JS\/CSS<\/li>\n<li>Prefixed options\/transients and custom post types<\/li>\n<li>REST: custom namespace only; added honeypot and rate limiting<\/li>\n<li>Security: strict sanitization\/validation for fields config JSON<\/li>\n<li>Compliance updates per Plugin Review feedback<\/li>\n<li>Prefixes, REST adjustments, enqueue fixes, security hardening<\/li>\n<li>Readme External services section added<\/li>\n<\/ul>\n\n<h4>1.0.1<\/h4>\n\n<ul>\n<li>Added <code>uninstall.php<\/code> to clean plugin options and transients (keeps CPT data)<\/li>\n<li>Version metadata adjusted<\/li>\n<\/ul>\n\n<h4>1.0.0<\/h4>\n\n<ul>\n<li>Initial release<\/li>\n<li>Multiple delivery methods (email, Telegram, Google Sheets)<\/li>\n<li>REST API support<\/li>\n<li>Custom post types for forms and submissions<\/li>\n<li>Security measures and validation<\/li>\n<li>Clean, maintainable code structure<\/li>\n<\/ul>","raw_excerpt":"Advanced form processing system with REST API support, Telegram notifications, and Google Sheets integration.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/ta.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/248360","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ta.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/ta.wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/ta.wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=248360"}],"author":[{"embeddable":true,"href":"https:\/\/ta.wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/mksddn"}],"wp:attachment":[{"href":"https:\/\/ta.wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=248360"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/ta.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=248360"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/ta.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=248360"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/ta.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=248360"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/ta.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=248360"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/ta.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=248360"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}