From 4b0f1d52d8c429219827e7f417959f3993f5ff9c Mon Sep 17 00:00:00 2001 From: Sherv Date: Tue, 20 Jan 2026 21:21:46 +0300 Subject: [PATCH 01/90] Add Windsurf rules for Formidable Forms development standards --- .windsurf/rules/formidable-core.md | 138 +++++++++++++++++++++++++ .windsurf/rules/php-wordpress.md | 144 +++++++++++++++++++++++++++ .windsurf/rules/web-search-policy.md | 89 +++++++++++++++++ 3 files changed, 371 insertions(+) create mode 100644 .windsurf/rules/formidable-core.md create mode 100644 .windsurf/rules/php-wordpress.md create mode 100644 .windsurf/rules/web-search-policy.md diff --git a/.windsurf/rules/formidable-core.md b/.windsurf/rules/formidable-core.md new file mode 100644 index 0000000000..00ef35a3ca --- /dev/null +++ b/.windsurf/rules/formidable-core.md @@ -0,0 +1,138 @@ +--- +trigger: always_on +description: Core Formidable Forms development rules. Enforces WordPress VIP standards, Formidable coding patterns, and enterprise best practices. +--- + +# Formidable Forms Development Rules + +You are an AI assistant specialized in Formidable Forms plugin development. This is an enterprise-level WordPress plugin that must follow strict coding standards. + +## 0. Critical Principles + +- **NEVER guess** - Always search and verify before making changes. +- **Minimal scope** - Fix at the most specific location, closest to the problem. +- **Backward compatibility** - Maintain 100% backward compatibility with existing callers. +- **Pro plugin awareness** - Code must work when Pro is active AND when Pro is inactive. + +## 1. Mandatory Research Before Changes + +Before ANY code change that involves WordPress functions or patterns: + +1. **Search the codebase** first to understand existing patterns. +2. **Search WordPress developer docs** using `@web` for: + - Function parameters and return types. + - Deprecated functions and alternatives. + - Security best practices. + - Performance implications. +3. **Search WordPress VIP docs** for performance-critical code. +4. **Verify** the approach aligns with existing Formidable patterns. + + + +- Database queries: Search WordPress VIP docs for query optimization. +- Sanitization: Search developer.wordpress.org for correct sanitize\_\* function. +- Escaping: Search for correct esc\_\* function for the output context. +- Hooks: Search codebase for existing hook patterns before adding new ones. +- Caching: Search VIP docs for caching best practices. + + +## 2. Code Analysis Phase + +Before proposing solutions: + +1. **Read and understand** the complete issue. +2. **Identify ALL affected locations** in the codebase. +3. **Map dependencies** - what calls this code, what does this code call. +4. **Check Pro plugin requirement** - does code need Pro or must work without it. + +## 3. Solution Selection + +- Propose 2-3 solutions with trade-offs clearly stated. +- Choose the solution with minimal scope and lowest risk. +- Fix at the most specific location. +- Prefer adding safety checks over refactoring existing code. + +## 4. PHP Coding Standards + + + +- Use `elseif` not `else if`. +- Use strict comparisons (`===`, `!==`) always. +- Use `in_array()` with third parameter `true`. +- Functions max 100 lines, files max 1000 lines. +- Cyclomatic complexity max 10, cognitive complexity warning at 10. +- Line length max 180 characters. +- Tabs for indentation, not spaces. +- Opening brace on same line for functions and control structures. +- Space after control structure keywords (`if`, `for`, `foreach`, etc.). + + +## 5. WordPress VIP Standards + + + +- NEVER use `$wpdb->query()` for SELECT - use `$wpdb->get_results()`, `$wpdb->get_row()`, `$wpdb->get_var()`. +- ALWAYS use `$wpdb->prepare()` for queries with variables. +- NEVER use `extract()`. +- NEVER use `eval()`. +- NEVER use `create_function()`. +- Avoid `file_get_contents()` for remote URLs - use `wp_remote_get()`. +- Avoid direct file operations - use WP_Filesystem. +- Limit query results with LIMIT clause. +- Use transients or object cache for expensive operations. +- Escape late, sanitize early. + + +## 6. Formidable-Specific Patterns + + + +- Class naming: `FrmClassName` for Lite, `FrmProClassName` for Pro. +- Method naming: `snake_case` for public, `camelCase` legacy methods preserved. +- Hook naming: `frm_hook_name` for Lite, `frm_pro_hook_name` for Pro. +- Text domains: `formidable` for Lite, `formidable-pro` for Pro add-ons. +- Factory pattern: Use `FrmFieldFactory::get_field_type()` for field instances. +- Use `FrmAppHelper` methods for common operations. +- Use `FrmDb` class for database operations. + + +## 7. Security Requirements + + +- ALL user input must be sanitized using appropriate WordPress functions. +- ALL output must be escaped using appropriate `esc_*` functions. +- ALL AJAX handlers must verify nonce with `wp_verify_nonce()`. +- ALL AJAX handlers must check capabilities with `current_user_can()`. +- ALL database queries must use `$wpdb->prepare()`. +- NEVER trust `$_GET`, `$_POST`, `$_REQUEST` directly. + + +## 8. PHPDoc Standards + + +- Use `{@inheritDoc}` for methods/properties inherited from parent class. +- Include `@since x.x` only for new methods in existing classes. +- Class-level `@since` for new classes covers all members. +- All comments must end with a period. +- Keep descriptions concise and clear. + + +## 9. Testing Requirements + + +- Extend `FrmUnitTest` for Lite tests, `FrmProUnitTest` for Pro tests. +- Use `$this->factory->form->create_and_get()` for test data. +- Use `$this->run_private_method()` to test protected methods. +- All assertion messages must end with a period. +- Test scenarios: Pro active, Pro inactive, empty data, missing keys. + + +## 10. Change Verification + +Before completing any change, verify: + +- [ ] Does this change break any existing functionality? +- [ ] Does this work when Pro plugin is inactive? +- [ ] Are there PHP warnings/errors in any scenario? +- [ ] Is the change backward compatible? +- [ ] Have you searched docs for best practices? diff --git a/.windsurf/rules/php-wordpress.md b/.windsurf/rules/php-wordpress.md new file mode 100644 index 0000000000..32d430b79f --- /dev/null +++ b/.windsurf/rules/php-wordpress.md @@ -0,0 +1,144 @@ +--- +trigger: glob +globs: ["*.php"] +description: PHP and WordPress coding standards for Formidable Forms development. +--- + +# PHP & WordPress Standards + +## Database Operations + +### Query Patterns + +```php +// CORRECT - Use prepared statements +$results = $wpdb->get_results( + $wpdb->prepare( + "SELECT * FROM {$wpdb->prefix}frm_items WHERE form_id = %d", + $form_id + ) +); + +// WRONG - Never do this +$results = $wpdb->query( "SELECT * FROM {$wpdb->prefix}frm_items WHERE form_id = " . $form_id ); +``` + +### Formidable Database Patterns + +```php +// Use FrmDb methods when available +FrmDb::get_col( $table, $where, $field ); +FrmDb::get_var( $table, $where, $field ); +FrmDb::get_results( $table, $where, $fields, $args ); +``` + +## Sanitization Functions + +Use the correct function for each data type: + +| Data Type | Function | +| --------- | ------------------------------- | +| Text | `sanitize_text_field()` | +| Textarea | `sanitize_textarea_field()` | +| Email | `sanitize_email()` | +| URL | `esc_url_raw()` | +| Integer | `absint()` or `intval()` | +| Filename | `sanitize_file_name()` | +| HTML | `wp_kses()` or `wp_kses_post()` | +| Key | `sanitize_key()` | + +## Escaping Functions + +Escape based on output context: + +| Context | Function | +| ---------- | ------------------ | +| HTML | `esc_html()` | +| Attribute | `esc_attr()` | +| URL | `esc_url()` | +| JavaScript | `esc_js()` | +| Textarea | `esc_textarea()` | +| SQL | `$wpdb->prepare()` | + +## Formidable Helper Methods + +```php +// Use FrmAppHelper for common operations +FrmAppHelper::get_param( $param, $default, $src, $sanitize ); +FrmAppHelper::get_post_param( $param, $default, $sanitize ); +FrmAppHelper::simple_get( $param, $sanitize, $default ); +FrmAppHelper::kses( $value, $allowed ); +FrmAppHelper::sanitize_with_html( $value ); +``` + +## Hooks and Filters + +### Naming Convention + +```php +// Lite hooks +do_action( 'frm_action_name', $args ); +apply_filters( 'frm_filter_name', $value, $args ); + +// Pro hooks +do_action( 'frm_pro_action_name', $args ); +apply_filters( 'frm_pro_filter_name', $value, $args ); +``` + +### Hook Documentation + +```php +/** + * Fires after form submission. + * + * @since 2.0 + * + * @param int $entry_id Entry ID. + * @param int $form_id Form ID. + * @param array $values Submitted values. + */ +do_action( 'frm_after_create_entry', $entry_id, $form_id, $values ); +``` + +## AJAX Handlers + +```php +// Always verify nonce and capabilities +public static function ajax_handler() { + check_ajax_referer( 'frm_ajax', 'nonce' ); + + if ( ! current_user_can( 'frm_edit_forms' ) ) { + wp_die( -1 ); + } + + // Handler code... + + wp_send_json_success( $data ); +} +``` + +## Performance Considerations + +- Cache expensive queries with transients or object cache. +- Use LIMIT in queries - never fetch unlimited results. +- Avoid queries in loops - batch operations when possible. +- Use `wp_cache_get()` / `wp_cache_set()` for repeated lookups. +- Index database columns used in WHERE clauses. + +## Internationalization + +```php +// Text domain for Lite +__( 'Text', 'formidable' ); +esc_html__( 'Text', 'formidable' ); + +// Text domain for Pro +__( 'Text', 'formidable-pro' ); + +// With placeholders +sprintf( + /* translators: %s: Field name */ + __( 'The %s field is required.', 'formidable' ), + $field_name +); +``` diff --git a/.windsurf/rules/web-search-policy.md b/.windsurf/rules/web-search-policy.md new file mode 100644 index 0000000000..86da9dd7d9 --- /dev/null +++ b/.windsurf/rules/web-search-policy.md @@ -0,0 +1,89 @@ +--- +trigger: always_on +description: Mandatory web search policy for WordPress and Formidable development. +--- + +# Web Search Policy + +## Mandatory Searches + +You MUST search the web in these scenarios: + +### 1. WordPress Functions + +Before using ANY WordPress function you're not 100% certain about: + +``` +@web WordPress [function_name] parameters return type +``` + +### 2. Database Operations + +Before writing any database query: + +``` +@web WordPress VIP database query best practices +@web WordPress $wpdb prepare examples +``` + +### 3. Security Functions + +Before using sanitization or escaping: + +``` +@web WordPress sanitize_text_field vs sanitize_textarea_field +@web WordPress esc_html vs esc_attr when to use +``` + +### 4. Deprecated Functions + +When you see a function that might be deprecated: + +``` +@web WordPress [function_name] deprecated alternative +``` + +### 5. Hooks and Actions + +Before adding new hooks: + +``` +@web WordPress hook naming conventions best practices +``` + +### 6. Performance Critical Code + +For loops, queries, or frequently executed code: + +``` +@web WordPress VIP performance optimization [topic] +``` + +## Search Sources Priority + +1. **developer.wordpress.org** - Official WordPress documentation. +2. **docs.wpvip.com** - WordPress VIP best practices. +3. **developer.wordpress.org/plugins** - Plugin development handbook. +4. **make.wordpress.org/core** - Core development decisions. + +## Quick Reference URLs + +When searching, prioritize these docs: + +- PHP Standards: `developer.wordpress.org/coding-standards/wordpress-coding-standards/php/` +- Security: `developer.wordpress.org/plugins/security/` +- Database: `developer.wordpress.org/plugins/database/` +- VIP Code Standards: `docs.wpvip.com/technical-references/code-review/` +- VIP Performance: `docs.wpvip.com/technical-references/caching/` + +## Search Before Action + +ALWAYS search BEFORE: + +- Adding a new WordPress function call. +- Writing a database query. +- Implementing security measures. +- Making performance optimizations. +- Adding new hooks or filters. + +The cost of searching is minimal compared to the cost of getting it wrong. From b1b9da932845215fa4ccc830b3379ba2567f6293 Mon Sep 17 00:00:00 2001 From: Sherv Date: Tue, 20 Jan 2026 21:21:52 +0300 Subject: [PATCH 02/90] Add Windsurf skills for field type creation and code review --- .windsurf/skills/add-field-type/SKILL.md | 128 +++++++++++++++++++++++ .windsurf/skills/code-review/SKILL.md | 114 ++++++++++++++++++++ 2 files changed, 242 insertions(+) create mode 100644 .windsurf/skills/add-field-type/SKILL.md create mode 100644 .windsurf/skills/code-review/SKILL.md diff --git a/.windsurf/skills/add-field-type/SKILL.md b/.windsurf/skills/add-field-type/SKILL.md new file mode 100644 index 0000000000..c52ee22855 --- /dev/null +++ b/.windsurf/skills/add-field-type/SKILL.md @@ -0,0 +1,128 @@ +--- +name: add-field-type +description: Guides creation of a new Formidable field type following all patterns and standards +--- + +# Add New Field Type + +Follow these steps to create a new field type for Formidable Forms. + +## Step 1: Determine Location + +- **Lite field** → `formidable-master/classes/models/fields/` +- **Pro field** → `formidable-pro-master/classes/models/fields/` + +## Step 2: Create Field Class + +Create new file: `FrmFieldYourType.php` (Lite) or `FrmProFieldYourType.php` (Pro) + +```php + array( + 'name' => __( 'Your Field', 'formidable-pro' ), + 'icon' => 'frmfont frm_your_icon', +), +``` + +## Step 5: Create Form Builder View + +Create view file: `classes/views/frmpro-fields/back-end/field-yourtype.php` + +## Step 6: Add Tests + +Create test file: `tests/fields/test_FrmProFieldYourType.php` + +```php +factory->field->create_and_get( + array( + 'type' => 'your_type', + 'form_id' => $this->factory->form->create(), + ) + ); + $this->assertEquals( 'your_type', $field->type, 'Field type should be your_type.' ); + } +} +``` + +## Checklist + +- [ ] Field class extends `FrmFieldType` +- [ ] Type registered in factory +- [ ] Added to field selection array +- [ ] Form builder view created +- [ ] Front-end rendering works +- [ ] Value saving works +- [ ] Validation works +- [ ] Tests written +- [ ] PHPDoc complete diff --git a/.windsurf/skills/code-review/SKILL.md b/.windsurf/skills/code-review/SKILL.md new file mode 100644 index 0000000000..bb20a0d956 --- /dev/null +++ b/.windsurf/skills/code-review/SKILL.md @@ -0,0 +1,114 @@ +--- +name: code-review +description: Performs thorough code review following Formidable and WordPress VIP standards +--- + +# Code Review Guidelines + +Use this skill to review code changes for Formidable Forms. + +## Review Process + +### 1. Security Review + +Check for these security issues: + +- [ ] **SQL Injection** - All queries use `$wpdb->prepare()` +- [ ] **XSS** - All output escaped with appropriate `esc_*` function +- [ ] **CSRF** - AJAX handlers verify nonce +- [ ] **Authorization** - Capability checks in place +- [ ] **Input Sanitization** - All user input sanitized + +### 2. WordPress VIP Compliance + +- [ ] No direct database queries without prepare() +- [ ] No `extract()`, `eval()`, or `create_function()` +- [ ] Query results limited with LIMIT +- [ ] No `file_get_contents()` for remote URLs +- [ ] Expensive operations cached + +### 3. Formidable Standards + +- [ ] Class naming follows `Frm` or `FrmPro` prefix +- [ ] Hook naming follows `frm_` or `frm_pro_` prefix +- [ ] Correct text domain used +- [ ] Factory pattern used for field types +- [ ] Helper methods used where available + +### 4. Code Quality + +- [ ] Functions under 100 lines +- [ ] Cyclomatic complexity under 10 +- [ ] Line length under 180 characters +- [ ] No code duplication +- [ ] Early returns used +- [ ] Clear variable naming + +### 5. Documentation + +- [ ] PHPDoc for all public methods +- [ ] `@since` tags for new methods +- [ ] `{@inheritDoc}` for overridden methods +- [ ] Comments end with periods +- [ ] Translators comments for sprintf + +### 6. Compatibility + +- [ ] Works when Pro inactive +- [ ] Backward compatible +- [ ] No PHP warnings/notices +- [ ] Handles empty/null values + +## Common Issues to Flag + +```php +// BAD: Direct query +$wpdb->query( "DELETE FROM ... WHERE id = $id" ); + +// GOOD: Prepared query +$wpdb->query( $wpdb->prepare( "DELETE FROM ... WHERE id = %d", $id ) ); + +// BAD: Unescaped output +echo $user_input; + +// GOOD: Escaped output +echo esc_html( $user_input ); + +// BAD: No nonce check +public static function ajax_handler() { + // Handler code... +} + +// GOOD: Nonce verified +public static function ajax_handler() { + check_ajax_referer( 'frm_ajax', 'nonce' ); + // Handler code... +} +``` + +## Review Output Format + +```markdown +## Code Review: [File/PR Name] + +### Security + +- ✅ SQL injection protection verified +- ⚠️ Missing escaping at line X + +### VIP Compliance + +- ✅ All queries prepared +- ❌ Missing LIMIT on query at line Y + +### Standards + +- ✅ Naming conventions followed +- ⚠️ Function exceeds 100 lines + +### Recommendations + +1. Add escaping at line X +2. Add LIMIT to query at line Y +3. Consider extracting method for lines A-B +``` From 5a53ec23de3add0b4bebf66ecbbc93be525ae87e Mon Sep 17 00:00:00 2001 From: Sherv Date: Tue, 20 Jan 2026 21:22:00 +0300 Subject: [PATCH 03/90] Add Windsurf workflows for bug fixing, testing, and security auditing --- .windsurf/workflows/fix-bug.md | 78 ++++++++++++++++ .windsurf/workflows/phpcs-check.md | 48 ++++++++++ .windsurf/workflows/run-tests.md | 52 +++++++++++ .windsurf/workflows/security-audit.md | 124 ++++++++++++++++++++++++++ 4 files changed, 302 insertions(+) create mode 100644 .windsurf/workflows/fix-bug.md create mode 100644 .windsurf/workflows/phpcs-check.md create mode 100644 .windsurf/workflows/run-tests.md create mode 100644 .windsurf/workflows/security-audit.md diff --git a/.windsurf/workflows/fix-bug.md b/.windsurf/workflows/fix-bug.md new file mode 100644 index 0000000000..cea8da5072 --- /dev/null +++ b/.windsurf/workflows/fix-bug.md @@ -0,0 +1,78 @@ +--- +name: fix-bug +description: Structured workflow for fixing bugs in Formidable Forms following enterprise practices +--- + +# Fix Bug Workflow + +## Phase 1: Analysis + +1. **Understand the bug report:** + - What is the expected behavior? + - What is the actual behavior? + - Steps to reproduce + +2. **Search the codebase:** + - Find the relevant code using `grep_search` or `code_search` + - Identify ALL affected locations + - Map dependencies (what calls this, what does this call) + +3. **Check Pro compatibility:** + - Does this bug affect Lite, Pro, or both? + - Will the fix work when Pro is inactive? + +## Phase 2: Research + +4. **Search WordPress docs** (if using WP functions): + + ``` + @web WordPress [function_name] parameters best practices + ``` + +5. **Search VIP docs** (if performance/security related): + + ``` + @web WordPress VIP [topic] best practices + ``` + +6. **Check existing patterns:** + - How is similar functionality handled elsewhere in the codebase? + - Are there helper methods that should be used? + +## Phase 3: Solution Design + +7. **Propose 2-3 solutions** with trade-offs: + - Solution A: [Description] - Pros/Cons + - Solution B: [Description] - Pros/Cons + - Recommended: [Choice with reasoning] + +8. **Verify minimal scope:** + - Fix at the most specific location + - Avoid unnecessary refactoring + - Preserve backward compatibility + +## Phase 4: Implementation + +9. **Make the fix:** + - Apply the smallest change that solves the problem + - Add defensive checks where needed + - Follow coding standards + +10. **Add/update tests:** + - Write test that reproduces the bug + - Verify test fails before fix + - Verify test passes after fix + +## Phase 5: Verification + +11. **Run verification checks:** + - [ ] PHPCS passes + - [ ] PHPUnit tests pass + - [ ] No PHP warnings/notices + - [ ] Works when Pro is inactive + - [ ] Backward compatible + +12. **Report completion:** + - Summary of the fix + - Files changed + - Any remaining concerns diff --git a/.windsurf/workflows/phpcs-check.md b/.windsurf/workflows/phpcs-check.md new file mode 100644 index 0000000000..dd35f66f63 --- /dev/null +++ b/.windsurf/workflows/phpcs-check.md @@ -0,0 +1,48 @@ +--- +name: phpcs-check +description: Run PHP CodeSniffer to check code against Formidable and WordPress VIP standards +--- + +# PHPCS Check + +## Steps + +1. Determine the target file or directory: + - If a specific file was mentioned, check that file + - If a directory was mentioned, check that directory + - Otherwise, check recently modified files + +2. Run PHPCS on Lite plugin: + + ```bash + cd /Users/sherv/Local\ Sites/formidable/app/public/wp-content/plugins/formidable-master + vendor/bin/phpcs --standard=phpcs.xml [target] + ``` + +3. Run PHPCS on Pro plugin: + + ```bash + cd /Users/sherv/Local\ Sites/formidable/app/public/wp-content/plugins/formidable-pro-master + vendor/bin/phpcs --standard=phpcs.xml [target] + ``` + +4. For auto-fixing issues: + + ```bash + vendor/bin/phpcbf --standard=phpcs.xml [target] + ``` + +5. Report findings: + - List errors by severity + - Explain what each error means + - Suggest fixes for common issues + +## Common PHPCS Errors + +| Error | Fix | +| -------------------------- | ------------------------------------------------- | +| Missing nonce verification | Add `check_ajax_referer()` or `wp_verify_nonce()` | +| Unescaped output | Add appropriate `esc_*` function | +| Unsanitized input | Add appropriate `sanitize_*` function | +| Unprepared SQL query | Use `$wpdb->prepare()` | +| Strict comparison | Use `===` instead of `==` | diff --git a/.windsurf/workflows/run-tests.md b/.windsurf/workflows/run-tests.md new file mode 100644 index 0000000000..e300095591 --- /dev/null +++ b/.windsurf/workflows/run-tests.md @@ -0,0 +1,52 @@ +--- +name: run-tests +description: Run PHPUnit tests for Formidable Forms plugins +--- + +# Run Formidable Tests + +## Steps + +1. Determine which plugin to test based on the current context: + - If working in `formidable-master` → Run Lite tests + - If working in `formidable-pro-master` → Run Pro tests + - If working in an add-on → Run add-on tests + +2. Check if PHPUnit is available: + + ```bash + vendor/bin/phpunit --version + ``` + +3. Run the appropriate tests: + + **For Lite plugin:** + + ```bash + cd /Users/sherv/Local\ Sites/formidable/app/public/wp-content/plugins/formidable-master + vendor/bin/phpunit + ``` + + **For Pro plugin:** + + ```bash + cd /Users/sherv/Local\ Sites/formidable/app/public/wp-content/plugins/formidable-pro-master + vendor/bin/phpunit + ``` + +4. If a specific test file was modified, run only that test: + + ```bash + vendor/bin/phpunit tests/phpunit/path/to/test_file.php + ``` + +5. If a specific test method needs to run: + + ```bash + vendor/bin/phpunit --filter test_method_name + ``` + +6. Report the results: + - Number of tests passed/failed + - Any error messages + - Suggestions for fixing failures diff --git a/.windsurf/workflows/security-audit.md b/.windsurf/workflows/security-audit.md new file mode 100644 index 0000000000..8ee1173f77 --- /dev/null +++ b/.windsurf/workflows/security-audit.md @@ -0,0 +1,124 @@ +--- +name: security-audit +description: Perform security audit on Formidable Forms code following WordPress VIP standards +--- + +# Security Audit Workflow + +## Step 1: Identify Scope + +Determine what to audit: + +- Specific file or function +- Recent changes (PR or commit) +- Entire feature area + +## Step 2: Input Handling Audit + +Check ALL user input sources: + +```php +// Sources to check: +$_GET, $_POST, $_REQUEST, $_SERVER, $_COOKIE +// Formidable helpers: +FrmAppHelper::get_param() +FrmAppHelper::get_post_param() +FrmAppHelper::simple_get() +``` + +For each input, verify: + +- [ ] Sanitized with appropriate function +- [ ] Type validated where needed +- [ ] Default values are safe + +## Step 3: Output Escaping Audit + +Check ALL output locations: + +| Context | Required Function | +| -------------- | ------------------ | +| HTML text | `esc_html()` | +| HTML attribute | `esc_attr()` | +| URL | `esc_url()` | +| JavaScript | `esc_js()` | +| Textarea | `esc_textarea()` | +| JSON | `wp_json_encode()` | + +Verify: + +- [ ] Every echo/print is escaped +- [ ] Correct function for context +- [ ] No raw user data in output + +## Step 4: SQL Injection Audit + +Check ALL database queries: + +```php +// MUST use prepare() for: +$wpdb->query() +$wpdb->get_results() +$wpdb->get_row() +$wpdb->get_var() +$wpdb->get_col() +``` + +Verify: + +- [ ] All queries use `$wpdb->prepare()` +- [ ] Correct placeholders used (%d, %s, %f) +- [ ] Table names properly prefixed +- [ ] LIMIT clauses present + +## Step 5: AJAX Security Audit + +For each AJAX handler: + +```php +// Required checks: +check_ajax_referer( 'frm_ajax', 'nonce' ); +if ( ! current_user_can( 'capability' ) ) { + wp_die( -1 ); +} +``` + +Verify: + +- [ ] Nonce verification present +- [ ] Capability check present +- [ ] Proper error response on failure + +## Step 6: File Operation Audit + +Check file operations: + +- [ ] No direct file includes with user input +- [ ] File uploads validated (type, size, content) +- [ ] WP_Filesystem used where appropriate +- [ ] No arbitrary file reads/writes + +## Step 7: Report Findings + +Generate security report: + +```markdown +## Security Audit Report: [Target] + +### Critical Issues (Fix Immediately) + +- [ ] Issue description with file:line + +### High Priority Issues + +- [ ] Issue description with file:line + +### Medium Priority Issues + +- [ ] Issue description with file:line + +### Recommendations + +1. [Specific recommendation] +2. [Specific recommendation] +``` From 760c4b14c44470d714a9680333cf6d986a4905bd Mon Sep 17 00:00:00 2001 From: Sherv Date: Tue, 20 Jan 2026 21:22:10 +0300 Subject: [PATCH 04/90] Add comprehensive Windsurf configuration for Formidable Forms development --- .windsurf/AGENTS.md | 72 ++++++++++++++++++++++++ .windsurf/README.md | 133 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 205 insertions(+) create mode 100644 .windsurf/AGENTS.md create mode 100644 .windsurf/README.md diff --git a/.windsurf/AGENTS.md b/.windsurf/AGENTS.md new file mode 100644 index 0000000000..3f52abf83e --- /dev/null +++ b/.windsurf/AGENTS.md @@ -0,0 +1,72 @@ +# Formidable Forms Development Guidelines + +This is an enterprise WordPress plugin ecosystem. All changes must follow strict quality standards. + +## Project Structure + +- **formidable-master** - Lite plugin (free version) +- **formidable-pro-master** - Pro plugin (premium features) +- **formidable-views** - Views add-on +- **formidable-\*-master** - Other add-ons (ai, dates, geo, signature, surveys, etc.) + +## Architecture Overview + +``` +classes/ +├── controllers/ # Handle requests, routing, admin pages +├── factories/ # Object creation (FrmFieldFactory) +├── helpers/ # Utility functions (FrmAppHelper, FrmFieldsHelper) +├── models/ # Data models (FrmForm, FrmField, FrmEntry) +│ └── fields/ # Field type classes (FrmFieldType, FrmProFieldVirtual) +├── views/ # PHP templates for rendering +└── widgets/ # WordPress widgets +``` + +## Key Classes + +| Class | Purpose | +| ----------------- | ---------------------------------------- | +| `FrmAppHelper` | Core utility methods, sanitization, URLs | +| `FrmFieldFactory` | Creates field type instances | +| `FrmFieldType` | Base class for all field types | +| `FrmDb` | Database operations wrapper | +| `FrmForm` | Form CRUD operations | +| `FrmField` | Field CRUD operations | +| `FrmEntry` | Entry CRUD operations | + +## Development Rules + +1. **Search before coding** - Always search codebase and WordPress docs first. +2. **Minimal changes** - Make smallest change that solves the problem. +3. **Pro compatibility** - Code must work with and without Pro active. +4. **Test coverage** - Add tests for new functionality. +5. **PHPDoc** - Document all public methods with proper tags. + +## Testing + +```bash +# Run all Lite tests +cd formidable-master && vendor/bin/phpunit + +# Run all Pro tests +cd formidable-pro-master && vendor/bin/phpunit + +# Run specific test file +vendor/bin/phpunit tests/phpunit/fields/test_FrmFieldType.php + +# Run specific test method +vendor/bin/phpunit --filter test_method_name +``` + +## Code Review Checklist + +- [ ] Follows WordPress Coding Standards +- [ ] Follows WordPress VIP standards +- [ ] All user input sanitized +- [ ] All output escaped +- [ ] Database queries use prepare() +- [ ] AJAX handlers verify nonce and capabilities +- [ ] Works when Pro is inactive +- [ ] No PHP warnings or notices +- [ ] PHPDoc comments complete +- [ ] Tests added/updated diff --git a/.windsurf/README.md b/.windsurf/README.md new file mode 100644 index 0000000000..b29a4aa117 --- /dev/null +++ b/.windsurf/README.md @@ -0,0 +1,133 @@ +# Windsurf Configuration for Formidable Forms + +This directory contains comprehensive Windsurf configuration files for Formidable Forms development. + +## Installation + +Copy the contents to your Formidable plugins directory: + +```bash +# Copy to formidable-master (Lite) +cp -r rules/ /path/to/formidable-master/.windsurf/rules/ +cp -r skills/ /path/to/formidable-master/.windsurf/skills/ +cp -r workflows/ /path/to/formidable-master/.windsurf/workflows/ +cp AGENTS.md /path/to/formidable-master/AGENTS.md + +# Copy to formidable-pro-master (Pro) +cp -r rules/ /path/to/formidable-pro-master/.windsurf/rules/ +cp -r skills/ /path/to/formidable-pro-master/.windsurf/skills/ +cp -r workflows/ /path/to/formidable-pro-master/.windsurf/workflows/ +cp AGENTS.md /path/to/formidable-pro-master/AGENTS.md +``` + +## Directory Structure + +``` +windsurf-formidable/ +├── README.md # This file +├── AGENTS.md # Root-level project guidelines +├── rules/ +│ ├── formidable-core.md # Core development rules (always_on) +│ ├── php-wordpress.md # PHP/WordPress standards (*.php glob) +│ └── web-search-policy.md # Mandatory web search rules (always_on) +├── skills/ +│ ├── add-field-type/ +│ │ └── SKILL.md # Guide for creating new field types +│ └── code-review/ +│ └── SKILL.md # Code review checklist and guidelines +└── workflows/ + ├── run-tests.md # /run-tests workflow + ├── phpcs-check.md # /phpcs-check workflow + ├── fix-bug.md # /fix-bug workflow + └── security-audit.md # /security-audit workflow +``` + +## Usage + +### Rules (Automatic) + +Rules are automatically applied based on their trigger: + +- **formidable-core.md** - Always active, enforces core development practices +- **php-wordpress.md** - Active for all PHP files +- **web-search-policy.md** - Always active, ensures web searches for best practices + +### Skills (Manual or Automatic) + +Invoke skills by typing `@skill-name` or let Cascade auto-invoke based on context: + +- `@add-field-type` - Guides creation of new field types +- `@code-review` - Performs comprehensive code review + +### Workflows (Slash Commands) + +Invoke workflows using slash commands: + +- `/run-tests` - Run PHPUnit tests +- `/phpcs-check` - Run PHP CodeSniffer +- `/fix-bug` - Structured bug fixing workflow +- `/security-audit` - Security audit checklist + +## Key Features + +### 1. Mandatory Web Search + +The rules enforce searching WordPress docs before: + +- Using any WordPress function +- Writing database queries +- Implementing security measures +- Making performance optimizations + +### 2. WordPress VIP Compliance + +All rules align with WordPress VIP coding standards: + +- Prepared SQL queries +- Input sanitization +- Output escaping +- Performance optimization + +### 3. Formidable Patterns + +Rules enforce Formidable-specific patterns: + +- Class naming conventions (Frm*, FrmPro*) +- Hook naming conventions (frm*\*, frm_pro*\*) +- Factory pattern usage +- Helper method usage + +### 4. Pro Compatibility + +All changes must work: + +- When Pro plugin is active +- When Pro plugin is inactive +- With empty/null data +- With missing configuration + +## Lint Warnings + +The markdown lint warnings (MD033, MD040) in these files are **intentional**: + +- XML-style tags (``, ``) help Cascade parse grouped rules +- Some code blocks without language specs are for search query examples + +These do not affect Windsurf functionality. + +## Customization + +Feel free to modify these files for your specific needs: + +1. **Add new rules** - Create new `.md` files in `rules/` +2. **Add new skills** - Create new directories in `skills/` with `SKILL.md` +3. **Add new workflows** - Create new `.md` files in `workflows/` + +## Related Documentation + +- [Windsurf Memories & Rules](https://docs.windsurf.com/windsurf/cascade/memories) +- [Windsurf Skills](https://docs.windsurf.com/windsurf/cascade/skills) +- [Windsurf Workflows](https://docs.windsurf.com/windsurf/cascade/workflows) +- [Windsurf AGENTS.md](https://docs.windsurf.com/windsurf/cascade/agents-md) +- [WordPress Coding Standards](https://developer.wordpress.org/coding-standards/) +- [WordPress VIP Documentation](https://docs.wpvip.com/) From 63ae03d60dbacb21f4fa99386324a484bbe8f4ed Mon Sep 17 00:00:00 2001 From: Sherv Date: Thu, 22 Jan 2026 18:19:03 +0300 Subject: [PATCH 05/90] Update fix-bug workflow with comprehensive enterprise debugging and testing procedures --- .windsurf/workflows/fix-bug.md | 219 +++++++++++++++++++++++---------- 1 file changed, 155 insertions(+), 64 deletions(-) diff --git a/.windsurf/workflows/fix-bug.md b/.windsurf/workflows/fix-bug.md index cea8da5072..5ba73a3924 100644 --- a/.windsurf/workflows/fix-bug.md +++ b/.windsurf/workflows/fix-bug.md @@ -1,78 +1,169 @@ --- -name: fix-bug -description: Structured workflow for fixing bugs in Formidable Forms following enterprise practices +description: Enterprise bug-fixing workflow for Formidable Forms following WordPress VIP standards --- # Fix Bug Workflow -## Phase 1: Analysis +## Phase 1: Understand the Bug -1. **Understand the bug report:** - - What is the expected behavior? - - What is the actual behavior? - - Steps to reproduce +- **Clarify the issue:** + - What is the expected behavior? + - What is the actual behavior? + - What are the exact steps to reproduce? + - Does the user have sample data, screenshots, or logs? + +- **Determine scope:** + - Does this affect Lite, Pro, or both? + - Which add-ons are involved (Views, Surveys, Signature, etc.)? + - Is this a regression or a long-standing issue? + +## Phase 2: Locate the Problem + +- **Search for the root cause:** + +```text +Use code_search to find relevant code based on the bug description. +``` + +- **Map the execution flow:** + - Trace the code path from trigger to symptom + - Identify ALL files and methods involved + - Document: what calls this code? What does this code call? + +- **Add diagnostic logging** (if flow is unclear): + - Add temporary `error_log()` statements at key decision points + - Include variable values, method names, and timestamps + - Use unique prefixes like `[FRM_DEBUG_BUGNAME]` for easy grep -2. **Search the codebase:** - - Find the relevant code using `grep_search` or `code_search` - - Identify ALL affected locations - - Map dependencies (what calls this, what does this call) +- **Identify the root cause:** + - Find the EXACT line/condition where behavior diverges + - Understand WHY it fails (not just WHERE) + - Document the root cause before proceeding -3. **Check Pro compatibility:** - - Does this bug affect Lite, Pro, or both? - - Will the fix work when Pro is inactive? +## Phase 3: Analyze All Fix Locations -## Phase 2: Research +- **List ALL possible fix locations:** + - Where could this be fixed? (may be multiple places) + - Which location is closest to the root cause? + - Which location has the smallest blast radius? + +- **Search for existing patterns:** -4. **Search WordPress docs** (if using WP functions): +```text +grep_search for similar functionality in the codebase. +Look for helper methods in FrmAppHelper, FrmDb, FrmFieldsHelper. +Check how similar issues were solved elsewhere. +``` + +- **Research WordPress best practices:** + +```text +@web WordPress [relevant_function] developer documentation +@web WordPress VIP [topic] best practices +``` + +## Phase 4: Design the Solution + +- **Propose 2-3 solutions** with clear trade-offs: + +| Solution | Description | Pros | Cons | +| -------- | -------------------- | ---------- | ------- | +| A | [Fix at location X] | [Benefits] | [Risks] | +| B | [Fix at location Y] | [Benefits] | [Risks] | +| C | [Different approach] | [Benefits] | [Risks] | + +- **Select the best solution based on:** + - Minimal scope (smallest change that solves the problem) + - Closest to root cause (not downstream workaround) + - Follows existing Formidable patterns + - Maintains backward compatibility + - Works with Pro active AND inactive + +- **Get user approval** before implementing if: + - Multiple valid approaches exist + - Solution requires significant changes + - Trade-offs are not obvious + +## Phase 5: Implement the Fix + +- **Follow Formidable coding standards:** + - Use `elseif` not `else if` + - Use strict comparisons (`===`, `!==`) + - Use `in_array()` with third parameter `true` + - Tabs for indentation + - Max 180 character line length + +- **Follow WordPress VIP standards:** + - ALL user input must be sanitized + - ALL output must be escaped + - ALL database queries must use `$wpdb->prepare()` + - NEVER use `extract()`, `eval()`, or `create_function()` + +- **Make the minimal fix:** + - Change ONLY what is necessary + - Do NOT refactor unrelated code + - Do NOT change method signatures unless required + - Add defensive checks where data enters, not everywhere + +- **Add PHPDoc if adding new methods:** + +```php +/** + * Brief description ending with period. + * + * @since x.x + * + * @param type $param Description. + * @return type Description. + */ +``` + +## Phase 6: Clean Up + +- **Remove all debug code:** + - Remove ALL `error_log()` statements + - Remove ALL temporary comments + - Ensure no debug files were created + +- **Verify code style:** + +```bash +vendor/bin/phpcs --standard=phpcs.xml [changed_files] +``` + +## Phase 7: Test the Fix + +- **Create or update tests:** + +```bash +vendor/bin/phpunit tests/phpunit/[relevant_test_file].php +``` + +- **Manual verification checklist:** + - [ ] Bug is fixed in reported scenario + - [ ] No PHP warnings or notices + - [ ] Works when Pro plugin is ACTIVE + - [ ] Works when Pro plugin is INACTIVE + - [ ] Does not break existing functionality + - [ ] Backward compatible with existing data + +## Phase 8: Report Completion + +- **Summarize the fix:** + - **Root cause:** [One sentence explaining why the bug occurred] + - **Solution:** [One sentence explaining the fix] + - **Files changed:** [List of modified files] + - **Testing done:** [What was verified] + - **Remaining concerns:** [Any edge cases or follow-ups] - ``` - @web WordPress [function_name] parameters best practices - ``` - -5. **Search VIP docs** (if performance/security related): - - ``` - @web WordPress VIP [topic] best practices - ``` - -6. **Check existing patterns:** - - How is similar functionality handled elsewhere in the codebase? - - Are there helper methods that should be used? - -## Phase 3: Solution Design - -7. **Propose 2-3 solutions** with trade-offs: - - Solution A: [Description] - Pros/Cons - - Solution B: [Description] - Pros/Cons - - Recommended: [Choice with reasoning] - -8. **Verify minimal scope:** - - Fix at the most specific location - - Avoid unnecessary refactoring - - Preserve backward compatibility - -## Phase 4: Implementation - -9. **Make the fix:** - - Apply the smallest change that solves the problem - - Add defensive checks where needed - - Follow coding standards - -10. **Add/update tests:** - - Write test that reproduces the bug - - Verify test fails before fix - - Verify test passes after fix +--- -## Phase 5: Verification + -11. **Run verification checks:** - - [ ] PHPCS passes - - [ ] PHPUnit tests pass - - [ ] No PHP warnings/notices - - [ ] Works when Pro is inactive - - [ ] Backward compatible +- NEVER guess - always search and verify before making changes +- Fix at the MOST SPECIFIC location, closest to the problem +- Maintain 100% backward compatibility +- Code must work with AND without Pro active +- Remove ALL debug code before completion -12. **Report completion:** - - Summary of the fix - - Files changed - - Any remaining concerns + From 38825eb05d77b60d2e78b7529cc948dd8670adc0 Mon Sep 17 00:00:00 2001 From: Sherv Date: Fri, 23 Jan 2026 21:30:05 +0300 Subject: [PATCH 06/90] Add pattern discovery process and verification rules to Formidable core guidelines --- .windsurf/rules/formidable-core.md | 58 ++++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 11 deletions(-) diff --git a/.windsurf/rules/formidable-core.md b/.windsurf/rules/formidable-core.md index 00ef35a3ca..9851147896 100644 --- a/.windsurf/rules/formidable-core.md +++ b/.windsurf/rules/formidable-core.md @@ -13,8 +13,42 @@ You are an AI assistant specialized in Formidable Forms plugin development. This - **Minimal scope** - Fix at the most specific location, closest to the problem. - **Backward compatibility** - Maintain 100% backward compatibility with existing callers. - **Pro plugin awareness** - Code must work when Pro is active AND when Pro is inactive. +- **No custom solutions** - NEVER invent new patterns; always use existing Formidable patterns. +- **User changes are final** - If the user makes manual changes after your updates, treat those as the authoritative version and do not revert or override them. -## 1. Mandatory Research Before Changes +## 1. Pattern Discovery Process + +Before making ANY change, follow this iterative process to find the correct Formidable pattern: + + + +**Step 1: Find existing patterns** + +- Search models, controllers, helpers, and other files for similar functionality. +- Identify how Formidable already handles this type of problem. +- Look for helper methods in `FrmAppHelper`, `FrmDb`, `FrmFieldsHelper`. + +**Step 2: Study pattern usage** + +- Search ALL places that use the discovered pattern. +- Understand the best way to use it based on existing implementations. +- Note any variations and when each is appropriate. + +**Step 3: Trace parent file hierarchy** + +- Search through all parent files up to the plugin root file. +- Understand the logic and behavior at each level. +- Verify the planned change location is optimal. + +**Step 4: Iterate if needed** + +- If a better practice or location is found, return to Step 1. +- Repeat until confident you have the best pattern AND best location. +- Do NOT proceed with custom solutions if existing patterns exist. + + + +## 2. Mandatory Research Before Changes Before ANY code change that involves WordPress functions or patterns: @@ -34,9 +68,10 @@ Before ANY code change that involves WordPress functions or patterns: - Escaping: Search for correct esc\_\* function for the output context. - Hooks: Search codebase for existing hook patterns before adding new ones. - Caching: Search VIP docs for caching best practices. - -## 2. Code Analysis Phase + + +## 3. Code Analysis Phase Before proposing solutions: @@ -45,14 +80,15 @@ Before proposing solutions: 3. **Map dependencies** - what calls this code, what does this code call. 4. **Check Pro plugin requirement** - does code need Pro or must work without it. -## 3. Solution Selection +## 4. Solution Selection - Propose 2-3 solutions with trade-offs clearly stated. - Choose the solution with minimal scope and lowest risk. - Fix at the most specific location. - Prefer adding safety checks over refactoring existing code. +- **Verify changes are not overkill** - If changes affect several areas, analyze all related places to ensure the flow and behavior are correct and the fix is not excessive. -## 4. PHP Coding Standards +## 5. PHP Coding Standards @@ -67,7 +103,7 @@ Before proposing solutions: - Space after control structure keywords (`if`, `for`, `foreach`, etc.). -## 5. WordPress VIP Standards +## 6. WordPress VIP Standards @@ -83,7 +119,7 @@ Before proposing solutions: - Escape late, sanitize early. -## 6. Formidable-Specific Patterns +## 7. Formidable-Specific Patterns @@ -96,7 +132,7 @@ Before proposing solutions: - Use `FrmDb` class for database operations. -## 7. Security Requirements +## 8. Security Requirements - ALL user input must be sanitized using appropriate WordPress functions. @@ -107,7 +143,7 @@ Before proposing solutions: - NEVER trust `$_GET`, `$_POST`, `$_REQUEST` directly. -## 8. PHPDoc Standards +## 9. PHPDoc Standards - Use `{@inheritDoc}` for methods/properties inherited from parent class. @@ -117,7 +153,7 @@ Before proposing solutions: - Keep descriptions concise and clear. -## 9. Testing Requirements +## 10. Testing Requirements - Extend `FrmUnitTest` for Lite tests, `FrmProUnitTest` for Pro tests. @@ -127,7 +163,7 @@ Before proposing solutions: - Test scenarios: Pro active, Pro inactive, empty data, missing keys. -## 10. Change Verification +## 11. Change Verification Before completing any change, verify: From d3dda2caa25731db18b2a8e12da098c672de3780 Mon Sep 17 00:00:00 2001 From: Sherv Date: Fri, 23 Jan 2026 21:30:15 +0300 Subject: [PATCH 07/90] Update fix-bug workflow to emphasize pattern discovery and prevent over-engineering - Replace generic "Analyze All Fix Locations" phase with structured Pattern Discovery Process - Add 4-step pattern discovery: find patterns, study usage, trace hierarchy, iterate - Add verification step to prevent overkill fixes when changes affect multiple areas - Emphasize using existing Formidable patterns over custom solutions - Add reminder to iterate through pattern discovery until best solution found - Ad --- .windsurf/workflows/fix-bug.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.windsurf/workflows/fix-bug.md b/.windsurf/workflows/fix-bug.md index 5ba73a3924..9c91d15258 100644 --- a/.windsurf/workflows/fix-bug.md +++ b/.windsurf/workflows/fix-bug.md @@ -40,20 +40,14 @@ Use code_search to find relevant code based on the bug description. - Understand WHY it fails (not just WHERE) - Document the root cause before proceeding -## Phase 3: Analyze All Fix Locations +## Phase 3: Find the Right Pattern and Location -- **List ALL possible fix locations:** - - Where could this be fixed? (may be multiple places) - - Which location is closest to the root cause? - - Which location has the smallest blast radius? +Follow the **Pattern Discovery Process** from formidable-core rules: -- **Search for existing patterns:** - -```text -grep_search for similar functionality in the codebase. -Look for helper methods in FrmAppHelper, FrmDb, FrmFieldsHelper. -Check how similar issues were solved elsewhere. -``` +- **Step 1: Find existing patterns** - Search models, controllers, helpers for similar functionality +- **Step 2: Study pattern usage** - Search ALL places that use the pattern to understand best usage +- **Step 3: Trace parent hierarchy** - Search parent files up to plugin root to understand logic +- **Step 4: Iterate** - If better pattern/location found, repeat from Step 1 - **Research WordPress best practices:** @@ -79,6 +73,8 @@ Check how similar issues were solved elsewhere. - Maintains backward compatibility - Works with Pro active AND inactive +- **Verify not overkill:** If changes affect several areas, analyze all related places to ensure the flow and behavior are correct and the fix is not excessive. + - **Get user approval** before implementing if: - Multiple valid approaches exist - Solution requires significant changes @@ -161,9 +157,13 @@ vendor/bin/phpunit tests/phpunit/[relevant_test_file].php - NEVER guess - always search and verify before making changes +- NEVER invent custom solutions - use existing Formidable patterns - Fix at the MOST SPECIFIC location, closest to the problem +- Iterate through Pattern Discovery until best pattern AND location found +- Verify changes are not overkill if affecting multiple areas - Maintain 100% backward compatibility - Code must work with AND without Pro active - Remove ALL debug code before completion +- If user makes manual changes after your updates, those are final From 090790218458145f87c7c54d7ad7af9531a4a5ef Mon Sep 17 00:00:00 2001 From: Sherv Date: Wed, 28 Jan 2026 19:22:05 +0300 Subject: [PATCH 08/90] Add comprehensive WordPress PHP coding standards documentation for AI agents Add detailed WordPress PHP coding standards guide (AGENTS.md) covering security, naming conventions, formatting, whitespace, control structures, operators, best practices, and general syntax. Document includes priority levels (CRITICAL/HIGH/MEDIUM/LOW) for each section and extensive code examples showing correct vs incorrect patterns. Intended as reference for AI agents and LLMs maintaining WordPress PHP code. --- .../wordpress-php-coding-standards/AGENTS.md | 1151 +++++++++++++++++ .../wordpress-php-coding-standards/SKILL.md | 142 ++ 2 files changed, 1293 insertions(+) create mode 100644 .windsurf/skills/wordpress-php-coding-standards/AGENTS.md create mode 100644 .windsurf/skills/wordpress-php-coding-standards/SKILL.md diff --git a/.windsurf/skills/wordpress-php-coding-standards/AGENTS.md b/.windsurf/skills/wordpress-php-coding-standards/AGENTS.md new file mode 100644 index 0000000000..bffc731a74 --- /dev/null +++ b/.windsurf/skills/wordpress-php-coding-standards/AGENTS.md @@ -0,0 +1,1151 @@ +# WordPress PHP Coding Standards + +**Version 1.0.0** +Based on WordPress Core Official Standards + +> **Note:** +> This document is for AI agents and LLMs to follow when maintaining, +> generating, or refactoring PHP code in the WordPress ecosystem. + +--- + +## Abstract + +These PHP coding standards are the official WordPress coding standards for PHP. They are mandatory for WordPress Core and recommended for all plugins and themes. They encompass not just code style, but also best practices for interoperability, translatability, and security. + +--- + +## Table of Contents + +1. [Security & Database](#1-security--database) — **CRITICAL** + - 1.1 [Database Queries](#11-database-queries) + - 1.2 [SQL Formatting](#12-sql-formatting) +2. [Naming Conventions](#2-naming-conventions) — **HIGH** + - 2.1 [Variables and Functions](#21-variables-and-functions) + - 2.2 [Classes, Interfaces, Traits, Enums](#22-classes-interfaces-traits-enums) + - 2.3 [Constants](#23-constants) + - 2.4 [File Naming](#24-file-naming) + - 2.5 [Dynamic Hooks](#25-dynamic-hooks) +3. [Formatting](#3-formatting) — **HIGH** + - 3.1 [Brace Style](#31-brace-style) + - 3.2 [Array Syntax](#32-array-syntax) + - 3.3 [Multiline Function Calls](#33-multiline-function-calls) + - 3.4 [Type Declarations](#34-type-declarations) +4. [Whitespace](#4-whitespace) — **MEDIUM** + - 4.1 [Space Usage](#41-space-usage) + - 4.2 [Indentation](#42-indentation) + - 4.3 [Trailing Spaces](#43-trailing-spaces) +5. [Control Structures](#5-control-structures) — **MEDIUM** + - 5.1 [Yoda Conditions](#51-yoda-conditions) + - 5.2 [Use elseif](#52-use-elseif) +6. [Operators](#6-operators) — **MEDIUM** + - 6.1 [Ternary Operator](#61-ternary-operator) + - 6.2 [Increment/Decrement Operators](#62-incrementdecrement-operators) + - 6.3 [Error Control Operator](#63-error-control-operator) +7. [Best Practices](#7-best-practices) — **MEDIUM** + - 7.1 [Self-Explanatory Flag Values](#71-self-explanatory-flag-values) + - 7.2 [Avoid Clever Code](#72-avoid-clever-code) + - 7.3 [Closures](#73-closures) + - 7.4 [Don't Use extract()](#74-dont-use-extract) + - 7.5 [Regular Expressions](#75-regular-expressions) +8. [General Syntax](#8-general-syntax) — **LOW** + - 8.1 [PHP Tags](#81-php-tags) + - 8.2 [Quotes](#82-quotes) + - 8.3 [Require/Include](#83-requireinclude) +9. [Namespaces & Imports](#9-namespaces--imports) — **MEDIUM** + - 9.1 [Namespace Declarations](#91-namespace-declarations) + - 9.2 [Import Use Statements](#92-import-use-statements) + - 9.3 [Trait Use Statements](#93-trait-use-statements) +10. [Shell Commands](#10-shell-commands) — **HIGH** + +--- + +## 1. Security & Database + +**Impact: CRITICAL** + +Database interactions must be secure and properly escaped to prevent SQL injection. + +### 1.1 Database Queries + +**Impact: CRITICAL (prevents SQL injection)** + +Avoid touching the database directly. Use WordPress functions when available. If you must write queries, always use `$wpdb->prepare()`. + +**Incorrect (direct query with unescaped data):** + +```php +$wpdb->query( "UPDATE $wpdb->posts SET post_title = '$var' WHERE ID = $id" ); +``` + +**Correct (using $wpdb->prepare()):** + +```php +$var = "dangerous'"; +$id = some_foo_number(); + +$wpdb->query( + $wpdb->prepare( + "UPDATE $wpdb->posts SET post_title = %s WHERE ID = %d", + $var, + $id + ) +); +``` + +**Placeholders:** + +- `%d` — integer +- `%f` — float +- `%s` — string +- `%i` — identifier (table/field names) + +**Important:** Do not quote placeholders! `$wpdb->prepare()` handles escaping and quoting. + +### 1.2 SQL Formatting + +**Impact: MEDIUM (readability)** + +Capitalize SQL keywords. Break complex statements into multiple lines. + +**Correct:** + +```php +$wpdb->query( + $wpdb->prepare( + "SELECT ID, post_title + FROM $wpdb->posts + WHERE post_status = %s + AND post_type = %s + ORDER BY post_date DESC", + 'publish', + 'post' + ) +); +``` + +--- + +## 2. Naming Conventions + +**Impact: HIGH** + +Consistent naming ensures code readability and discoverability. + +### 2.1 Variables and Functions + +**Impact: HIGH (code consistency)** + +Use lowercase letters with underscores. Never use camelCase. Don't abbreviate unnecessarily. + +**Incorrect:** + +```php +function someFunction( $someVariable ) {} +function getData( $usrId ) {} +``` + +**Correct:** + +```php +function some_function( $some_variable ) {} +function get_data( $user_id ) {} +``` + +### 2.2 Classes, Interfaces, Traits, Enums + +**Impact: HIGH (OOP consistency)** + +Use capitalized words separated by underscores. Acronyms should be all uppercase. + +**Incorrect:** + +```php +class walkerCategory extends Walker {} +class WpHttp {} +``` + +**Correct:** + +```php +class Walker_Category extends Walker {} +class WP_HTTP {} +interface Mailer_Interface {} +trait Forbid_Dynamic_Properties {} +enum Post_Status {} +``` + +### 2.3 Constants + +**Impact: MEDIUM (consistency)** + +All uppercase with underscores separating words. + +**Correct:** + +```php +define( 'DOING_AJAX', true ); +const MAX_UPLOAD_SIZE = 1048576; +``` + +### 2.4 File Naming + +**Impact: MEDIUM (project organization)** + +Use lowercase letters with hyphens separating words. + +**General files:** + +``` +my-plugin-name.php +template-parts.php +``` + +**Class files (prefix with `class-`):** + +``` +class-wp-error.php // For WP_Error class +class-walker-category.php // For Walker_Category class +``` + +**Template tags (suffix with `-template`):** + +``` +general-template.php +``` + +### 2.5 Dynamic Hooks + +**Impact: HIGH (hook discoverability)** + +Use interpolation with curly braces, not concatenation. Wrap in double quotes. + +**Incorrect:** + +```php +do_action( $new_status . '_' . $post->post_type, $post->ID, $post ); +``` + +**Correct:** + +```php +do_action( "{$new_status}_{$post->post_type}", $post->ID, $post ); +``` + +Use descriptive variable names in hooks: + +**Incorrect:** + +```php +do_action( "save_post_{$this->id}", $data ); +``` + +**Correct:** + +```php +do_action( "save_post_{$post_id}", $data ); +``` + +--- + +## 3. Formatting + +**Impact: HIGH** + +Structural formatting for readable and maintainable code. + +### 3.1 Brace Style + +**Impact: HIGH (code structure)** + +Always use braces, even for single statements. Opening brace on same line. + +**Incorrect:** + +```php +if ( condition ) + action(); + +if ( condition ) action(); +``` + +**Correct:** + +```php +if ( condition ) { + action1(); + action2(); +} elseif ( condition2 && condition3 ) { + action3(); +} else { + default_action(); +} +``` + +**Alternative syntax for templates:** + +```php + +
+ +
+ +
+ +
+ +``` + +### 3.2 Array Syntax + +**Impact: MEDIUM (consistency)** + +Always use long array syntax `array()`, not short syntax `[]`. + +**Incorrect:** + +```php +$array = [ 1, 2, 3 ]; +$args = [ + 'post_type' => 'page', +]; +``` + +**Correct:** + +```php +$array = array( 1, 2, 3 ); +$args = array( + 'post_type' => 'page', + 'post_author' => 123, + 'post_status' => 'publish', +); +``` + +**Note:** Include trailing comma after the last item for cleaner diffs. + +### 3.3 Multiline Function Calls + +**Impact: MEDIUM (readability)** + +Each parameter on its own line. Assign complex values to variables first. + +**Incorrect:** + +```php +$a = foo( array( 'use_this' => true, 'meta_key' => 'field_name' ), sprintf( __( 'Hello, %s!', 'textdomain' ), $friend_name ) ); +``` + +**Correct:** + +```php +$bar = array( + 'use_this' => true, + 'meta_key' => 'field_name', +); +$baz = sprintf( + /* translators: %s: Friend's name */ + __( 'Hello, %s!', 'yourtextdomain' ), + $friend_name +); + +$a = foo( + $bar, + $baz, + /* translators: %s: cat */ + sprintf( __( 'The best pet is a %s.' ), 'cat' ) +); +``` + +### 3.4 Type Declarations + +**Impact: MEDIUM (type safety)** + +One space before and after type. No space between nullability operator and type. + +**Incorrect:** + +```php +function baz(Class_Name $param_a, String$param_b, CALLABLE $param_c ) : ? iterable { + // Do something. +} +``` + +**Correct:** + +```php +function foo( Class_Name $parameter, callable $callable, int $number_of_things = 0 ) { + // Do something. +} + +function bar( + Interface_Name&Concrete_Class $param_a, + string|int $param_b, + callable $param_c = 'default_callable' +): User|false { + // Do something. +} +``` + +--- + +## 4. Whitespace + +**Impact: MEDIUM** + +Spacing rules for visual consistency. + +### 4.1 Space Usage + +**Impact: MEDIUM (readability)** + +Spaces after commas and around operators. Spaces inside parentheses of control structures. + +**Operators:** + +```php +SOME_CONST === 23; +foo() && bar(); +! $foo; +array( 1, 2, 3 ); +$baz . '-5'; +$term .= 'X'; +$result = 2 ** 3; +``` + +**Control structures:** + +```php +foreach ( $foo as $bar ) { + // ... +} + +if ( $foo && ( $bar || $baz ) ) { + // ... +} +``` + +**Functions:** + +```php +function my_function( $param1 = 'foo', $param2 = 'bar' ) { + // ... +} + +my_function( $param1, func_param( $param2 ) ); +``` + +**Array access (space only around variables):** + +```php +$x = $foo['bar']; // Correct - no space for literal +$x = $foo[0]; // Correct - no space for number +$x = $foo[ $bar ]; // Correct - space for variable +``` + +**Type casts (lowercase, short form):** + +```php +$foo = (bool) $bar; // Correct +$foo = (int) $value; // Correct + +$foo = (boolean) $bar; // Incorrect - use (bool) +$foo = (integer) $value; // Incorrect - use (int) +``` + +**Increment/decrement (no space):** + +```php +for ( $i = 0; $i < 10; $i++ ) {} +++$b; +``` + +### 4.2 Indentation + +**Impact: MEDIUM (code structure)** + +Use real tabs, not spaces. Spaces may be used mid-line for alignment. + +**Correct:** + +```php +$foo = 'somevalue'; +$foo2 = 'somevalue2'; +$foo34 = 'somevalue3'; +``` + +**Associative arrays (one item per line when more than one):** + +```php +// Single item - can be one line +$query = new WP_Query( array( 'ID' => 123 ) ); + +// Multiple items - each on own line +$args = array( + 'post_type' => 'page', + 'post_author' => 123, + 'post_status' => 'publish', +); +``` + +**Switch statements:** + +```php +switch ( $type ) { + case 'foo': + some_function(); + break; + + case 'bar': + some_function(); + break; +} +``` + +### 4.3 Trailing Spaces + +**Impact: LOW (clean diffs)** + +Remove trailing whitespace at end of lines. Omit closing PHP tag at end of file (preferred). No trailing blank lines at end of function body. + +--- + +## 5. Control Structures + +**Impact: MEDIUM** + +Rules for conditionals and loops. + +### 5.1 Yoda Conditions + +**Impact: HIGH (bug prevention)** + +Put constants/literals on the left side of comparisons. + +**Incorrect:** + +```php +if ( $the_force == true ) { + // Accidental assignment possible: if ( $the_force = true ) +} +``` + +**Correct:** + +```php +if ( true === $the_force ) { + $victorious = you_will( $be ); +} +``` + +**Applies to:** `==`, `!=`, `===`, `!==` + +**Does not apply to:** `<`, `>`, `<=`, `>=` (too difficult to read) + +### 5.2 Use elseif + +**Impact: MEDIUM (syntax compatibility)** + +Use `elseif`, not `else if`. Required for alternative syntax compatibility. + +**Incorrect:** + +```php +if ( condition ) { + // ... +} else if ( condition2 ) { + // ... +} +``` + +**Correct:** + +```php +if ( condition ) { + // ... +} elseif ( condition2 ) { + // ... +} +``` + +--- + +## 6. Operators + +**Impact: MEDIUM** + +Proper operator usage. + +### 6.1 Ternary Operator + +**Impact: MEDIUM (readability)** + +Test for true, not false (except with `! empty()`). Don't use short ternary. + +**Incorrect:** + +```php +$value = $condition ?: 'default'; // Short ternary - not allowed +$type = ( ! $is_valid ) ? 'invalid' : 'valid'; // Testing for false +``` + +**Correct:** + +```php +$musictype = ( 'jazz' === $music ) ? 'cool' : 'blah'; +$value = ( ! empty( $field ) ) ? $field : 'default'; +``` + +### 6.2 Increment/Decrement Operators + +**Impact: LOW (performance)** + +Prefer pre-increment/decrement for standalone statements. + +**Incorrect:** + +```php +$a--; +$count++; +``` + +**Correct:** + +```php +--$a; +++$count; +``` + +### 6.3 Error Control Operator + +**Impact: HIGH (error handling)** + +Don't use `@` to suppress errors. Do proper error checking instead. + +**Incorrect:** + +```php +$value = @file_get_contents( $file ); +``` + +**Correct:** + +```php +if ( file_exists( $file ) && is_readable( $file ) ) { + $value = file_get_contents( $file ); +} +``` + +--- + +## 7. Best Practices + +**Impact: MEDIUM** + +Recommendations for maintainable code. + +### 7.1 Self-Explanatory Flag Values + +**Impact: MEDIUM (readability)** + +Use descriptive strings instead of boolean flags. + +**Incorrect:** + +```php +function eat( $what, $slowly = true ) {} + +eat( 'mushrooms' ); +eat( 'mushrooms', true ); // What does true mean? +eat( 'dogfood', false ); // What does false mean? +``` + +**Correct:** + +```php +function eat( $what, $speed = 'slowly' ) {} + +eat( 'mushrooms' ); +eat( 'mushrooms', 'slowly' ); +eat( 'dogfood', 'quickly' ); +``` + +**For multiple options, use an array:** + +```php +function eat( $what, $args = array() ) {} + +eat( 'noodles', array( 'speed' => 'moderate' ) ); +``` + +### 7.2 Avoid Clever Code + +**Impact: HIGH (maintainability)** + +Readability over cleverness. Use strict comparisons. No assignments in conditionals. + +**Incorrect:** + +```php +isset( $var ) || $var = some_function(); + +if ( $data = $wpdb->get_var( '...' ) ) { + // Use $data +} + +if ( 0 == strpos( 'WordPress', 'foo' ) ) {} // Loose comparison +``` + +**Correct:** + +```php +if ( ! isset( $var ) ) { + $var = some_function(); +} + +$data = $wpdb->get_var( '...' ); +if ( $data ) { + // Use $data +} + +if ( 0 === strpos( $text, 'WordPress' ) ) {} // Strict comparison +``` + +**Switch fall-through must be documented:** + +```php +switch ( $foo ) { + case 'bar': + // Empty case can fall through without comment + case 'baz': + echo esc_html( $foo ); + break; + + case 'dog': + echo 'horse'; + // no break - explicit comment required + case 'fish': + echo 'bird'; + break; +} +``` + +**Never use:** + +- `goto` statement +- `eval()` construct +- `create_function()` (deprecated/removed) + +### 7.3 Closures + +**Impact: MEDIUM (hook compatibility)** + +Closures are allowed but should NOT be used as filter/action callbacks (difficult to remove). + +**Acceptable:** + +```php +$caption = preg_replace_callback( + '/<[a-zA-Z0-9]+(?: [^<>]+>)*/', + function ( $matches ) { + return preg_replace( '/[\r\n\t]+/', ' ', $matches[0] ); + }, + $caption +); +``` + +**Not recommended for hooks:** + +```php +// Hard to remove with remove_action() +add_action( 'init', function() { + // ... +} ); +``` + +### 7.4 Don't Use extract() + +**Impact: HIGH (debugging)** + +Never use `extract()`. It makes code harder to debug and understand. + +**Incorrect:** + +```php +extract( $args ); +echo $title; // Where did $title come from? +``` + +**Correct:** + +```php +$title = $args['title']; +echo $title; +``` + +### 7.5 Regular Expressions + +**Impact: MEDIUM (security/compatibility)** + +Use PCRE (`preg_` functions). Never use `/e` modifier. Use single-quoted strings. + +**Incorrect:** + +```php +preg_replace( '/pattern/e', 'replacement', $subject ); // /e is deprecated +``` + +**Correct:** + +```php +preg_replace_callback( + '/pattern/', + function ( $matches ) { + return process( $matches[0] ); + }, + $subject +); +``` + +--- + +## 8. General Syntax + +**Impact: LOW** + +Basic PHP syntax rules. + +### 8.1 PHP Tags + +**Impact: MEDIUM (compatibility)** + +Always use full PHP tags. Never use shorthand. + +**Incorrect:** + +```php + + +``` + +**Correct:** + +```php + + +``` + +**Multiline PHP in HTML (tags on their own lines):** + +```php + + + +``` + +### 8.2 Quotes + +**Impact: LOW (string handling)** + +Use single quotes when not evaluating variables. Alternate quote styles to avoid escaping. + +**Correct:** + +```php +echo 'Link name'; +echo "text with a ' single quote"; +``` + +**Always escape output in HTML attributes:** + +```php +echo '' . esc_html( $text ) . ''; +``` + +### 8.3 Require/Include + +**Impact: MEDIUM (file loading)** + +No parentheses. One space after keyword. Prefer `require_once` over `include_once`. + +**Incorrect:** + +```php +include_once( ABSPATH . 'file-name.php' ); +require_once ABSPATH . 'file-name.php'; // Extra space +``` + +**Correct:** + +```php +require_once ABSPATH . 'file-name.php'; +``` + +**Why `require_once`:** If file not found, `require` throws Fatal Error (stops execution). `include` only warns and continues, potentially causing security issues. + +--- + +## Object-Oriented Programming + +### One Structure Per File + +Each class, interface, trait, or enum should be in its own file. + +### Visibility + +Always declare visibility (`public`, `protected`, `private`). + +**Incorrect:** + +```php +class Foo { + var $bar; // Old syntax + function baz() {} // No visibility +} +``` + +**Correct:** + +```php +class Foo { + public $bar; + + public function baz() {} + + protected function qux() {} + + private function quux() {} +} +``` + +### Modifier Order + +```php +final public static function foo() {} +abstract protected function bar(); +``` + +### Object Instantiation + +Always use parentheses, even without arguments. + +**Incorrect:** + +```php +$obj = new Foo; +``` + +**Correct:** + +```php +$obj = new Foo(); +$obj = new Foo( $arg ); +``` + +--- + +## Magic Constants + +Use uppercase for magic constants. + +**Correct:** + +```php +__DIR__ +__FILE__ +__CLASS__ +__FUNCTION__ +``` + +--- + +## Spread Operator + +Space after `...` when used to spread. No space when collecting. + +```php +// Spreading +function_call( ...$array ); +$merged = array( ...$array1, ...$array2 ); + +// Collecting (variadic) +function variadic_function( ...$args ) {} +``` + +--- + +## 9. Namespaces & Imports + +**Impact: MEDIUM** + +Modern PHP namespace and import conventions for WordPress plugins and themes. + +### 9.1 Namespace Declarations + +**Impact: MEDIUM (organization)** + +Each part of a namespace name should consist of capitalized words separated by underscores. + +**Incorrect:** + +```php +namespace prefix\admin\domainUrl\subDomain; // camelCase not allowed +namespace Foo { + // Code - curly brace syntax not allowed +} +namespace { + // Global namespace declaration not allowed +} +``` + +**Correct:** + +```php +namespace Prefix\Admin\Domain_URL\Sub_Domain\Event; +``` + +**Rules:** + +- One blank line before the declaration, at least one blank line after +- Only one namespace declaration per file, at the top +- No curly brace syntax +- No global namespace declarations +- Use unique, long prefixes like `Vendor\Project_Name` to prevent conflicts +- Do NOT use `wp` or `WordPress` as namespace prefixes + +**Note:** Namespaces are encouraged for plugins/themes but not yet used in WordPress Core. + +### 9.2 Import Use Statements + +**Impact: MEDIUM (code organization)** + +Import `use` statements should be at the top of the file after the namespace declaration. + +**Order of imports:** + +1. Namespaces, classes, interfaces, traits, enums +2. Functions +3. Constants + +**Incorrect:** + +```php +namespace Project_Name\Feature; + +use const Project_Name\Sub_Feature\CONSTANT_A; // Constants before classes +use function Project_Name\Sub_Feature\function_a; // Functions before classes +use \Project_Name\Sub_Feature\Class_C as aliased_class_c; // Leading backslash, wrong alias naming + +class Foo { + // Code. +} + +use Project_Name\Another_Class; // Import after class definition - not allowed +``` + +**Correct:** + +```php +namespace Project_Name\Feature; + +use Project_Name\Sub_Feature\Class_A; +use Project_Name\Sub_Feature\Class_C as Aliased_Class_C; +use Project_Name\Sub_Feature\{ + Class_D, + Class_E as Aliased_Class_E, +} + +use function Project_Name\Sub_Feature\function_a; +use function Project_Name\Sub_Feature\function_b as aliased_function; + +use const Project_Name\Sub_Feature\CONSTANT_A; +use const Project_Name\Sub_Feature\CONSTANT_D as ALIASED_CONSTANT; + +// Rest of the code. +``` + +**Rules:** + +- No leading backslash in imports +- Aliases must follow WordPress naming conventions (capitalized words with underscores for classes, lowercase with underscores for functions) +- Don't combine different import types in one statement +- All imports before any class/function definitions + +**Note:** Import `use` statements are discouraged in WordPress Core for now. + +### 9.3 Trait Use Statements + +**Impact: MEDIUM (OOP organization)** + +Trait `use` statements should be at the top of a class with proper spacing. + +**Incorrect:** + +```php +class Foo { + // No blank line before trait use statement + use Bar_Trait; + + use Foo_Trait, Bazinga_Trait{Bar_Trait::method_name insteadof Foo_Trait; // Wrong formatting + Bazinga_Trait::method_name as bazinga_method; + }; + + public $baz = true; // Missing blank line after trait import +} +``` + +**Correct:** + +```php +class Foo { + + use Bar_Trait; + + use Foo_Trait, Bazinga_Trait { + Bar_Trait::method_name insteadof Foo_Trait; + Bazinga_Trait::method_name as bazinga_method; + } + + use Loopy_Trait { + eat as protected; + } + + public $baz = true; + + // Rest of class... +} +``` + +**Rules:** + +- One blank line before the first `use` statement +- At least one blank line after the last `use` statement (exception: if class only contains trait imports) +- Each aliasing/conflict resolution on its own line +- Proper indentation inside curly braces + +--- + +## 10. Shell Commands + +**Impact: HIGH (security)** + +Use of shell commands requires caution due to security implications. + +**Never use the backtick operator:** + +```php +// Incorrect - backtick operator is identical to shell_exec() +$output = `ls -la`; +``` + +**Why:** The backtick operator is equivalent to `shell_exec()`, and most hosts disable this function in `php.ini` for security reasons. It can lead to command injection vulnerabilities if user input is involved. + +**If shell commands are absolutely necessary:** + +- Use `escapeshellarg()` and `escapeshellcmd()` for any user-provided input +- Prefer WordPress functions that handle this safely when available +- Document why shell access is needed diff --git a/.windsurf/skills/wordpress-php-coding-standards/SKILL.md b/.windsurf/skills/wordpress-php-coding-standards/SKILL.md new file mode 100644 index 0000000000..37d509dd28 --- /dev/null +++ b/.windsurf/skills/wordpress-php-coding-standards/SKILL.md @@ -0,0 +1,142 @@ +# WordPress PHP Coding Standards + +**Version 1.0.0** +Based on WordPress Core Official Standards + +> **Note:** +> This document is for AI agents and LLMs to follow when maintaining, +> generating, or refactoring PHP code in the WordPress ecosystem. + +--- + +## Overview + +These PHP coding standards are the official WordPress coding standards. They are mandatory for WordPress Core and recommended for all plugins and themes. Beyond code style, they encompass best practices for interoperability, translatability, and security. + +**When to apply:** Any PHP file in WordPress Core, plugins, or themes. + +**Reference:** [WordPress PHP Coding Standards](https://developer.wordpress.org/coding-standards/wordpress-coding-standards/php/) + +--- + +## Rule Categories by Priority + +### 1. Security & Database — **CRITICAL** + +Fundamental security practices that prevent vulnerabilities. + +| Rule | Impact | +| --------------------------------------------- | ---------------------- | +| [Database Queries](rules/database-queries.md) | Prevents SQL injection | + +### 2. Naming Conventions — **HIGH** + +Consistent naming ensures code readability and discoverability. + +| Rule | Impact | +| ------------------------------------------------- | ---------------- | +| [Naming Conventions](rules/naming-conventions.md) | Code consistency | + +### 3. Formatting — **HIGH** + +Structural formatting for readable and maintainable code. + +| Rule | Impact | +| --------------------------------- | -------------- | +| [Formatting](rules/formatting.md) | Code structure | + +### 4. Whitespace & Indentation — **MEDIUM** + +Spacing rules for visual consistency. + +| Rule | Impact | +| --------------------------------- | ----------- | +| [Whitespace](rules/whitespace.md) | Readability | + +### 5. Control Structures — **MEDIUM** + +Rules for conditionals and loops. + +| Rule | Impact | +| ------------------------------------------------- | -------------- | +| [Control Structures](rules/control-structures.md) | Bug prevention | + +### 6. Operators — **MEDIUM** + +Proper operator usage. + +| Rule | Impact | +| ------------------------------- | -------------- | +| [Operators](rules/operators.md) | Error handling | + +### 7. Best Practices — **MEDIUM** + +Recommendations for maintainable code. + +| Rule | Impact | +| ----------------------------------------- | --------------- | +| [Best Practices](rules/best-practices.md) | Maintainability | + +### 8. General Syntax — **LOW** + +Basic PHP syntax rules. + +| Rule | Impact | +| ----------------------------------------- | ------------- | +| [General Syntax](rules/general-syntax.md) | Compatibility | + +### 9. Object-Oriented Programming — **MEDIUM** + +OOP guidelines and patterns. + +| Rule | Impact | +| ------------------- | --------------- | +| [OOP](rules/oop.md) | OOP consistency | + +### 10. Namespaces & Imports — **MEDIUM** + +Modern PHP namespace and import conventions for plugins and themes. + +| Rule | Impact | +| --------------------------------------------------- | ----------------- | +| [Namespaces & Imports](rules/namespaces-imports.md) | Code organization | + +### 11. Shell Commands — **HIGH** + +Security considerations for shell command execution. + +| Rule | Impact | +| ----------------------------------------- | -------- | +| [Shell Commands](rules/shell-commands.md) | Security | + +--- + +## Usage + +### Load all rules + +``` +@wordpress-php-coding-standards +``` + +### Load specific category + +``` +@wordpress-php-coding-standards/rules/naming-conventions +@wordpress-php-coding-standards/rules/database-queries +``` + +### Full compiled guide + +See [AGENTS.md](AGENTS.md) for the complete reference. + +--- + +## Tooling + +Use the official [WordPress Coding Standards](https://github.com/WordPress/WordPress-Coding-Standards) with [PHP_CodeSniffer](https://github.com/PHPCSStandards/PHP_CodeSniffer/) to automatically check code compliance. + +```bash +composer require --dev wp-coding-standards/wpcs +./vendor/bin/phpcs --standard=WordPress path/to/file.php +``` From fbee69560f00f47dadcb3ecd4d87ba86655c2af3 Mon Sep 17 00:00:00 2001 From: Sherv Date: Wed, 28 Jan 2026 19:22:22 +0300 Subject: [PATCH 09/90] Add WordPress PHP coding standards rules documentation split into focused modules Split comprehensive WordPress PHP coding standards into separate focused rule files covering best practices, control structures, database queries, formatting, general syntax, naming conventions, operators, security, and whitespace. Each file includes priority level, impact description, and extensive code examples showing correct vs incorrect patterns. Intended as modular reference for AI agents maintaining WordPress PHP --- .../rules/best-practices.md | 167 ++++++++++++++++++ .../rules/control-structures.md | 56 ++++++ .../rules/database-queries.md | 70 ++++++++ .../rules/formatting.md | 137 ++++++++++++++ .../rules/general-syntax.md | 76 ++++++++ .../rules/namespaces-imports.md | 148 ++++++++++++++++ .../rules/naming-conventions.md | 118 +++++++++++++ .../rules/oop.md | 97 ++++++++++ .../rules/operators.md | 64 +++++++ .../rules/shell-commands.md | 23 +++ .../rules/whitespace.md | 117 ++++++++++++ 11 files changed, 1073 insertions(+) create mode 100644 .windsurf/skills/wordpress-php-coding-standards/rules/best-practices.md create mode 100644 .windsurf/skills/wordpress-php-coding-standards/rules/control-structures.md create mode 100644 .windsurf/skills/wordpress-php-coding-standards/rules/database-queries.md create mode 100644 .windsurf/skills/wordpress-php-coding-standards/rules/formatting.md create mode 100644 .windsurf/skills/wordpress-php-coding-standards/rules/general-syntax.md create mode 100644 .windsurf/skills/wordpress-php-coding-standards/rules/namespaces-imports.md create mode 100644 .windsurf/skills/wordpress-php-coding-standards/rules/naming-conventions.md create mode 100644 .windsurf/skills/wordpress-php-coding-standards/rules/oop.md create mode 100644 .windsurf/skills/wordpress-php-coding-standards/rules/operators.md create mode 100644 .windsurf/skills/wordpress-php-coding-standards/rules/shell-commands.md create mode 100644 .windsurf/skills/wordpress-php-coding-standards/rules/whitespace.md diff --git a/.windsurf/skills/wordpress-php-coding-standards/rules/best-practices.md b/.windsurf/skills/wordpress-php-coding-standards/rules/best-practices.md new file mode 100644 index 0000000000..61b10fe576 --- /dev/null +++ b/.windsurf/skills/wordpress-php-coding-standards/rules/best-practices.md @@ -0,0 +1,167 @@ +# Best Practices + +**Priority: MEDIUM** +**Impact: Maintainability and debugging** + +--- + +## Self-Explanatory Flag Values + +Use descriptive strings instead of boolean flags. + +**Incorrect:** + +```php +function eat( $what, $slowly = true ) {} + +eat( 'mushrooms' ); +eat( 'mushrooms', true ); // What does true mean? +eat( 'dogfood', false ); // What does false mean? +``` + +**Correct:** + +```php +function eat( $what, $speed = 'slowly' ) {} + +eat( 'mushrooms' ); +eat( 'mushrooms', 'slowly' ); +eat( 'dogfood', 'quickly' ); +``` + +**For multiple options, use an array:** + +```php +function eat( $what, $args = array() ) {} + +eat( 'noodles', array( 'speed' => 'moderate' ) ); +``` + +--- + +## Avoid Clever Code + +Readability over cleverness. Use strict comparisons. No assignments in conditionals. + +**Incorrect:** + +```php +isset( $var ) || $var = some_function(); + +if ( $data = $wpdb->get_var( '...' ) ) { + // Use $data +} + +if ( 0 == strpos( 'WordPress', 'foo' ) ) {} // Loose comparison +``` + +**Correct:** + +```php +if ( ! isset( $var ) ) { + $var = some_function(); +} + +$data = $wpdb->get_var( '...' ); +if ( $data ) { + // Use $data +} + +if ( 0 === strpos( $text, 'WordPress' ) ) {} // Strict comparison +``` + +**Switch fall-through must be documented:** + +```php +switch ( $foo ) { + case 'bar': + // Empty case can fall through without comment + case 'baz': + echo esc_html( $foo ); + break; + + case 'dog': + echo 'horse'; + // no break - explicit comment required + case 'fish': + echo 'bird'; + break; +} +``` + +**Never use:** + +- `goto` statement +- `eval()` construct +- `create_function()` (deprecated/removed) + +--- + +## Closures + +Closures are allowed but should NOT be used as filter/action callbacks (difficult to remove). + +**Acceptable:** + +```php +$caption = preg_replace_callback( + '/<[a-zA-Z0-9]+(?: [^<>]+>)*/', + function ( $matches ) { + return preg_replace( '/[\r\n\t]+/', ' ', $matches[0] ); + }, + $caption +); +``` + +**Not recommended for hooks:** + +```php +// Hard to remove with remove_action() +add_action( 'init', function() { + // ... +} ); +``` + +--- + +## Don't Use extract() + +Never use `extract()`. It makes code harder to debug and understand. + +**Incorrect:** + +```php +extract( $args ); +echo $title; // Where did $title come from? +``` + +**Correct:** + +```php +$title = $args['title']; +echo $title; +``` + +--- + +## Regular Expressions + +Use PCRE (`preg_` functions). Never use `/e` modifier. Use single-quoted strings. + +**Incorrect:** + +```php +preg_replace( '/pattern/e', 'replacement', $subject ); // /e is deprecated +``` + +**Correct:** + +```php +preg_replace_callback( + '/pattern/', + function ( $matches ) { + return process( $matches[0] ); + }, + $subject +); +``` diff --git a/.windsurf/skills/wordpress-php-coding-standards/rules/control-structures.md b/.windsurf/skills/wordpress-php-coding-standards/rules/control-structures.md new file mode 100644 index 0000000000..44efc2fd3a --- /dev/null +++ b/.windsurf/skills/wordpress-php-coding-standards/rules/control-structures.md @@ -0,0 +1,56 @@ +# Control Structures + +**Priority: MEDIUM** +**Impact: Bug prevention and syntax compatibility** + +--- + +## Yoda Conditions + +Put constants/literals on the left side of comparisons. + +**Incorrect:** + +```php +if ( $the_force == true ) { + // Accidental assignment possible: if ( $the_force = true ) +} +``` + +**Correct:** + +```php +if ( true === $the_force ) { + $victorious = you_will( $be ); +} +``` + +**Applies to:** `==`, `!=`, `===`, `!==` + +**Does not apply to:** `<`, `>`, `<=`, `>=` (too difficult to read) + +--- + +## Use elseif + +Use `elseif`, not `else if`. Required for alternative syntax compatibility. + +**Incorrect:** + +```php +if ( condition ) { + // ... +} else if ( condition2 ) { + // ... +} +``` + +**Correct:** + +```php +if ( condition ) { + // ... +} elseif ( condition2 ) { + // ... +} +``` diff --git a/.windsurf/skills/wordpress-php-coding-standards/rules/database-queries.md b/.windsurf/skills/wordpress-php-coding-standards/rules/database-queries.md new file mode 100644 index 0000000000..1d40449fd3 --- /dev/null +++ b/.windsurf/skills/wordpress-php-coding-standards/rules/database-queries.md @@ -0,0 +1,70 @@ +# Database Queries + +**Priority: CRITICAL** +**Impact: Prevents SQL injection** + +--- + +## Overview + +Database interactions must be secure and properly escaped to prevent SQL injection. Avoid touching the database directly—use WordPress functions when available. If you must write queries, always use `$wpdb->prepare()`. + +--- + +## Rules + +### Always Use $wpdb->prepare() + +**Incorrect (direct query with unescaped data):** + +```php +$wpdb->query( "UPDATE $wpdb->posts SET post_title = '$var' WHERE ID = $id" ); +``` + +**Correct (using $wpdb->prepare()):** + +```php +$var = "dangerous'"; +$id = some_foo_number(); + +$wpdb->query( + $wpdb->prepare( + "UPDATE $wpdb->posts SET post_title = %s WHERE ID = %d", + $var, + $id + ) +); +``` + +--- + +## Placeholders + +- `%d` — integer +- `%f` — float +- `%s` — string +- `%i` — identifier (table/field names) + +**Important:** Do not quote placeholders! `$wpdb->prepare()` handles escaping and quoting. + +--- + +## SQL Formatting + +Capitalize SQL keywords. Break complex statements into multiple lines. + +**Correct:** + +```php +$wpdb->query( + $wpdb->prepare( + "SELECT ID, post_title + FROM $wpdb->posts + WHERE post_status = %s + AND post_type = %s + ORDER BY post_date DESC", + 'publish', + 'post' + ) +); +``` diff --git a/.windsurf/skills/wordpress-php-coding-standards/rules/formatting.md b/.windsurf/skills/wordpress-php-coding-standards/rules/formatting.md new file mode 100644 index 0000000000..3f333151d6 --- /dev/null +++ b/.windsurf/skills/wordpress-php-coding-standards/rules/formatting.md @@ -0,0 +1,137 @@ +# Formatting + +**Priority: HIGH** +**Impact: Code structure and maintainability** + +--- + +## Brace Style + +Always use braces, even for single statements. Opening brace on same line. + +**Incorrect:** + +```php +if ( condition ) + action(); + +if ( condition ) action(); +``` + +**Correct:** + +```php +if ( condition ) { + action1(); + action2(); +} elseif ( condition2 && condition3 ) { + action3(); +} else { + default_action(); +} +``` + +**Alternative syntax for templates:** + +```php + +
+ +
+ +
+ +
+ +``` + +--- + +## Array Syntax + +Always use long array syntax `array()`, not short syntax `[]`. + +**Incorrect:** + +```php +$array = [ 1, 2, 3 ]; +$args = [ + 'post_type' => 'page', +]; +``` + +**Correct:** + +```php +$array = array( 1, 2, 3 ); +$args = array( + 'post_type' => 'page', + 'post_author' => 123, + 'post_status' => 'publish', +); +``` + +**Note:** Include trailing comma after the last item for cleaner diffs. + +--- + +## Multiline Function Calls + +Each parameter on its own line. Assign complex values to variables first. + +**Incorrect:** + +```php +$a = foo( array( 'use_this' => true, 'meta_key' => 'field_name' ), sprintf( __( 'Hello, %s!', 'textdomain' ), $friend_name ) ); +``` + +**Correct:** + +```php +$bar = array( + 'use_this' => true, + 'meta_key' => 'field_name', +); +$baz = sprintf( + /* translators: %s: Friend's name */ + __( 'Hello, %s!', 'yourtextdomain' ), + $friend_name +); + +$a = foo( + $bar, + $baz, + /* translators: %s: cat */ + sprintf( __( 'The best pet is a %s.' ), 'cat' ) +); +``` + +--- + +## Type Declarations + +One space before and after type. No space between nullability operator and type. + +**Incorrect:** + +```php +function baz(Class_Name $param_a, String$param_b, CALLABLE $param_c ) : ? iterable { + // Do something. +} +``` + +**Correct:** + +```php +function foo( Class_Name $parameter, callable $callable, int $number_of_things = 0 ) { + // Do something. +} + +function bar( + Interface_Name&Concrete_Class $param_a, + string|int $param_b, + callable $param_c = 'default_callable' +): User|false { + // Do something. +} +``` diff --git a/.windsurf/skills/wordpress-php-coding-standards/rules/general-syntax.md b/.windsurf/skills/wordpress-php-coding-standards/rules/general-syntax.md new file mode 100644 index 0000000000..b277f441cb --- /dev/null +++ b/.windsurf/skills/wordpress-php-coding-standards/rules/general-syntax.md @@ -0,0 +1,76 @@ +# General Syntax + +**Priority: LOW** +**Impact: Compatibility and string handling** + +--- + +## PHP Tags + +Always use full PHP tags. Never use shorthand. + +**Incorrect:** + +```php + + +``` + +**Correct:** + +```php + + +``` + +**Multiline PHP in HTML (tags on their own lines):** + +```php + + + +``` + +--- + +## Quotes + +Use single quotes when not evaluating variables. Alternate quote styles to avoid escaping. + +**Correct:** + +```php +echo 'Link name'; +echo "text with a ' single quote"; +``` + +**Always escape output in HTML attributes:** + +```php +echo '' . esc_html( $text ) . ''; +``` + +--- + +## Require/Include + +No parentheses. One space after keyword. Prefer `require_once` over `include_once`. + +**Incorrect:** + +```php +include_once( ABSPATH . 'file-name.php' ); +require_once ABSPATH . 'file-name.php'; // Extra space +``` + +**Correct:** + +```php +require_once ABSPATH . 'file-name.php'; +``` + +**Why `require_once`:** If file not found, `require` throws Fatal Error (stops execution). `include` only warns and continues, potentially causing security issues. diff --git a/.windsurf/skills/wordpress-php-coding-standards/rules/namespaces-imports.md b/.windsurf/skills/wordpress-php-coding-standards/rules/namespaces-imports.md new file mode 100644 index 0000000000..92d7862777 --- /dev/null +++ b/.windsurf/skills/wordpress-php-coding-standards/rules/namespaces-imports.md @@ -0,0 +1,148 @@ +# Namespaces & Imports + +**Impact: MEDIUM** + +Modern PHP namespace and import conventions for WordPress plugins and themes. + +## Namespace Declarations + +**Impact: MEDIUM (organization)** + +Each part of a namespace name should consist of capitalized words separated by underscores. + +**Incorrect:** + +```php +namespace prefix\admin\domainUrl\subDomain; // camelCase not allowed +namespace Foo { + // Code - curly brace syntax not allowed +} +namespace { + // Global namespace declaration not allowed +} +``` + +**Correct:** + +```php +namespace Prefix\Admin\Domain_URL\Sub_Domain\Event; +``` + +**Rules:** +- One blank line before the declaration, at least one blank line after +- Only one namespace declaration per file, at the top +- No curly brace syntax +- No global namespace declarations +- Use unique, long prefixes like `Vendor\Project_Name` to prevent conflicts +- Do NOT use `wp` or `WordPress` as namespace prefixes + +**Note:** Namespaces are encouraged for plugins/themes but not yet used in WordPress Core. + +--- + +## Import Use Statements + +**Impact: MEDIUM (code organization)** + +Import `use` statements should be at the top of the file after the namespace declaration. + +**Order of imports:** +1. Namespaces, classes, interfaces, traits, enums +2. Functions +3. Constants + +**Incorrect:** + +```php +namespace Project_Name\Feature; + +use const Project_Name\Sub_Feature\CONSTANT_A; // Constants before classes +use function Project_Name\Sub_Feature\function_a; // Functions before classes +use \Project_Name\Sub_Feature\Class_C as aliased_class_c; // Leading backslash, wrong alias naming + +class Foo { + // Code. +} + +use Project_Name\Another_Class; // Import after class definition - not allowed +``` + +**Correct:** + +```php +namespace Project_Name\Feature; + +use Project_Name\Sub_Feature\Class_A; +use Project_Name\Sub_Feature\Class_C as Aliased_Class_C; +use Project_Name\Sub_Feature\{ + Class_D, + Class_E as Aliased_Class_E, +} + +use function Project_Name\Sub_Feature\function_a; +use function Project_Name\Sub_Feature\function_b as aliased_function; + +use const Project_Name\Sub_Feature\CONSTANT_A; +use const Project_Name\Sub_Feature\CONSTANT_D as ALIASED_CONSTANT; + +// Rest of the code. +``` + +**Rules:** +- No leading backslash in imports +- Aliases must follow WordPress naming conventions (capitalized words with underscores for classes, lowercase with underscores for functions) +- Don't combine different import types in one statement +- All imports before any class/function definitions + +**Note:** Import `use` statements are discouraged in WordPress Core for now. + +--- + +## Trait Use Statements + +**Impact: MEDIUM (OOP organization)** + +Trait `use` statements should be at the top of a class with proper spacing. + +**Incorrect:** + +```php +class Foo { + // No blank line before trait use statement + use Bar_Trait; + + use Foo_Trait, Bazinga_Trait{Bar_Trait::method_name insteadof Foo_Trait; // Wrong formatting + Bazinga_Trait::method_name as bazinga_method; + }; + + public $baz = true; // Missing blank line after trait import +} +``` + +**Correct:** + +```php +class Foo { + + use Bar_Trait; + + use Foo_Trait, Bazinga_Trait { + Bar_Trait::method_name insteadof Foo_Trait; + Bazinga_Trait::method_name as bazinga_method; + } + + use Loopy_Trait { + eat as protected; + } + + public $baz = true; + + // Rest of class... +} +``` + +**Rules:** +- One blank line before the first `use` statement +- At least one blank line after the last `use` statement (exception: if class only contains trait imports) +- Each aliasing/conflict resolution on its own line +- Proper indentation inside curly braces diff --git a/.windsurf/skills/wordpress-php-coding-standards/rules/naming-conventions.md b/.windsurf/skills/wordpress-php-coding-standards/rules/naming-conventions.md new file mode 100644 index 0000000000..5df6e7189e --- /dev/null +++ b/.windsurf/skills/wordpress-php-coding-standards/rules/naming-conventions.md @@ -0,0 +1,118 @@ +# Naming Conventions + +**Priority: HIGH** +**Impact: Code consistency and readability** + +--- + +## Variables and Functions + +Use lowercase letters with underscores. Never use camelCase. Don't abbreviate unnecessarily. + +**Incorrect:** + +```php +function someFunction( $someVariable ) {} +function getData( $usrId ) {} +``` + +**Correct:** + +```php +function some_function( $some_variable ) {} +function get_data( $user_id ) {} +``` + +--- + +## Classes, Interfaces, Traits, Enums + +Use capitalized words separated by underscores. Acronyms should be all uppercase. + +**Incorrect:** + +```php +class walkerCategory extends Walker {} +class WpHttp {} +``` + +**Correct:** + +```php +class Walker_Category extends Walker {} +class WP_HTTP {} +interface Mailer_Interface {} +trait Forbid_Dynamic_Properties {} +enum Post_Status {} +``` + +--- + +## Constants + +All uppercase with underscores separating words. + +**Correct:** + +```php +define( 'DOING_AJAX', true ); +const MAX_UPLOAD_SIZE = 1048576; +``` + +--- + +## File Naming + +Use lowercase letters with hyphens separating words. + +**General files:** + +```text +my-plugin-name.php +template-parts.php +``` + +**Class files (prefix with `class-`):** + +```text +class-wp-error.php // For WP_Error class +class-walker-category.php // For Walker_Category class +``` + +**Template tags (suffix with `-template`):** + +```text +general-template.php +``` + +--- + +## Dynamic Hooks + +Use interpolation with curly braces, not concatenation. Wrap in double quotes. + +**Incorrect:** + +```php +do_action( $new_status . '_' . $post->post_type, $post->ID, $post ); +``` + +**Correct:** + +```php +do_action( "{$new_status}_{$post->post_type}", $post->ID, $post ); +``` + +Use descriptive variable names in hooks: + +**Incorrect:** + +```php +do_action( "save_post_{$this->id}", $data ); +``` + +**Correct:** + +```php +do_action( "save_post_{$post_id}", $data ); +``` diff --git a/.windsurf/skills/wordpress-php-coding-standards/rules/oop.md b/.windsurf/skills/wordpress-php-coding-standards/rules/oop.md new file mode 100644 index 0000000000..4665e4b5ec --- /dev/null +++ b/.windsurf/skills/wordpress-php-coding-standards/rules/oop.md @@ -0,0 +1,97 @@ +# Object-Oriented Programming + +**Priority: MEDIUM** +**Impact: OOP consistency and maintainability** + +--- + +## One Structure Per File + +Each class, interface, trait, or enum should be in its own file. + +--- + +## Visibility + +Always declare visibility (`public`, `protected`, `private`). + +**Incorrect:** + +```php +class Foo { + var $bar; // Old syntax + function baz() {} // No visibility +} +``` + +**Correct:** + +```php +class Foo { + public $bar; + + public function baz() {} + + protected function qux() {} + + private function quux() {} +} +``` + +--- + +## Modifier Order + +```php +final public static function foo() {} +abstract protected function bar(); +``` + +--- + +## Object Instantiation + +Always use parentheses, even without arguments. + +**Incorrect:** + +```php +$obj = new Foo; +``` + +**Correct:** + +```php +$obj = new Foo(); +$obj = new Foo( $arg ); +``` + +--- + +## Magic Constants + +Use uppercase for magic constants. + +**Correct:** + +```php +__DIR__ +__FILE__ +__CLASS__ +__FUNCTION__ +``` + +--- + +## Spread Operator + +Space after `...` when used to spread. No space when collecting. + +```php +// Spreading +function_call( ...$array ); +$merged = array( ...$array1, ...$array2 ); + +// Collecting (variadic) +function variadic_function( ...$args ) {} +``` diff --git a/.windsurf/skills/wordpress-php-coding-standards/rules/operators.md b/.windsurf/skills/wordpress-php-coding-standards/rules/operators.md new file mode 100644 index 0000000000..edcbef7be1 --- /dev/null +++ b/.windsurf/skills/wordpress-php-coding-standards/rules/operators.md @@ -0,0 +1,64 @@ +# Operators + +**Priority: MEDIUM** +**Impact: Readability and error handling** + +--- + +## Ternary Operator + +Test for true, not false (except with `! empty()`). Don't use short ternary. + +**Incorrect:** + +```php +$value = $condition ?: 'default'; // Short ternary - not allowed +$type = ( ! $is_valid ) ? 'invalid' : 'valid'; // Testing for false +``` + +**Correct:** + +```php +$musictype = ( 'jazz' === $music ) ? 'cool' : 'blah'; +$value = ( ! empty( $field ) ) ? $field : 'default'; +``` + +--- + +## Increment/Decrement Operators + +Prefer pre-increment/decrement for standalone statements. + +**Incorrect:** + +```php +$a--; +$count++; +``` + +**Correct:** + +```php +--$a; +++$count; +``` + +--- + +## Error Control Operator + +Don't use `@` to suppress errors. Do proper error checking instead. + +**Incorrect:** + +```php +$value = @file_get_contents( $file ); +``` + +**Correct:** + +```php +if ( file_exists( $file ) && is_readable( $file ) ) { + $value = file_get_contents( $file ); +} +``` diff --git a/.windsurf/skills/wordpress-php-coding-standards/rules/shell-commands.md b/.windsurf/skills/wordpress-php-coding-standards/rules/shell-commands.md new file mode 100644 index 0000000000..4b8e1e91ff --- /dev/null +++ b/.windsurf/skills/wordpress-php-coding-standards/rules/shell-commands.md @@ -0,0 +1,23 @@ +# Shell Commands + +**Impact: HIGH (security)** + +Use of shell commands requires caution due to security implications. + +## Never Use the Backtick Operator + +**Incorrect:** + +```php +// Backtick operator is identical to shell_exec() +$output = `ls -la`; +``` + +**Why:** The backtick operator is equivalent to `shell_exec()`, and most hosts disable this function in `php.ini` for security reasons. It can lead to command injection vulnerabilities if user input is involved. + +**If shell commands are absolutely necessary:** +- Use `escapeshellarg()` and `escapeshellcmd()` for any user-provided input +- Prefer WordPress functions that handle this safely when available +- Document why shell access is needed + +**Security Note:** Shell command execution should be avoided whenever possible. Always look for safer alternatives using built-in PHP functions or WordPress APIs. diff --git a/.windsurf/skills/wordpress-php-coding-standards/rules/whitespace.md b/.windsurf/skills/wordpress-php-coding-standards/rules/whitespace.md new file mode 100644 index 0000000000..8b0d5f066c --- /dev/null +++ b/.windsurf/skills/wordpress-php-coding-standards/rules/whitespace.md @@ -0,0 +1,117 @@ +# Whitespace + +**Priority: MEDIUM** +**Impact: Readability and visual consistency** + +--- + +## Space Usage + +Spaces after commas and around operators. Spaces inside parentheses of control structures. + +**Operators:** + +```php +SOME_CONST === 23; +foo() && bar(); +! $foo; +array( 1, 2, 3 ); +$baz . '-5'; +$term .= 'X'; +$result = 2 ** 3; +``` + +**Control structures:** + +```php +foreach ( $foo as $bar ) { + // ... +} + +if ( $foo && ( $bar || $baz ) ) { + // ... +} +``` + +**Functions:** + +```php +function my_function( $param1 = 'foo', $param2 = 'bar' ) { + // ... +} + +my_function( $param1, func_param( $param2 ) ); +``` + +**Array access (space only around variables):** + +```php +$x = $foo['bar']; // Correct - no space for literal +$x = $foo[0]; // Correct - no space for number +$x = $foo[ $bar ]; // Correct - space for variable +``` + +**Type casts (lowercase, short form):** + +```php +$foo = (bool) $bar; // Correct +$foo = (int) $value; // Correct + +$foo = (boolean) $bar; // Incorrect - use (bool) +$foo = (integer) $value; // Incorrect - use (int) +``` + +**Increment/decrement (no space):** + +```php +for ( $i = 0; $i < 10; $i++ ) {} +++$b; +``` + +--- + +## Indentation + +Use real tabs, not spaces. Spaces may be used mid-line for alignment. + +**Correct:** + +```php +$foo = 'somevalue'; +$foo2 = 'somevalue2'; +$foo34 = 'somevalue3'; +``` + +**Associative arrays (one item per line when more than one):** + +```php +// Single item - can be one line +$query = new WP_Query( array( 'ID' => 123 ) ); + +// Multiple items - each on own line +$args = array( + 'post_type' => 'page', + 'post_author' => 123, + 'post_status' => 'publish', +); +``` + +**Switch statements:** + +```php +switch ( $type ) { + case 'foo': + some_function(); + break; + + case 'bar': + some_function(); + break; +} +``` + +--- + +## Trailing Spaces + +Remove trailing whitespace at end of lines. Omit closing PHP tag at end of file (preferred). No trailing blank lines at end of function body. From ffaef2f5ec762ec0203f8c84fc7b359432044ad4 Mon Sep 17 00:00:00 2001 From: Sherv Date: Wed, 28 Jan 2026 19:22:39 +0300 Subject: [PATCH 10/90] Add comprehensive WordPress JavaScript coding standards documentation for AI agents Add detailed WordPress JavaScript coding standards guide covering spacing, indentation, variables/naming, equality checks, syntax rules, and best practices. Document includes priority levels (HIGH/MEDIUM/LOW) for each section and extensive code examples showing correct vs incorrect patterns. Intended as reference for AI agents and LLMs maintaining WordPress JavaScript code. --- .../AGENTS.md | 677 ++++++++++++++++++ .../SKILL.md | 83 +++ 2 files changed, 760 insertions(+) create mode 100644 .windsurf/skills/wordpress-javascript-coding-standards/AGENTS.md create mode 100644 .windsurf/skills/wordpress-javascript-coding-standards/SKILL.md diff --git a/.windsurf/skills/wordpress-javascript-coding-standards/AGENTS.md b/.windsurf/skills/wordpress-javascript-coding-standards/AGENTS.md new file mode 100644 index 0000000000..3e904da91f --- /dev/null +++ b/.windsurf/skills/wordpress-javascript-coding-standards/AGENTS.md @@ -0,0 +1,677 @@ +# WordPress JavaScript Coding Standards + +**Version 1.0.0** +Based on WordPress Core Official Standards + +> **Note:** +> This document is for AI agents and LLMs to follow when maintaining, +> generating, or refactoring JavaScript code in the WordPress ecosystem. + +--- + +## Abstract + +JavaScript has become a critical component in developing WordPress-based applications (themes and plugins) as well as WordPress core. These standards ensure consistency, readability, and maintainability. + +--- + +## Table of Contents + +1. [Spacing](#1-spacing) — **HIGH** +2. [Indentation and Line Breaks](#2-indentation-and-line-breaks) — **HIGH** +3. [Variables and Naming](#3-variables-and-naming) — **MEDIUM** +4. [Equality and Type Checks](#4-equality-and-type-checks) — **MEDIUM** +5. [Syntax Rules](#5-syntax-rules) — **MEDIUM** +6. [Best Practices](#6-best-practices) — **LOW** + +--- + +## 1. Spacing + +**Impact: HIGH** + +Use spaces liberally for improved readability. Minification handles optimization. + +### 1.1 General Rules + +- Indentation with tabs +- No whitespace at end of line or on blank lines +- Lines should usually be no longer than 80 characters, max 100 +- `if`/`else`/`for`/`while`/`try` blocks always use braces and multiple lines +- Unary operators (`++`, `--`) must not have space next to operand +- `,` and `;` must not have preceding space +- `:` after property name must not have preceding space +- `?` and `:` in ternary must have space on both sides +- No filler spaces in empty constructs (`{}`, `[]`, `fn()`) +- New line at end of each file +- `!` negation operator should have following space + +### 1.2 Object Declarations + +**Incorrect:** + +```javascript +var obj = { ready: 9, when: 4, "you are": 15 }; +``` + +**Correct (multiline preferred):** + +```javascript +var obj = { + ready: 9, + when: 4, + "you are": 15, +}; +``` + +**Acceptable for small objects:** + +```javascript +var obj = { ready: 9, when: 4, "you are": 15 }; +``` + +### 1.3 Arrays and Function Calls + +Always include extra spaces around elements and arguments. + +**Correct:** + +```javascript +array = [a, b]; + +foo(arg); +foo("string", object); +foo(options, object[property]); +foo(node, "property", 2); + +prop = object["default"]; +firstArrayElement = arr[0]; +``` + +### 1.4 Control Structures + +**Correct:** + +```javascript +var i; + +if (condition) { + doSomething("with a string"); +} else if (otherCondition) { + otherThing({ + key: value, + otherKey: otherValue, + }); +} else { + somethingElse(true); +} + +// Space after ! negation (differs from jQuery) +while (!condition) { + iterating++; +} + +for (i = 0; i < 100; i++) { + object[array[i]] = someFn(i); + $(".container").val(array[i]); +} + +try { + // Expressions +} catch (e) { + // Expressions +} +``` + +--- + +## 2. Indentation and Line Breaks + +**Impact: HIGH** + +### 2.1 Tab Indentation + +Use tabs for indentation, even inside closures. + +**Correct:** + +```javascript +(function ($) { + // Expressions indented + + function doSomething() { + // Expressions indented + } +})(jQuery); +``` + +### 2.2 Blocks and Curly Braces + +Opening brace on same line. Closing brace on line after last statement. + +**Correct:** + +```javascript +var a, b, c; + +if (myFunction()) { + // Expressions +} else if ((a && b) || c) { + // Expressions +} else { + // Expressions +} +``` + +### 2.3 Multi-line Statements + +Line breaks must occur after an operator. + +**Incorrect:** + +```javascript +var html = + "

The sum of " + + a + + " and " + + b + + " plus " + + c + + " is " + + (a + b + c) + + "

"; +``` + +**Correct:** + +```javascript +var html = + "

The sum of " + + a + + " and " + + b + + " plus " + + c + + " is " + + (a + b + c) + + "

"; +``` + +**Ternary on multiple lines:** + +```javascript +var baz = firstCondition(foo) && secondCondition(bar) ? qux(foo, bar) : foo; +``` + +**Long conditionals:** + +```javascript +if (firstCondition() && secondCondition() && thirdCondition()) { + doStuff(); +} +``` + +### 2.4 Chained Method Calls + +One call per line. Extra indent when context changes. + +**Correct:** + +```javascript +elements.addClass("foo").children().html("hello").end().appendTo("body"); +``` + +--- + +## 3. Variables and Naming + +**Impact: MEDIUM** + +### 3.1 Variable Declarations + +Use `const` and `let` (ES6+). Avoid `var` in modern code. + +**Correct:** + +```javascript +const myName = "WordPress"; +let counter = 0; +``` + +**Legacy (when var is required):** + +```javascript +var myName = "WordPress"; +``` + +### 3.2 Naming Conventions + +Use camelCase with lowercase first letter. This differs from WordPress PHP standards. + +**Incorrect:** + +```javascript +var some_variable = "value"; +var SomeVariable = "value"; +``` + +**Correct:** + +```javascript +var someVariable = "value"; + +function myFunction() {} + +// Iterators allowed as single letters +for (var i = 0; i < 10; i++) {} +``` + +### 3.3 Abbreviations and Acronyms + +Treat as words in camelCase. + +**Correct:** + +```javascript +var defined; +var htmlContent; +var jsonData; +var xmlParser; +``` + +### 3.4 Class Definitions + +Use UpperCamelCase. + +**Correct:** + +```javascript +class MyClass { + constructor() {} +} +``` + +### 3.5 Constants + +Use SCREAMING_SNAKE_CASE. + +**Correct:** + +```javascript +const MAX_ITEMS = 100; +const API_URL = "https://api.example.com"; +``` + +### 3.6 Globals + +Avoid globals. If unavoidable, set them explicitly via `window`. + +**Correct:** + +```javascript +window.myGlobal = "value"; +``` + +**Document globals at top of file:** + +```javascript +/* global passwordStrength:true */ +``` + +- `:true` means the global is being defined in this file +- Omit `:true` for read-only globals defined elsewhere + +### 3.7 Common Libraries + +Backbone, jQuery, Underscore, and `wp` are registered globals in WordPress. + +**jQuery access pattern:** + +```javascript +(function ($) { + // Use $ safely here +})(jQuery); +``` + +**Extending wp object safely:** + +```javascript +// At the top of the file +window.wp = window.wp || {}; +``` + +--- + +## 4. Equality and Type Checks + +**Impact: MEDIUM** + +### 4.1 Strict Equality + +Always use `===` and `!==`. + +**Incorrect:** + +```javascript +if (foo == bar) { +} +if (foo != bar) { +} +``` + +**Correct:** + +```javascript +if (foo === bar) { +} +if (foo !== bar) { +} +``` + +### 4.2 Type Checks + +**String:** + +```javascript +typeof object === "string"; +``` + +**Number:** + +```javascript +typeof object === "number"; +``` + +**Boolean:** + +```javascript +typeof object === "boolean"; +``` + +**Object:** + +```javascript +typeof object === "object"; +// or +_.isObject(object); +``` + +**Plain Object:** + +```javascript +jQuery.isPlainObject(object); +``` + +**Function:** + +```javascript +_.isFunction(object); +// or +jQuery.isFunction(object); +``` + +**Array:** + +```javascript +_.isArray(object); +// or +jQuery.isArray(object); +``` + +**Element:** + +```javascript +object.nodeType; +// or +_.isElement(object); +``` + +**null:** + +```javascript +object === null; +``` + +**null or undefined:** + +```javascript +object == null; +``` + +**undefined:** + +```javascript +// Global Variables +typeof variable === "undefined"; + +// Local Variables +variable === undefined; + +// Properties +object.prop === undefined; + +// Any of the above +_.isUndefined(object); +``` + +--- + +## 5. Syntax Rules + +**Impact: MEDIUM** + +### 5.1 Semicolons + +Always use them. Never rely on Automatic Semicolon Insertion (ASI). + +**Incorrect:** + +```javascript +var foo = "bar"; +function myFunc() {} +``` + +**Correct:** + +```javascript +var foo = "bar"; +function myFunc() {} +``` + +### 5.2 Strings + +Use single quotes for string literals. + +**Incorrect:** + +```javascript +var myStr = "strings should use single quotes"; +``` + +**Correct:** + +```javascript +var myStr = "strings should be contained in single quotes"; +``` + +**Escaping:** + +```javascript +var escaped = "Note the backslash before the 'single quotes'"; +``` + +### 5.3 Switch Statements + +Use `break` for each case except `default`. Indent `case` one tab from `switch`. + +**Correct:** + +```javascript +switch (event.keyCode) { + // ENTER and SPACE both trigger x() + case $.ui.keyCode.ENTER: + case $.ui.keyCode.SPACE: + x(); + break; + + case $.ui.keyCode.ESCAPE: + y(); + break; + + default: + z(); +} +``` + +**Return values (set in cases, return at end):** + +```javascript +function getKeyCode(keyCode) { + var result; + + switch (event.keyCode) { + case $.ui.keyCode.ENTER: + case $.ui.keyCode.SPACE: + result = "commit"; + break; + + case $.ui.keyCode.ESCAPE: + result = "exit"; + break; + + default: + result = "default"; + } + + return result; +} +``` + +--- + +## 6. Best Practices + +**Impact: LOW** + +### 6.1 Comments + +Comments come before the code. Preceded by blank line. Single space after `//`. + +**Correct:** + +```javascript +someStatement(); + +// Explanation of something complex on the next line +$("p").doSomething(); + +// This is a comment that is long enough to warrant being stretched +// over the span of multiple lines. +``` + +**JSDoc format for documentation:** + +```javascript +/** + * Function description. + * + * @param {string} param1 - Description. + * @return {boolean} Description. + */ +function myFunction(param1) { + return true; +} +``` + +**Inline comments for special arguments:** + +```javascript +function foo(types, selector, data, fn, /* INTERNAL */ one) { + // Do stuff +} +``` + +### 6.2 Arrays + +Create using `[]` not `new Array()`. + +**Correct:** + +```javascript +var myArray = []; +var myArray = [1, 2, 3]; +``` + +### 6.3 Objects + +Create using `{}` not `new Object()`. + +**Correct:** + +```javascript +var myObject = {}; +var myObject = { key: "value" }; +``` + +### 6.4 Iteration + +Use `_.each()` or `jQuery.each()` for collections. + +**Correct:** + +```javascript +_.each(myArray, function (value, index) { + // Process value +}); + +$(".items").each(function () { + // Process element +}); +``` + +### 6.5 Code Refactoring + +Don't refactor just for style. "Whitespace-only" patches are discouraged. + +> "Code refactoring should not be done just because we can." – Andrew Nacin + +--- + +## JSHint Configuration + +Standard WordPress JSHint settings: + +```json +{ + "boss": true, + "curly": true, + "eqeqeq": true, + "eqnull": true, + "es3": true, + "expr": true, + "immed": true, + "noarg": true, + "nonbsp": true, + "onevar": true, + "quotmark": "single", + "trailing": true, + "undef": true, + "unused": true, + "browser": true, + "globals": { + "_": false, + "Backbone": false, + "jQuery": false, + "JSON": false, + "wp": false + } +} +``` + +**Ignore blocks when needed:** + +```javascript +/* jshint ignore:start */ +code_that_should_not_be_linted; +/* jshint ignore:end */ +``` diff --git a/.windsurf/skills/wordpress-javascript-coding-standards/SKILL.md b/.windsurf/skills/wordpress-javascript-coding-standards/SKILL.md new file mode 100644 index 0000000000..4155596c0b --- /dev/null +++ b/.windsurf/skills/wordpress-javascript-coding-standards/SKILL.md @@ -0,0 +1,83 @@ +# WordPress JavaScript Coding Standards + +**Version 1.0.0** +Based on WordPress Core Official Standards + +> **Note:** +> This document is for AI agents and LLMs to follow when maintaining, +> generating, or refactoring JavaScript code in the WordPress ecosystem. + +--- + +## Overview + +JavaScript has become a critical component in WordPress development. These standards ensure consistency and readability across WordPress Core, themes, and plugins. + +**When to apply:** Any JavaScript file in WordPress Core, plugins, or themes. + +**Reference:** [WordPress JavaScript Coding Standards](https://developer.wordpress.org/coding-standards/wordpress-coding-standards/javascript/) + +--- + +## Rule Categories by Priority + +### 1. Spacing — **HIGH** + +| Rule | Impact | +| --------------------------- | ----------- | +| [Spacing](rules/spacing.md) | Readability | + +### 2. Indentation & Line Breaks — **HIGH** + +| Rule | Impact | +| ----------------------------------- | -------------- | +| [Indentation](rules/indentation.md) | Code structure | + +### 3. Variables & Naming — **MEDIUM** + +| Rule | Impact | +| ------------------------------- | ----------- | +| [Variables](rules/variables.md) | Consistency | + +### 4. Equality & Type Checks — **MEDIUM** + +| Rule | Impact | +| ----------------------------- | -------------- | +| [Equality](rules/equality.md) | Bug prevention | + +### 5. Syntax Rules — **MEDIUM** + +| Rule | Impact | +| ------------------------- | -------------- | +| [Syntax](rules/syntax.md) | ASI prevention | + +### 6. Best Practices — **LOW** + +| Rule | Impact | +| ----------------------------------------- | ------------- | +| [Best Practices](rules/best-practices.md) | Documentation | + +--- + +## Usage + +### Load all rules + +```text +@wordpress-javascript-coding-standards +``` + +### Full compiled guide + +See [AGENTS.md](AGENTS.md) for the complete reference. + +--- + +## Tooling + +Use JSHint with WordPress configuration to check code compliance: + +```bash +npm install -g jshint +jshint --config .jshintrc path/to/file.js +``` From e421816adb740b9c1875000732663fb86fc46185 Mon Sep 17 00:00:00 2001 From: Sherv Date: Wed, 28 Jan 2026 19:22:58 +0300 Subject: [PATCH 11/90] Add modular WordPress JavaScript coding standards rules split into focused files Split comprehensive WordPress JavaScript coding standards into separate focused rule files covering spacing, indentation, equality checks, syntax rules, variables/naming, and best practices. Each file includes priority level (HIGH/MEDIUM/LOW), impact description, and extensive code examples showing correct vs incorrect patterns. Intended as modular reference for AI agents maintaining WordPress JavaScript code. --- .../rules/best-practices.md | 137 ++++++++++++++++++ .../rules/equality.md | 116 +++++++++++++++ .../rules/indentation.md | 104 +++++++++++++ .../rules/spacing.md | 103 +++++++++++++ .../rules/syntax.md | 97 +++++++++++++ .../rules/variables.md | 101 +++++++++++++ 6 files changed, 658 insertions(+) create mode 100644 .windsurf/skills/wordpress-javascript-coding-standards/rules/best-practices.md create mode 100644 .windsurf/skills/wordpress-javascript-coding-standards/rules/equality.md create mode 100644 .windsurf/skills/wordpress-javascript-coding-standards/rules/indentation.md create mode 100644 .windsurf/skills/wordpress-javascript-coding-standards/rules/spacing.md create mode 100644 .windsurf/skills/wordpress-javascript-coding-standards/rules/syntax.md create mode 100644 .windsurf/skills/wordpress-javascript-coding-standards/rules/variables.md diff --git a/.windsurf/skills/wordpress-javascript-coding-standards/rules/best-practices.md b/.windsurf/skills/wordpress-javascript-coding-standards/rules/best-practices.md new file mode 100644 index 0000000000..8d8c3e1c6c --- /dev/null +++ b/.windsurf/skills/wordpress-javascript-coding-standards/rules/best-practices.md @@ -0,0 +1,137 @@ +# Best Practices + +**Priority: LOW** +**Impact: Documentation and performance** + +--- + +## Comments + +Comments come before the code. Preceded by blank line. Single space after `//`. + +**Correct:** + +```javascript +someStatement(); + +// Explanation of something complex on the next line +$("p").doSomething(); + +// This is a comment that is long enough to warrant being stretched +// over the span of multiple lines. +``` + +**JSDoc format for documentation:** + +```javascript +/** + * Function description. + * + * @param {string} param1 - Description. + * @return {boolean} Description. + */ +function myFunction(param1) { + return true; +} +``` + +**Inline comments for special arguments:** + +```javascript +function foo(types, selector, data, fn, /* INTERNAL */ one) { + // Do stuff +} +``` + +--- + +## Arrays + +Create using `[]` not `new Array()`. + +**Correct:** + +```javascript +var myArray = []; +var myArray = [1, 2, 3]; +``` + +--- + +## Objects + +Create using `{}` not `new Object()`. + +**Correct:** + +```javascript +var myObject = {}; +var myObject = { key: "value" }; +``` + +--- + +## Iteration + +Use `_.each()` or `jQuery.each()` for collections. + +**Correct:** + +```javascript +_.each(myArray, function (value, index) { + // Process value +}); + +$(".items").each(function () { + // Process element +}); +``` + +--- + +## Code Refactoring + +Don't refactor just for style. "Whitespace-only" patches are discouraged. + +> "Code refactoring should not be done just because we can." – Andrew Nacin + +--- + +## JSHint Configuration + +Standard WordPress JSHint settings: + +```json +{ + "boss": true, + "curly": true, + "eqeqeq": true, + "eqnull": true, + "es3": true, + "expr": true, + "immed": true, + "noarg": true, + "nonbsp": true, + "onevar": true, + "quotmark": "single", + "trailing": true, + "undef": true, + "unused": true, + "browser": true, + "globals": { + "_": false, + "Backbone": false, + "jQuery": false, + "JSON": false, + "wp": false + } +} +``` + +**Ignore blocks when needed:** + +```javascript +/* jshint ignore:start */ +code_that_should_not_be_linted; +/* jshint ignore:end */ +``` diff --git a/.windsurf/skills/wordpress-javascript-coding-standards/rules/equality.md b/.windsurf/skills/wordpress-javascript-coding-standards/rules/equality.md new file mode 100644 index 0000000000..a5170c4825 --- /dev/null +++ b/.windsurf/skills/wordpress-javascript-coding-standards/rules/equality.md @@ -0,0 +1,116 @@ +# Equality and Type Checks + +**Priority: MEDIUM** +**Impact: Bug prevention and reliability** + +--- + +## Strict Equality + +Always use `===` and `!==`. + +**Incorrect:** + +```javascript +if (foo == bar) { +} +if (foo != bar) { +} +``` + +**Correct:** + +```javascript +if (foo === bar) { +} +if (foo !== bar) { +} +``` + +--- + +## Type Checks + +**String:** + +```javascript +typeof object === "string"; +``` + +**Number:** + +```javascript +typeof object === "number"; +``` + +**Boolean:** + +```javascript +typeof object === "boolean"; +``` + +**Object:** + +```javascript +typeof object === "object"; +// or +_.isObject(object); +``` + +**Plain Object:** + +```javascript +jQuery.isPlainObject(object); +``` + +**Function:** + +```javascript +_.isFunction(object); +// or +jQuery.isFunction(object); +``` + +**Array:** + +```javascript +_.isArray(object); +// or +jQuery.isArray(object); +``` + +**Element:** + +```javascript +object.nodeType; +// or +_.isElement(object); +``` + +**null:** + +```javascript +object === null; +``` + +**null or undefined:** + +```javascript +object == null; +``` + +**undefined:** + +```javascript +// Global Variables +typeof variable === "undefined"; + +// Local Variables +variable === undefined; + +// Properties +object.prop === undefined; + +// Any of the above +_.isUndefined(object); +``` diff --git a/.windsurf/skills/wordpress-javascript-coding-standards/rules/indentation.md b/.windsurf/skills/wordpress-javascript-coding-standards/rules/indentation.md new file mode 100644 index 0000000000..dc4bb26722 --- /dev/null +++ b/.windsurf/skills/wordpress-javascript-coding-standards/rules/indentation.md @@ -0,0 +1,104 @@ +# Indentation and Line Breaks + +**Priority: HIGH** +**Impact: Code structure and consistency** + +--- + +## Tab Indentation + +Use tabs for indentation, even inside closures. + +**Correct:** + +```javascript +(function ($) { + // Expressions indented + + function doSomething() { + // Expressions indented + } +})(jQuery); +``` + +--- + +## Blocks and Curly Braces + +Opening brace on same line. Closing brace on line after last statement. + +**Correct:** + +```javascript +var a, b, c; + +if (myFunction()) { + // Expressions +} else if ((a && b) || c) { + // Expressions +} else { + // Expressions +} +``` + +--- + +## Multi-line Statements + +Line breaks must occur after an operator. + +**Incorrect:** + +```javascript +var html = + "

The sum of " + + a + + " and " + + b + + " plus " + + c + + " is " + + (a + b + c) + + "

"; +``` + +**Correct:** + +```javascript +var html = + "

The sum of " + + a + + " and " + + b + + " plus " + + c + + " is " + + (a + b + c) + + "

"; +``` + +**Ternary on multiple lines:** + +```javascript +var baz = firstCondition(foo) && secondCondition(bar) ? qux(foo, bar) : foo; +``` + +**Long conditionals:** + +```javascript +if (firstCondition() && secondCondition() && thirdCondition()) { + doStuff(); +} +``` + +--- + +## Chained Method Calls + +One call per line. Extra indent when context changes. + +**Correct:** + +```javascript +elements.addClass("foo").children().html("hello").end().appendTo("body"); +``` diff --git a/.windsurf/skills/wordpress-javascript-coding-standards/rules/spacing.md b/.windsurf/skills/wordpress-javascript-coding-standards/rules/spacing.md new file mode 100644 index 0000000000..2b5008d0a4 --- /dev/null +++ b/.windsurf/skills/wordpress-javascript-coding-standards/rules/spacing.md @@ -0,0 +1,103 @@ +# Spacing + +**Priority: HIGH** +**Impact: Readability** + +--- + +## General Rules + +- Indentation with tabs +- No whitespace at end of line or on blank lines +- Lines should usually be no longer than 80 characters, max 100 +- `if`/`else`/`for`/`while`/`try` blocks always use braces and multiple lines +- Unary operators (`++`, `--`) must not have space next to operand +- `,` and `;` must not have preceding space +- `:` after property name must not have preceding space +- `?` and `:` in ternary must have space on both sides +- No filler spaces in empty constructs (`{}`, `[]`, `fn()`) +- New line at end of each file +- `!` negation operator should have following space + +--- + +## Object Declarations + +**Incorrect:** + +```javascript +var obj = { ready: 9, when: 4, "you are": 15 }; +``` + +**Correct (multiline preferred):** + +```javascript +var obj = { + ready: 9, + when: 4, + "you are": 15, +}; +``` + +**Acceptable for small objects:** + +```javascript +var obj = { ready: 9, when: 4, "you are": 15 }; +``` + +--- + +## Arrays and Function Calls + +Always include extra spaces around elements and arguments. + +**Correct:** + +```javascript +array = [a, b]; + +foo(arg); +foo("string", object); +foo(options, object[property]); +foo(node, "property", 2); + +prop = object["default"]; +firstArrayElement = arr[0]; +``` + +--- + +## Control Structures + +**Correct:** + +```javascript +var i; + +if (condition) { + doSomething("with a string"); +} else if (otherCondition) { + otherThing({ + key: value, + otherKey: otherValue, + }); +} else { + somethingElse(true); +} + +// Space after ! negation (differs from jQuery) +while (!condition) { + iterating++; +} + +for (i = 0; i < 100; i++) { + object[array[i]] = someFn(i); + $(".container").val(array[i]); +} + +try { + // Expressions +} catch (e) { + // Expressions +} +``` diff --git a/.windsurf/skills/wordpress-javascript-coding-standards/rules/syntax.md b/.windsurf/skills/wordpress-javascript-coding-standards/rules/syntax.md new file mode 100644 index 0000000000..79f5ed1ebf --- /dev/null +++ b/.windsurf/skills/wordpress-javascript-coding-standards/rules/syntax.md @@ -0,0 +1,97 @@ +# Syntax Rules + +**Priority: MEDIUM** +**Impact: Consistency and ASI prevention** + +--- + +## Semicolons + +Always use them. Never rely on Automatic Semicolon Insertion (ASI). + +**Incorrect:** + +```javascript +var foo = "bar"; +function myFunc() {} +``` + +**Correct:** + +```javascript +var foo = "bar"; +function myFunc() {} +``` + +--- + +## Strings + +Use single quotes for string literals. + +**Incorrect:** + +```javascript +var myStr = "strings should use single quotes"; +``` + +**Correct:** + +```javascript +var myStr = "strings should be contained in single quotes"; +``` + +**Escaping:** + +```javascript +var escaped = "Note the backslash before the 'single quotes'"; +``` + +--- + +## Switch Statements + +Use `break` for each case except `default`. Indent `case` one tab from `switch`. + +**Correct:** + +```javascript +switch (event.keyCode) { + // ENTER and SPACE both trigger x() + case $.ui.keyCode.ENTER: + case $.ui.keyCode.SPACE: + x(); + break; + + case $.ui.keyCode.ESCAPE: + y(); + break; + + default: + z(); +} +``` + +**Return values (set in cases, return at end):** + +```javascript +function getKeyCode(keyCode) { + var result; + + switch (event.keyCode) { + case $.ui.keyCode.ENTER: + case $.ui.keyCode.SPACE: + result = "commit"; + break; + + case $.ui.keyCode.ESCAPE: + result = "exit"; + break; + + default: + result = "default"; + } + + return result; +} +``` diff --git a/.windsurf/skills/wordpress-javascript-coding-standards/rules/variables.md b/.windsurf/skills/wordpress-javascript-coding-standards/rules/variables.md new file mode 100644 index 0000000000..ac70f7a623 --- /dev/null +++ b/.windsurf/skills/wordpress-javascript-coding-standards/rules/variables.md @@ -0,0 +1,101 @@ +# Variables and Naming + +**Priority: MEDIUM** +**Impact: Consistency and modern JS** + +--- + +## Variable Declarations + +Use `const` and `let` (ES6+). Avoid `var` in modern code. + +**Correct:** + +```javascript +const myName = "WordPress"; +let counter = 0; +``` + +**Legacy (when var is required):** + +```javascript +var myName = "WordPress"; +``` + +--- + +## Naming Conventions + +Use camelCase with lowercase first letter. This differs from WordPress PHP standards. + +**Incorrect:** + +```javascript +var some_variable = "value"; +var SomeVariable = "value"; +``` + +**Correct:** + +```javascript +var someVariable = "value"; + +function myFunction() {} + +// Iterators allowed as single letters +for (var i = 0; i < 10; i++) {} +``` + +--- + +## Abbreviations and Acronyms + +Treat as words in camelCase. + +**Correct:** + +```javascript +var defined; +var htmlContent; +var jsonData; +var xmlParser; +``` + +--- + +## Class Definitions + +Use UpperCamelCase. + +**Correct:** + +```javascript +class MyClass { + constructor() {} +} +``` + +--- + +## Constants + +Use SCREAMING_SNAKE_CASE. + +**Correct:** + +```javascript +const MAX_ITEMS = 100; +const API_URL = "https://api.example.com"; +``` + +--- + +## Globals + +Avoid globals. If unavoidable, set them explicitly via `window`. + +**Correct:** + +```javascript +window.myGlobal = "value"; +``` From 50fd03428869c6d79d9e73dd64d0c61fec1b4c6f Mon Sep 17 00:00:00 2001 From: Sherv Date: Wed, 28 Jan 2026 19:23:10 +0300 Subject: [PATCH 12/90] Add comprehensive WordPress HTML coding standards documentation for AI agents Add detailed WordPress HTML coding standards guide covering validation, self-closing elements, attributes/tags, quotes, and indentation. Document includes priority levels (HIGH/MEDIUM) for each section and extensive code examples showing correct vs incorrect patterns. Intended as reference for AI agents and LLMs maintaining WordPress HTML code. --- .../wordpress-html-coding-standards/AGENTS.md | 207 ++++++++++++++++++ .../wordpress-html-coding-standards/SKILL.md | 56 +++++ 2 files changed, 263 insertions(+) create mode 100644 .windsurf/skills/wordpress-html-coding-standards/AGENTS.md create mode 100644 .windsurf/skills/wordpress-html-coding-standards/SKILL.md diff --git a/.windsurf/skills/wordpress-html-coding-standards/AGENTS.md b/.windsurf/skills/wordpress-html-coding-standards/AGENTS.md new file mode 100644 index 0000000000..08271a0ff6 --- /dev/null +++ b/.windsurf/skills/wordpress-html-coding-standards/AGENTS.md @@ -0,0 +1,207 @@ +# WordPress HTML Coding Standards + +**Version 1.0.0** +Based on WordPress Core Official Standards + +> **Note:** +> This document is for AI agents and LLMs to follow when maintaining, +> generating, or refactoring HTML code in the WordPress ecosystem. + +--- + +## Abstract + +These HTML coding standards ensure well-formed, accessible markup. Validation helps weed out problems but is no substitute for manual code review. + +--- + +## Table of Contents + +1. [Validation](#1-validation) — **HIGH** +2. [Self-closing Elements](#2-self-closing-elements) — **HIGH** +3. [Attributes and Tags](#3-attributes-and-tags) — **MEDIUM** +4. [Quotes](#4-quotes) — **MEDIUM** +5. [Indentation](#5-indentation) — **MEDIUM** + +--- + +## 1. Validation + +**Impact: HIGH** + +All HTML should be verified against the W3C validator to ensure well-formed markup. + +**Tools:** + +- [W3C Markup Validation Service](https://validator.w3.org/) +- Browser developer tools + +**Note:** Validation alone doesn't guarantee good code—manual review is essential. + +--- + +## 2. Self-closing Elements + +**Impact: HIGH** + +All tags must be properly closed. Self-closing tags need exactly one space before the slash. + +**Incorrect:** + +```html +
+ + +``` + +**Correct:** + +```html +
+ + +``` + +The W3C specifies that a single space should precede the self-closing slash. + +--- + +## 3. Attributes and Tags + +**Impact: MEDIUM** + +### 3.1 Lowercase + +All tags and attributes must be lowercase. + +**Incorrect:** + +```html +
+ +
+``` + +**Correct:** + +```html +
+ +
+``` + +### 3.2 Attribute Values + +Lowercase for machine-interpreted values. Title case for human-readable values. + +**For machines:** + +```html + +``` + +**For humans:** + +```html +Example.com +``` + +--- + +## 4. Quotes + +**Impact: MEDIUM** + +### 4.1 Always Quote Attributes + +Use double or single quotes. Never omit quotes—it can cause security vulnerabilities. + +**Incorrect:** + +```html + +``` + +**Correct:** + +```html + +``` + +Or with single quotes: + +```html + +``` + +### 4.2 Boolean Attributes + +You may omit the value on boolean attributes, but never use `true` or `false`. + +**Incorrect:** + +```html + + +``` + +**Correct:** + +```html + + +``` + +--- + +## 5. Indentation + +**Impact: MEDIUM** + +### 5.1 Use Tabs + +HTML indentation should reflect logical structure. Use tabs, not spaces. + +### 5.2 PHP in HTML + +Indent PHP blocks to match surrounding HTML. Closing PHP blocks should match opening block indentation. + +**Incorrect:** + +```php + +
+

Not Found

+
+

Apologies, but no results were found.

+ +
+
+ +``` + +**Correct:** + +```php + +
+

Not Found

+
+

Apologies, but no results were found.

+ +
+
+ +``` + +--- + +## Best Practices Summary + +1. **Validate markup** — Use W3C validator +2. **Close all tags** — Including self-closing with space before slash +3. **Lowercase everything** — Tags, attributes, machine values +4. **Quote all attributes** — Double quotes preferred +5. **Boolean attributes** — Omit value or repeat attribute name, never `true`/`false` +6. **Use tabs** — Reflect logical structure +7. **Align PHP with HTML** — Maintain consistent indentation diff --git a/.windsurf/skills/wordpress-html-coding-standards/SKILL.md b/.windsurf/skills/wordpress-html-coding-standards/SKILL.md new file mode 100644 index 0000000000..fdf9205911 --- /dev/null +++ b/.windsurf/skills/wordpress-html-coding-standards/SKILL.md @@ -0,0 +1,56 @@ +# WordPress HTML Coding Standards + +**Version 1.0.0** +Based on WordPress Core Official Standards + +> **Note:** +> This document is for AI agents and LLMs to follow when maintaining, +> generating, or refactoring HTML code in the WordPress ecosystem. + +--- + +## Overview + +These HTML coding standards ensure well-formed, accessible markup across the WordPress ecosystem. + +**When to apply:** Any HTML in WordPress Core, plugins, themes, or templates. + +**Reference:** [WordPress HTML Coding Standards](https://developer.wordpress.org/coding-standards/wordpress-coding-standards/html/) + +--- + +## Rule Categories by Priority + +### 1. Validation — **HIGH** + +| Rule | Impact | +| --------------------------------- | ---------- | +| [Validation](rules/validation.md) | Compliance | + +### 2. Self-closing Elements — **HIGH** + +| Rule | Impact | +| ------------------------------------------------------- | ----------- | +| [Self-closing Elements](rules/self-closing-elements.md) | Consistency | + +### 3. Attributes — **MEDIUM** + +| Rule | Impact | +| --------------------------------- | ----------- | +| [Attributes](rules/attributes.md) | Reliability | + +### 4. Indentation — **MEDIUM** + +| Rule | Impact | +| ----------------------------------- | ----------- | +| [Indentation](rules/indentation.md) | Readability | + +--- + +## Usage + +```text +@wordpress-html-coding-standards +``` + +See [AGENTS.md](AGENTS.md) for the complete reference. From d55dc694a95d5445d0be9ea71e8e7da63aa38b1d Mon Sep 17 00:00:00 2001 From: Sherv Date: Wed, 28 Jan 2026 19:23:18 +0300 Subject: [PATCH 13/90] Add modular WordPress HTML coding standards rules split into focused files Split comprehensive WordPress HTML coding standards into separate focused rule files covering validation, self-closing elements, attributes/quotes, and indentation. Each file includes priority level (HIGH/MEDIUM), impact description, and extensive code examples showing correct vs incorrect patterns. Intended as modular reference for AI agents maintaining WordPress HTML code. --- .../rules/attributes.md | 89 +++++++++++++++ .../rules/indentation.md | 105 ++++++++++++++++++ .../rules/self-closing-elements.md | 57 ++++++++++ .../rules/validation.md | 47 ++++++++ 4 files changed, 298 insertions(+) create mode 100644 .windsurf/skills/wordpress-html-coding-standards/rules/attributes.md create mode 100644 .windsurf/skills/wordpress-html-coding-standards/rules/indentation.md create mode 100644 .windsurf/skills/wordpress-html-coding-standards/rules/self-closing-elements.md create mode 100644 .windsurf/skills/wordpress-html-coding-standards/rules/validation.md diff --git a/.windsurf/skills/wordpress-html-coding-standards/rules/attributes.md b/.windsurf/skills/wordpress-html-coding-standards/rules/attributes.md new file mode 100644 index 0000000000..5b62cbe0a6 --- /dev/null +++ b/.windsurf/skills/wordpress-html-coding-standards/rules/attributes.md @@ -0,0 +1,89 @@ +# Attributes and Quotes + +**Priority: HIGH** +**Impact: Parsing reliability and consistency** + +--- + +## Quote Usage + +Always use double quotes around attribute values, never single quotes or no quotes. + +**Incorrect:** + +```html +Link +Link + +
+``` + +**Correct:** + +```html +Link + +
+``` + +--- + +## Boolean Attributes + +Boolean attributes should not have a value assigned. The presence of the attribute implies `true`. + +**Incorrect:** + +```html + + + + + +``` + +--- + +## Custom Data Attributes + +Use `data-*` attributes for custom data. Use lowercase with hyphens. + +**Correct:** + +```html +
+ Content +
+``` diff --git a/.windsurf/skills/wordpress-html-coding-standards/rules/indentation.md b/.windsurf/skills/wordpress-html-coding-standards/rules/indentation.md new file mode 100644 index 0000000000..81c6c67939 --- /dev/null +++ b/.windsurf/skills/wordpress-html-coding-standards/rules/indentation.md @@ -0,0 +1,105 @@ +# Indentation + +**Priority: MEDIUM** +**Impact: Readability and maintainability** + +--- + +## General Rule + +Use tabs for indentation. Nested elements should be indented once per level. + +--- + +## Basic Structure + +**Correct:** + +```html + + + + + Page Title + + +
+ +
+
+
+

Article Title

+

Article content.

+
+
+
+

© 2024

+
+ + +``` + +--- + +## Mixed HTML and PHP + +When mixing HTML and PHP (common in WordPress templates), maintain consistent indentation. + +**Correct:** + +```php + +
+ +
> +

+
+ +
+
+ +
+ +``` + +--- + +## Long Attribute Lists + +When an element has many attributes, consider breaking to multiple lines. + +**Acceptable:** + +```html + +``` + +--- + +## Inline vs Block Elements + +- **Block elements:** Always on their own line with proper indentation +- **Inline elements:** Can remain on the same line as surrounding content + +**Correct:** + +```html +

This is a paragraph with bold text and a link.

+ +
+

Block element content.

+
+``` diff --git a/.windsurf/skills/wordpress-html-coding-standards/rules/self-closing-elements.md b/.windsurf/skills/wordpress-html-coding-standards/rules/self-closing-elements.md new file mode 100644 index 0000000000..69dfda94b2 --- /dev/null +++ b/.windsurf/skills/wordpress-html-coding-standards/rules/self-closing-elements.md @@ -0,0 +1,57 @@ +# Self-Closing Elements + +**Priority: MEDIUM** +**Impact: Consistency and standards compliance** + +--- + +## Void Elements + +Void elements (self-closing) should NOT have a trailing slash in HTML5. + +**Void elements include:** + +- `
` +- `
` +- `` +- `` +- `` +- `` +- `` +- `` +- `` +- `` +- `` +- `` +- `` +- `` + +--- + +## Examples + +**Incorrect (XHTML style):** + +```html +
+
+ + + +``` + +**Correct (HTML5 style):** + +```html +
+
+Description + + +``` + +--- + +## Exception + +When working with XHTML documents or XML-based systems (like some WordPress template systems), the trailing slash may be required for valid XML parsing. diff --git a/.windsurf/skills/wordpress-html-coding-standards/rules/validation.md b/.windsurf/skills/wordpress-html-coding-standards/rules/validation.md new file mode 100644 index 0000000000..ed5f716e64 --- /dev/null +++ b/.windsurf/skills/wordpress-html-coding-standards/rules/validation.md @@ -0,0 +1,47 @@ +# Validation + +**Priority: HIGH** +**Impact: Standards compliance and cross-browser compatibility** + +--- + +## General Rule + +All HTML pages should be verified against the W3C validator to ensure markup is well-formed. + +--- + +## Validation Requirements + +- No errors in final production code +- Warnings should be reviewed and addressed where appropriate +- Use appropriate DOCTYPE declaration + +--- + +## DOCTYPE + +Always use the HTML5 DOCTYPE. + +**Correct:** + +```html + + + + + Page Title + + + + + +``` + +--- + +## Validation Tools + +- **W3C Markup Validation Service:** +- **Browser DevTools:** Check for parsing errors in console +- **IDE/Editor plugins:** Real-time validation feedback From bee912b693f4231b000c4686b513a4e9746f5f12 Mon Sep 17 00:00:00 2001 From: Sherv Date: Wed, 28 Jan 2026 19:23:30 +0300 Subject: [PATCH 14/90] Add comprehensive WordPress CSS coding standards documentation for AI agents Add detailed WordPress CSS coding standards guide covering structure, selectors, properties, values, media queries, commenting, and best practices. Document includes priority levels (HIGH/MEDIUM/LOW) for each section and extensive code examples showing correct vs incorrect patterns. Intended as reference for AI agents and LLMs maintaining WordPress CSS code. --- .../wordpress-css-coding-standards/AGENTS.md | 578 ++++++++++++++++++ .../wordpress-css-coding-standards/SKILL.md | 74 +++ 2 files changed, 652 insertions(+) create mode 100644 .windsurf/skills/wordpress-css-coding-standards/AGENTS.md create mode 100644 .windsurf/skills/wordpress-css-coding-standards/SKILL.md diff --git a/.windsurf/skills/wordpress-css-coding-standards/AGENTS.md b/.windsurf/skills/wordpress-css-coding-standards/AGENTS.md new file mode 100644 index 0000000000..bd0008a726 --- /dev/null +++ b/.windsurf/skills/wordpress-css-coding-standards/AGENTS.md @@ -0,0 +1,578 @@ +# WordPress CSS Coding Standards + +**Version 1.0.0** +Based on WordPress Core Official Standards + +> **Note:** +> This document is for AI agents and LLMs to follow when maintaining, +> generating, or refactoring CSS code in the WordPress ecosystem. + +--- + +## Abstract + +The WordPress CSS Coding Standards create a baseline for collaboration and review within the WordPress ecosystem. The goal is to create code that is readable, meaningful, consistent, and beautiful. + +--- + +## Table of Contents + +1. [Structure](#1-structure) — **HIGH** +2. [Selectors](#2-selectors) — **HIGH** +3. [Properties](#3-properties) — **MEDIUM** +4. [Values](#4-values) — **MEDIUM** +5. [Media Queries](#5-media-queries) — **MEDIUM** +6. [Commenting](#6-commenting) — **LOW** +7. [Best Practices](#7-best-practices) — **LOW** + +--- + +## 1. Structure + +**Impact: HIGH** + +Maintain high legibility with consistent structure. + +### 1.1 Indentation + +Use tabs, not spaces. + +### 1.2 Spacing Between Blocks + +- Two blank lines between sections +- One blank line between blocks in a section + +### 1.3 Selector and Property Layout + +Each selector on its own line. Property-value pairs on their own line with one tab indentation. + +**Incorrect:** + +```css +#selector-1, +#selector-2, +#selector-3 { + background: #fff; + color: #000; +} + +#selector-1 { + background: #fff; + color: #000; +} +``` + +**Correct:** + +```css +#selector-1, +#selector-2, +#selector-3 { + background: #fff; + color: #000; +} +``` + +--- + +## 2. Selectors + +**Impact: HIGH** + +Balance efficiency with specificity. + +### 2.1 Naming Convention + +Use lowercase with hyphens. Avoid camelCase and underscores. + +**Incorrect:** + +```css +#commentForm { +} +#comment_form { +} +``` + +**Correct:** + +```css +#comment-form { +} +``` + +### 2.2 Human Readable Names + +Use descriptive names that explain what the element styles. + +**Incorrect:** + +```css +#c1-xr { +} /* What is c1-xr? */ +``` + +**Correct:** + +```css +#comment-form { +} +.post-title { +} +.sidebar-widget { +} +``` + +### 2.3 Attribute Selectors + +Use double quotes around values. + +**Incorrect:** + +```css +input[type="text"] { +} +``` + +**Correct:** + +```css +input[type="text"] { +} +``` + +### 2.4 Avoid Over-qualification + +Don't add unnecessary element qualifiers. + +**Incorrect:** + +```css +div#comment-form { +} +div.container { +} +``` + +**Correct:** + +```css +#comment-form { +} +.container { +} +``` + +--- + +## 3. Properties + +**Impact: MEDIUM** + +### 3.1 Formatting + +- Colon followed by a space +- All properties and values lowercase +- End with semicolon + +**Incorrect:** + +```css +#selector-1 { + background: #ffffff; + display: BLOCK; + margin-left: 20px; +} +``` + +**Correct:** + +```css +#selector-1 { + background: #fff; + display: block; + margin-left: 20px; +} +``` + +### 3.2 Property Ordering + +Group related properties. Recommended order: + +1. Display & Box Model +2. Positioning +3. Typography +4. Visual (colors, backgrounds) +5. Misc + +```css +.selector { + /* Display & Box Model */ + display: block; + width: 100%; + padding: 10px; + margin: 0; + + /* Positioning */ + position: relative; + top: 0; + + /* Typography */ + font-family: sans-serif; + font-size: 16px; + line-height: 1.5; + + /* Visual */ + background: #fff; + color: #333; + border: 1px solid #ccc; + + /* Misc */ + cursor: pointer; +} +``` + +### 3.3 Shorthand Properties + +Use shorthand for `background`, `border`, `font`, `list-style`, `margin`, and `padding`. + +**Incorrect:** + +```css +.selector { + margin-top: 10px; + margin-right: 20px; + margin-bottom: 10px; + margin-left: 20px; +} +``` + +**Correct:** + +```css +.selector { + margin: 10px 20px; +} +``` + +**Exception:** When overriding specific values: + +```css +.selector { + margin: 0; + margin-left: 20px; +} +``` + +### 3.4 Vendor Prefixes + +Include when necessary, standard property last. + +```css +.selector { + -webkit-transform: rotate(45deg); + -moz-transform: rotate(45deg); + -ms-transform: rotate(45deg); + transform: rotate(45deg); +} +``` + +--- + +## 4. Values + +**Impact: MEDIUM** + +### 4.1 Colors + +Use hex codes (lowercase, shorthand when possible). Use `rgba()` only when opacity needed. + +**Incorrect:** + +```css +.selector { + color: #ffffff; + background: RGB(255, 255, 255); +} +``` + +**Correct:** + +```css +.selector { + color: #fff; + background: rgba(0, 0, 0, 0.5); +} +``` + +### 4.2 Font Weights + +Use numeric values. + +**Incorrect:** + +```css +.selector { + font-weight: bold; + font-weight: normal; +} +``` + +**Correct:** + +```css +.selector { + font-weight: 700; + font-weight: 400; +} +``` + +### 4.3 Line Height + +Use unit-less values unless specific pixel value needed. + +**Incorrect:** + +```css +.selector { + line-height: 1.5em; +} +``` + +**Correct:** + +```css +.selector { + line-height: 1.5; +} +``` + +### 4.4 Zero Values + +No units on zero values (except `transition-duration`). + +**Incorrect:** + +```css +.selector { + margin: 0px 0px 20px 0px; +} +``` + +**Correct:** + +```css +.selector { + margin: 0 0 20px; +} +``` + +### 4.5 Decimal Values + +Use leading zero. + +**Incorrect:** + +```css +.selector { + opacity: 0.5; +} +``` + +**Correct:** + +```css +.selector { + opacity: 0.5; +} +``` + +### 4.6 Quotes + +Use double quotes. Required for font names with spaces and `content` property. + +**Incorrect:** + +```css +.selector { + font-family: + Times New Roman, + serif; + content: "hello"; +} +``` + +**Correct:** + +```css +.selector { + font-family: "Times New Roman", serif; + content: "hello"; +} +``` + +### 4.7 URL Values + +No quotes needed for simple URLs. + +```css +.selector { + background-image: url(images/bg.png); +} +``` + +### 4.8 Multi-part Values + +Use newlines for complex values like `box-shadow` and `text-shadow`. + +**Correct:** + +```css +.selector { + box-shadow: + 0 0 0 1px #5b9dd9, + 0 0 2px 1px rgba(30, 140, 190, 0.8); +} +``` + +--- + +## 5. Media Queries + +**Impact: MEDIUM** + +### 5.1 Placement + +Keep media queries grouped at bottom of stylesheet (or at bottom of sections for large files like `wp-admin.css`). + +### 5.2 Indentation + +Indent rule sets one level inside media query. + +**Correct:** + +```css +@media all and (max-width: 699px) and (min-width: 520px) { + .selector { + display: block; + } +} +``` + +--- + +## 6. Commenting + +**Impact: LOW** + +### 6.1 Comment Liberally + +Use `SCRIPT_DEBUG` constant and minified files in production. + +### 6.2 Table of Contents + +Use for longer stylesheets with index numbers. + +```css +/** + * Table of Contents + * + * 1.0 - Reset + * 2.0 - Typography + * 3.0 - Layout + * 4.0 - Components + * 5.0 - Media Queries + */ +``` + +### 6.3 Section Headers + +```css +/** + * 1.0 Reset + * + * Description of section, whether or not it has media queries, etc. + */ + +.selector { + float: left; +} +``` + +### 6.4 Inline Comments + +```css +/* This is a comment about this selector */ +.another-selector { + position: absolute; + top: 0 !important; /* I should explain why this is so !important */ +} +``` + +--- + +## 7. Best Practices + +**Impact: LOW** + +### 7.1 Remove Before Adding + +When fixing issues, try removing code before adding more. + +### 7.2 Avoid Magic Numbers + +Don't use arbitrary values as quick fixes. + +**Incorrect:** + +```css +.box { + margin-top: 37px; /* Why 37? */ +} +``` + +**Correct:** + +```css +.box { + margin-top: 2rem; /* Consistent spacing unit */ +} +``` + +### 7.3 Target Elements Directly + +Use classes on elements instead of parent selectors. + +**Incorrect:** + +```css +.highlight a { +} /* Selector on parent */ +``` + +**Correct:** + +```css +.highlight-link { +} /* Class on the element */ +``` + +### 7.4 Height vs Line-height + +Use `height` only for external elements (images). Use `line-height` for text flexibility. + +### 7.5 Don't Restate Defaults + +Don't declare default values. + +**Incorrect:** + +```css +div { + display: block; /* div is already block */ +} +``` + +### 7.6 WP Admin CSS + +Follow the same standards. Use `!important` sparingly and document why. diff --git a/.windsurf/skills/wordpress-css-coding-standards/SKILL.md b/.windsurf/skills/wordpress-css-coding-standards/SKILL.md new file mode 100644 index 0000000000..bdd5602db2 --- /dev/null +++ b/.windsurf/skills/wordpress-css-coding-standards/SKILL.md @@ -0,0 +1,74 @@ +# WordPress CSS Coding Standards + +**Version 1.0.0** +Based on WordPress Core Official Standards + +> **Note:** +> This document is for AI agents and LLMs to follow when maintaining, +> generating, or refactoring CSS code in the WordPress ecosystem. + +--- + +## Overview + +The WordPress CSS Coding Standards create a baseline for collaboration and review. Files should appear as though created by a single entity—readable, meaningful, consistent, and beautiful. + +**When to apply:** Any CSS file in WordPress Core, plugins, or themes. + +**Reference:** [WordPress CSS Coding Standards](https://developer.wordpress.org/coding-standards/wordpress-coding-standards/css/) + +--- + +## Rule Categories by Priority + +### 1. Structure — **HIGH** + +| Rule | Impact | +| ------------------------------- | ----------- | +| [Structure](rules/structure.md) | Readability | + +### 2. Selectors — **HIGH** + +| Rule | Impact | +| ------------------------------- | --------------- | +| [Selectors](rules/selectors.md) | Maintainability | + +### 3. Properties — **MEDIUM** + +| Rule | Impact | +| --------------------------------- | ----------- | +| [Properties](rules/properties.md) | Consistency | + +### 4. Values — **MEDIUM** + +| Rule | Impact | +| ------------------------- | ----------- | +| [Values](rules/values.md) | Consistency | + +### 5. Media Queries — **MEDIUM** + +| Rule | Impact | +| --------------------------------------- | ---------- | +| [Media Queries](rules/media-queries.md) | Responsive | + +### 6. Comments — **LOW** + +| Rule | Impact | +| ----------------------------- | ------------- | +| [Comments](rules/comments.md) | Documentation | + +### 7. Best Practices — **LOW** + +| Rule | Impact | +| ----------------------------------------- | --------------- | +| [Best Practices](rules/best-practices.md) | Maintainability | + +--- + +## Usage + +```text +@wordpress-css-coding-standards +``` + +See [AGENTS.md](AGENTS.md) for the complete reference. From 78a19d49fdcfab446840ff3300c67d061841a5d9 Mon Sep 17 00:00:00 2001 From: Sherv Date: Wed, 28 Jan 2026 19:23:40 +0300 Subject: [PATCH 15/90] Add modular WordPress CSS coding standards rules split into focused files Split comprehensive WordPress CSS coding standards into separate focused rule files covering structure, selectors, properties, values, media queries, comments, and best practices. Each file includes priority level (HIGH/MEDIUM/LOW), impact description, and extensive code examples showing correct vs incorrect patterns. Intended as modular reference for AI agents maintaining WordPress CSS code. --- .../rules/best-practices.md | 108 ++++++++++++++++++ .../rules/comments.md | 81 +++++++++++++ .../rules/media-queries.md | 48 ++++++++ .../rules/properties.md | 62 ++++++++++ .../rules/selectors.md | 65 +++++++++++ .../rules/structure.md | 63 ++++++++++ .../rules/values.md | 90 +++++++++++++++ 7 files changed, 517 insertions(+) create mode 100644 .windsurf/skills/wordpress-css-coding-standards/rules/best-practices.md create mode 100644 .windsurf/skills/wordpress-css-coding-standards/rules/comments.md create mode 100644 .windsurf/skills/wordpress-css-coding-standards/rules/media-queries.md create mode 100644 .windsurf/skills/wordpress-css-coding-standards/rules/properties.md create mode 100644 .windsurf/skills/wordpress-css-coding-standards/rules/selectors.md create mode 100644 .windsurf/skills/wordpress-css-coding-standards/rules/structure.md create mode 100644 .windsurf/skills/wordpress-css-coding-standards/rules/values.md diff --git a/.windsurf/skills/wordpress-css-coding-standards/rules/best-practices.md b/.windsurf/skills/wordpress-css-coding-standards/rules/best-practices.md new file mode 100644 index 0000000000..cb45c624a6 --- /dev/null +++ b/.windsurf/skills/wordpress-css-coding-standards/rules/best-practices.md @@ -0,0 +1,108 @@ +# Best Practices + +**Priority: MEDIUM** +**Impact: Performance and maintainability** + +--- + +## Avoid !important + +Only use when absolutely necessary. Indicates specificity problems. + +**Incorrect:** + +```css +.selector { + color: red !important; +} +``` + +**Correct:** + +```css +/* Increase specificity instead */ +.parent .selector { + color: red; +} +``` + +--- + +## Shorthand Properties + +Use shorthand when setting all values. Use longhand for partial overrides. + +**Correct:** + +```css +/* All four values */ +.selector { + margin: 10px 20px 10px 20px; +} + +/* Just top margin */ +.selector { + margin-top: 10px; +} +``` + +--- + +## Avoid Magic Numbers + +Document or use variables for non-obvious values. + +**Incorrect:** + +```css +.selector { + top: 37px; +} +``` + +**Correct:** + +```css +.selector { + /* header height (32px) + spacing (5px) */ + top: 37px; +} +``` + +--- + +## Performance + +- Avoid universal selectors (`*`) +- Avoid deeply nested selectors (max 3 levels) +- Minimize redundancy +- Group common declarations + +**Incorrect:** + +```css +body * { box-sizing: border-box; } +.nav .menu .item .link .text { color: red; } +``` + +**Correct:** + +```css +*, +*::before, +*::after { + box-sizing: border-box; +} + +.nav-link-text { + color: red; +} +``` + +--- + +## Code Refactoring + +Avoid whitespace-only patches. Don't refactor purely for style. + +> "Code refactoring should not be done just because we can." diff --git a/.windsurf/skills/wordpress-css-coding-standards/rules/comments.md b/.windsurf/skills/wordpress-css-coding-standards/rules/comments.md new file mode 100644 index 0000000000..1ae2cf73dd --- /dev/null +++ b/.windsurf/skills/wordpress-css-coding-standards/rules/comments.md @@ -0,0 +1,81 @@ +# Comments + +**Priority: LOW** +**Impact: Documentation** + +--- + +## Table of Contents + +For longer stylesheets, include a table of contents at the top. + +**Correct:** + +```css +/** + * TABLE OF CONTENTS + * + * 1. Reset + * 2. Typography + * 3. Layout + * 4. Components + * 4.1 Buttons + * 4.2 Forms + * 4.3 Navigation + * 5. Utilities + */ +``` + +--- + +## Section Headers + +Use consistent section dividers. + +**Correct:** + +```css +/** + * #.# Section title + * + * Description of section. + */ + +.selector-1 { + background: #fff; +} +``` + +--- + +## Inline Comments + +For single-line clarifications. + +**Correct:** + +```css +.selector { + /* Override WP default */ + background: #fff; + color: #000; /* Matches brand guidelines */ +} +``` + +--- + +## Multi-line Comments + +Use DocBlock style for complex explanations. + +**Correct:** + +```css +/** + * Long description explaining rationale, + * browser support considerations, or + * other important context. + * + * @see https://example.com/reference + */ +``` diff --git a/.windsurf/skills/wordpress-css-coding-standards/rules/media-queries.md b/.windsurf/skills/wordpress-css-coding-standards/rules/media-queries.md new file mode 100644 index 0000000000..c7cca3aed3 --- /dev/null +++ b/.windsurf/skills/wordpress-css-coding-standards/rules/media-queries.md @@ -0,0 +1,48 @@ +# Media Queries + +**Priority: MEDIUM** +**Impact: Responsive design organization** + +--- + +## Placement + +Place media queries near relevant rule sets, or at end of document in stylesheet sections. + +--- + +## Formatting + +- Opening brace on same line as query +- Contents indented one level +- Closing brace on own line + +**Correct:** + +```css +@media screen and (min-width: 768px) { + .selector { + width: 50%; + } + + .sidebar { + display: block; + } +} +``` + +--- + +## Breakpoints + +Use consistent breakpoints throughout the project. Prefer min-width (mobile-first). + +**Common Breakpoints:** + +```css +/* Mobile first approach */ +@media screen and (min-width: 480px) { /* Small */ } +@media screen and (min-width: 768px) { /* Medium */ } +@media screen and (min-width: 1024px) { /* Large */ } +@media screen and (min-width: 1200px) { /* Extra large */ } +``` diff --git a/.windsurf/skills/wordpress-css-coding-standards/rules/properties.md b/.windsurf/skills/wordpress-css-coding-standards/rules/properties.md new file mode 100644 index 0000000000..80437ec0e4 --- /dev/null +++ b/.windsurf/skills/wordpress-css-coding-standards/rules/properties.md @@ -0,0 +1,62 @@ +# Properties + +**Priority: MEDIUM** +**Impact: Consistency and readability** + +--- + +## Property Ordering + +Group properties logically: + +1. **Display & Position** — display, visibility, position, float, clear, overflow, z-index +2. **Box Model** — width, height, margin, padding, border +3. **Typography** — font, line-height, text-*, letter-spacing, word-spacing +4. **Visual** — background, color, list-style +5. **Other** — cursor, content, etc. + +**Correct:** + +```css +.selector { + /* Display & Position */ + display: block; + position: relative; + float: left; + + /* Box Model */ + width: 100%; + margin: 10px; + padding: 10px; + border: 1px solid #000; + + /* Typography */ + font-family: sans-serif; + font-size: 16px; + line-height: 1.4; + + /* Visual */ + background: #fff; + color: #000; +} +``` + +--- + +## Vendor Prefixes + +Stack prefixes vertically, aligned. Standard property last. + +**Correct:** + +```css +.selector { + -webkit-transition: all 0.3s ease; + -moz-transition: all 0.3s ease; + -ms-transition: all 0.3s ease; + -o-transition: all 0.3s ease; + transition: all 0.3s ease; +} +``` + +> **Note:** Use Autoprefixer in your build process to handle prefixes automatically. diff --git a/.windsurf/skills/wordpress-css-coding-standards/rules/selectors.md b/.windsurf/skills/wordpress-css-coding-standards/rules/selectors.md new file mode 100644 index 0000000000..b89734d7a7 --- /dev/null +++ b/.windsurf/skills/wordpress-css-coding-standards/rules/selectors.md @@ -0,0 +1,65 @@ +# Selectors + +**Priority: HIGH** +**Impact: Specificity and maintainability** + +--- + +## Selector Naming + +Use lowercase, separate words with hyphens. Use human-readable names. + +**Incorrect:** + +```css +.postHeader {} +.post_header {} +#commentForm {} +#comment_form {} +.u-teleportLeft {} +``` + +**Correct:** + +```css +#comment-form {} +.post-header {} +.comment-form-author {} +``` + +--- + +## Selector Structure + +- Avoid over-qualified selectors +- No IDs in selectors when possible (higher specificity = harder to override) +- Avoid tag selectors for common elements + +**Incorrect:** + +```css +div.container {} /* Over-qualified */ +#my-id {} /* Too specific */ +div {} /* Too generic */ +``` + +**Correct:** + +```css +.container {} +.my-class {} +.post-content p {} +``` + +--- + +## Attribute Selectors + +Always quote attribute values. + +**Correct:** + +```css +input[type="text"] {} +a[href^="https://"] {} +``` diff --git a/.windsurf/skills/wordpress-css-coding-standards/rules/structure.md b/.windsurf/skills/wordpress-css-coding-standards/rules/structure.md new file mode 100644 index 0000000000..8ffe0047b9 --- /dev/null +++ b/.windsurf/skills/wordpress-css-coding-standards/rules/structure.md @@ -0,0 +1,63 @@ +# Structure + +**Priority: HIGH** +**Impact: Readability and organization** + +--- + +## General Formatting + +Each selector on its own line. Opening brace on same line as last selector. Closing brace on its own line. + +**Correct:** + +```css +#selector-1, +#selector-2, +#selector-3 { + background: #fff; + color: #000; +} +``` + +--- + +## Property Formatting + +- One property per line +- Indent properties with single tab +- End each declaration with semicolon +- Double quotes around values + +**Correct:** + +```css +#selector-1 { + background: #fff; + color: #000; +} + +#selector-2 { + font-family: "Helvetica Neue", sans-serif; +} +``` + +--- + +## Blank Lines + +Separate rule sets by one blank line for readability. + +**Correct:** + +```css +#selector-1 { + background: #fff; + color: #000; +} + +#selector-2 { + background: #fff; + color: #000; +} +``` diff --git a/.windsurf/skills/wordpress-css-coding-standards/rules/values.md b/.windsurf/skills/wordpress-css-coding-standards/rules/values.md new file mode 100644 index 0000000000..195739a199 --- /dev/null +++ b/.windsurf/skills/wordpress-css-coding-standards/rules/values.md @@ -0,0 +1,90 @@ +# Values + +**Priority: MEDIUM** +**Impact: Consistency** + +--- + +## General Rules + +- Space after property colon +- Space after commas in multi-value properties +- Lowercase hex values, shorthand when possible +- Avoid units on zero values +- Use leading zero for decimals + +--- + +## Colors + +**Incorrect:** + +```css +.selector { + color: #FFFFFF; + background: #FF0000; +} +``` + +**Correct:** + +```css +.selector { + color: #fff; + background: #f00; +} +``` + +--- + +## Units + +**Incorrect:** + +```css +.selector { + margin: 0px; + padding: 0em; + opacity: .5; +} +``` + +**Correct:** + +```css +.selector { + margin: 0; + padding: 0; + opacity: 0.5; +} +``` + +--- + +## Multiple Values + +Space after each comma. + +**Correct:** + +```css +.selector { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + background: rgba(0, 0, 0, 0.5); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2), 0 2px 4px rgba(0, 0, 0, 0.1); +} +``` + +--- + +## URLs + +Quotes around URL paths. + +**Correct:** + +```css +.selector { + background: url("images/bg.png"); +} +``` From 3b61cdb0ff3f144fef78288ed773b70cba3817c6 Mon Sep 17 00:00:00 2001 From: Sherv Date: Wed, 28 Jan 2026 19:23:55 +0300 Subject: [PATCH 16/90] Add comprehensive WordPress accessibility coding standards documentation for AI agents Add detailed WordPress accessibility coding standards guide covering WCAG 2.2 Level AA compliance, organized around four core principles: Perceivable, Operable, Understandable, and Robust. Document includes conformance levels (CRITICAL/REQUIRED/ENCOURAGED), guidelines for text alternatives, keyboard accessibility, color contrast, semantic markup, ARIA usage, and WordPress-specific patterns. Features extensive --- .../AGENTS.md | 469 ++++++++++++++++++ .../SKILL.md | 86 ++++ 2 files changed, 555 insertions(+) create mode 100644 .windsurf/skills/wordpress-accessibility-coding-standards/AGENTS.md create mode 100644 .windsurf/skills/wordpress-accessibility-coding-standards/SKILL.md diff --git a/.windsurf/skills/wordpress-accessibility-coding-standards/AGENTS.md b/.windsurf/skills/wordpress-accessibility-coding-standards/AGENTS.md new file mode 100644 index 0000000000..7f67a1e785 --- /dev/null +++ b/.windsurf/skills/wordpress-accessibility-coding-standards/AGENTS.md @@ -0,0 +1,469 @@ +# WordPress Accessibility Coding Standards + +**Version 1.0.0** +Based on WordPress Core Official Standards + +> **Note:** +> This document is for AI agents and LLMs to follow when maintaining, +> generating, or refactoring code in the WordPress ecosystem to ensure accessibility. + +--- + +## Abstract + +Code integrated into the WordPress ecosystem—including WordPress core, WordPress.org websites, and official plugins—is expected to conform to the Web Content Accessibility Guidelines (WCAG), version 2.2, at level AA. + +--- + +## Table of Contents + +1. [Conformance Levels](#1-conformance-levels) +2. [WCAG Principles](#2-wcag-principles) +3. [Guidelines by Principle](#3-guidelines-by-principle) +4. [Success Criteria](#4-success-criteria) +5. [Techniques](#5-techniques) +6. [Authoritative Resources](#6-authoritative-resources) + +--- + +## 1. Conformance Levels + +### Level A — Minimum (CRITICAL) + +Addresses accessibility barriers on a very wide scale. Prevents many people from accessing the site. These are the **minimum requirements** for most web-based interfaces. + +### Level AA — WordPress Requirement (REQUIRED) + +The **WordPress commitment level**. These criteria address concerns that are generally more complicated but still common needs with broad reach. + +### Level AAA — Enhanced (ENCOURAGED) + +Targeted at very specific needs. May be difficult to implement effectively. Implement where relevant and possible. + +**Quick Reference:** [WCAG 2.2 Level A and AA Requirements](https://www.w3.org/WAI/WCAG22/quickref/) + +--- + +## 2. WCAG Principles + +WCAG 2.2 is organized around 4 principles. Content must be: + +### 2.1 Perceivable + +Users must be able to perceive the information presented. It cannot be invisible to all their senses. + +**Examples:** + +- Providing text alternatives for images (alt text) +- Providing captions for videos +- Ensuring sufficient color contrast + +### 2.2 Operable + +Users must be able to operate the interface. The interface cannot require interaction that a user cannot perform. + +**Examples:** + +- All functionality available via keyboard +- Users have enough time to read content +- No content that causes seizures + +### 2.3 Understandable + +Users must be able to understand the information and operation of the interface. + +**Examples:** + +- Readable text content +- Predictable page operation +- Input assistance for forms + +### 2.4 Robust + +Content must be robust enough to be interpreted by a wide variety of user agents, including assistive technologies. + +**Examples:** + +- Valid HTML markup +- ARIA used correctly +- Compatible with current and future technologies + +--- + +## 3. Guidelines by Principle + +### Principle 1: Perceivable + +**Guideline 1.1 — Text Alternatives** + +Provide text alternatives for any non-text content so it can be changed into other forms (large print, braille, speech, symbols, simpler language). + +```html + + + + +Company Name Logo + + + +``` + +**Guideline 1.2 — Time-based Media** + +Provide alternatives for time-based media (audio, video). + +- Captions for pre-recorded audio +- Audio descriptions for pre-recorded video +- Transcripts + +**Guideline 1.3 — Adaptable** + +Create content that can be presented in different ways without losing information or structure. + +```html + +
Important Title
+ + +

Important Title

+``` + +**Guideline 1.4 — Distinguishable** + +Make it easier for users to see and hear content. + +- **Color contrast:** 4.5:1 minimum for normal text, 3:1 for large text +- **Text resize:** Works up to 200% +- **Don't use color alone** to convey information + +```css +/* Incorrect: relies on color alone */ +.error { + color: red; +} + +/* Correct: uses multiple indicators */ +.error { + color: #d00; + border-left: 4px solid #d00; +} +.error::before { + content: "Error: "; + font-weight: bold; +} +``` + +### Principle 2: Operable + +**Guideline 2.1 — Keyboard Accessible** + +Make all functionality available from a keyboard. + +```html + +
Click me
+ + + + + +
+ Click me +
+``` + +**Guideline 2.2 — Enough Time** + +Provide users enough time to read and use content. + +- Allow users to turn off, adjust, or extend time limits +- Pause, stop, hide moving/auto-updating content + +**Guideline 2.3 — Seizures and Physical Reactions** + +Do not design content in a way that causes seizures. + +- No content flashing more than 3 times per second + +**Guideline 2.4 — Navigable** + +Provide ways to help users navigate, find content, and determine where they are. + +```html + + + + +Contact Us - Company Name + + + +Click here + + +Download Annual Report (PDF) +``` + +**Guideline 2.5 — Input Modalities** + +Make it easier to operate through various inputs beyond keyboard. + +- Target size: minimum 24x24 CSS pixels (44x44 recommended) +- Don't require complex gestures + +### Principle 3: Understandable + +**Guideline 3.1 — Readable** + +Make text content readable and understandable. + +```html + + + +

+ The French phrase c'est la vie means "that's life". +

+ +``` + +**Guideline 3.2 — Predictable** + +Make Web pages appear and operate in predictable ways. + +- Consistent navigation +- Consistent identification +- No unexpected context changes on focus/input + +```html + + + + + +``` + +**Guideline 3.3 — Input Assistance** + +Help users avoid and correct mistakes. + +```html + + + +Please enter a valid email address + + + + +Format: 123-456-7890 +``` + +### Principle 4: Robust + +**Guideline 4.1 — Compatible** + +Maximize compatibility with user agents and assistive technologies. + +```html + + +

Content

+ + +

Content

+ + + + + + + + + +
Form submitted successfully
+``` + +--- + +## 4. Success Criteria + +Each guideline has specific success criteria that must be met. These can be tested using: + +1. **Automated tools:** Axe, WAVE, Lighthouse +2. **Manual testing:** Keyboard navigation, screen reader testing +3. **User testing:** Testing with people who have disabilities + +### Key Level A Criteria + +- 1.1.1 Non-text Content (alt text) +- 1.3.1 Info and Relationships (semantic markup) +- 2.1.1 Keyboard (all functionality) +- 2.4.1 Bypass Blocks (skip links) +- 4.1.2 Name, Role, Value (ARIA) + +### Key Level AA Criteria + +- 1.4.3 Contrast (Minimum) - 4.5:1 +- 1.4.4 Resize Text - 200% +- 2.4.6 Headings and Labels +- 2.4.7 Focus Visible + +--- + +## 5. Techniques + +### Sufficient Techniques + +Required to meet success criteria. + +```php +// WordPress: Always provide alt text + $alt_text ) ); ?> + +// Screen reader text + +``` + +### Advisory Techniques + +Go beyond requirements (recommended). + +```html + +
+ +
+``` + +### Failure Techniques + +Patterns that **fail** accessibility requirements. + +```html + + + + + + + +Required +``` + +--- + +## 6. Authoritative Resources + +### Normative (Requirements) + +- [W3C WCAG 2.2](https://www.w3.org/TR/WCAG22) — Web Content Accessibility Guidelines +- [W3C ATAG 2.0](https://www.w3.org/TR/ATAG20/) — Authoring Tool Accessibility Guidelines +- [W3C WAI-ARIA 1.1](https://www.w3.org/TR/wai-aria/) — Accessible Rich Internet Applications + +### Informative (Guidance) + +- [Understanding WCAG 2.2](https://www.w3.org/WAI/WCAG22/Understanding/) +- [Using ARIA](https://www.w3.org/TR/using-aria/) +- [ARIA Authoring Practices Guide](https://www.w3.org/WAI/ARIA/apg/) — Design patterns + +### Technical Resources + +- [WordPress Accessibility Handbook](https://make.wordpress.org/accessibility/handbook/) +- [WordPress Accessibility Team](https://make.wordpress.org/accessibility/) + +--- + +## WordPress-Specific Practices + +### Screen Reader Text + +```css +.screen-reader-text { + border: 0; + clip: rect(1px, 1px, 1px, 1px); + clip-path: inset(50%); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; + word-wrap: normal !important; +} + +.screen-reader-text:focus { + background-color: #f1f1f1; + clip: auto !important; + clip-path: none; + display: block; + height: auto; + left: 5px; + padding: 15px 23px 14px; + top: 5px; + width: auto; + z-index: 100000; +} +``` + +### Skip Links + +```html + +``` + +### Focus Styles + +Never remove focus styles without providing an alternative. + +```css +/* Incorrect */ +*:focus { + outline: none; +} + +/* Correct */ +*:focus { + outline: 2px solid #0073aa; + outline-offset: 2px; +} +``` + +### ARIA in WordPress + +```php +// Accessible toggle + + +// Live regions for dynamic content +
+ +
+``` diff --git a/.windsurf/skills/wordpress-accessibility-coding-standards/SKILL.md b/.windsurf/skills/wordpress-accessibility-coding-standards/SKILL.md new file mode 100644 index 0000000000..cce10c9fc2 --- /dev/null +++ b/.windsurf/skills/wordpress-accessibility-coding-standards/SKILL.md @@ -0,0 +1,86 @@ +# WordPress Accessibility Coding Standards + +**Version 1.0.0** +Based on WordPress Core Official Standards + +> **Note:** +> This document is for AI agents and LLMs to follow when maintaining, +> generating, or refactoring code in the WordPress ecosystem to ensure accessibility. + +--- + +## Overview + +Code integrated into the WordPress ecosystem is expected to conform to **WCAG 2.2 at Level AA**. New interfaces should incorporate ATAG 2.0 guidelines where applicable. + +**When to apply:** All WordPress Core, plugins, themes, and WordPress.org websites. + +**Reference:** [WordPress Accessibility Coding Standards](https://developer.wordpress.org/coding-standards/wordpress-coding-standards/accessibility/) + +--- + +## Conformance Levels + +### Level A — **CRITICAL** + +Minimum requirements. Prevents major accessibility barriers. + +### Level AA — **REQUIRED** + +WordPress commitment level. More nuanced accessibility concerns. + +### Level AAA — **ENCOURAGED** + +Targeted at specific needs. Implement where relevant. + +--- + +## Rule Categories by WCAG Principle + +### 1. Perceivable — **CRITICAL** + +| Rule | Impact | +| ----------------------------------- | -------------- | +| [Perceivable](rules/perceivable.md) | Content access | + +### 2. Operable — **CRITICAL** + +| Rule | Impact | +| ----------------------------- | --------------------- | +| [Operable](rules/operable.md) | Keyboard & navigation | + +### 3. Understandable — **HIGH** + +| Rule | Impact | +| ----------------------------------------- | ------------------ | +| [Understandable](rules/understandable.md) | User comprehension | + +### 4. Robust — **HIGH** + +| Rule | Impact | +| ------------------------- | ------------- | +| [Robust](rules/robust.md) | Compatibility | + +### 5. WordPress-Specific — **HIGH** + +| Rule | Impact | +| ------------------------------------------------- | ------------ | +| [WordPress Specific](rules/wordpress-specific.md) | WP ecosystem | + +--- + +## Usage + +```text +@wordpress-accessibility-coding-standards +``` + +See [AGENTS.md](AGENTS.md) for the complete reference. + +--- + +## Normative References + +- [W3C WCAG 2.2](https://www.w3.org/TR/WCAG22) +- [W3C ATAG 2.0](https://www.w3.org/TR/ATAG20/) +- [W3C WAI-ARIA 1.1](https://www.w3.org/TR/wai-aria/) From 97eaa8d121f5b04743fb14a71cd340a40d49afac Mon Sep 17 00:00:00 2001 From: Sherv Date: Wed, 28 Jan 2026 19:24:06 +0300 Subject: [PATCH 17/90] Add modular WordPress accessibility coding standards rules split into focused files Split comprehensive WordPress accessibility coding standards into separate focused rule files covering WCAG 2.2 principles: Perceivable, Operable, Understandable, Robust, and WordPress-specific patterns. Each file includes priority level (CRITICAL/HIGH), impact description, and extensive code examples showing correct vs incorrect patterns. Intended as modular reference for AI agents maintaining WordPress accessible --- .../rules/operable.md | 122 +++++++++++++++ .../rules/perceivable.md | 122 +++++++++++++++ .../rules/robust.md | 91 ++++++++++++ .../rules/understandable.md | 116 +++++++++++++++ .../rules/wordpress-specific.md | 139 ++++++++++++++++++ 5 files changed, 590 insertions(+) create mode 100644 .windsurf/skills/wordpress-accessibility-coding-standards/rules/operable.md create mode 100644 .windsurf/skills/wordpress-accessibility-coding-standards/rules/perceivable.md create mode 100644 .windsurf/skills/wordpress-accessibility-coding-standards/rules/robust.md create mode 100644 .windsurf/skills/wordpress-accessibility-coding-standards/rules/understandable.md create mode 100644 .windsurf/skills/wordpress-accessibility-coding-standards/rules/wordpress-specific.md diff --git a/.windsurf/skills/wordpress-accessibility-coding-standards/rules/operable.md b/.windsurf/skills/wordpress-accessibility-coding-standards/rules/operable.md new file mode 100644 index 0000000000..8151a2d7bd --- /dev/null +++ b/.windsurf/skills/wordpress-accessibility-coding-standards/rules/operable.md @@ -0,0 +1,122 @@ +# Operable (WCAG Principle 2) + +**Priority: CRITICAL** +**Impact: Users must be able to operate the interface** + +--- + +## 2.1 Keyboard Accessible + +All functionality must be available via keyboard. + +### Focus Management + +**Correct:** + +```html + +Link + +``` + +### Custom Interactive Elements + +**Incorrect:** + +```html +
Click Me
+``` + +**Correct:** + +```html + + + +
+ Click Me +
+``` + +### Skip Links + +```html + + +
+ +
+``` + +--- + +## 2.2 Enough Time + +Provide users enough time to read and use content. + +- Allow users to turn off, adjust, or extend time limits +- Pause, stop, or hide moving content +- No timing on essential activities + +--- + +## 2.3 Seizures and Physical Reactions + +Do not design content that causes seizures. + +- No content flashing more than 3 times per second +- Warn users before auto-playing video with flashing + +--- + +## 2.4 Navigable + +Help users navigate and find content. + +### Page Titles + +```html +Contact Us - Company Name +``` + +### Focus Order + +Tab order should follow visual/logical order. + +### Link Purpose + +**Incorrect:** + +```html +Click here +Read more +``` + +**Correct:** + +```html +Read the full article about accessibility +``` + +### Multiple Ways + +Provide multiple ways to find pages (navigation, search, sitemap). + +### Headings and Labels + +Use descriptive headings that describe topic or purpose. + +--- + +## 2.5 Input Modalities + +Make it easier to operate through various inputs beyond keyboard. + +### Target Size + +- Minimum 44x44 CSS pixels for touch targets +- Adequate spacing between targets + +### Motion Actuation + +Don't require device motion (shaking, tilting) as sole input method. diff --git a/.windsurf/skills/wordpress-accessibility-coding-standards/rules/perceivable.md b/.windsurf/skills/wordpress-accessibility-coding-standards/rules/perceivable.md new file mode 100644 index 0000000000..ae3d3968bf --- /dev/null +++ b/.windsurf/skills/wordpress-accessibility-coding-standards/rules/perceivable.md @@ -0,0 +1,122 @@ +# Perceivable (WCAG Principle 1) + +**Priority: CRITICAL** +**Impact: Users must be able to perceive content** + +--- + +## 1.1 Text Alternatives + +Provide text alternatives for non-text content. + +### Images + +**Incorrect:** + +```html + + +``` + +**Correct:** + +```html +Company Name Logo +Sales chart showing 25% growth in Q4 +``` + +### Decorative Images + +```html + +``` + +### Icons with Actions + +```html + +``` + +--- + +## 1.2 Time-based Media + +Provide alternatives for audio and video content. + +- **Captions** for audio content in video +- **Audio descriptions** for visual content +- **Transcripts** for audio-only content + +--- + +## 1.3 Adaptable + +Create content that can be presented in different ways. + +### Semantic Structure + +**Incorrect:** + +```html +
Page Title
+
Section
+``` + +**Correct:** + +```html +

Page Title

+

Section

+``` + +### Reading Order + +Content should make sense when CSS is disabled. + +### Form Labels + +**Incorrect:** + +```html + +``` + +**Correct:** + +```html + + +``` + +--- + +## 1.4 Distinguishable + +Make it easy to see and hear content. + +### Color Contrast + +- **Normal text:** 4.5:1 minimum ratio +- **Large text (18pt+ or 14pt bold):** 3:1 minimum ratio +- **UI components:** 3:1 minimum ratio + +### Don't Use Color Alone + +**Incorrect:** + +```html +

Required fields are marked in red.

+``` + +**Correct:** + +```html +

Required fields are marked with an asterisk (*).

+ +``` + +### Text Resize + +Content must be readable at 200% zoom without loss of functionality. diff --git a/.windsurf/skills/wordpress-accessibility-coding-standards/rules/robust.md b/.windsurf/skills/wordpress-accessibility-coding-standards/rules/robust.md new file mode 100644 index 0000000000..112624a27d --- /dev/null +++ b/.windsurf/skills/wordpress-accessibility-coding-standards/rules/robust.md @@ -0,0 +1,91 @@ +# Robust (WCAG Principle 4) + +**Priority: HIGH** +**Impact: Content must work with current and future technologies** + +--- + +## 4.1 Compatible + +Maximize compatibility with current and future user agents. + +### Parsing (Valid HTML) + +- Elements have complete start and end tags +- Elements are nested according to specification +- No duplicate attributes +- IDs are unique + +**Incorrect:** + +```html +
+

Paragraph without closing tag +

Duplicate ID
+
+``` + +**Correct:** + +```html +
+

Paragraph with closing tag

+
Unique ID
+
+``` + +### Name, Role, Value + +For all UI components: + +- **Name:** Accessible name via label, aria-label, or aria-labelledby +- **Role:** Native HTML role or ARIA role +- **Value/State:** Current value and state programmatically available + +**Incorrect:** + +```html +
+``` + +**Correct:** + +```html + + + + +
+
+I agree to the terms +``` + +--- + +## Status Messages + +Convey status messages to assistive technologies without focus change. + +**Correct:** + +```html +
+ Your changes have been saved. +
+ +
+ Error: Please correct the form fields. +
+``` + +### Live Regions + +- `aria-live="polite"` — Announce when user is idle +- `aria-live="assertive"` — Announce immediately (use sparingly) +- `role="status"` — Polite live region for status messages +- `role="alert"` — Assertive live region for errors/warnings diff --git a/.windsurf/skills/wordpress-accessibility-coding-standards/rules/understandable.md b/.windsurf/skills/wordpress-accessibility-coding-standards/rules/understandable.md new file mode 100644 index 0000000000..e1e378e42f --- /dev/null +++ b/.windsurf/skills/wordpress-accessibility-coding-standards/rules/understandable.md @@ -0,0 +1,116 @@ +# Understandable (WCAG Principle 3) + +**Priority: HIGH** +**Impact: Users must be able to understand content and operation** + +--- + +## 3.1 Readable + +Make text content readable and understandable. + +### Language of Page + +```html + +``` + +### Language of Parts + +```html +

The French phrase c'est la vie means "that's life."

+``` + +### Unusual Words + +Provide definitions for jargon, idioms, and technical terms. + +--- + +## 3.2 Predictable + +Make pages appear and operate predictably. + +### On Focus + +Don't change context when element receives focus. + +**Incorrect:** + +```javascript +input.addEventListener('focus', function() { + window.location = '/new-page'; +}); +``` + +### On Input + +Don't change context when user inputs data (unless warned). + +**Incorrect:** + +```html + + + + + +``` + +### Consistent Navigation + +Navigation should be in the same relative order across pages. + +### Consistent Identification + +Components with same functionality should be identified consistently. + +--- + +## 3.3 Input Assistance + +Help users avoid and correct mistakes. + +### Error Identification + +**Incorrect:** + +```html + +``` + +**Correct:** + +```html + + +Please enter a valid email address. +``` + +### Labels or Instructions + +Provide clear instructions for required formats. + +```html + + +Format: (123) 456-7890 +``` + +### Error Suggestion + +When errors are detected, provide suggestions for correction. + +### Error Prevention + +For legal, financial, or data submissions: + +- Allow review before final submission +- Provide confirmation/undo capability +- Check input and allow correction diff --git a/.windsurf/skills/wordpress-accessibility-coding-standards/rules/wordpress-specific.md b/.windsurf/skills/wordpress-accessibility-coding-standards/rules/wordpress-specific.md new file mode 100644 index 0000000000..8dce4e2617 --- /dev/null +++ b/.windsurf/skills/wordpress-accessibility-coding-standards/rules/wordpress-specific.md @@ -0,0 +1,139 @@ +# WordPress-Specific Accessibility + +**Priority: HIGH** +**Impact: WordPress ecosystem compatibility** + +--- + +## Admin Accessibility + +WordPress admin must be usable by people with disabilities. + +### Admin Notices + +```php +
+

+
+``` + +### Screen Reader Text + +```php + + + +``` + +```css +.screen-reader-text { + border: 0; + clip: rect(1px, 1px, 1px, 1px); + clip-path: inset(50%); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; + word-wrap: normal !important; +} +``` + +--- + +## Theme Accessibility + +### Skip Links + +Required in accessible themes: + +```php + +``` + +### Navigation Landmarks + +```php + +``` + +### Search Form + +```php + +``` + +--- + +## Form Accessibility + +### WordPress Forms + +```php +

+ + +

+``` + +### Required Fields + +```php + + +``` + +--- + +## Media Accessibility + +### Images in Content + +```php + +<?php echo esc_attr( $alt_text ); ?> +``` + +### Embedded Media + +```php +
+ +
+
+``` + +--- + +## Testing in WordPress + +- **Keyboard navigation:** Tab through entire admin/frontend +- **Screen reader:** Test with NVDA, JAWS, or VoiceOver +- **Color contrast:** Use browser dev tools or WAVE +- **Zoom:** Test at 200% zoom +- **Automated:** Use axe, WAVE, or Lighthouse From 39407786210e354bc396968ee96c78ac083ec501 Mon Sep 17 00:00:00 2001 From: Sherv Date: Fri, 6 Feb 2026 19:07:19 +0300 Subject: [PATCH 18/90] feat(ci): add conventional commit message guidelines Add comprehensive documentation for Conventional Commits 1.0.0 specification including types, scopes, breaking change indicators, and AI commit message generation rules. --- .windsurf/rules/conventional-commits.md | 106 ++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 .windsurf/rules/conventional-commits.md diff --git a/.windsurf/rules/conventional-commits.md b/.windsurf/rules/conventional-commits.md new file mode 100644 index 0000000000..3e2d42be42 --- /dev/null +++ b/.windsurf/rules/conventional-commits.md @@ -0,0 +1,106 @@ +--- +trigger: always_on +description: Enforces conventional commit message format for all git commits in the project. +--- + +# Conventional Commit Messages + +All commit messages MUST follow the Conventional Commits 1.0.0 specification. + +## Format + +``` +(): + +[optional body] + +[optional footer(s)] +``` + +## Types + +| Type | Description | SemVer | +| ---------- | ---------------------------------------------------- | ------ | +| `fix` | Bug fix (patches a bug in the codebase) | PATCH | +| `feat` | New feature (adds functionality to the codebase) | MINOR | +| `docs` | Documentation only changes | - | +| `style` | Code style changes (formatting, whitespace, etc.) | - | +| `refactor` | Code change that neither fixes a bug nor adds feature| - | +| `perf` | Performance improvement | - | +| `test` | Adding or correcting tests | - | +| `build` | Changes to build system or external dependencies | - | +| `ci` | CI configuration changes | - | +| `chore` | Other changes that don't modify src or test files | - | + +## Scope (Optional) + +The scope provides additional context about what part of the codebase the commit affects: + +- `builder` - Form builder related changes +- `entries` - Entry management changes +- `fields` - Field types or field handling +- `api` - API endpoints or integrations +- `admin` - Admin UI changes +- `frontend` - Front-end form display +- `db` - Database operations +- `i18n` - Internationalization +- `security` - Security fixes or improvements +- `deps` - Dependencies + +## Breaking Changes + +Indicate breaking changes with: + +1. `!` after type/scope: `feat(api)!: change response format` +2. `BREAKING CHANGE:` footer in the commit body + +## Examples + +``` +fix(fields): resolve date field validation error + +The date field was incorrectly validating dates in non-US formats. +Added locale-aware date parsing. + +Fixes #1234 +``` + +``` +feat(builder): add drag-and-drop field reordering + +Implements smooth drag-and-drop functionality for reordering fields +in the form builder interface. +``` + +``` +fix: prevent XSS in field labels + +Escape HTML entities in field labels before output. +``` + +``` +refactor(entries)!: change entry meta storage format + +BREAKING CHANGE: Entry meta now uses JSON encoding instead of +serialized PHP arrays. Run migration script before updating. +``` + +## Rules + +1. Type MUST be lowercase. +2. Description MUST start with lowercase letter. +3. Description MUST NOT end with a period. +4. Description MUST be imperative mood ("add" not "added" or "adds"). +5. Body MUST be separated from description by a blank line. +6. Breaking changes MUST be indicated with `!` or `BREAKING CHANGE:` footer. +7. Keep description under 72 characters. + +## AI Commit Message Generation + +When generating commit messages: + +1. Analyze the staged changes to determine the appropriate type. +2. Identify the scope from the files/directories changed. +3. Write a concise description in imperative mood. +4. Add body with details if the change is complex. +5. Include issue references if mentioned in the conversation. From 869347019e616f85300962c81e9c1fb9e5f7e8e4 Mon Sep 17 00:00:00 2001 From: Sherv Date: Fri, 6 Feb 2026 19:07:35 +0300 Subject: [PATCH 19/90] docs(windsurf): add skill frontmatter and improve configuration documentation Add YAML frontmatter to all WordPress coding standards skills with name and description fields to enable automatic invocation. Update README with comprehensive explanation of Windsurf's four mechanisms (Rules, Skills, Workflows, AGENTS.md), their interaction, and activation flow. Add conventional commits reference and clarify skill auto-invocation behavior. --- .windsurf/README.md | 190 +++++++++++------- .../SKILL.md | 5 + .../wordpress-css-coding-standards/SKILL.md | 5 + .../wordpress-html-coding-standards/SKILL.md | 5 + .../SKILL.md | 5 + .../wordpress-php-coding-standards/SKILL.md | 5 + 6 files changed, 143 insertions(+), 72 deletions(-) diff --git a/.windsurf/README.md b/.windsurf/README.md index b29a4aa117..5c32962ae4 100644 --- a/.windsurf/README.md +++ b/.windsurf/README.md @@ -1,133 +1,179 @@ # Windsurf Configuration for Formidable Forms -This directory contains comprehensive Windsurf configuration files for Formidable Forms development. +This directory contains comprehensive Windsurf configuration files for Formidable Forms development following WordPress VIP standards. -## Installation +## How It All Works Together -Copy the contents to your Formidable plugins directory: +Windsurf has **four mechanisms** for customizing Cascade behavior. Here's how they interact: -```bash -# Copy to formidable-master (Lite) -cp -r rules/ /path/to/formidable-master/.windsurf/rules/ -cp -r skills/ /path/to/formidable-master/.windsurf/skills/ -cp -r workflows/ /path/to/formidable-master/.windsurf/workflows/ -cp AGENTS.md /path/to/formidable-master/AGENTS.md +| Mechanism | Purpose | Trigger | Best For | +| ------------- | ----------------------------- | ----------------------------------------- | ------------------------------------ | +| **Rules** | Behavioral guidelines | Always-on, glob, model decision, manual | Coding style, project conventions | +| **Skills** | Complex tasks with resources | Auto (progressive disclosure) or @mention | Multi-step workflows, reference docs | +| **Workflows** | Repetitive task sequences | /slash-command | Deployment, testing, bug fixing | +| **AGENTS.md** | Directory-scoped instructions | Auto based on file location | Location-specific conventions | -# Copy to formidable-pro-master (Pro) -cp -r rules/ /path/to/formidable-pro-master/.windsurf/rules/ -cp -r skills/ /path/to/formidable-pro-master/.windsurf/skills/ -cp -r workflows/ /path/to/formidable-pro-master/.windsurf/workflows/ -cp AGENTS.md /path/to/formidable-pro-master/AGENTS.md -``` +### Activation Flow + +1. **AGENTS.md** at root applies to ALL files (always on) +2. **Rules** with `trigger: always_on` apply to every conversation +3. **Rules** with `trigger: glob` apply when matching files are involved +4. **Skills** auto-invoke based on task relevance (via description matching) +5. **Workflows** invoke explicitly via `/command` ## Directory Structure ``` -windsurf-formidable/ +.windsurf/ ├── README.md # This file -├── AGENTS.md # Root-level project guidelines +├── AGENTS.md # Root-level project guidelines (always applies) ├── rules/ +│ ├── conventional-commits.md # Commit message format (always_on) │ ├── formidable-core.md # Core development rules (always_on) │ ├── php-wordpress.md # PHP/WordPress standards (*.php glob) │ └── web-search-policy.md # Mandatory web search rules (always_on) ├── skills/ -│ ├── add-field-type/ -│ │ └── SKILL.md # Guide for creating new field types -│ └── code-review/ -│ └── SKILL.md # Code review checklist and guidelines +│ ├── add-field-type/ # Guide for creating new field types +│ │ └── SKILL.md +│ ├── code-review/ # Code review checklist and guidelines +│ │ └── SKILL.md +│ ├── wordpress-php-coding-standards/ +│ │ ├── SKILL.md # Skill definition with frontmatter +│ │ ├── AGENTS.md # Full reference (supporting file) +│ │ └── rules/ # Detailed rule files +│ ├── wordpress-css-coding-standards/ +│ ├── wordpress-javascript-coding-standards/ +│ ├── wordpress-html-coding-standards/ +│ └── wordpress-accessibility-coding-standards/ └── workflows/ - ├── run-tests.md # /run-tests workflow - ├── phpcs-check.md # /phpcs-check workflow ├── fix-bug.md # /fix-bug workflow + ├── phpcs-check.md # /phpcs-check workflow + ├── run-tests.md # /run-tests workflow └── security-audit.md # /security-audit workflow ``` -## Usage - -### Rules (Automatic) +## Rules (Automatic Application) Rules are automatically applied based on their trigger: -- **formidable-core.md** - Always active, enforces core development practices -- **php-wordpress.md** - Active for all PHP files -- **web-search-policy.md** - Always active, ensures web searches for best practices +| Rule | Trigger | When Active | +| ------------------------- | ------------- | ----------------------- | +| `conventional-commits.md` | `always_on` | Every conversation | +| `formidable-core.md` | `always_on` | Every conversation | +| `web-search-policy.md` | `always_on` | Every conversation | +| `php-wordpress.md` | `glob: *.php` | When PHP files involved | + +## Skills (Progressive Disclosure + Manual) -### Skills (Manual or Automatic) +Skills are invoked in two ways: -Invoke skills by typing `@skill-name` or let Cascade auto-invoke based on context: +### Automatic Invocation -- `@add-field-type` - Guides creation of new field types -- `@code-review` - Performs comprehensive code review +Cascade reads the skill's `description` field and automatically invokes when relevant: -### Workflows (Slash Commands) +- Working with PHP files → `wordpress-php-coding-standards` may auto-invoke +- Working with CSS files → `wordpress-css-coding-standards` may auto-invoke +- Creating new field types → `add-field-type` may auto-invoke + +### Manual Invocation + +Type `@skill-name` in Cascade: + +- `@wordpress-php-coding-standards` - Full PHP standards reference +- `@wordpress-css-coding-standards` - CSS standards reference +- `@wordpress-javascript-coding-standards` - JavaScript standards reference +- `@wordpress-html-coding-standards` - HTML standards reference +- `@wordpress-accessibility-coding-standards` - WCAG 2.2 Level AA standards +- `@add-field-type` - Step-by-step field type creation +- `@code-review` - Code review checklist + +### Why Skills Have AGENTS.md Files + +The `AGENTS.md` files inside skill folders are **supporting resources**, not directory-scoped rules. When a skill is invoked, Cascade can read these comprehensive reference documents. + +## Workflows (Slash Commands) Invoke workflows using slash commands: -- `/run-tests` - Run PHPUnit tests -- `/phpcs-check` - Run PHP CodeSniffer -- `/fix-bug` - Structured bug fixing workflow -- `/security-audit` - Security audit checklist +| Command | Purpose | +| ----------------- | ------------------------------ | +| `/fix-bug` | Structured bug fixing workflow | +| `/run-tests` | Run PHPUnit tests | +| `/phpcs-check` | Run PHP CodeSniffer | +| `/security-audit` | Security audit checklist | + +### Skills Apply During Workflows + +When using `/fix-bug` on a PHP file, Cascade will: -## Key Features +1. Follow the workflow steps +2. Auto-invoke `wordpress-php-coding-standards` if relevant +3. Apply all `always_on` rules (formidable-core, web-search-policy, conventional-commits) -### 1. Mandatory Web Search +## Ensuring WordPress Standards Always Apply -The rules enforce searching WordPress docs before: +To ensure WordPress coding standards always apply: -- Using any WordPress function -- Writing database queries -- Implementing security measures -- Making performance optimizations +### Option 1: Let Auto-Invocation Work (Recommended) -### 2. WordPress VIP Compliance +The skill descriptions are crafted to trigger when working with relevant file types: -All rules align with WordPress VIP coding standards: +- "Apply when working with PHP files" → triggers for PHP work +- "Apply when working with CSS files" → triggers for CSS work -- Prepared SQL queries -- Input sanitization -- Output escaping -- Performance optimization +### Option 2: Explicit @mention -### 3. Formidable Patterns +Add `@wordpress-php-coding-standards` to your prompt when you want explicit standards enforcement. -Rules enforce Formidable-specific patterns: +### Option 3: Add to Workflow -- Class naming conventions (Frm*, FrmPro*) -- Hook naming conventions (frm*\*, frm_pro*\*) -- Factory pattern usage -- Helper method usage +Edit workflows to include skill references: -### 4. Pro Compatibility +```markdown +## Phase 5: Implement the Fix -All changes must work: +Follow @wordpress-php-coding-standards for all code changes. +``` + +## Commit Messages + +All commits must follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/): + +``` +(): + +[optional body] + +[optional footer(s)] +``` -- When Pro plugin is active -- When Pro plugin is inactive -- With empty/null data -- With missing configuration +**Types:** `fix`, `feat`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore` + +**Scopes:** `builder`, `entries`, `fields`, `api`, `admin`, `frontend`, `db`, `i18n`, `security`, `deps` + +**Examples:** + +``` +fix(fields): resolve date field validation error +feat(builder): add drag-and-drop field reordering +refactor(entries)!: change entry meta storage format +``` ## Lint Warnings The markdown lint warnings (MD033, MD040) in these files are **intentional**: - XML-style tags (``, ``) help Cascade parse grouped rules -- Some code blocks without language specs are for search query examples +- Code blocks without language specs are for commit message examples These do not affect Windsurf functionality. -## Customization - -Feel free to modify these files for your specific needs: - -1. **Add new rules** - Create new `.md` files in `rules/` -2. **Add new skills** - Create new directories in `skills/` with `SKILL.md` -3. **Add new workflows** - Create new `.md` files in `workflows/` - ## Related Documentation - [Windsurf Memories & Rules](https://docs.windsurf.com/windsurf/cascade/memories) - [Windsurf Skills](https://docs.windsurf.com/windsurf/cascade/skills) - [Windsurf Workflows](https://docs.windsurf.com/windsurf/cascade/workflows) - [Windsurf AGENTS.md](https://docs.windsurf.com/windsurf/cascade/agents-md) +- [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) - [WordPress Coding Standards](https://developer.wordpress.org/coding-standards/) - [WordPress VIP Documentation](https://docs.wpvip.com/) diff --git a/.windsurf/skills/wordpress-accessibility-coding-standards/SKILL.md b/.windsurf/skills/wordpress-accessibility-coding-standards/SKILL.md index cce10c9fc2..3bb338a490 100644 --- a/.windsurf/skills/wordpress-accessibility-coding-standards/SKILL.md +++ b/.windsurf/skills/wordpress-accessibility-coding-standards/SKILL.md @@ -1,3 +1,8 @@ +--- +name: wordpress-accessibility-coding-standards +description: WordPress accessibility coding standards (WCAG 2.2 Level AA). Apply when working with UI elements, forms, or any user-facing code in WordPress plugins or themes. +--- + # WordPress Accessibility Coding Standards **Version 1.0.0** diff --git a/.windsurf/skills/wordpress-css-coding-standards/SKILL.md b/.windsurf/skills/wordpress-css-coding-standards/SKILL.md index bdd5602db2..c4956ff6a9 100644 --- a/.windsurf/skills/wordpress-css-coding-standards/SKILL.md +++ b/.windsurf/skills/wordpress-css-coding-standards/SKILL.md @@ -1,3 +1,8 @@ +--- +name: wordpress-css-coding-standards +description: WordPress CSS coding standards for maintaining, generating, or refactoring CSS code. Apply when working with CSS files in WordPress plugins or themes. +--- + # WordPress CSS Coding Standards **Version 1.0.0** diff --git a/.windsurf/skills/wordpress-html-coding-standards/SKILL.md b/.windsurf/skills/wordpress-html-coding-standards/SKILL.md index fdf9205911..567c6729fe 100644 --- a/.windsurf/skills/wordpress-html-coding-standards/SKILL.md +++ b/.windsurf/skills/wordpress-html-coding-standards/SKILL.md @@ -1,3 +1,8 @@ +--- +name: wordpress-html-coding-standards +description: WordPress HTML coding standards for maintaining, generating, or refactoring HTML code. Apply when working with HTML or PHP template files in WordPress plugins or themes. +--- + # WordPress HTML Coding Standards **Version 1.0.0** diff --git a/.windsurf/skills/wordpress-javascript-coding-standards/SKILL.md b/.windsurf/skills/wordpress-javascript-coding-standards/SKILL.md index 4155596c0b..aca02c76e4 100644 --- a/.windsurf/skills/wordpress-javascript-coding-standards/SKILL.md +++ b/.windsurf/skills/wordpress-javascript-coding-standards/SKILL.md @@ -1,3 +1,8 @@ +--- +name: wordpress-javascript-coding-standards +description: WordPress JavaScript coding standards for maintaining, generating, or refactoring JS code. Apply when working with JavaScript files in WordPress plugins or themes. +--- + # WordPress JavaScript Coding Standards **Version 1.0.0** diff --git a/.windsurf/skills/wordpress-php-coding-standards/SKILL.md b/.windsurf/skills/wordpress-php-coding-standards/SKILL.md index 37d509dd28..f5abd39fe7 100644 --- a/.windsurf/skills/wordpress-php-coding-standards/SKILL.md +++ b/.windsurf/skills/wordpress-php-coding-standards/SKILL.md @@ -1,3 +1,8 @@ +--- +name: wordpress-php-coding-standards +description: WordPress PHP coding standards for maintaining, generating, or refactoring PHP code. Apply when working with PHP files in WordPress plugins or themes. +--- + # WordPress PHP Coding Standards **Version 1.0.0** From a730b7de2a1bf9c67a64797f94be1d8786e2ed1b Mon Sep 17 00:00:00 2001 From: Sherv Date: Tue, 10 Feb 2026 11:58:38 +0300 Subject: [PATCH 20/90] refactor(windsurf): consolidate configuration files into centralized structure Remove scattered Windsurf configuration files (rules, skills, workflows) and move to centralized .windsurf/config/ directory structure. Eliminates duplicate PHP/WordPress standards documentation and web search policy rules. Consolidates field type creation guide, code review checklist, and PHPCS workflow into unified configuration system. --- .windsurf/rules/php-wordpress.md | 144 ----------------------- .windsurf/rules/web-search-policy.md | 89 -------------- .windsurf/skills/add-field-type/SKILL.md | 128 -------------------- .windsurf/skills/code-review/SKILL.md | 114 ------------------ .windsurf/workflows/phpcs-check.md | 48 -------- .windsurf/workflows/run-tests.md | 52 -------- .windsurf/workflows/security-audit.md | 124 ------------------- 7 files changed, 699 deletions(-) delete mode 100644 .windsurf/rules/php-wordpress.md delete mode 100644 .windsurf/rules/web-search-policy.md delete mode 100644 .windsurf/skills/add-field-type/SKILL.md delete mode 100644 .windsurf/skills/code-review/SKILL.md delete mode 100644 .windsurf/workflows/phpcs-check.md delete mode 100644 .windsurf/workflows/run-tests.md delete mode 100644 .windsurf/workflows/security-audit.md diff --git a/.windsurf/rules/php-wordpress.md b/.windsurf/rules/php-wordpress.md deleted file mode 100644 index 32d430b79f..0000000000 --- a/.windsurf/rules/php-wordpress.md +++ /dev/null @@ -1,144 +0,0 @@ ---- -trigger: glob -globs: ["*.php"] -description: PHP and WordPress coding standards for Formidable Forms development. ---- - -# PHP & WordPress Standards - -## Database Operations - -### Query Patterns - -```php -// CORRECT - Use prepared statements -$results = $wpdb->get_results( - $wpdb->prepare( - "SELECT * FROM {$wpdb->prefix}frm_items WHERE form_id = %d", - $form_id - ) -); - -// WRONG - Never do this -$results = $wpdb->query( "SELECT * FROM {$wpdb->prefix}frm_items WHERE form_id = " . $form_id ); -``` - -### Formidable Database Patterns - -```php -// Use FrmDb methods when available -FrmDb::get_col( $table, $where, $field ); -FrmDb::get_var( $table, $where, $field ); -FrmDb::get_results( $table, $where, $fields, $args ); -``` - -## Sanitization Functions - -Use the correct function for each data type: - -| Data Type | Function | -| --------- | ------------------------------- | -| Text | `sanitize_text_field()` | -| Textarea | `sanitize_textarea_field()` | -| Email | `sanitize_email()` | -| URL | `esc_url_raw()` | -| Integer | `absint()` or `intval()` | -| Filename | `sanitize_file_name()` | -| HTML | `wp_kses()` or `wp_kses_post()` | -| Key | `sanitize_key()` | - -## Escaping Functions - -Escape based on output context: - -| Context | Function | -| ---------- | ------------------ | -| HTML | `esc_html()` | -| Attribute | `esc_attr()` | -| URL | `esc_url()` | -| JavaScript | `esc_js()` | -| Textarea | `esc_textarea()` | -| SQL | `$wpdb->prepare()` | - -## Formidable Helper Methods - -```php -// Use FrmAppHelper for common operations -FrmAppHelper::get_param( $param, $default, $src, $sanitize ); -FrmAppHelper::get_post_param( $param, $default, $sanitize ); -FrmAppHelper::simple_get( $param, $sanitize, $default ); -FrmAppHelper::kses( $value, $allowed ); -FrmAppHelper::sanitize_with_html( $value ); -``` - -## Hooks and Filters - -### Naming Convention - -```php -// Lite hooks -do_action( 'frm_action_name', $args ); -apply_filters( 'frm_filter_name', $value, $args ); - -// Pro hooks -do_action( 'frm_pro_action_name', $args ); -apply_filters( 'frm_pro_filter_name', $value, $args ); -``` - -### Hook Documentation - -```php -/** - * Fires after form submission. - * - * @since 2.0 - * - * @param int $entry_id Entry ID. - * @param int $form_id Form ID. - * @param array $values Submitted values. - */ -do_action( 'frm_after_create_entry', $entry_id, $form_id, $values ); -``` - -## AJAX Handlers - -```php -// Always verify nonce and capabilities -public static function ajax_handler() { - check_ajax_referer( 'frm_ajax', 'nonce' ); - - if ( ! current_user_can( 'frm_edit_forms' ) ) { - wp_die( -1 ); - } - - // Handler code... - - wp_send_json_success( $data ); -} -``` - -## Performance Considerations - -- Cache expensive queries with transients or object cache. -- Use LIMIT in queries - never fetch unlimited results. -- Avoid queries in loops - batch operations when possible. -- Use `wp_cache_get()` / `wp_cache_set()` for repeated lookups. -- Index database columns used in WHERE clauses. - -## Internationalization - -```php -// Text domain for Lite -__( 'Text', 'formidable' ); -esc_html__( 'Text', 'formidable' ); - -// Text domain for Pro -__( 'Text', 'formidable-pro' ); - -// With placeholders -sprintf( - /* translators: %s: Field name */ - __( 'The %s field is required.', 'formidable' ), - $field_name -); -``` diff --git a/.windsurf/rules/web-search-policy.md b/.windsurf/rules/web-search-policy.md deleted file mode 100644 index 86da9dd7d9..0000000000 --- a/.windsurf/rules/web-search-policy.md +++ /dev/null @@ -1,89 +0,0 @@ ---- -trigger: always_on -description: Mandatory web search policy for WordPress and Formidable development. ---- - -# Web Search Policy - -## Mandatory Searches - -You MUST search the web in these scenarios: - -### 1. WordPress Functions - -Before using ANY WordPress function you're not 100% certain about: - -``` -@web WordPress [function_name] parameters return type -``` - -### 2. Database Operations - -Before writing any database query: - -``` -@web WordPress VIP database query best practices -@web WordPress $wpdb prepare examples -``` - -### 3. Security Functions - -Before using sanitization or escaping: - -``` -@web WordPress sanitize_text_field vs sanitize_textarea_field -@web WordPress esc_html vs esc_attr when to use -``` - -### 4. Deprecated Functions - -When you see a function that might be deprecated: - -``` -@web WordPress [function_name] deprecated alternative -``` - -### 5. Hooks and Actions - -Before adding new hooks: - -``` -@web WordPress hook naming conventions best practices -``` - -### 6. Performance Critical Code - -For loops, queries, or frequently executed code: - -``` -@web WordPress VIP performance optimization [topic] -``` - -## Search Sources Priority - -1. **developer.wordpress.org** - Official WordPress documentation. -2. **docs.wpvip.com** - WordPress VIP best practices. -3. **developer.wordpress.org/plugins** - Plugin development handbook. -4. **make.wordpress.org/core** - Core development decisions. - -## Quick Reference URLs - -When searching, prioritize these docs: - -- PHP Standards: `developer.wordpress.org/coding-standards/wordpress-coding-standards/php/` -- Security: `developer.wordpress.org/plugins/security/` -- Database: `developer.wordpress.org/plugins/database/` -- VIP Code Standards: `docs.wpvip.com/technical-references/code-review/` -- VIP Performance: `docs.wpvip.com/technical-references/caching/` - -## Search Before Action - -ALWAYS search BEFORE: - -- Adding a new WordPress function call. -- Writing a database query. -- Implementing security measures. -- Making performance optimizations. -- Adding new hooks or filters. - -The cost of searching is minimal compared to the cost of getting it wrong. diff --git a/.windsurf/skills/add-field-type/SKILL.md b/.windsurf/skills/add-field-type/SKILL.md deleted file mode 100644 index c52ee22855..0000000000 --- a/.windsurf/skills/add-field-type/SKILL.md +++ /dev/null @@ -1,128 +0,0 @@ ---- -name: add-field-type -description: Guides creation of a new Formidable field type following all patterns and standards ---- - -# Add New Field Type - -Follow these steps to create a new field type for Formidable Forms. - -## Step 1: Determine Location - -- **Lite field** → `formidable-master/classes/models/fields/` -- **Pro field** → `formidable-pro-master/classes/models/fields/` - -## Step 2: Create Field Class - -Create new file: `FrmFieldYourType.php` (Lite) or `FrmProFieldYourType.php` (Pro) - -```php - array( - 'name' => __( 'Your Field', 'formidable-pro' ), - 'icon' => 'frmfont frm_your_icon', -), -``` - -## Step 5: Create Form Builder View - -Create view file: `classes/views/frmpro-fields/back-end/field-yourtype.php` - -## Step 6: Add Tests - -Create test file: `tests/fields/test_FrmProFieldYourType.php` - -```php -factory->field->create_and_get( - array( - 'type' => 'your_type', - 'form_id' => $this->factory->form->create(), - ) - ); - $this->assertEquals( 'your_type', $field->type, 'Field type should be your_type.' ); - } -} -``` - -## Checklist - -- [ ] Field class extends `FrmFieldType` -- [ ] Type registered in factory -- [ ] Added to field selection array -- [ ] Form builder view created -- [ ] Front-end rendering works -- [ ] Value saving works -- [ ] Validation works -- [ ] Tests written -- [ ] PHPDoc complete diff --git a/.windsurf/skills/code-review/SKILL.md b/.windsurf/skills/code-review/SKILL.md deleted file mode 100644 index bb20a0d956..0000000000 --- a/.windsurf/skills/code-review/SKILL.md +++ /dev/null @@ -1,114 +0,0 @@ ---- -name: code-review -description: Performs thorough code review following Formidable and WordPress VIP standards ---- - -# Code Review Guidelines - -Use this skill to review code changes for Formidable Forms. - -## Review Process - -### 1. Security Review - -Check for these security issues: - -- [ ] **SQL Injection** - All queries use `$wpdb->prepare()` -- [ ] **XSS** - All output escaped with appropriate `esc_*` function -- [ ] **CSRF** - AJAX handlers verify nonce -- [ ] **Authorization** - Capability checks in place -- [ ] **Input Sanitization** - All user input sanitized - -### 2. WordPress VIP Compliance - -- [ ] No direct database queries without prepare() -- [ ] No `extract()`, `eval()`, or `create_function()` -- [ ] Query results limited with LIMIT -- [ ] No `file_get_contents()` for remote URLs -- [ ] Expensive operations cached - -### 3. Formidable Standards - -- [ ] Class naming follows `Frm` or `FrmPro` prefix -- [ ] Hook naming follows `frm_` or `frm_pro_` prefix -- [ ] Correct text domain used -- [ ] Factory pattern used for field types -- [ ] Helper methods used where available - -### 4. Code Quality - -- [ ] Functions under 100 lines -- [ ] Cyclomatic complexity under 10 -- [ ] Line length under 180 characters -- [ ] No code duplication -- [ ] Early returns used -- [ ] Clear variable naming - -### 5. Documentation - -- [ ] PHPDoc for all public methods -- [ ] `@since` tags for new methods -- [ ] `{@inheritDoc}` for overridden methods -- [ ] Comments end with periods -- [ ] Translators comments for sprintf - -### 6. Compatibility - -- [ ] Works when Pro inactive -- [ ] Backward compatible -- [ ] No PHP warnings/notices -- [ ] Handles empty/null values - -## Common Issues to Flag - -```php -// BAD: Direct query -$wpdb->query( "DELETE FROM ... WHERE id = $id" ); - -// GOOD: Prepared query -$wpdb->query( $wpdb->prepare( "DELETE FROM ... WHERE id = %d", $id ) ); - -// BAD: Unescaped output -echo $user_input; - -// GOOD: Escaped output -echo esc_html( $user_input ); - -// BAD: No nonce check -public static function ajax_handler() { - // Handler code... -} - -// GOOD: Nonce verified -public static function ajax_handler() { - check_ajax_referer( 'frm_ajax', 'nonce' ); - // Handler code... -} -``` - -## Review Output Format - -```markdown -## Code Review: [File/PR Name] - -### Security - -- ✅ SQL injection protection verified -- ⚠️ Missing escaping at line X - -### VIP Compliance - -- ✅ All queries prepared -- ❌ Missing LIMIT on query at line Y - -### Standards - -- ✅ Naming conventions followed -- ⚠️ Function exceeds 100 lines - -### Recommendations - -1. Add escaping at line X -2. Add LIMIT to query at line Y -3. Consider extracting method for lines A-B -``` diff --git a/.windsurf/workflows/phpcs-check.md b/.windsurf/workflows/phpcs-check.md deleted file mode 100644 index dd35f66f63..0000000000 --- a/.windsurf/workflows/phpcs-check.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -name: phpcs-check -description: Run PHP CodeSniffer to check code against Formidable and WordPress VIP standards ---- - -# PHPCS Check - -## Steps - -1. Determine the target file or directory: - - If a specific file was mentioned, check that file - - If a directory was mentioned, check that directory - - Otherwise, check recently modified files - -2. Run PHPCS on Lite plugin: - - ```bash - cd /Users/sherv/Local\ Sites/formidable/app/public/wp-content/plugins/formidable-master - vendor/bin/phpcs --standard=phpcs.xml [target] - ``` - -3. Run PHPCS on Pro plugin: - - ```bash - cd /Users/sherv/Local\ Sites/formidable/app/public/wp-content/plugins/formidable-pro-master - vendor/bin/phpcs --standard=phpcs.xml [target] - ``` - -4. For auto-fixing issues: - - ```bash - vendor/bin/phpcbf --standard=phpcs.xml [target] - ``` - -5. Report findings: - - List errors by severity - - Explain what each error means - - Suggest fixes for common issues - -## Common PHPCS Errors - -| Error | Fix | -| -------------------------- | ------------------------------------------------- | -| Missing nonce verification | Add `check_ajax_referer()` or `wp_verify_nonce()` | -| Unescaped output | Add appropriate `esc_*` function | -| Unsanitized input | Add appropriate `sanitize_*` function | -| Unprepared SQL query | Use `$wpdb->prepare()` | -| Strict comparison | Use `===` instead of `==` | diff --git a/.windsurf/workflows/run-tests.md b/.windsurf/workflows/run-tests.md deleted file mode 100644 index e300095591..0000000000 --- a/.windsurf/workflows/run-tests.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -name: run-tests -description: Run PHPUnit tests for Formidable Forms plugins ---- - -# Run Formidable Tests - -## Steps - -1. Determine which plugin to test based on the current context: - - If working in `formidable-master` → Run Lite tests - - If working in `formidable-pro-master` → Run Pro tests - - If working in an add-on → Run add-on tests - -2. Check if PHPUnit is available: - - ```bash - vendor/bin/phpunit --version - ``` - -3. Run the appropriate tests: - - **For Lite plugin:** - - ```bash - cd /Users/sherv/Local\ Sites/formidable/app/public/wp-content/plugins/formidable-master - vendor/bin/phpunit - ``` - - **For Pro plugin:** - - ```bash - cd /Users/sherv/Local\ Sites/formidable/app/public/wp-content/plugins/formidable-pro-master - vendor/bin/phpunit - ``` - -4. If a specific test file was modified, run only that test: - - ```bash - vendor/bin/phpunit tests/phpunit/path/to/test_file.php - ``` - -5. If a specific test method needs to run: - - ```bash - vendor/bin/phpunit --filter test_method_name - ``` - -6. Report the results: - - Number of tests passed/failed - - Any error messages - - Suggestions for fixing failures diff --git a/.windsurf/workflows/security-audit.md b/.windsurf/workflows/security-audit.md deleted file mode 100644 index 8ee1173f77..0000000000 --- a/.windsurf/workflows/security-audit.md +++ /dev/null @@ -1,124 +0,0 @@ ---- -name: security-audit -description: Perform security audit on Formidable Forms code following WordPress VIP standards ---- - -# Security Audit Workflow - -## Step 1: Identify Scope - -Determine what to audit: - -- Specific file or function -- Recent changes (PR or commit) -- Entire feature area - -## Step 2: Input Handling Audit - -Check ALL user input sources: - -```php -// Sources to check: -$_GET, $_POST, $_REQUEST, $_SERVER, $_COOKIE -// Formidable helpers: -FrmAppHelper::get_param() -FrmAppHelper::get_post_param() -FrmAppHelper::simple_get() -``` - -For each input, verify: - -- [ ] Sanitized with appropriate function -- [ ] Type validated where needed -- [ ] Default values are safe - -## Step 3: Output Escaping Audit - -Check ALL output locations: - -| Context | Required Function | -| -------------- | ------------------ | -| HTML text | `esc_html()` | -| HTML attribute | `esc_attr()` | -| URL | `esc_url()` | -| JavaScript | `esc_js()` | -| Textarea | `esc_textarea()` | -| JSON | `wp_json_encode()` | - -Verify: - -- [ ] Every echo/print is escaped -- [ ] Correct function for context -- [ ] No raw user data in output - -## Step 4: SQL Injection Audit - -Check ALL database queries: - -```php -// MUST use prepare() for: -$wpdb->query() -$wpdb->get_results() -$wpdb->get_row() -$wpdb->get_var() -$wpdb->get_col() -``` - -Verify: - -- [ ] All queries use `$wpdb->prepare()` -- [ ] Correct placeholders used (%d, %s, %f) -- [ ] Table names properly prefixed -- [ ] LIMIT clauses present - -## Step 5: AJAX Security Audit - -For each AJAX handler: - -```php -// Required checks: -check_ajax_referer( 'frm_ajax', 'nonce' ); -if ( ! current_user_can( 'capability' ) ) { - wp_die( -1 ); -} -``` - -Verify: - -- [ ] Nonce verification present -- [ ] Capability check present -- [ ] Proper error response on failure - -## Step 6: File Operation Audit - -Check file operations: - -- [ ] No direct file includes with user input -- [ ] File uploads validated (type, size, content) -- [ ] WP_Filesystem used where appropriate -- [ ] No arbitrary file reads/writes - -## Step 7: Report Findings - -Generate security report: - -```markdown -## Security Audit Report: [Target] - -### Critical Issues (Fix Immediately) - -- [ ] Issue description with file:line - -### High Priority Issues - -- [ ] Issue description with file:line - -### Medium Priority Issues - -- [ ] Issue description with file:line - -### Recommendations - -1. [Specific recommendation] -2. [Specific recommendation] -``` From 603164e5727454114e904dd9171e97eacbe1f2fd Mon Sep 17 00:00:00 2001 From: Sherv Date: Tue, 10 Feb 2026 11:59:56 +0300 Subject: [PATCH 21/90] refactor(windsurf): remove redundant AGENTS.md and README.md from root .windsurf directory Remove duplicate AGENTS.md project guidelines and README.md documentation that were superseded by centralized configuration structure in .windsurf/config/. Eliminates redundant project structure overview, development rules, testing instructions, and Windsurf mechanism documentation now maintained in consolidated config directory. --- .windsurf/AGENTS.md | 72 ------------------ .windsurf/README.md | 179 -------------------------------------------- 2 files changed, 251 deletions(-) delete mode 100644 .windsurf/AGENTS.md delete mode 100644 .windsurf/README.md diff --git a/.windsurf/AGENTS.md b/.windsurf/AGENTS.md deleted file mode 100644 index 3f52abf83e..0000000000 --- a/.windsurf/AGENTS.md +++ /dev/null @@ -1,72 +0,0 @@ -# Formidable Forms Development Guidelines - -This is an enterprise WordPress plugin ecosystem. All changes must follow strict quality standards. - -## Project Structure - -- **formidable-master** - Lite plugin (free version) -- **formidable-pro-master** - Pro plugin (premium features) -- **formidable-views** - Views add-on -- **formidable-\*-master** - Other add-ons (ai, dates, geo, signature, surveys, etc.) - -## Architecture Overview - -``` -classes/ -├── controllers/ # Handle requests, routing, admin pages -├── factories/ # Object creation (FrmFieldFactory) -├── helpers/ # Utility functions (FrmAppHelper, FrmFieldsHelper) -├── models/ # Data models (FrmForm, FrmField, FrmEntry) -│ └── fields/ # Field type classes (FrmFieldType, FrmProFieldVirtual) -├── views/ # PHP templates for rendering -└── widgets/ # WordPress widgets -``` - -## Key Classes - -| Class | Purpose | -| ----------------- | ---------------------------------------- | -| `FrmAppHelper` | Core utility methods, sanitization, URLs | -| `FrmFieldFactory` | Creates field type instances | -| `FrmFieldType` | Base class for all field types | -| `FrmDb` | Database operations wrapper | -| `FrmForm` | Form CRUD operations | -| `FrmField` | Field CRUD operations | -| `FrmEntry` | Entry CRUD operations | - -## Development Rules - -1. **Search before coding** - Always search codebase and WordPress docs first. -2. **Minimal changes** - Make smallest change that solves the problem. -3. **Pro compatibility** - Code must work with and without Pro active. -4. **Test coverage** - Add tests for new functionality. -5. **PHPDoc** - Document all public methods with proper tags. - -## Testing - -```bash -# Run all Lite tests -cd formidable-master && vendor/bin/phpunit - -# Run all Pro tests -cd formidable-pro-master && vendor/bin/phpunit - -# Run specific test file -vendor/bin/phpunit tests/phpunit/fields/test_FrmFieldType.php - -# Run specific test method -vendor/bin/phpunit --filter test_method_name -``` - -## Code Review Checklist - -- [ ] Follows WordPress Coding Standards -- [ ] Follows WordPress VIP standards -- [ ] All user input sanitized -- [ ] All output escaped -- [ ] Database queries use prepare() -- [ ] AJAX handlers verify nonce and capabilities -- [ ] Works when Pro is inactive -- [ ] No PHP warnings or notices -- [ ] PHPDoc comments complete -- [ ] Tests added/updated diff --git a/.windsurf/README.md b/.windsurf/README.md deleted file mode 100644 index 5c32962ae4..0000000000 --- a/.windsurf/README.md +++ /dev/null @@ -1,179 +0,0 @@ -# Windsurf Configuration for Formidable Forms - -This directory contains comprehensive Windsurf configuration files for Formidable Forms development following WordPress VIP standards. - -## How It All Works Together - -Windsurf has **four mechanisms** for customizing Cascade behavior. Here's how they interact: - -| Mechanism | Purpose | Trigger | Best For | -| ------------- | ----------------------------- | ----------------------------------------- | ------------------------------------ | -| **Rules** | Behavioral guidelines | Always-on, glob, model decision, manual | Coding style, project conventions | -| **Skills** | Complex tasks with resources | Auto (progressive disclosure) or @mention | Multi-step workflows, reference docs | -| **Workflows** | Repetitive task sequences | /slash-command | Deployment, testing, bug fixing | -| **AGENTS.md** | Directory-scoped instructions | Auto based on file location | Location-specific conventions | - -### Activation Flow - -1. **AGENTS.md** at root applies to ALL files (always on) -2. **Rules** with `trigger: always_on` apply to every conversation -3. **Rules** with `trigger: glob` apply when matching files are involved -4. **Skills** auto-invoke based on task relevance (via description matching) -5. **Workflows** invoke explicitly via `/command` - -## Directory Structure - -``` -.windsurf/ -├── README.md # This file -├── AGENTS.md # Root-level project guidelines (always applies) -├── rules/ -│ ├── conventional-commits.md # Commit message format (always_on) -│ ├── formidable-core.md # Core development rules (always_on) -│ ├── php-wordpress.md # PHP/WordPress standards (*.php glob) -│ └── web-search-policy.md # Mandatory web search rules (always_on) -├── skills/ -│ ├── add-field-type/ # Guide for creating new field types -│ │ └── SKILL.md -│ ├── code-review/ # Code review checklist and guidelines -│ │ └── SKILL.md -│ ├── wordpress-php-coding-standards/ -│ │ ├── SKILL.md # Skill definition with frontmatter -│ │ ├── AGENTS.md # Full reference (supporting file) -│ │ └── rules/ # Detailed rule files -│ ├── wordpress-css-coding-standards/ -│ ├── wordpress-javascript-coding-standards/ -│ ├── wordpress-html-coding-standards/ -│ └── wordpress-accessibility-coding-standards/ -└── workflows/ - ├── fix-bug.md # /fix-bug workflow - ├── phpcs-check.md # /phpcs-check workflow - ├── run-tests.md # /run-tests workflow - └── security-audit.md # /security-audit workflow -``` - -## Rules (Automatic Application) - -Rules are automatically applied based on their trigger: - -| Rule | Trigger | When Active | -| ------------------------- | ------------- | ----------------------- | -| `conventional-commits.md` | `always_on` | Every conversation | -| `formidable-core.md` | `always_on` | Every conversation | -| `web-search-policy.md` | `always_on` | Every conversation | -| `php-wordpress.md` | `glob: *.php` | When PHP files involved | - -## Skills (Progressive Disclosure + Manual) - -Skills are invoked in two ways: - -### Automatic Invocation - -Cascade reads the skill's `description` field and automatically invokes when relevant: - -- Working with PHP files → `wordpress-php-coding-standards` may auto-invoke -- Working with CSS files → `wordpress-css-coding-standards` may auto-invoke -- Creating new field types → `add-field-type` may auto-invoke - -### Manual Invocation - -Type `@skill-name` in Cascade: - -- `@wordpress-php-coding-standards` - Full PHP standards reference -- `@wordpress-css-coding-standards` - CSS standards reference -- `@wordpress-javascript-coding-standards` - JavaScript standards reference -- `@wordpress-html-coding-standards` - HTML standards reference -- `@wordpress-accessibility-coding-standards` - WCAG 2.2 Level AA standards -- `@add-field-type` - Step-by-step field type creation -- `@code-review` - Code review checklist - -### Why Skills Have AGENTS.md Files - -The `AGENTS.md` files inside skill folders are **supporting resources**, not directory-scoped rules. When a skill is invoked, Cascade can read these comprehensive reference documents. - -## Workflows (Slash Commands) - -Invoke workflows using slash commands: - -| Command | Purpose | -| ----------------- | ------------------------------ | -| `/fix-bug` | Structured bug fixing workflow | -| `/run-tests` | Run PHPUnit tests | -| `/phpcs-check` | Run PHP CodeSniffer | -| `/security-audit` | Security audit checklist | - -### Skills Apply During Workflows - -When using `/fix-bug` on a PHP file, Cascade will: - -1. Follow the workflow steps -2. Auto-invoke `wordpress-php-coding-standards` if relevant -3. Apply all `always_on` rules (formidable-core, web-search-policy, conventional-commits) - -## Ensuring WordPress Standards Always Apply - -To ensure WordPress coding standards always apply: - -### Option 1: Let Auto-Invocation Work (Recommended) - -The skill descriptions are crafted to trigger when working with relevant file types: - -- "Apply when working with PHP files" → triggers for PHP work -- "Apply when working with CSS files" → triggers for CSS work - -### Option 2: Explicit @mention - -Add `@wordpress-php-coding-standards` to your prompt when you want explicit standards enforcement. - -### Option 3: Add to Workflow - -Edit workflows to include skill references: - -```markdown -## Phase 5: Implement the Fix - -Follow @wordpress-php-coding-standards for all code changes. -``` - -## Commit Messages - -All commits must follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/): - -``` -(): - -[optional body] - -[optional footer(s)] -``` - -**Types:** `fix`, `feat`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore` - -**Scopes:** `builder`, `entries`, `fields`, `api`, `admin`, `frontend`, `db`, `i18n`, `security`, `deps` - -**Examples:** - -``` -fix(fields): resolve date field validation error -feat(builder): add drag-and-drop field reordering -refactor(entries)!: change entry meta storage format -``` - -## Lint Warnings - -The markdown lint warnings (MD033, MD040) in these files are **intentional**: - -- XML-style tags (``, ``) help Cascade parse grouped rules -- Code blocks without language specs are for commit message examples - -These do not affect Windsurf functionality. - -## Related Documentation - -- [Windsurf Memories & Rules](https://docs.windsurf.com/windsurf/cascade/memories) -- [Windsurf Skills](https://docs.windsurf.com/windsurf/cascade/skills) -- [Windsurf Workflows](https://docs.windsurf.com/windsurf/cascade/workflows) -- [Windsurf AGENTS.md](https://docs.windsurf.com/windsurf/cascade/agents-md) -- [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) -- [WordPress Coding Standards](https://developer.wordpress.org/coding-standards/) -- [WordPress VIP Documentation](https://docs.wpvip.com/) From 1be6bd8d84d354196dbe0ef5f77e46038c049425 Mon Sep 17 00:00:00 2001 From: Sherv Date: Tue, 10 Feb 2026 13:59:41 +0300 Subject: [PATCH 22/90] feat(windsurf): add conventional commits rule for standardized commit messages Add comprehensive conventional commits specification rule with type definitions, scope guidelines, breaking change indicators, and AI generation instructions. Includes SemVer mapping, formatting rules, and practical examples for consistent commit message format across the project. --- .../rules/enterprise/conventional-commits.md | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 .windsurf/rules/enterprise/conventional-commits.md diff --git a/.windsurf/rules/enterprise/conventional-commits.md b/.windsurf/rules/enterprise/conventional-commits.md new file mode 100644 index 0000000000..3e2d42be42 --- /dev/null +++ b/.windsurf/rules/enterprise/conventional-commits.md @@ -0,0 +1,106 @@ +--- +trigger: always_on +description: Enforces conventional commit message format for all git commits in the project. +--- + +# Conventional Commit Messages + +All commit messages MUST follow the Conventional Commits 1.0.0 specification. + +## Format + +``` +(): + +[optional body] + +[optional footer(s)] +``` + +## Types + +| Type | Description | SemVer | +| ---------- | ---------------------------------------------------- | ------ | +| `fix` | Bug fix (patches a bug in the codebase) | PATCH | +| `feat` | New feature (adds functionality to the codebase) | MINOR | +| `docs` | Documentation only changes | - | +| `style` | Code style changes (formatting, whitespace, etc.) | - | +| `refactor` | Code change that neither fixes a bug nor adds feature| - | +| `perf` | Performance improvement | - | +| `test` | Adding or correcting tests | - | +| `build` | Changes to build system or external dependencies | - | +| `ci` | CI configuration changes | - | +| `chore` | Other changes that don't modify src or test files | - | + +## Scope (Optional) + +The scope provides additional context about what part of the codebase the commit affects: + +- `builder` - Form builder related changes +- `entries` - Entry management changes +- `fields` - Field types or field handling +- `api` - API endpoints or integrations +- `admin` - Admin UI changes +- `frontend` - Front-end form display +- `db` - Database operations +- `i18n` - Internationalization +- `security` - Security fixes or improvements +- `deps` - Dependencies + +## Breaking Changes + +Indicate breaking changes with: + +1. `!` after type/scope: `feat(api)!: change response format` +2. `BREAKING CHANGE:` footer in the commit body + +## Examples + +``` +fix(fields): resolve date field validation error + +The date field was incorrectly validating dates in non-US formats. +Added locale-aware date parsing. + +Fixes #1234 +``` + +``` +feat(builder): add drag-and-drop field reordering + +Implements smooth drag-and-drop functionality for reordering fields +in the form builder interface. +``` + +``` +fix: prevent XSS in field labels + +Escape HTML entities in field labels before output. +``` + +``` +refactor(entries)!: change entry meta storage format + +BREAKING CHANGE: Entry meta now uses JSON encoding instead of +serialized PHP arrays. Run migration script before updating. +``` + +## Rules + +1. Type MUST be lowercase. +2. Description MUST start with lowercase letter. +3. Description MUST NOT end with a period. +4. Description MUST be imperative mood ("add" not "added" or "adds"). +5. Body MUST be separated from description by a blank line. +6. Breaking changes MUST be indicated with `!` or `BREAKING CHANGE:` footer. +7. Keep description under 72 characters. + +## AI Commit Message Generation + +When generating commit messages: + +1. Analyze the staged changes to determine the appropriate type. +2. Identify the scope from the files/directories changed. +3. Write a concise description in imperative mood. +4. Add body with details if the change is complex. +5. Include issue references if mentioned in the conversation. From 058954badab78f234857d44752ecd7ef0f8861d6 Mon Sep 17 00:00:00 2001 From: Sherv Date: Tue, 10 Feb 2026 14:02:49 +0300 Subject: [PATCH 23/90] feat(windsurf): add enterprise code change principles rule for safe modifications Add comprehensive enterprise development principles rule covering analysis phase, solution selection, change execution, mandatory research requirements, and verification checklist. Includes core principles for minimal-scope changes, backward compatibility, and codebase research requirements before modifications. --- .windsurf/rules/enterprise/principles.md | 72 ++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 .windsurf/rules/enterprise/principles.md diff --git a/.windsurf/rules/enterprise/principles.md b/.windsurf/rules/enterprise/principles.md new file mode 100644 index 0000000000..9c11ff5871 --- /dev/null +++ b/.windsurf/rules/enterprise/principles.md @@ -0,0 +1,72 @@ +--- +trigger: always_on +description: Enterprise code change principles for safe, minimal-scope modifications. +--- + +# Enterprise Code Change Principles + +Critical principles for enterprise-level plugin development. + +--- + +## Core Principles + +1. **NEVER guess** — Always search and verify before making changes +2. **Minimal scope** — Fix at the most specific location, closest to the problem +3. **Backward compatibility** — Maintain 100% compatibility with existing callers +4. **No custom solutions** — Never invent new patterns; use existing ones +5. **User changes are final** — If user makes manual changes, treat as authoritative + +--- + +## Analysis Phase + +Before proposing solutions: + +1. **Read and understand** the complete issue +2. **Identify ALL affected locations** in the codebase +3. **Map dependencies** — What calls this code? What does this code call? +4. **Check plugin requirements** — Must code work standalone or require dependencies? + +--- + +## Solution Selection + +- Propose 2-3 solutions with trade-offs clearly stated +- Choose solution with minimal scope and lowest risk +- Fix at the most specific location +- Prefer adding safety checks over refactoring +- **Verify changes are not overkill** — If affecting several areas, analyze all to ensure fix is not excessive + +--- + +## Change Execution + +- Make the smallest change that completely solves the problem +- Never change method signatures, return types, or data structures +- Never refactor unrelated code in the same commit +- Add defensive checks where data comes in, not where used everywhere + +--- + +## Mandatory Research + +Before ANY code change involving platform functions: + +1. **Search codebase first** — Understand existing patterns +2. **Search official docs** — Function parameters, return types, deprecated alternatives +3. **Search platform-specific docs** — Performance and security best practices +4. **Verify alignment** — Ensure approach matches existing patterns + +--- + +## Change Verification Checklist + +Before completing any change: + +- [ ] Does this change break any existing functionality? +- [ ] Does this work with all plugin configurations? +- [ ] Are there warnings/errors in any scenario? +- [ ] Is the change backward compatible? +- [ ] Have you searched docs for best practices? +- [ ] Can this be tested without touching other code? From 3b9dd2b621e2bf2063d708a21cf7ac2613896c33 Mon Sep 17 00:00:00 2001 From: Sherv Date: Tue, 10 Feb 2026 14:16:25 +0300 Subject: [PATCH 24/90] feat(windsurf): add Formidable Forms patterns rule for plugin-specific conventions Add comprehensive Formidable Forms patterns rule covering pattern discovery process, class/method/hook naming conventions, text domains, factory pattern usage, helper class utilities (FrmAppHelper, FrmDb, FrmFieldsHelper), and Pro plugin awareness checks. Includes mandatory pattern research requirements before code changes and examples for common operations. --- .windsurf/rules/formidable/patterns.md | 109 +++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 .windsurf/rules/formidable/patterns.md diff --git a/.windsurf/rules/formidable/patterns.md b/.windsurf/rules/formidable/patterns.md new file mode 100644 index 0000000000..eb4f03b83e --- /dev/null +++ b/.windsurf/rules/formidable/patterns.md @@ -0,0 +1,109 @@ +--- +trigger: always_on +description: Formidable Forms plugin-specific patterns and conventions. Always active for this workspace. +--- + +# Formidable Forms Patterns + +Formidable-specific patterns, naming conventions, and architectural decisions. + +--- + +## Pattern Discovery Process + +Before making ANY change: + +1. **Find existing patterns** — Search models, controllers, helpers for similar functionality +2. **Study pattern usage** — Search ALL places using the pattern +3. **Trace parent hierarchy** — Search parent files up to plugin root +4. **Iterate if needed** — If better pattern found, repeat from step 1 + +**Never invent custom solutions if existing patterns exist.** + +--- + +## Class Naming + +- **Lite plugin:** `FrmClassName` (e.g., `FrmAppHelper`, `FrmDb`, `FrmField`) +- **Pro plugin:** `FrmProClassName` (e.g., `FrmProAppHelper`, `FrmProField`) + +--- + +## Method Naming + +- **Public methods:** `snake_case` +- **Legacy methods:** Preserve existing `camelCase` for backward compatibility + +--- + +## Hook Naming + +- **Lite hooks:** `frm_hook_name` +- **Pro hooks:** `frm_pro_hook_name` + +--- + +## Text Domains + +- **Lite:** `formidable` +- **Pro and add-ons:** `formidable-pro` + +--- + +## Factory Pattern + +Use `FrmFieldFactory::get_field_type()` for field type instances. + +```php +$field_obj = FrmFieldFactory::get_field_type( $field ); +``` + +--- + +## Helper Classes + +### FrmAppHelper + +Common utility methods for the plugin. + +```php +FrmAppHelper::get_post_param( 'key', 'default', 'sanitize_text_field' ); +FrmAppHelper::get_param( 'key', 'default', 'get', 'sanitize_text_field' ); +FrmAppHelper::simple_get( 'key', 'sanitize_text_field' ); +``` + +### FrmDb + +Database operations wrapper. + +```php +FrmDb::get_col( $table, $where, $field ); +FrmDb::get_row( $table, $where, $fields ); +FrmDb::get_results( $table, $where, $fields ); +FrmDb::get_var( $table, $where, $field ); +``` + +### FrmFieldsHelper + +Field-specific utilities. + +--- + +## Pro Plugin Awareness + +**All code must work when:** + +- Pro is active +- Pro is inactive + +```php +// Check if Pro is active +if ( class_exists( 'FrmProAppHelper' ) ) { + // Pro-specific code +} + +// Or use the constant +if ( defined( 'FRM_PRO_VERSION' ) ) { + // Pro-specific code +} +``` From 97335b7e34c005bb11720e4ce3442683c3d4c5cc Mon Sep 17 00:00:00 2001 From: Sherv Date: Tue, 10 Feb 2026 14:17:14 +0300 Subject: [PATCH 25/90] feat(windsurf): add Formidable Forms testing conventions rule for test development standards Add comprehensive testing conventions rule covering test class inheritance (FrmUnitTest/FrmProUnitTest), factory method usage for test data creation, protected method testing utilities, assertion message formatting requirements, mandatory test scenarios (Pro active/inactive, empty data, edge cases), and PHPUnit/PHPCS command examples. --- .windsurf/rules/formidable/testing.md | 91 +++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 .windsurf/rules/formidable/testing.md diff --git a/.windsurf/rules/formidable/testing.md b/.windsurf/rules/formidable/testing.md new file mode 100644 index 0000000000..f8777781e6 --- /dev/null +++ b/.windsurf/rules/formidable/testing.md @@ -0,0 +1,91 @@ +--- +trigger: model_decision +description: Formidable Forms testing conventions and requirements. Apply when writing or modifying tests. +--- + +# Formidable Forms Testing + +Testing conventions specific to Formidable Forms plugin. + +--- + +## Test Classes + +- **Lite tests:** Extend `FrmUnitTest` +- **Pro tests:** Extend `FrmProUnitTest` + +```php +class Test_FrmField extends FrmUnitTest { + // Tests +} +``` + +--- + +## Test Data Creation + +Use factory methods for test data. + +```php +$form = $this->factory->form->create_and_get(); +$field = $this->factory->field->create_and_get( array( + 'form_id' => $form->id, + 'type' => 'text', +) ); +$entry = $this->factory->entry->create_and_get( array( + 'form_id' => $form->id, +) ); +``` + +--- + +## Testing Protected Methods + +```php +$result = $this->run_private_method( array( $object, 'method_name' ), array( $arg1, $arg2 ) ); +``` + +--- + +## Assertion Messages + +All assertion messages must end with a period. + +```php +$this->assertEquals( $expected, $actual, 'The value should match expected.' ); +$this->assertTrue( $condition, 'Condition should be true.' ); +``` + +--- + +## Required Test Scenarios + +Every fix/feature must be tested with: + +1. **Pro active** — With formidable-pro plugin enabled +2. **Pro inactive** — Lite-only scenario +3. **Empty data** — Empty arrays, null values, missing keys +4. **Edge cases** — Boundary conditions, special characters + +--- + +## Running Tests + +```bash +# All tests +vendor/bin/phpunit + +# Specific test file +vendor/bin/phpunit tests/phpunit/test-file.php + +# Specific test method +vendor/bin/phpunit --filter test_method_name +``` + +--- + +## Code Style Check + +```bash +vendor/bin/phpcs --standard=phpcs.xml path/to/file.php +``` From bdec0b5351cc8840a14747f8bd441f8b3603af60 Mon Sep 17 00:00:00 2001 From: Sherv Date: Tue, 10 Feb 2026 15:02:58 +0300 Subject: [PATCH 26/90] refactor(windsurf): standardize punctuation in enterprise and testing rules from em dash to colon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace em dashes (—) with colons (:) in principle descriptions, analysis steps, research requirements, pattern discovery process, and mandatory test scenarios for consistent formatting across enterprise principles, Formidable patterns, and testing conventions documentation. --- .windsurf/rules/enterprise/principles.md | 24 ++++++++++++------------ .windsurf/rules/formidable/patterns.md | 8 ++++---- .windsurf/rules/formidable/testing.md | 8 ++++---- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/.windsurf/rules/enterprise/principles.md b/.windsurf/rules/enterprise/principles.md index 9c11ff5871..a25fb5619a 100644 --- a/.windsurf/rules/enterprise/principles.md +++ b/.windsurf/rules/enterprise/principles.md @@ -11,11 +11,11 @@ Critical principles for enterprise-level plugin development. ## Core Principles -1. **NEVER guess** — Always search and verify before making changes -2. **Minimal scope** — Fix at the most specific location, closest to the problem -3. **Backward compatibility** — Maintain 100% compatibility with existing callers -4. **No custom solutions** — Never invent new patterns; use existing ones -5. **User changes are final** — If user makes manual changes, treat as authoritative +1. **NEVER guess**: Always search and verify before making changes +2. **Minimal scope**: Fix at the most specific location, closest to the problem +3. **Backward compatibility**: Maintain 100% compatibility with existing callers +4. **No custom solutions**: Never invent new patterns, use existing ones +5. **User changes are final**: If user makes manual changes, treat as authoritative --- @@ -25,8 +25,8 @@ Before proposing solutions: 1. **Read and understand** the complete issue 2. **Identify ALL affected locations** in the codebase -3. **Map dependencies** — What calls this code? What does this code call? -4. **Check plugin requirements** — Must code work standalone or require dependencies? +3. **Map dependencies**: What calls this code? What does this code call? +4. **Check plugin requirements**: Must code work standalone or require dependencies? --- @@ -36,7 +36,7 @@ Before proposing solutions: - Choose solution with minimal scope and lowest risk - Fix at the most specific location - Prefer adding safety checks over refactoring -- **Verify changes are not overkill** — If affecting several areas, analyze all to ensure fix is not excessive +- **Verify changes are not overkill**: If affecting several areas, analyze all to ensure fix is not excessive --- @@ -53,10 +53,10 @@ Before proposing solutions: Before ANY code change involving platform functions: -1. **Search codebase first** — Understand existing patterns -2. **Search official docs** — Function parameters, return types, deprecated alternatives -3. **Search platform-specific docs** — Performance and security best practices -4. **Verify alignment** — Ensure approach matches existing patterns +1. **Search codebase first**: Understand existing patterns +2. **Search official docs**: Function parameters, return types, deprecated alternatives +3. **Search platform-specific docs**: Performance and security best practices +4. **Verify alignment**: Ensure approach matches existing patterns --- diff --git a/.windsurf/rules/formidable/patterns.md b/.windsurf/rules/formidable/patterns.md index eb4f03b83e..42b6a25da9 100644 --- a/.windsurf/rules/formidable/patterns.md +++ b/.windsurf/rules/formidable/patterns.md @@ -13,10 +13,10 @@ Formidable-specific patterns, naming conventions, and architectural decisions. Before making ANY change: -1. **Find existing patterns** — Search models, controllers, helpers for similar functionality -2. **Study pattern usage** — Search ALL places using the pattern -3. **Trace parent hierarchy** — Search parent files up to plugin root -4. **Iterate if needed** — If better pattern found, repeat from step 1 +1. **Find existing patterns**: Search models, controllers, helpers for similar functionality +2. **Study pattern usage**: Search ALL places using the pattern +3. **Trace parent hierarchy**: Search parent files up to plugin root +4. **Iterate if needed**: If better pattern found, repeat from step 1 **Never invent custom solutions if existing patterns exist.** diff --git a/.windsurf/rules/formidable/testing.md b/.windsurf/rules/formidable/testing.md index f8777781e6..c0397161e7 100644 --- a/.windsurf/rules/formidable/testing.md +++ b/.windsurf/rules/formidable/testing.md @@ -62,10 +62,10 @@ $this->assertTrue( $condition, 'Condition should be true.' ); Every fix/feature must be tested with: -1. **Pro active** — With formidable-pro plugin enabled -2. **Pro inactive** — Lite-only scenario -3. **Empty data** — Empty arrays, null values, missing keys -4. **Edge cases** — Boundary conditions, special characters +1. **Pro active**: With formidable-pro plugin enabled +2. **Pro inactive**: Lite-only scenario +3. **Empty data**: Empty arrays, null values, missing keys +4. **Edge cases**: Boundary conditions, special characters --- From fa43df1c32227356b5a09b5e5dbb86bb89f16686 Mon Sep 17 00:00:00 2001 From: Sherv Date: Tue, 10 Feb 2026 16:25:16 +0300 Subject: [PATCH 27/90] feat(windsurf): add WordPress accessibility coding standards rule for WCAG 2.2 Level AA compliance Add comprehensive WordPress accessibility rule covering WCAG 2.2 Level AA requirements with perceivable content guidelines (text alternatives, semantic structure, color contrast ratios), operable interface standards (keyboard accessibility, focus management, target sizes), understandable content principles (language attributes, error handling, predictable behavior), and robust compatibility requirements --- .windsurf/rules/wordpress/accessibility.md | 608 +++++++++++++++++++++ 1 file changed, 608 insertions(+) create mode 100644 .windsurf/rules/wordpress/accessibility.md diff --git a/.windsurf/rules/wordpress/accessibility.md b/.windsurf/rules/wordpress/accessibility.md new file mode 100644 index 0000000000..f6d253ff8f --- /dev/null +++ b/.windsurf/rules/wordpress/accessibility.md @@ -0,0 +1,608 @@ +--- +trigger: model_decision +description: WordPress accessibility coding standards (WCAG 2.2 Level AA). Apply when working with UI elements, forms, or any user-facing code. +--- + +# WordPress Accessibility Coding Standards + +Based on WordPress Core Official Standards (WCAG 2.2 Level AA). + +**Reference:** [WordPress Accessibility Coding Standards](https://developer.wordpress.org/coding-standards/wordpress-coding-standards/accessibility/) + +--- + +## Conformance Levels + +| Level | Priority | Description | +| --------- | ---------- | ---------------------------------- | +| Level A | Critical | Minimum accessibility requirements | +| Level AA | Required | WordPress commitment level | +| Level AAA | Encouraged | Enhanced accessibility | + +--- + +## 1. Perceivable + +Content must be presentable in ways users can perceive. + +### 1.1 Text Alternatives + +Provide text alternatives for non-text content. + +**Images** + +```html + +Sales chart showing 50% growth in Q4 + + + + + +
+ System architecture diagram +
+ Detailed description of the system architecture... +
+
+``` + +**Form Controls** + +```html + + + + + +``` + +**Audio and Video** + +- Provide captions for video content +- Provide transcripts for audio content +- Provide audio descriptions for video with important visual information + +### 1.2 Adaptable + +Create content that can be presented in different ways. + +**Semantic Structure** + +```html + +

Page Title

+

Section Title

+

Subsection Title

+ + + +
...
+ + + +
    +
  • Item one
  • +
  • Item two
  • +
+``` + +**Reading Order** + +Ensure visual order matches DOM order. + +**Meaningful Sequence** + +Content should make sense when read in order. + +### 1.3 Distinguishable + +Make content distinguishable from background and other content. + +**Color Contrast** + +| Text Type | Minimum Ratio | +| -------------------------------- | ------------- | +| Normal text | 4.5:1 | +| Large text (18pt+ or 14pt+ bold) | 3:1 | +| UI components and graphics | 3:1 | + +**Color Not Sole Indicator** + +Never use color alone to convey information. + +```css +/* Incorrect - color only */ +.error { + color: #d00; +} + +/* Correct - multiple indicators */ +.error { + color: #d00; + border-left: 4px solid #d00; +} +.error::before { + content: "Error: "; + font-weight: bold; +} +``` + +**Text Resize** + +Text must be resizable up to 200% without loss of content or functionality. + +**Text Spacing** + +Content must remain readable with adjusted spacing: + +- Line height: 1.5 times font size +- Paragraph spacing: 2 times font size +- Letter spacing: 0.12 times font size +- Word spacing: 0.16 times font size + +--- + +## 2. Operable + +User interface components must be operable. + +### 2.1 Keyboard Accessible + +All functionality must be available via keyboard. + +**Focusable Elements** + +```html + + +Link + + +
+ Custom Button +
+``` + +```javascript +function handleKeydown(event) { + if (event.key === "Enter" || event.key === " ") { + event.preventDefault(); + handleClick(); + } +} +``` + +**Focus Trap Prevention** + +Ensure users can navigate away from all components. + +**Keyboard Shortcuts** + +If implementing shortcuts, allow users to turn them off or remap them. + +### 2.2 Enough Time + +Give users enough time to read and use content. + +- Allow users to turn off time limits +- Allow users to extend time limits +- Warn before timeout + +### 2.3 Seizures and Physical Reactions + +Do not design content that causes seizures. + +- No content that flashes more than 3 times per second +- Provide warnings for flashing content + +### 2.4 Navigable + +Provide ways to help users navigate and find content. + +**Skip Links** + +```html + +``` + +```css +.skip-link { + position: absolute; + top: -100px; + left: 0; + z-index: 100000; +} + +.skip-link:focus { + top: 7px; + left: 6px; + background: #f1f1f1; + padding: 15px 23px 14px; +} +``` + +**Page Titles** + +Descriptive and unique page titles. + +```html +Edit Post "Hello World" - My WordPress Site +``` + +**Link Purpose** + +Links should be understandable from link text alone. + +```html + +Click here +Read more + + +Download Annual Report (PDF, 2MB) +Read more about WordPress accessibility +``` + +**Multiple Ways** + +Provide multiple ways to locate content (navigation, search, sitemap). + +**Headings and Labels** + +Use descriptive headings and labels. + +**Focus Visible** + +Focus indicator must be visible. + +```css +*:focus { + outline: 2px solid #0073aa; + outline-offset: 2px; +} + +/* Never remove focus outline without replacement */ +*:focus { + outline: none; /* INCORRECT */ +} +``` + +### 2.5 Input Modalities + +Make functionality easier to operate through various inputs. + +**Target Size** + +Minimum target size of 24x24 CSS pixels. Recommended 44x44 pixels. + +```css +.button, +.nav-link { + min-width: 44px; + min-height: 44px; +} +``` + +**Pointer Gestures** + +Do not require complex gestures. Provide alternatives. + +**Motion Activation** + +Do not require device motion. Provide alternatives. + +--- + +## 3. Understandable + +Information and UI operation must be understandable. + +### 3.1 Readable + +Make text content readable and understandable. + +**Language of Page** + +```html + +``` + +**Language of Parts** + +```html +

+ The French phrase c'est la vie means "that's life". +

+``` + +### 3.2 Predictable + +Make pages appear and operate predictably. + +**On Focus** + +No change of context on focus. + +```javascript +// Incorrect - auto-submit on focus +input.addEventListener("focus", function () { + form.submit(); +}); +``` + +**On Input** + +No unexpected context change on input. + +```javascript +// Incorrect - auto-navigate on select change +select.addEventListener("change", function () { + window.location = this.value; +}); + +// Correct - require button press +button.addEventListener("click", function () { + window.location = select.value; +}); +``` + +**Consistent Navigation** + +Navigation should appear in same order across pages. + +**Consistent Identification** + +Components with same function should be identified consistently. + +### 3.3 Input Assistance + +Help users avoid and correct mistakes. + +**Error Identification** + +```html + + + + Please enter a valid email address + +``` + +**Labels or Instructions** + +```html + + +``` + +**Error Suggestion** + +Provide specific error messages with suggestions. + +```html + + Password must be at least 8 characters and include a number + +``` + +**Error Prevention** + +For legal and financial transactions: + +- Allow users to review before submitting +- Allow users to correct errors +- Provide confirmation + +--- + +## 4. Robust + +Content must be robust enough for assistive technologies. + +### 4.1 Compatible + +Maximize compatibility with current and future tools. + +**Valid HTML** + +Use valid HTML that parses correctly. + +**Name, Role, Value** + +Ensure all UI components have accessible names and roles. + +```html + + + + +
+ + +
+
Panel content...
+``` + +**Status Messages** + +Announce status messages to screen readers. + +```html +
Form submitted successfully
+ +
3 results found
+``` + +--- + +## WordPress Specific Practices + +### Screen Reader Text + +```css +.screen-reader-text { + border: 0; + clip: rect(1px, 1px, 1px, 1px); + clip-path: inset(50%); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; + word-wrap: normal !important; +} + +.screen-reader-text:focus { + background-color: #f1f1f1; + border-radius: 3px; + box-shadow: 0 0 2px 2px rgba(0, 0, 0, 0.6); + clip: auto !important; + clip-path: none; + color: #21759b; + display: block; + font-size: 14px; + font-weight: 700; + height: auto; + left: 5px; + line-height: normal; + padding: 15px 23px 14px; + text-decoration: none; + top: 5px; + width: auto; + z-index: 100000; +} +``` + +### Admin Notices + +```php +
+

+
+``` + +### Live Regions + +```php +
+ +
+``` + +```javascript +// Update live region content +document.getElementById("ajax-response").textContent = + "Loading complete. 5 items loaded."; +``` + +### Toggles and Expandables + +```html + + +``` + +```javascript +button.addEventListener("click", function () { + var expanded = this.getAttribute("aria-expanded") === "true"; + this.setAttribute("aria-expanded", !expanded); + content.hidden = expanded; +}); +``` + +--- + +## Common Failure Patterns + +### Missing Form Labels + +```html + + + + + + +``` + +### Empty Links + +```html + + + + + + + Go to page + +``` + +### Missing Skip Link + +```html + + +``` + +### Low Contrast Text + +Use contrast checker tools to verify ratios. + +### Keyboard Traps + +Ensure all modals and popups can be closed with Escape key. + +--- + +## Testing Tools + +### Automated + +- axe DevTools +- WAVE +- Lighthouse +- Pa11y + +### Manual + +- Keyboard-only navigation +- Screen reader testing (NVDA, JAWS, VoiceOver) +- High contrast mode +- Zoom to 200% + +### User Testing + +- Include people with disabilities in testing From 4c5d0d1f790779253a735c3f1cce7eb4d5080aa8 Mon Sep 17 00:00:00 2001 From: Sherv Date: Tue, 10 Feb 2026 16:33:05 +0300 Subject: [PATCH 28/90] feat(windsurf): add WordPress CSS coding standards rule for stylesheet development Add comprehensive WordPress CSS coding standards rule covering structure (indentation, spacing, selector layout), selector conventions (naming, specificity, attribute selectors), property formatting and ordering (display/box model, positioning, typography, visual, animation), value standards (colors, font weights, line height, zero values, quotes), media queries, commenting guidelines (table of contents, section headers, inline --- .windsurf/rules/wordpress/css.md | 560 +++++++++++++++++++++++++++++++ 1 file changed, 560 insertions(+) create mode 100644 .windsurf/rules/wordpress/css.md diff --git a/.windsurf/rules/wordpress/css.md b/.windsurf/rules/wordpress/css.md new file mode 100644 index 0000000000..aefba00aae --- /dev/null +++ b/.windsurf/rules/wordpress/css.md @@ -0,0 +1,560 @@ +--- +trigger: glob +globs: ["**/*.css", "**/*.scss", "**/*.less"] +description: WordPress CSS coding standards. Auto-applies when working with CSS files. +--- + +# WordPress CSS Coding Standards + +Based on WordPress Core Official Standards. Apply when maintaining, generating, or refactoring CSS code. + +**Reference:** [WordPress CSS Coding Standards](https://developer.wordpress.org/coding-standards/wordpress-coding-standards/css/) + +--- + +## 1. Structure + +### Indentation + +Use tabs, not spaces. + +### Spacing Between Blocks + +- Two blank lines between sections +- One blank line between blocks in a section + +### Selector and Property Layout + +Each selector on its own line. Property-value pairs on own line with one tab indentation. + +```css +#selector-1, +#selector-2, +#selector-3 { + background: #fff; + color: #000; +} +``` + +### Closing Brace + +Closing brace on its own line at same indentation level as opening selector. + +```css +.selector { + property: value; +} +``` + +--- + +## 2. Selectors + +### Naming Convention + +Lowercase letters and hyphens only. No camelCase or underscores. + +```css +#comment-form { +} +.post-title { +} +.sidebar-widget { +} +``` + +### Selector Specificity + +Avoid over-qualification. Do not add unnecessary element selectors. + +```css +/* Incorrect */ +div#comment-form { +} +ul.nav-menu { +} + +/* Correct */ +#comment-form { +} +.nav-menu { +} +``` + +### Attribute Selectors + +Use double quotes around attribute values. + +```css +input[type="text"] { +} +a[href^="https://"] +{ +} +``` + +### Selector Length + +Keep selectors short. Maximum 3-4 levels deep. + +```css +/* Too specific */ +body .page-wrapper .content-area .post-list .post-item .post-title { +} + +/* Better */ +.post-list .post-title { +} +``` + +### Universal Selector + +Avoid universal selector `*` except for specific resets. + +### ID Selectors + +Use sparingly. Prefer classes for reusability. + +--- + +## 3. Properties + +### Formatting + +- Colon followed by a single space +- All lowercase for property names and values +- End with semicolon + +```css +.selector { + background-color: #fff; + font-size: 16px; + text-align: center; +} +``` + +### Property Ordering + +Group related properties together in this order: + +1. Display and Box Model +2. Positioning +3. Typography +4. Visual (colors, backgrounds) +5. Animation +6. Miscellaneous + +```css +.selector { + /* Display and Box Model */ + display: block; + box-sizing: border-box; + width: 100%; + height: auto; + padding: 10px; + margin: 0 auto; + border: 1px solid #ccc; + + /* Positioning */ + position: relative; + top: 0; + left: 0; + z-index: 10; + + /* Typography */ + font-family: "Helvetica Neue", sans-serif; + font-size: 16px; + font-weight: 400; + line-height: 1.5; + text-align: left; + text-transform: none; + color: #333; + + /* Visual */ + background-color: #fff; + background-image: url("image.png"); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + opacity: 1; + + /* Animation */ + transition: all 0.3s ease; + transform: translateX(0); + + /* Miscellaneous */ + cursor: pointer; + overflow: hidden; +} +``` + +### Shorthand Properties + +Use shorthand for `background`, `border`, `font`, `margin`, `padding` when setting all values. + +```css +/* Shorthand */ +margin: 10px 20px; +padding: 10px; +border: 1px solid #ccc; + +/* Individual properties when setting one value */ +margin-top: 10px; +padding-left: 20px; +border-bottom: 2px solid #000; +``` + +### Vendor Prefixes + +Place standard property last. + +```css +.selector { + -webkit-transform: rotate(45deg); + -moz-transform: rotate(45deg); + -ms-transform: rotate(45deg); + transform: rotate(45deg); +} +``` + +--- + +## 4. Values + +### Colors + +Use hex codes in lowercase. Use shorthand when possible. + +```css +/* Correct */ +color: #fff; +background: #aabbcc; + +/* For transparency use rgba */ +background: rgba(0, 0, 0, 0.5); +``` + +### Font Weights + +Use numeric values. + +| Weight | Value | +| ---------- | ----- | +| Normal | 400 | +| Bold | 700 | +| Light | 300 | +| Extra Bold | 800 | + +```css +font-weight: 400; +font-weight: 700; +``` + +### Line Height + +Use unitless values for line-height. + +```css +/* Correct */ +line-height: 1.5; + +/* Incorrect */ +line-height: 24px; +line-height: 150%; +``` + +### Zero Values + +Omit units on zero values except for `transition-duration`. + +```css +margin: 0; +padding: 0 10px; +border: 0; + +/* Exception */ +transition-duration: 0s; +``` + +### Decimal Values + +Include leading zero for decimals less than 1. + +```css +opacity: 0.5; +``` + +### Quotes + +Use double quotes. Required for font names with spaces, URL values, and attribute selectors. + +```css +font-family: "Times New Roman", serif; +background: url("image.png"); +content: ""; +``` + +### URL Values + +Do not use quotes in url() for simple paths. + +```css +/* Correct */ +background: url(images/background.png); + +/* Also correct for paths with special characters */ +background: url("images/my image.png"); +``` + +--- + +## 5. Media Queries + +### Placement + +Group media queries at the bottom of the stylesheet or use component-based organization with media queries near related rules. + +### Indentation + +Indent rule sets one level inside media query. + +```css +@media all and (max-width: 699px) and (min-width: 520px) { + .selector { + display: block; + } +} +``` + +### Common Breakpoints + +```css +/* Mobile first approach */ +.selector { + width: 100%; +} + +@media screen and (min-width: 600px) { + .selector { + width: 50%; + } +} + +@media screen and (min-width: 1024px) { + .selector { + width: 33.333%; + } +} +``` + +### Feature Queries + +```css +@supports (display: grid) { + .container { + display: grid; + } +} +``` + +--- + +## 6. Commenting + +### Table of Contents + +Include at the top of major stylesheets. + +```css +/** + * Table of Contents + * + * 1.0 Reset + * 2.0 Typography + * 3.0 Elements + * 4.0 Forms + * 5.0 Navigation + * 6.0 Accessibility + * 7.0 Widgets + * 8.0 Content + * 9.0 Media Queries + */ +``` + +### Section Headers + +```css +/** + * 1.0 Reset + * + * Resetting and rebuilding styles have been + * having together so they can operate on a + * common base. + */ +``` + +### Subsection Headers + +```css +/** + * 1.1 Reset Lists + */ +``` + +### Inline Comments + +```css +/* This is a comment about the following rule */ +.selector { + property: value; /* This is a comment about this property */ +} +``` + +### Multi-line Comments + +```css +/** + * This is a longer comment that spans + * multiple lines. Use this format for + * detailed explanations. + */ +``` + +--- + +## 7. Best Practices + +### Remove Before Adding + +Remove unused code before adding new code. + +### Magic Numbers + +Avoid unexplained numbers. Document or calculate values. + +```css +/* Incorrect */ +.selector { + top: 37px; +} + +/* Correct */ +.selector { + /* Offset = header height (50px) - element height (13px) */ + top: 37px; +} +``` + +### Direct Targeting + +Target elements directly with classes rather than relying on element position. + +```css +/* Incorrect */ +.nav li:nth-child(3) a { +} + +/* Correct */ +.nav-contact-link { +} +``` + +### Line Height for Text + +Use `line-height` for vertically centering text. Use `height` only for elements with fixed dimensions. + +### Default Values + +Do not restate default values unless intentionally overriding. + +### Important Declaration + +Avoid `!important`. If used, document why. + +```css +/* Override third-party plugin styles */ +.plugin-element { + color: #333 !important; +} +``` + +### Box Model + +Use `box-sizing: border-box` for predictable sizing. + +```css +*, +*::before, +*::after { + box-sizing: border-box; +} +``` + +--- + +## 8. SCSS/Sass Specific + +### Nesting + +Maximum 3 levels deep. + +```scss +.block { + .element { + .modifier { + // Stop here + } + } +} +``` + +### Variables + +Use descriptive names. + +```scss +$color-primary: #0073aa; +$font-size-base: 16px; +$spacing-unit: 8px; +``` + +### Mixins + +```scss +@mixin button-style($bg-color, $text-color) { + background: $bg-color; + color: $text-color; + padding: 10px 20px; + border: none; + cursor: pointer; +} +``` + +### Extend + +Use sparingly and prefer mixins. + +```scss +%clearfix { + &::after { + content: ""; + display: table; + clear: both; + } +} +``` + +--- + +## Tooling + +```bash +# Stylelint with WordPress config +npm install --save-dev stylelint @wordpress/stylelint-config + +# .stylelintrc.json +{ + "extends": "@wordpress/stylelint-config" +} + +# Run check +npx stylelint "**/*.css" +``` From d975866edaf24d58b032ab396f512764402acd09 Mon Sep 17 00:00:00 2001 From: Sherv Date: Tue, 10 Feb 2026 16:35:25 +0300 Subject: [PATCH 29/90] feat(windsurf): add WordPress HTML coding standards rule for markup development Add comprehensive WordPress HTML coding standards rule covering validation requirements, document structure (doctype, language, charset, viewport), self-closing element formatting, attribute conventions (lowercase, quoting, boolean values), semantic tag usage, tab-based indentation, form structure with proper labels, accessible table markup, link/image best practices, script/style placement, and commenting guidelines --- .windsurf/rules/wordpress/html.md | 519 ++++++++++++++++++++++++++++++ 1 file changed, 519 insertions(+) create mode 100644 .windsurf/rules/wordpress/html.md diff --git a/.windsurf/rules/wordpress/html.md b/.windsurf/rules/wordpress/html.md new file mode 100644 index 0000000000..b279c9d4bc --- /dev/null +++ b/.windsurf/rules/wordpress/html.md @@ -0,0 +1,519 @@ +--- +trigger: glob +globs: ["**/*.html", "**/*.htm"] +description: WordPress HTML coding standards. Auto-applies when working with HTML files. +--- + +# WordPress HTML Coding Standards + +Based on WordPress Core Official Standards. Apply when maintaining, generating, or refactoring HTML code. + +**Reference:** [WordPress HTML Coding Standards](https://developer.wordpress.org/coding-standards/wordpress-coding-standards/html/) + +--- + +## 1. Validation + +All HTML pages should be verified against the W3C validator to ensure markup is well-formed. + +**Tools:** + +- [W3C Markup Validation Service](https://validator.w3.org/) +- Browser developer tools +- IDE extensions + +**Key validation checks:** + +- Properly nested elements +- Required attributes present +- No duplicate IDs +- Correct doctype declaration + +--- + +## 2. Document Structure + +### Doctype + +Always use HTML5 doctype. + +```html + +``` + +### Language Attribute + +Specify the language on the html element. + +```html + +``` + +### Character Encoding + +Declare UTF-8 encoding early in the head. + +```html + + + +``` + +### Viewport + +Include viewport meta for responsive design. + +```html + +``` + +--- + +## 3. Self-closing Elements + +All tags must be properly closed. Self-closing elements require a space before the closing slash. + +### Void Elements + +These elements are self-closing and should have a space before the slash. + +```html +
+
+Description + + + +``` + +### Incorrect + +```html + +
+ + + +
+ +``` + +--- + +## 4. Attributes + +### Lowercase + +All attribute names must be lowercase. + +```html + +
+ + +
+``` + +### Attribute Values + +- Use lowercase for machine-interpreted values +- Use Title Case for human-readable values + +```html + +Example +``` + +### Required Attributes + +Always include required attributes for elements. + +| Element | Required Attributes | +|---------|---------------------| +| `img` | `src`, `alt` | +| `a` | `href` | +| `input` | `type`, `name` | +| `label` | `for` (when not wrapping input) | +| `form` | `action`, `method` | +| `script` | `src` (for external) | +| `link` | `rel`, `href` | + +```html +A sunset over mountains +About Us + + +``` + +--- + +## 5. Quotes + +### Always Quote Attributes + +Never omit quotes around attribute values. Unquoted attributes can cause security vulnerabilities. + +```html + + + + + +``` + +### Use Double Quotes + +Prefer double quotes for attribute values. + +```html +
+``` + +### Boolean Attributes + +Omit the value or repeat the attribute name. Never use true or false. + +```html + + + + + + + + +``` + +--- + +## 6. Tags + +### Lowercase + +All tag names must be lowercase. + +```html + +
+

Content

+
+ + +
+

Content

+
+``` + +### Proper Nesting + +Elements must be properly nested and closed. + +```html + +

Bold text

+ + +

Bold text

+``` + +### Semantic Elements + +Use semantic HTML5 elements where appropriate. + +```html +
+ +
+ +
+
+
+

Article Title

+
+
+

Content here.

+
+
+ +
+ +
+

Copyright information

+
+``` + +--- + +## 7. Indentation + +### Use Tabs + +HTML indentation should reflect the logical structure using tabs. + +```html +
+
+

Title

+
+
+
+

Content

+
+
+
+``` + +### PHP in HTML + +Indent PHP blocks to match surrounding HTML structure. + +```php + +
+

Not Found

+
+

Apologies, but no results were found.

+ +
+
+ +``` + +### Inline Elements + +Keep short inline elements on same line. + +```html +

This is bold and italic text.

+``` + +### Block Elements + +Put block elements on their own lines. + +```html +
+

First paragraph.

+

Second paragraph.

+
+``` + +--- + +## 8. Forms + +### Form Structure + +```html +
+
+ Personal Information + +
+ + +
+ +
+ + +
+
+ + +
+``` + +### Labels + +Always associate labels with form controls. + +```html + + + + + + +``` + +### Input Types + +Use appropriate input types for better UX and validation. + +| Type | Use Case | +|------|----------| +| `email` | Email addresses | +| `tel` | Phone numbers | +| `url` | URLs | +| `number` | Numeric input | +| `date` | Date selection | +| `search` | Search fields | +| `password` | Passwords | + +--- + +## 9. Tables + +### Structure + +```html + + + + + + + + + + + + + + + + + + + + + + + + +
Monthly Sales
MonthSales
January$10,000
February$12,000
Total$22,000
+``` + +### Scope Attribute + +Use scope attribute on header cells. + +```html +Column Header +Row Header +``` + +--- + +## 10. Links and Images + +### Links + +```html + + + External Site + + + +About Us + + +Contact Us + + + +``` + +### Images + +```html + +Sales increased 50% in Q4 + + + + + +
+ Mountain landscape at sunset +
Rocky Mountains, Colorado
+
+ + +Responsive image example +``` + +--- + +## 11. Scripts and Styles + +### Script Placement + +Place scripts at the end of body or use defer attribute. + +```html + + + + + +``` + +Or with defer: + +```html + + + +``` + +### Inline Scripts and Styles + +Avoid inline scripts and styles. Use external files. + +```html + +
Text
+ + + +
Text
+ +``` + +--- + +## 12. Comments + +```html + + + + + + + +``` + +--- + +## Best Practices Summary + +1. **Validate markup** using W3C validator +2. **Close all tags** including self-closing with space before slash +3. **Lowercase everything** for tags, attributes, and machine values +4. **Quote all attributes** using double quotes +5. **Boolean attributes** omit value or repeat name +6. **Use tabs** for indentation reflecting logical structure +7. **Align PHP with HTML** maintaining consistent indentation +8. **Use semantic elements** for document structure +9. **Associate labels** with form controls +10. **Include alt text** for images From 056417a77f6bf0eadea83fab593eb8d4fd3a57c6 Mon Sep 17 00:00:00 2001 From: Sherv Date: Tue, 10 Feb 2026 16:42:38 +0300 Subject: [PATCH 30/90] feat(windsurf): add WordPress JavaScript coding standards rule for JS development Add comprehensive WordPress JavaScript coding standards rule covering spacing conventions (tabs, object/array declarations, function calls), indentation and line breaks, variable naming (camelCase, PascalCase for classes, SCREAMING_SNAKE_CASE for constants), strict equality operators, syntax rules (semicolons, single quotes, template literals, switch statements), best practices (JSDoc comments, literal syntax, iteration --- .windsurf/rules/wordpress/javascript.md | 539 ++++++++++++++++++++++++ 1 file changed, 539 insertions(+) create mode 100644 .windsurf/rules/wordpress/javascript.md diff --git a/.windsurf/rules/wordpress/javascript.md b/.windsurf/rules/wordpress/javascript.md new file mode 100644 index 0000000000..4ceccbf89a --- /dev/null +++ b/.windsurf/rules/wordpress/javascript.md @@ -0,0 +1,539 @@ +--- +trigger: glob +globs: ["**/*.js", "**/*.jsx", "**/*.mjs"] +description: WordPress JavaScript coding standards. Auto-applies when working with JS files. +--- + +# WordPress JavaScript Coding Standards + +Based on WordPress Core Official Standards. Apply when maintaining, generating, or refactoring JavaScript code. + +**Reference:** [WordPress JavaScript Coding Standards](https://developer.wordpress.org/coding-standards/wordpress-coding-standards/javascript/) + +--- + +## 1. Spacing + +### General Rules + +- Use tabs for indentation +- No whitespace at end of line or on blank lines +- Lines should be 80-100 characters maximum +- Always use braces for `if`, `else`, `for`, `while`, `try` +- Add space after `!` negation operator +- Include new line at end of each file + +### Object Declarations + +Use multiline format for clarity. + +```javascript +var obj = { + ready: 9, + when: 4, + 'you are': 15, +}; +``` + +For single property, inline is acceptable. + +```javascript +var obj = { ready: 9 }; +``` + +### Array Declarations + +```javascript +var arr = [ + 'one', + 'two', + 'three', +]; +``` + +### Function Calls + +Include space inside parentheses. + +```javascript +foo( arg1, arg2 ); +``` + +No space for empty parentheses. + +```javascript +foo(); +``` + +### Control Structures + +```javascript +if ( condition ) { + doSomething( 'with a string' ); +} else if ( otherCondition ) { + otherThing( { + key: value, + } ); +} else { + somethingElse( true ); +} + +while ( ! condition ) { + iterating++; +} + +for ( i = 0; i < 100; i++ ) { + object[ array[ i ] ] = someFn( i ); +} +``` + +### Operators + +Include spaces around operators. + +```javascript +var x = a + b; +var y = a && b; +var z = a ? b : c; +``` + +--- + +## 2. Indentation and Line Breaks + +### Tab Indentation + +Use tabs for all indentation, including inside closures. + +```javascript +( function( $ ) { + function doSomething() { + // Expressions indented with tabs + } +} )( jQuery ); +``` + +### Blocks + +Opening brace on same line as the statement. Closing brace on its own line after the last statement. + +```javascript +if ( condition ) { + doAction(); +} +``` + +### Multi-line Statements + +When a statement is too long to fit on one line, line breaks must occur after an operator. + +```javascript +var html = '

The sum of ' + a + ' and ' + b + ' plus ' + c + + ' is ' + ( a + b + c ) + '

'; +``` + +### Chained Method Calls + +Use one call per line for chains that are hard to read on one line. + +```javascript +elements + .addClass( 'foo' ) + .children() + .html( 'hello' ) + .end() + .appendTo( 'body' ); +``` + +--- + +## 3. Variables and Naming + +### Variable Declarations + +Use `const` for values that do not change. Use `let` for values that change. Avoid `var` in modern code. + +```javascript +const MAX_COUNT = 100; +let currentCount = 0; +``` + +Declare each variable on its own line. + +```javascript +let a = 1; +let b = 2; +``` + +### Naming Conventions + +Use camelCase with lowercase first letter for variables and functions. + +```javascript +var someVariable = 'value'; +function myFunction() {} +``` + +### Class Definitions + +Use UpperCamelCase (PascalCase) for class names. + +```javascript +class MyClass { + constructor() {} +} +``` + +### Constants + +Use SCREAMING_SNAKE_CASE for true constants. + +```javascript +const MAX_ITEMS = 100; +const API_ENDPOINT = '/api/v1'; +``` + +### Private Properties + +Prefix private properties with underscore. + +```javascript +this._privateProperty = 'value'; +``` + +### Globals + +Avoid global variables. If unavoidable, set via `window`. + +```javascript +window.myGlobal = 'value'; +``` + +### jQuery Pattern + +Use IIFE to protect jQuery dollar sign. + +```javascript +( function( $ ) { + // Use $ safely here +} )( jQuery ); +``` + +--- + +## 4. Equality and Type Checks + +### Strict Equality + +Always use strict equality operators `===` and `!==`. + +```javascript +// Correct +if ( name === 'John' ) {} +if ( count !== 0 ) {} + +// Incorrect +if ( name == 'John' ) {} +if ( count != 0 ) {} +``` + +### Type Checks + +| Type | Check | +|------|-------| +| String | `typeof object === 'string'` | +| Number | `typeof object === 'number'` | +| Boolean | `typeof object === 'boolean'` | +| Object | `typeof object === 'object'` | +| Function | `typeof object === 'function'` | +| null | `object === null` | +| undefined | `typeof variable === 'undefined'` | +| Property exists | `object.hasOwnProperty( prop )` | +| Array | `Array.isArray( object )` | + +--- + +## 5. Syntax Rules + +### Semicolons + +Always use semicolons. Never rely on Automatic Semicolon Insertion (ASI). + +```javascript +var foo = 'bar'; +``` + +### Strings + +Use single quotes for strings. + +```javascript +var myStr = 'strings should use single quotes'; +``` + +For HTML inside JavaScript, use single quotes for the JavaScript string and double quotes for HTML attributes. + +```javascript +var html = 'Link'; +``` + +### Template Literals + +Use template literals for string interpolation in ES6+. + +```javascript +const message = `Hello, ${name}!`; +``` + +### Switch Statements + +Use `break` for each case except when explicitly falling through. Indent `case` one tab from `switch`. + +```javascript +switch ( event.keyCode ) { + case $.ui.keyCode.ENTER: + case $.ui.keyCode.SPACE: + executeFunction(); + break; + + case $.ui.keyCode.ESCAPE: + closeModal(); + break; + + default: + break; +} +``` + +--- + +## 6. Best Practices + +### Comments + +Place comments on line before the code. Precede with blank line. Use JSDoc for documentation. + +```javascript +/** + * Function description. + * + * @param {string} param1 Description of parameter. + * @return {boolean} Description of return value. + */ +function myFunction( param1 ) { + // Single-line comment + return true; +} +``` + +### Arrays and Objects + +Create arrays and objects using literal syntax. + +```javascript +// Correct +var arr = []; +var obj = {}; + +// Incorrect +var arr = new Array(); +var obj = new Object(); +``` + +### Iteration + +For collections, use appropriate iteration methods. + +```javascript +// jQuery +$.each( collection, function( index, item ) { + // code +} ); + +// Native +collection.forEach( function( item, index ) { + // code +} ); + +// ES6+ +for ( const item of collection ) { + // code +} +``` + +### Avoid eval() + +Never use `eval()`. + +### Avoid with Statement + +Never use `with` statement. + +### Code Refactoring + +Do not refactor working code just for style. Only refactor if you are already modifying that code. + +--- + +## 7. Functions + +### Function Declarations + +```javascript +function myFunction( arg1, arg2 ) { + return arg1 + arg2; +} +``` + +### Function Expressions + +```javascript +var myFunction = function( arg1, arg2 ) { + return arg1 + arg2; +}; +``` + +### Arrow Functions (ES6+) + +```javascript +const myFunction = ( arg1, arg2 ) => { + return arg1 + arg2; +}; + +// Short form for single expression +const double = ( n ) => n * 2; +``` + +### Default Parameters + +```javascript +function greet( name = 'World' ) { + return 'Hello, ' + name; +} +``` + +--- + +## 8. Classes (ES6+) + +```javascript +class Animal { + constructor( name ) { + this.name = name; + } + + speak() { + console.log( this.name + ' makes a sound.' ); + } +} + +class Dog extends Animal { + constructor( name ) { + super( name ); + } + + speak() { + console.log( this.name + ' barks.' ); + } +} +``` + +--- + +## 9. Modules (ES6+) + +### Importing + +```javascript +import { Component } from 'react'; +import * as utils from './utils'; +import defaultExport from './module'; +``` + +### Exporting + +```javascript +export function myFunction() {} +export const myConstant = 'value'; +export default MyClass; +``` + +--- + +## 10. Promises and Async + +### Promises + +```javascript +fetchData() + .then( function( data ) { + return processData( data ); + } ) + .then( function( result ) { + displayResult( result ); + } ) + .catch( function( error ) { + handleError( error ); + } ); +``` + +### Async/Await (ES2017+) + +```javascript +async function getData() { + try { + const data = await fetchData(); + const result = await processData( data ); + return result; + } catch ( error ) { + handleError( error ); + } +} +``` + +--- + +## JSHint Configuration + +```json +{ + "boss": true, + "curly": true, + "eqeqeq": true, + "eqnull": true, + "es3": true, + "expr": true, + "immed": true, + "noarg": true, + "nonbsp": true, + "onevar": true, + "quotmark": "single", + "trailing": true, + "undef": true, + "unused": true, + "browser": true, + "globals": { + "_": false, + "Backbone": false, + "jQuery": false, + "JSON": false, + "wp": false + } +} +``` + +--- + +## ESLint Configuration + +For modern projects, use ESLint with WordPress config. + +```bash +npm install --save-dev @wordpress/eslint-plugin +``` + +```json +{ + "extends": [ "plugin:@wordpress/eslint-plugin/recommended" ] +} +``` From 38d52a1b70d08b41f62e3b890f9e79a053ec52c8 Mon Sep 17 00:00:00 2001 From: Sherv Date: Tue, 10 Feb 2026 16:58:10 +0300 Subject: [PATCH 31/90] feat(windsurf): add WordPress PHP coding standards rule for PHP development Add comprehensive WordPress PHP coding standards rule covering PHP 7.0 compatibility requirements, security practices (prepared statements, SQL formatting), naming conventions (variables, classes, constants, dynamic hooks), formatting rules (brace style, array syntax, type declarations, spread operator), whitespace and indentation standards, control structures (Yoda conditions, elseif usage, alternative syntax), operator --- .windsurf/rules/wordpress/php.md | 659 +++++++++++++++++++++++++++++++ 1 file changed, 659 insertions(+) create mode 100644 .windsurf/rules/wordpress/php.md diff --git a/.windsurf/rules/wordpress/php.md b/.windsurf/rules/wordpress/php.md new file mode 100644 index 0000000000..ee477531a8 --- /dev/null +++ b/.windsurf/rules/wordpress/php.md @@ -0,0 +1,659 @@ +--- +trigger: glob +globs: ["**/*.php"] +description: WordPress PHP coding standards. Auto-applies when working with PHP files. +--- + +# WordPress PHP Coding Standards + +Based on WordPress Core Official Standards. Apply when maintaining, generating, or refactoring PHP code. + +**Reference:** [WordPress PHP Coding Standards](https://developer.wordpress.org/coding-standards/wordpress-coding-standards/php/) + +--- + +## PHP Version Requirement + +**Target Version: PHP 7.0** + +All new code must be compatible with PHP 7.0. Use modern PHP 7.0 syntax but do not use features from PHP 7.1 or higher. + +### PHP 7.0 Features (Use These) + +- Scalar type declarations: `function foo(int $id, string $name)` +- Return type declarations: `function foo(): bool` +- Null coalescing operator: `$value = $array['key'] ?? 'default'` +- Spaceship operator: `$result = $a <=> $b` +- Anonymous classes: `new class { ... }` +- Group use declarations: `use Some\Namespace\{ClassA, ClassB}` +- `define()` with arrays: `define('ITEMS', ['a', 'b'])` + +### PHP 7.1+ Features (Do NOT Use) + +- Nullable types: `?string` (PHP 7.1) +- Void return type: `: void` (PHP 7.1) +- Class constant visibility: `private const` (PHP 7.1) +- Iterable pseudo-type: `iterable` (PHP 7.1) +- Multi-catch exceptions: `catch (A | B $e)` (PHP 7.1) +- Negative string offsets: `$str[-1]` (PHP 7.1) +- Object type hint: `object` (PHP 7.2) +- Trailing commas in function calls (PHP 7.3) +- Arrow functions: `fn($x) => $x * 2` (PHP 7.4) +- Typed properties: `public int $id` (PHP 7.4) +- Null safe operator: `?->` (PHP 8.0) +- Named arguments (PHP 8.0) +- Match expression (PHP 8.0) +- Constructor property promotion (PHP 8.0) + +### Example PHP 7.0 Compatible Code + +```php +/** + * Process user data. + * + * @param int $user_id User ID. + * @param string $action Action to perform. + * @param array $options Optional settings. + * @return bool Whether the operation succeeded. + */ +function process_user_data( int $user_id, string $action, array $options = array() ): bool { + $timeout = $options['timeout'] ?? 30; + $result = $options['result'] ?? 'default'; + + if ( empty( $user_id ) ) { + return false; + } + + return true; +} +``` + +--- + +## 1. Security and Database + +### Prepared Statements + +Always use `$wpdb->prepare()` for queries with variables. + +```php +$wpdb->query( + $wpdb->prepare( + "UPDATE $wpdb->posts SET post_title = %s WHERE ID = %d", + $var, + $id + ) +); +``` + +### Placeholders + +| Placeholder | Type | +| ----------- | ------------------------------ | +| `%d` | Integer (whole number) | +| `%f` | Float (decimal number) | +| `%s` | String | +| `%i` | Identifier (table/field names) | + +### SQL Formatting + +- Capitalize SQL keywords (SELECT, FROM, WHERE, etc.) +- Break complex statements into multiple lines +- Use consistent indentation for readability + +```php +$wpdb->query( + $wpdb->prepare( + " + SELECT post_title, post_content + FROM $wpdb->posts + WHERE post_status = %s + AND post_type = %s + ORDER BY post_date DESC + LIMIT %d + ", + 'publish', + 'post', + 10 + ) +); +``` + +--- + +## 2. Naming Conventions + +### Variables and Functions + +Use lowercase with underscores. Never use camelCase. + +```php +function some_function( $some_variable ) { + $local_variable = ''; +} +``` + +### Classes, Interfaces, Traits, Enums + +Capitalized words separated by underscores. Acronyms should be uppercase. + +```php +class Walker_Category extends Walker {} +class WP_HTTP {} +interface Arrayable {} +trait Singleton {} +enum Post_Status {} +``` + +### Constants + +All uppercase with underscores. + +```php +define( 'DOING_AJAX', true ); +const MY_CONSTANT = 'value'; +``` + +### Dynamic Hooks + +Use interpolation, not concatenation. + +```php +// Correct +do_action( "{$new_status}_{$post->post_type}", $post->ID, $post ); + +// Incorrect +do_action( $new_status . '_' . $post->post_type, $post->ID, $post ); +``` + +--- + +## 3. Formatting + +### Brace Style + +Always use braces even for single-line blocks. Opening brace on same line. + +```php +if ( condition ) { + action(); +} elseif ( condition2 ) { + action2(); +} else { + default_action(); +} +``` + +### Array Syntax + +Use long array syntax `array()`, not short syntax `[]`. + +```php +$args = array( + 'post_type' => 'page', + 'post_status' => 'publish', +); +``` + +### Multi-line Function Calls + +```php +$result = some_function( + $arg1, + $arg2, + array( + 'key1' => 'value1', + 'key2' => 'value2', + ) +); +``` + +### Type Declarations + +One space before and after type declarations. + +```php +function foo( Class_Name $parameter, callable $callable, int $count = 0 ): bool { + return true; +} +``` + +### Spread Operator + +No space between spread operator and variable. + +```php +function foo( &...$spread ) { + bar( ...$spread ); +} +``` + +--- + +## 4. Whitespace and Indentation + +### Space Usage + +| Context | Rule | +| ------------------------------------ | ------------------------------ | +| After commas | Space required | +| Around operators | Spaces required | +| Inside control structure parentheses | Spaces required | +| Array literal access | No space: `$foo['bar']` | +| Array variable access | Space required: `$foo[ $bar ]` | +| Function call parentheses | No space inside | +| Type casts | Space after: `(int) $value` | + +```php +// Correct examples +$x = ( $a + $b ) * $c; +if ( true === $condition ) {} +$value = $array['literal']; +$value = $array[ $variable ]; +some_function( $arg1, $arg2 ); +$integer = (int) $value; +``` + +### Indentation + +Use real tabs for indentation, not spaces. + +### Blank Lines + +- One blank line after opening PHP tag +- One blank line before closing PHP tag (if used) +- No extra blank lines at start/end of function body + +--- + +## 5. Control Structures + +### Yoda Conditions + +Put the constant or literal value on the left side of comparisons. + +```php +// Correct +if ( true === $the_force ) { + $victorious = you_will( $be ); +} + +// Incorrect +if ( $the_force === true ) { + $victorious = you_will( $be ); +} +``` + +### elseif vs else if + +Always use `elseif`, not `else if`. + +```php +// Correct +if ( condition ) { + action(); +} elseif ( condition2 ) { + action2(); +} + +// Incorrect +if ( condition ) { + action(); +} else if ( condition2 ) { + action2(); +} +``` + +### Alternative Syntax in Templates + +Use alternative syntax with explicit ending statements in templates. + +```php + + +
+ +
+ + +``` + +--- + +## 6. Operators + +### Ternary Operator + +- Test for true, not false +- Do not use short ternary `?:` +- Ternaries should not be nested + +```php +// Correct +$value = ( ! empty( $field ) ) ? $field : 'default'; + +// Incorrect +$value = empty( $field ) ? 'default' : $field; + +// Incorrect - short ternary +$value = $field ?: 'default'; +``` + +### Null Coalescing Operator + +Use `??` for null checks. + +```php +$value = $array['key'] ?? 'default'; +``` + +### Error Control Operator + +Never use `@` to suppress errors. + +```php +// Incorrect +$value = @file_get_contents( $file ); + +// Correct +if ( file_exists( $file ) && is_readable( $file ) ) { + $value = file_get_contents( $file ); +} +``` + +### Increment/Decrement + +Do not use pre-increment/decrement in standalone statements. + +```php +// Correct +$a++; +++$b; + +// Do not use results in expressions +$a = $b++; +``` + +--- + +## 7. Best Practices + +### Boolean Arguments + +Use descriptive string values instead of boolean flags. + +```php +// Correct +function some_function( $args ) { + $type = $args['type'] ?? 'default'; +} +some_function( array( 'type' => 'hierarchical' ) ); + +// Incorrect +function some_function( $hierarchical = false ) {} +some_function( true ); +``` + +### Readability Over Cleverness + +Write clear and readable code. Avoid clever one-liners that are hard to understand. + +### Assignments in Conditionals + +Never put assignments inside conditionals. + +```php +// Incorrect +if ( $result = some_function() ) {} + +// Correct +$result = some_function(); +if ( $result ) {} +``` + +### Strict Comparisons + +Always use strict comparisons (`===` and `!==`) unless loose comparison is explicitly required. + +### Closures as Callbacks + +Do not use closures (anonymous functions) as callbacks for actions and filters. + +```php +// Incorrect +add_action( 'init', function() { + // code +} ); + +// Correct +add_action( 'init', 'my_init_function' ); +function my_init_function() { + // code +} +``` + +### Forbidden Functions + +| Function | Reason | +| ------------------- | ------------------------ | +| `extract()` | Makes code unpredictable | +| `eval()` | Security vulnerability | +| `create_function()` | Deprecated and insecure | +| `compact()` | Reduces readability | + +### Regular Expressions + +Use PCRE functions (`preg_match`, `preg_replace`, etc.) instead of POSIX functions. + +--- + +## 8. General Syntax + +### PHP Tags + +Always use full PHP tags. Never use shorthand. + +```php +// Correct + + +// Incorrect + + +// Incorrect + +``` + +### Quotes + +Use single quotes when not evaluating anything inside the string. + +```php +// Correct +$str = 'Hello World'; +$str = "Hello $name"; +$str = "Hello {$user->name}"; + +// Incorrect +$str = "Hello World"; +``` + +### String Concatenation + +Space on both sides of the concatenation operator. + +```php +$string = 'Hello ' . $name . ', welcome!'; +``` + +### Include and Require + +Do not use parentheses. Use `require_once` for dependencies. + +```php +// Correct +require_once ABSPATH . 'wp-admin/includes/file.php'; + +// Incorrect +require_once( ABSPATH . 'wp-admin/includes/file.php' ); +``` + +--- + +## 9. Object-Oriented Programming + +### One Structure Per File + +Each class, interface, trait, or enum should be in its own file. + +### Visibility + +Always declare visibility for properties and methods. + +```php +class My_Class { + public $public_property; + protected $protected_property; + private $private_property; + + public function public_method() {} + protected function protected_method() {} + private function private_method() {} +} +``` + +### Class Instantiation + +Always use parentheses when instantiating a class. + +```php +// Correct +$instance = new My_Class(); + +// Incorrect +$instance = new My_Class; +``` + +### Object Operator Spacing + +No spaces around the object operator. + +```php +$object->property; +$object->method(); +``` + +--- + +## 10. Namespaces and Imports + +### Namespace Naming + +Capitalized words separated by underscores. + +```php +namespace Jenga\Starter_Plugin; +namespace Jenga\Starter_Plugin\Helpers; +``` + +### Namespace Declaration + +One namespace per file, at the top after the PHP opening tag. + +### WordPress Prefix + +Never use `WP_` or `WordPress` as a namespace prefix. + +### Use Statements + +One use statement per line. + +```php +use Jenga\Starter_Plugin\Helpers\Array_Helper; +use Jenga\Starter_Plugin\Helpers\String_Helper; +``` + +--- + +## 11. Shell Commands + +### No Backticks + +Never use backtick operator for shell commands. + +```php +// Incorrect +$output = `ls -la`; + +// Correct +$output = shell_exec( 'ls -la' ); +``` + +### Escape User Input + +Always escape user input before using in shell commands. + +```php +$safe_arg = escapeshellarg( $user_input ); +$safe_cmd = escapeshellcmd( $command ); +$output = shell_exec( "ls $safe_arg" ); +``` + +--- + +## 12. Magic Methods + +Use magic methods appropriately and document them. + +```php +/** + * Get a property value. + * + * @param string $name Property name. + * @return mixed Property value. + */ +public function __get( $name ) { + return $this->data[ $name ] ?? null; +} +``` + +--- + +## 13. Closures + +### Spacing + +Space after `function` keyword. Space before and after `use`. + +```php +$closure = function ( $arg ) use ( $var ) { + return $arg . $var; +}; +``` + +### Arrow Functions + +Space around the arrow. + +```php +$double = fn( $n ) => $n * 2; +``` + +--- + +## Tooling + +```bash +# Install WordPress Coding Standards +composer require --dev wp-coding-standards/wpcs + +# Check a file +./vendor/bin/phpcs --standard=WordPress path/to/file.php + +# Auto-fix issues +./vendor/bin/phpcbf --standard=WordPress path/to/file.php +``` From fd930262d3668b21feb2d68558d0c89b9e59420b Mon Sep 17 00:00:00 2001 From: Sherv Date: Tue, 10 Feb 2026 17:10:06 +0300 Subject: [PATCH 32/90] feat(windsurf): add WordPress Gutenberg block editor development standards rule Add comprehensive WordPress Block Editor (Gutenberg) development standards rule covering block.json registration requirements, API version 3 usage, useBlockProps hook implementation, file organization structure, edit/save component patterns, WordPress package imports, data layer usage (useSelect/useDispatch), block controls (InspectorControls/BlockControls), InnerBlocks implementation, RichText component usage, media --- .windsurf/rules/wordpress/gutenberg.md | 842 +++++++++++++++++++++++++ 1 file changed, 842 insertions(+) create mode 100644 .windsurf/rules/wordpress/gutenberg.md diff --git a/.windsurf/rules/wordpress/gutenberg.md b/.windsurf/rules/wordpress/gutenberg.md new file mode 100644 index 0000000000..1d20b9eec8 --- /dev/null +++ b/.windsurf/rules/wordpress/gutenberg.md @@ -0,0 +1,842 @@ +--- +trigger: glob +globs: + [ + "**/blocks/**/*.js", + "**/blocks/**/*.jsx", + "**/block-editor/**/*.js", + "**/gutenberg/**/*.js", + "**/*.block.js", + "**/block.json", + ] +description: WordPress Block Editor (Gutenberg) development standards. Auto-applies when working with block-related files. +--- + +# WordPress Block Editor (Gutenberg) Standards + +Comprehensive guidelines for developing WordPress blocks and extending the Block Editor. + +**References:** + +- [Block Editor Handbook](https://developer.wordpress.org/block-editor/) +- [Block API Reference](https://developer.wordpress.org/block-editor/reference-guides/block-api/) +- [Component Reference](https://developer.wordpress.org/block-editor/reference-guides/components/) +- [Data Module Reference](https://developer.wordpress.org/block-editor/reference-guides/data/) + +--- + +## Critical Rules + +### Use block.json for Registration + +Always register blocks using `block.json` metadata file. This is the canonical method since WordPress 5.8. + +```json +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 3, + "name": "my-plugin/my-block", + "title": "My Block", + "category": "widgets", + "icon": "star", + "description": "A custom block.", + "keywords": ["custom", "block"], + "textdomain": "my-plugin", + "attributes": { + "content": { + "type": "string", + "source": "html", + "selector": ".content" + } + }, + "supports": { + "align": true, + "html": false + }, + "editorScript": "file:./index.js", + "editorStyle": "file:./index.css", + "style": "file:./style.css", + "render": "file:./render.php" +} +``` + +**Benefits:** + +- Lazy loading of assets (only loads when block is present) +- Block Directory recognition +- Server-side registration via REST API +- Schema validation in editors + +### Use API Version 3 + +Always use `apiVersion: 3` (introduced in WordPress 6.3) for new blocks. + +```json +{ + "apiVersion": 3 +} +``` + +### Use useBlockProps Hook + +Every block wrapper must use `useBlockProps` for proper editor integration. + +```jsx +import { useBlockProps } from "@wordpress/block-editor"; + +export default function Edit({ attributes, setAttributes }) { + const blockProps = useBlockProps(); + + return
{/* Block content */}
; +} +``` + +For custom classes or attributes: + +```jsx +const blockProps = useBlockProps({ + className: "my-custom-class", + "data-custom": "value", +}); +``` + +--- + +## 1. Block Structure + +### File Organization + +```text +blocks/ +└── my-block/ + ├── block.json # Block metadata (required) + ├── index.js # Block registration + ├── edit.js # Editor component + ├── save.js # Save component (static blocks) + ├── render.php # Server-side render (dynamic blocks) + ├── index.css # Editor styles + ├── style.css # Frontend + editor styles + └── view.js # Frontend JavaScript +``` + +### Block Registration + +```jsx +// index.js +import { registerBlockType } from "@wordpress/blocks"; +import Edit from "./edit"; +import save from "./save"; +import metadata from "./block.json"; + +registerBlockType(metadata.name, { + edit: Edit, + save, +}); +``` + +--- + +## 2. Edit Component + +### Functional Components Only + +Use functional components with hooks. Never use class components. + +```jsx +// CORRECT +import { useBlockProps } from "@wordpress/block-editor"; + +export default function Edit({ attributes, setAttributes, isSelected }) { + const blockProps = useBlockProps(); + + return
{/* Content */}
; +} + +// INCORRECT - Class component +class Edit extends Component { + render() { + return
{/* Content */}
; + } +} +``` + +### Destructure Props + +Always destructure props at the function signature level. + +```jsx +export default function Edit({ + attributes, + setAttributes, + isSelected, + clientId, + context, +}) { + const { content, alignment } = attributes; + // ... +} +``` + +### Update Attributes Immutably + +Use `setAttributes` with object spread for updates. + +```jsx +// CORRECT +setAttributes({ content: newContent }); +setAttributes({ ...attributes, items: [...items, newItem] }); + +// INCORRECT - Direct mutation +attributes.content = newContent; +``` + +--- + +## 3. Save Component + +### Static vs Dynamic Blocks + +**Static blocks:** Content saved to post content as HTML. + +```jsx +// save.js +import { useBlockProps } from "@wordpress/block-editor"; + +export default function save({ attributes }) { + const blockProps = useBlockProps.save(); + + return ( +
+

{attributes.content}

+
+ ); +} +``` + +**Dynamic blocks:** Content rendered server-side via PHP. + +```jsx +// save.js - Return null for dynamic blocks +export default function save() { + return null; +} +``` + +```php +// render.php +
> + +
+``` + +### Block Wrapper in Save + +Always use `useBlockProps.save()` in the save function. + +```jsx +import { useBlockProps } from "@wordpress/block-editor"; + +export default function save({ attributes }) { + const blockProps = useBlockProps.save({ + className: `align-${attributes.alignment}`, + }); + + return
{attributes.content}
; +} +``` + +--- + +## 4. WordPress Packages + +### Essential Packages + +```jsx +// Block Editor +import { + useBlockProps, + RichText, + InspectorControls, + BlockControls, + InnerBlocks, + MediaUpload, + MediaUploadCheck, +} from "@wordpress/block-editor"; + +// Components +import { + PanelBody, + TextControl, + ToggleControl, + SelectControl, + Button, + Spinner, + Placeholder, +} from "@wordpress/components"; + +// Data +import { useSelect, useDispatch } from "@wordpress/data"; + +// Core utilities +import { __ } from "@wordpress/i18n"; +import { useEffect, useState, useCallback } from "@wordpress/element"; +import domReady from "@wordpress/dom-ready"; +``` + +### Importing via WordPress Global + +When not using a build process, access packages via `wp` global. + +```javascript +const { useBlockProps } = wp.blockEditor; +const { Button } = wp.components; +const { __ } = wp.i18n; +``` + +### Package Dependencies + +In PHP, declare script dependencies correctly: + +```php +wp_enqueue_script( + 'my-block-editor', + plugins_url( 'build/index.js', __FILE__ ), + array( 'wp-blocks', 'wp-block-editor', 'wp-components', 'wp-i18n', 'wp-element' ), + filemtime( plugin_dir_path( __FILE__ ) . 'build/index.js' ) +); +``` + +--- + +## 5. Data Layer + +### useSelect for Reading Data + +```jsx +import { useSelect } from "@wordpress/data"; +import { store as coreStore } from "@wordpress/core-data"; + +function MyComponent() { + const posts = useSelect((select) => { + return select(coreStore).getEntityRecords("postType", "post", { + per_page: 10, + }); + }, []); + + if (!posts) { + return ; + } + + return ( +
    + {posts.map((post) => ( +
  • {post.title.rendered}
  • + ))} +
+ ); +} +``` + +### useDispatch for Writing Data + +```jsx +import { useDispatch } from "@wordpress/data"; +import { store as noticesStore } from "@wordpress/notices"; + +function MyComponent() { + const { createSuccessNotice } = useDispatch(noticesStore); + + const handleSave = () => { + // Save logic + createSuccessNotice("Saved successfully!"); + }; + + return ; +} +``` + +### Available Data Stores + +| Store | Purpose | +| ------------------- | ----------------------------------------- | +| `core` | WordPress core data (posts, users, terms) | +| `core/block-editor` | Block editor state | +| `core/blocks` | Block types registry | +| `core/editor` | Post editor state | +| `core/notices` | Admin notices | +| `core/preferences` | User preferences | + +--- + +## 6. Block Controls + +### Inspector Controls (Sidebar) + +```jsx +import { InspectorControls } from "@wordpress/block-editor"; +import { PanelBody, ToggleControl, RangeControl } from "@wordpress/components"; + +export default function Edit({ attributes, setAttributes }) { + const { showTitle, columns } = attributes; + + return ( + <> + + + setAttributes({ showTitle: value })} + /> + setAttributes({ columns: value })} + min={1} + max={4} + /> + + + {/* Block content */} + + ); +} +``` + +### Block Controls (Toolbar) + +```jsx +import { BlockControls, AlignmentToolbar } from "@wordpress/block-editor"; + +export default function Edit({ attributes, setAttributes }) { + const { alignment } = attributes; + + return ( + <> + + setAttributes({ alignment: value })} + /> + + {/* Block content */} + + ); +} +``` + +--- + +## 7. Inner Blocks + +### Basic Usage + +```jsx +import { useBlockProps, InnerBlocks } from "@wordpress/block-editor"; + +export default function Edit() { + const blockProps = useBlockProps(); + + return ( +
+ +
+ ); +} + +export function save() { + const blockProps = useBlockProps.save(); + + return ( +
+ +
+ ); +} +``` + +### Template Lock Options + +| Value | Behavior | +| --------------- | ----------------------------------------- | +| `false` | Blocks can be moved, added, removed | +| `'all'` | No changes allowed | +| `'insert'` | Blocks can be moved but not added/removed | +| `'contentOnly'` | Only content editing allowed | + +--- + +## 8. Rich Text + +```jsx +import { RichText, useBlockProps } from "@wordpress/block-editor"; + +export default function Edit({ attributes, setAttributes }) { + const blockProps = useBlockProps(); + + return ( + setAttributes({ content })} + placeholder={__("Enter text...", "my-plugin")} + allowedFormats={["core/bold", "core/italic", "core/link"]} + /> + ); +} + +export function save({ attributes }) { + const blockProps = useBlockProps.save(); + + return ( + + ); +} +``` + +--- + +## 9. Block Supports + +Define in `block.json` for automatic features: + +```json +{ + "supports": { + "align": ["wide", "full"], + "anchor": true, + "className": true, + "color": { + "background": true, + "text": true, + "gradients": true + }, + "spacing": { + "margin": true, + "padding": true + }, + "typography": { + "fontSize": true, + "lineHeight": true + }, + "html": false, + "reusable": true + } +} +``` + +--- + +## 10. Block Filters + +### Modify Block Registration + +```jsx +import { addFilter } from "@wordpress/hooks"; + +addFilter( + "blocks.registerBlockType", + "my-plugin/modify-paragraph", + (settings, name) => { + if (name !== "core/paragraph") { + return settings; + } + + return { + ...settings, + supports: { + ...settings.supports, + customClassName: false, + }, + }; + }, +); +``` + +### Extend Block Edit + +```jsx +import { addFilter } from "@wordpress/hooks"; +import { createHigherOrderComponent } from "@wordpress/compose"; +import { InspectorControls } from "@wordpress/block-editor"; +import { PanelBody } from "@wordpress/components"; + +const withCustomControls = createHigherOrderComponent((BlockEdit) => { + return (props) => { + if (props.name !== "core/paragraph") { + return ; + } + + return ( + <> + + + + {/* Custom controls */} + + + + ); + }; +}, "withCustomControls"); + +addFilter("editor.BlockEdit", "my-plugin/custom-controls", withCustomControls); +``` + +--- + +## 11. Internationalization + +### Translating Strings + +```jsx +import { __, _n, sprintf } from "@wordpress/i18n"; + +// Simple string +const label = __("My Label", "my-plugin"); + +// Pluralization +const message = sprintf(_n("%d item", "%d items", count, "my-plugin"), count); + +// With variables +const greeting = sprintf(__("Hello, %s!", "my-plugin"), userName); +``` + +### In block.json + +Translatable fields are automatically handled when `textdomain` is set. + +```json +{ + "textdomain": "my-plugin", + "title": "My Block", + "description": "A custom block.", + "keywords": ["custom"] +} +``` + +--- + +## 12. Build Process + +### wp-scripts Commands + +```bash +# Development with watch +npm start + +# Production build +npm run build + +# Linting +npm run lint:js +npm run lint:css + +# Testing +npm run test:unit +``` + +### webpack Configuration + +For custom config, extend `@wordpress/scripts`: + +```javascript +// webpack.config.js +const defaultConfig = require("@wordpress/scripts/config/webpack.config"); + +module.exports = { + ...defaultConfig, + entry: { + ...defaultConfig.entry, + "custom-entry": "./src/custom-entry.js", + }, +}; +``` + +--- + +## 13. React Best Practices (Aligned with WP) + +### Memoization + +```jsx +import { useMemo, useCallback } from "@wordpress/element"; + +function MyComponent({ items, onSelect }) { + // Memoize expensive computations + const processedItems = useMemo(() => { + return items.map((item) => expensiveProcess(item)); + }, [items]); + + // Memoize callbacks passed to children + const handleSelect = useCallback( + (id) => { + onSelect(id); + }, + [onSelect], + ); + + return ; +} +``` + +### Avoid Re-renders + +```jsx +// INCORRECT - Creates new object every render +; + +// CORRECT - Stable reference +const style = useMemo(() => ({ color: "red" }), []); +; +``` + +### State Initialization + +```jsx +// CORRECT - Lazy initialization for expensive values +const [state, setState] = useState(() => computeExpensiveValue()); + +// INCORRECT - Runs on every render +const [state, setState] = useState(computeExpensiveValue()); +``` + +### Derived State + +```jsx +// CORRECT - Derive during render +function MyComponent({ items }) { + const filteredItems = items.filter((item) => item.active); + // ... +} + +// INCORRECT - Unnecessary state and effect +function MyComponent({ items }) { + const [filteredItems, setFilteredItems] = useState([]); + + useEffect(() => { + setFilteredItems(items.filter((item) => item.active)); + }, [items]); +} +``` + +--- + +## 14. Performance + +### Parallel Data Fetching + +```jsx +// CORRECT - Parallel fetching +const { posts, categories } = useSelect( ( select ) => { + const { getEntityRecords } = select( coreStore ); + return { + posts: getEntityRecords( 'postType', 'post' ), + categories: getEntityRecords( 'taxonomy', 'category' ), + }; +}, [] ); + +// INCORRECT - Sequential in separate hooks +const posts = useSelect( ( select ) => /* ... */ ); +const categories = useSelect( ( select ) => /* ... */ ); +``` + +### Selector Dependencies + +```jsx +// CORRECT - Minimal dependencies +const postTitle = useSelect( + (select) => { + return select(coreStore).getEntityRecord("postType", "post", postId)?.title; + }, + [postId], +); + +// INCORRECT - Returns entire object, causes re-renders +const post = useSelect( + (select) => { + return select(coreStore).getEntityRecord("postType", "post", postId); + }, + [postId], +); +``` + +### Dynamic Imports + +```jsx +import { lazy, Suspense } from "@wordpress/element"; + +const HeavyComponent = lazy(() => import("./HeavyComponent")); + +function MyBlock() { + return ( + }> + + + ); +} +``` + +--- + +## 15. Composition Patterns + +### Compound Components + +```jsx +// Instead of boolean props + + +// Use composition + + + + + + +``` + +### Context for Shared State + +```jsx +import { createContext, useContext } from "@wordpress/element"; + +const BlockContext = createContext(); + +function BlockProvider({ children, value }) { + return ( + {children} + ); +} + +function useBlockContext() { + return useContext(BlockContext); +} +``` + +--- + +## Tooling + +```bash +# Create new block +npx @wordpress/create-block my-block + +# Create block in existing plugin +npx @wordpress/create-block my-block --no-plugin + +# With interactive mode +npx @wordpress/create-block + +# Install packages +npm install @wordpress/scripts --save-dev +npm install @wordpress/block-editor @wordpress/blocks @wordpress/components @wordpress/i18n +``` From efd3bc984353493434090dd2ac21ba874f9cbd0d Mon Sep 17 00:00:00 2001 From: Sherv Date: Tue, 10 Feb 2026 17:10:16 +0300 Subject: [PATCH 33/90] feat(windsurf): modernize WordPress JavaScript coding standards with ES6+ best practices and anti-jQuery rules Add comprehensive modern JavaScript standards covering critical rules for new code (no jQuery, functional over class-based, ES6+ syntax), variable declarations (const/let usage, one per declaration, declare close to use), naming conventions (camelCase, PascalCase, SCREAMING_SNAKE_CASE with descriptive names), function patterns (arrow functions, pure functions, default/rest parameters, early --- .windsurf/rules/wordpress/javascript.md | 1210 +++++++++++++++++------ 1 file changed, 914 insertions(+), 296 deletions(-) diff --git a/.windsurf/rules/wordpress/javascript.md b/.windsurf/rules/wordpress/javascript.md index 4ceccbf89a..f41be23ac9 100644 --- a/.windsurf/rules/wordpress/javascript.md +++ b/.windsurf/rules/wordpress/javascript.md @@ -1,539 +1,1157 @@ --- trigger: glob globs: ["**/*.js", "**/*.jsx", "**/*.mjs"] -description: WordPress JavaScript coding standards. Auto-applies when working with JS files. +description: WordPress JavaScript coding standards with modern ES6+ best practices. Auto-applies when working with JS files. --- # WordPress JavaScript Coding Standards -Based on WordPress Core Official Standards. Apply when maintaining, generating, or refactoring JavaScript code. +Based on WordPress Core Official Standards and modern best practices from Google, Airbnb, and major tech companies. -**Reference:** [WordPress JavaScript Coding Standards](https://developer.wordpress.org/coding-standards/wordpress-coding-standards/javascript/) +**References:** + +- [WordPress JavaScript Coding Standards](https://developer.wordpress.org/coding-standards/wordpress-coding-standards/javascript/) +- [Google JavaScript Style Guide](https://google.github.io/styleguide/jsguide.html) +- [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript) --- -## 1. Spacing +## Critical Rules for New Code -### General Rules +### No jQuery -- Use tabs for indentation -- No whitespace at end of line or on blank lines -- Lines should be 80-100 characters maximum -- Always use braces for `if`, `else`, `for`, `while`, `try` -- Add space after `!` negation operator -- Include new line at end of each file +New code must NOT use jQuery. Use native DOM APIs and modern JavaScript. -### Object Declarations +```javascript +// INCORRECT - jQuery +$(".my-element").addClass("active"); +$(".my-element").on("click", handler); +$.ajax({ url: "/api" }); + +// CORRECT - Native +document.querySelector(".my-element").classList.add("active"); +document.querySelector(".my-element").addEventListener("click", handler); +fetch("/api"); +``` -Use multiline format for clarity. +### Functional Over Class-Based + +Prefer functional and modular implementations over classes. ```javascript -var obj = { - ready: 9, - when: 4, - 'you are': 15, +// INCORRECT - Class-based +class TemplateManager { + constructor() { + this.templates = []; + } + + addTemplate(template) { + this.templates.push(template); + } +} + +// CORRECT - Functional/Modular +const createTemplateManager = () => { + const templates = []; + + const addTemplate = (template) => { + templates.push(template); + }; + + const getTemplates = () => [...templates]; + + return { addTemplate, getTemplates }; }; ``` -For single property, inline is acceptable. +### ES6+ Modern Syntax + +All new code must use ES6+ features. Target stable ECMAScript features only. + +--- + +## 1. Variables and Declarations + +### Use const and let + +Never use `var`. Use `const` by default. Use `let` only when reassignment is needed. ```javascript -var obj = { ready: 9 }; +// CORRECT +const MAX_COUNT = 100; +const config = { timeout: 30 }; +let currentIndex = 0; + +// INCORRECT +var count = 0; ``` -### Array Declarations +### One Variable Per Declaration + +Each variable gets its own declaration statement. ```javascript -var arr = [ - 'one', - 'two', - 'three', -]; +// CORRECT +const a = 1; +const b = 2; +let c = 3; + +// INCORRECT +const a = 1, + b = 2; +let c = 3, + d = 4; ``` -### Function Calls +### Declare Close to First Use -Include space inside parentheses. +Declare variables close to where they are first used, not at the top of functions. ```javascript -foo( arg1, arg2 ); +// CORRECT +function processItems(items) { + if (!items.length) { + return []; + } + + const processed = []; + for (const item of items) { + const result = transform(item); + processed.push(result); + } + return processed; +} ``` -No space for empty parentheses. +--- + +## 2. Naming Conventions + +| Type | Convention | Example | +| -------------- | ---------------------- | ------------------------------ | +| Variables | camelCase | `currentUser`, `itemCount` | +| Functions | camelCase | `getUserData`, `handleClick` | +| Constants | SCREAMING_SNAKE_CASE | `MAX_ITEMS`, `API_URL` | +| Classes | PascalCase | `UserManager`, `FormValidator` | +| Private | Leading underscore | `_privateMethod` | +| Boolean | Prefix with is/has/can | `isActive`, `hasPermission` | +| Event handlers | Prefix with on/handle | `onClick`, `handleSubmit` | + +### Descriptive Names + +Use clear, descriptive names. Avoid abbreviations except well-known ones. ```javascript -foo(); +// CORRECT +const userAuthenticationToken = getToken(); +const isFormValid = validateForm(formData); +const handleFormSubmit = (event) => {}; + +// INCORRECT +const uat = getToken(); +const valid = validateForm(formData); +const submit = (e) => {}; ``` -### Control Structures +--- + +## 3. Functions + +### Prefer Arrow Functions + +Use arrow functions for callbacks and short functions. ```javascript -if ( condition ) { - doSomething( 'with a string' ); -} else if ( otherCondition ) { - otherThing( { - key: value, - } ); -} else { - somethingElse( true ); +// CORRECT +const numbers = [1, 2, 3]; +const doubled = numbers.map((n) => n * 2); + +elements.forEach((element) => { + element.classList.add("active"); +}); + +// Named function for complex logic +function processComplexData(data) { + // Multiple statements } +``` + +### Pure Functions + +Prefer pure functions without side effects. + +```javascript +// CORRECT - Pure function +const calculateTotal = (items) => { + return items.reduce((sum, item) => sum + item.price, 0); +}; + +// INCORRECT - Impure function with side effects +let total = 0; +const calculateTotal = (items) => { + items.forEach((item) => { + total += item.price; // Mutates external state + }); +}; +``` + +### Default Parameters -while ( ! condition ) { - iterating++; +Use default parameters instead of conditional logic. + +```javascript +// CORRECT +function createUser(name, role = "subscriber") { + return { name, role }; } -for ( i = 0; i < 100; i++ ) { - object[ array[ i ] ] = someFn( i ); +// INCORRECT +function createUser(name, role) { + role = role || "subscriber"; + return { name, role }; } ``` -### Operators +### Rest Parameters -Include spaces around operators. +Use rest parameters instead of `arguments` object. ```javascript -var x = a + b; -var y = a && b; -var z = a ? b : c; +// CORRECT +function sum(...numbers) { + return numbers.reduce((total, n) => total + n, 0); +} + +// INCORRECT +function sum() { + return Array.prototype.slice.call(arguments).reduce((t, n) => t + n, 0); +} +``` + +### Early Returns + +Use early returns to avoid deep nesting. + +```javascript +// CORRECT +function processUser(user) { + if (!user) { + return null; + } + + if (!user.isActive) { + return { error: "User inactive" }; + } + + return { data: user.profile }; +} + +// INCORRECT +function processUser(user) { + if (user) { + if (user.isActive) { + return { data: user.profile }; + } else { + return { error: "User inactive" }; + } + } else { + return null; + } +} ``` --- -## 2. Indentation and Line Breaks +## 4. Objects and Arrays -### Tab Indentation +### Object Shorthand -Use tabs for all indentation, including inside closures. +Use shorthand property and method syntax. ```javascript -( function( $ ) { - function doSomething() { - // Expressions indented with tabs - } -} )( jQuery ); +// CORRECT +const name = "John"; +const age = 30; +const user = { + name, + age, + greet() { + return `Hello, ${this.name}`; + }, +}; + +// INCORRECT +const user = { + name: name, + age: age, + greet: function () { + return "Hello, " + this.name; + }, +}; ``` -### Blocks +### Destructuring -Opening brace on same line as the statement. Closing brace on its own line after the last statement. +Use destructuring for objects and arrays. ```javascript -if ( condition ) { - doAction(); +// CORRECT +const { name, email } = user; +const [first, second] = items; +const { data: userData } = response; + +function processUser({ name, email, role = "user" }) { + // Use destructured values } + +// INCORRECT +const name = user.name; +const email = user.email; +const first = items[0]; ``` -### Multi-line Statements +### Spread Operator -When a statement is too long to fit on one line, line breaks must occur after an operator. +Use spread for copying and merging. ```javascript -var html = '

The sum of ' + a + ' and ' + b + ' plus ' + c + - ' is ' + ( a + b + c ) + '

'; +// CORRECT +const newArray = [...oldArray, newItem]; +const newObject = { ...oldObject, newProperty: value }; +const merged = { ...defaults, ...options }; + +// INCORRECT +const newArray = oldArray.concat([newItem]); +const newObject = Object.assign({}, oldObject, { newProperty: value }); ``` -### Chained Method Calls +### Computed Property Names -Use one call per line for chains that are hard to read on one line. +Use computed property names when needed. ```javascript -elements - .addClass( 'foo' ) - .children() - .html( 'hello' ) - .end() - .appendTo( 'body' ); +// CORRECT +const key = "dynamicKey"; +const obj = { + [key]: value, + [`prefix_${key}`]: otherValue, +}; ``` --- -## 3. Variables and Naming +## 5. Modules -### Variable Declarations +### ES Modules Only -Use `const` for values that do not change. Use `let` for values that change. Avoid `var` in modern code. +Use ES modules (import/export) for all new code. ```javascript -const MAX_COUNT = 100; -let currentCount = 0; +// CORRECT +import { getState, setState } from "./shared"; +import defaultExport from "./module"; +export const myFunction = () => {}; +export default mainFunction; + +// INCORRECT +const module = require("./module"); +module.exports = myFunction; ``` -Declare each variable on its own line. +### Named Exports Preferred + +Prefer named exports over default exports for better refactoring. ```javascript -let a = 1; -let b = 2; +// CORRECT +export const processData = (data) => {}; +export const validateData = (data) => {}; + +// Use +import { processData, validateData } from "./utils"; ``` -### Naming Conventions +### Single Import Per Module -Use camelCase with lowercase first letter for variables and functions. +Import from a module only once per file. ```javascript -var someVariable = 'value'; -function myFunction() {} +// CORRECT +import { foo, bar, baz } from "./utils"; + +// INCORRECT +import { foo } from "./utils"; +import { bar } from "./utils"; ``` -### Class Definitions +### No Wildcard Imports -Use UpperCamelCase (PascalCase) for class names. +Avoid wildcard imports in production code. ```javascript -class MyClass { - constructor() {} -} +// CORRECT +import { specificFunction } from "./utils"; + +// INCORRECT +import * as utils from "./utils"; ``` -### Constants +--- + +## 6. DOM Manipulation + +### Query Selectors -Use SCREAMING_SNAKE_CASE for true constants. +Use native query selectors. ```javascript -const MAX_ITEMS = 100; -const API_ENDPOINT = '/api/v1'; -``` +// Single element +const element = document.querySelector(".my-class"); +const byId = document.getElementById("my-id"); + +// Multiple elements +const elements = document.querySelectorAll(".my-class"); -### Private Properties +// Scoped query +const child = parent.querySelector(".child"); +``` -Prefix private properties with underscore. +### Element Creation ```javascript -this._privateProperty = 'value'; -``` +// Create element +const div = document.createElement("div"); +div.className = "my-class"; +div.textContent = "Hello"; +div.dataset.id = "123"; + +// Append +container.appendChild(div); -### Globals +// Insert HTML (use with caution, escape user input) +container.insertAdjacentHTML("beforeend", '
'); +``` -Avoid global variables. If unavoidable, set via `window`. +### Event Handling ```javascript -window.myGlobal = 'value'; -``` +// Add listener +element.addEventListener("click", handleClick); -### jQuery Pattern +// Remove listener +element.removeEventListener("click", handleClick); + +// Event delegation +container.addEventListener("click", (event) => { + if (event.target.matches(".button")) { + handleButtonClick(event); + } +}); + +// Custom events +element.dispatchEvent(new CustomEvent("custom-event", { detail: data })); +``` -Use IIFE to protect jQuery dollar sign. +### Class Manipulation ```javascript -( function( $ ) { - // Use $ safely here -} )( jQuery ); +element.classList.add("active"); +element.classList.remove("active"); +element.classList.toggle("active"); +element.classList.contains("active"); +element.classList.replace("old", "new"); ``` --- -## 4. Equality and Type Checks +## 7. Async Operations -### Strict Equality +### Fetch API -Always use strict equality operators `===` and `!==`. +Use Fetch API instead of XMLHttpRequest or jQuery.ajax. ```javascript -// Correct -if ( name === 'John' ) {} -if ( count !== 0 ) {} - -// Incorrect -if ( name == 'John' ) {} -if ( count != 0 ) {} +// GET request +const response = await fetch("/api/data"); +const data = await response.json(); + +// POST request +const response = await fetch("/api/submit", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(payload), +}); ``` -### Type Checks +### Async/Await -| Type | Check | -|------|-------| -| String | `typeof object === 'string'` | -| Number | `typeof object === 'number'` | -| Boolean | `typeof object === 'boolean'` | -| Object | `typeof object === 'object'` | -| Function | `typeof object === 'function'` | -| null | `object === null` | -| undefined | `typeof variable === 'undefined'` | -| Property exists | `object.hasOwnProperty( prop )` | -| Array | `Array.isArray( object )` | +Prefer async/await over Promise chains. ---- +```javascript +// CORRECT +async function loadData() { + try { + const response = await fetch(url); + if (!response.ok) { + throw new Error("Network error"); + } + const data = await response.json(); + return processData(data); + } catch (error) { + handleError(error); + return null; + } +} -## 5. Syntax Rules +// INCORRECT - Promise chain for simple operations +function loadData() { + return fetch(url) + .then((response) => response.json()) + .then((data) => processData(data)) + .catch((error) => handleError(error)); +} +``` -### Semicolons +### Parallel Async Operations -Always use semicolons. Never rely on Automatic Semicolon Insertion (ASI). +Use Promise.all for independent parallel operations. ```javascript -var foo = 'bar'; +// CORRECT - Parallel execution +const [users, posts, comments] = await Promise.all([ + fetchUsers(), + fetchPosts(), + fetchComments(), +]); + +// INCORRECT - Sequential when parallel is possible +const users = await fetchUsers(); +const posts = await fetchPosts(); +const comments = await fetchComments(); ``` -### Strings +--- + +## 8. Error Handling -Use single quotes for strings. +### Try-Catch for Async + +Always wrap async operations in try-catch. ```javascript -var myStr = 'strings should use single quotes'; +async function fetchData() { + try { + const response = await fetch(url); + return await response.json(); + } catch (error) { + console.error("Fetch failed:", error.message); + return null; + } +} ``` -For HTML inside JavaScript, use single quotes for the JavaScript string and double quotes for HTML attributes. +### Meaningful Error Messages + +Provide context in error messages. ```javascript -var html = 'Link'; +// CORRECT +throw new Error(`Failed to load template: ${templateId}`); + +// INCORRECT +throw new Error("Error"); ``` -### Template Literals +--- -Use template literals for string interpolation in ES6+. +## 9. State Management + +### Encapsulated State + +Use closures or modules for state management. ```javascript -const message = `Hello, ${name}!`; +// CORRECT - Module pattern from codebase +import { + getState, + getSingleState, + setState, + setSingleState, +} from "core/page-skeleton"; + +// Initialize state +setState({ + currentView: "list", + selectedItem: null, + isLoading: false, +}); + +// Update state +setSingleState("isLoading", true); + +// Read state +const { currentView, selectedItem } = getState(); ``` -### Switch Statements +### Immutable Updates -Use `break` for each case except when explicitly falling through. Indent `case` one tab from `switch`. +Never mutate state directly. ```javascript -switch ( event.keyCode ) { - case $.ui.keyCode.ENTER: - case $.ui.keyCode.SPACE: - executeFunction(); - break; - - case $.ui.keyCode.ESCAPE: - closeModal(); - break; +// CORRECT +const newState = { + ...state, + items: [...state.items, newItem], +}; - default: - break; -} +// INCORRECT +state.items.push(newItem); ``` --- -## 6. Best Practices +## 10. Anti-Patterns to Avoid -### Comments +### Global Variable Pollution -Place comments on line before the code. Precede with blank line. Use JSDoc for documentation. +```javascript +// INCORRECT +myGlobalVar = "value"; +window.myApp = {}; + +// CORRECT - Use modules +export const myValue = "value"; +``` + +### Callback Hell ```javascript -/** - * Function description. - * - * @param {string} param1 Description of parameter. - * @return {boolean} Description of return value. - */ -function myFunction( param1 ) { - // Single-line comment - return true; -} +// INCORRECT +getData((data) => { + processData(data, (result) => { + saveData(result, (response) => { + // Deeply nested + }); + }); +}); + +// CORRECT +const data = await getData(); +const result = await processData(data); +const response = await saveData(result); +``` + +### Modifying Built-in Prototypes + +```javascript +// NEVER DO THIS +Array.prototype.customMethod = function () {}; +String.prototype.myHelper = function () {}; ``` -### Arrays and Objects +### Using eval() or Function() + +```javascript +// NEVER DO THIS +eval(userInput); +new Function(userInput)(); +``` -Create arrays and objects using literal syntax. +### Magic Numbers/Strings ```javascript -// Correct -var arr = []; -var obj = {}; +// INCORRECT +if (status === 3) { +} +element.style.width = "768px"; + +// CORRECT +const STATUS_COMPLETE = 3; +const TABLET_BREAKPOINT = "768px"; -// Incorrect -var arr = new Array(); -var obj = new Object(); +if (status === STATUS_COMPLETE) { +} +element.style.width = TABLET_BREAKPOINT; ``` -### Iteration +### Excessive DOM Queries -For collections, use appropriate iteration methods. +```javascript +// INCORRECT +document.querySelector(".item").classList.add("active"); +document.querySelector(".item").textContent = "Updated"; +document.querySelector(".item").dataset.id = "123"; + +// CORRECT - Cache the reference +const item = document.querySelector(".item"); +item.classList.add("active"); +item.textContent = "Updated"; +item.dataset.id = "123"; +``` + +### DOM Manipulation in Loops ```javascript -// jQuery -$.each( collection, function( index, item ) { - // code -} ); +// INCORRECT - Causes reflow on each iteration +items.forEach((item) => { + container.appendChild(createItem(item)); +}); + +// CORRECT - Use DocumentFragment +const fragment = document.createDocumentFragment(); +items.forEach((item) => { + fragment.appendChild(createItem(item)); +}); +container.appendChild(fragment); +``` -// Native -collection.forEach( function( item, index ) { - // code -} ); +--- -// ES6+ -for ( const item of collection ) { - // code -} +## 11. Project Structure + +Follow modular folder structure as in form-templates and onboarding-wizard. + +``` +module-name/ +├── index.js # Entry point, exports public API +├── initializeModule.js # Initialization logic +├── elements/ # DOM element references +│ ├── elements.js +│ └── index.js +├── events/ # Event listeners +│ ├── clickListener.js +│ └── index.js +├── shared/ # Shared state and constants +│ ├── constants.js +│ ├── pageState.js +│ └── index.js +├── ui/ # UI manipulation functions +│ ├── showModal.js +│ └── index.js +└── utils/ # Utility functions + ├── validation.js + └── index.js ``` -### Avoid eval() +--- + +## 12. WordPress Integration -Never use `eval()`. +### Using wp.hooks -### Avoid with Statement +```javascript +// Actions +wp.hooks.doAction("myPlugin.beforeInit", { getState, setState }); +wp.hooks.addAction("myPlugin.afterSave", "myPlugin", handleAfterSave); + +// Filters +const value = wp.hooks.applyFilters("myPlugin.filterValue", defaultValue); +wp.hooks.addFilter("myPlugin.filterValue", "myPlugin", modifyValue); +``` -Never use `with` statement. +### Using @wordpress packages -### Code Refactoring +```javascript +import domReady from "@wordpress/dom-ready"; +import { __ } from "@wordpress/i18n"; +import apiFetch from "@wordpress/api-fetch"; -Do not refactor working code just for style. Only refactor if you are already modifying that code. +domReady(() => { + initializeModule(); +}); +``` --- -## 7. Functions +## Formatting -### Function Declarations +### Spacing + +- Use tabs for indentation +- Space inside parentheses for function calls with arguments +- Space after `!` negation operator +- Spaces around operators ```javascript -function myFunction( arg1, arg2 ) { - return arg1 + arg2; +if (condition) { + doSomething(arg1, arg2); } + +const isValid = !isEmpty && hasValue; +const total = a + b * c; ``` -### Function Expressions +### Line Length + +Keep lines under 100 characters. Break after operators. ```javascript -var myFunction = function( arg1, arg2 ) { - return arg1 + arg2; -}; +const result = + veryLongVariableName + anotherLongVariableName + yetAnotherVariable; ``` -### Arrow Functions (ES6+) +### Trailing Commas + +Use trailing commas in multiline structures. ```javascript -const myFunction = ( arg1, arg2 ) => { - return arg1 + arg2; +const obj = { + property1: "value1", + property2: "value2", }; -// Short form for single expression -const double = ( n ) => n * 2; +const arr = ["item1", "item2"]; ``` -### Default Parameters +--- -```javascript -function greet( name = 'World' ) { - return 'Hello, ' + name; +## 13. React Best Practices + +These patterns apply when writing React components for WordPress (blocks, admin pages, etc.). + +### Functional Components Only + +Never use class components. Use functional components with hooks. + +```jsx +// CORRECT +function MyComponent({ title, onSave }) { + const [isLoading, setIsLoading] = useState(false); + + return
{title}
; +} + +// INCORRECT +class MyComponent extends Component { + render() { + return
{this.props.title}
; + } } ``` ---- +### Hooks Rules -## 8. Classes (ES6+) +1. Only call hooks at the top level +2. Only call hooks from React functions +3. Use `@wordpress/element` for React hooks in WordPress -```javascript -class Animal { - constructor( name ) { - this.name = name; - } +```jsx +import { useState, useEffect, useCallback, useMemo } from "@wordpress/element"; - speak() { - console.log( this.name + ' makes a sound.' ); - } +function MyComponent({ items }) { + // CORRECT - Hooks at top level + const [selected, setSelected] = useState(null); + + // INCORRECT - Conditional hook + if (items.length) { + const [count, setCount] = useState(0); // Error! + } } +``` -class Dog extends Animal { - constructor( name ) { - super( name ); - } +### Memoization + +Use `useMemo` for expensive computations and `useCallback` for stable function references. + +```jsx +import { useMemo, useCallback } from "@wordpress/element"; + +function ItemList({ items, onSelect }) { + // Memoize expensive computation + const processedItems = useMemo(() => { + return items.map((item) => expensiveProcess(item)); + }, [items]); + + // Stable callback reference + const handleSelect = useCallback( + (id) => { + onSelect(id); + }, + [onSelect], + ); + + return ( +
    + {processedItems.map((item) => ( +
  • handleSelect(item.id)}> + {item.name} +
  • + ))} +
+ ); +} +``` - speak() { - console.log( this.name + ' barks.' ); - } +### Avoid Unnecessary Re-renders + +```jsx +// INCORRECT - Creates new object every render + + i.active ) } /> + handleClick() } /> + +// CORRECT - Stable references +const style = useMemo( () => ( { color: 'red' } ), [] ); +const activeItems = useMemo( () => items.filter( ( i ) => i.active ), [ items ] ); +const handleClick = useCallback( () => { /* ... */ }, [] ); + + + + +``` + +### Derived State + +Derive values during render instead of syncing with effects. + +```jsx +// CORRECT - Derived during render +function FilteredList({ items, filter }) { + const filteredItems = items.filter((item) => item.type === filter); + + return ; +} + +// INCORRECT - Unnecessary state and effect +function FilteredList({ items, filter }) { + const [filteredItems, setFilteredItems] = useState([]); + + useEffect(() => { + setFilteredItems(items.filter((item) => item.type === filter)); + }, [items, filter]); + + return ; } ``` ---- +### State Initialization -## 9. Modules (ES6+) +Use lazy initialization for expensive values. -### Importing +```jsx +// CORRECT - Lazy initialization (function only called once) +const [state, setState] = useState(() => computeExpensiveValue(props)); -```javascript -import { Component } from 'react'; -import * as utils from './utils'; -import defaultExport from './module'; +// INCORRECT - Runs on every render +const [state, setState] = useState(computeExpensiveValue(props)); ``` -### Exporting +### Functional setState -```javascript -export function myFunction() {} -export const myConstant = 'value'; -export default MyClass; +Use functional updates when new state depends on previous state. + +```jsx +// CORRECT - Functional update +setCount((prevCount) => prevCount + 1); +setItems((prevItems) => [...prevItems, newItem]); + +// INCORRECT - May use stale state +setCount(count + 1); ``` ---- +### useRef for Transient Values -## 10. Promises and Async +Use refs for values that change frequently but do not need re-renders. -### Promises +```jsx +function Draggable() { + const positionRef = useRef({ x: 0, y: 0 }); -```javascript -fetchData() - .then( function( data ) { - return processData( data ); - } ) - .then( function( result ) { - displayResult( result ); - } ) - .catch( function( error ) { - handleError( error ); - } ); + const handleMouseMove = (event) => { + // Update ref without re-render + positionRef.current = { x: event.clientX, y: event.clientY }; + }; + + return
; +} ``` -### Async/Await (ES2017+) +### Early Returns in Components -```javascript -async function getData() { - try { - const data = await fetchData(); - const result = await processData( data ); - return result; - } catch ( error ) { - handleError( error ); - } +```jsx +function UserProfile({ user, isLoading }) { + if (isLoading) { + return ; + } + + if (!user) { + return ; + } + + return ( +
+

{user.name}

+ {/* Rest of component */} +
+ ); } ``` --- -## JSHint Configuration +## 14. Performance Patterns -```json -{ - "boss": true, - "curly": true, - "eqeqeq": true, - "eqnull": true, - "es3": true, - "expr": true, - "immed": true, - "noarg": true, - "nonbsp": true, - "onevar": true, - "quotmark": "single", - "trailing": true, - "undef": true, - "unused": true, - "browser": true, - "globals": { - "_": false, - "Backbone": false, - "jQuery": false, - "JSON": false, - "wp": false +### Promise.all for Parallel Fetching + +```jsx +// CORRECT - Parallel execution +const [users, posts] = await Promise.all([fetchUsers(), fetchPosts()]); + +// INCORRECT - Sequential when parallel is possible +const users = await fetchUsers(); +const posts = await fetchPosts(); +``` + +### Cache Function Results + +```jsx +// Module-level cache +const cache = new Map(); + +function expensiveOperation( key ) { + if ( cache.has( key ) ) { + return cache.get( key ); } + + const result = /* expensive computation */; + cache.set( key, result ); + return result; +} +``` + +### Use Set/Map for Lookups + +```jsx +// CORRECT - O(1) lookup +const selectedIds = new Set(selectedItems.map((item) => item.id)); +const isSelected = (id) => selectedIds.has(id); + +// INCORRECT - O(n) lookup +const isSelected = (id) => selectedItems.some((item) => item.id === id); +``` + +### Batch DOM Operations + +```jsx +// CORRECT - Single reflow +const fragment = document.createDocumentFragment(); +items.forEach((item) => { + const li = document.createElement("li"); + li.textContent = item.name; + fragment.appendChild(li); +}); +container.appendChild(fragment); + +// INCORRECT - Multiple reflows +items.forEach((item) => { + const li = document.createElement("li"); + li.textContent = item.name; + container.appendChild(li); // Reflow on each iteration +}); +``` + +### Content Visibility for Long Lists + +```css +.long-list-item { + content-visibility: auto; + contain-intrinsic-size: 0 50px; } ``` --- -## ESLint Configuration +## 15. Composition Patterns + +### Compound Components + +Instead of boolean props, use composition. + +```jsx +// INCORRECT - Boolean prop proliferation + + +// CORRECT - Compound components + + Title + + Content + + + + +``` + +### Children Over Render Props + +```jsx +// CORRECT - Children for composition + + Title + { content } + + +// AVOID - Render props when children work +
Title
} + renderContent={ () => content } +/> +``` + +### Context for Shared State -For modern projects, use ESLint with WordPress config. +```jsx +import { createContext, useContext, useState } from "@wordpress/element"; + +const FormContext = createContext(); + +function FormProvider({ children }) { + const [values, setValues] = useState({}); + const [errors, setErrors] = useState({}); + + const setValue = (name, value) => { + setValues((prev) => ({ ...prev, [name]: value })); + }; + + return ( + + {children} + + ); +} + +function useForm() { + const context = useContext(FormContext); + if (!context) { + throw new Error("useForm must be used within FormProvider"); + } + return context; +} +``` + +--- + +## Tooling ```bash +# Install ESLint with WordPress config npm install --save-dev @wordpress/eslint-plugin -``` -```json +# .eslintrc.json { - "extends": [ "plugin:@wordpress/eslint-plugin/recommended" ] + "extends": [ "plugin:@wordpress/eslint-plugin/recommended" ], + "rules": { + "no-var": "error", + "prefer-const": "error", + "no-unused-vars": "error" + } } ``` From 5dc435a3970419f3a870ef137c42a3199c8c8acc Mon Sep 17 00:00:00 2001 From: Sherv Date: Tue, 10 Feb 2026 17:10:56 +0300 Subject: [PATCH 34/90] feat(windsurf): add WordPress VIP coding standards rule for enterprise hosting platform requirements Add comprehensive WordPress VIP standards rule covering database query optimization (proper $wpdb methods, prepared statements, LIMIT clauses), forbidden functions (extract, eval, file operations), remote request handling (WP HTTP API, timeouts, SSL verification), file operations (WP_Filesystem, upload handling), caching strategies (transients, object cache, invalidation), output escaping (late esc --- .windsurf/rules/wordpress/vip.md | 585 +++++++++++++++++++++++++++++++ 1 file changed, 585 insertions(+) create mode 100644 .windsurf/rules/wordpress/vip.md diff --git a/.windsurf/rules/wordpress/vip.md b/.windsurf/rules/wordpress/vip.md new file mode 100644 index 0000000000..f2e503c28a --- /dev/null +++ b/.windsurf/rules/wordpress/vip.md @@ -0,0 +1,585 @@ +--- +trigger: glob +globs: ["**/*.php"] +description: WordPress VIP coding standards for performance and security. Auto-applies to PHP files. +--- + +# WordPress VIP Standards + +WordPress VIP platform requirements for performance, security, and scalability. + +**Reference:** [WordPress VIP Documentation](https://docs.wpvip.com/) + +--- + +## 1. Database Queries + +### Use Proper Methods + +Never use `$wpdb->query()` for SELECT statements. Use specific methods. + +| Method | Use Case | +|--------|----------| +| `$wpdb->get_results()` | Multiple rows | +| `$wpdb->get_row()` | Single row | +| `$wpdb->get_var()` | Single value | +| `$wpdb->get_col()` | Single column | + +```php +// Correct +$posts = $wpdb->get_results( + $wpdb->prepare( + "SELECT * FROM $wpdb->posts WHERE post_status = %s", + 'publish' + ) +); + +// Incorrect +$posts = $wpdb->query( "SELECT * FROM $wpdb->posts" ); +``` + +### Always Use Prepare + +Always use `$wpdb->prepare()` for queries with variables. + +```php +$wpdb->query( + $wpdb->prepare( + "UPDATE $wpdb->posts SET post_title = %s WHERE ID = %d", + $title, + $id + ) +); +``` + +### Limit Results + +Always include LIMIT clause to prevent unbounded queries. + +```php +$wpdb->get_results( + $wpdb->prepare( + "SELECT * FROM $wpdb->posts WHERE post_type = %s LIMIT %d", + 'page', + 100 + ) +); +``` + +### Avoid Direct Queries When Possible + +Use WordPress functions instead of direct queries. + +```php +// Prefer this +$posts = get_posts( array( + 'post_type' => 'post', + 'posts_per_page' => 10, +) ); + +// Over direct query +$posts = $wpdb->get_results( "SELECT * FROM $wpdb->posts LIMIT 10" ); +``` + +--- + +## 2. Forbidden Functions + +Never use these functions: + +| Function | Reason | Alternative | +|----------|--------|-------------| +| `extract()` | Makes code unpredictable | Access array keys directly | +| `eval()` | Security vulnerability | Refactor code logic | +| `create_function()` | Deprecated, insecure | Use anonymous functions | +| `compact()` | Reduces readability | Build arrays explicitly | +| `file_get_contents()` for URLs | Unreliable, no error handling | `wp_remote_get()` | +| `file_put_contents()` | Direct file access | WP_Filesystem | +| `curl_*` functions | Inconsistent behavior | WP HTTP API | +| `serialize()`/`unserialize()` for user data | Security risk | `json_encode()`/`json_decode()` | + +--- + +## 3. Remote Requests + +### HTTP API + +Use WordPress HTTP API for all remote requests. + +```php +// GET request +$response = wp_remote_get( 'https://api.example.com/data' ); + +if ( is_wp_error( $response ) ) { + $error_message = $response->get_error_message(); + // Handle error +} else { + $body = wp_remote_retrieve_body( $response ); + $data = json_decode( $body, true ); +} + +// POST request +$response = wp_remote_post( + 'https://api.example.com/submit', + array( + 'body' => array( + 'key' => 'value', + ), + 'timeout' => 30, + ) +); +``` + +### Timeouts + +Always set appropriate timeouts. + +```php +$response = wp_remote_get( + $url, + array( + 'timeout' => 15, + ) +); +``` + +### SSL Verification + +Never disable SSL verification in production. + +```php +// INCORRECT - Never do this +$response = wp_remote_get( + $url, + array( + 'sslverify' => false, + ) +); +``` + +--- + +## 4. File Operations + +### WP_Filesystem + +Use WP_Filesystem for file operations. + +```php +global $wp_filesystem; + +if ( ! function_exists( 'WP_Filesystem' ) ) { + require_once ABSPATH . 'wp-admin/includes/file.php'; +} + +WP_Filesystem(); + +// Read file +$content = $wp_filesystem->get_contents( $file_path ); + +// Write file +$wp_filesystem->put_contents( $file_path, $content, FS_CHMOD_FILE ); + +// Check if file exists +if ( $wp_filesystem->exists( $file_path ) ) { + // File exists +} +``` + +### Upload Handling + +```php +if ( ! function_exists( 'wp_handle_upload' ) ) { + require_once ABSPATH . 'wp-admin/includes/file.php'; +} + +$upload = wp_handle_upload( + $_FILES['file'], + array( + 'test_form' => false, + 'mimes' => array( + 'jpg|jpeg' => 'image/jpeg', + 'png' => 'image/png', + ), + ) +); + +if ( isset( $upload['error'] ) ) { + // Handle error +} +``` + +--- + +## 5. Caching + +### Transients + +Use transients for cached data. + +```php +$data = get_transient( 'my_cache_key' ); + +if ( false === $data ) { + $data = expensive_operation(); + set_transient( 'my_cache_key', $data, HOUR_IN_SECONDS ); +} + +return $data; +``` + +### Object Cache + +Use object cache for frequently accessed data. + +```php +$data = wp_cache_get( 'key', 'group' ); + +if ( false === $data ) { + $data = expensive_operation(); + wp_cache_set( 'key', $data, 'group', HOUR_IN_SECONDS ); +} + +return $data; +``` + +### Cache Keys + +Use descriptive and unique cache keys. + +```php +$cache_key = sprintf( 'user_posts_%d_%s', $user_id, $post_type ); +``` + +### Cache Invalidation + +Invalidate cache when data changes. + +```php +add_action( 'save_post', function( $post_id ) { + delete_transient( 'posts_cache' ); + wp_cache_delete( 'posts_list', 'my_plugin' ); +} ); +``` + +--- + +## 6. Escaping Output + +### Escape Late + +Escape data at output, not at assignment. + +```php +// Correct - escape at output +echo esc_html( $title ); + +// Incorrect - escape at assignment then output later +$title = esc_html( $raw_title ); +// ... later +echo $title; // Might be double-escaped or bypassed +``` + +### Escaping Functions + +| Function | Use Case | +|----------|----------| +| `esc_html()` | HTML element content | +| `esc_attr()` | HTML attributes | +| `esc_url()` | URLs | +| `esc_js()` | Inline JavaScript | +| `esc_textarea()` | Textarea content | +| `wp_kses_post()` | Allow safe HTML | +| `wp_kses()` | Custom allowed HTML | + +```php +
+

+ + + +
+ +
+
+``` + +### Translation with Escaping + +```php +echo esc_html__( 'Translated text', 'textdomain' ); +echo esc_attr__( 'Attribute text', 'textdomain' ); +printf( + esc_html__( 'Hello, %s!', 'textdomain' ), + esc_html( $name ) +); +``` + +--- + +## 7. Sanitizing Input + +### Sanitize Early + +Sanitize user input as early as possible. + +```php +$title = sanitize_text_field( wp_unslash( $_POST['title'] ) ); +$email = sanitize_email( $_POST['email'] ); +$url = esc_url_raw( $_POST['url'] ); +$key = sanitize_key( $_POST['key'] ); +$ids = array_map( 'absint', $_POST['ids'] ); +``` + +### Sanitization Functions + +| Function | Use Case | +|----------|----------| +| `sanitize_text_field()` | Single line text | +| `sanitize_textarea_field()` | Multi-line text | +| `sanitize_email()` | Email addresses | +| `sanitize_file_name()` | File names | +| `sanitize_key()` | Keys and slugs | +| `sanitize_title()` | Titles and slugs | +| `absint()` | Positive integers | +| `intval()` | Integers | +| `wp_kses()` | HTML with allowed tags | + +### Nonce Verification + +Always verify nonces for form submissions. + +```php +// In form +wp_nonce_field( 'my_action', 'my_nonce' ); + +// On submission +if ( ! isset( $_POST['my_nonce'] ) || + ! wp_verify_nonce( $_POST['my_nonce'], 'my_action' ) ) { + wp_die( 'Security check failed' ); +} +``` + +### Capability Checks + +Always verify user capabilities. + +```php +if ( ! current_user_can( 'edit_posts' ) ) { + wp_die( 'Unauthorized access' ); +} +``` + +--- + +## 8. Query Optimization + +### Avoid Meta Queries on Large Tables + +Meta queries do not scale. Consider alternatives: + +- Custom tables for high-volume data +- Taxonomy terms for filterable data +- Caching query results + +### Efficient Post Queries + +```php +$args = array( + 'post_type' => 'post', + 'posts_per_page' => 10, + 'no_found_rows' => true, // Skip pagination count + 'update_post_meta_cache' => false, // Skip meta cache if not needed + 'update_post_term_cache' => false, // Skip term cache if not needed + 'fields' => 'ids', // Only get IDs if that is all you need +); + +$query = new WP_Query( $args ); +``` + +### Avoid posts_per_page = -1 + +Never get all posts without limit. + +```php +// Incorrect +$args = array( + 'posts_per_page' => -1, +); + +// Correct - use reasonable limit +$args = array( + 'posts_per_page' => 100, +); +``` + +### Use Proper Indexing + +Ensure custom queries use indexed columns. + +--- + +## 9. Error Handling + +### No Error Suppression + +Never use `@` operator. + +```php +// Incorrect +$value = @file_get_contents( $file ); + +// Correct +if ( file_exists( $file ) && is_readable( $file ) ) { + $value = file_get_contents( $file ); +} else { + $value = false; +} +``` + +### Proper Error Checking + +```php +$result = some_operation(); + +if ( is_wp_error( $result ) ) { + error_log( 'Operation failed: ' . $result->get_error_message() ); + return false; +} + +return $result; +``` + +### Try-Catch for Exceptions + +```php +try { + $result = risky_operation(); +} catch ( Exception $e ) { + error_log( 'Exception: ' . $e->getMessage() ); + return new WP_Error( 'operation_failed', $e->getMessage() ); +} +``` + +--- + +## 10. Performance Best Practices + +### Lazy Loading + +Load resources only when needed. + +```php +add_action( 'wp_enqueue_scripts', function() { + if ( is_singular( 'product' ) ) { + wp_enqueue_script( 'product-gallery' ); + } +} ); +``` + +### Avoid Loading All Posts + +```php +// Incorrect - loads all posts into memory +$posts = get_posts( array( 'numberposts' => -1 ) ); +foreach ( $posts as $post ) { + // Process +} + +// Correct - batch processing +$paged = 1; +do { + $posts = get_posts( array( + 'numberposts' => 100, + 'paged' => $paged, + ) ); + + foreach ( $posts as $post ) { + // Process + } + + $paged++; +} while ( count( $posts ) === 100 ); +``` + +### Avoid Remote Requests in Loops + +```php +// Incorrect +foreach ( $items as $item ) { + $data = wp_remote_get( $item['url'] ); +} + +// Correct - batch or cache +$cached = get_transient( 'batch_data' ); +if ( false === $cached ) { + // Make single batch request or queue for background processing +} +``` + +--- + +## 11. Background Processing + +### WP Cron + +```php +// Schedule event +if ( ! wp_next_scheduled( 'my_cron_event' ) ) { + wp_schedule_event( time(), 'hourly', 'my_cron_event' ); +} + +// Handle event +add_action( 'my_cron_event', 'my_cron_function' ); +function my_cron_function() { + // Perform background task +} + +// Unschedule on deactivation +register_deactivation_hook( __FILE__, function() { + wp_clear_scheduled_hook( 'my_cron_event' ); +} ); +``` + +### Action Scheduler + +For more complex background jobs, use Action Scheduler library. + +--- + +## 12. Logging + +### Use Error Log + +```php +if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { + error_log( 'Debug message: ' . print_r( $data, true ) ); +} +``` + +### Never Log Sensitive Data + +```php +// INCORRECT - logs password +error_log( 'User login: ' . $username . ' / ' . $password ); + +// Correct +error_log( 'User login attempt: ' . $username ); +``` + +--- + +## Tooling + +```bash +# Install VIP Coding Standards +composer require automattic/vipwpcs + +# Configure phpcs.xml + + + + +# Run check +./vendor/bin/phpcs --standard=WordPress-VIP-Go path/to/file.php +``` From b658dda5056e35dd3a0587deaefe395b49aebe83 Mon Sep 17 00:00:00 2001 From: Sherv Date: Tue, 10 Feb 2026 17:11:29 +0300 Subject: [PATCH 35/90] refactor(windsurf): remove deprecated rules and consolidate WordPress coding standards into centralized skill-based documentation structure Remove conventional-commits.md, formidable-core.md, and wordpress-accessibility-coding-standards skill directory. Consolidate WordPress accessibility, CSS, HTML, JavaScript, PHP, Gutenberg, VIP, and modern JavaScript standards into unified skill-based documentation structure with improved organization, comprehensive coverage of WCAG 2.2 Level AA requirements --- .windsurf/rules/conventional-commits.md | 106 -- .windsurf/rules/formidable-core.md | 174 --- .../AGENTS.md | 469 ------- .../SKILL.md | 91 -- .../rules/operable.md | 122 -- .../rules/perceivable.md | 122 -- .../rules/robust.md | 91 -- .../rules/understandable.md | 116 -- .../rules/wordpress-specific.md | 139 -- .../wordpress-css-coding-standards/AGENTS.md | 578 --------- .../wordpress-css-coding-standards/SKILL.md | 79 -- .../rules/best-practices.md | 108 -- .../rules/comments.md | 81 -- .../rules/media-queries.md | 48 - .../rules/properties.md | 62 - .../rules/selectors.md | 65 - .../rules/structure.md | 63 - .../rules/values.md | 90 -- .../wordpress-html-coding-standards/AGENTS.md | 207 --- .../wordpress-html-coding-standards/SKILL.md | 61 - .../rules/attributes.md | 89 -- .../rules/indentation.md | 105 -- .../rules/self-closing-elements.md | 57 - .../rules/validation.md | 47 - .../AGENTS.md | 677 ---------- .../SKILL.md | 88 -- .../rules/best-practices.md | 137 -- .../rules/equality.md | 116 -- .../rules/indentation.md | 104 -- .../rules/spacing.md | 103 -- .../rules/syntax.md | 97 -- .../rules/variables.md | 101 -- .../wordpress-php-coding-standards/AGENTS.md | 1151 ----------------- .../wordpress-php-coding-standards/SKILL.md | 147 --- .../rules/best-practices.md | 167 --- .../rules/control-structures.md | 56 - .../rules/database-queries.md | 70 - .../rules/formatting.md | 137 -- .../rules/general-syntax.md | 76 -- .../rules/namespaces-imports.md | 148 --- .../rules/naming-conventions.md | 118 -- .../rules/oop.md | 97 -- .../rules/operators.md | 64 - .../rules/shell-commands.md | 23 - .../rules/whitespace.md | 117 -- .windsurf/workflows/fix-bug.md | 169 --- 46 files changed, 7133 deletions(-) delete mode 100644 .windsurf/rules/conventional-commits.md delete mode 100644 .windsurf/rules/formidable-core.md delete mode 100644 .windsurf/skills/wordpress-accessibility-coding-standards/AGENTS.md delete mode 100644 .windsurf/skills/wordpress-accessibility-coding-standards/SKILL.md delete mode 100644 .windsurf/skills/wordpress-accessibility-coding-standards/rules/operable.md delete mode 100644 .windsurf/skills/wordpress-accessibility-coding-standards/rules/perceivable.md delete mode 100644 .windsurf/skills/wordpress-accessibility-coding-standards/rules/robust.md delete mode 100644 .windsurf/skills/wordpress-accessibility-coding-standards/rules/understandable.md delete mode 100644 .windsurf/skills/wordpress-accessibility-coding-standards/rules/wordpress-specific.md delete mode 100644 .windsurf/skills/wordpress-css-coding-standards/AGENTS.md delete mode 100644 .windsurf/skills/wordpress-css-coding-standards/SKILL.md delete mode 100644 .windsurf/skills/wordpress-css-coding-standards/rules/best-practices.md delete mode 100644 .windsurf/skills/wordpress-css-coding-standards/rules/comments.md delete mode 100644 .windsurf/skills/wordpress-css-coding-standards/rules/media-queries.md delete mode 100644 .windsurf/skills/wordpress-css-coding-standards/rules/properties.md delete mode 100644 .windsurf/skills/wordpress-css-coding-standards/rules/selectors.md delete mode 100644 .windsurf/skills/wordpress-css-coding-standards/rules/structure.md delete mode 100644 .windsurf/skills/wordpress-css-coding-standards/rules/values.md delete mode 100644 .windsurf/skills/wordpress-html-coding-standards/AGENTS.md delete mode 100644 .windsurf/skills/wordpress-html-coding-standards/SKILL.md delete mode 100644 .windsurf/skills/wordpress-html-coding-standards/rules/attributes.md delete mode 100644 .windsurf/skills/wordpress-html-coding-standards/rules/indentation.md delete mode 100644 .windsurf/skills/wordpress-html-coding-standards/rules/self-closing-elements.md delete mode 100644 .windsurf/skills/wordpress-html-coding-standards/rules/validation.md delete mode 100644 .windsurf/skills/wordpress-javascript-coding-standards/AGENTS.md delete mode 100644 .windsurf/skills/wordpress-javascript-coding-standards/SKILL.md delete mode 100644 .windsurf/skills/wordpress-javascript-coding-standards/rules/best-practices.md delete mode 100644 .windsurf/skills/wordpress-javascript-coding-standards/rules/equality.md delete mode 100644 .windsurf/skills/wordpress-javascript-coding-standards/rules/indentation.md delete mode 100644 .windsurf/skills/wordpress-javascript-coding-standards/rules/spacing.md delete mode 100644 .windsurf/skills/wordpress-javascript-coding-standards/rules/syntax.md delete mode 100644 .windsurf/skills/wordpress-javascript-coding-standards/rules/variables.md delete mode 100644 .windsurf/skills/wordpress-php-coding-standards/AGENTS.md delete mode 100644 .windsurf/skills/wordpress-php-coding-standards/SKILL.md delete mode 100644 .windsurf/skills/wordpress-php-coding-standards/rules/best-practices.md delete mode 100644 .windsurf/skills/wordpress-php-coding-standards/rules/control-structures.md delete mode 100644 .windsurf/skills/wordpress-php-coding-standards/rules/database-queries.md delete mode 100644 .windsurf/skills/wordpress-php-coding-standards/rules/formatting.md delete mode 100644 .windsurf/skills/wordpress-php-coding-standards/rules/general-syntax.md delete mode 100644 .windsurf/skills/wordpress-php-coding-standards/rules/namespaces-imports.md delete mode 100644 .windsurf/skills/wordpress-php-coding-standards/rules/naming-conventions.md delete mode 100644 .windsurf/skills/wordpress-php-coding-standards/rules/oop.md delete mode 100644 .windsurf/skills/wordpress-php-coding-standards/rules/operators.md delete mode 100644 .windsurf/skills/wordpress-php-coding-standards/rules/shell-commands.md delete mode 100644 .windsurf/skills/wordpress-php-coding-standards/rules/whitespace.md delete mode 100644 .windsurf/workflows/fix-bug.md diff --git a/.windsurf/rules/conventional-commits.md b/.windsurf/rules/conventional-commits.md deleted file mode 100644 index 3e2d42be42..0000000000 --- a/.windsurf/rules/conventional-commits.md +++ /dev/null @@ -1,106 +0,0 @@ ---- -trigger: always_on -description: Enforces conventional commit message format for all git commits in the project. ---- - -# Conventional Commit Messages - -All commit messages MUST follow the Conventional Commits 1.0.0 specification. - -## Format - -``` -(): - -[optional body] - -[optional footer(s)] -``` - -## Types - -| Type | Description | SemVer | -| ---------- | ---------------------------------------------------- | ------ | -| `fix` | Bug fix (patches a bug in the codebase) | PATCH | -| `feat` | New feature (adds functionality to the codebase) | MINOR | -| `docs` | Documentation only changes | - | -| `style` | Code style changes (formatting, whitespace, etc.) | - | -| `refactor` | Code change that neither fixes a bug nor adds feature| - | -| `perf` | Performance improvement | - | -| `test` | Adding or correcting tests | - | -| `build` | Changes to build system or external dependencies | - | -| `ci` | CI configuration changes | - | -| `chore` | Other changes that don't modify src or test files | - | - -## Scope (Optional) - -The scope provides additional context about what part of the codebase the commit affects: - -- `builder` - Form builder related changes -- `entries` - Entry management changes -- `fields` - Field types or field handling -- `api` - API endpoints or integrations -- `admin` - Admin UI changes -- `frontend` - Front-end form display -- `db` - Database operations -- `i18n` - Internationalization -- `security` - Security fixes or improvements -- `deps` - Dependencies - -## Breaking Changes - -Indicate breaking changes with: - -1. `!` after type/scope: `feat(api)!: change response format` -2. `BREAKING CHANGE:` footer in the commit body - -## Examples - -``` -fix(fields): resolve date field validation error - -The date field was incorrectly validating dates in non-US formats. -Added locale-aware date parsing. - -Fixes #1234 -``` - -``` -feat(builder): add drag-and-drop field reordering - -Implements smooth drag-and-drop functionality for reordering fields -in the form builder interface. -``` - -``` -fix: prevent XSS in field labels - -Escape HTML entities in field labels before output. -``` - -``` -refactor(entries)!: change entry meta storage format - -BREAKING CHANGE: Entry meta now uses JSON encoding instead of -serialized PHP arrays. Run migration script before updating. -``` - -## Rules - -1. Type MUST be lowercase. -2. Description MUST start with lowercase letter. -3. Description MUST NOT end with a period. -4. Description MUST be imperative mood ("add" not "added" or "adds"). -5. Body MUST be separated from description by a blank line. -6. Breaking changes MUST be indicated with `!` or `BREAKING CHANGE:` footer. -7. Keep description under 72 characters. - -## AI Commit Message Generation - -When generating commit messages: - -1. Analyze the staged changes to determine the appropriate type. -2. Identify the scope from the files/directories changed. -3. Write a concise description in imperative mood. -4. Add body with details if the change is complex. -5. Include issue references if mentioned in the conversation. diff --git a/.windsurf/rules/formidable-core.md b/.windsurf/rules/formidable-core.md deleted file mode 100644 index 9851147896..0000000000 --- a/.windsurf/rules/formidable-core.md +++ /dev/null @@ -1,174 +0,0 @@ ---- -trigger: always_on -description: Core Formidable Forms development rules. Enforces WordPress VIP standards, Formidable coding patterns, and enterprise best practices. ---- - -# Formidable Forms Development Rules - -You are an AI assistant specialized in Formidable Forms plugin development. This is an enterprise-level WordPress plugin that must follow strict coding standards. - -## 0. Critical Principles - -- **NEVER guess** - Always search and verify before making changes. -- **Minimal scope** - Fix at the most specific location, closest to the problem. -- **Backward compatibility** - Maintain 100% backward compatibility with existing callers. -- **Pro plugin awareness** - Code must work when Pro is active AND when Pro is inactive. -- **No custom solutions** - NEVER invent new patterns; always use existing Formidable patterns. -- **User changes are final** - If the user makes manual changes after your updates, treat those as the authoritative version and do not revert or override them. - -## 1. Pattern Discovery Process - -Before making ANY change, follow this iterative process to find the correct Formidable pattern: - - - -**Step 1: Find existing patterns** - -- Search models, controllers, helpers, and other files for similar functionality. -- Identify how Formidable already handles this type of problem. -- Look for helper methods in `FrmAppHelper`, `FrmDb`, `FrmFieldsHelper`. - -**Step 2: Study pattern usage** - -- Search ALL places that use the discovered pattern. -- Understand the best way to use it based on existing implementations. -- Note any variations and when each is appropriate. - -**Step 3: Trace parent file hierarchy** - -- Search through all parent files up to the plugin root file. -- Understand the logic and behavior at each level. -- Verify the planned change location is optimal. - -**Step 4: Iterate if needed** - -- If a better practice or location is found, return to Step 1. -- Repeat until confident you have the best pattern AND best location. -- Do NOT proceed with custom solutions if existing patterns exist. - - - -## 2. Mandatory Research Before Changes - -Before ANY code change that involves WordPress functions or patterns: - -1. **Search the codebase** first to understand existing patterns. -2. **Search WordPress developer docs** using `@web` for: - - Function parameters and return types. - - Deprecated functions and alternatives. - - Security best practices. - - Performance implications. -3. **Search WordPress VIP docs** for performance-critical code. -4. **Verify** the approach aligns with existing Formidable patterns. - - - -- Database queries: Search WordPress VIP docs for query optimization. -- Sanitization: Search developer.wordpress.org for correct sanitize\_\* function. -- Escaping: Search for correct esc\_\* function for the output context. -- Hooks: Search codebase for existing hook patterns before adding new ones. -- Caching: Search VIP docs for caching best practices. - - - -## 3. Code Analysis Phase - -Before proposing solutions: - -1. **Read and understand** the complete issue. -2. **Identify ALL affected locations** in the codebase. -3. **Map dependencies** - what calls this code, what does this code call. -4. **Check Pro plugin requirement** - does code need Pro or must work without it. - -## 4. Solution Selection - -- Propose 2-3 solutions with trade-offs clearly stated. -- Choose the solution with minimal scope and lowest risk. -- Fix at the most specific location. -- Prefer adding safety checks over refactoring existing code. -- **Verify changes are not overkill** - If changes affect several areas, analyze all related places to ensure the flow and behavior are correct and the fix is not excessive. - -## 5. PHP Coding Standards - - - -- Use `elseif` not `else if`. -- Use strict comparisons (`===`, `!==`) always. -- Use `in_array()` with third parameter `true`. -- Functions max 100 lines, files max 1000 lines. -- Cyclomatic complexity max 10, cognitive complexity warning at 10. -- Line length max 180 characters. -- Tabs for indentation, not spaces. -- Opening brace on same line for functions and control structures. -- Space after control structure keywords (`if`, `for`, `foreach`, etc.). - - -## 6. WordPress VIP Standards - - - -- NEVER use `$wpdb->query()` for SELECT - use `$wpdb->get_results()`, `$wpdb->get_row()`, `$wpdb->get_var()`. -- ALWAYS use `$wpdb->prepare()` for queries with variables. -- NEVER use `extract()`. -- NEVER use `eval()`. -- NEVER use `create_function()`. -- Avoid `file_get_contents()` for remote URLs - use `wp_remote_get()`. -- Avoid direct file operations - use WP_Filesystem. -- Limit query results with LIMIT clause. -- Use transients or object cache for expensive operations. -- Escape late, sanitize early. - - -## 7. Formidable-Specific Patterns - - - -- Class naming: `FrmClassName` for Lite, `FrmProClassName` for Pro. -- Method naming: `snake_case` for public, `camelCase` legacy methods preserved. -- Hook naming: `frm_hook_name` for Lite, `frm_pro_hook_name` for Pro. -- Text domains: `formidable` for Lite, `formidable-pro` for Pro add-ons. -- Factory pattern: Use `FrmFieldFactory::get_field_type()` for field instances. -- Use `FrmAppHelper` methods for common operations. -- Use `FrmDb` class for database operations. - - -## 8. Security Requirements - - -- ALL user input must be sanitized using appropriate WordPress functions. -- ALL output must be escaped using appropriate `esc_*` functions. -- ALL AJAX handlers must verify nonce with `wp_verify_nonce()`. -- ALL AJAX handlers must check capabilities with `current_user_can()`. -- ALL database queries must use `$wpdb->prepare()`. -- NEVER trust `$_GET`, `$_POST`, `$_REQUEST` directly. - - -## 9. PHPDoc Standards - - -- Use `{@inheritDoc}` for methods/properties inherited from parent class. -- Include `@since x.x` only for new methods in existing classes. -- Class-level `@since` for new classes covers all members. -- All comments must end with a period. -- Keep descriptions concise and clear. - - -## 10. Testing Requirements - - -- Extend `FrmUnitTest` for Lite tests, `FrmProUnitTest` for Pro tests. -- Use `$this->factory->form->create_and_get()` for test data. -- Use `$this->run_private_method()` to test protected methods. -- All assertion messages must end with a period. -- Test scenarios: Pro active, Pro inactive, empty data, missing keys. - - -## 11. Change Verification - -Before completing any change, verify: - -- [ ] Does this change break any existing functionality? -- [ ] Does this work when Pro plugin is inactive? -- [ ] Are there PHP warnings/errors in any scenario? -- [ ] Is the change backward compatible? -- [ ] Have you searched docs for best practices? diff --git a/.windsurf/skills/wordpress-accessibility-coding-standards/AGENTS.md b/.windsurf/skills/wordpress-accessibility-coding-standards/AGENTS.md deleted file mode 100644 index 7f67a1e785..0000000000 --- a/.windsurf/skills/wordpress-accessibility-coding-standards/AGENTS.md +++ /dev/null @@ -1,469 +0,0 @@ -# WordPress Accessibility Coding Standards - -**Version 1.0.0** -Based on WordPress Core Official Standards - -> **Note:** -> This document is for AI agents and LLMs to follow when maintaining, -> generating, or refactoring code in the WordPress ecosystem to ensure accessibility. - ---- - -## Abstract - -Code integrated into the WordPress ecosystem—including WordPress core, WordPress.org websites, and official plugins—is expected to conform to the Web Content Accessibility Guidelines (WCAG), version 2.2, at level AA. - ---- - -## Table of Contents - -1. [Conformance Levels](#1-conformance-levels) -2. [WCAG Principles](#2-wcag-principles) -3. [Guidelines by Principle](#3-guidelines-by-principle) -4. [Success Criteria](#4-success-criteria) -5. [Techniques](#5-techniques) -6. [Authoritative Resources](#6-authoritative-resources) - ---- - -## 1. Conformance Levels - -### Level A — Minimum (CRITICAL) - -Addresses accessibility barriers on a very wide scale. Prevents many people from accessing the site. These are the **minimum requirements** for most web-based interfaces. - -### Level AA — WordPress Requirement (REQUIRED) - -The **WordPress commitment level**. These criteria address concerns that are generally more complicated but still common needs with broad reach. - -### Level AAA — Enhanced (ENCOURAGED) - -Targeted at very specific needs. May be difficult to implement effectively. Implement where relevant and possible. - -**Quick Reference:** [WCAG 2.2 Level A and AA Requirements](https://www.w3.org/WAI/WCAG22/quickref/) - ---- - -## 2. WCAG Principles - -WCAG 2.2 is organized around 4 principles. Content must be: - -### 2.1 Perceivable - -Users must be able to perceive the information presented. It cannot be invisible to all their senses. - -**Examples:** - -- Providing text alternatives for images (alt text) -- Providing captions for videos -- Ensuring sufficient color contrast - -### 2.2 Operable - -Users must be able to operate the interface. The interface cannot require interaction that a user cannot perform. - -**Examples:** - -- All functionality available via keyboard -- Users have enough time to read content -- No content that causes seizures - -### 2.3 Understandable - -Users must be able to understand the information and operation of the interface. - -**Examples:** - -- Readable text content -- Predictable page operation -- Input assistance for forms - -### 2.4 Robust - -Content must be robust enough to be interpreted by a wide variety of user agents, including assistive technologies. - -**Examples:** - -- Valid HTML markup -- ARIA used correctly -- Compatible with current and future technologies - ---- - -## 3. Guidelines by Principle - -### Principle 1: Perceivable - -**Guideline 1.1 — Text Alternatives** - -Provide text alternatives for any non-text content so it can be changed into other forms (large print, braille, speech, symbols, simpler language). - -```html - - - - -Company Name Logo - - - -``` - -**Guideline 1.2 — Time-based Media** - -Provide alternatives for time-based media (audio, video). - -- Captions for pre-recorded audio -- Audio descriptions for pre-recorded video -- Transcripts - -**Guideline 1.3 — Adaptable** - -Create content that can be presented in different ways without losing information or structure. - -```html - -
Important Title
- - -

Important Title

-``` - -**Guideline 1.4 — Distinguishable** - -Make it easier for users to see and hear content. - -- **Color contrast:** 4.5:1 minimum for normal text, 3:1 for large text -- **Text resize:** Works up to 200% -- **Don't use color alone** to convey information - -```css -/* Incorrect: relies on color alone */ -.error { - color: red; -} - -/* Correct: uses multiple indicators */ -.error { - color: #d00; - border-left: 4px solid #d00; -} -.error::before { - content: "Error: "; - font-weight: bold; -} -``` - -### Principle 2: Operable - -**Guideline 2.1 — Keyboard Accessible** - -Make all functionality available from a keyboard. - -```html - -
Click me
- - - - - -
- Click me -
-``` - -**Guideline 2.2 — Enough Time** - -Provide users enough time to read and use content. - -- Allow users to turn off, adjust, or extend time limits -- Pause, stop, hide moving/auto-updating content - -**Guideline 2.3 — Seizures and Physical Reactions** - -Do not design content in a way that causes seizures. - -- No content flashing more than 3 times per second - -**Guideline 2.4 — Navigable** - -Provide ways to help users navigate, find content, and determine where they are. - -```html - - - - -Contact Us - Company Name - - - -Click here - - -Download Annual Report (PDF) -``` - -**Guideline 2.5 — Input Modalities** - -Make it easier to operate through various inputs beyond keyboard. - -- Target size: minimum 24x24 CSS pixels (44x44 recommended) -- Don't require complex gestures - -### Principle 3: Understandable - -**Guideline 3.1 — Readable** - -Make text content readable and understandable. - -```html - - - -

- The French phrase c'est la vie means "that's life". -

- -``` - -**Guideline 3.2 — Predictable** - -Make Web pages appear and operate in predictable ways. - -- Consistent navigation -- Consistent identification -- No unexpected context changes on focus/input - -```html - - - - - -``` - -**Guideline 3.3 — Input Assistance** - -Help users avoid and correct mistakes. - -```html - - - -Please enter a valid email address - - - - -Format: 123-456-7890 -``` - -### Principle 4: Robust - -**Guideline 4.1 — Compatible** - -Maximize compatibility with user agents and assistive technologies. - -```html - - -

Content

- - -

Content

- - - - - - - - - -
Form submitted successfully
-``` - ---- - -## 4. Success Criteria - -Each guideline has specific success criteria that must be met. These can be tested using: - -1. **Automated tools:** Axe, WAVE, Lighthouse -2. **Manual testing:** Keyboard navigation, screen reader testing -3. **User testing:** Testing with people who have disabilities - -### Key Level A Criteria - -- 1.1.1 Non-text Content (alt text) -- 1.3.1 Info and Relationships (semantic markup) -- 2.1.1 Keyboard (all functionality) -- 2.4.1 Bypass Blocks (skip links) -- 4.1.2 Name, Role, Value (ARIA) - -### Key Level AA Criteria - -- 1.4.3 Contrast (Minimum) - 4.5:1 -- 1.4.4 Resize Text - 200% -- 2.4.6 Headings and Labels -- 2.4.7 Focus Visible - ---- - -## 5. Techniques - -### Sufficient Techniques - -Required to meet success criteria. - -```php -// WordPress: Always provide alt text - $alt_text ) ); ?> - -// Screen reader text - -``` - -### Advisory Techniques - -Go beyond requirements (recommended). - -```html - -
- -
-``` - -### Failure Techniques - -Patterns that **fail** accessibility requirements. - -```html - - - - - - - -Required -``` - ---- - -## 6. Authoritative Resources - -### Normative (Requirements) - -- [W3C WCAG 2.2](https://www.w3.org/TR/WCAG22) — Web Content Accessibility Guidelines -- [W3C ATAG 2.0](https://www.w3.org/TR/ATAG20/) — Authoring Tool Accessibility Guidelines -- [W3C WAI-ARIA 1.1](https://www.w3.org/TR/wai-aria/) — Accessible Rich Internet Applications - -### Informative (Guidance) - -- [Understanding WCAG 2.2](https://www.w3.org/WAI/WCAG22/Understanding/) -- [Using ARIA](https://www.w3.org/TR/using-aria/) -- [ARIA Authoring Practices Guide](https://www.w3.org/WAI/ARIA/apg/) — Design patterns - -### Technical Resources - -- [WordPress Accessibility Handbook](https://make.wordpress.org/accessibility/handbook/) -- [WordPress Accessibility Team](https://make.wordpress.org/accessibility/) - ---- - -## WordPress-Specific Practices - -### Screen Reader Text - -```css -.screen-reader-text { - border: 0; - clip: rect(1px, 1px, 1px, 1px); - clip-path: inset(50%); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - width: 1px; - word-wrap: normal !important; -} - -.screen-reader-text:focus { - background-color: #f1f1f1; - clip: auto !important; - clip-path: none; - display: block; - height: auto; - left: 5px; - padding: 15px 23px 14px; - top: 5px; - width: auto; - z-index: 100000; -} -``` - -### Skip Links - -```html - -``` - -### Focus Styles - -Never remove focus styles without providing an alternative. - -```css -/* Incorrect */ -*:focus { - outline: none; -} - -/* Correct */ -*:focus { - outline: 2px solid #0073aa; - outline-offset: 2px; -} -``` - -### ARIA in WordPress - -```php -// Accessible toggle - - -// Live regions for dynamic content -
- -
-``` diff --git a/.windsurf/skills/wordpress-accessibility-coding-standards/SKILL.md b/.windsurf/skills/wordpress-accessibility-coding-standards/SKILL.md deleted file mode 100644 index 3bb338a490..0000000000 --- a/.windsurf/skills/wordpress-accessibility-coding-standards/SKILL.md +++ /dev/null @@ -1,91 +0,0 @@ ---- -name: wordpress-accessibility-coding-standards -description: WordPress accessibility coding standards (WCAG 2.2 Level AA). Apply when working with UI elements, forms, or any user-facing code in WordPress plugins or themes. ---- - -# WordPress Accessibility Coding Standards - -**Version 1.0.0** -Based on WordPress Core Official Standards - -> **Note:** -> This document is for AI agents and LLMs to follow when maintaining, -> generating, or refactoring code in the WordPress ecosystem to ensure accessibility. - ---- - -## Overview - -Code integrated into the WordPress ecosystem is expected to conform to **WCAG 2.2 at Level AA**. New interfaces should incorporate ATAG 2.0 guidelines where applicable. - -**When to apply:** All WordPress Core, plugins, themes, and WordPress.org websites. - -**Reference:** [WordPress Accessibility Coding Standards](https://developer.wordpress.org/coding-standards/wordpress-coding-standards/accessibility/) - ---- - -## Conformance Levels - -### Level A — **CRITICAL** - -Minimum requirements. Prevents major accessibility barriers. - -### Level AA — **REQUIRED** - -WordPress commitment level. More nuanced accessibility concerns. - -### Level AAA — **ENCOURAGED** - -Targeted at specific needs. Implement where relevant. - ---- - -## Rule Categories by WCAG Principle - -### 1. Perceivable — **CRITICAL** - -| Rule | Impact | -| ----------------------------------- | -------------- | -| [Perceivable](rules/perceivable.md) | Content access | - -### 2. Operable — **CRITICAL** - -| Rule | Impact | -| ----------------------------- | --------------------- | -| [Operable](rules/operable.md) | Keyboard & navigation | - -### 3. Understandable — **HIGH** - -| Rule | Impact | -| ----------------------------------------- | ------------------ | -| [Understandable](rules/understandable.md) | User comprehension | - -### 4. Robust — **HIGH** - -| Rule | Impact | -| ------------------------- | ------------- | -| [Robust](rules/robust.md) | Compatibility | - -### 5. WordPress-Specific — **HIGH** - -| Rule | Impact | -| ------------------------------------------------- | ------------ | -| [WordPress Specific](rules/wordpress-specific.md) | WP ecosystem | - ---- - -## Usage - -```text -@wordpress-accessibility-coding-standards -``` - -See [AGENTS.md](AGENTS.md) for the complete reference. - ---- - -## Normative References - -- [W3C WCAG 2.2](https://www.w3.org/TR/WCAG22) -- [W3C ATAG 2.0](https://www.w3.org/TR/ATAG20/) -- [W3C WAI-ARIA 1.1](https://www.w3.org/TR/wai-aria/) diff --git a/.windsurf/skills/wordpress-accessibility-coding-standards/rules/operable.md b/.windsurf/skills/wordpress-accessibility-coding-standards/rules/operable.md deleted file mode 100644 index 8151a2d7bd..0000000000 --- a/.windsurf/skills/wordpress-accessibility-coding-standards/rules/operable.md +++ /dev/null @@ -1,122 +0,0 @@ -# Operable (WCAG Principle 2) - -**Priority: CRITICAL** -**Impact: Users must be able to operate the interface** - ---- - -## 2.1 Keyboard Accessible - -All functionality must be available via keyboard. - -### Focus Management - -**Correct:** - -```html - -Link - -``` - -### Custom Interactive Elements - -**Incorrect:** - -```html -
Click Me
-``` - -**Correct:** - -```html - - - -
- Click Me -
-``` - -### Skip Links - -```html - - -
- -
-``` - ---- - -## 2.2 Enough Time - -Provide users enough time to read and use content. - -- Allow users to turn off, adjust, or extend time limits -- Pause, stop, or hide moving content -- No timing on essential activities - ---- - -## 2.3 Seizures and Physical Reactions - -Do not design content that causes seizures. - -- No content flashing more than 3 times per second -- Warn users before auto-playing video with flashing - ---- - -## 2.4 Navigable - -Help users navigate and find content. - -### Page Titles - -```html -Contact Us - Company Name -``` - -### Focus Order - -Tab order should follow visual/logical order. - -### Link Purpose - -**Incorrect:** - -```html -Click here -Read more -``` - -**Correct:** - -```html -Read the full article about accessibility -``` - -### Multiple Ways - -Provide multiple ways to find pages (navigation, search, sitemap). - -### Headings and Labels - -Use descriptive headings that describe topic or purpose. - ---- - -## 2.5 Input Modalities - -Make it easier to operate through various inputs beyond keyboard. - -### Target Size - -- Minimum 44x44 CSS pixels for touch targets -- Adequate spacing between targets - -### Motion Actuation - -Don't require device motion (shaking, tilting) as sole input method. diff --git a/.windsurf/skills/wordpress-accessibility-coding-standards/rules/perceivable.md b/.windsurf/skills/wordpress-accessibility-coding-standards/rules/perceivable.md deleted file mode 100644 index ae3d3968bf..0000000000 --- a/.windsurf/skills/wordpress-accessibility-coding-standards/rules/perceivable.md +++ /dev/null @@ -1,122 +0,0 @@ -# Perceivable (WCAG Principle 1) - -**Priority: CRITICAL** -**Impact: Users must be able to perceive content** - ---- - -## 1.1 Text Alternatives - -Provide text alternatives for non-text content. - -### Images - -**Incorrect:** - -```html - - -``` - -**Correct:** - -```html -Company Name Logo -Sales chart showing 25% growth in Q4 -``` - -### Decorative Images - -```html - -``` - -### Icons with Actions - -```html - -``` - ---- - -## 1.2 Time-based Media - -Provide alternatives for audio and video content. - -- **Captions** for audio content in video -- **Audio descriptions** for visual content -- **Transcripts** for audio-only content - ---- - -## 1.3 Adaptable - -Create content that can be presented in different ways. - -### Semantic Structure - -**Incorrect:** - -```html -
Page Title
-
Section
-``` - -**Correct:** - -```html -

Page Title

-

Section

-``` - -### Reading Order - -Content should make sense when CSS is disabled. - -### Form Labels - -**Incorrect:** - -```html - -``` - -**Correct:** - -```html - - -``` - ---- - -## 1.4 Distinguishable - -Make it easy to see and hear content. - -### Color Contrast - -- **Normal text:** 4.5:1 minimum ratio -- **Large text (18pt+ or 14pt bold):** 3:1 minimum ratio -- **UI components:** 3:1 minimum ratio - -### Don't Use Color Alone - -**Incorrect:** - -```html -

Required fields are marked in red.

-``` - -**Correct:** - -```html -

Required fields are marked with an asterisk (*).

- -``` - -### Text Resize - -Content must be readable at 200% zoom without loss of functionality. diff --git a/.windsurf/skills/wordpress-accessibility-coding-standards/rules/robust.md b/.windsurf/skills/wordpress-accessibility-coding-standards/rules/robust.md deleted file mode 100644 index 112624a27d..0000000000 --- a/.windsurf/skills/wordpress-accessibility-coding-standards/rules/robust.md +++ /dev/null @@ -1,91 +0,0 @@ -# Robust (WCAG Principle 4) - -**Priority: HIGH** -**Impact: Content must work with current and future technologies** - ---- - -## 4.1 Compatible - -Maximize compatibility with current and future user agents. - -### Parsing (Valid HTML) - -- Elements have complete start and end tags -- Elements are nested according to specification -- No duplicate attributes -- IDs are unique - -**Incorrect:** - -```html -
-

Paragraph without closing tag -

Duplicate ID
-
-``` - -**Correct:** - -```html -
-

Paragraph with closing tag

-
Unique ID
-
-``` - -### Name, Role, Value - -For all UI components: - -- **Name:** Accessible name via label, aria-label, or aria-labelledby -- **Role:** Native HTML role or ARIA role -- **Value/State:** Current value and state programmatically available - -**Incorrect:** - -```html -
-``` - -**Correct:** - -```html - - - - -
-
-I agree to the terms -``` - ---- - -## Status Messages - -Convey status messages to assistive technologies without focus change. - -**Correct:** - -```html -
- Your changes have been saved. -
- -
- Error: Please correct the form fields. -
-``` - -### Live Regions - -- `aria-live="polite"` — Announce when user is idle -- `aria-live="assertive"` — Announce immediately (use sparingly) -- `role="status"` — Polite live region for status messages -- `role="alert"` — Assertive live region for errors/warnings diff --git a/.windsurf/skills/wordpress-accessibility-coding-standards/rules/understandable.md b/.windsurf/skills/wordpress-accessibility-coding-standards/rules/understandable.md deleted file mode 100644 index e1e378e42f..0000000000 --- a/.windsurf/skills/wordpress-accessibility-coding-standards/rules/understandable.md +++ /dev/null @@ -1,116 +0,0 @@ -# Understandable (WCAG Principle 3) - -**Priority: HIGH** -**Impact: Users must be able to understand content and operation** - ---- - -## 3.1 Readable - -Make text content readable and understandable. - -### Language of Page - -```html - -``` - -### Language of Parts - -```html -

The French phrase c'est la vie means "that's life."

-``` - -### Unusual Words - -Provide definitions for jargon, idioms, and technical terms. - ---- - -## 3.2 Predictable - -Make pages appear and operate predictably. - -### On Focus - -Don't change context when element receives focus. - -**Incorrect:** - -```javascript -input.addEventListener('focus', function() { - window.location = '/new-page'; -}); -``` - -### On Input - -Don't change context when user inputs data (unless warned). - -**Incorrect:** - -```html - - - - - -``` - -### Consistent Navigation - -Navigation should be in the same relative order across pages. - -### Consistent Identification - -Components with same functionality should be identified consistently. - ---- - -## 3.3 Input Assistance - -Help users avoid and correct mistakes. - -### Error Identification - -**Incorrect:** - -```html - -``` - -**Correct:** - -```html - - -Please enter a valid email address. -``` - -### Labels or Instructions - -Provide clear instructions for required formats. - -```html - - -Format: (123) 456-7890 -``` - -### Error Suggestion - -When errors are detected, provide suggestions for correction. - -### Error Prevention - -For legal, financial, or data submissions: - -- Allow review before final submission -- Provide confirmation/undo capability -- Check input and allow correction diff --git a/.windsurf/skills/wordpress-accessibility-coding-standards/rules/wordpress-specific.md b/.windsurf/skills/wordpress-accessibility-coding-standards/rules/wordpress-specific.md deleted file mode 100644 index 8dce4e2617..0000000000 --- a/.windsurf/skills/wordpress-accessibility-coding-standards/rules/wordpress-specific.md +++ /dev/null @@ -1,139 +0,0 @@ -# WordPress-Specific Accessibility - -**Priority: HIGH** -**Impact: WordPress ecosystem compatibility** - ---- - -## Admin Accessibility - -WordPress admin must be usable by people with disabilities. - -### Admin Notices - -```php -
-

-
-``` - -### Screen Reader Text - -```php - - - -``` - -```css -.screen-reader-text { - border: 0; - clip: rect(1px, 1px, 1px, 1px); - clip-path: inset(50%); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - width: 1px; - word-wrap: normal !important; -} -``` - ---- - -## Theme Accessibility - -### Skip Links - -Required in accessible themes: - -```php - -``` - -### Navigation Landmarks - -```php - -``` - -### Search Form - -```php - -``` - ---- - -## Form Accessibility - -### WordPress Forms - -```php -

- - -

-``` - -### Required Fields - -```php - - -``` - ---- - -## Media Accessibility - -### Images in Content - -```php - -<?php echo esc_attr( $alt_text ); ?> -``` - -### Embedded Media - -```php -
- -
-
-``` - ---- - -## Testing in WordPress - -- **Keyboard navigation:** Tab through entire admin/frontend -- **Screen reader:** Test with NVDA, JAWS, or VoiceOver -- **Color contrast:** Use browser dev tools or WAVE -- **Zoom:** Test at 200% zoom -- **Automated:** Use axe, WAVE, or Lighthouse diff --git a/.windsurf/skills/wordpress-css-coding-standards/AGENTS.md b/.windsurf/skills/wordpress-css-coding-standards/AGENTS.md deleted file mode 100644 index bd0008a726..0000000000 --- a/.windsurf/skills/wordpress-css-coding-standards/AGENTS.md +++ /dev/null @@ -1,578 +0,0 @@ -# WordPress CSS Coding Standards - -**Version 1.0.0** -Based on WordPress Core Official Standards - -> **Note:** -> This document is for AI agents and LLMs to follow when maintaining, -> generating, or refactoring CSS code in the WordPress ecosystem. - ---- - -## Abstract - -The WordPress CSS Coding Standards create a baseline for collaboration and review within the WordPress ecosystem. The goal is to create code that is readable, meaningful, consistent, and beautiful. - ---- - -## Table of Contents - -1. [Structure](#1-structure) — **HIGH** -2. [Selectors](#2-selectors) — **HIGH** -3. [Properties](#3-properties) — **MEDIUM** -4. [Values](#4-values) — **MEDIUM** -5. [Media Queries](#5-media-queries) — **MEDIUM** -6. [Commenting](#6-commenting) — **LOW** -7. [Best Practices](#7-best-practices) — **LOW** - ---- - -## 1. Structure - -**Impact: HIGH** - -Maintain high legibility with consistent structure. - -### 1.1 Indentation - -Use tabs, not spaces. - -### 1.2 Spacing Between Blocks - -- Two blank lines between sections -- One blank line between blocks in a section - -### 1.3 Selector and Property Layout - -Each selector on its own line. Property-value pairs on their own line with one tab indentation. - -**Incorrect:** - -```css -#selector-1, -#selector-2, -#selector-3 { - background: #fff; - color: #000; -} - -#selector-1 { - background: #fff; - color: #000; -} -``` - -**Correct:** - -```css -#selector-1, -#selector-2, -#selector-3 { - background: #fff; - color: #000; -} -``` - ---- - -## 2. Selectors - -**Impact: HIGH** - -Balance efficiency with specificity. - -### 2.1 Naming Convention - -Use lowercase with hyphens. Avoid camelCase and underscores. - -**Incorrect:** - -```css -#commentForm { -} -#comment_form { -} -``` - -**Correct:** - -```css -#comment-form { -} -``` - -### 2.2 Human Readable Names - -Use descriptive names that explain what the element styles. - -**Incorrect:** - -```css -#c1-xr { -} /* What is c1-xr? */ -``` - -**Correct:** - -```css -#comment-form { -} -.post-title { -} -.sidebar-widget { -} -``` - -### 2.3 Attribute Selectors - -Use double quotes around values. - -**Incorrect:** - -```css -input[type="text"] { -} -``` - -**Correct:** - -```css -input[type="text"] { -} -``` - -### 2.4 Avoid Over-qualification - -Don't add unnecessary element qualifiers. - -**Incorrect:** - -```css -div#comment-form { -} -div.container { -} -``` - -**Correct:** - -```css -#comment-form { -} -.container { -} -``` - ---- - -## 3. Properties - -**Impact: MEDIUM** - -### 3.1 Formatting - -- Colon followed by a space -- All properties and values lowercase -- End with semicolon - -**Incorrect:** - -```css -#selector-1 { - background: #ffffff; - display: BLOCK; - margin-left: 20px; -} -``` - -**Correct:** - -```css -#selector-1 { - background: #fff; - display: block; - margin-left: 20px; -} -``` - -### 3.2 Property Ordering - -Group related properties. Recommended order: - -1. Display & Box Model -2. Positioning -3. Typography -4. Visual (colors, backgrounds) -5. Misc - -```css -.selector { - /* Display & Box Model */ - display: block; - width: 100%; - padding: 10px; - margin: 0; - - /* Positioning */ - position: relative; - top: 0; - - /* Typography */ - font-family: sans-serif; - font-size: 16px; - line-height: 1.5; - - /* Visual */ - background: #fff; - color: #333; - border: 1px solid #ccc; - - /* Misc */ - cursor: pointer; -} -``` - -### 3.3 Shorthand Properties - -Use shorthand for `background`, `border`, `font`, `list-style`, `margin`, and `padding`. - -**Incorrect:** - -```css -.selector { - margin-top: 10px; - margin-right: 20px; - margin-bottom: 10px; - margin-left: 20px; -} -``` - -**Correct:** - -```css -.selector { - margin: 10px 20px; -} -``` - -**Exception:** When overriding specific values: - -```css -.selector { - margin: 0; - margin-left: 20px; -} -``` - -### 3.4 Vendor Prefixes - -Include when necessary, standard property last. - -```css -.selector { - -webkit-transform: rotate(45deg); - -moz-transform: rotate(45deg); - -ms-transform: rotate(45deg); - transform: rotate(45deg); -} -``` - ---- - -## 4. Values - -**Impact: MEDIUM** - -### 4.1 Colors - -Use hex codes (lowercase, shorthand when possible). Use `rgba()` only when opacity needed. - -**Incorrect:** - -```css -.selector { - color: #ffffff; - background: RGB(255, 255, 255); -} -``` - -**Correct:** - -```css -.selector { - color: #fff; - background: rgba(0, 0, 0, 0.5); -} -``` - -### 4.2 Font Weights - -Use numeric values. - -**Incorrect:** - -```css -.selector { - font-weight: bold; - font-weight: normal; -} -``` - -**Correct:** - -```css -.selector { - font-weight: 700; - font-weight: 400; -} -``` - -### 4.3 Line Height - -Use unit-less values unless specific pixel value needed. - -**Incorrect:** - -```css -.selector { - line-height: 1.5em; -} -``` - -**Correct:** - -```css -.selector { - line-height: 1.5; -} -``` - -### 4.4 Zero Values - -No units on zero values (except `transition-duration`). - -**Incorrect:** - -```css -.selector { - margin: 0px 0px 20px 0px; -} -``` - -**Correct:** - -```css -.selector { - margin: 0 0 20px; -} -``` - -### 4.5 Decimal Values - -Use leading zero. - -**Incorrect:** - -```css -.selector { - opacity: 0.5; -} -``` - -**Correct:** - -```css -.selector { - opacity: 0.5; -} -``` - -### 4.6 Quotes - -Use double quotes. Required for font names with spaces and `content` property. - -**Incorrect:** - -```css -.selector { - font-family: - Times New Roman, - serif; - content: "hello"; -} -``` - -**Correct:** - -```css -.selector { - font-family: "Times New Roman", serif; - content: "hello"; -} -``` - -### 4.7 URL Values - -No quotes needed for simple URLs. - -```css -.selector { - background-image: url(images/bg.png); -} -``` - -### 4.8 Multi-part Values - -Use newlines for complex values like `box-shadow` and `text-shadow`. - -**Correct:** - -```css -.selector { - box-shadow: - 0 0 0 1px #5b9dd9, - 0 0 2px 1px rgba(30, 140, 190, 0.8); -} -``` - ---- - -## 5. Media Queries - -**Impact: MEDIUM** - -### 5.1 Placement - -Keep media queries grouped at bottom of stylesheet (or at bottom of sections for large files like `wp-admin.css`). - -### 5.2 Indentation - -Indent rule sets one level inside media query. - -**Correct:** - -```css -@media all and (max-width: 699px) and (min-width: 520px) { - .selector { - display: block; - } -} -``` - ---- - -## 6. Commenting - -**Impact: LOW** - -### 6.1 Comment Liberally - -Use `SCRIPT_DEBUG` constant and minified files in production. - -### 6.2 Table of Contents - -Use for longer stylesheets with index numbers. - -```css -/** - * Table of Contents - * - * 1.0 - Reset - * 2.0 - Typography - * 3.0 - Layout - * 4.0 - Components - * 5.0 - Media Queries - */ -``` - -### 6.3 Section Headers - -```css -/** - * 1.0 Reset - * - * Description of section, whether or not it has media queries, etc. - */ - -.selector { - float: left; -} -``` - -### 6.4 Inline Comments - -```css -/* This is a comment about this selector */ -.another-selector { - position: absolute; - top: 0 !important; /* I should explain why this is so !important */ -} -``` - ---- - -## 7. Best Practices - -**Impact: LOW** - -### 7.1 Remove Before Adding - -When fixing issues, try removing code before adding more. - -### 7.2 Avoid Magic Numbers - -Don't use arbitrary values as quick fixes. - -**Incorrect:** - -```css -.box { - margin-top: 37px; /* Why 37? */ -} -``` - -**Correct:** - -```css -.box { - margin-top: 2rem; /* Consistent spacing unit */ -} -``` - -### 7.3 Target Elements Directly - -Use classes on elements instead of parent selectors. - -**Incorrect:** - -```css -.highlight a { -} /* Selector on parent */ -``` - -**Correct:** - -```css -.highlight-link { -} /* Class on the element */ -``` - -### 7.4 Height vs Line-height - -Use `height` only for external elements (images). Use `line-height` for text flexibility. - -### 7.5 Don't Restate Defaults - -Don't declare default values. - -**Incorrect:** - -```css -div { - display: block; /* div is already block */ -} -``` - -### 7.6 WP Admin CSS - -Follow the same standards. Use `!important` sparingly and document why. diff --git a/.windsurf/skills/wordpress-css-coding-standards/SKILL.md b/.windsurf/skills/wordpress-css-coding-standards/SKILL.md deleted file mode 100644 index c4956ff6a9..0000000000 --- a/.windsurf/skills/wordpress-css-coding-standards/SKILL.md +++ /dev/null @@ -1,79 +0,0 @@ ---- -name: wordpress-css-coding-standards -description: WordPress CSS coding standards for maintaining, generating, or refactoring CSS code. Apply when working with CSS files in WordPress plugins or themes. ---- - -# WordPress CSS Coding Standards - -**Version 1.0.0** -Based on WordPress Core Official Standards - -> **Note:** -> This document is for AI agents and LLMs to follow when maintaining, -> generating, or refactoring CSS code in the WordPress ecosystem. - ---- - -## Overview - -The WordPress CSS Coding Standards create a baseline for collaboration and review. Files should appear as though created by a single entity—readable, meaningful, consistent, and beautiful. - -**When to apply:** Any CSS file in WordPress Core, plugins, or themes. - -**Reference:** [WordPress CSS Coding Standards](https://developer.wordpress.org/coding-standards/wordpress-coding-standards/css/) - ---- - -## Rule Categories by Priority - -### 1. Structure — **HIGH** - -| Rule | Impact | -| ------------------------------- | ----------- | -| [Structure](rules/structure.md) | Readability | - -### 2. Selectors — **HIGH** - -| Rule | Impact | -| ------------------------------- | --------------- | -| [Selectors](rules/selectors.md) | Maintainability | - -### 3. Properties — **MEDIUM** - -| Rule | Impact | -| --------------------------------- | ----------- | -| [Properties](rules/properties.md) | Consistency | - -### 4. Values — **MEDIUM** - -| Rule | Impact | -| ------------------------- | ----------- | -| [Values](rules/values.md) | Consistency | - -### 5. Media Queries — **MEDIUM** - -| Rule | Impact | -| --------------------------------------- | ---------- | -| [Media Queries](rules/media-queries.md) | Responsive | - -### 6. Comments — **LOW** - -| Rule | Impact | -| ----------------------------- | ------------- | -| [Comments](rules/comments.md) | Documentation | - -### 7. Best Practices — **LOW** - -| Rule | Impact | -| ----------------------------------------- | --------------- | -| [Best Practices](rules/best-practices.md) | Maintainability | - ---- - -## Usage - -```text -@wordpress-css-coding-standards -``` - -See [AGENTS.md](AGENTS.md) for the complete reference. diff --git a/.windsurf/skills/wordpress-css-coding-standards/rules/best-practices.md b/.windsurf/skills/wordpress-css-coding-standards/rules/best-practices.md deleted file mode 100644 index cb45c624a6..0000000000 --- a/.windsurf/skills/wordpress-css-coding-standards/rules/best-practices.md +++ /dev/null @@ -1,108 +0,0 @@ -# Best Practices - -**Priority: MEDIUM** -**Impact: Performance and maintainability** - ---- - -## Avoid !important - -Only use when absolutely necessary. Indicates specificity problems. - -**Incorrect:** - -```css -.selector { - color: red !important; -} -``` - -**Correct:** - -```css -/* Increase specificity instead */ -.parent .selector { - color: red; -} -``` - ---- - -## Shorthand Properties - -Use shorthand when setting all values. Use longhand for partial overrides. - -**Correct:** - -```css -/* All four values */ -.selector { - margin: 10px 20px 10px 20px; -} - -/* Just top margin */ -.selector { - margin-top: 10px; -} -``` - ---- - -## Avoid Magic Numbers - -Document or use variables for non-obvious values. - -**Incorrect:** - -```css -.selector { - top: 37px; -} -``` - -**Correct:** - -```css -.selector { - /* header height (32px) + spacing (5px) */ - top: 37px; -} -``` - ---- - -## Performance - -- Avoid universal selectors (`*`) -- Avoid deeply nested selectors (max 3 levels) -- Minimize redundancy -- Group common declarations - -**Incorrect:** - -```css -body * { box-sizing: border-box; } -.nav .menu .item .link .text { color: red; } -``` - -**Correct:** - -```css -*, -*::before, -*::after { - box-sizing: border-box; -} - -.nav-link-text { - color: red; -} -``` - ---- - -## Code Refactoring - -Avoid whitespace-only patches. Don't refactor purely for style. - -> "Code refactoring should not be done just because we can." diff --git a/.windsurf/skills/wordpress-css-coding-standards/rules/comments.md b/.windsurf/skills/wordpress-css-coding-standards/rules/comments.md deleted file mode 100644 index 1ae2cf73dd..0000000000 --- a/.windsurf/skills/wordpress-css-coding-standards/rules/comments.md +++ /dev/null @@ -1,81 +0,0 @@ -# Comments - -**Priority: LOW** -**Impact: Documentation** - ---- - -## Table of Contents - -For longer stylesheets, include a table of contents at the top. - -**Correct:** - -```css -/** - * TABLE OF CONTENTS - * - * 1. Reset - * 2. Typography - * 3. Layout - * 4. Components - * 4.1 Buttons - * 4.2 Forms - * 4.3 Navigation - * 5. Utilities - */ -``` - ---- - -## Section Headers - -Use consistent section dividers. - -**Correct:** - -```css -/** - * #.# Section title - * - * Description of section. - */ - -.selector-1 { - background: #fff; -} -``` - ---- - -## Inline Comments - -For single-line clarifications. - -**Correct:** - -```css -.selector { - /* Override WP default */ - background: #fff; - color: #000; /* Matches brand guidelines */ -} -``` - ---- - -## Multi-line Comments - -Use DocBlock style for complex explanations. - -**Correct:** - -```css -/** - * Long description explaining rationale, - * browser support considerations, or - * other important context. - * - * @see https://example.com/reference - */ -``` diff --git a/.windsurf/skills/wordpress-css-coding-standards/rules/media-queries.md b/.windsurf/skills/wordpress-css-coding-standards/rules/media-queries.md deleted file mode 100644 index c7cca3aed3..0000000000 --- a/.windsurf/skills/wordpress-css-coding-standards/rules/media-queries.md +++ /dev/null @@ -1,48 +0,0 @@ -# Media Queries - -**Priority: MEDIUM** -**Impact: Responsive design organization** - ---- - -## Placement - -Place media queries near relevant rule sets, or at end of document in stylesheet sections. - ---- - -## Formatting - -- Opening brace on same line as query -- Contents indented one level -- Closing brace on own line - -**Correct:** - -```css -@media screen and (min-width: 768px) { - .selector { - width: 50%; - } - - .sidebar { - display: block; - } -} -``` - ---- - -## Breakpoints - -Use consistent breakpoints throughout the project. Prefer min-width (mobile-first). - -**Common Breakpoints:** - -```css -/* Mobile first approach */ -@media screen and (min-width: 480px) { /* Small */ } -@media screen and (min-width: 768px) { /* Medium */ } -@media screen and (min-width: 1024px) { /* Large */ } -@media screen and (min-width: 1200px) { /* Extra large */ } -``` diff --git a/.windsurf/skills/wordpress-css-coding-standards/rules/properties.md b/.windsurf/skills/wordpress-css-coding-standards/rules/properties.md deleted file mode 100644 index 80437ec0e4..0000000000 --- a/.windsurf/skills/wordpress-css-coding-standards/rules/properties.md +++ /dev/null @@ -1,62 +0,0 @@ -# Properties - -**Priority: MEDIUM** -**Impact: Consistency and readability** - ---- - -## Property Ordering - -Group properties logically: - -1. **Display & Position** — display, visibility, position, float, clear, overflow, z-index -2. **Box Model** — width, height, margin, padding, border -3. **Typography** — font, line-height, text-*, letter-spacing, word-spacing -4. **Visual** — background, color, list-style -5. **Other** — cursor, content, etc. - -**Correct:** - -```css -.selector { - /* Display & Position */ - display: block; - position: relative; - float: left; - - /* Box Model */ - width: 100%; - margin: 10px; - padding: 10px; - border: 1px solid #000; - - /* Typography */ - font-family: sans-serif; - font-size: 16px; - line-height: 1.4; - - /* Visual */ - background: #fff; - color: #000; -} -``` - ---- - -## Vendor Prefixes - -Stack prefixes vertically, aligned. Standard property last. - -**Correct:** - -```css -.selector { - -webkit-transition: all 0.3s ease; - -moz-transition: all 0.3s ease; - -ms-transition: all 0.3s ease; - -o-transition: all 0.3s ease; - transition: all 0.3s ease; -} -``` - -> **Note:** Use Autoprefixer in your build process to handle prefixes automatically. diff --git a/.windsurf/skills/wordpress-css-coding-standards/rules/selectors.md b/.windsurf/skills/wordpress-css-coding-standards/rules/selectors.md deleted file mode 100644 index b89734d7a7..0000000000 --- a/.windsurf/skills/wordpress-css-coding-standards/rules/selectors.md +++ /dev/null @@ -1,65 +0,0 @@ -# Selectors - -**Priority: HIGH** -**Impact: Specificity and maintainability** - ---- - -## Selector Naming - -Use lowercase, separate words with hyphens. Use human-readable names. - -**Incorrect:** - -```css -.postHeader {} -.post_header {} -#commentForm {} -#comment_form {} -.u-teleportLeft {} -``` - -**Correct:** - -```css -#comment-form {} -.post-header {} -.comment-form-author {} -``` - ---- - -## Selector Structure - -- Avoid over-qualified selectors -- No IDs in selectors when possible (higher specificity = harder to override) -- Avoid tag selectors for common elements - -**Incorrect:** - -```css -div.container {} /* Over-qualified */ -#my-id {} /* Too specific */ -div {} /* Too generic */ -``` - -**Correct:** - -```css -.container {} -.my-class {} -.post-content p {} -``` - ---- - -## Attribute Selectors - -Always quote attribute values. - -**Correct:** - -```css -input[type="text"] {} -a[href^="https://"] {} -``` diff --git a/.windsurf/skills/wordpress-css-coding-standards/rules/structure.md b/.windsurf/skills/wordpress-css-coding-standards/rules/structure.md deleted file mode 100644 index 8ffe0047b9..0000000000 --- a/.windsurf/skills/wordpress-css-coding-standards/rules/structure.md +++ /dev/null @@ -1,63 +0,0 @@ -# Structure - -**Priority: HIGH** -**Impact: Readability and organization** - ---- - -## General Formatting - -Each selector on its own line. Opening brace on same line as last selector. Closing brace on its own line. - -**Correct:** - -```css -#selector-1, -#selector-2, -#selector-3 { - background: #fff; - color: #000; -} -``` - ---- - -## Property Formatting - -- One property per line -- Indent properties with single tab -- End each declaration with semicolon -- Double quotes around values - -**Correct:** - -```css -#selector-1 { - background: #fff; - color: #000; -} - -#selector-2 { - font-family: "Helvetica Neue", sans-serif; -} -``` - ---- - -## Blank Lines - -Separate rule sets by one blank line for readability. - -**Correct:** - -```css -#selector-1 { - background: #fff; - color: #000; -} - -#selector-2 { - background: #fff; - color: #000; -} -``` diff --git a/.windsurf/skills/wordpress-css-coding-standards/rules/values.md b/.windsurf/skills/wordpress-css-coding-standards/rules/values.md deleted file mode 100644 index 195739a199..0000000000 --- a/.windsurf/skills/wordpress-css-coding-standards/rules/values.md +++ /dev/null @@ -1,90 +0,0 @@ -# Values - -**Priority: MEDIUM** -**Impact: Consistency** - ---- - -## General Rules - -- Space after property colon -- Space after commas in multi-value properties -- Lowercase hex values, shorthand when possible -- Avoid units on zero values -- Use leading zero for decimals - ---- - -## Colors - -**Incorrect:** - -```css -.selector { - color: #FFFFFF; - background: #FF0000; -} -``` - -**Correct:** - -```css -.selector { - color: #fff; - background: #f00; -} -``` - ---- - -## Units - -**Incorrect:** - -```css -.selector { - margin: 0px; - padding: 0em; - opacity: .5; -} -``` - -**Correct:** - -```css -.selector { - margin: 0; - padding: 0; - opacity: 0.5; -} -``` - ---- - -## Multiple Values - -Space after each comma. - -**Correct:** - -```css -.selector { - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - background: rgba(0, 0, 0, 0.5); - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2), 0 2px 4px rgba(0, 0, 0, 0.1); -} -``` - ---- - -## URLs - -Quotes around URL paths. - -**Correct:** - -```css -.selector { - background: url("images/bg.png"); -} -``` diff --git a/.windsurf/skills/wordpress-html-coding-standards/AGENTS.md b/.windsurf/skills/wordpress-html-coding-standards/AGENTS.md deleted file mode 100644 index 08271a0ff6..0000000000 --- a/.windsurf/skills/wordpress-html-coding-standards/AGENTS.md +++ /dev/null @@ -1,207 +0,0 @@ -# WordPress HTML Coding Standards - -**Version 1.0.0** -Based on WordPress Core Official Standards - -> **Note:** -> This document is for AI agents and LLMs to follow when maintaining, -> generating, or refactoring HTML code in the WordPress ecosystem. - ---- - -## Abstract - -These HTML coding standards ensure well-formed, accessible markup. Validation helps weed out problems but is no substitute for manual code review. - ---- - -## Table of Contents - -1. [Validation](#1-validation) — **HIGH** -2. [Self-closing Elements](#2-self-closing-elements) — **HIGH** -3. [Attributes and Tags](#3-attributes-and-tags) — **MEDIUM** -4. [Quotes](#4-quotes) — **MEDIUM** -5. [Indentation](#5-indentation) — **MEDIUM** - ---- - -## 1. Validation - -**Impact: HIGH** - -All HTML should be verified against the W3C validator to ensure well-formed markup. - -**Tools:** - -- [W3C Markup Validation Service](https://validator.w3.org/) -- Browser developer tools - -**Note:** Validation alone doesn't guarantee good code—manual review is essential. - ---- - -## 2. Self-closing Elements - -**Impact: HIGH** - -All tags must be properly closed. Self-closing tags need exactly one space before the slash. - -**Incorrect:** - -```html -
- - -``` - -**Correct:** - -```html -
- - -``` - -The W3C specifies that a single space should precede the self-closing slash. - ---- - -## 3. Attributes and Tags - -**Impact: MEDIUM** - -### 3.1 Lowercase - -All tags and attributes must be lowercase. - -**Incorrect:** - -```html -
- -
-``` - -**Correct:** - -```html -
- -
-``` - -### 3.2 Attribute Values - -Lowercase for machine-interpreted values. Title case for human-readable values. - -**For machines:** - -```html - -``` - -**For humans:** - -```html -Example.com -``` - ---- - -## 4. Quotes - -**Impact: MEDIUM** - -### 4.1 Always Quote Attributes - -Use double or single quotes. Never omit quotes—it can cause security vulnerabilities. - -**Incorrect:** - -```html - -``` - -**Correct:** - -```html - -``` - -Or with single quotes: - -```html - -``` - -### 4.2 Boolean Attributes - -You may omit the value on boolean attributes, but never use `true` or `false`. - -**Incorrect:** - -```html - - -``` - -**Correct:** - -```html - - -``` - ---- - -## 5. Indentation - -**Impact: MEDIUM** - -### 5.1 Use Tabs - -HTML indentation should reflect logical structure. Use tabs, not spaces. - -### 5.2 PHP in HTML - -Indent PHP blocks to match surrounding HTML. Closing PHP blocks should match opening block indentation. - -**Incorrect:** - -```php - -
-

Not Found

-
-

Apologies, but no results were found.

- -
-
- -``` - -**Correct:** - -```php - -
-

Not Found

-
-

Apologies, but no results were found.

- -
-
- -``` - ---- - -## Best Practices Summary - -1. **Validate markup** — Use W3C validator -2. **Close all tags** — Including self-closing with space before slash -3. **Lowercase everything** — Tags, attributes, machine values -4. **Quote all attributes** — Double quotes preferred -5. **Boolean attributes** — Omit value or repeat attribute name, never `true`/`false` -6. **Use tabs** — Reflect logical structure -7. **Align PHP with HTML** — Maintain consistent indentation diff --git a/.windsurf/skills/wordpress-html-coding-standards/SKILL.md b/.windsurf/skills/wordpress-html-coding-standards/SKILL.md deleted file mode 100644 index 567c6729fe..0000000000 --- a/.windsurf/skills/wordpress-html-coding-standards/SKILL.md +++ /dev/null @@ -1,61 +0,0 @@ ---- -name: wordpress-html-coding-standards -description: WordPress HTML coding standards for maintaining, generating, or refactoring HTML code. Apply when working with HTML or PHP template files in WordPress plugins or themes. ---- - -# WordPress HTML Coding Standards - -**Version 1.0.0** -Based on WordPress Core Official Standards - -> **Note:** -> This document is for AI agents and LLMs to follow when maintaining, -> generating, or refactoring HTML code in the WordPress ecosystem. - ---- - -## Overview - -These HTML coding standards ensure well-formed, accessible markup across the WordPress ecosystem. - -**When to apply:** Any HTML in WordPress Core, plugins, themes, or templates. - -**Reference:** [WordPress HTML Coding Standards](https://developer.wordpress.org/coding-standards/wordpress-coding-standards/html/) - ---- - -## Rule Categories by Priority - -### 1. Validation — **HIGH** - -| Rule | Impact | -| --------------------------------- | ---------- | -| [Validation](rules/validation.md) | Compliance | - -### 2. Self-closing Elements — **HIGH** - -| Rule | Impact | -| ------------------------------------------------------- | ----------- | -| [Self-closing Elements](rules/self-closing-elements.md) | Consistency | - -### 3. Attributes — **MEDIUM** - -| Rule | Impact | -| --------------------------------- | ----------- | -| [Attributes](rules/attributes.md) | Reliability | - -### 4. Indentation — **MEDIUM** - -| Rule | Impact | -| ----------------------------------- | ----------- | -| [Indentation](rules/indentation.md) | Readability | - ---- - -## Usage - -```text -@wordpress-html-coding-standards -``` - -See [AGENTS.md](AGENTS.md) for the complete reference. diff --git a/.windsurf/skills/wordpress-html-coding-standards/rules/attributes.md b/.windsurf/skills/wordpress-html-coding-standards/rules/attributes.md deleted file mode 100644 index 5b62cbe0a6..0000000000 --- a/.windsurf/skills/wordpress-html-coding-standards/rules/attributes.md +++ /dev/null @@ -1,89 +0,0 @@ -# Attributes and Quotes - -**Priority: HIGH** -**Impact: Parsing reliability and consistency** - ---- - -## Quote Usage - -Always use double quotes around attribute values, never single quotes or no quotes. - -**Incorrect:** - -```html -Link -Link - -
-``` - -**Correct:** - -```html -Link - -
-``` - ---- - -## Boolean Attributes - -Boolean attributes should not have a value assigned. The presence of the attribute implies `true`. - -**Incorrect:** - -```html - - - - - -``` - ---- - -## Custom Data Attributes - -Use `data-*` attributes for custom data. Use lowercase with hyphens. - -**Correct:** - -```html -
- Content -
-``` diff --git a/.windsurf/skills/wordpress-html-coding-standards/rules/indentation.md b/.windsurf/skills/wordpress-html-coding-standards/rules/indentation.md deleted file mode 100644 index 81c6c67939..0000000000 --- a/.windsurf/skills/wordpress-html-coding-standards/rules/indentation.md +++ /dev/null @@ -1,105 +0,0 @@ -# Indentation - -**Priority: MEDIUM** -**Impact: Readability and maintainability** - ---- - -## General Rule - -Use tabs for indentation. Nested elements should be indented once per level. - ---- - -## Basic Structure - -**Correct:** - -```html - - - - - Page Title - - -
- -
-
-
-

Article Title

-

Article content.

-
-
-
-

© 2024

-
- - -``` - ---- - -## Mixed HTML and PHP - -When mixing HTML and PHP (common in WordPress templates), maintain consistent indentation. - -**Correct:** - -```php - -
- -
> -

-
- -
-
- -
- -``` - ---- - -## Long Attribute Lists - -When an element has many attributes, consider breaking to multiple lines. - -**Acceptable:** - -```html - -``` - ---- - -## Inline vs Block Elements - -- **Block elements:** Always on their own line with proper indentation -- **Inline elements:** Can remain on the same line as surrounding content - -**Correct:** - -```html -

This is a paragraph with bold text and a link.

- -
-

Block element content.

-
-``` diff --git a/.windsurf/skills/wordpress-html-coding-standards/rules/self-closing-elements.md b/.windsurf/skills/wordpress-html-coding-standards/rules/self-closing-elements.md deleted file mode 100644 index 69dfda94b2..0000000000 --- a/.windsurf/skills/wordpress-html-coding-standards/rules/self-closing-elements.md +++ /dev/null @@ -1,57 +0,0 @@ -# Self-Closing Elements - -**Priority: MEDIUM** -**Impact: Consistency and standards compliance** - ---- - -## Void Elements - -Void elements (self-closing) should NOT have a trailing slash in HTML5. - -**Void elements include:** - -- `
` -- `
` -- `` -- `` -- `` -- `` -- `` -- `` -- `` -- `` -- `` -- `` -- `` -- `` - ---- - -## Examples - -**Incorrect (XHTML style):** - -```html -
-
- - - -``` - -**Correct (HTML5 style):** - -```html -
-
-Description - - -``` - ---- - -## Exception - -When working with XHTML documents or XML-based systems (like some WordPress template systems), the trailing slash may be required for valid XML parsing. diff --git a/.windsurf/skills/wordpress-html-coding-standards/rules/validation.md b/.windsurf/skills/wordpress-html-coding-standards/rules/validation.md deleted file mode 100644 index ed5f716e64..0000000000 --- a/.windsurf/skills/wordpress-html-coding-standards/rules/validation.md +++ /dev/null @@ -1,47 +0,0 @@ -# Validation - -**Priority: HIGH** -**Impact: Standards compliance and cross-browser compatibility** - ---- - -## General Rule - -All HTML pages should be verified against the W3C validator to ensure markup is well-formed. - ---- - -## Validation Requirements - -- No errors in final production code -- Warnings should be reviewed and addressed where appropriate -- Use appropriate DOCTYPE declaration - ---- - -## DOCTYPE - -Always use the HTML5 DOCTYPE. - -**Correct:** - -```html - - - - - Page Title - - - - - -``` - ---- - -## Validation Tools - -- **W3C Markup Validation Service:** -- **Browser DevTools:** Check for parsing errors in console -- **IDE/Editor plugins:** Real-time validation feedback diff --git a/.windsurf/skills/wordpress-javascript-coding-standards/AGENTS.md b/.windsurf/skills/wordpress-javascript-coding-standards/AGENTS.md deleted file mode 100644 index 3e904da91f..0000000000 --- a/.windsurf/skills/wordpress-javascript-coding-standards/AGENTS.md +++ /dev/null @@ -1,677 +0,0 @@ -# WordPress JavaScript Coding Standards - -**Version 1.0.0** -Based on WordPress Core Official Standards - -> **Note:** -> This document is for AI agents and LLMs to follow when maintaining, -> generating, or refactoring JavaScript code in the WordPress ecosystem. - ---- - -## Abstract - -JavaScript has become a critical component in developing WordPress-based applications (themes and plugins) as well as WordPress core. These standards ensure consistency, readability, and maintainability. - ---- - -## Table of Contents - -1. [Spacing](#1-spacing) — **HIGH** -2. [Indentation and Line Breaks](#2-indentation-and-line-breaks) — **HIGH** -3. [Variables and Naming](#3-variables-and-naming) — **MEDIUM** -4. [Equality and Type Checks](#4-equality-and-type-checks) — **MEDIUM** -5. [Syntax Rules](#5-syntax-rules) — **MEDIUM** -6. [Best Practices](#6-best-practices) — **LOW** - ---- - -## 1. Spacing - -**Impact: HIGH** - -Use spaces liberally for improved readability. Minification handles optimization. - -### 1.1 General Rules - -- Indentation with tabs -- No whitespace at end of line or on blank lines -- Lines should usually be no longer than 80 characters, max 100 -- `if`/`else`/`for`/`while`/`try` blocks always use braces and multiple lines -- Unary operators (`++`, `--`) must not have space next to operand -- `,` and `;` must not have preceding space -- `:` after property name must not have preceding space -- `?` and `:` in ternary must have space on both sides -- No filler spaces in empty constructs (`{}`, `[]`, `fn()`) -- New line at end of each file -- `!` negation operator should have following space - -### 1.2 Object Declarations - -**Incorrect:** - -```javascript -var obj = { ready: 9, when: 4, "you are": 15 }; -``` - -**Correct (multiline preferred):** - -```javascript -var obj = { - ready: 9, - when: 4, - "you are": 15, -}; -``` - -**Acceptable for small objects:** - -```javascript -var obj = { ready: 9, when: 4, "you are": 15 }; -``` - -### 1.3 Arrays and Function Calls - -Always include extra spaces around elements and arguments. - -**Correct:** - -```javascript -array = [a, b]; - -foo(arg); -foo("string", object); -foo(options, object[property]); -foo(node, "property", 2); - -prop = object["default"]; -firstArrayElement = arr[0]; -``` - -### 1.4 Control Structures - -**Correct:** - -```javascript -var i; - -if (condition) { - doSomething("with a string"); -} else if (otherCondition) { - otherThing({ - key: value, - otherKey: otherValue, - }); -} else { - somethingElse(true); -} - -// Space after ! negation (differs from jQuery) -while (!condition) { - iterating++; -} - -for (i = 0; i < 100; i++) { - object[array[i]] = someFn(i); - $(".container").val(array[i]); -} - -try { - // Expressions -} catch (e) { - // Expressions -} -``` - ---- - -## 2. Indentation and Line Breaks - -**Impact: HIGH** - -### 2.1 Tab Indentation - -Use tabs for indentation, even inside closures. - -**Correct:** - -```javascript -(function ($) { - // Expressions indented - - function doSomething() { - // Expressions indented - } -})(jQuery); -``` - -### 2.2 Blocks and Curly Braces - -Opening brace on same line. Closing brace on line after last statement. - -**Correct:** - -```javascript -var a, b, c; - -if (myFunction()) { - // Expressions -} else if ((a && b) || c) { - // Expressions -} else { - // Expressions -} -``` - -### 2.3 Multi-line Statements - -Line breaks must occur after an operator. - -**Incorrect:** - -```javascript -var html = - "

The sum of " + - a + - " and " + - b + - " plus " + - c + - " is " + - (a + b + c) + - "

"; -``` - -**Correct:** - -```javascript -var html = - "

The sum of " + - a + - " and " + - b + - " plus " + - c + - " is " + - (a + b + c) + - "

"; -``` - -**Ternary on multiple lines:** - -```javascript -var baz = firstCondition(foo) && secondCondition(bar) ? qux(foo, bar) : foo; -``` - -**Long conditionals:** - -```javascript -if (firstCondition() && secondCondition() && thirdCondition()) { - doStuff(); -} -``` - -### 2.4 Chained Method Calls - -One call per line. Extra indent when context changes. - -**Correct:** - -```javascript -elements.addClass("foo").children().html("hello").end().appendTo("body"); -``` - ---- - -## 3. Variables and Naming - -**Impact: MEDIUM** - -### 3.1 Variable Declarations - -Use `const` and `let` (ES6+). Avoid `var` in modern code. - -**Correct:** - -```javascript -const myName = "WordPress"; -let counter = 0; -``` - -**Legacy (when var is required):** - -```javascript -var myName = "WordPress"; -``` - -### 3.2 Naming Conventions - -Use camelCase with lowercase first letter. This differs from WordPress PHP standards. - -**Incorrect:** - -```javascript -var some_variable = "value"; -var SomeVariable = "value"; -``` - -**Correct:** - -```javascript -var someVariable = "value"; - -function myFunction() {} - -// Iterators allowed as single letters -for (var i = 0; i < 10; i++) {} -``` - -### 3.3 Abbreviations and Acronyms - -Treat as words in camelCase. - -**Correct:** - -```javascript -var defined; -var htmlContent; -var jsonData; -var xmlParser; -``` - -### 3.4 Class Definitions - -Use UpperCamelCase. - -**Correct:** - -```javascript -class MyClass { - constructor() {} -} -``` - -### 3.5 Constants - -Use SCREAMING_SNAKE_CASE. - -**Correct:** - -```javascript -const MAX_ITEMS = 100; -const API_URL = "https://api.example.com"; -``` - -### 3.6 Globals - -Avoid globals. If unavoidable, set them explicitly via `window`. - -**Correct:** - -```javascript -window.myGlobal = "value"; -``` - -**Document globals at top of file:** - -```javascript -/* global passwordStrength:true */ -``` - -- `:true` means the global is being defined in this file -- Omit `:true` for read-only globals defined elsewhere - -### 3.7 Common Libraries - -Backbone, jQuery, Underscore, and `wp` are registered globals in WordPress. - -**jQuery access pattern:** - -```javascript -(function ($) { - // Use $ safely here -})(jQuery); -``` - -**Extending wp object safely:** - -```javascript -// At the top of the file -window.wp = window.wp || {}; -``` - ---- - -## 4. Equality and Type Checks - -**Impact: MEDIUM** - -### 4.1 Strict Equality - -Always use `===` and `!==`. - -**Incorrect:** - -```javascript -if (foo == bar) { -} -if (foo != bar) { -} -``` - -**Correct:** - -```javascript -if (foo === bar) { -} -if (foo !== bar) { -} -``` - -### 4.2 Type Checks - -**String:** - -```javascript -typeof object === "string"; -``` - -**Number:** - -```javascript -typeof object === "number"; -``` - -**Boolean:** - -```javascript -typeof object === "boolean"; -``` - -**Object:** - -```javascript -typeof object === "object"; -// or -_.isObject(object); -``` - -**Plain Object:** - -```javascript -jQuery.isPlainObject(object); -``` - -**Function:** - -```javascript -_.isFunction(object); -// or -jQuery.isFunction(object); -``` - -**Array:** - -```javascript -_.isArray(object); -// or -jQuery.isArray(object); -``` - -**Element:** - -```javascript -object.nodeType; -// or -_.isElement(object); -``` - -**null:** - -```javascript -object === null; -``` - -**null or undefined:** - -```javascript -object == null; -``` - -**undefined:** - -```javascript -// Global Variables -typeof variable === "undefined"; - -// Local Variables -variable === undefined; - -// Properties -object.prop === undefined; - -// Any of the above -_.isUndefined(object); -``` - ---- - -## 5. Syntax Rules - -**Impact: MEDIUM** - -### 5.1 Semicolons - -Always use them. Never rely on Automatic Semicolon Insertion (ASI). - -**Incorrect:** - -```javascript -var foo = "bar"; -function myFunc() {} -``` - -**Correct:** - -```javascript -var foo = "bar"; -function myFunc() {} -``` - -### 5.2 Strings - -Use single quotes for string literals. - -**Incorrect:** - -```javascript -var myStr = "strings should use single quotes"; -``` - -**Correct:** - -```javascript -var myStr = "strings should be contained in single quotes"; -``` - -**Escaping:** - -```javascript -var escaped = "Note the backslash before the 'single quotes'"; -``` - -### 5.3 Switch Statements - -Use `break` for each case except `default`. Indent `case` one tab from `switch`. - -**Correct:** - -```javascript -switch (event.keyCode) { - // ENTER and SPACE both trigger x() - case $.ui.keyCode.ENTER: - case $.ui.keyCode.SPACE: - x(); - break; - - case $.ui.keyCode.ESCAPE: - y(); - break; - - default: - z(); -} -``` - -**Return values (set in cases, return at end):** - -```javascript -function getKeyCode(keyCode) { - var result; - - switch (event.keyCode) { - case $.ui.keyCode.ENTER: - case $.ui.keyCode.SPACE: - result = "commit"; - break; - - case $.ui.keyCode.ESCAPE: - result = "exit"; - break; - - default: - result = "default"; - } - - return result; -} -``` - ---- - -## 6. Best Practices - -**Impact: LOW** - -### 6.1 Comments - -Comments come before the code. Preceded by blank line. Single space after `//`. - -**Correct:** - -```javascript -someStatement(); - -// Explanation of something complex on the next line -$("p").doSomething(); - -// This is a comment that is long enough to warrant being stretched -// over the span of multiple lines. -``` - -**JSDoc format for documentation:** - -```javascript -/** - * Function description. - * - * @param {string} param1 - Description. - * @return {boolean} Description. - */ -function myFunction(param1) { - return true; -} -``` - -**Inline comments for special arguments:** - -```javascript -function foo(types, selector, data, fn, /* INTERNAL */ one) { - // Do stuff -} -``` - -### 6.2 Arrays - -Create using `[]` not `new Array()`. - -**Correct:** - -```javascript -var myArray = []; -var myArray = [1, 2, 3]; -``` - -### 6.3 Objects - -Create using `{}` not `new Object()`. - -**Correct:** - -```javascript -var myObject = {}; -var myObject = { key: "value" }; -``` - -### 6.4 Iteration - -Use `_.each()` or `jQuery.each()` for collections. - -**Correct:** - -```javascript -_.each(myArray, function (value, index) { - // Process value -}); - -$(".items").each(function () { - // Process element -}); -``` - -### 6.5 Code Refactoring - -Don't refactor just for style. "Whitespace-only" patches are discouraged. - -> "Code refactoring should not be done just because we can." – Andrew Nacin - ---- - -## JSHint Configuration - -Standard WordPress JSHint settings: - -```json -{ - "boss": true, - "curly": true, - "eqeqeq": true, - "eqnull": true, - "es3": true, - "expr": true, - "immed": true, - "noarg": true, - "nonbsp": true, - "onevar": true, - "quotmark": "single", - "trailing": true, - "undef": true, - "unused": true, - "browser": true, - "globals": { - "_": false, - "Backbone": false, - "jQuery": false, - "JSON": false, - "wp": false - } -} -``` - -**Ignore blocks when needed:** - -```javascript -/* jshint ignore:start */ -code_that_should_not_be_linted; -/* jshint ignore:end */ -``` diff --git a/.windsurf/skills/wordpress-javascript-coding-standards/SKILL.md b/.windsurf/skills/wordpress-javascript-coding-standards/SKILL.md deleted file mode 100644 index aca02c76e4..0000000000 --- a/.windsurf/skills/wordpress-javascript-coding-standards/SKILL.md +++ /dev/null @@ -1,88 +0,0 @@ ---- -name: wordpress-javascript-coding-standards -description: WordPress JavaScript coding standards for maintaining, generating, or refactoring JS code. Apply when working with JavaScript files in WordPress plugins or themes. ---- - -# WordPress JavaScript Coding Standards - -**Version 1.0.0** -Based on WordPress Core Official Standards - -> **Note:** -> This document is for AI agents and LLMs to follow when maintaining, -> generating, or refactoring JavaScript code in the WordPress ecosystem. - ---- - -## Overview - -JavaScript has become a critical component in WordPress development. These standards ensure consistency and readability across WordPress Core, themes, and plugins. - -**When to apply:** Any JavaScript file in WordPress Core, plugins, or themes. - -**Reference:** [WordPress JavaScript Coding Standards](https://developer.wordpress.org/coding-standards/wordpress-coding-standards/javascript/) - ---- - -## Rule Categories by Priority - -### 1. Spacing — **HIGH** - -| Rule | Impact | -| --------------------------- | ----------- | -| [Spacing](rules/spacing.md) | Readability | - -### 2. Indentation & Line Breaks — **HIGH** - -| Rule | Impact | -| ----------------------------------- | -------------- | -| [Indentation](rules/indentation.md) | Code structure | - -### 3. Variables & Naming — **MEDIUM** - -| Rule | Impact | -| ------------------------------- | ----------- | -| [Variables](rules/variables.md) | Consistency | - -### 4. Equality & Type Checks — **MEDIUM** - -| Rule | Impact | -| ----------------------------- | -------------- | -| [Equality](rules/equality.md) | Bug prevention | - -### 5. Syntax Rules — **MEDIUM** - -| Rule | Impact | -| ------------------------- | -------------- | -| [Syntax](rules/syntax.md) | ASI prevention | - -### 6. Best Practices — **LOW** - -| Rule | Impact | -| ----------------------------------------- | ------------- | -| [Best Practices](rules/best-practices.md) | Documentation | - ---- - -## Usage - -### Load all rules - -```text -@wordpress-javascript-coding-standards -``` - -### Full compiled guide - -See [AGENTS.md](AGENTS.md) for the complete reference. - ---- - -## Tooling - -Use JSHint with WordPress configuration to check code compliance: - -```bash -npm install -g jshint -jshint --config .jshintrc path/to/file.js -``` diff --git a/.windsurf/skills/wordpress-javascript-coding-standards/rules/best-practices.md b/.windsurf/skills/wordpress-javascript-coding-standards/rules/best-practices.md deleted file mode 100644 index 8d8c3e1c6c..0000000000 --- a/.windsurf/skills/wordpress-javascript-coding-standards/rules/best-practices.md +++ /dev/null @@ -1,137 +0,0 @@ -# Best Practices - -**Priority: LOW** -**Impact: Documentation and performance** - ---- - -## Comments - -Comments come before the code. Preceded by blank line. Single space after `//`. - -**Correct:** - -```javascript -someStatement(); - -// Explanation of something complex on the next line -$("p").doSomething(); - -// This is a comment that is long enough to warrant being stretched -// over the span of multiple lines. -``` - -**JSDoc format for documentation:** - -```javascript -/** - * Function description. - * - * @param {string} param1 - Description. - * @return {boolean} Description. - */ -function myFunction(param1) { - return true; -} -``` - -**Inline comments for special arguments:** - -```javascript -function foo(types, selector, data, fn, /* INTERNAL */ one) { - // Do stuff -} -``` - ---- - -## Arrays - -Create using `[]` not `new Array()`. - -**Correct:** - -```javascript -var myArray = []; -var myArray = [1, 2, 3]; -``` - ---- - -## Objects - -Create using `{}` not `new Object()`. - -**Correct:** - -```javascript -var myObject = {}; -var myObject = { key: "value" }; -``` - ---- - -## Iteration - -Use `_.each()` or `jQuery.each()` for collections. - -**Correct:** - -```javascript -_.each(myArray, function (value, index) { - // Process value -}); - -$(".items").each(function () { - // Process element -}); -``` - ---- - -## Code Refactoring - -Don't refactor just for style. "Whitespace-only" patches are discouraged. - -> "Code refactoring should not be done just because we can." – Andrew Nacin - ---- - -## JSHint Configuration - -Standard WordPress JSHint settings: - -```json -{ - "boss": true, - "curly": true, - "eqeqeq": true, - "eqnull": true, - "es3": true, - "expr": true, - "immed": true, - "noarg": true, - "nonbsp": true, - "onevar": true, - "quotmark": "single", - "trailing": true, - "undef": true, - "unused": true, - "browser": true, - "globals": { - "_": false, - "Backbone": false, - "jQuery": false, - "JSON": false, - "wp": false - } -} -``` - -**Ignore blocks when needed:** - -```javascript -/* jshint ignore:start */ -code_that_should_not_be_linted; -/* jshint ignore:end */ -``` diff --git a/.windsurf/skills/wordpress-javascript-coding-standards/rules/equality.md b/.windsurf/skills/wordpress-javascript-coding-standards/rules/equality.md deleted file mode 100644 index a5170c4825..0000000000 --- a/.windsurf/skills/wordpress-javascript-coding-standards/rules/equality.md +++ /dev/null @@ -1,116 +0,0 @@ -# Equality and Type Checks - -**Priority: MEDIUM** -**Impact: Bug prevention and reliability** - ---- - -## Strict Equality - -Always use `===` and `!==`. - -**Incorrect:** - -```javascript -if (foo == bar) { -} -if (foo != bar) { -} -``` - -**Correct:** - -```javascript -if (foo === bar) { -} -if (foo !== bar) { -} -``` - ---- - -## Type Checks - -**String:** - -```javascript -typeof object === "string"; -``` - -**Number:** - -```javascript -typeof object === "number"; -``` - -**Boolean:** - -```javascript -typeof object === "boolean"; -``` - -**Object:** - -```javascript -typeof object === "object"; -// or -_.isObject(object); -``` - -**Plain Object:** - -```javascript -jQuery.isPlainObject(object); -``` - -**Function:** - -```javascript -_.isFunction(object); -// or -jQuery.isFunction(object); -``` - -**Array:** - -```javascript -_.isArray(object); -// or -jQuery.isArray(object); -``` - -**Element:** - -```javascript -object.nodeType; -// or -_.isElement(object); -``` - -**null:** - -```javascript -object === null; -``` - -**null or undefined:** - -```javascript -object == null; -``` - -**undefined:** - -```javascript -// Global Variables -typeof variable === "undefined"; - -// Local Variables -variable === undefined; - -// Properties -object.prop === undefined; - -// Any of the above -_.isUndefined(object); -``` diff --git a/.windsurf/skills/wordpress-javascript-coding-standards/rules/indentation.md b/.windsurf/skills/wordpress-javascript-coding-standards/rules/indentation.md deleted file mode 100644 index dc4bb26722..0000000000 --- a/.windsurf/skills/wordpress-javascript-coding-standards/rules/indentation.md +++ /dev/null @@ -1,104 +0,0 @@ -# Indentation and Line Breaks - -**Priority: HIGH** -**Impact: Code structure and consistency** - ---- - -## Tab Indentation - -Use tabs for indentation, even inside closures. - -**Correct:** - -```javascript -(function ($) { - // Expressions indented - - function doSomething() { - // Expressions indented - } -})(jQuery); -``` - ---- - -## Blocks and Curly Braces - -Opening brace on same line. Closing brace on line after last statement. - -**Correct:** - -```javascript -var a, b, c; - -if (myFunction()) { - // Expressions -} else if ((a && b) || c) { - // Expressions -} else { - // Expressions -} -``` - ---- - -## Multi-line Statements - -Line breaks must occur after an operator. - -**Incorrect:** - -```javascript -var html = - "

The sum of " + - a + - " and " + - b + - " plus " + - c + - " is " + - (a + b + c) + - "

"; -``` - -**Correct:** - -```javascript -var html = - "

The sum of " + - a + - " and " + - b + - " plus " + - c + - " is " + - (a + b + c) + - "

"; -``` - -**Ternary on multiple lines:** - -```javascript -var baz = firstCondition(foo) && secondCondition(bar) ? qux(foo, bar) : foo; -``` - -**Long conditionals:** - -```javascript -if (firstCondition() && secondCondition() && thirdCondition()) { - doStuff(); -} -``` - ---- - -## Chained Method Calls - -One call per line. Extra indent when context changes. - -**Correct:** - -```javascript -elements.addClass("foo").children().html("hello").end().appendTo("body"); -``` diff --git a/.windsurf/skills/wordpress-javascript-coding-standards/rules/spacing.md b/.windsurf/skills/wordpress-javascript-coding-standards/rules/spacing.md deleted file mode 100644 index 2b5008d0a4..0000000000 --- a/.windsurf/skills/wordpress-javascript-coding-standards/rules/spacing.md +++ /dev/null @@ -1,103 +0,0 @@ -# Spacing - -**Priority: HIGH** -**Impact: Readability** - ---- - -## General Rules - -- Indentation with tabs -- No whitespace at end of line or on blank lines -- Lines should usually be no longer than 80 characters, max 100 -- `if`/`else`/`for`/`while`/`try` blocks always use braces and multiple lines -- Unary operators (`++`, `--`) must not have space next to operand -- `,` and `;` must not have preceding space -- `:` after property name must not have preceding space -- `?` and `:` in ternary must have space on both sides -- No filler spaces in empty constructs (`{}`, `[]`, `fn()`) -- New line at end of each file -- `!` negation operator should have following space - ---- - -## Object Declarations - -**Incorrect:** - -```javascript -var obj = { ready: 9, when: 4, "you are": 15 }; -``` - -**Correct (multiline preferred):** - -```javascript -var obj = { - ready: 9, - when: 4, - "you are": 15, -}; -``` - -**Acceptable for small objects:** - -```javascript -var obj = { ready: 9, when: 4, "you are": 15 }; -``` - ---- - -## Arrays and Function Calls - -Always include extra spaces around elements and arguments. - -**Correct:** - -```javascript -array = [a, b]; - -foo(arg); -foo("string", object); -foo(options, object[property]); -foo(node, "property", 2); - -prop = object["default"]; -firstArrayElement = arr[0]; -``` - ---- - -## Control Structures - -**Correct:** - -```javascript -var i; - -if (condition) { - doSomething("with a string"); -} else if (otherCondition) { - otherThing({ - key: value, - otherKey: otherValue, - }); -} else { - somethingElse(true); -} - -// Space after ! negation (differs from jQuery) -while (!condition) { - iterating++; -} - -for (i = 0; i < 100; i++) { - object[array[i]] = someFn(i); - $(".container").val(array[i]); -} - -try { - // Expressions -} catch (e) { - // Expressions -} -``` diff --git a/.windsurf/skills/wordpress-javascript-coding-standards/rules/syntax.md b/.windsurf/skills/wordpress-javascript-coding-standards/rules/syntax.md deleted file mode 100644 index 79f5ed1ebf..0000000000 --- a/.windsurf/skills/wordpress-javascript-coding-standards/rules/syntax.md +++ /dev/null @@ -1,97 +0,0 @@ -# Syntax Rules - -**Priority: MEDIUM** -**Impact: Consistency and ASI prevention** - ---- - -## Semicolons - -Always use them. Never rely on Automatic Semicolon Insertion (ASI). - -**Incorrect:** - -```javascript -var foo = "bar"; -function myFunc() {} -``` - -**Correct:** - -```javascript -var foo = "bar"; -function myFunc() {} -``` - ---- - -## Strings - -Use single quotes for string literals. - -**Incorrect:** - -```javascript -var myStr = "strings should use single quotes"; -``` - -**Correct:** - -```javascript -var myStr = "strings should be contained in single quotes"; -``` - -**Escaping:** - -```javascript -var escaped = "Note the backslash before the 'single quotes'"; -``` - ---- - -## Switch Statements - -Use `break` for each case except `default`. Indent `case` one tab from `switch`. - -**Correct:** - -```javascript -switch (event.keyCode) { - // ENTER and SPACE both trigger x() - case $.ui.keyCode.ENTER: - case $.ui.keyCode.SPACE: - x(); - break; - - case $.ui.keyCode.ESCAPE: - y(); - break; - - default: - z(); -} -``` - -**Return values (set in cases, return at end):** - -```javascript -function getKeyCode(keyCode) { - var result; - - switch (event.keyCode) { - case $.ui.keyCode.ENTER: - case $.ui.keyCode.SPACE: - result = "commit"; - break; - - case $.ui.keyCode.ESCAPE: - result = "exit"; - break; - - default: - result = "default"; - } - - return result; -} -``` diff --git a/.windsurf/skills/wordpress-javascript-coding-standards/rules/variables.md b/.windsurf/skills/wordpress-javascript-coding-standards/rules/variables.md deleted file mode 100644 index ac70f7a623..0000000000 --- a/.windsurf/skills/wordpress-javascript-coding-standards/rules/variables.md +++ /dev/null @@ -1,101 +0,0 @@ -# Variables and Naming - -**Priority: MEDIUM** -**Impact: Consistency and modern JS** - ---- - -## Variable Declarations - -Use `const` and `let` (ES6+). Avoid `var` in modern code. - -**Correct:** - -```javascript -const myName = "WordPress"; -let counter = 0; -``` - -**Legacy (when var is required):** - -```javascript -var myName = "WordPress"; -``` - ---- - -## Naming Conventions - -Use camelCase with lowercase first letter. This differs from WordPress PHP standards. - -**Incorrect:** - -```javascript -var some_variable = "value"; -var SomeVariable = "value"; -``` - -**Correct:** - -```javascript -var someVariable = "value"; - -function myFunction() {} - -// Iterators allowed as single letters -for (var i = 0; i < 10; i++) {} -``` - ---- - -## Abbreviations and Acronyms - -Treat as words in camelCase. - -**Correct:** - -```javascript -var defined; -var htmlContent; -var jsonData; -var xmlParser; -``` - ---- - -## Class Definitions - -Use UpperCamelCase. - -**Correct:** - -```javascript -class MyClass { - constructor() {} -} -``` - ---- - -## Constants - -Use SCREAMING_SNAKE_CASE. - -**Correct:** - -```javascript -const MAX_ITEMS = 100; -const API_URL = "https://api.example.com"; -``` - ---- - -## Globals - -Avoid globals. If unavoidable, set them explicitly via `window`. - -**Correct:** - -```javascript -window.myGlobal = "value"; -``` diff --git a/.windsurf/skills/wordpress-php-coding-standards/AGENTS.md b/.windsurf/skills/wordpress-php-coding-standards/AGENTS.md deleted file mode 100644 index bffc731a74..0000000000 --- a/.windsurf/skills/wordpress-php-coding-standards/AGENTS.md +++ /dev/null @@ -1,1151 +0,0 @@ -# WordPress PHP Coding Standards - -**Version 1.0.0** -Based on WordPress Core Official Standards - -> **Note:** -> This document is for AI agents and LLMs to follow when maintaining, -> generating, or refactoring PHP code in the WordPress ecosystem. - ---- - -## Abstract - -These PHP coding standards are the official WordPress coding standards for PHP. They are mandatory for WordPress Core and recommended for all plugins and themes. They encompass not just code style, but also best practices for interoperability, translatability, and security. - ---- - -## Table of Contents - -1. [Security & Database](#1-security--database) — **CRITICAL** - - 1.1 [Database Queries](#11-database-queries) - - 1.2 [SQL Formatting](#12-sql-formatting) -2. [Naming Conventions](#2-naming-conventions) — **HIGH** - - 2.1 [Variables and Functions](#21-variables-and-functions) - - 2.2 [Classes, Interfaces, Traits, Enums](#22-classes-interfaces-traits-enums) - - 2.3 [Constants](#23-constants) - - 2.4 [File Naming](#24-file-naming) - - 2.5 [Dynamic Hooks](#25-dynamic-hooks) -3. [Formatting](#3-formatting) — **HIGH** - - 3.1 [Brace Style](#31-brace-style) - - 3.2 [Array Syntax](#32-array-syntax) - - 3.3 [Multiline Function Calls](#33-multiline-function-calls) - - 3.4 [Type Declarations](#34-type-declarations) -4. [Whitespace](#4-whitespace) — **MEDIUM** - - 4.1 [Space Usage](#41-space-usage) - - 4.2 [Indentation](#42-indentation) - - 4.3 [Trailing Spaces](#43-trailing-spaces) -5. [Control Structures](#5-control-structures) — **MEDIUM** - - 5.1 [Yoda Conditions](#51-yoda-conditions) - - 5.2 [Use elseif](#52-use-elseif) -6. [Operators](#6-operators) — **MEDIUM** - - 6.1 [Ternary Operator](#61-ternary-operator) - - 6.2 [Increment/Decrement Operators](#62-incrementdecrement-operators) - - 6.3 [Error Control Operator](#63-error-control-operator) -7. [Best Practices](#7-best-practices) — **MEDIUM** - - 7.1 [Self-Explanatory Flag Values](#71-self-explanatory-flag-values) - - 7.2 [Avoid Clever Code](#72-avoid-clever-code) - - 7.3 [Closures](#73-closures) - - 7.4 [Don't Use extract()](#74-dont-use-extract) - - 7.5 [Regular Expressions](#75-regular-expressions) -8. [General Syntax](#8-general-syntax) — **LOW** - - 8.1 [PHP Tags](#81-php-tags) - - 8.2 [Quotes](#82-quotes) - - 8.3 [Require/Include](#83-requireinclude) -9. [Namespaces & Imports](#9-namespaces--imports) — **MEDIUM** - - 9.1 [Namespace Declarations](#91-namespace-declarations) - - 9.2 [Import Use Statements](#92-import-use-statements) - - 9.3 [Trait Use Statements](#93-trait-use-statements) -10. [Shell Commands](#10-shell-commands) — **HIGH** - ---- - -## 1. Security & Database - -**Impact: CRITICAL** - -Database interactions must be secure and properly escaped to prevent SQL injection. - -### 1.1 Database Queries - -**Impact: CRITICAL (prevents SQL injection)** - -Avoid touching the database directly. Use WordPress functions when available. If you must write queries, always use `$wpdb->prepare()`. - -**Incorrect (direct query with unescaped data):** - -```php -$wpdb->query( "UPDATE $wpdb->posts SET post_title = '$var' WHERE ID = $id" ); -``` - -**Correct (using $wpdb->prepare()):** - -```php -$var = "dangerous'"; -$id = some_foo_number(); - -$wpdb->query( - $wpdb->prepare( - "UPDATE $wpdb->posts SET post_title = %s WHERE ID = %d", - $var, - $id - ) -); -``` - -**Placeholders:** - -- `%d` — integer -- `%f` — float -- `%s` — string -- `%i` — identifier (table/field names) - -**Important:** Do not quote placeholders! `$wpdb->prepare()` handles escaping and quoting. - -### 1.2 SQL Formatting - -**Impact: MEDIUM (readability)** - -Capitalize SQL keywords. Break complex statements into multiple lines. - -**Correct:** - -```php -$wpdb->query( - $wpdb->prepare( - "SELECT ID, post_title - FROM $wpdb->posts - WHERE post_status = %s - AND post_type = %s - ORDER BY post_date DESC", - 'publish', - 'post' - ) -); -``` - ---- - -## 2. Naming Conventions - -**Impact: HIGH** - -Consistent naming ensures code readability and discoverability. - -### 2.1 Variables and Functions - -**Impact: HIGH (code consistency)** - -Use lowercase letters with underscores. Never use camelCase. Don't abbreviate unnecessarily. - -**Incorrect:** - -```php -function someFunction( $someVariable ) {} -function getData( $usrId ) {} -``` - -**Correct:** - -```php -function some_function( $some_variable ) {} -function get_data( $user_id ) {} -``` - -### 2.2 Classes, Interfaces, Traits, Enums - -**Impact: HIGH (OOP consistency)** - -Use capitalized words separated by underscores. Acronyms should be all uppercase. - -**Incorrect:** - -```php -class walkerCategory extends Walker {} -class WpHttp {} -``` - -**Correct:** - -```php -class Walker_Category extends Walker {} -class WP_HTTP {} -interface Mailer_Interface {} -trait Forbid_Dynamic_Properties {} -enum Post_Status {} -``` - -### 2.3 Constants - -**Impact: MEDIUM (consistency)** - -All uppercase with underscores separating words. - -**Correct:** - -```php -define( 'DOING_AJAX', true ); -const MAX_UPLOAD_SIZE = 1048576; -``` - -### 2.4 File Naming - -**Impact: MEDIUM (project organization)** - -Use lowercase letters with hyphens separating words. - -**General files:** - -``` -my-plugin-name.php -template-parts.php -``` - -**Class files (prefix with `class-`):** - -``` -class-wp-error.php // For WP_Error class -class-walker-category.php // For Walker_Category class -``` - -**Template tags (suffix with `-template`):** - -``` -general-template.php -``` - -### 2.5 Dynamic Hooks - -**Impact: HIGH (hook discoverability)** - -Use interpolation with curly braces, not concatenation. Wrap in double quotes. - -**Incorrect:** - -```php -do_action( $new_status . '_' . $post->post_type, $post->ID, $post ); -``` - -**Correct:** - -```php -do_action( "{$new_status}_{$post->post_type}", $post->ID, $post ); -``` - -Use descriptive variable names in hooks: - -**Incorrect:** - -```php -do_action( "save_post_{$this->id}", $data ); -``` - -**Correct:** - -```php -do_action( "save_post_{$post_id}", $data ); -``` - ---- - -## 3. Formatting - -**Impact: HIGH** - -Structural formatting for readable and maintainable code. - -### 3.1 Brace Style - -**Impact: HIGH (code structure)** - -Always use braces, even for single statements. Opening brace on same line. - -**Incorrect:** - -```php -if ( condition ) - action(); - -if ( condition ) action(); -``` - -**Correct:** - -```php -if ( condition ) { - action1(); - action2(); -} elseif ( condition2 && condition3 ) { - action3(); -} else { - default_action(); -} -``` - -**Alternative syntax for templates:** - -```php - -
- -
- -
- -
- -``` - -### 3.2 Array Syntax - -**Impact: MEDIUM (consistency)** - -Always use long array syntax `array()`, not short syntax `[]`. - -**Incorrect:** - -```php -$array = [ 1, 2, 3 ]; -$args = [ - 'post_type' => 'page', -]; -``` - -**Correct:** - -```php -$array = array( 1, 2, 3 ); -$args = array( - 'post_type' => 'page', - 'post_author' => 123, - 'post_status' => 'publish', -); -``` - -**Note:** Include trailing comma after the last item for cleaner diffs. - -### 3.3 Multiline Function Calls - -**Impact: MEDIUM (readability)** - -Each parameter on its own line. Assign complex values to variables first. - -**Incorrect:** - -```php -$a = foo( array( 'use_this' => true, 'meta_key' => 'field_name' ), sprintf( __( 'Hello, %s!', 'textdomain' ), $friend_name ) ); -``` - -**Correct:** - -```php -$bar = array( - 'use_this' => true, - 'meta_key' => 'field_name', -); -$baz = sprintf( - /* translators: %s: Friend's name */ - __( 'Hello, %s!', 'yourtextdomain' ), - $friend_name -); - -$a = foo( - $bar, - $baz, - /* translators: %s: cat */ - sprintf( __( 'The best pet is a %s.' ), 'cat' ) -); -``` - -### 3.4 Type Declarations - -**Impact: MEDIUM (type safety)** - -One space before and after type. No space between nullability operator and type. - -**Incorrect:** - -```php -function baz(Class_Name $param_a, String$param_b, CALLABLE $param_c ) : ? iterable { - // Do something. -} -``` - -**Correct:** - -```php -function foo( Class_Name $parameter, callable $callable, int $number_of_things = 0 ) { - // Do something. -} - -function bar( - Interface_Name&Concrete_Class $param_a, - string|int $param_b, - callable $param_c = 'default_callable' -): User|false { - // Do something. -} -``` - ---- - -## 4. Whitespace - -**Impact: MEDIUM** - -Spacing rules for visual consistency. - -### 4.1 Space Usage - -**Impact: MEDIUM (readability)** - -Spaces after commas and around operators. Spaces inside parentheses of control structures. - -**Operators:** - -```php -SOME_CONST === 23; -foo() && bar(); -! $foo; -array( 1, 2, 3 ); -$baz . '-5'; -$term .= 'X'; -$result = 2 ** 3; -``` - -**Control structures:** - -```php -foreach ( $foo as $bar ) { - // ... -} - -if ( $foo && ( $bar || $baz ) ) { - // ... -} -``` - -**Functions:** - -```php -function my_function( $param1 = 'foo', $param2 = 'bar' ) { - // ... -} - -my_function( $param1, func_param( $param2 ) ); -``` - -**Array access (space only around variables):** - -```php -$x = $foo['bar']; // Correct - no space for literal -$x = $foo[0]; // Correct - no space for number -$x = $foo[ $bar ]; // Correct - space for variable -``` - -**Type casts (lowercase, short form):** - -```php -$foo = (bool) $bar; // Correct -$foo = (int) $value; // Correct - -$foo = (boolean) $bar; // Incorrect - use (bool) -$foo = (integer) $value; // Incorrect - use (int) -``` - -**Increment/decrement (no space):** - -```php -for ( $i = 0; $i < 10; $i++ ) {} -++$b; -``` - -### 4.2 Indentation - -**Impact: MEDIUM (code structure)** - -Use real tabs, not spaces. Spaces may be used mid-line for alignment. - -**Correct:** - -```php -$foo = 'somevalue'; -$foo2 = 'somevalue2'; -$foo34 = 'somevalue3'; -``` - -**Associative arrays (one item per line when more than one):** - -```php -// Single item - can be one line -$query = new WP_Query( array( 'ID' => 123 ) ); - -// Multiple items - each on own line -$args = array( - 'post_type' => 'page', - 'post_author' => 123, - 'post_status' => 'publish', -); -``` - -**Switch statements:** - -```php -switch ( $type ) { - case 'foo': - some_function(); - break; - - case 'bar': - some_function(); - break; -} -``` - -### 4.3 Trailing Spaces - -**Impact: LOW (clean diffs)** - -Remove trailing whitespace at end of lines. Omit closing PHP tag at end of file (preferred). No trailing blank lines at end of function body. - ---- - -## 5. Control Structures - -**Impact: MEDIUM** - -Rules for conditionals and loops. - -### 5.1 Yoda Conditions - -**Impact: HIGH (bug prevention)** - -Put constants/literals on the left side of comparisons. - -**Incorrect:** - -```php -if ( $the_force == true ) { - // Accidental assignment possible: if ( $the_force = true ) -} -``` - -**Correct:** - -```php -if ( true === $the_force ) { - $victorious = you_will( $be ); -} -``` - -**Applies to:** `==`, `!=`, `===`, `!==` - -**Does not apply to:** `<`, `>`, `<=`, `>=` (too difficult to read) - -### 5.2 Use elseif - -**Impact: MEDIUM (syntax compatibility)** - -Use `elseif`, not `else if`. Required for alternative syntax compatibility. - -**Incorrect:** - -```php -if ( condition ) { - // ... -} else if ( condition2 ) { - // ... -} -``` - -**Correct:** - -```php -if ( condition ) { - // ... -} elseif ( condition2 ) { - // ... -} -``` - ---- - -## 6. Operators - -**Impact: MEDIUM** - -Proper operator usage. - -### 6.1 Ternary Operator - -**Impact: MEDIUM (readability)** - -Test for true, not false (except with `! empty()`). Don't use short ternary. - -**Incorrect:** - -```php -$value = $condition ?: 'default'; // Short ternary - not allowed -$type = ( ! $is_valid ) ? 'invalid' : 'valid'; // Testing for false -``` - -**Correct:** - -```php -$musictype = ( 'jazz' === $music ) ? 'cool' : 'blah'; -$value = ( ! empty( $field ) ) ? $field : 'default'; -``` - -### 6.2 Increment/Decrement Operators - -**Impact: LOW (performance)** - -Prefer pre-increment/decrement for standalone statements. - -**Incorrect:** - -```php -$a--; -$count++; -``` - -**Correct:** - -```php ---$a; -++$count; -``` - -### 6.3 Error Control Operator - -**Impact: HIGH (error handling)** - -Don't use `@` to suppress errors. Do proper error checking instead. - -**Incorrect:** - -```php -$value = @file_get_contents( $file ); -``` - -**Correct:** - -```php -if ( file_exists( $file ) && is_readable( $file ) ) { - $value = file_get_contents( $file ); -} -``` - ---- - -## 7. Best Practices - -**Impact: MEDIUM** - -Recommendations for maintainable code. - -### 7.1 Self-Explanatory Flag Values - -**Impact: MEDIUM (readability)** - -Use descriptive strings instead of boolean flags. - -**Incorrect:** - -```php -function eat( $what, $slowly = true ) {} - -eat( 'mushrooms' ); -eat( 'mushrooms', true ); // What does true mean? -eat( 'dogfood', false ); // What does false mean? -``` - -**Correct:** - -```php -function eat( $what, $speed = 'slowly' ) {} - -eat( 'mushrooms' ); -eat( 'mushrooms', 'slowly' ); -eat( 'dogfood', 'quickly' ); -``` - -**For multiple options, use an array:** - -```php -function eat( $what, $args = array() ) {} - -eat( 'noodles', array( 'speed' => 'moderate' ) ); -``` - -### 7.2 Avoid Clever Code - -**Impact: HIGH (maintainability)** - -Readability over cleverness. Use strict comparisons. No assignments in conditionals. - -**Incorrect:** - -```php -isset( $var ) || $var = some_function(); - -if ( $data = $wpdb->get_var( '...' ) ) { - // Use $data -} - -if ( 0 == strpos( 'WordPress', 'foo' ) ) {} // Loose comparison -``` - -**Correct:** - -```php -if ( ! isset( $var ) ) { - $var = some_function(); -} - -$data = $wpdb->get_var( '...' ); -if ( $data ) { - // Use $data -} - -if ( 0 === strpos( $text, 'WordPress' ) ) {} // Strict comparison -``` - -**Switch fall-through must be documented:** - -```php -switch ( $foo ) { - case 'bar': - // Empty case can fall through without comment - case 'baz': - echo esc_html( $foo ); - break; - - case 'dog': - echo 'horse'; - // no break - explicit comment required - case 'fish': - echo 'bird'; - break; -} -``` - -**Never use:** - -- `goto` statement -- `eval()` construct -- `create_function()` (deprecated/removed) - -### 7.3 Closures - -**Impact: MEDIUM (hook compatibility)** - -Closures are allowed but should NOT be used as filter/action callbacks (difficult to remove). - -**Acceptable:** - -```php -$caption = preg_replace_callback( - '/<[a-zA-Z0-9]+(?: [^<>]+>)*/', - function ( $matches ) { - return preg_replace( '/[\r\n\t]+/', ' ', $matches[0] ); - }, - $caption -); -``` - -**Not recommended for hooks:** - -```php -// Hard to remove with remove_action() -add_action( 'init', function() { - // ... -} ); -``` - -### 7.4 Don't Use extract() - -**Impact: HIGH (debugging)** - -Never use `extract()`. It makes code harder to debug and understand. - -**Incorrect:** - -```php -extract( $args ); -echo $title; // Where did $title come from? -``` - -**Correct:** - -```php -$title = $args['title']; -echo $title; -``` - -### 7.5 Regular Expressions - -**Impact: MEDIUM (security/compatibility)** - -Use PCRE (`preg_` functions). Never use `/e` modifier. Use single-quoted strings. - -**Incorrect:** - -```php -preg_replace( '/pattern/e', 'replacement', $subject ); // /e is deprecated -``` - -**Correct:** - -```php -preg_replace_callback( - '/pattern/', - function ( $matches ) { - return process( $matches[0] ); - }, - $subject -); -``` - ---- - -## 8. General Syntax - -**Impact: LOW** - -Basic PHP syntax rules. - -### 8.1 PHP Tags - -**Impact: MEDIUM (compatibility)** - -Always use full PHP tags. Never use shorthand. - -**Incorrect:** - -```php - - -``` - -**Correct:** - -```php - - -``` - -**Multiline PHP in HTML (tags on their own lines):** - -```php - - - -``` - -### 8.2 Quotes - -**Impact: LOW (string handling)** - -Use single quotes when not evaluating variables. Alternate quote styles to avoid escaping. - -**Correct:** - -```php -echo 'Link name'; -echo "text with a ' single quote"; -``` - -**Always escape output in HTML attributes:** - -```php -echo '' . esc_html( $text ) . ''; -``` - -### 8.3 Require/Include - -**Impact: MEDIUM (file loading)** - -No parentheses. One space after keyword. Prefer `require_once` over `include_once`. - -**Incorrect:** - -```php -include_once( ABSPATH . 'file-name.php' ); -require_once ABSPATH . 'file-name.php'; // Extra space -``` - -**Correct:** - -```php -require_once ABSPATH . 'file-name.php'; -``` - -**Why `require_once`:** If file not found, `require` throws Fatal Error (stops execution). `include` only warns and continues, potentially causing security issues. - ---- - -## Object-Oriented Programming - -### One Structure Per File - -Each class, interface, trait, or enum should be in its own file. - -### Visibility - -Always declare visibility (`public`, `protected`, `private`). - -**Incorrect:** - -```php -class Foo { - var $bar; // Old syntax - function baz() {} // No visibility -} -``` - -**Correct:** - -```php -class Foo { - public $bar; - - public function baz() {} - - protected function qux() {} - - private function quux() {} -} -``` - -### Modifier Order - -```php -final public static function foo() {} -abstract protected function bar(); -``` - -### Object Instantiation - -Always use parentheses, even without arguments. - -**Incorrect:** - -```php -$obj = new Foo; -``` - -**Correct:** - -```php -$obj = new Foo(); -$obj = new Foo( $arg ); -``` - ---- - -## Magic Constants - -Use uppercase for magic constants. - -**Correct:** - -```php -__DIR__ -__FILE__ -__CLASS__ -__FUNCTION__ -``` - ---- - -## Spread Operator - -Space after `...` when used to spread. No space when collecting. - -```php -// Spreading -function_call( ...$array ); -$merged = array( ...$array1, ...$array2 ); - -// Collecting (variadic) -function variadic_function( ...$args ) {} -``` - ---- - -## 9. Namespaces & Imports - -**Impact: MEDIUM** - -Modern PHP namespace and import conventions for WordPress plugins and themes. - -### 9.1 Namespace Declarations - -**Impact: MEDIUM (organization)** - -Each part of a namespace name should consist of capitalized words separated by underscores. - -**Incorrect:** - -```php -namespace prefix\admin\domainUrl\subDomain; // camelCase not allowed -namespace Foo { - // Code - curly brace syntax not allowed -} -namespace { - // Global namespace declaration not allowed -} -``` - -**Correct:** - -```php -namespace Prefix\Admin\Domain_URL\Sub_Domain\Event; -``` - -**Rules:** - -- One blank line before the declaration, at least one blank line after -- Only one namespace declaration per file, at the top -- No curly brace syntax -- No global namespace declarations -- Use unique, long prefixes like `Vendor\Project_Name` to prevent conflicts -- Do NOT use `wp` or `WordPress` as namespace prefixes - -**Note:** Namespaces are encouraged for plugins/themes but not yet used in WordPress Core. - -### 9.2 Import Use Statements - -**Impact: MEDIUM (code organization)** - -Import `use` statements should be at the top of the file after the namespace declaration. - -**Order of imports:** - -1. Namespaces, classes, interfaces, traits, enums -2. Functions -3. Constants - -**Incorrect:** - -```php -namespace Project_Name\Feature; - -use const Project_Name\Sub_Feature\CONSTANT_A; // Constants before classes -use function Project_Name\Sub_Feature\function_a; // Functions before classes -use \Project_Name\Sub_Feature\Class_C as aliased_class_c; // Leading backslash, wrong alias naming - -class Foo { - // Code. -} - -use Project_Name\Another_Class; // Import after class definition - not allowed -``` - -**Correct:** - -```php -namespace Project_Name\Feature; - -use Project_Name\Sub_Feature\Class_A; -use Project_Name\Sub_Feature\Class_C as Aliased_Class_C; -use Project_Name\Sub_Feature\{ - Class_D, - Class_E as Aliased_Class_E, -} - -use function Project_Name\Sub_Feature\function_a; -use function Project_Name\Sub_Feature\function_b as aliased_function; - -use const Project_Name\Sub_Feature\CONSTANT_A; -use const Project_Name\Sub_Feature\CONSTANT_D as ALIASED_CONSTANT; - -// Rest of the code. -``` - -**Rules:** - -- No leading backslash in imports -- Aliases must follow WordPress naming conventions (capitalized words with underscores for classes, lowercase with underscores for functions) -- Don't combine different import types in one statement -- All imports before any class/function definitions - -**Note:** Import `use` statements are discouraged in WordPress Core for now. - -### 9.3 Trait Use Statements - -**Impact: MEDIUM (OOP organization)** - -Trait `use` statements should be at the top of a class with proper spacing. - -**Incorrect:** - -```php -class Foo { - // No blank line before trait use statement - use Bar_Trait; - - use Foo_Trait, Bazinga_Trait{Bar_Trait::method_name insteadof Foo_Trait; // Wrong formatting - Bazinga_Trait::method_name as bazinga_method; - }; - - public $baz = true; // Missing blank line after trait import -} -``` - -**Correct:** - -```php -class Foo { - - use Bar_Trait; - - use Foo_Trait, Bazinga_Trait { - Bar_Trait::method_name insteadof Foo_Trait; - Bazinga_Trait::method_name as bazinga_method; - } - - use Loopy_Trait { - eat as protected; - } - - public $baz = true; - - // Rest of class... -} -``` - -**Rules:** - -- One blank line before the first `use` statement -- At least one blank line after the last `use` statement (exception: if class only contains trait imports) -- Each aliasing/conflict resolution on its own line -- Proper indentation inside curly braces - ---- - -## 10. Shell Commands - -**Impact: HIGH (security)** - -Use of shell commands requires caution due to security implications. - -**Never use the backtick operator:** - -```php -// Incorrect - backtick operator is identical to shell_exec() -$output = `ls -la`; -``` - -**Why:** The backtick operator is equivalent to `shell_exec()`, and most hosts disable this function in `php.ini` for security reasons. It can lead to command injection vulnerabilities if user input is involved. - -**If shell commands are absolutely necessary:** - -- Use `escapeshellarg()` and `escapeshellcmd()` for any user-provided input -- Prefer WordPress functions that handle this safely when available -- Document why shell access is needed diff --git a/.windsurf/skills/wordpress-php-coding-standards/SKILL.md b/.windsurf/skills/wordpress-php-coding-standards/SKILL.md deleted file mode 100644 index f5abd39fe7..0000000000 --- a/.windsurf/skills/wordpress-php-coding-standards/SKILL.md +++ /dev/null @@ -1,147 +0,0 @@ ---- -name: wordpress-php-coding-standards -description: WordPress PHP coding standards for maintaining, generating, or refactoring PHP code. Apply when working with PHP files in WordPress plugins or themes. ---- - -# WordPress PHP Coding Standards - -**Version 1.0.0** -Based on WordPress Core Official Standards - -> **Note:** -> This document is for AI agents and LLMs to follow when maintaining, -> generating, or refactoring PHP code in the WordPress ecosystem. - ---- - -## Overview - -These PHP coding standards are the official WordPress coding standards. They are mandatory for WordPress Core and recommended for all plugins and themes. Beyond code style, they encompass best practices for interoperability, translatability, and security. - -**When to apply:** Any PHP file in WordPress Core, plugins, or themes. - -**Reference:** [WordPress PHP Coding Standards](https://developer.wordpress.org/coding-standards/wordpress-coding-standards/php/) - ---- - -## Rule Categories by Priority - -### 1. Security & Database — **CRITICAL** - -Fundamental security practices that prevent vulnerabilities. - -| Rule | Impact | -| --------------------------------------------- | ---------------------- | -| [Database Queries](rules/database-queries.md) | Prevents SQL injection | - -### 2. Naming Conventions — **HIGH** - -Consistent naming ensures code readability and discoverability. - -| Rule | Impact | -| ------------------------------------------------- | ---------------- | -| [Naming Conventions](rules/naming-conventions.md) | Code consistency | - -### 3. Formatting — **HIGH** - -Structural formatting for readable and maintainable code. - -| Rule | Impact | -| --------------------------------- | -------------- | -| [Formatting](rules/formatting.md) | Code structure | - -### 4. Whitespace & Indentation — **MEDIUM** - -Spacing rules for visual consistency. - -| Rule | Impact | -| --------------------------------- | ----------- | -| [Whitespace](rules/whitespace.md) | Readability | - -### 5. Control Structures — **MEDIUM** - -Rules for conditionals and loops. - -| Rule | Impact | -| ------------------------------------------------- | -------------- | -| [Control Structures](rules/control-structures.md) | Bug prevention | - -### 6. Operators — **MEDIUM** - -Proper operator usage. - -| Rule | Impact | -| ------------------------------- | -------------- | -| [Operators](rules/operators.md) | Error handling | - -### 7. Best Practices — **MEDIUM** - -Recommendations for maintainable code. - -| Rule | Impact | -| ----------------------------------------- | --------------- | -| [Best Practices](rules/best-practices.md) | Maintainability | - -### 8. General Syntax — **LOW** - -Basic PHP syntax rules. - -| Rule | Impact | -| ----------------------------------------- | ------------- | -| [General Syntax](rules/general-syntax.md) | Compatibility | - -### 9. Object-Oriented Programming — **MEDIUM** - -OOP guidelines and patterns. - -| Rule | Impact | -| ------------------- | --------------- | -| [OOP](rules/oop.md) | OOP consistency | - -### 10. Namespaces & Imports — **MEDIUM** - -Modern PHP namespace and import conventions for plugins and themes. - -| Rule | Impact | -| --------------------------------------------------- | ----------------- | -| [Namespaces & Imports](rules/namespaces-imports.md) | Code organization | - -### 11. Shell Commands — **HIGH** - -Security considerations for shell command execution. - -| Rule | Impact | -| ----------------------------------------- | -------- | -| [Shell Commands](rules/shell-commands.md) | Security | - ---- - -## Usage - -### Load all rules - -``` -@wordpress-php-coding-standards -``` - -### Load specific category - -``` -@wordpress-php-coding-standards/rules/naming-conventions -@wordpress-php-coding-standards/rules/database-queries -``` - -### Full compiled guide - -See [AGENTS.md](AGENTS.md) for the complete reference. - ---- - -## Tooling - -Use the official [WordPress Coding Standards](https://github.com/WordPress/WordPress-Coding-Standards) with [PHP_CodeSniffer](https://github.com/PHPCSStandards/PHP_CodeSniffer/) to automatically check code compliance. - -```bash -composer require --dev wp-coding-standards/wpcs -./vendor/bin/phpcs --standard=WordPress path/to/file.php -``` diff --git a/.windsurf/skills/wordpress-php-coding-standards/rules/best-practices.md b/.windsurf/skills/wordpress-php-coding-standards/rules/best-practices.md deleted file mode 100644 index 61b10fe576..0000000000 --- a/.windsurf/skills/wordpress-php-coding-standards/rules/best-practices.md +++ /dev/null @@ -1,167 +0,0 @@ -# Best Practices - -**Priority: MEDIUM** -**Impact: Maintainability and debugging** - ---- - -## Self-Explanatory Flag Values - -Use descriptive strings instead of boolean flags. - -**Incorrect:** - -```php -function eat( $what, $slowly = true ) {} - -eat( 'mushrooms' ); -eat( 'mushrooms', true ); // What does true mean? -eat( 'dogfood', false ); // What does false mean? -``` - -**Correct:** - -```php -function eat( $what, $speed = 'slowly' ) {} - -eat( 'mushrooms' ); -eat( 'mushrooms', 'slowly' ); -eat( 'dogfood', 'quickly' ); -``` - -**For multiple options, use an array:** - -```php -function eat( $what, $args = array() ) {} - -eat( 'noodles', array( 'speed' => 'moderate' ) ); -``` - ---- - -## Avoid Clever Code - -Readability over cleverness. Use strict comparisons. No assignments in conditionals. - -**Incorrect:** - -```php -isset( $var ) || $var = some_function(); - -if ( $data = $wpdb->get_var( '...' ) ) { - // Use $data -} - -if ( 0 == strpos( 'WordPress', 'foo' ) ) {} // Loose comparison -``` - -**Correct:** - -```php -if ( ! isset( $var ) ) { - $var = some_function(); -} - -$data = $wpdb->get_var( '...' ); -if ( $data ) { - // Use $data -} - -if ( 0 === strpos( $text, 'WordPress' ) ) {} // Strict comparison -``` - -**Switch fall-through must be documented:** - -```php -switch ( $foo ) { - case 'bar': - // Empty case can fall through without comment - case 'baz': - echo esc_html( $foo ); - break; - - case 'dog': - echo 'horse'; - // no break - explicit comment required - case 'fish': - echo 'bird'; - break; -} -``` - -**Never use:** - -- `goto` statement -- `eval()` construct -- `create_function()` (deprecated/removed) - ---- - -## Closures - -Closures are allowed but should NOT be used as filter/action callbacks (difficult to remove). - -**Acceptable:** - -```php -$caption = preg_replace_callback( - '/<[a-zA-Z0-9]+(?: [^<>]+>)*/', - function ( $matches ) { - return preg_replace( '/[\r\n\t]+/', ' ', $matches[0] ); - }, - $caption -); -``` - -**Not recommended for hooks:** - -```php -// Hard to remove with remove_action() -add_action( 'init', function() { - // ... -} ); -``` - ---- - -## Don't Use extract() - -Never use `extract()`. It makes code harder to debug and understand. - -**Incorrect:** - -```php -extract( $args ); -echo $title; // Where did $title come from? -``` - -**Correct:** - -```php -$title = $args['title']; -echo $title; -``` - ---- - -## Regular Expressions - -Use PCRE (`preg_` functions). Never use `/e` modifier. Use single-quoted strings. - -**Incorrect:** - -```php -preg_replace( '/pattern/e', 'replacement', $subject ); // /e is deprecated -``` - -**Correct:** - -```php -preg_replace_callback( - '/pattern/', - function ( $matches ) { - return process( $matches[0] ); - }, - $subject -); -``` diff --git a/.windsurf/skills/wordpress-php-coding-standards/rules/control-structures.md b/.windsurf/skills/wordpress-php-coding-standards/rules/control-structures.md deleted file mode 100644 index 44efc2fd3a..0000000000 --- a/.windsurf/skills/wordpress-php-coding-standards/rules/control-structures.md +++ /dev/null @@ -1,56 +0,0 @@ -# Control Structures - -**Priority: MEDIUM** -**Impact: Bug prevention and syntax compatibility** - ---- - -## Yoda Conditions - -Put constants/literals on the left side of comparisons. - -**Incorrect:** - -```php -if ( $the_force == true ) { - // Accidental assignment possible: if ( $the_force = true ) -} -``` - -**Correct:** - -```php -if ( true === $the_force ) { - $victorious = you_will( $be ); -} -``` - -**Applies to:** `==`, `!=`, `===`, `!==` - -**Does not apply to:** `<`, `>`, `<=`, `>=` (too difficult to read) - ---- - -## Use elseif - -Use `elseif`, not `else if`. Required for alternative syntax compatibility. - -**Incorrect:** - -```php -if ( condition ) { - // ... -} else if ( condition2 ) { - // ... -} -``` - -**Correct:** - -```php -if ( condition ) { - // ... -} elseif ( condition2 ) { - // ... -} -``` diff --git a/.windsurf/skills/wordpress-php-coding-standards/rules/database-queries.md b/.windsurf/skills/wordpress-php-coding-standards/rules/database-queries.md deleted file mode 100644 index 1d40449fd3..0000000000 --- a/.windsurf/skills/wordpress-php-coding-standards/rules/database-queries.md +++ /dev/null @@ -1,70 +0,0 @@ -# Database Queries - -**Priority: CRITICAL** -**Impact: Prevents SQL injection** - ---- - -## Overview - -Database interactions must be secure and properly escaped to prevent SQL injection. Avoid touching the database directly—use WordPress functions when available. If you must write queries, always use `$wpdb->prepare()`. - ---- - -## Rules - -### Always Use $wpdb->prepare() - -**Incorrect (direct query with unescaped data):** - -```php -$wpdb->query( "UPDATE $wpdb->posts SET post_title = '$var' WHERE ID = $id" ); -``` - -**Correct (using $wpdb->prepare()):** - -```php -$var = "dangerous'"; -$id = some_foo_number(); - -$wpdb->query( - $wpdb->prepare( - "UPDATE $wpdb->posts SET post_title = %s WHERE ID = %d", - $var, - $id - ) -); -``` - ---- - -## Placeholders - -- `%d` — integer -- `%f` — float -- `%s` — string -- `%i` — identifier (table/field names) - -**Important:** Do not quote placeholders! `$wpdb->prepare()` handles escaping and quoting. - ---- - -## SQL Formatting - -Capitalize SQL keywords. Break complex statements into multiple lines. - -**Correct:** - -```php -$wpdb->query( - $wpdb->prepare( - "SELECT ID, post_title - FROM $wpdb->posts - WHERE post_status = %s - AND post_type = %s - ORDER BY post_date DESC", - 'publish', - 'post' - ) -); -``` diff --git a/.windsurf/skills/wordpress-php-coding-standards/rules/formatting.md b/.windsurf/skills/wordpress-php-coding-standards/rules/formatting.md deleted file mode 100644 index 3f333151d6..0000000000 --- a/.windsurf/skills/wordpress-php-coding-standards/rules/formatting.md +++ /dev/null @@ -1,137 +0,0 @@ -# Formatting - -**Priority: HIGH** -**Impact: Code structure and maintainability** - ---- - -## Brace Style - -Always use braces, even for single statements. Opening brace on same line. - -**Incorrect:** - -```php -if ( condition ) - action(); - -if ( condition ) action(); -``` - -**Correct:** - -```php -if ( condition ) { - action1(); - action2(); -} elseif ( condition2 && condition3 ) { - action3(); -} else { - default_action(); -} -``` - -**Alternative syntax for templates:** - -```php - -
- -
- -
- -
- -``` - ---- - -## Array Syntax - -Always use long array syntax `array()`, not short syntax `[]`. - -**Incorrect:** - -```php -$array = [ 1, 2, 3 ]; -$args = [ - 'post_type' => 'page', -]; -``` - -**Correct:** - -```php -$array = array( 1, 2, 3 ); -$args = array( - 'post_type' => 'page', - 'post_author' => 123, - 'post_status' => 'publish', -); -``` - -**Note:** Include trailing comma after the last item for cleaner diffs. - ---- - -## Multiline Function Calls - -Each parameter on its own line. Assign complex values to variables first. - -**Incorrect:** - -```php -$a = foo( array( 'use_this' => true, 'meta_key' => 'field_name' ), sprintf( __( 'Hello, %s!', 'textdomain' ), $friend_name ) ); -``` - -**Correct:** - -```php -$bar = array( - 'use_this' => true, - 'meta_key' => 'field_name', -); -$baz = sprintf( - /* translators: %s: Friend's name */ - __( 'Hello, %s!', 'yourtextdomain' ), - $friend_name -); - -$a = foo( - $bar, - $baz, - /* translators: %s: cat */ - sprintf( __( 'The best pet is a %s.' ), 'cat' ) -); -``` - ---- - -## Type Declarations - -One space before and after type. No space between nullability operator and type. - -**Incorrect:** - -```php -function baz(Class_Name $param_a, String$param_b, CALLABLE $param_c ) : ? iterable { - // Do something. -} -``` - -**Correct:** - -```php -function foo( Class_Name $parameter, callable $callable, int $number_of_things = 0 ) { - // Do something. -} - -function bar( - Interface_Name&Concrete_Class $param_a, - string|int $param_b, - callable $param_c = 'default_callable' -): User|false { - // Do something. -} -``` diff --git a/.windsurf/skills/wordpress-php-coding-standards/rules/general-syntax.md b/.windsurf/skills/wordpress-php-coding-standards/rules/general-syntax.md deleted file mode 100644 index b277f441cb..0000000000 --- a/.windsurf/skills/wordpress-php-coding-standards/rules/general-syntax.md +++ /dev/null @@ -1,76 +0,0 @@ -# General Syntax - -**Priority: LOW** -**Impact: Compatibility and string handling** - ---- - -## PHP Tags - -Always use full PHP tags. Never use shorthand. - -**Incorrect:** - -```php - - -``` - -**Correct:** - -```php - - -``` - -**Multiline PHP in HTML (tags on their own lines):** - -```php - - - -``` - ---- - -## Quotes - -Use single quotes when not evaluating variables. Alternate quote styles to avoid escaping. - -**Correct:** - -```php -echo 'Link name'; -echo "text with a ' single quote"; -``` - -**Always escape output in HTML attributes:** - -```php -echo '' . esc_html( $text ) . ''; -``` - ---- - -## Require/Include - -No parentheses. One space after keyword. Prefer `require_once` over `include_once`. - -**Incorrect:** - -```php -include_once( ABSPATH . 'file-name.php' ); -require_once ABSPATH . 'file-name.php'; // Extra space -``` - -**Correct:** - -```php -require_once ABSPATH . 'file-name.php'; -``` - -**Why `require_once`:** If file not found, `require` throws Fatal Error (stops execution). `include` only warns and continues, potentially causing security issues. diff --git a/.windsurf/skills/wordpress-php-coding-standards/rules/namespaces-imports.md b/.windsurf/skills/wordpress-php-coding-standards/rules/namespaces-imports.md deleted file mode 100644 index 92d7862777..0000000000 --- a/.windsurf/skills/wordpress-php-coding-standards/rules/namespaces-imports.md +++ /dev/null @@ -1,148 +0,0 @@ -# Namespaces & Imports - -**Impact: MEDIUM** - -Modern PHP namespace and import conventions for WordPress plugins and themes. - -## Namespace Declarations - -**Impact: MEDIUM (organization)** - -Each part of a namespace name should consist of capitalized words separated by underscores. - -**Incorrect:** - -```php -namespace prefix\admin\domainUrl\subDomain; // camelCase not allowed -namespace Foo { - // Code - curly brace syntax not allowed -} -namespace { - // Global namespace declaration not allowed -} -``` - -**Correct:** - -```php -namespace Prefix\Admin\Domain_URL\Sub_Domain\Event; -``` - -**Rules:** -- One blank line before the declaration, at least one blank line after -- Only one namespace declaration per file, at the top -- No curly brace syntax -- No global namespace declarations -- Use unique, long prefixes like `Vendor\Project_Name` to prevent conflicts -- Do NOT use `wp` or `WordPress` as namespace prefixes - -**Note:** Namespaces are encouraged for plugins/themes but not yet used in WordPress Core. - ---- - -## Import Use Statements - -**Impact: MEDIUM (code organization)** - -Import `use` statements should be at the top of the file after the namespace declaration. - -**Order of imports:** -1. Namespaces, classes, interfaces, traits, enums -2. Functions -3. Constants - -**Incorrect:** - -```php -namespace Project_Name\Feature; - -use const Project_Name\Sub_Feature\CONSTANT_A; // Constants before classes -use function Project_Name\Sub_Feature\function_a; // Functions before classes -use \Project_Name\Sub_Feature\Class_C as aliased_class_c; // Leading backslash, wrong alias naming - -class Foo { - // Code. -} - -use Project_Name\Another_Class; // Import after class definition - not allowed -``` - -**Correct:** - -```php -namespace Project_Name\Feature; - -use Project_Name\Sub_Feature\Class_A; -use Project_Name\Sub_Feature\Class_C as Aliased_Class_C; -use Project_Name\Sub_Feature\{ - Class_D, - Class_E as Aliased_Class_E, -} - -use function Project_Name\Sub_Feature\function_a; -use function Project_Name\Sub_Feature\function_b as aliased_function; - -use const Project_Name\Sub_Feature\CONSTANT_A; -use const Project_Name\Sub_Feature\CONSTANT_D as ALIASED_CONSTANT; - -// Rest of the code. -``` - -**Rules:** -- No leading backslash in imports -- Aliases must follow WordPress naming conventions (capitalized words with underscores for classes, lowercase with underscores for functions) -- Don't combine different import types in one statement -- All imports before any class/function definitions - -**Note:** Import `use` statements are discouraged in WordPress Core for now. - ---- - -## Trait Use Statements - -**Impact: MEDIUM (OOP organization)** - -Trait `use` statements should be at the top of a class with proper spacing. - -**Incorrect:** - -```php -class Foo { - // No blank line before trait use statement - use Bar_Trait; - - use Foo_Trait, Bazinga_Trait{Bar_Trait::method_name insteadof Foo_Trait; // Wrong formatting - Bazinga_Trait::method_name as bazinga_method; - }; - - public $baz = true; // Missing blank line after trait import -} -``` - -**Correct:** - -```php -class Foo { - - use Bar_Trait; - - use Foo_Trait, Bazinga_Trait { - Bar_Trait::method_name insteadof Foo_Trait; - Bazinga_Trait::method_name as bazinga_method; - } - - use Loopy_Trait { - eat as protected; - } - - public $baz = true; - - // Rest of class... -} -``` - -**Rules:** -- One blank line before the first `use` statement -- At least one blank line after the last `use` statement (exception: if class only contains trait imports) -- Each aliasing/conflict resolution on its own line -- Proper indentation inside curly braces diff --git a/.windsurf/skills/wordpress-php-coding-standards/rules/naming-conventions.md b/.windsurf/skills/wordpress-php-coding-standards/rules/naming-conventions.md deleted file mode 100644 index 5df6e7189e..0000000000 --- a/.windsurf/skills/wordpress-php-coding-standards/rules/naming-conventions.md +++ /dev/null @@ -1,118 +0,0 @@ -# Naming Conventions - -**Priority: HIGH** -**Impact: Code consistency and readability** - ---- - -## Variables and Functions - -Use lowercase letters with underscores. Never use camelCase. Don't abbreviate unnecessarily. - -**Incorrect:** - -```php -function someFunction( $someVariable ) {} -function getData( $usrId ) {} -``` - -**Correct:** - -```php -function some_function( $some_variable ) {} -function get_data( $user_id ) {} -``` - ---- - -## Classes, Interfaces, Traits, Enums - -Use capitalized words separated by underscores. Acronyms should be all uppercase. - -**Incorrect:** - -```php -class walkerCategory extends Walker {} -class WpHttp {} -``` - -**Correct:** - -```php -class Walker_Category extends Walker {} -class WP_HTTP {} -interface Mailer_Interface {} -trait Forbid_Dynamic_Properties {} -enum Post_Status {} -``` - ---- - -## Constants - -All uppercase with underscores separating words. - -**Correct:** - -```php -define( 'DOING_AJAX', true ); -const MAX_UPLOAD_SIZE = 1048576; -``` - ---- - -## File Naming - -Use lowercase letters with hyphens separating words. - -**General files:** - -```text -my-plugin-name.php -template-parts.php -``` - -**Class files (prefix with `class-`):** - -```text -class-wp-error.php // For WP_Error class -class-walker-category.php // For Walker_Category class -``` - -**Template tags (suffix with `-template`):** - -```text -general-template.php -``` - ---- - -## Dynamic Hooks - -Use interpolation with curly braces, not concatenation. Wrap in double quotes. - -**Incorrect:** - -```php -do_action( $new_status . '_' . $post->post_type, $post->ID, $post ); -``` - -**Correct:** - -```php -do_action( "{$new_status}_{$post->post_type}", $post->ID, $post ); -``` - -Use descriptive variable names in hooks: - -**Incorrect:** - -```php -do_action( "save_post_{$this->id}", $data ); -``` - -**Correct:** - -```php -do_action( "save_post_{$post_id}", $data ); -``` diff --git a/.windsurf/skills/wordpress-php-coding-standards/rules/oop.md b/.windsurf/skills/wordpress-php-coding-standards/rules/oop.md deleted file mode 100644 index 4665e4b5ec..0000000000 --- a/.windsurf/skills/wordpress-php-coding-standards/rules/oop.md +++ /dev/null @@ -1,97 +0,0 @@ -# Object-Oriented Programming - -**Priority: MEDIUM** -**Impact: OOP consistency and maintainability** - ---- - -## One Structure Per File - -Each class, interface, trait, or enum should be in its own file. - ---- - -## Visibility - -Always declare visibility (`public`, `protected`, `private`). - -**Incorrect:** - -```php -class Foo { - var $bar; // Old syntax - function baz() {} // No visibility -} -``` - -**Correct:** - -```php -class Foo { - public $bar; - - public function baz() {} - - protected function qux() {} - - private function quux() {} -} -``` - ---- - -## Modifier Order - -```php -final public static function foo() {} -abstract protected function bar(); -``` - ---- - -## Object Instantiation - -Always use parentheses, even without arguments. - -**Incorrect:** - -```php -$obj = new Foo; -``` - -**Correct:** - -```php -$obj = new Foo(); -$obj = new Foo( $arg ); -``` - ---- - -## Magic Constants - -Use uppercase for magic constants. - -**Correct:** - -```php -__DIR__ -__FILE__ -__CLASS__ -__FUNCTION__ -``` - ---- - -## Spread Operator - -Space after `...` when used to spread. No space when collecting. - -```php -// Spreading -function_call( ...$array ); -$merged = array( ...$array1, ...$array2 ); - -// Collecting (variadic) -function variadic_function( ...$args ) {} -``` diff --git a/.windsurf/skills/wordpress-php-coding-standards/rules/operators.md b/.windsurf/skills/wordpress-php-coding-standards/rules/operators.md deleted file mode 100644 index edcbef7be1..0000000000 --- a/.windsurf/skills/wordpress-php-coding-standards/rules/operators.md +++ /dev/null @@ -1,64 +0,0 @@ -# Operators - -**Priority: MEDIUM** -**Impact: Readability and error handling** - ---- - -## Ternary Operator - -Test for true, not false (except with `! empty()`). Don't use short ternary. - -**Incorrect:** - -```php -$value = $condition ?: 'default'; // Short ternary - not allowed -$type = ( ! $is_valid ) ? 'invalid' : 'valid'; // Testing for false -``` - -**Correct:** - -```php -$musictype = ( 'jazz' === $music ) ? 'cool' : 'blah'; -$value = ( ! empty( $field ) ) ? $field : 'default'; -``` - ---- - -## Increment/Decrement Operators - -Prefer pre-increment/decrement for standalone statements. - -**Incorrect:** - -```php -$a--; -$count++; -``` - -**Correct:** - -```php ---$a; -++$count; -``` - ---- - -## Error Control Operator - -Don't use `@` to suppress errors. Do proper error checking instead. - -**Incorrect:** - -```php -$value = @file_get_contents( $file ); -``` - -**Correct:** - -```php -if ( file_exists( $file ) && is_readable( $file ) ) { - $value = file_get_contents( $file ); -} -``` diff --git a/.windsurf/skills/wordpress-php-coding-standards/rules/shell-commands.md b/.windsurf/skills/wordpress-php-coding-standards/rules/shell-commands.md deleted file mode 100644 index 4b8e1e91ff..0000000000 --- a/.windsurf/skills/wordpress-php-coding-standards/rules/shell-commands.md +++ /dev/null @@ -1,23 +0,0 @@ -# Shell Commands - -**Impact: HIGH (security)** - -Use of shell commands requires caution due to security implications. - -## Never Use the Backtick Operator - -**Incorrect:** - -```php -// Backtick operator is identical to shell_exec() -$output = `ls -la`; -``` - -**Why:** The backtick operator is equivalent to `shell_exec()`, and most hosts disable this function in `php.ini` for security reasons. It can lead to command injection vulnerabilities if user input is involved. - -**If shell commands are absolutely necessary:** -- Use `escapeshellarg()` and `escapeshellcmd()` for any user-provided input -- Prefer WordPress functions that handle this safely when available -- Document why shell access is needed - -**Security Note:** Shell command execution should be avoided whenever possible. Always look for safer alternatives using built-in PHP functions or WordPress APIs. diff --git a/.windsurf/skills/wordpress-php-coding-standards/rules/whitespace.md b/.windsurf/skills/wordpress-php-coding-standards/rules/whitespace.md deleted file mode 100644 index 8b0d5f066c..0000000000 --- a/.windsurf/skills/wordpress-php-coding-standards/rules/whitespace.md +++ /dev/null @@ -1,117 +0,0 @@ -# Whitespace - -**Priority: MEDIUM** -**Impact: Readability and visual consistency** - ---- - -## Space Usage - -Spaces after commas and around operators. Spaces inside parentheses of control structures. - -**Operators:** - -```php -SOME_CONST === 23; -foo() && bar(); -! $foo; -array( 1, 2, 3 ); -$baz . '-5'; -$term .= 'X'; -$result = 2 ** 3; -``` - -**Control structures:** - -```php -foreach ( $foo as $bar ) { - // ... -} - -if ( $foo && ( $bar || $baz ) ) { - // ... -} -``` - -**Functions:** - -```php -function my_function( $param1 = 'foo', $param2 = 'bar' ) { - // ... -} - -my_function( $param1, func_param( $param2 ) ); -``` - -**Array access (space only around variables):** - -```php -$x = $foo['bar']; // Correct - no space for literal -$x = $foo[0]; // Correct - no space for number -$x = $foo[ $bar ]; // Correct - space for variable -``` - -**Type casts (lowercase, short form):** - -```php -$foo = (bool) $bar; // Correct -$foo = (int) $value; // Correct - -$foo = (boolean) $bar; // Incorrect - use (bool) -$foo = (integer) $value; // Incorrect - use (int) -``` - -**Increment/decrement (no space):** - -```php -for ( $i = 0; $i < 10; $i++ ) {} -++$b; -``` - ---- - -## Indentation - -Use real tabs, not spaces. Spaces may be used mid-line for alignment. - -**Correct:** - -```php -$foo = 'somevalue'; -$foo2 = 'somevalue2'; -$foo34 = 'somevalue3'; -``` - -**Associative arrays (one item per line when more than one):** - -```php -// Single item - can be one line -$query = new WP_Query( array( 'ID' => 123 ) ); - -// Multiple items - each on own line -$args = array( - 'post_type' => 'page', - 'post_author' => 123, - 'post_status' => 'publish', -); -``` - -**Switch statements:** - -```php -switch ( $type ) { - case 'foo': - some_function(); - break; - - case 'bar': - some_function(); - break; -} -``` - ---- - -## Trailing Spaces - -Remove trailing whitespace at end of lines. Omit closing PHP tag at end of file (preferred). No trailing blank lines at end of function body. diff --git a/.windsurf/workflows/fix-bug.md b/.windsurf/workflows/fix-bug.md deleted file mode 100644 index 9c91d15258..0000000000 --- a/.windsurf/workflows/fix-bug.md +++ /dev/null @@ -1,169 +0,0 @@ ---- -description: Enterprise bug-fixing workflow for Formidable Forms following WordPress VIP standards ---- - -# Fix Bug Workflow - -## Phase 1: Understand the Bug - -- **Clarify the issue:** - - What is the expected behavior? - - What is the actual behavior? - - What are the exact steps to reproduce? - - Does the user have sample data, screenshots, or logs? - -- **Determine scope:** - - Does this affect Lite, Pro, or both? - - Which add-ons are involved (Views, Surveys, Signature, etc.)? - - Is this a regression or a long-standing issue? - -## Phase 2: Locate the Problem - -- **Search for the root cause:** - -```text -Use code_search to find relevant code based on the bug description. -``` - -- **Map the execution flow:** - - Trace the code path from trigger to symptom - - Identify ALL files and methods involved - - Document: what calls this code? What does this code call? - -- **Add diagnostic logging** (if flow is unclear): - - Add temporary `error_log()` statements at key decision points - - Include variable values, method names, and timestamps - - Use unique prefixes like `[FRM_DEBUG_BUGNAME]` for easy grep - -- **Identify the root cause:** - - Find the EXACT line/condition where behavior diverges - - Understand WHY it fails (not just WHERE) - - Document the root cause before proceeding - -## Phase 3: Find the Right Pattern and Location - -Follow the **Pattern Discovery Process** from formidable-core rules: - -- **Step 1: Find existing patterns** - Search models, controllers, helpers for similar functionality -- **Step 2: Study pattern usage** - Search ALL places that use the pattern to understand best usage -- **Step 3: Trace parent hierarchy** - Search parent files up to plugin root to understand logic -- **Step 4: Iterate** - If better pattern/location found, repeat from Step 1 - -- **Research WordPress best practices:** - -```text -@web WordPress [relevant_function] developer documentation -@web WordPress VIP [topic] best practices -``` - -## Phase 4: Design the Solution - -- **Propose 2-3 solutions** with clear trade-offs: - -| Solution | Description | Pros | Cons | -| -------- | -------------------- | ---------- | ------- | -| A | [Fix at location X] | [Benefits] | [Risks] | -| B | [Fix at location Y] | [Benefits] | [Risks] | -| C | [Different approach] | [Benefits] | [Risks] | - -- **Select the best solution based on:** - - Minimal scope (smallest change that solves the problem) - - Closest to root cause (not downstream workaround) - - Follows existing Formidable patterns - - Maintains backward compatibility - - Works with Pro active AND inactive - -- **Verify not overkill:** If changes affect several areas, analyze all related places to ensure the flow and behavior are correct and the fix is not excessive. - -- **Get user approval** before implementing if: - - Multiple valid approaches exist - - Solution requires significant changes - - Trade-offs are not obvious - -## Phase 5: Implement the Fix - -- **Follow Formidable coding standards:** - - Use `elseif` not `else if` - - Use strict comparisons (`===`, `!==`) - - Use `in_array()` with third parameter `true` - - Tabs for indentation - - Max 180 character line length - -- **Follow WordPress VIP standards:** - - ALL user input must be sanitized - - ALL output must be escaped - - ALL database queries must use `$wpdb->prepare()` - - NEVER use `extract()`, `eval()`, or `create_function()` - -- **Make the minimal fix:** - - Change ONLY what is necessary - - Do NOT refactor unrelated code - - Do NOT change method signatures unless required - - Add defensive checks where data enters, not everywhere - -- **Add PHPDoc if adding new methods:** - -```php -/** - * Brief description ending with period. - * - * @since x.x - * - * @param type $param Description. - * @return type Description. - */ -``` - -## Phase 6: Clean Up - -- **Remove all debug code:** - - Remove ALL `error_log()` statements - - Remove ALL temporary comments - - Ensure no debug files were created - -- **Verify code style:** - -```bash -vendor/bin/phpcs --standard=phpcs.xml [changed_files] -``` - -## Phase 7: Test the Fix - -- **Create or update tests:** - -```bash -vendor/bin/phpunit tests/phpunit/[relevant_test_file].php -``` - -- **Manual verification checklist:** - - [ ] Bug is fixed in reported scenario - - [ ] No PHP warnings or notices - - [ ] Works when Pro plugin is ACTIVE - - [ ] Works when Pro plugin is INACTIVE - - [ ] Does not break existing functionality - - [ ] Backward compatible with existing data - -## Phase 8: Report Completion - -- **Summarize the fix:** - - **Root cause:** [One sentence explaining why the bug occurred] - - **Solution:** [One sentence explaining the fix] - - **Files changed:** [List of modified files] - - **Testing done:** [What was verified] - - **Remaining concerns:** [Any edge cases or follow-ups] - ---- - - - -- NEVER guess - always search and verify before making changes -- NEVER invent custom solutions - use existing Formidable patterns -- Fix at the MOST SPECIFIC location, closest to the problem -- Iterate through Pattern Discovery until best pattern AND location found -- Verify changes are not overkill if affecting multiple areas -- Maintain 100% backward compatibility -- Code must work with AND without Pro active -- Remove ALL debug code before completion -- If user makes manual changes after your updates, those are final - - From 2ff7a0711c5b8408cd67dc1336f549483873bf91 Mon Sep 17 00:00:00 2001 From: Sherv Date: Tue, 10 Feb 2026 17:11:40 +0300 Subject: [PATCH 36/90] feat(windsurf): add enterprise bug-fixing skill with structured workflow for Formidable Forms debugging Add comprehensive fix-bug skill covering 8-phase workflow (Understand, Locate, Pattern, Design, Implement, Clean, Test, Report), verification checklist with pre-implementation checks, solution selection criteria, implementation standards, testing requirements, cleanup tasks, solution comparison template for proposing 2-3 fixes with trade-offs, and completion report template with root cause analysis, --- .windsurf/skills/fix-bug/SKILL.md | 39 +++++++++ .windsurf/skills/fix-bug/checklist.md | 52 ++++++++++++ .windsurf/skills/fix-bug/report-template.md | 56 +++++++++++++ .windsurf/skills/fix-bug/solution-template.md | 80 +++++++++++++++++++ 4 files changed, 227 insertions(+) create mode 100644 .windsurf/skills/fix-bug/SKILL.md create mode 100644 .windsurf/skills/fix-bug/checklist.md create mode 100644 .windsurf/skills/fix-bug/report-template.md create mode 100644 .windsurf/skills/fix-bug/solution-template.md diff --git a/.windsurf/skills/fix-bug/SKILL.md b/.windsurf/skills/fix-bug/SKILL.md new file mode 100644 index 0000000000..68bb7a688b --- /dev/null +++ b/.windsurf/skills/fix-bug/SKILL.md @@ -0,0 +1,39 @@ +--- +name: fix-bug +description: Enterprise bug-fixing workflow for Formidable Forms following WordPress VIP standards. Use when fixing bugs or debugging issues. +--- + +# Fix Bug Skill + +Enterprise bug-fixing workflow for Formidable Forms following WordPress VIP standards. + +## When to Use + +- Fixing reported bugs +- Debugging unexpected behavior +- Investigating error logs +- Resolving compatibility issues + +## Workflow Phases + +1. **Understand** - Clarify issue, expected vs actual behavior, reproduction steps +2. **Locate** - Find root cause using code_search, trace execution flow +3. **Pattern** - Find existing patterns, research WordPress best practices +4. **Design** - Propose 2-3 solutions with trade-offs +5. **Implement** - Apply minimal fix following coding standards +6. **Clean** - Remove debug code, verify code style +7. **Test** - Verify fix, check Pro active/inactive scenarios +8. **Report** - Summarize root cause, solution, and files changed + +## Supporting Resources + +- [checklist.md](checklist.md) - Verification checklist +- [solution-template.md](solution-template.md) - Solution comparison template +- [report-template.md](report-template.md) - Completion report template + +## Invocation + +```text +@fix-bug +/fix-bug +``` diff --git a/.windsurf/skills/fix-bug/checklist.md b/.windsurf/skills/fix-bug/checklist.md new file mode 100644 index 0000000000..4d70e4b762 --- /dev/null +++ b/.windsurf/skills/fix-bug/checklist.md @@ -0,0 +1,52 @@ +# Bug Fix Verification Checklist + +## Before Implementation + +- [ ] Understood the complete issue +- [ ] Identified root cause (not just symptoms) +- [ ] Mapped all affected locations +- [ ] Checked Pro plugin requirement +- [ ] Found existing patterns for the fix +- [ ] Searched WordPress docs for best practices + +## Solution Selection + +- [ ] Proposed 2-3 solutions +- [ ] Documented trade-offs for each +- [ ] Selected minimal scope solution +- [ ] Verified fix is at most specific location +- [ ] Confirmed not overkill if affecting multiple areas + +## Implementation + +- [ ] Follows WordPress PHP coding standards +- [ ] Follows Formidable naming patterns +- [ ] Uses existing helper methods +- [ ] All input sanitized +- [ ] All output escaped +- [ ] Database queries use $wpdb->prepare() +- [ ] No forbidden functions (extract, eval, etc.) + +## Testing + +- [ ] Bug is fixed in reported scenario +- [ ] Works when Pro is ACTIVE +- [ ] Works when Pro is INACTIVE +- [ ] No PHP warnings or notices +- [ ] Empty data handled correctly +- [ ] Edge cases tested +- [ ] Backward compatible + +## Cleanup + +- [ ] Removed ALL error_log() statements +- [ ] Removed ALL debug comments +- [ ] No debug files created +- [ ] Code passes phpcs check + +## Documentation + +- [ ] PHPDoc added for new methods +- [ ] Complex logic has comments +- [ ] Root cause documented +- [ ] Solution documented diff --git a/.windsurf/skills/fix-bug/report-template.md b/.windsurf/skills/fix-bug/report-template.md new file mode 100644 index 0000000000..051a050580 --- /dev/null +++ b/.windsurf/skills/fix-bug/report-template.md @@ -0,0 +1,56 @@ +# Bug Fix Report + +## Summary + +**Issue:** [Brief description] + +**Status:** Fixed / Partially Fixed / Needs More Info + +--- + +## Root Cause + +[One paragraph explaining why the bug occurred] + +--- + +## Solution + +[One paragraph explaining the fix] + +--- + +## Files Changed + +| File | Change | +|------|--------| +| `path/to/file1.php` | [Brief description] | +| `path/to/file2.php` | [Brief description] | + +--- + +## Testing Done + +- [ ] Bug is fixed in reported scenario +- [ ] Works when Pro is ACTIVE +- [ ] Works when Pro is INACTIVE +- [ ] No PHP warnings or notices +- [ ] Backward compatible + +--- + +## Remaining Concerns + +[Any edge cases, follow-up items, or things to monitor] + +--- + +## Commit Message + +``` +fix(scope): brief description + +Detailed explanation of what was fixed and why. + +Fixes #ISSUE_NUMBER +``` diff --git a/.windsurf/skills/fix-bug/solution-template.md b/.windsurf/skills/fix-bug/solution-template.md new file mode 100644 index 0000000000..9b21ed7ed2 --- /dev/null +++ b/.windsurf/skills/fix-bug/solution-template.md @@ -0,0 +1,80 @@ +# Solution Comparison Template + +## Problem Summary + +**Issue:** [Brief description of the bug] + +**Expected:** [What should happen] + +**Actual:** [What is happening] + +**Root Cause:** [Why this is happening] + +--- + +## Proposed Solutions + +### Solution A: [Name] + +**Location:** `path/to/file.php` + +**Change:** +```php +// Proposed code change +``` + +| Pros | Cons | +|------|------| +| [Benefit 1] | [Drawback 1] | +| [Benefit 2] | [Drawback 2] | + +**Risk Level:** Low / Medium / High + +--- + +### Solution B: [Name] + +**Location:** `path/to/file.php` + +**Change:** +```php +// Proposed code change +``` + +| Pros | Cons | +|------|------| +| [Benefit 1] | [Drawback 1] | +| [Benefit 2] | [Drawback 2] | + +**Risk Level:** Low / Medium / High + +--- + +### Solution C: [Name] (if applicable) + +**Location:** `path/to/file.php` + +**Change:** +```php +// Proposed code change +``` + +| Pros | Cons | +|------|------| +| [Benefit 1] | [Drawback 1] | +| [Benefit 2] | [Drawback 2] | + +**Risk Level:** Low / Medium / High + +--- + +## Recommendation + +**Selected Solution:** [A/B/C] + +**Rationale:** +- Minimal scope +- Closest to root cause +- Follows existing patterns +- Maintains backward compatibility +- Works with Pro active AND inactive From 5de9ff1ace88c32def92f0d0943be2548af977e4 Mon Sep 17 00:00:00 2001 From: Sherv Date: Wed, 11 Feb 2026 13:35:31 +0300 Subject: [PATCH 37/90] style(windsurf): fix table column alignment in conventional commits documentation --- .../rules/enterprise/conventional-commits.md | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.windsurf/rules/enterprise/conventional-commits.md b/.windsurf/rules/enterprise/conventional-commits.md index 3e2d42be42..62c07ab057 100644 --- a/.windsurf/rules/enterprise/conventional-commits.md +++ b/.windsurf/rules/enterprise/conventional-commits.md @@ -19,18 +19,18 @@ All commit messages MUST follow the Conventional Commits 1.0.0 specification. ## Types -| Type | Description | SemVer | -| ---------- | ---------------------------------------------------- | ------ | -| `fix` | Bug fix (patches a bug in the codebase) | PATCH | -| `feat` | New feature (adds functionality to the codebase) | MINOR | -| `docs` | Documentation only changes | - | -| `style` | Code style changes (formatting, whitespace, etc.) | - | -| `refactor` | Code change that neither fixes a bug nor adds feature| - | -| `perf` | Performance improvement | - | -| `test` | Adding or correcting tests | - | -| `build` | Changes to build system or external dependencies | - | -| `ci` | CI configuration changes | - | -| `chore` | Other changes that don't modify src or test files | - | +| Type | Description | SemVer | +| ---------- | ----------------------------------------------------- | ------ | +| `fix` | Bug fix (patches a bug in the codebase) | PATCH | +| `feat` | New feature (adds functionality to the codebase) | MINOR | +| `docs` | Documentation only changes | - | +| `style` | Code style changes (formatting, whitespace, etc.) | - | +| `refactor` | Code change that neither fixes a bug nor adds feature | - | +| `perf` | Performance improvement | - | +| `test` | Adding or correcting tests | - | +| `build` | Changes to build system or external dependencies | - | +| `ci` | CI configuration changes | - | +| `chore` | Other changes that don't modify src or test files | - | ## Scope (Optional) From 26975ec270d7a9333e2fb2ab49c9b4720c0c3a7e Mon Sep 17 00:00:00 2001 From: Sherv Date: Wed, 11 Feb 2026 13:46:02 +0300 Subject: [PATCH 38/90] refactor(windsurf): expand custom solutions principle to include web research and official standards verification --- .windsurf/rules/enterprise/principles.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.windsurf/rules/enterprise/principles.md b/.windsurf/rules/enterprise/principles.md index a25fb5619a..76f9f27349 100644 --- a/.windsurf/rules/enterprise/principles.md +++ b/.windsurf/rules/enterprise/principles.md @@ -14,7 +14,7 @@ Critical principles for enterprise-level plugin development. 1. **NEVER guess**: Always search and verify before making changes 2. **Minimal scope**: Fix at the most specific location, closest to the problem 3. **Backward compatibility**: Maintain 100% compatibility with existing callers -4. **No custom solutions**: Never invent new patterns, use existing ones +4. **No custom solutions**: Never invent new patterns. Use existing ones or search the web to review best practices, then follow the official WordPress standards or VIP guidelines. 5. **User changes are final**: If user makes manual changes, treat as authoritative --- From 614fdd3f99ef4fd78268f994e28516845605fa52 Mon Sep 17 00:00:00 2001 From: Sherv Date: Wed, 11 Feb 2026 15:58:41 +0300 Subject: [PATCH 39/90] feat(windsurf): add Fast Context requirement to enterprise analysis phase for codebase awareness Add code_search tool requirement as step 2 in analysis phase to ensure comprehensive understanding of all relevant code locations, dependencies, and related functionality before proposing solutions. Renumber subsequent steps (identify affected locations, map dependencies, check plugin requirements) to steps 3-5. --- .windsurf/rules/enterprise/principles.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.windsurf/rules/enterprise/principles.md b/.windsurf/rules/enterprise/principles.md index 76f9f27349..4a0176ccf4 100644 --- a/.windsurf/rules/enterprise/principles.md +++ b/.windsurf/rules/enterprise/principles.md @@ -24,9 +24,10 @@ Critical principles for enterprise-level plugin development. Before proposing solutions: 1. **Read and understand** the complete issue -2. **Identify ALL affected locations** in the codebase -3. **Map dependencies**: What calls this code? What does this code call? -4. **Check plugin requirements**: Must code work standalone or require dependencies? +2. **Use Fast Context for codebase awareness**: Use the code_search tool to understand all relevant code locations, dependencies, and related functionality before making changes +3. **Identify ALL affected locations** in the codebase +4. **Map dependencies**: What calls this code? What does this code call? +5. **Check plugin requirements**: Must code work standalone or require dependencies? --- From d93c524cd1a3fbeda801436b1ae5d9f4306386d1 Mon Sep 17 00:00:00 2001 From: Sherv Date: Wed, 11 Feb 2026 16:09:27 +0300 Subject: [PATCH 40/90] feat(windsurf): add WCAG 2.2 Level AA conformance requirement and simplify skip link implementation in accessibility standards Add mandatory WCAG 2.2 Level AA conformance statement for WordPress ecosystem code. Simplify skip links documentation by removing custom CSS in favor of WordPress Core's `.screen-reader-text` class which handles both visually hidden state and visible-on-focus behavior. Update skip link example with cleaner HTML structure and reference to WordPress Specific Practices section --- .windsurf/rules/wordpress/accessibility.md | 30 +++++++++------------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/.windsurf/rules/wordpress/accessibility.md b/.windsurf/rules/wordpress/accessibility.md index f6d253ff8f..bfc31670b8 100644 --- a/.windsurf/rules/wordpress/accessibility.md +++ b/.windsurf/rules/wordpress/accessibility.md @@ -13,6 +13,8 @@ Based on WordPress Core Official Standards (WCAG 2.2 Level AA). ## Conformance Levels +Code integrated into the WordPress ecosystem is expected to conform to the **Web Content Accessibility Guidelines (WCAG), version 2.2, at level AA**. This is mandatory for all new and updated code. + | Level | Priority | Description | | --------- | ---------- | ---------------------------------- | | Level A | Critical | Minimum accessibility requirements | @@ -213,28 +215,20 @@ Provide ways to help users navigate and find content. **Skip Links** -```html - -``` +Skip links allow keyboard and screen reader users to bypass repetitive navigation. WordPress Core uses the `.screen-reader-text` class for skip links that become visible on focus. -```css -.skip-link { - position: absolute; - top: -100px; - left: 0; - z-index: 100000; -} +```html + + Skip to main content -.skip-link:focus { - top: 7px; - left: 6px; - background: #f1f1f1; - padding: 15px 23px 14px; -} + +
+ +
``` +The `.screen-reader-text` class (defined in WordPress Specific Practices section) handles both the visually hidden state and the visible-on-focus behavior. No additional CSS is required when using this class. + **Page Titles** Descriptive and unique page titles. From 9fe14034d8696a8fb1e7acb54117d372726ebcab Mon Sep 17 00:00:00 2001 From: Sherv Date: Wed, 11 Feb 2026 18:51:07 +0300 Subject: [PATCH 41/90] feat(windsurf): expand analysis phase with pattern discovery workflow and enforce existing pattern reuse in enterprise principles Add 6 new steps to analysis phase workflow: find existing patterns in models/controllers/helpers/views, study pattern usage across codebase, trace parent hierarchy to plugin root, analyze complete class/file context for features/logic/flows, and iterate if better patterns found. Add explicit mandate against inventing custom solutions when existing patterns exist. Ren --- .windsurf/rules/enterprise/principles.md | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/.windsurf/rules/enterprise/principles.md b/.windsurf/rules/enterprise/principles.md index 4a0176ccf4..963be175b0 100644 --- a/.windsurf/rules/enterprise/principles.md +++ b/.windsurf/rules/enterprise/principles.md @@ -24,10 +24,17 @@ Critical principles for enterprise-level plugin development. Before proposing solutions: 1. **Read and understand** the complete issue -2. **Use Fast Context for codebase awareness**: Use the code_search tool to understand all relevant code locations, dependencies, and related functionality before making changes -3. **Identify ALL affected locations** in the codebase -4. **Map dependencies**: What calls this code? What does this code call? -5. **Check plugin requirements**: Must code work standalone or require dependencies? +2. **Find existing patterns**: Search models, controllers, helpers, views for similar functionality +3. **Study pattern usage**: Search ALL places using the pattern +4. **Trace parent hierarchy**: Search parent files up to plugin root +5. **Use Fast Context for codebase awareness**: Use the code_search tool to understand all relevant code locations, dependencies, and related functionality before making changes +6. **Analyze complete context**: Completely analyze the class or file being changed to understand all context around features, logic, and flows for complete understanding +7. **Identify ALL affected locations** in the codebase +8. **Map dependencies**: What calls this code? What does this code call? +9. **Check plugin requirements**: Must code work standalone or require dependencies? +10. **Iterate if needed**: If a better pattern is found, repeat from step 2 + +**Never invent custom solutions if existing patterns exist.** --- From f189c288645d688d0fdcd0e9cdeed1d6784275fd Mon Sep 17 00:00:00 2001 From: Sherv Date: Wed, 11 Feb 2026 18:53:49 +0300 Subject: [PATCH 42/90] feat(windsurf): add Formidable Forms PHP-specific coding standards and patterns rule Add comprehensive PHP patterns rule covering PHP 7.0 minimum version requirement with allowed/forbidden features table, WordPress 5.5 minimum version with version_compare fallback pattern, class naming conventions (FrmClassName for lite, FrmProClassName for pro), hook naming patterns (frm_hook_name, frm_pro_hook_name), and complete PHPCS sniffs documentation organized into whitespace rules (blank line management --- .windsurf/rules/formidable/frm-php.md | 166 ++++++++++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 .windsurf/rules/formidable/frm-php.md diff --git a/.windsurf/rules/formidable/frm-php.md b/.windsurf/rules/formidable/frm-php.md new file mode 100644 index 0000000000..a89cfeab23 --- /dev/null +++ b/.windsurf/rules/formidable/frm-php.md @@ -0,0 +1,166 @@ +--- +trigger: glob +globs: ["**/*.php"] +description: Formidable Forms PHP-specific patterns and conventions. Auto-applies to PHP files. +--- + +# Formidable Forms PHP Patterns + +PHP-specific patterns, coding standards, and architectural decisions for Formidable Forms. + +--- + +## PHP Version Requirement + +Target **PHP 7.0** as the minimum version. Write code using PHP 7.0 features and **do NOT use PHP 7.1+ features**. + +### PHP 7.0 Features (Allowed) + +- **Scalar type declarations**: `string`, `int`, `float`, `bool` for parameters +- **Return type declarations**: Specify return types after `)` +- **Null coalescing operator**: `$value = $input ?? 'default';` +- **Spaceship operator**: `$a <=> $b` returns -1, 0, or 1 +- **Constant arrays with define()**: `define( 'ITEMS', array( 'a', 'b' ) );` +- **Anonymous classes**: `new class { ... }` +- **Generator return expressions**: Generators can return values +- **Integer division**: `intdiv( 10, 3 )` +- **CSPRNG functions**: `random_bytes()`, `random_int()` + +### PHP 7.1+ Features (NOT Allowed) + +| Feature | Version | Why Avoid | +| ---------------------------------- | ------- | ---------------------- | +| Nullable types (`?string`) | 7.1 | Syntax not available | +| `void` return type | 7.1 | Type not available | +| `iterable` pseudo-type | 7.1 | Type not available | +| Class constant visibility | 7.1 | Syntax not available | +| Multi-catch exceptions (`\|`) | 7.1 | Syntax not available | +| Symmetric array destructuring | 7.1 | Syntax not available | +| `object` type hint | 7.2 | Type not available | +| Arrow functions (`fn()`) | 7.4 | Syntax not available | +| Typed properties | 7.4 | Syntax not available | +| Null coalescing assignment (`??=`) | 7.4 | Operator not available | +| Spread operator in arrays | 7.4 | Syntax not available | +| Null safe operator (`?->`) | 8.0 | Operator not available | +| Named arguments | 8.0 | Syntax not available | +| Match expressions | 8.0 | Syntax not available | +| Constructor property promotion | 8.0 | Syntax not available | +| Union types | 8.0 | Syntax not available | +| Attributes | 8.0 | Syntax not available | + +--- + +## WordPress Version Requirement + +Target **WordPress 5.5** as the minimum version. + +### Version Compatibility + +When using newer WordPress functions or features: + +1. **Check version before use**: Use `version_compare()` for WP version checks +2. **Provide fallbacks**: Implement backward-compatible alternatives +3. **Document requirements**: Note minimum versions in docblocks + +```php +// Example: Using a newer function with fallback +if ( version_compare( get_bloginfo( 'version' ), '5.9', '>=' ) ) { + // Use WordPress 5.9+ feature + $result = wp_new_function(); +} else { + // Fallback for older versions + $result = frm_legacy_fallback(); +} +``` + +--- + +## Class Naming + +- **Lite plugin:** `FrmClassName` (e.g., `FrmAppHelper`, `FrmDb`, `FrmField`) +- **Pro plugin:** `FrmProClassName` (e.g., `FrmProAppHelper`, `FrmProField`) + +--- + +## Hook Naming + +- **Lite hooks:** `frm_hook_name` +- **Pro hooks:** `frm_pro_hook_name` +- **Addons hooks:** `frm_addon_hook_name` + +--- + +## PHPCS Sniffs Rules + +Formidable has custom PHP_CodeSniffer sniffs in `phpcs-sniffs/Formidable/`. **Always read these sniffs before making changes** as they are continuously updated. + +### Whitespace Rules + +| Sniff | Description | +| --------------------------------- | ---------------------------------------- | +| `BlankLineAfterClosingBrace` | Add blank line after closing brace | +| `BlankLineBeforeReturnAfterBrace` | Add blank line before return after brace | +| `ConsecutiveAssignmentSpacing` | Align consecutive assignments | +| `NoBlankLineAfterLoopOpen` | No blank line after loop opening | +| `NoBlankLineAfterIfOpen` | No blank line after if opening | +| `NoBlankLineAfterFunctionOpen` | No blank line after function opening | +| `NoBlankLineBeforeCloseBrace` | No blank line before closing brace | +| `NoBlankLineInShortIf` | No blank lines in short if statements | +| `ShortFunctionBlankLine` | Proper blank lines in short functions | + +### Code Analysis Rules + +| Sniff | Description | +| ------------------------------------ | ------------------------------------------------------- | +| `FlipIfToEarlyReturn` | Convert if blocks to early returns | +| `FlipIfElseToEarlyReturn` | Convert if/else to early return pattern | +| `FlipForeachIfToContinue` | Convert foreach if to continue | +| `FlipNegativeTernary` | Flip negative ternary conditions | +| `PreferArrayKeyExists` | Use `array_key_exists()` over `isset()` for null values | +| `PreferEmptyArrayComparison` | Use `=== array()` over `empty()` for arrays | +| `PreferStrictComparison` | Use `===` and `!==` over `==` and `!=` | +| `PreferStrictInArray` | Use strict mode in `in_array()` | +| `PreferStrcasecmp` | Use `strcasecmp()` for case-insensitive comparison | +| `MoveSimpleCheckBeforeExpensiveCall` | Reorder conditions for performance | +| `SimplifyIfReturn` | Simplify if statements that only return | +| `SimplifyEmptyTernary` | Simplify redundant empty ternaries | +| `RedundantEmptyAfterTypeCheck` | Remove redundant empty after type check | +| `RedundantIssetBeforeNotEmpty` | Remove redundant isset before !empty | +| `RedundantParentheses` | Remove unnecessary parentheses | + +### Security Rules + +| Sniff | Description | +| -------------------------- | --------------------------------------------- | +| `AddDirectFileAccessCheck` | Add direct file access prevention | +| `BreakEchoConcatenation` | Break echo concatenation for escaping | +| `EscapeInHtml` | Ensure proper escaping in HTML output | +| `PreferWpSafeRedirect` | Use `wp_safe_redirect()` over `wp_redirect()` | + +### Commenting Rules + +| Sniff | Description | +| ------------------------ | -------------------------------------- | +| `AddBoolReturnTag` | Add `@return bool` tag when applicable | +| `AddMissingDocblock` | Add missing docblocks | +| `AddMissingParamType` | Add missing `@param` types | +| `FixIncorrectReturnType` | Fix incorrect `@return` types | +| `CommentSpacing` | Ensure proper comment spacing | + +### PHPUnit Rules + +| Sniff | Description | +| ---------------------------- | ---------------------------------- | +| `PreferAssertIsArray` | Use `assertIsArray()` | +| `PreferAssertArrayHasKey` | Use `assertArrayHasKey()` | +| `PreferAssertStringContains` | Use `assertStringContainsString()` | +| `PreferAssertContains` | Use `assertContains()` | +| `PreferAssertFileExists` | Use `assertFileExists()` | + +### Future-Proofing + +Before making code changes: + +1. **Read the ruleset**: Check `phpcs-sniffs/Formidable/ruleset.xml` for current rules +2. **Check individual sniffs**: Browse `phpcs-sniffs/Formidable/Sniffs/` for detailed implementations +3. **Stay updated**: Sniffs are continuously updated, always verify current rules From 217239cbf869d0617f0ac06d72711300ea9a4bd6 Mon Sep 17 00:00:00 2001 From: Sherv Date: Wed, 11 Feb 2026 19:42:37 +0300 Subject: [PATCH 43/90] refactor(windsurf): consolidate Formidable Forms testing documentation and remove redundant patterns file Expand testing.md with comprehensive test scenarios (plugin state, data, user), test structure patterns (AAA pattern, factory methods, data providers), assertion best practices, mocking/stubbing examples, front-end vs admin testing, and test file organization. Remove patterns.md as its content (class naming, method naming, hook naming, helper classes, Pro plugin awareness) is now covered in other --- .windsurf/rules/formidable/patterns.md | 109 ---------- .windsurf/rules/formidable/testing.md | 268 +++++++++++++++++++++++-- 2 files changed, 254 insertions(+), 123 deletions(-) delete mode 100644 .windsurf/rules/formidable/patterns.md diff --git a/.windsurf/rules/formidable/patterns.md b/.windsurf/rules/formidable/patterns.md deleted file mode 100644 index 42b6a25da9..0000000000 --- a/.windsurf/rules/formidable/patterns.md +++ /dev/null @@ -1,109 +0,0 @@ ---- -trigger: always_on -description: Formidable Forms plugin-specific patterns and conventions. Always active for this workspace. ---- - -# Formidable Forms Patterns - -Formidable-specific patterns, naming conventions, and architectural decisions. - ---- - -## Pattern Discovery Process - -Before making ANY change: - -1. **Find existing patterns**: Search models, controllers, helpers for similar functionality -2. **Study pattern usage**: Search ALL places using the pattern -3. **Trace parent hierarchy**: Search parent files up to plugin root -4. **Iterate if needed**: If better pattern found, repeat from step 1 - -**Never invent custom solutions if existing patterns exist.** - ---- - -## Class Naming - -- **Lite plugin:** `FrmClassName` (e.g., `FrmAppHelper`, `FrmDb`, `FrmField`) -- **Pro plugin:** `FrmProClassName` (e.g., `FrmProAppHelper`, `FrmProField`) - ---- - -## Method Naming - -- **Public methods:** `snake_case` -- **Legacy methods:** Preserve existing `camelCase` for backward compatibility - ---- - -## Hook Naming - -- **Lite hooks:** `frm_hook_name` -- **Pro hooks:** `frm_pro_hook_name` - ---- - -## Text Domains - -- **Lite:** `formidable` -- **Pro and add-ons:** `formidable-pro` - ---- - -## Factory Pattern - -Use `FrmFieldFactory::get_field_type()` for field type instances. - -```php -$field_obj = FrmFieldFactory::get_field_type( $field ); -``` - ---- - -## Helper Classes - -### FrmAppHelper - -Common utility methods for the plugin. - -```php -FrmAppHelper::get_post_param( 'key', 'default', 'sanitize_text_field' ); -FrmAppHelper::get_param( 'key', 'default', 'get', 'sanitize_text_field' ); -FrmAppHelper::simple_get( 'key', 'sanitize_text_field' ); -``` - -### FrmDb - -Database operations wrapper. - -```php -FrmDb::get_col( $table, $where, $field ); -FrmDb::get_row( $table, $where, $fields ); -FrmDb::get_results( $table, $where, $fields ); -FrmDb::get_var( $table, $where, $field ); -``` - -### FrmFieldsHelper - -Field-specific utilities. - ---- - -## Pro Plugin Awareness - -**All code must work when:** - -- Pro is active -- Pro is inactive - -```php -// Check if Pro is active -if ( class_exists( 'FrmProAppHelper' ) ) { - // Pro-specific code -} - -// Or use the constant -if ( defined( 'FRM_PRO_VERSION' ) ) { - // Pro-specific code -} -``` diff --git a/.windsurf/rules/formidable/testing.md b/.windsurf/rules/formidable/testing.md index c0397161e7..7b6bbc7171 100644 --- a/.windsurf/rules/formidable/testing.md +++ b/.windsurf/rules/formidable/testing.md @@ -9,63 +9,271 @@ Testing conventions specific to Formidable Forms plugin. --- +## Required Test Scenarios + +Every fix/feature **MUST** be tested with these scenarios before completion: + +### Plugin State Scenarios + +1. **Pro active**: Test with formidable-pro plugin enabled +2. **Pro inactive**: Test in Lite-only mode +3. **Fresh install**: No prior data exists +4. **Migration scenario**: Upgrading from previous version + +### Data Scenarios + +1. **Empty data**: Empty arrays, null values, missing keys +2. **Valid data**: Expected input with correct types +3. **Invalid data**: Wrong types, malformed input +4. **Edge cases**: Boundary values, special characters, Unicode +5. **Large datasets**: Performance with many entries/fields + +### User Scenarios + +1. **Administrator**: Full capabilities +2. **Editor**: Limited capabilities +3. **Subscriber**: Minimal capabilities +4. **Logged out**: No user context + +--- + ## Test Classes - **Lite tests:** Extend `FrmUnitTest` - **Pro tests:** Extend `FrmProUnitTest` +- **AJAX tests:** Extend `FrmAjaxUnitTest` ```php class Test_FrmField extends FrmUnitTest { - // Tests + + public function test_method_does_expected_behavior() { + // Arrange + $form = $this->factory->form->create_and_get(); + + // Act + $result = FrmField::get_all_for_form( $form->id ); + + // Assert + $this->assertIsArray( $result, 'Result should be an array.' ); + } +} +``` + +--- + +## Test Method Naming + +Use descriptive names that explain the scenario: + +```php +// Pattern: test_{method}_{scenario}_{expected_outcome} +public function test_get_field_returns_null_for_invalid_id() {} +public function test_create_entry_saves_meta_values() {} +public function test_delete_form_removes_all_fields() {} +public function test_validate_field_rejects_empty_required_field() {} +``` + +--- + +## Test Structure (AAA Pattern) + +Follow **Arrange-Act-Assert** pattern: + +```php +public function test_entry_creation_with_valid_data() { + // Arrange - Set up test data and conditions + $form = $this->factory->form->create_and_get(); + $field = $this->factory->field->create_and_get( array( + 'form_id' => $form->id, + 'type' => 'text', + ) ); + + // Act - Execute the code being tested + $entry_id = FrmEntry::create( array( + 'form_id' => $form->id, + 'item_meta' => array( + $field->id => 'Test Value', + ), + ) ); + + // Assert - Verify the results + $this->assertIsNumeric( $entry_id, 'Entry ID should be numeric.' ); + $this->assertGreaterThan( 0, $entry_id, 'Entry ID should be positive.' ); } ``` --- -## Test Data Creation +## Factory Methods -Use factory methods for test data. +Use factory methods for test data creation: ```php +// Create form $form = $this->factory->form->create_and_get(); + +// Create field with options $field = $this->factory->field->create_and_get( array( 'form_id' => $form->id, 'type' => 'text', + 'name' => 'Test Field', ) ); + +// Create entry $entry = $this->factory->entry->create_and_get( array( 'form_id' => $form->id, ) ); + +// Create multiple items +$entries = $this->factory->entry->create_many( 5, array( + 'form_id' => $form->id, +) ); + +// Create user with role +$user_id = $this->factory->user->create( array( + 'role' => 'editor', +) ); ``` --- -## Testing Protected Methods +## Testing Private/Protected Methods ```php -$result = $this->run_private_method( array( $object, 'method_name' ), array( $arg1, $arg2 ) ); +// For private methods +$result = $this->run_private_method( + array( $object, 'private_method_name' ), + array( $arg1, $arg2 ) +); + +// For private properties +$value = $this->get_private_property( $object, 'property_name' ); +$this->set_private_property( $object, 'property_name', $new_value ); ``` --- -## Assertion Messages +## User Context Testing + +```php +public function test_admin_can_delete_form() { + // Set user role + $this->set_user_by_role( 'administrator' ); + + $form = $this->factory->form->create_and_get(); + $result = FrmForm::destroy( $form->id ); -All assertion messages must end with a period. + $this->assertTrue( $result, 'Admin should be able to delete form.' ); +} + +public function test_subscriber_cannot_delete_form() { + $this->set_user_by_role( 'subscriber' ); + + $form = $this->factory->form->create_and_get(); + $result = FrmForm::destroy( $form->id ); + + $this->assertFalse( $result, 'Subscriber should not delete form.' ); +} +``` + +--- + +## Assertion Best Practices + +### Use Specific Assertions + +```php +// CORRECT - Specific assertions +$this->assertSame( $expected, $actual, 'Values should be identical.' ); +$this->assertIsArray( $result, 'Result should be an array.' ); +$this->assertArrayHasKey( 'id', $data, 'Data should have id key.' ); +$this->assertStringContainsString( 'error', $message, 'Message should contain error.' ); + +// INCORRECT - Generic assertions +$this->assertTrue( $expected === $actual ); +$this->assertTrue( is_array( $result ) ); +``` + +### Message Requirements + +All assertion messages must end with a period: ```php $this->assertEquals( $expected, $actual, 'The value should match expected.' ); $this->assertTrue( $condition, 'Condition should be true.' ); +$this->assertNotEmpty( $data, 'Data should not be empty.' ); ``` --- -## Required Test Scenarios +## Data Providers + +Use data providers for testing multiple scenarios: -Every fix/feature must be tested with: +```php +/** + * @dataProvider field_type_provider + */ +public function test_field_validation( $field_type, $value, $expected_valid ) { + $field = $this->factory->field->create_and_get( array( + 'type' => $field_type, + ) ); + + $is_valid = FrmEntryValidate::validate_field( $field, $value ); + + $this->assertSame( $expected_valid, $is_valid ); +} -1. **Pro active**: With formidable-pro plugin enabled -2. **Pro inactive**: Lite-only scenario -3. **Empty data**: Empty arrays, null values, missing keys -4. **Edge cases**: Boundary conditions, special characters +public function field_type_provider() { + return array( + 'text with value' => array( 'text', 'hello', true ), + 'text empty' => array( 'text', '', true ), + 'email valid' => array( 'email', 'test@example.com', true ), + 'email invalid' => array( 'email', 'invalid', false ), + 'number valid' => array( 'number', '123', true ), + 'number with letters' => array( 'number', 'abc', false ), + ); +} +``` + +--- + +## Mocking and Stubbing + +```php +public function test_api_request_handles_error() { + // Mock HTTP response + add_filter( 'pre_http_request', function() { + return new WP_Error( 'http_error', 'Connection failed' ); + } ); + + $result = FrmAPI::make_request( '/endpoint' ); + + $this->assertInstanceOf( WP_Error::class, $result ); +} +``` + +--- + +## Front-end vs Admin Testing + +```php +public function test_shortcode_output_on_frontend() { + // Switch to front-end context + $this->set_front_end(); + + $output = do_shortcode( '[formidable id="1"]' ); + + $this->assertStringContainsString( 'set_admin_screen( 'admin.php?page=formidable' ); + + $this->assertTrue( is_admin(), 'Should be in admin context.' ); +} +``` --- @@ -76,10 +284,38 @@ Every fix/feature must be tested with: vendor/bin/phpunit # Specific test file -vendor/bin/phpunit tests/phpunit/test-file.php +vendor/bin/phpunit tests/phpunit/fields/test-FrmField.php # Specific test method vendor/bin/phpunit --filter test_method_name + +# With coverage +vendor/bin/phpunit --coverage-html coverage/ + +# Specific group +vendor/bin/phpunit --group ajax +``` + +--- + +## Test File Organization + +```text +tests/phpunit/ +├── base/ +│ ├── FrmUnitTest.php +│ ├── FrmAjaxUnitTest.php +│ └── testdata.xml +├── fields/ +│ ├── test-FrmField.php +│ └── test-FrmFieldValidation.php +├── forms/ +│ ├── test-FrmForm.php +│ └── test-FrmFormAction.php +├── entries/ +│ └── test-FrmEntry.php +└── helpers/ + └── test-FrmAppHelper.php ``` --- @@ -87,5 +323,9 @@ vendor/bin/phpunit --filter test_method_name ## Code Style Check ```bash +# PHPCS check vendor/bin/phpcs --standard=phpcs.xml path/to/file.php + +# Auto-fix +vendor/bin/phpcbf --standard=phpcs.xml path/to/file.php ``` From 029634ff4b3ac00424af0e1204817476df493847 Mon Sep 17 00:00:00 2001 From: Sherv Date: Wed, 11 Feb 2026 19:44:23 +0300 Subject: [PATCH 44/90] feat(windsurf): add comprehensive WordPress Block Editor (Gutenberg) development standards rule Add extensive Gutenberg standards covering block.json registration (API version 3, schema validation, lazy loading), useBlockProps hook requirement, file organization patterns (block.json, edit.js, save.js, render.php), functional components with hooks, static vs dynamic blocks, WordPress packages (@wordpress/block-editor, @wordpress/components, @wordpress/data), data layer patterns (useSelect/useDispatch), --- .../{gutenberg.md => block-editor.md} | 10 +- .windsurf/rules/wordpress/vip.md | 585 ------------------ 2 files changed, 8 insertions(+), 587 deletions(-) rename .windsurf/rules/wordpress/{gutenberg.md => block-editor.md} (98%) delete mode 100644 .windsurf/rules/wordpress/vip.md diff --git a/.windsurf/rules/wordpress/gutenberg.md b/.windsurf/rules/wordpress/block-editor.md similarity index 98% rename from .windsurf/rules/wordpress/gutenberg.md rename to .windsurf/rules/wordpress/block-editor.md index 1d20b9eec8..8f9d735413 100644 --- a/.windsurf/rules/wordpress/gutenberg.md +++ b/.windsurf/rules/wordpress/block-editor.md @@ -2,12 +2,12 @@ trigger: glob globs: [ + "**/*block.js", + "**/block.json", "**/blocks/**/*.js", "**/blocks/**/*.jsx", "**/block-editor/**/*.js", "**/gutenberg/**/*.js", - "**/*.block.js", - "**/block.json", ] description: WordPress Block Editor (Gutenberg) development standards. Auto-applies when working with block-related files. --- @@ -824,6 +824,12 @@ function useBlockContext() { --- +## VIP Standards + +For WordPress VIP-specific block editor standards including dynamic blocks, block bindings, Script Modules API, and enterprise performance patterns, see `wordpress-vip/wpvip-block-editor.md`. + +--- + ## Tooling ```bash diff --git a/.windsurf/rules/wordpress/vip.md b/.windsurf/rules/wordpress/vip.md deleted file mode 100644 index f2e503c28a..0000000000 --- a/.windsurf/rules/wordpress/vip.md +++ /dev/null @@ -1,585 +0,0 @@ ---- -trigger: glob -globs: ["**/*.php"] -description: WordPress VIP coding standards for performance and security. Auto-applies to PHP files. ---- - -# WordPress VIP Standards - -WordPress VIP platform requirements for performance, security, and scalability. - -**Reference:** [WordPress VIP Documentation](https://docs.wpvip.com/) - ---- - -## 1. Database Queries - -### Use Proper Methods - -Never use `$wpdb->query()` for SELECT statements. Use specific methods. - -| Method | Use Case | -|--------|----------| -| `$wpdb->get_results()` | Multiple rows | -| `$wpdb->get_row()` | Single row | -| `$wpdb->get_var()` | Single value | -| `$wpdb->get_col()` | Single column | - -```php -// Correct -$posts = $wpdb->get_results( - $wpdb->prepare( - "SELECT * FROM $wpdb->posts WHERE post_status = %s", - 'publish' - ) -); - -// Incorrect -$posts = $wpdb->query( "SELECT * FROM $wpdb->posts" ); -``` - -### Always Use Prepare - -Always use `$wpdb->prepare()` for queries with variables. - -```php -$wpdb->query( - $wpdb->prepare( - "UPDATE $wpdb->posts SET post_title = %s WHERE ID = %d", - $title, - $id - ) -); -``` - -### Limit Results - -Always include LIMIT clause to prevent unbounded queries. - -```php -$wpdb->get_results( - $wpdb->prepare( - "SELECT * FROM $wpdb->posts WHERE post_type = %s LIMIT %d", - 'page', - 100 - ) -); -``` - -### Avoid Direct Queries When Possible - -Use WordPress functions instead of direct queries. - -```php -// Prefer this -$posts = get_posts( array( - 'post_type' => 'post', - 'posts_per_page' => 10, -) ); - -// Over direct query -$posts = $wpdb->get_results( "SELECT * FROM $wpdb->posts LIMIT 10" ); -``` - ---- - -## 2. Forbidden Functions - -Never use these functions: - -| Function | Reason | Alternative | -|----------|--------|-------------| -| `extract()` | Makes code unpredictable | Access array keys directly | -| `eval()` | Security vulnerability | Refactor code logic | -| `create_function()` | Deprecated, insecure | Use anonymous functions | -| `compact()` | Reduces readability | Build arrays explicitly | -| `file_get_contents()` for URLs | Unreliable, no error handling | `wp_remote_get()` | -| `file_put_contents()` | Direct file access | WP_Filesystem | -| `curl_*` functions | Inconsistent behavior | WP HTTP API | -| `serialize()`/`unserialize()` for user data | Security risk | `json_encode()`/`json_decode()` | - ---- - -## 3. Remote Requests - -### HTTP API - -Use WordPress HTTP API for all remote requests. - -```php -// GET request -$response = wp_remote_get( 'https://api.example.com/data' ); - -if ( is_wp_error( $response ) ) { - $error_message = $response->get_error_message(); - // Handle error -} else { - $body = wp_remote_retrieve_body( $response ); - $data = json_decode( $body, true ); -} - -// POST request -$response = wp_remote_post( - 'https://api.example.com/submit', - array( - 'body' => array( - 'key' => 'value', - ), - 'timeout' => 30, - ) -); -``` - -### Timeouts - -Always set appropriate timeouts. - -```php -$response = wp_remote_get( - $url, - array( - 'timeout' => 15, - ) -); -``` - -### SSL Verification - -Never disable SSL verification in production. - -```php -// INCORRECT - Never do this -$response = wp_remote_get( - $url, - array( - 'sslverify' => false, - ) -); -``` - ---- - -## 4. File Operations - -### WP_Filesystem - -Use WP_Filesystem for file operations. - -```php -global $wp_filesystem; - -if ( ! function_exists( 'WP_Filesystem' ) ) { - require_once ABSPATH . 'wp-admin/includes/file.php'; -} - -WP_Filesystem(); - -// Read file -$content = $wp_filesystem->get_contents( $file_path ); - -// Write file -$wp_filesystem->put_contents( $file_path, $content, FS_CHMOD_FILE ); - -// Check if file exists -if ( $wp_filesystem->exists( $file_path ) ) { - // File exists -} -``` - -### Upload Handling - -```php -if ( ! function_exists( 'wp_handle_upload' ) ) { - require_once ABSPATH . 'wp-admin/includes/file.php'; -} - -$upload = wp_handle_upload( - $_FILES['file'], - array( - 'test_form' => false, - 'mimes' => array( - 'jpg|jpeg' => 'image/jpeg', - 'png' => 'image/png', - ), - ) -); - -if ( isset( $upload['error'] ) ) { - // Handle error -} -``` - ---- - -## 5. Caching - -### Transients - -Use transients for cached data. - -```php -$data = get_transient( 'my_cache_key' ); - -if ( false === $data ) { - $data = expensive_operation(); - set_transient( 'my_cache_key', $data, HOUR_IN_SECONDS ); -} - -return $data; -``` - -### Object Cache - -Use object cache for frequently accessed data. - -```php -$data = wp_cache_get( 'key', 'group' ); - -if ( false === $data ) { - $data = expensive_operation(); - wp_cache_set( 'key', $data, 'group', HOUR_IN_SECONDS ); -} - -return $data; -``` - -### Cache Keys - -Use descriptive and unique cache keys. - -```php -$cache_key = sprintf( 'user_posts_%d_%s', $user_id, $post_type ); -``` - -### Cache Invalidation - -Invalidate cache when data changes. - -```php -add_action( 'save_post', function( $post_id ) { - delete_transient( 'posts_cache' ); - wp_cache_delete( 'posts_list', 'my_plugin' ); -} ); -``` - ---- - -## 6. Escaping Output - -### Escape Late - -Escape data at output, not at assignment. - -```php -// Correct - escape at output -echo esc_html( $title ); - -// Incorrect - escape at assignment then output later -$title = esc_html( $raw_title ); -// ... later -echo $title; // Might be double-escaped or bypassed -``` - -### Escaping Functions - -| Function | Use Case | -|----------|----------| -| `esc_html()` | HTML element content | -| `esc_attr()` | HTML attributes | -| `esc_url()` | URLs | -| `esc_js()` | Inline JavaScript | -| `esc_textarea()` | Textarea content | -| `wp_kses_post()` | Allow safe HTML | -| `wp_kses()` | Custom allowed HTML | - -```php -
-

- - - -
- -
-
-``` - -### Translation with Escaping - -```php -echo esc_html__( 'Translated text', 'textdomain' ); -echo esc_attr__( 'Attribute text', 'textdomain' ); -printf( - esc_html__( 'Hello, %s!', 'textdomain' ), - esc_html( $name ) -); -``` - ---- - -## 7. Sanitizing Input - -### Sanitize Early - -Sanitize user input as early as possible. - -```php -$title = sanitize_text_field( wp_unslash( $_POST['title'] ) ); -$email = sanitize_email( $_POST['email'] ); -$url = esc_url_raw( $_POST['url'] ); -$key = sanitize_key( $_POST['key'] ); -$ids = array_map( 'absint', $_POST['ids'] ); -``` - -### Sanitization Functions - -| Function | Use Case | -|----------|----------| -| `sanitize_text_field()` | Single line text | -| `sanitize_textarea_field()` | Multi-line text | -| `sanitize_email()` | Email addresses | -| `sanitize_file_name()` | File names | -| `sanitize_key()` | Keys and slugs | -| `sanitize_title()` | Titles and slugs | -| `absint()` | Positive integers | -| `intval()` | Integers | -| `wp_kses()` | HTML with allowed tags | - -### Nonce Verification - -Always verify nonces for form submissions. - -```php -// In form -wp_nonce_field( 'my_action', 'my_nonce' ); - -// On submission -if ( ! isset( $_POST['my_nonce'] ) || - ! wp_verify_nonce( $_POST['my_nonce'], 'my_action' ) ) { - wp_die( 'Security check failed' ); -} -``` - -### Capability Checks - -Always verify user capabilities. - -```php -if ( ! current_user_can( 'edit_posts' ) ) { - wp_die( 'Unauthorized access' ); -} -``` - ---- - -## 8. Query Optimization - -### Avoid Meta Queries on Large Tables - -Meta queries do not scale. Consider alternatives: - -- Custom tables for high-volume data -- Taxonomy terms for filterable data -- Caching query results - -### Efficient Post Queries - -```php -$args = array( - 'post_type' => 'post', - 'posts_per_page' => 10, - 'no_found_rows' => true, // Skip pagination count - 'update_post_meta_cache' => false, // Skip meta cache if not needed - 'update_post_term_cache' => false, // Skip term cache if not needed - 'fields' => 'ids', // Only get IDs if that is all you need -); - -$query = new WP_Query( $args ); -``` - -### Avoid posts_per_page = -1 - -Never get all posts without limit. - -```php -// Incorrect -$args = array( - 'posts_per_page' => -1, -); - -// Correct - use reasonable limit -$args = array( - 'posts_per_page' => 100, -); -``` - -### Use Proper Indexing - -Ensure custom queries use indexed columns. - ---- - -## 9. Error Handling - -### No Error Suppression - -Never use `@` operator. - -```php -// Incorrect -$value = @file_get_contents( $file ); - -// Correct -if ( file_exists( $file ) && is_readable( $file ) ) { - $value = file_get_contents( $file ); -} else { - $value = false; -} -``` - -### Proper Error Checking - -```php -$result = some_operation(); - -if ( is_wp_error( $result ) ) { - error_log( 'Operation failed: ' . $result->get_error_message() ); - return false; -} - -return $result; -``` - -### Try-Catch for Exceptions - -```php -try { - $result = risky_operation(); -} catch ( Exception $e ) { - error_log( 'Exception: ' . $e->getMessage() ); - return new WP_Error( 'operation_failed', $e->getMessage() ); -} -``` - ---- - -## 10. Performance Best Practices - -### Lazy Loading - -Load resources only when needed. - -```php -add_action( 'wp_enqueue_scripts', function() { - if ( is_singular( 'product' ) ) { - wp_enqueue_script( 'product-gallery' ); - } -} ); -``` - -### Avoid Loading All Posts - -```php -// Incorrect - loads all posts into memory -$posts = get_posts( array( 'numberposts' => -1 ) ); -foreach ( $posts as $post ) { - // Process -} - -// Correct - batch processing -$paged = 1; -do { - $posts = get_posts( array( - 'numberposts' => 100, - 'paged' => $paged, - ) ); - - foreach ( $posts as $post ) { - // Process - } - - $paged++; -} while ( count( $posts ) === 100 ); -``` - -### Avoid Remote Requests in Loops - -```php -// Incorrect -foreach ( $items as $item ) { - $data = wp_remote_get( $item['url'] ); -} - -// Correct - batch or cache -$cached = get_transient( 'batch_data' ); -if ( false === $cached ) { - // Make single batch request or queue for background processing -} -``` - ---- - -## 11. Background Processing - -### WP Cron - -```php -// Schedule event -if ( ! wp_next_scheduled( 'my_cron_event' ) ) { - wp_schedule_event( time(), 'hourly', 'my_cron_event' ); -} - -// Handle event -add_action( 'my_cron_event', 'my_cron_function' ); -function my_cron_function() { - // Perform background task -} - -// Unschedule on deactivation -register_deactivation_hook( __FILE__, function() { - wp_clear_scheduled_hook( 'my_cron_event' ); -} ); -``` - -### Action Scheduler - -For more complex background jobs, use Action Scheduler library. - ---- - -## 12. Logging - -### Use Error Log - -```php -if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { - error_log( 'Debug message: ' . print_r( $data, true ) ); -} -``` - -### Never Log Sensitive Data - -```php -// INCORRECT - logs password -error_log( 'User login: ' . $username . ' / ' . $password ); - -// Correct -error_log( 'User login attempt: ' . $username ); -``` - ---- - -## Tooling - -```bash -# Install VIP Coding Standards -composer require automattic/vipwpcs - -# Configure phpcs.xml - - - - -# Run check -./vendor/bin/phpcs --standard=WordPress-VIP-Go path/to/file.php -``` From fcb1579dce63cae59fe57b915c4060120561785e Mon Sep 17 00:00:00 2001 From: Sherv Date: Wed, 11 Feb 2026 19:53:09 +0300 Subject: [PATCH 45/90] feat(windsurf): add WordPress VIP Block Editor standards rule with dynamic blocks, bindings, and Script Modules API Add comprehensive VIP Block Editor standards covering dynamic block implementation (render property vs render_callback methods, static vs dynamic decision matrix), block bindings for custom fields and data sources (WordPress 6.5+), Script Modules API with viewScriptModule and dynamic imports, custom block categories, security patterns (attribute validation, output escaping, capability --- .../rules/wordpress-vip/wpvip-block-editor.md | 352 ++++++++++++++++++ 1 file changed, 352 insertions(+) create mode 100644 .windsurf/rules/wordpress-vip/wpvip-block-editor.md diff --git a/.windsurf/rules/wordpress-vip/wpvip-block-editor.md b/.windsurf/rules/wordpress-vip/wpvip-block-editor.md new file mode 100644 index 0000000000..42e8faec12 --- /dev/null +++ b/.windsurf/rules/wordpress-vip/wpvip-block-editor.md @@ -0,0 +1,352 @@ +--- +trigger: glob +globs: + [ + '**/blocks/**/*.js', + '**/blocks/**/*.jsx', + '**/block-editor/**/*.js', + '**/gutenberg/**/*.js', + '**/*.block.js', + '**/block.json', + ] +description: WordPress VIP Block Editor standards for enterprise block development. Auto-applies when working with block-related files. +--- + +# WordPress VIP Block Editor Standards + +Enterprise-level guidelines for developing WordPress blocks on the VIP platform. + +**Reference:** [WordPress VIP Learn - Enterprise Block Editor](https://learn.wpvip.com/) + +--- + +## Dynamic Blocks + +Dynamic blocks generate content server-side using PHP, ideal for frequently updated data. + +### render Property Method + +```json +{ + "apiVersion": 3, + "name": "my-plugin/dynamic-block", + "render": "file:./render.php" +} +``` + +```php + +
> + +
+``` + +### render_callback Method + +```php +register_block_type( __DIR__ . '/build', array( + 'render_callback' => function( $attributes, $content, $block ) { + $wrapper_attributes = get_block_wrapper_attributes(); + return sprintf( + '
%2$s
', + $wrapper_attributes, + esc_html( $attributes['title'] ) + ); + }, +) ); +``` + +### Static vs Dynamic Decision + +| Use Static | Use Dynamic | +| -------------------- | --------------------------------- | +| Simple content | Frequently changing data | +| Performance priority | External API data | +| No external data | Code updates affect all instances | +| Consistent output | Complex server-side logic | + +--- + +## Block Bindings + +Block bindings dynamically populate content from data sources (WordPress 6.5+). + +### Using Custom Fields + +```php +// Register meta field +register_meta( 'post', 'custom_subtitle', array( + 'show_in_rest' => true, + 'single' => true, + 'type' => 'string', +) ); +``` + +```html + +

+ +``` + +### Custom Binding Source + +```php +register_block_bindings_source( 'my-plugin/custom-source', array( + 'label' => __( 'Custom Source', 'my-plugin' ), + 'get_value_callback' => function( $source_args, $block_instance, $attribute_name ) { + return get_option( $source_args['key'], '' ); + }, + 'uses_context' => array( 'postId' ), +) ); +``` + +--- + +## Script Modules API + +Native ESM support in WordPress 6.5+ for optimized loading. + +### viewScriptModule in block.json + +```json +{ + "apiVersion": 3, + "name": "my-plugin/interactive-block", + "viewScriptModule": "file:./view.js" +} +``` + +### Register Script Module + +```php +wp_register_script_module( + '@my-plugin/feature', + plugin_dir_url( __FILE__ ) . 'build/feature.js' +); + +wp_enqueue_script_module( + '@my-plugin/main', + plugin_dir_url( __FILE__ ) . 'build/main.js', + array( '@my-plugin/feature' ) +); +``` + +### Dynamic Import + +```javascript +// Load on demand +document.getElementById( 'trigger' ).addEventListener( 'click', async () => { + const { initialize } = await import( '@my-plugin/heavy-feature' ); + initialize(); +} ); +``` + +--- + +## Block Categories + +### Create Custom Category + +```php +add_filter( 'block_categories_all', function( $categories, $post ) { + return array_merge( + array( + array( + 'slug' => 'my-plugin', + 'title' => __( 'My Plugin', 'my-plugin' ), + 'icon' => 'star-filled', + ), + ), + $categories + ); +}, 10, 2 ); +``` + +### Assign Block to Category + +```json +{ + "name": "my-plugin/custom-block", + "category": "my-plugin" +} +``` + +--- + +## Security in Blocks + +### Validate Attributes + +```jsx +const updateTitle = ( newTitle ) => { + if ( newTitle.length > 100 ) { + return; // Reject invalid input + } + setAttributes( { title: newTitle } ); +}; +``` + +### Escape Output in Save + +```jsx +import { escapeHTML, escapeAttribute } from '@wordpress/escape-html'; + +export default function save( { attributes } ) { + return ( +
+

{ escapeHTML( attributes.title ) }

+
+ ); +} +``` + +### PHP Escaping in render.php + +```php +
> +

+ + + +
+``` + +### Capability Checks + +```jsx +import { useSelect } from '@wordpress/data'; +import { store as coreStore } from '@wordpress/core-data'; + +function RestrictedControl() { + const canEdit = useSelect( ( select ) => { + return select( coreStore ).canUser( 'update', 'settings' ); + }, [] ); + + if ( ! canEdit ) { + return null; + } + + return ; +} +``` + +--- + +## Accessibility in Blocks + +### Semantic HTML + +```jsx +// Use semantic elements + + +
+ { /* Main content */ } +
+``` + +### Keyboard Support + +```jsx +function CustomButton( { onClick, children } ) { + return ( + + ); +} +``` + +### ARIA Live Regions + +```jsx +import { useState } from '@wordpress/element'; + +function DynamicContent() { + const [ status, setStatus ] = useState( '' ); + + return ( +
+ { status } +
+ ); +} +``` + +--- + +## Performance Optimization + +### Code Splitting + +```javascript +// webpack.config.js +const defaultConfig = require( '@wordpress/scripts/config/webpack.config' ); +const path = require( 'path' ); + +module.exports = { + ...defaultConfig, + entry: { + main: path.resolve( process.cwd(), 'resources/js/module.js' ), + editor: path.resolve( process.cwd(), 'resources/js/editor.js' ), + }, +}; +``` + +### Lazy Loading Components + +```jsx +import { lazy, Suspense } from '@wordpress/element'; + +const HeavyComponent = lazy( () => import( './HeavyComponent' ) ); + +function MyBlock() { + return ( + }> + + + ); +} +``` + +### Caching in Data Layer + +```jsx +import { useSelect } from '@wordpress/data'; +import { store as coreStore } from '@wordpress/core-data'; + +// CORRECT - Minimal dependencies, automatic caching +const postTitle = useSelect( + ( select ) => { + return select( coreStore ).getEntityRecord( 'postType', 'post', postId )?.title; + }, + [ postId ] +); +``` + +--- + +## Tooling + +```bash +# Create new block +npx @wordpress/create-block my-block + +# Build with modules support +npm run build -- --experimental-modules + +# Install VIP-compatible packages +npm install @wordpress/scripts --save-dev +``` From 342cf0f9ebf5f047f809561f5c0c2ecbb9fda2cb Mon Sep 17 00:00:00 2001 From: Sherv Date: Wed, 11 Feb 2026 19:53:40 +0300 Subject: [PATCH 46/90] feat(windsurf): add WordPress VIP performance optimization standards rule with MySQL query optimization, caching strategies, and asset loading patterns Add comprehensive VIP performance standards covering MySQL query optimization (query count targets, efficient WP_Query with no_found_rows/update_post_meta_cache flags, fields parameter, meta query alternatives), caching strategies (full page vs object cache vs transients, partial output caching with ob_start/ob_get_clean, cache invalidation patterns --- .../rules/wordpress-vip/wpvip-performance.md | 316 ++++++++++++++++++ 1 file changed, 316 insertions(+) create mode 100644 .windsurf/rules/wordpress-vip/wpvip-performance.md diff --git a/.windsurf/rules/wordpress-vip/wpvip-performance.md b/.windsurf/rules/wordpress-vip/wpvip-performance.md new file mode 100644 index 0000000000..529e39de0f --- /dev/null +++ b/.windsurf/rules/wordpress-vip/wpvip-performance.md @@ -0,0 +1,316 @@ +--- +trigger: glob +globs: ['**/*.php'] +description: WordPress VIP performance optimization standards. Auto-applies to PHP files. +--- + +# WordPress VIP Performance Standards + +Enterprise-level performance optimization for WordPress VIP platform. + +**Reference:** [WordPress VIP Learn - Enterprise Performance](https://learn.wpvip.com/) + +--- + +## MySQL Query Optimization + +### Query Count + +Each SQL query adds overhead and latency. Reduce the number of queries. + +```php +// Use Query Monitor plugin to identify unnecessary queries +// Target: < 50 queries per page load +``` + +### Query Design + +```php +// CORRECT - Efficient query with proper limits +$posts = $wpdb->get_results( + $wpdb->prepare( + "SELECT ID, post_title FROM $wpdb->posts + WHERE post_status = %s + AND post_type = %s + LIMIT %d", + 'publish', + 'post', + 100 + ) +); + +// INCORRECT - Unbounded query +$posts = $wpdb->get_results( "SELECT * FROM $wpdb->posts" ); +``` + +### Efficient WP_Query + +```php +$args = array( + 'post_type' => 'post', + 'posts_per_page' => 10, + 'no_found_rows' => true, // Skip pagination count + 'update_post_meta_cache' => false, // Skip meta cache if not needed + 'update_post_term_cache' => false, // Skip term cache if not needed + 'fields' => 'ids', // Only get IDs if that is all you need +); + +$query = new WP_Query( $args ); +``` + +### Avoid Meta Queries at Scale + +Meta queries do not scale. Consider alternatives: + +- Custom tables for high-volume data +- Taxonomy terms for filterable data +- Caching query results + +--- + +## Caching Strategies + +### Full Page Caching + +Pages can be cached and served directly without regenerating content. + +| Cacheable | Not Cacheable | +| ---------------------------- | ------------------- | +| Static pages | User-specific data | +| Blog posts | Shopping carts | +| Category archives | Account pages | +| Content that changes rarely | Real-time data | + +### Object Cache + +```php +$data = wp_cache_get( 'my_cache_key', 'my_group' ); + +if ( false === $data ) { + $data = expensive_operation(); + wp_cache_set( 'my_cache_key', $data, 'my_group', HOUR_IN_SECONDS ); +} + +return $data; +``` + +### Transients + +```php +$data = get_transient( 'my_transient_key' ); + +if ( false === $data ) { + $data = expensive_operation(); + set_transient( 'my_transient_key', $data, HOUR_IN_SECONDS ); +} + +return $data; +``` + +### Partial Output Caching + +Cache static parts of pages when full page caching is not possible. + +```php +$cache_key = sprintf( 'footer_output_%s', get_locale() ); +$output = wp_cache_get( $cache_key, 'partials' ); + +if ( false === $output ) { + ob_start(); + get_template_part( 'template-parts/footer' ); + $output = ob_get_clean(); + wp_cache_set( $cache_key, $output, 'partials', DAY_IN_SECONDS ); +} + +echo $output; +``` + +### Cache Invalidation + +```php +add_action( 'save_post', function( $post_id ) { + delete_transient( 'posts_cache' ); + wp_cache_delete( 'posts_list', 'my_plugin' ); +} ); +``` + +--- + +## Template Optimization + +### Avoid Template Over-use + +Each template requires additional processing and database queries. + +```php +// CAUTION - Too many template parts +get_template_part( 'header' ); +get_template_part( 'nav' ); +get_template_part( 'sidebar' ); +get_template_part( 'content' ); +get_template_part( 'footer' ); + +// BETTER - Cache reusable partials +$nav = wp_cache_get( 'main_nav', 'partials' ); +if ( false === $nav ) { + ob_start(); + get_template_part( 'nav' ); + $nav = ob_get_clean(); + wp_cache_set( 'main_nav', $nav, 'partials', HOUR_IN_SECONDS ); +} +echo $nav; +``` + +--- + +## Hook Performance + +### Avoid Excessive Callbacks + +Only use necessary hooks and avoid excessive callbacks. + +```php +// INCORRECT - Running on every admin request +add_action( 'admin_init', 'expensive_operation' ); + +// CORRECT - Conditional execution +add_action( 'admin_init', function() { + if ( ! isset( $_GET['my_action'] ) ) { + return; + } + expensive_operation(); +} ); +``` + +### Cache Filter Results + +Filters are not cached by default. + +```php +add_filter( 'expensive_filter', function( $value ) { + $cache_key = 'filter_result_' . md5( serialize( $value ) ); + $cached = wp_cache_get( $cache_key ); + + if ( false !== $cached ) { + return $cached; + } + + $result = process_value( $value ); + wp_cache_set( $cache_key, $result, '', HOUR_IN_SECONDS ); + + return $result; +} ); +``` + +--- + +## WP Cron Optimization + +### Disable Front-end Cron + +```php +// wp-config.php +define( 'DISABLE_WP_CRON', true ); +``` + +### Use Server Cron + +```bash +# Crontab entry - run every minute +* * * * * cd /path/to/wordpress && wp cron event run --due-now +``` + +### VIP Cron Control + +On WordPress VIP, cron is handled by Automattic Cron Control plugin automatically. + +--- + +## Batch Processing + +### Avoid Loading All Posts + +```php +// INCORRECT - Loads all posts into memory +$posts = get_posts( array( 'numberposts' => -1 ) ); +foreach ( $posts as $post ) { + process( $post ); +} + +// CORRECT - Batch processing +$paged = 1; +do { + $posts = get_posts( array( + 'numberposts' => 100, + 'paged' => $paged, + ) ); + + foreach ( $posts as $post ) { + process( $post ); + } + + $paged++; +} while ( count( $posts ) === 100 ); +``` + +### Avoid Remote Requests in Loops + +```php +// INCORRECT - Multiple requests +foreach ( $items as $item ) { + $data = wp_remote_get( $item['url'] ); +} + +// CORRECT - Batch or background process +wp_schedule_single_event( time(), 'process_items_batch', array( $items ) ); +``` + +--- + +## Asset Loading + +### Conditional Loading + +```php +add_action( 'wp_enqueue_scripts', function() { + // Only load on specific pages + if ( is_singular( 'product' ) ) { + wp_enqueue_script( 'product-gallery' ); + } + + // Only load when shortcode is present + global $post; + if ( has_shortcode( $post->post_content, 'my_shortcode' ) ) { + wp_enqueue_script( 'my-shortcode-script' ); + } +} ); +``` + +### Defer Non-Critical Scripts + +```php +add_filter( 'script_loader_tag', function( $tag, $handle ) { + $defer_scripts = array( 'analytics', 'tracking' ); + + if ( in_array( $handle, $defer_scripts, true ) ) { + return str_replace( ' src', ' defer src', $tag ); + } + + return $tag; +}, 10, 2 ); +``` + +--- + +## Tooling + +```bash +# Query Monitor - Identify slow queries +# Install Query Monitor plugin + +# New Relic - Performance monitoring +# Available on VIP platform + +# VIP Code Analysis +vip @mysite.production -- wp vip migration analyze +``` From f29ee235d2491a8817b1c044e7bb19350fc41cac Mon Sep 17 00:00:00 2001 From: Sherv Date: Wed, 11 Feb 2026 19:54:31 +0300 Subject: [PATCH 47/90] feat(windsurf): add WordPress VIP PHP coding standards rule with database queries, forbidden functions, and security patterns Add comprehensive VIP PHP standards covering database query methods (get_results/get_row/get_var/get_col with prepare and LIMIT requirements), forbidden functions table (extract, eval, create_function, file_get_contents, curl, serialize with alternatives), WordPress HTTP API for remote requests (wp_remote_get/post with timeout and SSL verification), WP_Filesystem for file operations, --- .windsurf/rules/wordpress-vip/wpvip-php.md | 585 +++++++++++++++++++++ 1 file changed, 585 insertions(+) create mode 100644 .windsurf/rules/wordpress-vip/wpvip-php.md diff --git a/.windsurf/rules/wordpress-vip/wpvip-php.md b/.windsurf/rules/wordpress-vip/wpvip-php.md new file mode 100644 index 0000000000..f2e503c28a --- /dev/null +++ b/.windsurf/rules/wordpress-vip/wpvip-php.md @@ -0,0 +1,585 @@ +--- +trigger: glob +globs: ["**/*.php"] +description: WordPress VIP coding standards for performance and security. Auto-applies to PHP files. +--- + +# WordPress VIP Standards + +WordPress VIP platform requirements for performance, security, and scalability. + +**Reference:** [WordPress VIP Documentation](https://docs.wpvip.com/) + +--- + +## 1. Database Queries + +### Use Proper Methods + +Never use `$wpdb->query()` for SELECT statements. Use specific methods. + +| Method | Use Case | +|--------|----------| +| `$wpdb->get_results()` | Multiple rows | +| `$wpdb->get_row()` | Single row | +| `$wpdb->get_var()` | Single value | +| `$wpdb->get_col()` | Single column | + +```php +// Correct +$posts = $wpdb->get_results( + $wpdb->prepare( + "SELECT * FROM $wpdb->posts WHERE post_status = %s", + 'publish' + ) +); + +// Incorrect +$posts = $wpdb->query( "SELECT * FROM $wpdb->posts" ); +``` + +### Always Use Prepare + +Always use `$wpdb->prepare()` for queries with variables. + +```php +$wpdb->query( + $wpdb->prepare( + "UPDATE $wpdb->posts SET post_title = %s WHERE ID = %d", + $title, + $id + ) +); +``` + +### Limit Results + +Always include LIMIT clause to prevent unbounded queries. + +```php +$wpdb->get_results( + $wpdb->prepare( + "SELECT * FROM $wpdb->posts WHERE post_type = %s LIMIT %d", + 'page', + 100 + ) +); +``` + +### Avoid Direct Queries When Possible + +Use WordPress functions instead of direct queries. + +```php +// Prefer this +$posts = get_posts( array( + 'post_type' => 'post', + 'posts_per_page' => 10, +) ); + +// Over direct query +$posts = $wpdb->get_results( "SELECT * FROM $wpdb->posts LIMIT 10" ); +``` + +--- + +## 2. Forbidden Functions + +Never use these functions: + +| Function | Reason | Alternative | +|----------|--------|-------------| +| `extract()` | Makes code unpredictable | Access array keys directly | +| `eval()` | Security vulnerability | Refactor code logic | +| `create_function()` | Deprecated, insecure | Use anonymous functions | +| `compact()` | Reduces readability | Build arrays explicitly | +| `file_get_contents()` for URLs | Unreliable, no error handling | `wp_remote_get()` | +| `file_put_contents()` | Direct file access | WP_Filesystem | +| `curl_*` functions | Inconsistent behavior | WP HTTP API | +| `serialize()`/`unserialize()` for user data | Security risk | `json_encode()`/`json_decode()` | + +--- + +## 3. Remote Requests + +### HTTP API + +Use WordPress HTTP API for all remote requests. + +```php +// GET request +$response = wp_remote_get( 'https://api.example.com/data' ); + +if ( is_wp_error( $response ) ) { + $error_message = $response->get_error_message(); + // Handle error +} else { + $body = wp_remote_retrieve_body( $response ); + $data = json_decode( $body, true ); +} + +// POST request +$response = wp_remote_post( + 'https://api.example.com/submit', + array( + 'body' => array( + 'key' => 'value', + ), + 'timeout' => 30, + ) +); +``` + +### Timeouts + +Always set appropriate timeouts. + +```php +$response = wp_remote_get( + $url, + array( + 'timeout' => 15, + ) +); +``` + +### SSL Verification + +Never disable SSL verification in production. + +```php +// INCORRECT - Never do this +$response = wp_remote_get( + $url, + array( + 'sslverify' => false, + ) +); +``` + +--- + +## 4. File Operations + +### WP_Filesystem + +Use WP_Filesystem for file operations. + +```php +global $wp_filesystem; + +if ( ! function_exists( 'WP_Filesystem' ) ) { + require_once ABSPATH . 'wp-admin/includes/file.php'; +} + +WP_Filesystem(); + +// Read file +$content = $wp_filesystem->get_contents( $file_path ); + +// Write file +$wp_filesystem->put_contents( $file_path, $content, FS_CHMOD_FILE ); + +// Check if file exists +if ( $wp_filesystem->exists( $file_path ) ) { + // File exists +} +``` + +### Upload Handling + +```php +if ( ! function_exists( 'wp_handle_upload' ) ) { + require_once ABSPATH . 'wp-admin/includes/file.php'; +} + +$upload = wp_handle_upload( + $_FILES['file'], + array( + 'test_form' => false, + 'mimes' => array( + 'jpg|jpeg' => 'image/jpeg', + 'png' => 'image/png', + ), + ) +); + +if ( isset( $upload['error'] ) ) { + // Handle error +} +``` + +--- + +## 5. Caching + +### Transients + +Use transients for cached data. + +```php +$data = get_transient( 'my_cache_key' ); + +if ( false === $data ) { + $data = expensive_operation(); + set_transient( 'my_cache_key', $data, HOUR_IN_SECONDS ); +} + +return $data; +``` + +### Object Cache + +Use object cache for frequently accessed data. + +```php +$data = wp_cache_get( 'key', 'group' ); + +if ( false === $data ) { + $data = expensive_operation(); + wp_cache_set( 'key', $data, 'group', HOUR_IN_SECONDS ); +} + +return $data; +``` + +### Cache Keys + +Use descriptive and unique cache keys. + +```php +$cache_key = sprintf( 'user_posts_%d_%s', $user_id, $post_type ); +``` + +### Cache Invalidation + +Invalidate cache when data changes. + +```php +add_action( 'save_post', function( $post_id ) { + delete_transient( 'posts_cache' ); + wp_cache_delete( 'posts_list', 'my_plugin' ); +} ); +``` + +--- + +## 6. Escaping Output + +### Escape Late + +Escape data at output, not at assignment. + +```php +// Correct - escape at output +echo esc_html( $title ); + +// Incorrect - escape at assignment then output later +$title = esc_html( $raw_title ); +// ... later +echo $title; // Might be double-escaped or bypassed +``` + +### Escaping Functions + +| Function | Use Case | +|----------|----------| +| `esc_html()` | HTML element content | +| `esc_attr()` | HTML attributes | +| `esc_url()` | URLs | +| `esc_js()` | Inline JavaScript | +| `esc_textarea()` | Textarea content | +| `wp_kses_post()` | Allow safe HTML | +| `wp_kses()` | Custom allowed HTML | + +```php +
+

+ + + +
+ +
+
+``` + +### Translation with Escaping + +```php +echo esc_html__( 'Translated text', 'textdomain' ); +echo esc_attr__( 'Attribute text', 'textdomain' ); +printf( + esc_html__( 'Hello, %s!', 'textdomain' ), + esc_html( $name ) +); +``` + +--- + +## 7. Sanitizing Input + +### Sanitize Early + +Sanitize user input as early as possible. + +```php +$title = sanitize_text_field( wp_unslash( $_POST['title'] ) ); +$email = sanitize_email( $_POST['email'] ); +$url = esc_url_raw( $_POST['url'] ); +$key = sanitize_key( $_POST['key'] ); +$ids = array_map( 'absint', $_POST['ids'] ); +``` + +### Sanitization Functions + +| Function | Use Case | +|----------|----------| +| `sanitize_text_field()` | Single line text | +| `sanitize_textarea_field()` | Multi-line text | +| `sanitize_email()` | Email addresses | +| `sanitize_file_name()` | File names | +| `sanitize_key()` | Keys and slugs | +| `sanitize_title()` | Titles and slugs | +| `absint()` | Positive integers | +| `intval()` | Integers | +| `wp_kses()` | HTML with allowed tags | + +### Nonce Verification + +Always verify nonces for form submissions. + +```php +// In form +wp_nonce_field( 'my_action', 'my_nonce' ); + +// On submission +if ( ! isset( $_POST['my_nonce'] ) || + ! wp_verify_nonce( $_POST['my_nonce'], 'my_action' ) ) { + wp_die( 'Security check failed' ); +} +``` + +### Capability Checks + +Always verify user capabilities. + +```php +if ( ! current_user_can( 'edit_posts' ) ) { + wp_die( 'Unauthorized access' ); +} +``` + +--- + +## 8. Query Optimization + +### Avoid Meta Queries on Large Tables + +Meta queries do not scale. Consider alternatives: + +- Custom tables for high-volume data +- Taxonomy terms for filterable data +- Caching query results + +### Efficient Post Queries + +```php +$args = array( + 'post_type' => 'post', + 'posts_per_page' => 10, + 'no_found_rows' => true, // Skip pagination count + 'update_post_meta_cache' => false, // Skip meta cache if not needed + 'update_post_term_cache' => false, // Skip term cache if not needed + 'fields' => 'ids', // Only get IDs if that is all you need +); + +$query = new WP_Query( $args ); +``` + +### Avoid posts_per_page = -1 + +Never get all posts without limit. + +```php +// Incorrect +$args = array( + 'posts_per_page' => -1, +); + +// Correct - use reasonable limit +$args = array( + 'posts_per_page' => 100, +); +``` + +### Use Proper Indexing + +Ensure custom queries use indexed columns. + +--- + +## 9. Error Handling + +### No Error Suppression + +Never use `@` operator. + +```php +// Incorrect +$value = @file_get_contents( $file ); + +// Correct +if ( file_exists( $file ) && is_readable( $file ) ) { + $value = file_get_contents( $file ); +} else { + $value = false; +} +``` + +### Proper Error Checking + +```php +$result = some_operation(); + +if ( is_wp_error( $result ) ) { + error_log( 'Operation failed: ' . $result->get_error_message() ); + return false; +} + +return $result; +``` + +### Try-Catch for Exceptions + +```php +try { + $result = risky_operation(); +} catch ( Exception $e ) { + error_log( 'Exception: ' . $e->getMessage() ); + return new WP_Error( 'operation_failed', $e->getMessage() ); +} +``` + +--- + +## 10. Performance Best Practices + +### Lazy Loading + +Load resources only when needed. + +```php +add_action( 'wp_enqueue_scripts', function() { + if ( is_singular( 'product' ) ) { + wp_enqueue_script( 'product-gallery' ); + } +} ); +``` + +### Avoid Loading All Posts + +```php +// Incorrect - loads all posts into memory +$posts = get_posts( array( 'numberposts' => -1 ) ); +foreach ( $posts as $post ) { + // Process +} + +// Correct - batch processing +$paged = 1; +do { + $posts = get_posts( array( + 'numberposts' => 100, + 'paged' => $paged, + ) ); + + foreach ( $posts as $post ) { + // Process + } + + $paged++; +} while ( count( $posts ) === 100 ); +``` + +### Avoid Remote Requests in Loops + +```php +// Incorrect +foreach ( $items as $item ) { + $data = wp_remote_get( $item['url'] ); +} + +// Correct - batch or cache +$cached = get_transient( 'batch_data' ); +if ( false === $cached ) { + // Make single batch request or queue for background processing +} +``` + +--- + +## 11. Background Processing + +### WP Cron + +```php +// Schedule event +if ( ! wp_next_scheduled( 'my_cron_event' ) ) { + wp_schedule_event( time(), 'hourly', 'my_cron_event' ); +} + +// Handle event +add_action( 'my_cron_event', 'my_cron_function' ); +function my_cron_function() { + // Perform background task +} + +// Unschedule on deactivation +register_deactivation_hook( __FILE__, function() { + wp_clear_scheduled_hook( 'my_cron_event' ); +} ); +``` + +### Action Scheduler + +For more complex background jobs, use Action Scheduler library. + +--- + +## 12. Logging + +### Use Error Log + +```php +if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { + error_log( 'Debug message: ' . print_r( $data, true ) ); +} +``` + +### Never Log Sensitive Data + +```php +// INCORRECT - logs password +error_log( 'User login: ' . $username . ' / ' . $password ); + +// Correct +error_log( 'User login attempt: ' . $username ); +``` + +--- + +## Tooling + +```bash +# Install VIP Coding Standards +composer require automattic/vipwpcs + +# Configure phpcs.xml + + + + +# Run check +./vendor/bin/phpcs --standard=WordPress-VIP-Go path/to/file.php +``` From 465b1f1266f451a4d9f33350fb87194b92cac4ff Mon Sep 17 00:00:00 2001 From: Sherv Date: Wed, 11 Feb 2026 19:54:59 +0300 Subject: [PATCH 48/90] feat(windsurf): add WordPress VIP security standards rule with OWASP-based XSS prevention, SQL injection protection, and authentication patterns Add comprehensive VIP security standards covering XSS prevention (escape late principle, escaping functions table for HTML/attributes/URLs/JS, JavaScript XSS with DOMPurify), SQL injection prevention (prepare placeholders %d/%f/%s/%i, WordPress API preference), input sanitization (sanitize_text_field/sanitize_email/sanitize_key with function reference table --- .../rules/wordpress-vip/wpvip-security.md | 351 ++++++++++++++++++ 1 file changed, 351 insertions(+) create mode 100644 .windsurf/rules/wordpress-vip/wpvip-security.md diff --git a/.windsurf/rules/wordpress-vip/wpvip-security.md b/.windsurf/rules/wordpress-vip/wpvip-security.md new file mode 100644 index 0000000000..d47b0bd88e --- /dev/null +++ b/.windsurf/rules/wordpress-vip/wpvip-security.md @@ -0,0 +1,351 @@ +--- +trigger: glob +globs: ['**/*.php', '**/*.js'] +description: WordPress VIP security standards based on OWASP guidelines. Auto-applies to PHP and JS files. +--- + +# WordPress VIP Security Standards + +Enterprise-level security for WordPress VIP platform based on OWASP Top 10. + +**Reference:** [WordPress VIP Learn - Enterprise Security](https://learn.wpvip.com/) + +--- + +## XSS Prevention + +### Escape Late + +Escape data immediately before output, not at assignment. + +```php +// CORRECT - Escape at output +echo esc_html( $title ); + +// INCORRECT - Escape at assignment +$title = esc_html( $raw_title ); +// ... later +echo $title; // May be double-escaped or bypassed +``` + +### Escaping Functions + +| Function | Use Case | +| -------- | -------- | +| `esc_html()` | HTML element content | +| `esc_attr()` | HTML attributes | +| `esc_url()` | URLs and links | +| `esc_js()` | Inline JavaScript | +| `esc_textarea()` | Textarea content | +| `wp_kses_post()` | Allow safe HTML | +| `wp_kses()` | Custom allowed HTML | + +### Output Examples + +```php +
+

+ + + +
+ +
+
+``` + +### JavaScript XSS Prevention + +```php +// Pass data to JavaScript safely +wp_localize_script( 'my-script', 'myData', array( + 'title' => esc_js( $title ), + 'data' => wp_json_encode( $data ), +) ); +``` + +```javascript +// UNSAFE - Never use +element.innerHTML = userData; +$( element ).html( userData ); +eval( userInput ); + +// SAFE - Programmatic DOM manipulation +const text = document.createTextNode( userData ); +element.appendChild( text ); + +// SAFE - Use DOMPurify for HTML +import DOMPurify from 'dompurify'; +element.innerHTML = DOMPurify.sanitize( userData ); +``` + +--- + +## SQL Injection Prevention + +### Always Use Prepare + +```php +$wpdb->query( + $wpdb->prepare( + "UPDATE $wpdb->posts SET post_title = %s WHERE ID = %d", + $title, + $id + ) +); +``` + +### Prepare Placeholders + +| Placeholder | Type | +| ----------- | ---- | +| `%d` | Integer | +| `%f` | Float | +| `%s` | String | +| `%i` | Identifier (table/field names) | + +### Use WordPress APIs + +```php +// PREFERRED - WordPress functions +$posts = get_posts( array( + 'post_type' => 'post', + 'posts_per_page' => 10, +) ); + +// AVOID - Direct queries when possible +$posts = $wpdb->get_results( "SELECT * FROM $wpdb->posts LIMIT 10" ); +``` + +--- + +## Input Sanitization + +### Sanitize Early + +```php +$title = sanitize_text_field( wp_unslash( $_POST['title'] ) ); +$email = sanitize_email( $_POST['email'] ); +$url = esc_url_raw( $_POST['url'] ); +$key = sanitize_key( $_POST['key'] ); +$ids = array_map( 'absint', $_POST['ids'] ); +``` + +### Sanitization Functions + +| Function | Use Case | +| -------- | -------- | +| `sanitize_text_field()` | Single line text | +| `sanitize_textarea_field()` | Multi-line text | +| `sanitize_email()` | Email addresses | +| `sanitize_file_name()` | File names | +| `sanitize_key()` | Keys and slugs | +| `sanitize_title()` | Titles and slugs | +| `absint()` | Positive integers | +| `intval()` | Integers | +| `wp_kses()` | HTML with allowed tags | + +--- + +## Input Validation + +### Validate Against Trusted Values + +```php +// CORRECT - Validate against allowed list +$allowed = array( 'draft', 'publish', 'pending' ); +if ( ! in_array( $status, $allowed, true ) ) { + wp_die( 'Invalid status' ); +} + +// Use strict comparison +if ( $value === 'expected' ) { + // Process +} +``` + +--- + +## Authentication & Authorization + +### Nonce Verification + +```php +// In form +wp_nonce_field( 'my_action', 'my_nonce' ); + +// On submission +if ( ! isset( $_POST['my_nonce'] ) || + ! wp_verify_nonce( $_POST['my_nonce'], 'my_action' ) ) { + wp_die( 'Security check failed' ); +} +``` + +### AJAX Nonce + +```php +// Enqueue with nonce +wp_localize_script( 'my-script', 'myAjax', array( + 'ajaxurl' => admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( 'my_ajax_action' ), +) ); + +// Verify in handler +add_action( 'wp_ajax_my_action', function() { + check_ajax_referer( 'my_ajax_action', 'nonce' ); + // Process request +} ); +``` + +### Capability Checks + +```php +if ( ! current_user_can( 'edit_posts' ) ) { + wp_die( 'Unauthorized access' ); +} + +// Check specific post +if ( ! current_user_can( 'edit_post', $post_id ) ) { + wp_die( 'You cannot edit this post' ); +} +``` + +--- + +## File Operations + +### Use WP_Filesystem + +```php +global $wp_filesystem; + +if ( ! function_exists( 'WP_Filesystem' ) ) { + require_once ABSPATH . 'wp-admin/includes/file.php'; +} + +WP_Filesystem(); + +// Read file +$content = $wp_filesystem->get_contents( $file_path ); + +// Write file +$wp_filesystem->put_contents( $file_path, $content, FS_CHMOD_FILE ); +``` + +### Upload Validation + +```php +$upload = wp_handle_upload( + $_FILES['file'], + array( + 'test_form' => false, + 'mimes' => array( + 'jpg|jpeg' => 'image/jpeg', + 'png' => 'image/png', + 'pdf' => 'application/pdf', + ), + ) +); + +if ( isset( $upload['error'] ) ) { + wp_die( $upload['error'] ); +} +``` + +--- + +## Remote Requests + +### Use WordPress HTTP API + +```php +$response = wp_remote_get( 'https://api.example.com/data', array( + 'timeout' => 15, +) ); + +if ( is_wp_error( $response ) ) { + error_log( 'API Error: ' . $response->get_error_message() ); + return false; +} + +$body = wp_remote_retrieve_body( $response ); +$data = json_decode( $body, true ); +``` + +### Never Disable SSL Verification + +```php +// NEVER do this in production +$response = wp_remote_get( $url, array( + 'sslverify' => false, // DANGEROUS +) ); +``` + +--- + +## Forbidden Functions + +| Function | Reason | Alternative | +| -------- | ------ | ----------- | +| `extract()` | Unpredictable | Access array keys directly | +| `eval()` | Security vulnerability | Refactor code logic | +| `create_function()` | Deprecated, insecure | Anonymous functions | +| `serialize()` for user data | Security risk | `json_encode()` | +| `file_get_contents()` for URLs | Unreliable | `wp_remote_get()` | +| `curl_*` functions | Inconsistent | WP HTTP API | + +--- + +## Error Handling + +### No Error Suppression + +```php +// INCORRECT +$value = @file_get_contents( $file ); + +// CORRECT +if ( file_exists( $file ) && is_readable( $file ) ) { + $value = file_get_contents( $file ); +} else { + $value = false; +} +``` + +### Proper Error Checking + +```php +$result = some_operation(); + +if ( is_wp_error( $result ) ) { + error_log( 'Operation failed: ' . $result->get_error_message() ); + return false; +} + +return $result; +``` + +### Never Log Sensitive Data + +```php +// INCORRECT +error_log( 'Login: ' . $username . ' / ' . $password ); + +// CORRECT +error_log( 'Login attempt: ' . $username ); +``` + +--- + +## Tooling + +```bash +# VIP PHPCS +composer require automattic/vipwpcs + +# Run check +./vendor/bin/phpcs --standard=WordPress-VIP-Go path/to/file.php + +# Snyk for vulnerability scanning +snyk test +``` From 046ca723208bc90334972423e92e61200c24e54e Mon Sep 17 00:00:00 2001 From: Sherv Date: Wed, 11 Feb 2026 19:55:40 +0300 Subject: [PATCH 49/90] refactor(windsurf): remove redundant WordPress VIP PHP standards rule as content is covered in wpvip-performance.md and wpvip-security.md Remove wpvip-php.md containing database query methods, forbidden functions, remote requests, file operations, caching, escaping, sanitization, query optimization, error handling, and performance patterns. This content is now comprehensively covered across wpvip-performance.md (database optimization, caching strategies, query patterns) and wpvip-security.md (esc --- .windsurf/rules/wordpress-vip/wpvip-php.md | 585 --------------------- 1 file changed, 585 deletions(-) delete mode 100644 .windsurf/rules/wordpress-vip/wpvip-php.md diff --git a/.windsurf/rules/wordpress-vip/wpvip-php.md b/.windsurf/rules/wordpress-vip/wpvip-php.md deleted file mode 100644 index f2e503c28a..0000000000 --- a/.windsurf/rules/wordpress-vip/wpvip-php.md +++ /dev/null @@ -1,585 +0,0 @@ ---- -trigger: glob -globs: ["**/*.php"] -description: WordPress VIP coding standards for performance and security. Auto-applies to PHP files. ---- - -# WordPress VIP Standards - -WordPress VIP platform requirements for performance, security, and scalability. - -**Reference:** [WordPress VIP Documentation](https://docs.wpvip.com/) - ---- - -## 1. Database Queries - -### Use Proper Methods - -Never use `$wpdb->query()` for SELECT statements. Use specific methods. - -| Method | Use Case | -|--------|----------| -| `$wpdb->get_results()` | Multiple rows | -| `$wpdb->get_row()` | Single row | -| `$wpdb->get_var()` | Single value | -| `$wpdb->get_col()` | Single column | - -```php -// Correct -$posts = $wpdb->get_results( - $wpdb->prepare( - "SELECT * FROM $wpdb->posts WHERE post_status = %s", - 'publish' - ) -); - -// Incorrect -$posts = $wpdb->query( "SELECT * FROM $wpdb->posts" ); -``` - -### Always Use Prepare - -Always use `$wpdb->prepare()` for queries with variables. - -```php -$wpdb->query( - $wpdb->prepare( - "UPDATE $wpdb->posts SET post_title = %s WHERE ID = %d", - $title, - $id - ) -); -``` - -### Limit Results - -Always include LIMIT clause to prevent unbounded queries. - -```php -$wpdb->get_results( - $wpdb->prepare( - "SELECT * FROM $wpdb->posts WHERE post_type = %s LIMIT %d", - 'page', - 100 - ) -); -``` - -### Avoid Direct Queries When Possible - -Use WordPress functions instead of direct queries. - -```php -// Prefer this -$posts = get_posts( array( - 'post_type' => 'post', - 'posts_per_page' => 10, -) ); - -// Over direct query -$posts = $wpdb->get_results( "SELECT * FROM $wpdb->posts LIMIT 10" ); -``` - ---- - -## 2. Forbidden Functions - -Never use these functions: - -| Function | Reason | Alternative | -|----------|--------|-------------| -| `extract()` | Makes code unpredictable | Access array keys directly | -| `eval()` | Security vulnerability | Refactor code logic | -| `create_function()` | Deprecated, insecure | Use anonymous functions | -| `compact()` | Reduces readability | Build arrays explicitly | -| `file_get_contents()` for URLs | Unreliable, no error handling | `wp_remote_get()` | -| `file_put_contents()` | Direct file access | WP_Filesystem | -| `curl_*` functions | Inconsistent behavior | WP HTTP API | -| `serialize()`/`unserialize()` for user data | Security risk | `json_encode()`/`json_decode()` | - ---- - -## 3. Remote Requests - -### HTTP API - -Use WordPress HTTP API for all remote requests. - -```php -// GET request -$response = wp_remote_get( 'https://api.example.com/data' ); - -if ( is_wp_error( $response ) ) { - $error_message = $response->get_error_message(); - // Handle error -} else { - $body = wp_remote_retrieve_body( $response ); - $data = json_decode( $body, true ); -} - -// POST request -$response = wp_remote_post( - 'https://api.example.com/submit', - array( - 'body' => array( - 'key' => 'value', - ), - 'timeout' => 30, - ) -); -``` - -### Timeouts - -Always set appropriate timeouts. - -```php -$response = wp_remote_get( - $url, - array( - 'timeout' => 15, - ) -); -``` - -### SSL Verification - -Never disable SSL verification in production. - -```php -// INCORRECT - Never do this -$response = wp_remote_get( - $url, - array( - 'sslverify' => false, - ) -); -``` - ---- - -## 4. File Operations - -### WP_Filesystem - -Use WP_Filesystem for file operations. - -```php -global $wp_filesystem; - -if ( ! function_exists( 'WP_Filesystem' ) ) { - require_once ABSPATH . 'wp-admin/includes/file.php'; -} - -WP_Filesystem(); - -// Read file -$content = $wp_filesystem->get_contents( $file_path ); - -// Write file -$wp_filesystem->put_contents( $file_path, $content, FS_CHMOD_FILE ); - -// Check if file exists -if ( $wp_filesystem->exists( $file_path ) ) { - // File exists -} -``` - -### Upload Handling - -```php -if ( ! function_exists( 'wp_handle_upload' ) ) { - require_once ABSPATH . 'wp-admin/includes/file.php'; -} - -$upload = wp_handle_upload( - $_FILES['file'], - array( - 'test_form' => false, - 'mimes' => array( - 'jpg|jpeg' => 'image/jpeg', - 'png' => 'image/png', - ), - ) -); - -if ( isset( $upload['error'] ) ) { - // Handle error -} -``` - ---- - -## 5. Caching - -### Transients - -Use transients for cached data. - -```php -$data = get_transient( 'my_cache_key' ); - -if ( false === $data ) { - $data = expensive_operation(); - set_transient( 'my_cache_key', $data, HOUR_IN_SECONDS ); -} - -return $data; -``` - -### Object Cache - -Use object cache for frequently accessed data. - -```php -$data = wp_cache_get( 'key', 'group' ); - -if ( false === $data ) { - $data = expensive_operation(); - wp_cache_set( 'key', $data, 'group', HOUR_IN_SECONDS ); -} - -return $data; -``` - -### Cache Keys - -Use descriptive and unique cache keys. - -```php -$cache_key = sprintf( 'user_posts_%d_%s', $user_id, $post_type ); -``` - -### Cache Invalidation - -Invalidate cache when data changes. - -```php -add_action( 'save_post', function( $post_id ) { - delete_transient( 'posts_cache' ); - wp_cache_delete( 'posts_list', 'my_plugin' ); -} ); -``` - ---- - -## 6. Escaping Output - -### Escape Late - -Escape data at output, not at assignment. - -```php -// Correct - escape at output -echo esc_html( $title ); - -// Incorrect - escape at assignment then output later -$title = esc_html( $raw_title ); -// ... later -echo $title; // Might be double-escaped or bypassed -``` - -### Escaping Functions - -| Function | Use Case | -|----------|----------| -| `esc_html()` | HTML element content | -| `esc_attr()` | HTML attributes | -| `esc_url()` | URLs | -| `esc_js()` | Inline JavaScript | -| `esc_textarea()` | Textarea content | -| `wp_kses_post()` | Allow safe HTML | -| `wp_kses()` | Custom allowed HTML | - -```php -
-

- - - -
- -
-
-``` - -### Translation with Escaping - -```php -echo esc_html__( 'Translated text', 'textdomain' ); -echo esc_attr__( 'Attribute text', 'textdomain' ); -printf( - esc_html__( 'Hello, %s!', 'textdomain' ), - esc_html( $name ) -); -``` - ---- - -## 7. Sanitizing Input - -### Sanitize Early - -Sanitize user input as early as possible. - -```php -$title = sanitize_text_field( wp_unslash( $_POST['title'] ) ); -$email = sanitize_email( $_POST['email'] ); -$url = esc_url_raw( $_POST['url'] ); -$key = sanitize_key( $_POST['key'] ); -$ids = array_map( 'absint', $_POST['ids'] ); -``` - -### Sanitization Functions - -| Function | Use Case | -|----------|----------| -| `sanitize_text_field()` | Single line text | -| `sanitize_textarea_field()` | Multi-line text | -| `sanitize_email()` | Email addresses | -| `sanitize_file_name()` | File names | -| `sanitize_key()` | Keys and slugs | -| `sanitize_title()` | Titles and slugs | -| `absint()` | Positive integers | -| `intval()` | Integers | -| `wp_kses()` | HTML with allowed tags | - -### Nonce Verification - -Always verify nonces for form submissions. - -```php -// In form -wp_nonce_field( 'my_action', 'my_nonce' ); - -// On submission -if ( ! isset( $_POST['my_nonce'] ) || - ! wp_verify_nonce( $_POST['my_nonce'], 'my_action' ) ) { - wp_die( 'Security check failed' ); -} -``` - -### Capability Checks - -Always verify user capabilities. - -```php -if ( ! current_user_can( 'edit_posts' ) ) { - wp_die( 'Unauthorized access' ); -} -``` - ---- - -## 8. Query Optimization - -### Avoid Meta Queries on Large Tables - -Meta queries do not scale. Consider alternatives: - -- Custom tables for high-volume data -- Taxonomy terms for filterable data -- Caching query results - -### Efficient Post Queries - -```php -$args = array( - 'post_type' => 'post', - 'posts_per_page' => 10, - 'no_found_rows' => true, // Skip pagination count - 'update_post_meta_cache' => false, // Skip meta cache if not needed - 'update_post_term_cache' => false, // Skip term cache if not needed - 'fields' => 'ids', // Only get IDs if that is all you need -); - -$query = new WP_Query( $args ); -``` - -### Avoid posts_per_page = -1 - -Never get all posts without limit. - -```php -// Incorrect -$args = array( - 'posts_per_page' => -1, -); - -// Correct - use reasonable limit -$args = array( - 'posts_per_page' => 100, -); -``` - -### Use Proper Indexing - -Ensure custom queries use indexed columns. - ---- - -## 9. Error Handling - -### No Error Suppression - -Never use `@` operator. - -```php -// Incorrect -$value = @file_get_contents( $file ); - -// Correct -if ( file_exists( $file ) && is_readable( $file ) ) { - $value = file_get_contents( $file ); -} else { - $value = false; -} -``` - -### Proper Error Checking - -```php -$result = some_operation(); - -if ( is_wp_error( $result ) ) { - error_log( 'Operation failed: ' . $result->get_error_message() ); - return false; -} - -return $result; -``` - -### Try-Catch for Exceptions - -```php -try { - $result = risky_operation(); -} catch ( Exception $e ) { - error_log( 'Exception: ' . $e->getMessage() ); - return new WP_Error( 'operation_failed', $e->getMessage() ); -} -``` - ---- - -## 10. Performance Best Practices - -### Lazy Loading - -Load resources only when needed. - -```php -add_action( 'wp_enqueue_scripts', function() { - if ( is_singular( 'product' ) ) { - wp_enqueue_script( 'product-gallery' ); - } -} ); -``` - -### Avoid Loading All Posts - -```php -// Incorrect - loads all posts into memory -$posts = get_posts( array( 'numberposts' => -1 ) ); -foreach ( $posts as $post ) { - // Process -} - -// Correct - batch processing -$paged = 1; -do { - $posts = get_posts( array( - 'numberposts' => 100, - 'paged' => $paged, - ) ); - - foreach ( $posts as $post ) { - // Process - } - - $paged++; -} while ( count( $posts ) === 100 ); -``` - -### Avoid Remote Requests in Loops - -```php -// Incorrect -foreach ( $items as $item ) { - $data = wp_remote_get( $item['url'] ); -} - -// Correct - batch or cache -$cached = get_transient( 'batch_data' ); -if ( false === $cached ) { - // Make single batch request or queue for background processing -} -``` - ---- - -## 11. Background Processing - -### WP Cron - -```php -// Schedule event -if ( ! wp_next_scheduled( 'my_cron_event' ) ) { - wp_schedule_event( time(), 'hourly', 'my_cron_event' ); -} - -// Handle event -add_action( 'my_cron_event', 'my_cron_function' ); -function my_cron_function() { - // Perform background task -} - -// Unschedule on deactivation -register_deactivation_hook( __FILE__, function() { - wp_clear_scheduled_hook( 'my_cron_event' ); -} ); -``` - -### Action Scheduler - -For more complex background jobs, use Action Scheduler library. - ---- - -## 12. Logging - -### Use Error Log - -```php -if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { - error_log( 'Debug message: ' . print_r( $data, true ) ); -} -``` - -### Never Log Sensitive Data - -```php -// INCORRECT - logs password -error_log( 'User login: ' . $username . ' / ' . $password ); - -// Correct -error_log( 'User login attempt: ' . $username ); -``` - ---- - -## Tooling - -```bash -# Install VIP Coding Standards -composer require automattic/vipwpcs - -# Configure phpcs.xml - - - - -# Run check -./vendor/bin/phpcs --standard=WordPress-VIP-Go path/to/file.php -``` From 1fadde61585619a25a3b32af2fdad3bd352c4dd4 Mon Sep 17 00:00:00 2001 From: Sherv Date: Wed, 11 Feb 2026 20:04:46 +0300 Subject: [PATCH 50/90] feat(windsurf): add WordPress CSS coding standards rule with Formidable Forms patterns, stylelint configuration, and SCSS guidelines Add comprehensive CSS standards covering browser support via .browserslistrc, stylelint configuration with @wordpress/stylelint-config/scss, core rules table (color-hex-length, font-weight-notation, length-zero-no-unit, selector-pseudo-element-colon-notation), structure patterns (tabs, spacing, selector layout), Formidable naming convention with frm- prefix, selector --- .windsurf/rules/formidable/frm-css.md | 497 +++++++++++++++++++++++ .windsurf/rules/wordpress/css.md | 560 -------------------------- 2 files changed, 497 insertions(+), 560 deletions(-) create mode 100644 .windsurf/rules/formidable/frm-css.md delete mode 100644 .windsurf/rules/wordpress/css.md diff --git a/.windsurf/rules/formidable/frm-css.md b/.windsurf/rules/formidable/frm-css.md new file mode 100644 index 0000000000..524296b978 --- /dev/null +++ b/.windsurf/rules/formidable/frm-css.md @@ -0,0 +1,497 @@ +--- +trigger: "glob" +globs: + - "**/*.css" + - "**/*.scss" + - "**/*.less" +description: "WordPress CSS coding standards with Formidable Forms patterns. Auto-applies when working with CSS files." +--- + +# WordPress CSS Coding Standards + +Based on WordPress Core Official Standards and `@wordpress/stylelint-config`. Apply when maintaining, generating, or refactoring CSS code. + +**References:** + +- [WordPress CSS Coding Standards](https://developer.wordpress.org/coding-standards/wordpress-coding-standards/css/) +- [@wordpress/stylelint-config](https://github.com/WordPress/gutenberg/tree/trunk/packages/stylelint-config) + +--- + +## Browser Support + +Formidable extends `@wordpress/browserslist-config`. Check `.browserslistrc` for current targets: + +```text +extends @wordpress/browserslist-config +``` + +Do NOT hardcode browser targets. Always reference the project's `.browserslistrc` file for the authoritative list. + +--- + +## Stylelint Configuration + +Formidable uses `@wordpress/stylelint-config/scss`. See `.stylelintrc.json`: + +```json +{ + "extends": "@wordpress/stylelint-config/scss" +} +``` + +All CSS/SCSS must pass stylelint before commit. + +--- + +## Stylelint Rules (from @wordpress/stylelint-config) + +These rules are enforced by `@wordpress/stylelint-config`: + +### Core Rules + +| Rule | Value | Description | +| ---------------------------------------- | --------- | ---------------------------- | +| `color-hex-length` | `short` | Use `#fff` not `#ffffff` | +| `color-named` | `never` | No named colors like `red` | +| `font-weight-notation` | `numeric` | Use `400` not `normal` | +| `function-url-quotes` | `never` | No quotes in `url()` | +| `length-zero-no-unit` | `true` | Use `0` not `0px` | +| `selector-attribute-quotes` | `always` | Quotes in `[type="text"]` | +| `selector-pseudo-element-colon-notation` | `double` | Use `::before` not `:before` | + +### Selector Pattern + +Selectors should use lowercase with hyphens: + +```css +/* Correct */ +.frm-button-primary { +} +.frm-field-container { +} + +/* Incorrect */ +.frmButtonPrimary { +} +.frm_field_container { +} +``` + +--- + +## 1. Structure + +### Indentation + +Use tabs, not spaces. + +### Spacing + +- One blank line between rule sets +- Empty line before at-rules (except after blockless) +- Newline after opening brace +- Newline before closing brace + +### Selector Layout + +Each selector on its own line. Opening brace on same line as last selector. + +```css +.frm-selector-1, +.frm-selector-2, +.frm-selector-3 { + background: #fff; + color: #000; +} +``` + +--- + +## 2. Selectors + +### Formidable Naming Convention + +Formidable uses the `frm-` prefix for classes: + +```css +.frm-button { +} +.frm-field-container { +} +.frm-dashboard-widget { +} +.frm-counter-card { +} +``` + +### Selector Specificity + +Avoid over-qualification. Do not add unnecessary element selectors. + +```css +/* Incorrect */ +div.frm-container { +} + +/* Correct */ +.frm-container { +} +``` + +### Attribute Selectors + +Always use quotes around attribute values. + +```css +input[type="text"] { +} +input[name="item_meta"] { +} +``` + +### Selector Depth + +Maximum 3-4 levels deep. + +```css +/* Too specific */ +.frm-dashboard-container .frm-widget .frm-card .frm-title .frm-text { +} + +/* Better */ +.frm-widget .frm-title { +} +``` + +--- + +## 3. Properties + +### Formatting + +- Colon followed by single space +- All lowercase for property names and values +- Trailing semicolon required +- No space before colon + +```css +.frm-button { + background-color: #fff; + font-size: 16px; + text-align: center; +} +``` + +### Property Ordering + +Group related properties: + +1. **Display and Box Model** - display, box-sizing, width, height, padding, margin, border +2. **Positioning** - position, top, right, bottom, left, z-index +3. **Typography** - font-family, font-size, font-weight, line-height, text-align, color +4. **Visual** - background, box-shadow, opacity, border-radius +5. **Animation** - transition, transform, animation +6. **Miscellaneous** - cursor, overflow + +### Shorthand Properties + +Use shorthand when setting all values: + +```css +margin: 10px 20px; +padding: var(--gap-sm); +border: 1px solid var(--grey-300); +``` + +--- + +## 4. Values + +### CSS Custom Properties (Variables) + +Formidable uses CSS custom properties extensively. See `resources/scss/admin/base/_variables.scss`: + +```css +:root { + --grey-700: #344054; + --grey-500: #667085; + --grey-300: #d0d5dd; + --primary-500: #4199fd; + --primary-700: #2b66a9; + --error-500: #f04438; + --success-500: #12b76a; + --small-radius: 8px; + --gap-xs: 8px; + --gap-sm: 16px; + --gap-md: 24px; + --text-sm: 14px; + --text-md: 16px; +} + +.frm-button { + background: var(--primary-500); + padding: var(--gap-sm); + border-radius: var(--small-radius); +} +``` + +### Colors + +Use hex codes in lowercase shorthand. Use CSS variables when available. + +```css +/* Use variables */ +color: var(--grey-700); +background: var(--primary-500); + +/* Hex shorthand when no variable */ +color: #fff; +background: #abc; + +/* rgba for transparency */ +background: rgba(16, 24, 40, 0.1); +``` + +### Font Weights + +Use numeric values (required by stylelint). + +| Weight | Value | +| --------- | ----- | +| Light | 300 | +| Normal | 400 | +| Medium | 500 | +| Semi-bold | 600 | +| Bold | 700 | + +### Line Height + +Use unitless values or CSS variables: + +```css +line-height: 1.5; +line-height: var(--leading); +``` + +### Zero Values + +Omit units on zero values (required by stylelint): + +```css +margin: 0; +padding: 0 10px; +border: 0; +``` + +### Decimal Values + +Include leading zero (required by stylelint): + +```css +opacity: 0.5; +``` + +### URL Values + +No quotes in `url()` (required by stylelint): + +```css +background: url(images/background.png); +``` + +### String Quotes + +Use double quotes for strings: + +```css +content: ""; +font-family: "Helvetica Neue", sans-serif; +``` + +--- + +## 5. Media Queries + +### Placement + +Group media queries at the bottom of the stylesheet or use component-based organization with media queries near related rules. + +### Media Query Syntax + +Indent rule sets one level inside media query: + +```css +@media all and (max-width: 699px) and (min-width: 520px) { + .frm-selector { + display: block; + } +} +``` + +### Mobile First Approach + +```css +.frm-widget { + width: 100%; +} + +@media screen and (min-width: 600px) { + .frm-widget { + width: 50%; + } +} + +@media screen and (min-width: 1024px) { + .frm-widget { + width: 33.333%; + } +} +``` + +--- + +## 6. Comments + +### Section Headers + +```css +/** + * Section Name + * + * Description of this section. + */ +``` + +### Inline Comments + +```css +/* Comment before rule */ +.frm-button { + color: var(--grey-700); /* Inline comment */ +} +``` + +--- + +## 7. Best Practices + +### Use CSS Variables + +Prefer CSS custom properties over hardcoded values: + +```css +/* Incorrect */ +.frm-button { + padding: 16px; + color: #344054; +} + +/* Correct */ +.frm-button { + padding: var(--gap-sm); + color: var(--grey-700); +} +``` + +### Avoid Magic Numbers + +Document or calculate values: + +```css +.frm-overlay { + /* Offset = header height (50px) - element height (13px) */ + top: 37px; +} +``` + +### Avoid !important + +If used, document why: + +```css +/* Override third-party plugin styles */ +.frm-override { + color: var(--grey-700) !important; +} +``` + +### Box Sizing + +Formidable uses `border-box` globally: + +```css +*, +*::before, +*::after { + box-sizing: border-box; +} +``` + +--- + +## 8. SCSS Rules + +Formidable uses SCSS with `@wordpress/stylelint-config/scss`. + +### SCSS-Specific Stylelint Rules + +| Rule | Description | +| --------------------------------------------- | --------------------------- | +| `scss/at-else-closing-brace-newline-after` | Newline after closing brace | +| `scss/at-else-empty-line-before` | No empty line before @else | +| `scss/selector-no-redundant-nesting-selector` | No redundant `&` nesting | + +### Nesting + +Maximum 3 levels deep: + +```scss +.frm-block { + .frm-element { + .frm-modifier { + // Stop here + } + } +} +``` + +### Avoid Redundant Nesting + +```scss +/* Incorrect */ +.frm-button { + & .frm-icon { + color: #fff; + } +} + +/* Correct */ +.frm-button { + .frm-icon { + color: #fff; + } +} +``` + +### @if/@else Formatting + +```scss +@if $condition { + color: #fff; +} @else { + color: #000; +} +``` + +--- + +## Tooling + +```bash +# Run stylelint check +npx stylelint "**/*.css" "**/*.scss" + +# Auto-fix issues +npx stylelint "**/*.css" --fix +``` diff --git a/.windsurf/rules/wordpress/css.md b/.windsurf/rules/wordpress/css.md deleted file mode 100644 index aefba00aae..0000000000 --- a/.windsurf/rules/wordpress/css.md +++ /dev/null @@ -1,560 +0,0 @@ ---- -trigger: glob -globs: ["**/*.css", "**/*.scss", "**/*.less"] -description: WordPress CSS coding standards. Auto-applies when working with CSS files. ---- - -# WordPress CSS Coding Standards - -Based on WordPress Core Official Standards. Apply when maintaining, generating, or refactoring CSS code. - -**Reference:** [WordPress CSS Coding Standards](https://developer.wordpress.org/coding-standards/wordpress-coding-standards/css/) - ---- - -## 1. Structure - -### Indentation - -Use tabs, not spaces. - -### Spacing Between Blocks - -- Two blank lines between sections -- One blank line between blocks in a section - -### Selector and Property Layout - -Each selector on its own line. Property-value pairs on own line with one tab indentation. - -```css -#selector-1, -#selector-2, -#selector-3 { - background: #fff; - color: #000; -} -``` - -### Closing Brace - -Closing brace on its own line at same indentation level as opening selector. - -```css -.selector { - property: value; -} -``` - ---- - -## 2. Selectors - -### Naming Convention - -Lowercase letters and hyphens only. No camelCase or underscores. - -```css -#comment-form { -} -.post-title { -} -.sidebar-widget { -} -``` - -### Selector Specificity - -Avoid over-qualification. Do not add unnecessary element selectors. - -```css -/* Incorrect */ -div#comment-form { -} -ul.nav-menu { -} - -/* Correct */ -#comment-form { -} -.nav-menu { -} -``` - -### Attribute Selectors - -Use double quotes around attribute values. - -```css -input[type="text"] { -} -a[href^="https://"] -{ -} -``` - -### Selector Length - -Keep selectors short. Maximum 3-4 levels deep. - -```css -/* Too specific */ -body .page-wrapper .content-area .post-list .post-item .post-title { -} - -/* Better */ -.post-list .post-title { -} -``` - -### Universal Selector - -Avoid universal selector `*` except for specific resets. - -### ID Selectors - -Use sparingly. Prefer classes for reusability. - ---- - -## 3. Properties - -### Formatting - -- Colon followed by a single space -- All lowercase for property names and values -- End with semicolon - -```css -.selector { - background-color: #fff; - font-size: 16px; - text-align: center; -} -``` - -### Property Ordering - -Group related properties together in this order: - -1. Display and Box Model -2. Positioning -3. Typography -4. Visual (colors, backgrounds) -5. Animation -6. Miscellaneous - -```css -.selector { - /* Display and Box Model */ - display: block; - box-sizing: border-box; - width: 100%; - height: auto; - padding: 10px; - margin: 0 auto; - border: 1px solid #ccc; - - /* Positioning */ - position: relative; - top: 0; - left: 0; - z-index: 10; - - /* Typography */ - font-family: "Helvetica Neue", sans-serif; - font-size: 16px; - font-weight: 400; - line-height: 1.5; - text-align: left; - text-transform: none; - color: #333; - - /* Visual */ - background-color: #fff; - background-image: url("image.png"); - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); - opacity: 1; - - /* Animation */ - transition: all 0.3s ease; - transform: translateX(0); - - /* Miscellaneous */ - cursor: pointer; - overflow: hidden; -} -``` - -### Shorthand Properties - -Use shorthand for `background`, `border`, `font`, `margin`, `padding` when setting all values. - -```css -/* Shorthand */ -margin: 10px 20px; -padding: 10px; -border: 1px solid #ccc; - -/* Individual properties when setting one value */ -margin-top: 10px; -padding-left: 20px; -border-bottom: 2px solid #000; -``` - -### Vendor Prefixes - -Place standard property last. - -```css -.selector { - -webkit-transform: rotate(45deg); - -moz-transform: rotate(45deg); - -ms-transform: rotate(45deg); - transform: rotate(45deg); -} -``` - ---- - -## 4. Values - -### Colors - -Use hex codes in lowercase. Use shorthand when possible. - -```css -/* Correct */ -color: #fff; -background: #aabbcc; - -/* For transparency use rgba */ -background: rgba(0, 0, 0, 0.5); -``` - -### Font Weights - -Use numeric values. - -| Weight | Value | -| ---------- | ----- | -| Normal | 400 | -| Bold | 700 | -| Light | 300 | -| Extra Bold | 800 | - -```css -font-weight: 400; -font-weight: 700; -``` - -### Line Height - -Use unitless values for line-height. - -```css -/* Correct */ -line-height: 1.5; - -/* Incorrect */ -line-height: 24px; -line-height: 150%; -``` - -### Zero Values - -Omit units on zero values except for `transition-duration`. - -```css -margin: 0; -padding: 0 10px; -border: 0; - -/* Exception */ -transition-duration: 0s; -``` - -### Decimal Values - -Include leading zero for decimals less than 1. - -```css -opacity: 0.5; -``` - -### Quotes - -Use double quotes. Required for font names with spaces, URL values, and attribute selectors. - -```css -font-family: "Times New Roman", serif; -background: url("image.png"); -content: ""; -``` - -### URL Values - -Do not use quotes in url() for simple paths. - -```css -/* Correct */ -background: url(images/background.png); - -/* Also correct for paths with special characters */ -background: url("images/my image.png"); -``` - ---- - -## 5. Media Queries - -### Placement - -Group media queries at the bottom of the stylesheet or use component-based organization with media queries near related rules. - -### Indentation - -Indent rule sets one level inside media query. - -```css -@media all and (max-width: 699px) and (min-width: 520px) { - .selector { - display: block; - } -} -``` - -### Common Breakpoints - -```css -/* Mobile first approach */ -.selector { - width: 100%; -} - -@media screen and (min-width: 600px) { - .selector { - width: 50%; - } -} - -@media screen and (min-width: 1024px) { - .selector { - width: 33.333%; - } -} -``` - -### Feature Queries - -```css -@supports (display: grid) { - .container { - display: grid; - } -} -``` - ---- - -## 6. Commenting - -### Table of Contents - -Include at the top of major stylesheets. - -```css -/** - * Table of Contents - * - * 1.0 Reset - * 2.0 Typography - * 3.0 Elements - * 4.0 Forms - * 5.0 Navigation - * 6.0 Accessibility - * 7.0 Widgets - * 8.0 Content - * 9.0 Media Queries - */ -``` - -### Section Headers - -```css -/** - * 1.0 Reset - * - * Resetting and rebuilding styles have been - * having together so they can operate on a - * common base. - */ -``` - -### Subsection Headers - -```css -/** - * 1.1 Reset Lists - */ -``` - -### Inline Comments - -```css -/* This is a comment about the following rule */ -.selector { - property: value; /* This is a comment about this property */ -} -``` - -### Multi-line Comments - -```css -/** - * This is a longer comment that spans - * multiple lines. Use this format for - * detailed explanations. - */ -``` - ---- - -## 7. Best Practices - -### Remove Before Adding - -Remove unused code before adding new code. - -### Magic Numbers - -Avoid unexplained numbers. Document or calculate values. - -```css -/* Incorrect */ -.selector { - top: 37px; -} - -/* Correct */ -.selector { - /* Offset = header height (50px) - element height (13px) */ - top: 37px; -} -``` - -### Direct Targeting - -Target elements directly with classes rather than relying on element position. - -```css -/* Incorrect */ -.nav li:nth-child(3) a { -} - -/* Correct */ -.nav-contact-link { -} -``` - -### Line Height for Text - -Use `line-height` for vertically centering text. Use `height` only for elements with fixed dimensions. - -### Default Values - -Do not restate default values unless intentionally overriding. - -### Important Declaration - -Avoid `!important`. If used, document why. - -```css -/* Override third-party plugin styles */ -.plugin-element { - color: #333 !important; -} -``` - -### Box Model - -Use `box-sizing: border-box` for predictable sizing. - -```css -*, -*::before, -*::after { - box-sizing: border-box; -} -``` - ---- - -## 8. SCSS/Sass Specific - -### Nesting - -Maximum 3 levels deep. - -```scss -.block { - .element { - .modifier { - // Stop here - } - } -} -``` - -### Variables - -Use descriptive names. - -```scss -$color-primary: #0073aa; -$font-size-base: 16px; -$spacing-unit: 8px; -``` - -### Mixins - -```scss -@mixin button-style($bg-color, $text-color) { - background: $bg-color; - color: $text-color; - padding: 10px 20px; - border: none; - cursor: pointer; -} -``` - -### Extend - -Use sparingly and prefer mixins. - -```scss -%clearfix { - &::after { - content: ""; - display: table; - clear: both; - } -} -``` - ---- - -## Tooling - -```bash -# Stylelint with WordPress config -npm install --save-dev stylelint @wordpress/stylelint-config - -# .stylelintrc.json -{ - "extends": "@wordpress/stylelint-config" -} - -# Run check -npx stylelint "**/*.css" -``` From aa708fc5df1d264dc3547b469c39e5c2f1ba47b0 Mon Sep 17 00:00:00 2001 From: Sherv Date: Wed, 11 Feb 2026 20:05:41 +0300 Subject: [PATCH 51/90] feat(windsurf): add 50/72 character limit rule to conventional commits standard with readability rationale Add comprehensive character limits section covering 50-character subject line limit (GitHub truncation, git log readability) and 72-character body line limit (terminal width, Git log formatting). Include limits table with element/limit/reason columns. Update rules section to replace generic "under 72 characters" with specific subject line (50 chars) and body line (72 chars) requirements. Change --- .../rules/enterprise/conventional-commits.md | 38 +++++++++++++++---- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/.windsurf/rules/enterprise/conventional-commits.md b/.windsurf/rules/enterprise/conventional-commits.md index 62c07ab057..1fefd1d469 100644 --- a/.windsurf/rules/enterprise/conventional-commits.md +++ b/.windsurf/rules/enterprise/conventional-commits.md @@ -9,7 +9,7 @@ All commit messages MUST follow the Conventional Commits 1.0.0 specification. ## Format -``` +```text (): [optional body] @@ -56,7 +56,7 @@ Indicate breaking changes with: ## Examples -``` +```text fix(fields): resolve date field validation error The date field was incorrectly validating dates in non-US formats. @@ -65,26 +65,49 @@ Added locale-aware date parsing. Fixes #1234 ``` -``` +```text feat(builder): add drag-and-drop field reordering Implements smooth drag-and-drop functionality for reordering fields in the form builder interface. ``` -``` +```text fix: prevent XSS in field labels Escape HTML entities in field labels before output. ``` -``` -refactor(entries)!: change entry meta storage format +```text +refactor(entries)!: change entry meta storage BREAKING CHANGE: Entry meta now uses JSON encoding instead of serialized PHP arrays. Run migration script before updating. ``` +## Character Limits (50/72 Rule) + +The 50/72 rule is a Git convention for readable commit messages: + +| Element | Limit | Reason | +| ------------ | ------------- | ------------------------------------------------------- | +| Subject line | 50 characters | GitHub truncates at 72, but 50 is ideal for readability | +| Body lines | 72 characters | Matches terminal width and Git log formatting | + +**Subject Line (50 chars):** + +- Forces concise, scannable summaries +- Displays fully in `git log --oneline` +- GitHub shows full title without truncation + +**Body Lines (72 chars):** + +- Readable in terminals and Git interfaces +- Prevents awkward line breaks +- Matches email format conventions + +--- + ## Rules 1. Type MUST be lowercase. @@ -93,7 +116,8 @@ serialized PHP arrays. Run migration script before updating. 4. Description MUST be imperative mood ("add" not "added" or "adds"). 5. Body MUST be separated from description by a blank line. 6. Breaking changes MUST be indicated with `!` or `BREAKING CHANGE:` footer. -7. Keep description under 72 characters. +7. Subject line MUST be 50 characters or fewer. +8. Body lines MUST wrap at 72 characters. ## AI Commit Message Generation From e27dace56c33a159ba0a5992e12f0e8053bf84da Mon Sep 17 00:00:00 2001 From: Sherv Date: Wed, 11 Feb 2026 20:20:13 +0300 Subject: [PATCH 52/90] refactor(windsurf): consolidate CSS file globs into single-line array format in frm-css.md rule Convert multi-line globs array (3 lines for .css, .scss, .less) to compact single-line array format for improved readability while maintaining identical functionality. No changes to trigger type or description. --- .windsurf/rules/formidable/frm-css.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.windsurf/rules/formidable/frm-css.md b/.windsurf/rules/formidable/frm-css.md index 524296b978..6ccf940e80 100644 --- a/.windsurf/rules/formidable/frm-css.md +++ b/.windsurf/rules/formidable/frm-css.md @@ -1,9 +1,6 @@ --- trigger: "glob" -globs: - - "**/*.css" - - "**/*.scss" - - "**/*.less" +globs: ["**/*.css", "**/*.scss", "**/*.less"] description: "WordPress CSS coding standards with Formidable Forms patterns. Auto-applies when working with CSS files." --- From d71a10d53c35753f909098fd0e2fdea5c681c842 Mon Sep 17 00:00:00 2001 From: Sherv Date: Thu, 12 Feb 2026 14:25:41 +0300 Subject: [PATCH 53/90] refactor(windsurf): convert CSS and PHP code examples from spaces to tabs for consistent indentation across Formidable Forms rules Replace 2-space indentation with tab indentation in frm-css.md (stylelint config, selectors, properties, custom properties, media queries, comments, SCSS examples) and frm-php.md (version comparison example). Update testing.md to use tabs in all test method examples (AAA pattern, factory methods, data providers, mocking, assertions). Maintain identical functionality --- .windsurf/rules/formidable/frm-css.md | 114 +-- .windsurf/rules/formidable/frm-php.md | 8 +- .windsurf/rules/formidable/testing.md | 139 ++-- .../rules/wordpress-vip/wpvip-block-editor.md | 192 ++--- .../rules/wordpress-vip/wpvip-performance.md | 136 ++-- .../rules/wordpress-vip/wpvip-security.md | 90 +-- .windsurf/rules/wordpress/accessibility.md | 197 ++--- .windsurf/rules/wordpress/block-editor.md | 690 ++++++++--------- .windsurf/rules/wordpress/html.md | 170 ++--- .windsurf/rules/wordpress/javascript.md | 712 +++++++++--------- .windsurf/rules/wordpress/php.md | 122 +-- 11 files changed, 1288 insertions(+), 1282 deletions(-) diff --git a/.windsurf/rules/formidable/frm-css.md b/.windsurf/rules/formidable/frm-css.md index 6ccf940e80..688bfce593 100644 --- a/.windsurf/rules/formidable/frm-css.md +++ b/.windsurf/rules/formidable/frm-css.md @@ -33,7 +33,7 @@ Formidable uses `@wordpress/stylelint-config/scss`. See `.stylelintrc.json`: ```json { - "extends": "@wordpress/stylelint-config/scss" + "extends": "@wordpress/stylelint-config/scss" } ``` @@ -65,12 +65,14 @@ Selectors should use lowercase with hyphens: /* Correct */ .frm-button-primary { } + .frm-field-container { } /* Incorrect */ .frmButtonPrimary { } + .frm_field_container { } ``` @@ -98,8 +100,8 @@ Each selector on its own line. Opening brace on same line as last selector. .frm-selector-1, .frm-selector-2, .frm-selector-3 { - background: #fff; - color: #000; + background: #fff; + color: #000; } ``` @@ -114,10 +116,13 @@ Formidable uses the `frm-` prefix for classes: ```css .frm-button { } + .frm-field-container { } + .frm-dashboard-widget { } + .frm-counter-card { } ``` @@ -143,6 +148,7 @@ Always use quotes around attribute values. ```css input[type="text"] { } + input[name="item_meta"] { } ``` @@ -174,9 +180,9 @@ Maximum 3-4 levels deep. ```css .frm-button { - background-color: #fff; - font-size: 16px; - text-align: center; + background-color: #fff; + font-size: 16px; + text-align: center; } ``` @@ -211,25 +217,25 @@ Formidable uses CSS custom properties extensively. See `resources/scss/admin/bas ```css :root { - --grey-700: #344054; - --grey-500: #667085; - --grey-300: #d0d5dd; - --primary-500: #4199fd; - --primary-700: #2b66a9; - --error-500: #f04438; - --success-500: #12b76a; - --small-radius: 8px; - --gap-xs: 8px; - --gap-sm: 16px; - --gap-md: 24px; - --text-sm: 14px; - --text-md: 16px; + --grey-700: #344054; + --grey-500: #667085; + --grey-300: #d0d5dd; + --primary-500: #4199fd; + --primary-700: #2b66a9; + --error-500: #f04438; + --success-500: #12b76a; + --small-radius: 8px; + --gap-xs: 8px; + --gap-sm: 16px; + --gap-md: 24px; + --text-sm: 14px; + --text-md: 16px; } .frm-button { - background: var(--primary-500); - padding: var(--gap-sm); - border-radius: var(--small-radius); + background: var(--primary-500); + padding: var(--gap-sm); + border-radius: var(--small-radius); } ``` @@ -320,9 +326,9 @@ Indent rule sets one level inside media query: ```css @media all and (max-width: 699px) and (min-width: 520px) { - .frm-selector { - display: block; - } + .frm-selector { + display: block; + } } ``` @@ -330,19 +336,19 @@ Indent rule sets one level inside media query: ```css .frm-widget { - width: 100%; + width: 100%; } @media screen and (min-width: 600px) { - .frm-widget { - width: 50%; - } + .frm-widget { + width: 50%; + } } @media screen and (min-width: 1024px) { - .frm-widget { - width: 33.333%; - } + .frm-widget { + width: 33.333%; + } } ``` @@ -365,7 +371,7 @@ Indent rule sets one level inside media query: ```css /* Comment before rule */ .frm-button { - color: var(--grey-700); /* Inline comment */ + color: var(--grey-700); /* Inline comment */ } ``` @@ -380,14 +386,14 @@ Prefer CSS custom properties over hardcoded values: ```css /* Incorrect */ .frm-button { - padding: 16px; - color: #344054; + padding: 16px; + color: #344054; } /* Correct */ .frm-button { - padding: var(--gap-sm); - color: var(--grey-700); + padding: var(--gap-sm); + color: var(--grey-700); } ``` @@ -397,8 +403,8 @@ Document or calculate values: ```css .frm-overlay { - /* Offset = header height (50px) - element height (13px) */ - top: 37px; + /* Offset = header height (50px) - element height (13px) */ + top: 37px; } ``` @@ -409,7 +415,7 @@ If used, document why: ```css /* Override third-party plugin styles */ .frm-override { - color: var(--grey-700) !important; + color: var(--grey-700) !important; } ``` @@ -421,7 +427,7 @@ Formidable uses `border-box` globally: *, *::before, *::after { - box-sizing: border-box; + box-sizing: border-box; } ``` @@ -445,11 +451,11 @@ Maximum 3 levels deep: ```scss .frm-block { - .frm-element { - .frm-modifier { - // Stop here - } - } + .frm-element { + .frm-modifier { + // Stop here + } + } } ``` @@ -458,16 +464,16 @@ Maximum 3 levels deep: ```scss /* Incorrect */ .frm-button { - & .frm-icon { - color: #fff; - } + & .frm-icon { + color: #fff; + } } /* Correct */ .frm-button { - .frm-icon { - color: #fff; - } + .frm-icon { + color: #fff; + } } ``` @@ -475,9 +481,9 @@ Maximum 3 levels deep: ```scss @if $condition { - color: #fff; + color: #fff; } @else { - color: #000; + color: #000; } ``` diff --git a/.windsurf/rules/formidable/frm-php.md b/.windsurf/rules/formidable/frm-php.md index a89cfeab23..4e3767e914 100644 --- a/.windsurf/rules/formidable/frm-php.md +++ b/.windsurf/rules/formidable/frm-php.md @@ -65,11 +65,11 @@ When using newer WordPress functions or features: ```php // Example: Using a newer function with fallback if ( version_compare( get_bloginfo( 'version' ), '5.9', '>=' ) ) { - // Use WordPress 5.9+ feature - $result = wp_new_function(); + // Use WordPress 5.9+ feature + $result = wp_new_function(); } else { - // Fallback for older versions - $result = frm_legacy_fallback(); + // Fallback for older versions + $result = frm_legacy_fallback(); } ``` diff --git a/.windsurf/rules/formidable/testing.md b/.windsurf/rules/formidable/testing.md index 7b6bbc7171..aaf7b17e8d 100644 --- a/.windsurf/rules/formidable/testing.md +++ b/.windsurf/rules/formidable/testing.md @@ -45,17 +45,16 @@ Every fix/feature **MUST** be tested with these scenarios before completion: ```php class Test_FrmField extends FrmUnitTest { + public function test_method_does_expected_behavior() { + // Arrange + $form = $this->factory->form->create_and_get(); - public function test_method_does_expected_behavior() { - // Arrange - $form = $this->factory->form->create_and_get(); + // Act + $result = FrmField::get_all_for_form( $form->id ); - // Act - $result = FrmField::get_all_for_form( $form->id ); - - // Assert - $this->assertIsArray( $result, 'Result should be an array.' ); - } + // Assert + $this->assertIsArray( $result, 'Result should be an array.' ); + } } ``` @@ -81,24 +80,24 @@ Follow **Arrange-Act-Assert** pattern: ```php public function test_entry_creation_with_valid_data() { - // Arrange - Set up test data and conditions - $form = $this->factory->form->create_and_get(); - $field = $this->factory->field->create_and_get( array( - 'form_id' => $form->id, - 'type' => 'text', - ) ); - - // Act - Execute the code being tested - $entry_id = FrmEntry::create( array( - 'form_id' => $form->id, - 'item_meta' => array( - $field->id => 'Test Value', - ), - ) ); - - // Assert - Verify the results - $this->assertIsNumeric( $entry_id, 'Entry ID should be numeric.' ); - $this->assertGreaterThan( 0, $entry_id, 'Entry ID should be positive.' ); + // Arrange - Set up test data and conditions + $form = $this->factory->form->create_and_get(); + $field = $this->factory->field->create_and_get( array( + 'form_id' => $form->id, + 'type' => 'text', + ) ); + + // Act - Execute the code being tested + $entry_id = FrmEntry::create( array( + 'form_id' => $form->id, + 'item_meta' => array( + $field->id => 'Test Value', + ), + ) ); + + // Assert - Verify the results + $this->assertIsNumeric( $entry_id, 'Entry ID should be numeric.' ); + $this->assertGreaterThan( 0, $entry_id, 'Entry ID should be positive.' ); } ``` @@ -114,24 +113,24 @@ $form = $this->factory->form->create_and_get(); // Create field with options $field = $this->factory->field->create_and_get( array( - 'form_id' => $form->id, - 'type' => 'text', - 'name' => 'Test Field', + 'form_id' => $form->id, + 'type' => 'text', + 'name' => 'Test Field', ) ); // Create entry $entry = $this->factory->entry->create_and_get( array( - 'form_id' => $form->id, + 'form_id' => $form->id, ) ); // Create multiple items $entries = $this->factory->entry->create_many( 5, array( - 'form_id' => $form->id, + 'form_id' => $form->id, ) ); // Create user with role $user_id = $this->factory->user->create( array( - 'role' => 'editor', + 'role' => 'editor', ) ); ``` @@ -142,8 +141,8 @@ $user_id = $this->factory->user->create( array( ```php // For private methods $result = $this->run_private_method( - array( $object, 'private_method_name' ), - array( $arg1, $arg2 ) + array( $object, 'private_method_name' ), + array( $arg1, $arg2 ) ); // For private properties @@ -157,22 +156,22 @@ $this->set_private_property( $object, 'property_name', $new_value ); ```php public function test_admin_can_delete_form() { - // Set user role - $this->set_user_by_role( 'administrator' ); + // Set user role + $this->set_user_by_role( 'administrator' ); - $form = $this->factory->form->create_and_get(); - $result = FrmForm::destroy( $form->id ); + $form = $this->factory->form->create_and_get(); + $result = FrmForm::destroy( $form->id ); - $this->assertTrue( $result, 'Admin should be able to delete form.' ); + $this->assertTrue( $result, 'Admin should be able to delete form.' ); } public function test_subscriber_cannot_delete_form() { - $this->set_user_by_role( 'subscriber' ); + $this->set_user_by_role( 'subscriber' ); - $form = $this->factory->form->create_and_get(); - $result = FrmForm::destroy( $form->id ); + $form = $this->factory->form->create_and_get(); + $result = FrmForm::destroy( $form->id ); - $this->assertFalse( $result, 'Subscriber should not delete form.' ); + $this->assertFalse( $result, 'Subscriber should not delete form.' ); } ``` @@ -215,24 +214,24 @@ Use data providers for testing multiple scenarios: * @dataProvider field_type_provider */ public function test_field_validation( $field_type, $value, $expected_valid ) { - $field = $this->factory->field->create_and_get( array( - 'type' => $field_type, - ) ); + $field = $this->factory->field->create_and_get( array( + 'type' => $field_type, + ) ); - $is_valid = FrmEntryValidate::validate_field( $field, $value ); + $is_valid = FrmEntryValidate::validate_field( $field, $value ); - $this->assertSame( $expected_valid, $is_valid ); + $this->assertSame( $expected_valid, $is_valid ); } public function field_type_provider() { - return array( - 'text with value' => array( 'text', 'hello', true ), - 'text empty' => array( 'text', '', true ), - 'email valid' => array( 'email', 'test@example.com', true ), - 'email invalid' => array( 'email', 'invalid', false ), - 'number valid' => array( 'number', '123', true ), - 'number with letters' => array( 'number', 'abc', false ), - ); + return array( + 'text with value' => array( 'text', 'hello', true ), + 'text empty' => array( 'text', '', true ), + 'email valid' => array( 'email', 'test@example.com', true ), + 'email invalid' => array( 'email', 'invalid', false ), + 'number valid' => array( 'number', '123', true ), + 'number with letters' => array( 'number', 'abc', false ), + ); } ``` @@ -242,14 +241,14 @@ public function field_type_provider() { ```php public function test_api_request_handles_error() { - // Mock HTTP response - add_filter( 'pre_http_request', function() { - return new WP_Error( 'http_error', 'Connection failed' ); - } ); + // Mock HTTP response + add_filter( 'pre_http_request', function() { + return new WP_Error( 'http_error', 'Connection failed' ); + } ); - $result = FrmAPI::make_request( '/endpoint' ); + $result = FrmAPI::make_request( '/endpoint' ); - $this->assertInstanceOf( WP_Error::class, $result ); + $this->assertInstanceOf( WP_Error::class, $result ); } ``` @@ -259,19 +258,19 @@ public function test_api_request_handles_error() { ```php public function test_shortcode_output_on_frontend() { - // Switch to front-end context - $this->set_front_end(); + // Switch to front-end context + $this->set_front_end(); - $output = do_shortcode( '[formidable id="1"]' ); + $output = do_shortcode( '[formidable id="1"]' ); - $this->assertStringContainsString( 'assertStringContainsString( 'set_admin_screen( 'admin.php?page=formidable' ); + // Switch to admin context + $this->set_admin_screen( 'admin.php?page=formidable' ); - $this->assertTrue( is_admin(), 'Should be in admin context.' ); + $this->assertTrue( is_admin(), 'Should be in admin context.' ); } ``` diff --git a/.windsurf/rules/wordpress-vip/wpvip-block-editor.md b/.windsurf/rules/wordpress-vip/wpvip-block-editor.md index 42e8faec12..6c60355076 100644 --- a/.windsurf/rules/wordpress-vip/wpvip-block-editor.md +++ b/.windsurf/rules/wordpress-vip/wpvip-block-editor.md @@ -28,9 +28,9 @@ Dynamic blocks generate content server-side using PHP, ideal for frequently upda ```json { - "apiVersion": 3, - "name": "my-plugin/dynamic-block", - "render": "file:./render.php" + "apiVersion": 3, + "name": "my-plugin/dynamic-block", + "render": "file:./render.php" } ``` @@ -40,7 +40,7 @@ Dynamic blocks generate content server-side using PHP, ideal for frequently upda $wrapper_attributes = get_block_wrapper_attributes(); ?>
> - +
``` @@ -48,14 +48,14 @@ $wrapper_attributes = get_block_wrapper_attributes(); ```php register_block_type( __DIR__ . '/build', array( - 'render_callback' => function( $attributes, $content, $block ) { - $wrapper_attributes = get_block_wrapper_attributes(); - return sprintf( - '
%2$s
', - $wrapper_attributes, - esc_html( $attributes['title'] ) - ); - }, + 'render_callback' => function( $attributes, $content, $block ) { + $wrapper_attributes = get_block_wrapper_attributes(); + return sprintf( + '
%2$s
', + $wrapper_attributes, + esc_html( $attributes['title'] ) + ); + }, ) ); ``` @@ -79,9 +79,9 @@ Block bindings dynamically populate content from data sources (WordPress 6.5+). ```php // Register meta field register_meta( 'post', 'custom_subtitle', array( - 'show_in_rest' => true, - 'single' => true, - 'type' => 'string', + 'show_in_rest' => true, + 'single' => true, + 'type' => 'string', ) ); ``` @@ -95,11 +95,11 @@ register_meta( 'post', 'custom_subtitle', array( ```php register_block_bindings_source( 'my-plugin/custom-source', array( - 'label' => __( 'Custom Source', 'my-plugin' ), - 'get_value_callback' => function( $source_args, $block_instance, $attribute_name ) { - return get_option( $source_args['key'], '' ); - }, - 'uses_context' => array( 'postId' ), + 'label' => __( 'Custom Source', 'my-plugin' ), + 'get_value_callback' => function( $source_args, $block_instance, $attribute_name ) { + return get_option( $source_args['key'], '' ); + }, + 'uses_context' => array( 'postId' ), ) ); ``` @@ -113,9 +113,9 @@ Native ESM support in WordPress 6.5+ for optimized loading. ```json { - "apiVersion": 3, - "name": "my-plugin/interactive-block", - "viewScriptModule": "file:./view.js" + "apiVersion": 3, + "name": "my-plugin/interactive-block", + "viewScriptModule": "file:./view.js" } ``` @@ -123,14 +123,14 @@ Native ESM support in WordPress 6.5+ for optimized loading. ```php wp_register_script_module( - '@my-plugin/feature', - plugin_dir_url( __FILE__ ) . 'build/feature.js' + '@my-plugin/feature', + plugin_dir_url( __FILE__ ) . 'build/feature.js' ); wp_enqueue_script_module( - '@my-plugin/main', - plugin_dir_url( __FILE__ ) . 'build/main.js', - array( '@my-plugin/feature' ) + '@my-plugin/main', + plugin_dir_url( __FILE__ ) . 'build/main.js', + array( '@my-plugin/feature' ) ); ``` @@ -139,8 +139,8 @@ wp_enqueue_script_module( ```javascript // Load on demand document.getElementById( 'trigger' ).addEventListener( 'click', async () => { - const { initialize } = await import( '@my-plugin/heavy-feature' ); - initialize(); + const { initialize } = await import( '@my-plugin/heavy-feature' ); + initialize(); } ); ``` @@ -152,16 +152,16 @@ document.getElementById( 'trigger' ).addEventListener( 'click', async () => { ```php add_filter( 'block_categories_all', function( $categories, $post ) { - return array_merge( - array( - array( - 'slug' => 'my-plugin', - 'title' => __( 'My Plugin', 'my-plugin' ), - 'icon' => 'star-filled', - ), - ), - $categories - ); + return array_merge( + array( + array( + 'slug' => 'my-plugin', + 'title' => __( 'My Plugin', 'my-plugin' ), + 'icon' => 'star-filled', + ), + ), + $categories + ); }, 10, 2 ); ``` @@ -169,8 +169,8 @@ add_filter( 'block_categories_all', function( $categories, $post ) { ```json { - "name": "my-plugin/custom-block", - "category": "my-plugin" + "name": "my-plugin/custom-block", + "category": "my-plugin" } ``` @@ -182,10 +182,10 @@ add_filter( 'block_categories_all', function( $categories, $post ) { ```jsx const updateTitle = ( newTitle ) => { - if ( newTitle.length > 100 ) { - return; // Reject invalid input - } - setAttributes( { title: newTitle } ); + if ( newTitle.length > 100 ) { + return; // Reject invalid input + } + setAttributes( { title: newTitle } ); }; ``` @@ -195,11 +195,11 @@ const updateTitle = ( newTitle ) => { import { escapeHTML, escapeAttribute } from '@wordpress/escape-html'; export default function save( { attributes } ) { - return ( -
-

{ escapeHTML( attributes.title ) }

-
- ); + return ( +
+

{ escapeHTML( attributes.title ) }

+
+ ); } ``` @@ -207,10 +207,10 @@ export default function save( { attributes } ) { ```php
> -

- - - +

+ + +
``` @@ -221,15 +221,15 @@ import { useSelect } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; function RestrictedControl() { - const canEdit = useSelect( ( select ) => { - return select( coreStore ).canUser( 'update', 'settings' ); - }, [] ); + const canEdit = useSelect( ( select ) => { + return select( coreStore ).canUser( 'update', 'settings' ); + }, [] ); - if ( ! canEdit ) { - return null; - } + if ( ! canEdit ) { + return null; + } - return ; + return ; } ``` @@ -242,11 +242,11 @@ function RestrictedControl() { ```jsx // Use semantic elements
- { /* Main content */ } + { /* Main content */ }
``` @@ -254,18 +254,18 @@ function RestrictedControl() { ```jsx function CustomButton( { onClick, children } ) { - return ( - - ); + return ( + + ); } ``` @@ -275,13 +275,13 @@ function CustomButton( { onClick, children } ) { import { useState } from '@wordpress/element'; function DynamicContent() { - const [ status, setStatus ] = useState( '' ); + const [ status, setStatus ] = useState( '' ); - return ( -
- { status } -
- ); + return ( +
+ { status } +
+ ); } ``` @@ -297,11 +297,11 @@ const defaultConfig = require( '@wordpress/scripts/config/webpack.config' ); const path = require( 'path' ); module.exports = { - ...defaultConfig, - entry: { - main: path.resolve( process.cwd(), 'resources/js/module.js' ), - editor: path.resolve( process.cwd(), 'resources/js/editor.js' ), - }, + ...defaultConfig, + entry: { + main: path.resolve( process.cwd(), 'resources/js/module.js' ), + editor: path.resolve( process.cwd(), 'resources/js/editor.js' ), + }, }; ``` @@ -313,11 +313,11 @@ import { lazy, Suspense } from '@wordpress/element'; const HeavyComponent = lazy( () => import( './HeavyComponent' ) ); function MyBlock() { - return ( - }> - - - ); + return ( + }> + + + ); } ``` @@ -329,10 +329,10 @@ import { store as coreStore } from '@wordpress/core-data'; // CORRECT - Minimal dependencies, automatic caching const postTitle = useSelect( - ( select ) => { - return select( coreStore ).getEntityRecord( 'postType', 'post', postId )?.title; - }, - [ postId ] + ( select ) => { + return select( coreStore ).getEntityRecord( 'postType', 'post', postId )?.title; + }, + [ postId ] ); ``` diff --git a/.windsurf/rules/wordpress-vip/wpvip-performance.md b/.windsurf/rules/wordpress-vip/wpvip-performance.md index 529e39de0f..416ebecf40 100644 --- a/.windsurf/rules/wordpress-vip/wpvip-performance.md +++ b/.windsurf/rules/wordpress-vip/wpvip-performance.md @@ -28,15 +28,15 @@ Each SQL query adds overhead and latency. Reduce the number of queries. ```php // CORRECT - Efficient query with proper limits $posts = $wpdb->get_results( - $wpdb->prepare( - "SELECT ID, post_title FROM $wpdb->posts - WHERE post_status = %s - AND post_type = %s - LIMIT %d", - 'publish', - 'post', - 100 - ) + $wpdb->prepare( + "SELECT ID, post_title FROM $wpdb->posts + WHERE post_status = %s + AND post_type = %s + LIMIT %d", + 'publish', + 'post', + 100 + ) ); // INCORRECT - Unbounded query @@ -47,12 +47,12 @@ $posts = $wpdb->get_results( "SELECT * FROM $wpdb->posts" ); ```php $args = array( - 'post_type' => 'post', - 'posts_per_page' => 10, - 'no_found_rows' => true, // Skip pagination count - 'update_post_meta_cache' => false, // Skip meta cache if not needed - 'update_post_term_cache' => false, // Skip term cache if not needed - 'fields' => 'ids', // Only get IDs if that is all you need + 'post_type' => 'post', + 'posts_per_page' => 10, + 'no_found_rows' => true, // Skip pagination count + 'update_post_meta_cache' => false, // Skip meta cache if not needed + 'update_post_term_cache' => false, // Skip term cache if not needed + 'fields' => 'ids', // Only get IDs if that is all you need ); $query = new WP_Query( $args ); @@ -87,8 +87,8 @@ Pages can be cached and served directly without regenerating content. $data = wp_cache_get( 'my_cache_key', 'my_group' ); if ( false === $data ) { - $data = expensive_operation(); - wp_cache_set( 'my_cache_key', $data, 'my_group', HOUR_IN_SECONDS ); + $data = expensive_operation(); + wp_cache_set( 'my_cache_key', $data, 'my_group', HOUR_IN_SECONDS ); } return $data; @@ -100,8 +100,8 @@ return $data; $data = get_transient( 'my_transient_key' ); if ( false === $data ) { - $data = expensive_operation(); - set_transient( 'my_transient_key', $data, HOUR_IN_SECONDS ); + $data = expensive_operation(); + set_transient( 'my_transient_key', $data, HOUR_IN_SECONDS ); } return $data; @@ -116,10 +116,10 @@ $cache_key = sprintf( 'footer_output_%s', get_locale() ); $output = wp_cache_get( $cache_key, 'partials' ); if ( false === $output ) { - ob_start(); - get_template_part( 'template-parts/footer' ); - $output = ob_get_clean(); - wp_cache_set( $cache_key, $output, 'partials', DAY_IN_SECONDS ); + ob_start(); + get_template_part( 'template-parts/footer' ); + $output = ob_get_clean(); + wp_cache_set( $cache_key, $output, 'partials', DAY_IN_SECONDS ); } echo $output; @@ -129,8 +129,8 @@ echo $output; ```php add_action( 'save_post', function( $post_id ) { - delete_transient( 'posts_cache' ); - wp_cache_delete( 'posts_list', 'my_plugin' ); + delete_transient( 'posts_cache' ); + wp_cache_delete( 'posts_list', 'my_plugin' ); } ); ``` @@ -153,10 +153,10 @@ get_template_part( 'footer' ); // BETTER - Cache reusable partials $nav = wp_cache_get( 'main_nav', 'partials' ); if ( false === $nav ) { - ob_start(); - get_template_part( 'nav' ); - $nav = ob_get_clean(); - wp_cache_set( 'main_nav', $nav, 'partials', HOUR_IN_SECONDS ); + ob_start(); + get_template_part( 'nav' ); + $nav = ob_get_clean(); + wp_cache_set( 'main_nav', $nav, 'partials', HOUR_IN_SECONDS ); } echo $nav; ``` @@ -175,10 +175,10 @@ add_action( 'admin_init', 'expensive_operation' ); // CORRECT - Conditional execution add_action( 'admin_init', function() { - if ( ! isset( $_GET['my_action'] ) ) { - return; - } - expensive_operation(); + if ( ! isset( $_GET['my_action'] ) ) { + return; + } + expensive_operation(); } ); ``` @@ -188,17 +188,17 @@ Filters are not cached by default. ```php add_filter( 'expensive_filter', function( $value ) { - $cache_key = 'filter_result_' . md5( serialize( $value ) ); - $cached = wp_cache_get( $cache_key ); + $cache_key = 'filter_result_' . md5( serialize( $value ) ); + $cached = wp_cache_get( $cache_key ); - if ( false !== $cached ) { - return $cached; - } + if ( false !== $cached ) { + return $cached; + } - $result = process_value( $value ); - wp_cache_set( $cache_key, $result, '', HOUR_IN_SECONDS ); + $result = process_value( $value ); + wp_cache_set( $cache_key, $result, '', HOUR_IN_SECONDS ); - return $result; + return $result; } ); ``` @@ -234,22 +234,22 @@ On WordPress VIP, cron is handled by Automattic Cron Control plugin automaticall // INCORRECT - Loads all posts into memory $posts = get_posts( array( 'numberposts' => -1 ) ); foreach ( $posts as $post ) { - process( $post ); + process( $post ); } // CORRECT - Batch processing $paged = 1; do { - $posts = get_posts( array( - 'numberposts' => 100, - 'paged' => $paged, - ) ); + $posts = get_posts( array( + 'numberposts' => 100, + 'paged' => $paged, + ) ); - foreach ( $posts as $post ) { - process( $post ); - } + foreach ( $posts as $post ) { + process( $post ); + } - $paged++; + $paged++; } while ( count( $posts ) === 100 ); ``` @@ -258,7 +258,7 @@ do { ```php // INCORRECT - Multiple requests foreach ( $items as $item ) { - $data = wp_remote_get( $item['url'] ); + $data = wp_remote_get( $item['url'] ); } // CORRECT - Batch or background process @@ -273,16 +273,16 @@ wp_schedule_single_event( time(), 'process_items_batch', array( $items ) ); ```php add_action( 'wp_enqueue_scripts', function() { - // Only load on specific pages - if ( is_singular( 'product' ) ) { - wp_enqueue_script( 'product-gallery' ); - } - - // Only load when shortcode is present - global $post; - if ( has_shortcode( $post->post_content, 'my_shortcode' ) ) { - wp_enqueue_script( 'my-shortcode-script' ); - } + // Only load on specific pages + if ( is_singular( 'product' ) ) { + wp_enqueue_script( 'product-gallery' ); + } + + // Only load when shortcode is present + global $post; + if ( has_shortcode( $post->post_content, 'my_shortcode' ) ) { + wp_enqueue_script( 'my-shortcode-script' ); + } } ); ``` @@ -290,13 +290,13 @@ add_action( 'wp_enqueue_scripts', function() { ```php add_filter( 'script_loader_tag', function( $tag, $handle ) { - $defer_scripts = array( 'analytics', 'tracking' ); - - if ( in_array( $handle, $defer_scripts, true ) ) { - return str_replace( ' src', ' defer src', $tag ); - } - - return $tag; + $defer_scripts = array( 'analytics', 'tracking' ); + + if ( in_array( $handle, $defer_scripts, true ) ) { + return str_replace( ' src', ' defer src', $tag ); + } + + return $tag; }, 10, 2 ); ``` diff --git a/.windsurf/rules/wordpress-vip/wpvip-security.md b/.windsurf/rules/wordpress-vip/wpvip-security.md index d47b0bd88e..d23e91ad93 100644 --- a/.windsurf/rules/wordpress-vip/wpvip-security.md +++ b/.windsurf/rules/wordpress-vip/wpvip-security.md @@ -44,13 +44,13 @@ echo $title; // May be double-escaped or bypassed ```php
-

- - - -
- -
+

+ + + +
+ +
``` @@ -59,8 +59,8 @@ echo $title; // May be double-escaped or bypassed ```php // Pass data to JavaScript safely wp_localize_script( 'my-script', 'myData', array( - 'title' => esc_js( $title ), - 'data' => wp_json_encode( $data ), + 'title' => esc_js( $title ), + 'data' => wp_json_encode( $data ), ) ); ``` @@ -87,11 +87,11 @@ element.innerHTML = DOMPurify.sanitize( userData ); ```php $wpdb->query( - $wpdb->prepare( - "UPDATE $wpdb->posts SET post_title = %s WHERE ID = %d", - $title, - $id - ) + $wpdb->prepare( + "UPDATE $wpdb->posts SET post_title = %s WHERE ID = %d", + $title, + $id + ) ); ``` @@ -109,8 +109,8 @@ $wpdb->query( ```php // PREFERRED - WordPress functions $posts = get_posts( array( - 'post_type' => 'post', - 'posts_per_page' => 10, + 'post_type' => 'post', + 'posts_per_page' => 10, ) ); // AVOID - Direct queries when possible @@ -155,12 +155,12 @@ $ids = array_map( 'absint', $_POST['ids'] ); // CORRECT - Validate against allowed list $allowed = array( 'draft', 'publish', 'pending' ); if ( ! in_array( $status, $allowed, true ) ) { - wp_die( 'Invalid status' ); + wp_die( 'Invalid status' ); } // Use strict comparison if ( $value === 'expected' ) { - // Process + // Process } ``` @@ -176,8 +176,8 @@ wp_nonce_field( 'my_action', 'my_nonce' ); // On submission if ( ! isset( $_POST['my_nonce'] ) || - ! wp_verify_nonce( $_POST['my_nonce'], 'my_action' ) ) { - wp_die( 'Security check failed' ); + ! wp_verify_nonce( $_POST['my_nonce'], 'my_action' ) ) { + wp_die( 'Security check failed' ); } ``` @@ -186,14 +186,14 @@ if ( ! isset( $_POST['my_nonce'] ) || ```php // Enqueue with nonce wp_localize_script( 'my-script', 'myAjax', array( - 'ajaxurl' => admin_url( 'admin-ajax.php' ), - 'nonce' => wp_create_nonce( 'my_ajax_action' ), + 'ajaxurl' => admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( 'my_ajax_action' ), ) ); // Verify in handler add_action( 'wp_ajax_my_action', function() { - check_ajax_referer( 'my_ajax_action', 'nonce' ); - // Process request + check_ajax_referer( 'my_ajax_action', 'nonce' ); + // Process request } ); ``` @@ -201,12 +201,12 @@ add_action( 'wp_ajax_my_action', function() { ```php if ( ! current_user_can( 'edit_posts' ) ) { - wp_die( 'Unauthorized access' ); + wp_die( 'Unauthorized access' ); } // Check specific post if ( ! current_user_can( 'edit_post', $post_id ) ) { - wp_die( 'You cannot edit this post' ); + wp_die( 'You cannot edit this post' ); } ``` @@ -220,7 +220,7 @@ if ( ! current_user_can( 'edit_post', $post_id ) ) { global $wp_filesystem; if ( ! function_exists( 'WP_Filesystem' ) ) { - require_once ABSPATH . 'wp-admin/includes/file.php'; + require_once ABSPATH . 'wp-admin/includes/file.php'; } WP_Filesystem(); @@ -236,19 +236,19 @@ $wp_filesystem->put_contents( $file_path, $content, FS_CHMOD_FILE ); ```php $upload = wp_handle_upload( - $_FILES['file'], - array( - 'test_form' => false, - 'mimes' => array( - 'jpg|jpeg' => 'image/jpeg', - 'png' => 'image/png', - 'pdf' => 'application/pdf', - ), - ) + $_FILES['file'], + array( + 'test_form' => false, + 'mimes' => array( + 'jpg|jpeg' => 'image/jpeg', + 'png' => 'image/png', + 'pdf' => 'application/pdf', + ), + ) ); if ( isset( $upload['error'] ) ) { - wp_die( $upload['error'] ); + wp_die( $upload['error'] ); } ``` @@ -260,12 +260,12 @@ if ( isset( $upload['error'] ) ) { ```php $response = wp_remote_get( 'https://api.example.com/data', array( - 'timeout' => 15, + 'timeout' => 15, ) ); if ( is_wp_error( $response ) ) { - error_log( 'API Error: ' . $response->get_error_message() ); - return false; + error_log( 'API Error: ' . $response->get_error_message() ); + return false; } $body = wp_remote_retrieve_body( $response ); @@ -277,7 +277,7 @@ $data = json_decode( $body, true ); ```php // NEVER do this in production $response = wp_remote_get( $url, array( - 'sslverify' => false, // DANGEROUS + 'sslverify' => false, // DANGEROUS ) ); ``` @@ -306,9 +306,9 @@ $value = @file_get_contents( $file ); // CORRECT if ( file_exists( $file ) && is_readable( $file ) ) { - $value = file_get_contents( $file ); + $value = file_get_contents( $file ); } else { - $value = false; + $value = false; } ``` @@ -318,8 +318,8 @@ if ( file_exists( $file ) && is_readable( $file ) ) { $result = some_operation(); if ( is_wp_error( $result ) ) { - error_log( 'Operation failed: ' . $result->get_error_message() ); - return false; + error_log( 'Operation failed: ' . $result->get_error_message() ); + return false; } return $result; diff --git a/.windsurf/rules/wordpress/accessibility.md b/.windsurf/rules/wordpress/accessibility.md index bfc31670b8..5966e58815 100644 --- a/.windsurf/rules/wordpress/accessibility.md +++ b/.windsurf/rules/wordpress/accessibility.md @@ -42,14 +42,14 @@ Provide text alternatives for non-text content.
- System architecture diagram -
- Detailed description of the system architecture... -
+ System architecture diagram +
+ Detailed description of the system architecture... +
``` @@ -61,7 +61,7 @@ Provide text alternatives for non-text content. ``` @@ -90,8 +90,8 @@ Create content that can be presented in different ways.
    -
  • Item one
  • -
  • Item two
  • +
  • Item one
  • +
  • Item two
``` @@ -122,17 +122,18 @@ Never use color alone to convey information. ```css /* Incorrect - color only */ .error { - color: #d00; + color: #d00; } /* Correct - multiple indicators */ .error { - color: #d00; - border-left: 4px solid #d00; + color: #d00; + border-left: 4px solid #d00; } + .error::before { - content: "Error: "; - font-weight: bold; + content: "Error: "; + font-weight: 700; } ``` @@ -168,21 +169,21 @@ All functionality must be available via keyboard.
- Custom Button + Custom Button
``` ```javascript -function handleKeydown(event) { - if (event.key === "Enter" || event.key === " ") { - event.preventDefault(); - handleClick(); - } +function handleKeydown( event ) { + if ( event.key === 'Enter' || event.key === ' ' ) { + event.preventDefault(); + handleClick(); + } } ``` @@ -223,7 +224,7 @@ Skip links allow keyboard and screen reader users to bypass repetitive navigatio
- +
``` @@ -265,13 +266,13 @@ Focus indicator must be visible. ```css *:focus { - outline: 2px solid #0073aa; - outline-offset: 2px; + outline: 2px solid #0073aa; + outline-offset: 2px; } /* Never remove focus outline without replacement */ *:focus { - outline: none; /* INCORRECT */ + outline: none; /* INCORRECT */ } ``` @@ -286,8 +287,8 @@ Minimum target size of 24x24 CSS pixels. Recommended 44x44 pixels. ```css .button, .nav-link { - min-width: 44px; - min-height: 44px; + min-width: 44px; + min-height: 44px; } ``` @@ -319,7 +320,7 @@ Make text content readable and understandable. ```html

- The French phrase c'est la vie means "that's life". + The French phrase c'est la vie means "that's life".

``` @@ -333,9 +334,9 @@ No change of context on focus. ```javascript // Incorrect - auto-submit on focus -input.addEventListener("focus", function () { - form.submit(); -}); +input.addEventListener( 'focus', function () { + form.submit(); +} ); ``` **On Input** @@ -344,14 +345,14 @@ No unexpected context change on input. ```javascript // Incorrect - auto-navigate on select change -select.addEventListener("change", function () { - window.location = this.value; -}); +select.addEventListener( 'change', function () { + window.location = this.value; +} ); // Correct - require button press -button.addEventListener("click", function () { - window.location = select.value; -}); +button.addEventListener( 'click', function () { + window.location = select.value; +} ); ``` **Consistent Navigation** @@ -371,15 +372,15 @@ Help users avoid and correct mistakes. ```html - Please enter a valid email address + Please enter a valid email address ``` @@ -396,7 +397,7 @@ Provide specific error messages with suggestions. ```html - Password must be at least 8 characters and include a number + Password must be at least 8 characters and include a number ``` @@ -432,12 +433,12 @@ Ensure all UI components have accessible names and roles.
- - + +
Panel content...
``` @@ -460,36 +461,36 @@ Announce status messages to screen readers. ```css .screen-reader-text { - border: 0; - clip: rect(1px, 1px, 1px, 1px); - clip-path: inset(50%); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - width: 1px; - word-wrap: normal !important; + border: 0; + clip: rect( 1px, 1px, 1px, 1px ); + clip-path: inset( 50% ); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; + word-wrap: normal !important; } .screen-reader-text:focus { - background-color: #f1f1f1; - border-radius: 3px; - box-shadow: 0 0 2px 2px rgba(0, 0, 0, 0.6); - clip: auto !important; - clip-path: none; - color: #21759b; - display: block; - font-size: 14px; - font-weight: 700; - height: auto; - left: 5px; - line-height: normal; - padding: 15px 23px 14px; - text-decoration: none; - top: 5px; - width: auto; - z-index: 100000; + background-color: #f1f1f1; + border-radius: 3px; + box-shadow: 0 0 2px 2px rgba(0, 0, 0, 0.6); + clip: auto !important; + clip-path: none; + color: #21759b; + display: block; + font-size: 14px; + font-weight: 700; + height: auto; + left: 5px; + line-height: normal; + padding: 15px 23px 14px; + text-decoration: none; + top: 5px; + width: auto; + z-index: 100000; } ``` @@ -497,7 +498,7 @@ Announce status messages to screen readers. ```php
-

+

``` @@ -505,35 +506,35 @@ Announce status messages to screen readers. ```php
- +
``` ```javascript // Update live region content -document.getElementById("ajax-response").textContent = - "Loading complete. 5 items loaded."; +document.getElementById( 'ajax-response' ).textContent = + 'Loading complete. 5 items loaded.'; ``` ### Toggles and Expandables ```html ``` ```javascript -button.addEventListener("click", function () { - var expanded = this.getAttribute("aria-expanded") === "true"; - this.setAttribute("aria-expanded", !expanded); - content.hidden = expanded; -}); +button.addEventListener( 'click', function () { + const expanded = this.getAttribute( 'aria-expanded' ) === 'true'; + this.setAttribute( 'aria-expanded', ! expanded ); + content.hidden = expanded; +} ); ``` --- @@ -559,8 +560,8 @@ button.addEventListener("click", function () { - - Go to page + + Go to page ``` diff --git a/.windsurf/rules/wordpress/block-editor.md b/.windsurf/rules/wordpress/block-editor.md index 8f9d735413..a4646db885 100644 --- a/.windsurf/rules/wordpress/block-editor.md +++ b/.windsurf/rules/wordpress/block-editor.md @@ -33,30 +33,30 @@ Always register blocks using `block.json` metadata file. This is the canonical m ```json { - "$schema": "https://schemas.wp.org/trunk/block.json", - "apiVersion": 3, - "name": "my-plugin/my-block", - "title": "My Block", - "category": "widgets", - "icon": "star", - "description": "A custom block.", - "keywords": ["custom", "block"], - "textdomain": "my-plugin", - "attributes": { - "content": { - "type": "string", - "source": "html", - "selector": ".content" - } - }, - "supports": { - "align": true, - "html": false - }, - "editorScript": "file:./index.js", - "editorStyle": "file:./index.css", - "style": "file:./style.css", - "render": "file:./render.php" + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 3, + "name": "my-plugin/my-block", + "title": "My Block", + "category": "widgets", + "icon": "star", + "description": "A custom block.", + "keywords": ["custom", "block"], + "textdomain": "my-plugin", + "attributes": { + "content": { + "type": "string", + "source": "html", + "selector": ".content" + } + }, + "supports": { + "align": true, + "html": false + }, + "editorScript": "file:./index.js", + "editorStyle": "file:./index.css", + "style": "file:./style.css", + "render": "file:./render.php" } ``` @@ -73,7 +73,7 @@ Always use `apiVersion: 3` (introduced in WordPress 6.3) for new blocks. ```json { - "apiVersion": 3 + "apiVersion": 3 } ``` @@ -82,22 +82,22 @@ Always use `apiVersion: 3` (introduced in WordPress 6.3) for new blocks. Every block wrapper must use `useBlockProps` for proper editor integration. ```jsx -import { useBlockProps } from "@wordpress/block-editor"; +import { useBlockProps } from '@wordpress/block-editor'; -export default function Edit({ attributes, setAttributes }) { - const blockProps = useBlockProps(); +export default function Edit( { attributes, setAttributes } ) { + const blockProps = useBlockProps(); - return
{/* Block content */}
; + return
{ /* Block content */ }
; } ``` For custom classes or attributes: ```jsx -const blockProps = useBlockProps({ - className: "my-custom-class", - "data-custom": "value", -}); +const blockProps = useBlockProps( { + className: 'my-custom-class', + 'data-custom': 'value', +} ); ``` --- @@ -123,15 +123,15 @@ blocks/ ```jsx // index.js -import { registerBlockType } from "@wordpress/blocks"; -import Edit from "./edit"; -import save from "./save"; -import metadata from "./block.json"; +import { registerBlockType } from '@wordpress/blocks'; +import Edit from './edit'; +import save from './save'; +import metadata from './block.json'; -registerBlockType(metadata.name, { - edit: Edit, - save, -}); +registerBlockType( metadata.name, { + edit: Edit, + save, +} ); ``` --- @@ -144,19 +144,19 @@ Use functional components with hooks. Never use class components. ```jsx // CORRECT -import { useBlockProps } from "@wordpress/block-editor"; +import { useBlockProps } from '@wordpress/block-editor'; -export default function Edit({ attributes, setAttributes, isSelected }) { - const blockProps = useBlockProps(); +export default function Edit( { attributes, setAttributes, isSelected } ) { + const blockProps = useBlockProps(); - return
{/* Content */}
; + return
{ /* Content */ }
; } // INCORRECT - Class component class Edit extends Component { - render() { - return
{/* Content */}
; - } + render() { + return
{ /* Content */ }
; + } } ``` @@ -165,15 +165,15 @@ class Edit extends Component { Always destructure props at the function signature level. ```jsx -export default function Edit({ - attributes, - setAttributes, - isSelected, - clientId, - context, -}) { - const { content, alignment } = attributes; - // ... +export default function Edit( { + attributes, + setAttributes, + isSelected, + clientId, + context, +} ) { + const { content, alignment } = attributes; + // ... } ``` @@ -183,8 +183,8 @@ Use `setAttributes` with object spread for updates. ```jsx // CORRECT -setAttributes({ content: newContent }); -setAttributes({ ...attributes, items: [...items, newItem] }); +setAttributes( { content: newContent } ); +setAttributes( { ...attributes, items: [ ...items, newItem ] } ); // INCORRECT - Direct mutation attributes.content = newContent; @@ -200,16 +200,16 @@ attributes.content = newContent; ```jsx // save.js -import { useBlockProps } from "@wordpress/block-editor"; +import { useBlockProps } from '@wordpress/block-editor'; -export default function save({ attributes }) { - const blockProps = useBlockProps.save(); +export default function save( { attributes } ) { + const blockProps = useBlockProps.save(); - return ( -
-

{attributes.content}

-
- ); + return ( +
+

{ attributes.content }

+
+ ); } ``` @@ -218,14 +218,14 @@ export default function save({ attributes }) { ```jsx // save.js - Return null for dynamic blocks export default function save() { - return null; + return null; } ``` ```php // render.php
> - +
``` @@ -234,14 +234,14 @@ export default function save() { Always use `useBlockProps.save()` in the save function. ```jsx -import { useBlockProps } from "@wordpress/block-editor"; +import { useBlockProps } from '@wordpress/block-editor'; -export default function save({ attributes }) { - const blockProps = useBlockProps.save({ - className: `align-${attributes.alignment}`, - }); +export default function save( { attributes } ) { + const blockProps = useBlockProps.save( { + className: `align-${ attributes.alignment }`, + } ); - return
{attributes.content}
; + return
{ attributes.content }
; } ``` @@ -254,33 +254,33 @@ export default function save({ attributes }) { ```jsx // Block Editor import { - useBlockProps, - RichText, - InspectorControls, - BlockControls, - InnerBlocks, - MediaUpload, - MediaUploadCheck, -} from "@wordpress/block-editor"; + useBlockProps, + RichText, + InspectorControls, + BlockControls, + InnerBlocks, + MediaUpload, + MediaUploadCheck, +} from '@wordpress/block-editor'; // Components import { - PanelBody, - TextControl, - ToggleControl, - SelectControl, - Button, - Spinner, - Placeholder, -} from "@wordpress/components"; + PanelBody, + TextControl, + ToggleControl, + SelectControl, + Button, + Spinner, + Placeholder, +} from '@wordpress/components'; // Data -import { useSelect, useDispatch } from "@wordpress/data"; +import { useSelect, useDispatch } from '@wordpress/data'; // Core utilities -import { __ } from "@wordpress/i18n"; -import { useEffect, useState, useCallback } from "@wordpress/element"; -import domReady from "@wordpress/dom-ready"; +import { __ } from '@wordpress/i18n'; +import { useEffect, useState, useCallback } from '@wordpress/element'; +import domReady from '@wordpress/dom-ready'; ``` ### Importing via WordPress Global @@ -299,10 +299,10 @@ In PHP, declare script dependencies correctly: ```php wp_enqueue_script( - 'my-block-editor', - plugins_url( 'build/index.js', __FILE__ ), - array( 'wp-blocks', 'wp-block-editor', 'wp-components', 'wp-i18n', 'wp-element' ), - filemtime( plugin_dir_path( __FILE__ ) . 'build/index.js' ) + 'my-block-editor', + plugins_url( 'build/index.js', __FILE__ ), + array( 'wp-blocks', 'wp-block-editor', 'wp-components', 'wp-i18n', 'wp-element' ), + filemtime( plugin_dir_path( __FILE__ ) . 'build/index.js' ) ); ``` @@ -313,45 +313,45 @@ wp_enqueue_script( ### useSelect for Reading Data ```jsx -import { useSelect } from "@wordpress/data"; -import { store as coreStore } from "@wordpress/core-data"; +import { useSelect } from '@wordpress/data'; +import { store as coreStore } from '@wordpress/core-data'; function MyComponent() { - const posts = useSelect((select) => { - return select(coreStore).getEntityRecords("postType", "post", { - per_page: 10, - }); - }, []); - - if (!posts) { - return ; - } - - return ( -
    - {posts.map((post) => ( -
  • {post.title.rendered}
  • - ))} -
- ); + const posts = useSelect( ( select ) => { + return select( coreStore ).getEntityRecords( 'postType', 'post', { + per_page: 10, + } ); + }, [] ); + + if ( ! posts ) { + return ; + } + + return ( +
    + { posts.map( ( post ) => ( +
  • { post.title.rendered }
  • + ) ) } +
+ ); } ``` ### useDispatch for Writing Data ```jsx -import { useDispatch } from "@wordpress/data"; -import { store as noticesStore } from "@wordpress/notices"; +import { useDispatch } from '@wordpress/data'; +import { store as noticesStore } from '@wordpress/notices'; function MyComponent() { - const { createSuccessNotice } = useDispatch(noticesStore); + const { createSuccessNotice } = useDispatch( noticesStore ); - const handleSave = () => { - // Save logic - createSuccessNotice("Saved successfully!"); - }; + const handleSave = () => { + // Save logic + createSuccessNotice( 'Saved successfully!' ); + }; - return ; + return ; } ``` @@ -373,55 +373,55 @@ function MyComponent() { ### Inspector Controls (Sidebar) ```jsx -import { InspectorControls } from "@wordpress/block-editor"; -import { PanelBody, ToggleControl, RangeControl } from "@wordpress/components"; - -export default function Edit({ attributes, setAttributes }) { - const { showTitle, columns } = attributes; - - return ( - <> - - - setAttributes({ showTitle: value })} - /> - setAttributes({ columns: value })} - min={1} - max={4} - /> - - - {/* Block content */} - - ); +import { InspectorControls } from '@wordpress/block-editor'; +import { PanelBody, ToggleControl, RangeControl } from '@wordpress/components'; + +export default function Edit( { attributes, setAttributes } ) { + const { showTitle, columns } = attributes; + + return ( + <> + + + setAttributes( { showTitle: value } ) } + /> + setAttributes( { columns: value } ) } + min={ 1 } + max={ 4 } + /> + + + { /* Block content */ } + + ); } ``` ### Block Controls (Toolbar) ```jsx -import { BlockControls, AlignmentToolbar } from "@wordpress/block-editor"; - -export default function Edit({ attributes, setAttributes }) { - const { alignment } = attributes; - - return ( - <> - - setAttributes({ alignment: value })} - /> - - {/* Block content */} - - ); +import { BlockControls, AlignmentToolbar } from '@wordpress/block-editor'; + +export default function Edit( { attributes, setAttributes } ) { + const { alignment } = attributes; + + return ( + <> + + setAttributes( { alignment: value } ) } + /> + + { /* Block content */ } + + ); } ``` @@ -432,33 +432,33 @@ export default function Edit({ attributes, setAttributes }) { ### Basic Usage ```jsx -import { useBlockProps, InnerBlocks } from "@wordpress/block-editor"; +import { useBlockProps, InnerBlocks } from '@wordpress/block-editor'; export default function Edit() { - const blockProps = useBlockProps(); - - return ( -
- -
- ); + const blockProps = useBlockProps(); + + return ( +
+ +
+ ); } export function save() { - const blockProps = useBlockProps.save(); + const blockProps = useBlockProps.save(); - return ( -
- -
- ); + return ( +
+ +
+ ); } ``` @@ -476,29 +476,29 @@ export function save() { ## 8. Rich Text ```jsx -import { RichText, useBlockProps } from "@wordpress/block-editor"; - -export default function Edit({ attributes, setAttributes }) { - const blockProps = useBlockProps(); - - return ( - setAttributes({ content })} - placeholder={__("Enter text...", "my-plugin")} - allowedFormats={["core/bold", "core/italic", "core/link"]} - /> - ); +import { RichText, useBlockProps } from '@wordpress/block-editor'; + +export default function Edit( { attributes, setAttributes } ) { + const blockProps = useBlockProps(); + + return ( + setAttributes( { content } ) } + placeholder={ __( 'Enter text...', 'my-plugin' ) } + allowedFormats={ [ 'core/bold', 'core/italic', 'core/link' ] } + /> + ); } -export function save({ attributes }) { - const blockProps = useBlockProps.save(); +export function save( { attributes } ) { + const blockProps = useBlockProps.save(); - return ( - - ); + return ( + + ); } ``` @@ -510,26 +510,26 @@ Define in `block.json` for automatic features: ```json { - "supports": { - "align": ["wide", "full"], - "anchor": true, - "className": true, - "color": { - "background": true, - "text": true, - "gradients": true - }, - "spacing": { - "margin": true, - "padding": true - }, - "typography": { - "fontSize": true, - "lineHeight": true - }, - "html": false, - "reusable": true - } + "supports": { + "align": ["wide", "full"], + "anchor": true, + "className": true, + "color": { + "background": true, + "text": true, + "gradients": true + }, + "spacing": { + "margin": true, + "padding": true + }, + "typography": { + "fontSize": true, + "lineHeight": true + }, + "html": false, + "reusable": true + } } ``` @@ -540,55 +540,55 @@ Define in `block.json` for automatic features: ### Modify Block Registration ```jsx -import { addFilter } from "@wordpress/hooks"; +import { addFilter } from '@wordpress/hooks'; addFilter( - "blocks.registerBlockType", - "my-plugin/modify-paragraph", - (settings, name) => { - if (name !== "core/paragraph") { - return settings; - } - - return { - ...settings, - supports: { - ...settings.supports, - customClassName: false, - }, - }; - }, + 'blocks.registerBlockType', + 'my-plugin/modify-paragraph', + ( settings, name ) => { + if ( name !== 'core/paragraph' ) { + return settings; + } + + return { + ...settings, + supports: { + ...settings.supports, + customClassName: false, + }, + }; + }, ); ``` ### Extend Block Edit ```jsx -import { addFilter } from "@wordpress/hooks"; -import { createHigherOrderComponent } from "@wordpress/compose"; -import { InspectorControls } from "@wordpress/block-editor"; -import { PanelBody } from "@wordpress/components"; - -const withCustomControls = createHigherOrderComponent((BlockEdit) => { - return (props) => { - if (props.name !== "core/paragraph") { - return ; - } - - return ( - <> - - - - {/* Custom controls */} - - - - ); - }; -}, "withCustomControls"); - -addFilter("editor.BlockEdit", "my-plugin/custom-controls", withCustomControls); +import { addFilter } from '@wordpress/hooks'; +import { createHigherOrderComponent } from '@wordpress/compose'; +import { InspectorControls } from '@wordpress/block-editor'; +import { PanelBody } from '@wordpress/components'; + +const withCustomControls = createHigherOrderComponent( ( BlockEdit ) => { + return ( props ) => { + if ( props.name !== 'core/paragraph' ) { + return ; + } + + return ( + <> + + + + { /* Custom controls */ } + + + + ); + }; +}, 'withCustomControls' ); + +addFilter( 'editor.BlockEdit', 'my-plugin/custom-controls', withCustomControls ); ``` --- @@ -598,16 +598,16 @@ addFilter("editor.BlockEdit", "my-plugin/custom-controls", withCustomControls); ### Translating Strings ```jsx -import { __, _n, sprintf } from "@wordpress/i18n"; +import { __, _n, sprintf } from '@wordpress/i18n'; // Simple string -const label = __("My Label", "my-plugin"); +const label = __( 'My Label', 'my-plugin' ); // Pluralization -const message = sprintf(_n("%d item", "%d items", count, "my-plugin"), count); +const message = sprintf( _n( '%d item', '%d items', count, 'my-plugin' ), count ); // With variables -const greeting = sprintf(__("Hello, %s!", "my-plugin"), userName); +const greeting = sprintf( __( 'Hello, %s!', 'my-plugin' ), userName ); ``` ### In block.json @@ -616,10 +616,10 @@ Translatable fields are automatically handled when `textdomain` is set. ```json { - "textdomain": "my-plugin", - "title": "My Block", - "description": "A custom block.", - "keywords": ["custom"] + "textdomain": "my-plugin", + "title": "My Block", + "description": "A custom block.", + "keywords": ["custom"] } ``` @@ -650,14 +650,14 @@ For custom config, extend `@wordpress/scripts`: ```javascript // webpack.config.js -const defaultConfig = require("@wordpress/scripts/config/webpack.config"); +const defaultConfig = require( '@wordpress/scripts/config/webpack.config' ); module.exports = { - ...defaultConfig, - entry: { - ...defaultConfig.entry, - "custom-entry": "./src/custom-entry.js", - }, + ...defaultConfig, + entry: { + ...defaultConfig.entry, + 'custom-entry': './src/custom-entry.js', + }, }; ``` @@ -668,23 +668,23 @@ module.exports = { ### Memoization ```jsx -import { useMemo, useCallback } from "@wordpress/element"; - -function MyComponent({ items, onSelect }) { - // Memoize expensive computations - const processedItems = useMemo(() => { - return items.map((item) => expensiveProcess(item)); - }, [items]); - - // Memoize callbacks passed to children - const handleSelect = useCallback( - (id) => { - onSelect(id); - }, - [onSelect], - ); - - return ; +import { useMemo, useCallback } from '@wordpress/element'; + +function MyComponent( { items, onSelect } ) { + // Memoize expensive computations + const processedItems = useMemo( () => { + return items.map( ( item ) => expensiveProcess( item ) ); + }, [ items ] ); + + // Memoize callbacks passed to children + const handleSelect = useCallback( + ( id ) => { + onSelect( id ); + }, + [ onSelect ], + ); + + return ; } ``` @@ -692,39 +692,39 @@ function MyComponent({ items, onSelect }) { ```jsx // INCORRECT - Creates new object every render -; +; // CORRECT - Stable reference -const style = useMemo(() => ({ color: "red" }), []); -; +const style = useMemo( () => ( { color: 'red' } ), [] ); +; ``` ### State Initialization ```jsx // CORRECT - Lazy initialization for expensive values -const [state, setState] = useState(() => computeExpensiveValue()); +const [ state, setState ] = useState( () => computeExpensiveValue() ); // INCORRECT - Runs on every render -const [state, setState] = useState(computeExpensiveValue()); +const [ state, setState ] = useState( computeExpensiveValue() ); ``` ### Derived State ```jsx // CORRECT - Derive during render -function MyComponent({ items }) { - const filteredItems = items.filter((item) => item.active); - // ... +function MyComponent( { items } ) { + const filteredItems = items.filter( ( item ) => item.active ); + // ... } // INCORRECT - Unnecessary state and effect -function MyComponent({ items }) { - const [filteredItems, setFilteredItems] = useState([]); +function MyComponent( { items } ) { + const [ filteredItems, setFilteredItems ] = useState( [] ); - useEffect(() => { - setFilteredItems(items.filter((item) => item.active)); - }, [items]); + useEffect( () => { + setFilteredItems( items.filter( ( item ) => item.active ) ); + }, [ items ] ); } ``` @@ -737,11 +737,11 @@ function MyComponent({ items }) { ```jsx // CORRECT - Parallel fetching const { posts, categories } = useSelect( ( select ) => { - const { getEntityRecords } = select( coreStore ); - return { - posts: getEntityRecords( 'postType', 'post' ), - categories: getEntityRecords( 'taxonomy', 'category' ), - }; + const { getEntityRecords } = select( coreStore ); + return { + posts: getEntityRecords( 'postType', 'post' ), + categories: getEntityRecords( 'taxonomy', 'category' ), + }; }, [] ); // INCORRECT - Sequential in separate hooks @@ -754,34 +754,34 @@ const categories = useSelect( ( select ) => /* ... */ ); ```jsx // CORRECT - Minimal dependencies const postTitle = useSelect( - (select) => { - return select(coreStore).getEntityRecord("postType", "post", postId)?.title; - }, - [postId], + ( select ) => { + return select( coreStore ).getEntityRecord( 'postType', 'post', postId )?.title; + }, + [ postId ], ); // INCORRECT - Returns entire object, causes re-renders const post = useSelect( - (select) => { - return select(coreStore).getEntityRecord("postType", "post", postId); - }, - [postId], + ( select ) => { + return select( coreStore ).getEntityRecord( 'postType', 'post', postId ); + }, + [ postId ], ); ``` ### Dynamic Imports ```jsx -import { lazy, Suspense } from "@wordpress/element"; +import { lazy, Suspense } from '@wordpress/element'; -const HeavyComponent = lazy(() => import("./HeavyComponent")); +const HeavyComponent = lazy( () => import( './HeavyComponent' ) ); function MyBlock() { - return ( - }> - - - ); + return ( + }> + + + ); } ``` @@ -797,28 +797,28 @@ function MyBlock() { // Use composition - - - - + + + + ``` ### Context for Shared State ```jsx -import { createContext, useContext } from "@wordpress/element"; +import { createContext, useContext } from '@wordpress/element'; const BlockContext = createContext(); -function BlockProvider({ children, value }) { - return ( - {children} - ); +function BlockProvider( { children, value } ) { + return ( + { children } + ); } function useBlockContext() { - return useContext(BlockContext); + return useContext( BlockContext ); } ``` diff --git a/.windsurf/rules/wordpress/html.md b/.windsurf/rules/wordpress/html.md index b279c9d4bc..0f72b624b4 100644 --- a/.windsurf/rules/wordpress/html.md +++ b/.windsurf/rules/wordpress/html.md @@ -55,7 +55,7 @@ Declare UTF-8 encoding early in the head. ```html - + ``` @@ -195,12 +195,12 @@ All tag names must be lowercase. ```html
-

Content

+

Content

-

Content

+

Content

``` @@ -222,29 +222,29 @@ Use semantic HTML5 elements where appropriate. ```html
- +
-
-
-

Article Title

-
-
-

Content here.

-
-
- +
+
+

Article Title

+
+
+

Content here.

+
+
+
-

Copyright information

+

Copyright information

``` @@ -258,14 +258,14 @@ HTML indentation should reflect the logical structure using tabs. ```html
-
-

Title

-
-
-
-

Content

-
-
+
+

Title

+
+
+
+

Content

+
+
``` @@ -275,13 +275,13 @@ Indent PHP blocks to match surrounding HTML structure. ```php -
-

Not Found

-
-

Apologies, but no results were found.

- -
-
+
+

Not Found

+
+

Apologies, but no results were found.

+ +
+
``` @@ -299,8 +299,8 @@ Put block elements on their own lines. ```html
-

First paragraph.

-

Second paragraph.

+

First paragraph.

+

Second paragraph.

``` @@ -312,21 +312,21 @@ Put block elements on their own lines. ```html
-
- Personal Information +
+ Personal Information -
- - -
+
+ + +
-
- - -
-
+
+ + +
+
- +
``` @@ -341,7 +341,7 @@ Always associate labels with form controls. ``` @@ -367,29 +367,29 @@ Use appropriate input types for better UX and validation. ```html - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + +
Monthly Sales
MonthSales
January$10,000
February$12,000
Total$22,000
Monthly Sales
MonthSales
January$10,000
February$12,000
Total$22,000
``` @@ -411,7 +411,7 @@ Use scope attribute on header cells. ```html - External Site + External Site @@ -435,16 +435,16 @@ Use scope attribute on header cells.
- Mountain landscape at sunset -
Rocky Mountains, Colorado
+ Mountain landscape at sunset +
Rocky Mountains, Colorado
Responsive image example ``` @@ -458,9 +458,9 @@ Place scripts at the end of body or use defer attribute. ```html - + - + ``` @@ -468,7 +468,7 @@ Or with defer: ```html - + ``` @@ -494,8 +494,8 @@ Avoid inline scripts and styles. Use external files. diff --git a/.windsurf/rules/wordpress/javascript.md b/.windsurf/rules/wordpress/javascript.md index f41be23ac9..7e02ea69b1 100644 --- a/.windsurf/rules/wordpress/javascript.md +++ b/.windsurf/rules/wordpress/javascript.md @@ -24,14 +24,14 @@ New code must NOT use jQuery. Use native DOM APIs and modern JavaScript. ```javascript // INCORRECT - jQuery -$(".my-element").addClass("active"); -$(".my-element").on("click", handler); -$.ajax({ url: "/api" }); +$( '.my-element' ).addClass( 'active' ); +$( '.my-element' ).on( 'click', handler ); +$.ajax( { url: '/api' } ); // CORRECT - Native -document.querySelector(".my-element").classList.add("active"); -document.querySelector(".my-element").addEventListener("click", handler); -fetch("/api"); +document.querySelector( '.my-element' ).classList.add( 'active' ); +document.querySelector( '.my-element' ).addEventListener( 'click', handler ); +fetch( '/api' ); ``` ### Functional Over Class-Based @@ -41,26 +41,26 @@ Prefer functional and modular implementations over classes. ```javascript // INCORRECT - Class-based class TemplateManager { - constructor() { - this.templates = []; - } + constructor() { + this.templates = []; + } - addTemplate(template) { - this.templates.push(template); - } + addTemplate( template ) { + this.templates.push( template ); + } } // CORRECT - Functional/Modular const createTemplateManager = () => { - const templates = []; + const templates = []; - const addTemplate = (template) => { - templates.push(template); - }; + const addTemplate = ( template ) => { + templates.push( template ); + }; - const getTemplates = () => [...templates]; + const getTemplates = () => [ ...templates ]; - return { addTemplate, getTemplates }; + return { addTemplate, getTemplates }; }; ``` @@ -98,9 +98,9 @@ let c = 3; // INCORRECT const a = 1, - b = 2; + b = 2; let c = 3, - d = 4; + d = 4; ``` ### Declare Close to First Use @@ -109,17 +109,17 @@ Declare variables close to where they are first used, not at the top of function ```javascript // CORRECT -function processItems(items) { - if (!items.length) { - return []; - } - - const processed = []; - for (const item of items) { - const result = transform(item); - processed.push(result); - } - return processed; +function processItems( items ) { + if ( ! items.length ) { + return []; + } + + const processed = []; + for ( const item of items ) { + const result = transform( item ); + processed.push( result ); + } + return processed; } ``` @@ -144,13 +144,13 @@ Use clear, descriptive names. Avoid abbreviations except well-known ones. ```javascript // CORRECT const userAuthenticationToken = getToken(); -const isFormValid = validateForm(formData); -const handleFormSubmit = (event) => {}; +const isFormValid = validateForm( formData ); +const handleFormSubmit = ( event ) => {}; // INCORRECT const uat = getToken(); -const valid = validateForm(formData); -const submit = (e) => {}; +const valid = validateForm( formData ); +const submit = ( e ) => {}; ``` --- @@ -163,16 +163,16 @@ Use arrow functions for callbacks and short functions. ```javascript // CORRECT -const numbers = [1, 2, 3]; -const doubled = numbers.map((n) => n * 2); +const numbers = [ 1, 2, 3 ]; +const doubled = numbers.map( ( n ) => n * 2 ); -elements.forEach((element) => { - element.classList.add("active"); -}); +elements.forEach( ( element ) => { + element.classList.add( 'active' ); +} ); // Named function for complex logic -function processComplexData(data) { - // Multiple statements +function processComplexData( data ) { + // Multiple statements } ``` @@ -182,16 +182,16 @@ Prefer pure functions without side effects. ```javascript // CORRECT - Pure function -const calculateTotal = (items) => { - return items.reduce((sum, item) => sum + item.price, 0); +const calculateTotal = ( items ) => { + return items.reduce( ( sum, item ) => sum + item.price, 0 ); }; // INCORRECT - Impure function with side effects let total = 0; -const calculateTotal = (items) => { - items.forEach((item) => { - total += item.price; // Mutates external state - }); +const calculateTotal = ( items ) => { + items.forEach( ( item ) => { + total += item.price; // Mutates external state + } ); }; ``` @@ -201,14 +201,14 @@ Use default parameters instead of conditional logic. ```javascript // CORRECT -function createUser(name, role = "subscriber") { - return { name, role }; +function createUser( name, role = 'subscriber' ) { + return { name, role }; } // INCORRECT -function createUser(name, role) { - role = role || "subscriber"; - return { name, role }; +function createUser( name, role ) { + role = role || 'subscriber'; + return { name, role }; } ``` @@ -218,13 +218,13 @@ Use rest parameters instead of `arguments` object. ```javascript // CORRECT -function sum(...numbers) { - return numbers.reduce((total, n) => total + n, 0); +function sum( ...numbers ) { + return numbers.reduce( ( total, n ) => total + n, 0 ); } // INCORRECT function sum() { - return Array.prototype.slice.call(arguments).reduce((t, n) => t + n, 0); + return Array.prototype.slice.call( arguments ).reduce( ( t, n ) => t + n, 0 ); } ``` @@ -234,29 +234,29 @@ Use early returns to avoid deep nesting. ```javascript // CORRECT -function processUser(user) { - if (!user) { - return null; - } +function processUser( user ) { + if ( ! user ) { + return null; + } - if (!user.isActive) { - return { error: "User inactive" }; - } + if ( ! user.isActive ) { + return { error: 'User inactive' }; + } - return { data: user.profile }; + return { data: user.profile }; } // INCORRECT -function processUser(user) { - if (user) { - if (user.isActive) { - return { data: user.profile }; - } else { - return { error: "User inactive" }; - } - } else { - return null; - } +function processUser( user ) { + if ( user ) { + if ( user.isActive ) { + return { data: user.profile }; + } else { + return { error: 'User inactive' }; + } + } else { + return null; + } } ``` @@ -270,23 +270,23 @@ Use shorthand property and method syntax. ```javascript // CORRECT -const name = "John"; +const name = 'John'; const age = 30; const user = { - name, - age, - greet() { - return `Hello, ${this.name}`; - }, + name, + age, + greet() { + return `Hello, ${ this.name }`; + }, }; // INCORRECT const user = { - name: name, - age: age, - greet: function () { - return "Hello, " + this.name; - }, + name: name, + age: age, + greet: function () { + return 'Hello, ' + this.name; + }, }; ``` @@ -297,17 +297,17 @@ Use destructuring for objects and arrays. ```javascript // CORRECT const { name, email } = user; -const [first, second] = items; +const [ first, second ] = items; const { data: userData } = response; -function processUser({ name, email, role = "user" }) { - // Use destructured values +function processUser( { name, email, role = 'user' } ) { + // Use destructured values } // INCORRECT const name = user.name; const email = user.email; -const first = items[0]; +const first = items[ 0 ]; ``` ### Spread Operator @@ -316,13 +316,13 @@ Use spread for copying and merging. ```javascript // CORRECT -const newArray = [...oldArray, newItem]; +const newArray = [ ...oldArray, newItem ]; const newObject = { ...oldObject, newProperty: value }; const merged = { ...defaults, ...options }; // INCORRECT -const newArray = oldArray.concat([newItem]); -const newObject = Object.assign({}, oldObject, { newProperty: value }); +const newArray = oldArray.concat( [ newItem ] ); +const newObject = Object.assign( {}, oldObject, { newProperty: value } ); ``` ### Computed Property Names @@ -331,10 +331,10 @@ Use computed property names when needed. ```javascript // CORRECT -const key = "dynamicKey"; +const key = 'dynamicKey'; const obj = { - [key]: value, - [`prefix_${key}`]: otherValue, + [ key ]: value, + [ `prefix_${ key }` ]: otherValue, }; ``` @@ -348,13 +348,13 @@ Use ES modules (import/export) for all new code. ```javascript // CORRECT -import { getState, setState } from "./shared"; -import defaultExport from "./module"; +import { getState, setState } from './shared'; +import defaultExport from './module'; export const myFunction = () => {}; export default mainFunction; // INCORRECT -const module = require("./module"); +const module = require( './module' ); module.exports = myFunction; ``` @@ -364,11 +364,11 @@ Prefer named exports over default exports for better refactoring. ```javascript // CORRECT -export const processData = (data) => {}; -export const validateData = (data) => {}; +export const processData = ( data ) => {}; +export const validateData = ( data ) => {}; // Use -import { processData, validateData } from "./utils"; +import { processData, validateData } from './utils'; ``` ### Single Import Per Module @@ -377,11 +377,11 @@ Import from a module only once per file. ```javascript // CORRECT -import { foo, bar, baz } from "./utils"; +import { foo, bar, baz } from './utils'; // INCORRECT -import { foo } from "./utils"; -import { bar } from "./utils"; +import { foo } from './utils'; +import { bar } from './utils'; ``` ### No Wildcard Imports @@ -390,10 +390,10 @@ Avoid wildcard imports in production code. ```javascript // CORRECT -import { specificFunction } from "./utils"; +import { specificFunction } from './utils'; // INCORRECT -import * as utils from "./utils"; +import * as utils from './utils'; ``` --- @@ -406,60 +406,60 @@ Use native query selectors. ```javascript // Single element -const element = document.querySelector(".my-class"); -const byId = document.getElementById("my-id"); +const element = document.querySelector( '.my-class' ); +const byId = document.getElementById( 'my-id' ); // Multiple elements -const elements = document.querySelectorAll(".my-class"); +const elements = document.querySelectorAll( '.my-class' ); // Scoped query -const child = parent.querySelector(".child"); +const child = parent.querySelector( '.child' ); ``` ### Element Creation ```javascript // Create element -const div = document.createElement("div"); -div.className = "my-class"; -div.textContent = "Hello"; -div.dataset.id = "123"; +const div = document.createElement( 'div' ); +div.className = 'my-class'; +div.textContent = 'Hello'; +div.dataset.id = '123'; // Append -container.appendChild(div); +container.appendChild( div ); // Insert HTML (use with caution, escape user input) -container.insertAdjacentHTML("beforeend", '
'); +container.insertAdjacentHTML( 'beforeend', '
' ); ``` ### Event Handling ```javascript // Add listener -element.addEventListener("click", handleClick); +element.addEventListener( 'click', handleClick ); // Remove listener -element.removeEventListener("click", handleClick); +element.removeEventListener( 'click', handleClick ); // Event delegation -container.addEventListener("click", (event) => { - if (event.target.matches(".button")) { - handleButtonClick(event); - } -}); +container.addEventListener( 'click', ( event ) => { + if ( event.target.matches( '.button' ) ) { + handleButtonClick( event ); + } +} ); // Custom events -element.dispatchEvent(new CustomEvent("custom-event", { detail: data })); +element.dispatchEvent( new CustomEvent( 'custom-event', { detail: data } ) ); ``` ### Class Manipulation ```javascript -element.classList.add("active"); -element.classList.remove("active"); -element.classList.toggle("active"); -element.classList.contains("active"); -element.classList.replace("old", "new"); +element.classList.add( 'active' ); +element.classList.remove( 'active' ); +element.classList.toggle( 'active' ); +element.classList.contains( 'active' ); +element.classList.replace( 'old', 'new' ); ``` --- @@ -472,17 +472,17 @@ Use Fetch API instead of XMLHttpRequest or jQuery.ajax. ```javascript // GET request -const response = await fetch("/api/data"); +const response = await fetch( '/api/data' ); const data = await response.json(); // POST request -const response = await fetch("/api/submit", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(payload), -}); +const response = await fetch( '/api/submit', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( payload ), +} ); ``` ### Async/Await @@ -492,25 +492,25 @@ Prefer async/await over Promise chains. ```javascript // CORRECT async function loadData() { - try { - const response = await fetch(url); - if (!response.ok) { - throw new Error("Network error"); - } - const data = await response.json(); - return processData(data); - } catch (error) { - handleError(error); - return null; - } + try { + const response = await fetch( url ); + if ( ! response.ok ) { + throw new Error( 'Network error' ); + } + const data = await response.json(); + return processData( data ); + } catch ( error ) { + handleError( error ); + return null; + } } // INCORRECT - Promise chain for simple operations function loadData() { - return fetch(url) - .then((response) => response.json()) - .then((data) => processData(data)) - .catch((error) => handleError(error)); + return fetch( url ) + .then( ( response ) => response.json() ) + .then( ( data ) => processData( data ) ) + .catch( ( error ) => handleError( error ) ); } ``` @@ -520,11 +520,11 @@ Use Promise.all for independent parallel operations. ```javascript // CORRECT - Parallel execution -const [users, posts, comments] = await Promise.all([ - fetchUsers(), - fetchPosts(), - fetchComments(), -]); +const [ users, posts, comments ] = await Promise.all( [ + fetchUsers(), + fetchPosts(), + fetchComments(), +] ); // INCORRECT - Sequential when parallel is possible const users = await fetchUsers(); @@ -542,13 +542,13 @@ Always wrap async operations in try-catch. ```javascript async function fetchData() { - try { - const response = await fetch(url); - return await response.json(); - } catch (error) { - console.error("Fetch failed:", error.message); - return null; - } + try { + const response = await fetch( url ); + return await response.json(); + } catch ( error ) { + console.error( 'Fetch failed:', error.message ); + return null; + } } ``` @@ -558,10 +558,10 @@ Provide context in error messages. ```javascript // CORRECT -throw new Error(`Failed to load template: ${templateId}`); +throw new Error( `Failed to load template: ${ templateId }` ); // INCORRECT -throw new Error("Error"); +throw new Error( 'Error' ); ``` --- @@ -575,21 +575,21 @@ Use closures or modules for state management. ```javascript // CORRECT - Module pattern from codebase import { - getState, - getSingleState, - setState, - setSingleState, -} from "core/page-skeleton"; + getState, + getSingleState, + setState, + setSingleState, +} from 'core/page-skeleton'; // Initialize state -setState({ - currentView: "list", - selectedItem: null, - isLoading: false, -}); +setState( { + currentView: 'list', + selectedItem: null, + isLoading: false, +} ); // Update state -setSingleState("isLoading", true); +setSingleState( 'isLoading', true ); // Read state const { currentView, selectedItem } = getState(); @@ -602,12 +602,12 @@ Never mutate state directly. ```javascript // CORRECT const newState = { - ...state, - items: [...state.items, newItem], + ...state, + items: [ ...state.items, newItem ], }; // INCORRECT -state.items.push(newItem); +state.items.push( newItem ); ``` --- @@ -618,29 +618,29 @@ state.items.push(newItem); ```javascript // INCORRECT -myGlobalVar = "value"; +myGlobalVar = 'value'; window.myApp = {}; // CORRECT - Use modules -export const myValue = "value"; +export const myValue = 'value'; ``` ### Callback Hell ```javascript // INCORRECT -getData((data) => { - processData(data, (result) => { - saveData(result, (response) => { - // Deeply nested - }); - }); -}); +getData( ( data ) => { + processData( data, ( result ) => { + saveData( result, ( response ) => { + // Deeply nested + } ); + } ); +} ); // CORRECT const data = await getData(); -const result = await processData(data); -const response = await saveData(result); +const result = await processData( data ); +const response = await saveData( result ); ``` ### Modifying Built-in Prototypes @@ -663,15 +663,15 @@ new Function(userInput)(); ```javascript // INCORRECT -if (status === 3) { +if ( status === 3 ) { } -element.style.width = "768px"; +element.style.width = '768px'; // CORRECT const STATUS_COMPLETE = 3; -const TABLET_BREAKPOINT = "768px"; +const TABLET_BREAKPOINT = '768px'; -if (status === STATUS_COMPLETE) { +if ( status === STATUS_COMPLETE ) { } element.style.width = TABLET_BREAKPOINT; ``` @@ -680,31 +680,31 @@ element.style.width = TABLET_BREAKPOINT; ```javascript // INCORRECT -document.querySelector(".item").classList.add("active"); -document.querySelector(".item").textContent = "Updated"; -document.querySelector(".item").dataset.id = "123"; +document.querySelector( '.item' ).classList.add( 'active' ); +document.querySelector( '.item' ).textContent = 'Updated'; +document.querySelector( '.item' ).dataset.id = '123'; // CORRECT - Cache the reference -const item = document.querySelector(".item"); -item.classList.add("active"); -item.textContent = "Updated"; -item.dataset.id = "123"; +const item = document.querySelector( '.item' ); +item.classList.add( 'active' ); +item.textContent = 'Updated'; +item.dataset.id = '123'; ``` ### DOM Manipulation in Loops ```javascript // INCORRECT - Causes reflow on each iteration -items.forEach((item) => { - container.appendChild(createItem(item)); -}); +items.forEach( ( item ) => { + container.appendChild( createItem( item ) ); +} ); // CORRECT - Use DocumentFragment const fragment = document.createDocumentFragment(); -items.forEach((item) => { - fragment.appendChild(createItem(item)); -}); -container.appendChild(fragment); +items.forEach( ( item ) => { + fragment.appendChild( createItem( item ) ); +} ); +container.appendChild( fragment ); ``` --- @@ -713,7 +713,7 @@ container.appendChild(fragment); Follow modular folder structure as in form-templates and onboarding-wizard. -``` +```text module-name/ ├── index.js # Entry point, exports public API ├── initializeModule.js # Initialization logic @@ -743,24 +743,24 @@ module-name/ ```javascript // Actions -wp.hooks.doAction("myPlugin.beforeInit", { getState, setState }); -wp.hooks.addAction("myPlugin.afterSave", "myPlugin", handleAfterSave); +wp.hooks.doAction( 'myPlugin.beforeInit', { getState, setState } ); +wp.hooks.addAction( 'myPlugin.afterSave', 'myPlugin', handleAfterSave ); // Filters -const value = wp.hooks.applyFilters("myPlugin.filterValue", defaultValue); -wp.hooks.addFilter("myPlugin.filterValue", "myPlugin", modifyValue); +const value = wp.hooks.applyFilters( 'myPlugin.filterValue', defaultValue ); +wp.hooks.addFilter( 'myPlugin.filterValue', 'myPlugin', modifyValue ); ``` ### Using @wordpress packages ```javascript -import domReady from "@wordpress/dom-ready"; -import { __ } from "@wordpress/i18n"; -import apiFetch from "@wordpress/api-fetch"; +import domReady from '@wordpress/dom-ready'; +import { __ } from '@wordpress/i18n'; +import apiFetch from '@wordpress/api-fetch'; -domReady(() => { - initializeModule(); -}); +domReady( () => { + initializeModule(); +} ); ``` --- @@ -775,11 +775,11 @@ domReady(() => { - Spaces around operators ```javascript -if (condition) { - doSomething(arg1, arg2); +if ( condition ) { + doSomething( arg1, arg2 ); } -const isValid = !isEmpty && hasValue; +const isValid = ! isEmpty && hasValue; const total = a + b * c; ``` @@ -789,7 +789,7 @@ Keep lines under 100 characters. Break after operators. ```javascript const result = - veryLongVariableName + anotherLongVariableName + yetAnotherVariable; + veryLongVariableName + anotherLongVariableName + yetAnotherVariable; ``` ### Trailing Commas @@ -798,11 +798,11 @@ Use trailing commas in multiline structures. ```javascript const obj = { - property1: "value1", - property2: "value2", + property1: 'value1', + property2: 'value2', }; -const arr = ["item1", "item2"]; +const arr = [ 'item1', 'item2' ]; ``` --- @@ -817,17 +817,17 @@ Never use class components. Use functional components with hooks. ```jsx // CORRECT -function MyComponent({ title, onSave }) { - const [isLoading, setIsLoading] = useState(false); +function MyComponent( { title, onSave } ) { + const [ isLoading, setIsLoading ] = useState( false ); - return
{title}
; + return
{ title }
; } // INCORRECT class MyComponent extends Component { - render() { - return
{this.props.title}
; - } + render() { + return
{ this.props.title }
; + } } ``` @@ -838,16 +838,16 @@ class MyComponent extends Component { 3. Use `@wordpress/element` for React hooks in WordPress ```jsx -import { useState, useEffect, useCallback, useMemo } from "@wordpress/element"; +import { useState, useEffect, useCallback, useMemo } from '@wordpress/element'; -function MyComponent({ items }) { - // CORRECT - Hooks at top level - const [selected, setSelected] = useState(null); +function MyComponent( { items } ) { + // CORRECT - Hooks at top level + const [ selected, setSelected ] = useState( null ); - // INCORRECT - Conditional hook - if (items.length) { - const [count, setCount] = useState(0); // Error! - } + // INCORRECT - Conditional hook + if ( items.length ) { + const [ count, setCount ] = useState( 0 ); // Error! + } } ``` @@ -856,31 +856,31 @@ function MyComponent({ items }) { Use `useMemo` for expensive computations and `useCallback` for stable function references. ```jsx -import { useMemo, useCallback } from "@wordpress/element"; - -function ItemList({ items, onSelect }) { - // Memoize expensive computation - const processedItems = useMemo(() => { - return items.map((item) => expensiveProcess(item)); - }, [items]); - - // Stable callback reference - const handleSelect = useCallback( - (id) => { - onSelect(id); - }, - [onSelect], - ); - - return ( -
    - {processedItems.map((item) => ( -
  • handleSelect(item.id)}> - {item.name} -
  • - ))} -
- ); +import { useMemo, useCallback } from '@wordpress/element'; + +function ItemList( { items, onSelect } ) { + // Memoize expensive computation + const processedItems = useMemo( () => { + return items.map( ( item ) => expensiveProcess( item ) ); + }, [ items ] ); + + // Stable callback reference + const handleSelect = useCallback( + ( id ) => { + onSelect( id ); + }, + [ onSelect ], + ); + + return ( +
    + { processedItems.map( ( item ) => ( +
  • handleSelect( item.id ) }> + { item.name } +
  • + ) ) } +
+ ); } ``` @@ -908,21 +908,21 @@ Derive values during render instead of syncing with effects. ```jsx // CORRECT - Derived during render -function FilteredList({ items, filter }) { - const filteredItems = items.filter((item) => item.type === filter); +function FilteredList( { items, filter } ) { + const filteredItems = items.filter( ( item ) => item.type === filter ); - return ; + return ; } // INCORRECT - Unnecessary state and effect -function FilteredList({ items, filter }) { - const [filteredItems, setFilteredItems] = useState([]); +function FilteredList( { items, filter } ) { + const [ filteredItems, setFilteredItems ] = useState( [] ); - useEffect(() => { - setFilteredItems(items.filter((item) => item.type === filter)); - }, [items, filter]); + useEffect( () => { + setFilteredItems( items.filter( ( item ) => item.type === filter ) ); + }, [ items, filter ] ); - return ; + return ; } ``` @@ -932,10 +932,10 @@ Use lazy initialization for expensive values. ```jsx // CORRECT - Lazy initialization (function only called once) -const [state, setState] = useState(() => computeExpensiveValue(props)); +const [ state, setState ] = useState( () => computeExpensiveValue( props ) ); // INCORRECT - Runs on every render -const [state, setState] = useState(computeExpensiveValue(props)); +const [ state, setState ] = useState( computeExpensiveValue( props ) ); ``` ### Functional setState @@ -944,11 +944,11 @@ Use functional updates when new state depends on previous state. ```jsx // CORRECT - Functional update -setCount((prevCount) => prevCount + 1); -setItems((prevItems) => [...prevItems, newItem]); +setCount( ( prevCount ) => prevCount + 1 ); +setItems( ( prevItems ) => [ ...prevItems, newItem ] ); // INCORRECT - May use stale state -setCount(count + 1); +setCount( count + 1 ); ``` ### useRef for Transient Values @@ -957,35 +957,35 @@ Use refs for values that change frequently but do not need re-renders. ```jsx function Draggable() { - const positionRef = useRef({ x: 0, y: 0 }); + const positionRef = useRef( { x: 0, y: 0 } ); - const handleMouseMove = (event) => { - // Update ref without re-render - positionRef.current = { x: event.clientX, y: event.clientY }; - }; + const handleMouseMove = ( event ) => { + // Update ref without re-render + positionRef.current = { x: event.clientX, y: event.clientY }; + }; - return
; + return
; } ``` ### Early Returns in Components ```jsx -function UserProfile({ user, isLoading }) { - if (isLoading) { - return ; - } - - if (!user) { - return ; - } - - return ( -
-

{user.name}

- {/* Rest of component */} -
- ); +function UserProfile( { user, isLoading } ) { + if ( isLoading ) { + return ; + } + + if ( ! user ) { + return ; + } + + return ( +
+

{ user.name }

+ { /* Rest of component */ } +
+ ); } ``` @@ -997,7 +997,7 @@ function UserProfile({ user, isLoading }) { ```jsx // CORRECT - Parallel execution -const [users, posts] = await Promise.all([fetchUsers(), fetchPosts()]); +const [ users, posts ] = await Promise.all( [ fetchUsers(), fetchPosts() ] ); // INCORRECT - Sequential when parallel is possible const users = await fetchUsers(); @@ -1011,13 +1011,13 @@ const posts = await fetchPosts(); const cache = new Map(); function expensiveOperation( key ) { - if ( cache.has( key ) ) { - return cache.get( key ); - } + if ( cache.has( key ) ) { + return cache.get( key ); + } - const result = /* expensive computation */; - cache.set( key, result ); - return result; + const result = /* expensive computation */; + cache.set( key, result ); + return result; } ``` @@ -1025,11 +1025,11 @@ function expensiveOperation( key ) { ```jsx // CORRECT - O(1) lookup -const selectedIds = new Set(selectedItems.map((item) => item.id)); -const isSelected = (id) => selectedIds.has(id); +const selectedIds = new Set( selectedItems.map( ( item ) => item.id ) ); +const isSelected = ( id ) => selectedIds.has( id ); // INCORRECT - O(n) lookup -const isSelected = (id) => selectedItems.some((item) => item.id === id); +const isSelected = ( id ) => selectedItems.some( ( item ) => item.id === id ); ``` ### Batch DOM Operations @@ -1037,27 +1037,27 @@ const isSelected = (id) => selectedItems.some((item) => item.id === id); ```jsx // CORRECT - Single reflow const fragment = document.createDocumentFragment(); -items.forEach((item) => { - const li = document.createElement("li"); - li.textContent = item.name; - fragment.appendChild(li); -}); -container.appendChild(fragment); +items.forEach( ( item ) => { + const li = document.createElement( 'li' ); + li.textContent = item.name; + fragment.appendChild( li ); +} ); +container.appendChild( fragment ); // INCORRECT - Multiple reflows -items.forEach((item) => { - const li = document.createElement("li"); - li.textContent = item.name; - container.appendChild(li); // Reflow on each iteration -}); +items.forEach( ( item ) => { + const li = document.createElement( 'li' ); + li.textContent = item.name; + container.appendChild( li ); // Reflow on each iteration +} ); ``` ### Content Visibility for Long Lists ```css .long-list-item { - content-visibility: auto; - contain-intrinsic-size: 0 50px; + content-visibility: auto; + contain-intrinsic-size: 0 50px; } ``` @@ -1072,21 +1072,21 @@ Instead of boolean props, use composition. ```jsx // INCORRECT - Boolean prop proliferation // CORRECT - Compound components - Title - - Content - - - + Title + + Content + + + ``` @@ -1095,45 +1095,45 @@ Instead of boolean props, use composition. ```jsx // CORRECT - Children for composition - Title - { content } + Title + { content } // AVOID - Render props when children work
Title
} - renderContent={ () => content } + renderHeader={ () =>
Title
} + renderContent={ () => content } /> ``` ### Context for Shared State ```jsx -import { createContext, useContext, useState } from "@wordpress/element"; +import { createContext, useContext, useState } from '@wordpress/element'; const FormContext = createContext(); -function FormProvider({ children }) { - const [values, setValues] = useState({}); - const [errors, setErrors] = useState({}); +function FormProvider( { children } ) { + const [ values, setValues ] = useState( {} ); + const [ errors, setErrors ] = useState( {} ); - const setValue = (name, value) => { - setValues((prev) => ({ ...prev, [name]: value })); - }; + const setValue = ( name, value ) => { + setValues( ( prev ) => ( { ...prev, [ name ]: value } ) ); + }; - return ( - - {children} - - ); + return ( + + { children } + + ); } function useForm() { - const context = useContext(FormContext); - if (!context) { - throw new Error("useForm must be used within FormProvider"); - } - return context; + const context = useContext( FormContext ); + if ( ! context ) { + throw new Error( 'useForm must be used within FormProvider' ); + } + return context; } ``` diff --git a/.windsurf/rules/wordpress/php.md b/.windsurf/rules/wordpress/php.md index ee477531a8..1c8f11c665 100644 --- a/.windsurf/rules/wordpress/php.md +++ b/.windsurf/rules/wordpress/php.md @@ -57,14 +57,14 @@ All new code must be compatible with PHP 7.0. Use modern PHP 7.0 syntax but do n * @return bool Whether the operation succeeded. */ function process_user_data( int $user_id, string $action, array $options = array() ): bool { - $timeout = $options['timeout'] ?? 30; - $result = $options['result'] ?? 'default'; + $timeout = $options['timeout'] ?? 30; + $result = $options['result'] ?? 'default'; - if ( empty( $user_id ) ) { - return false; - } + if ( empty( $user_id ) ) { + return false; + } - return true; + return true; } ``` @@ -78,11 +78,11 @@ Always use `$wpdb->prepare()` for queries with variables. ```php $wpdb->query( - $wpdb->prepare( - "UPDATE $wpdb->posts SET post_title = %s WHERE ID = %d", - $var, - $id - ) + $wpdb->prepare( + "UPDATE $wpdb->posts SET post_title = %s WHERE ID = %d", + $var, + $id + ) ); ``` @@ -103,19 +103,19 @@ $wpdb->query( ```php $wpdb->query( - $wpdb->prepare( - " - SELECT post_title, post_content - FROM $wpdb->posts - WHERE post_status = %s - AND post_type = %s - ORDER BY post_date DESC - LIMIT %d - ", - 'publish', - 'post', - 10 - ) + $wpdb->prepare( + " + SELECT post_title, post_content + FROM $wpdb->posts + WHERE post_status = %s + AND post_type = %s + ORDER BY post_date DESC + LIMIT %d + ", + 'publish', + 'post', + 10 + ) ); ``` @@ -129,7 +129,7 @@ Use lowercase with underscores. Never use camelCase. ```php function some_function( $some_variable ) { - $local_variable = ''; + $local_variable = ''; } ``` @@ -176,11 +176,11 @@ Always use braces even for single-line blocks. Opening brace on same line. ```php if ( condition ) { - action(); + action(); } elseif ( condition2 ) { - action2(); + action2(); } else { - default_action(); + default_action(); } ``` @@ -190,8 +190,8 @@ Use long array syntax `array()`, not short syntax `[]`. ```php $args = array( - 'post_type' => 'page', - 'post_status' => 'publish', + 'post_type' => 'page', + 'post_status' => 'publish', ); ``` @@ -199,12 +199,12 @@ $args = array( ```php $result = some_function( - $arg1, - $arg2, - array( - 'key1' => 'value1', - 'key2' => 'value2', - ) + $arg1, + $arg2, + array( + 'key1' => 'value1', + 'key2' => 'value2', + ) ); ``` @@ -214,7 +214,7 @@ One space before and after type declarations. ```php function foo( Class_Name $parameter, callable $callable, int $count = 0 ): bool { - return true; + return true; } ``` @@ -224,7 +224,7 @@ No space between spread operator and variable. ```php function foo( &...$spread ) { - bar( ...$spread ); + bar( ...$spread ); } ``` @@ -275,12 +275,12 @@ Put the constant or literal value on the left side of comparisons. ```php // Correct if ( true === $the_force ) { - $victorious = you_will( $be ); + $victorious = you_will( $be ); } // Incorrect if ( $the_force === true ) { - $victorious = you_will( $be ); + $victorious = you_will( $be ); } ``` @@ -291,16 +291,16 @@ Always use `elseif`, not `else if`. ```php // Correct if ( condition ) { - action(); + action(); } elseif ( condition2 ) { - action2(); + action2(); } // Incorrect if ( condition ) { - action(); + action(); } else if ( condition2 ) { - action2(); + action2(); } ``` @@ -310,11 +310,11 @@ Use alternative syntax with explicit ending statements in templates. ```php - -
- -
- + +
+ +
+ ``` @@ -357,7 +357,7 @@ $value = @file_get_contents( $file ); // Correct if ( file_exists( $file ) && is_readable( $file ) ) { - $value = file_get_contents( $file ); + $value = file_get_contents( $file ); } ``` @@ -385,7 +385,7 @@ Use descriptive string values instead of boolean flags. ```php // Correct function some_function( $args ) { - $type = $args['type'] ?? 'default'; + $type = $args['type'] ?? 'default'; } some_function( array( 'type' => 'hierarchical' ) ); @@ -422,13 +422,13 @@ Do not use closures (anonymous functions) as callbacks for actions and filters. ```php // Incorrect add_action( 'init', function() { - // code + // code } ); // Correct add_action( 'init', 'my_init_function' ); function my_init_function() { - // code + // code } ``` @@ -516,13 +516,13 @@ Always declare visibility for properties and methods. ```php class My_Class { - public $public_property; - protected $protected_property; - private $private_property; + public $public_property; + protected $protected_property; + private $private_property; - public function public_method() {} - protected function protected_method() {} - private function private_method() {} + public function public_method() {} + protected function protected_method() {} + private function private_method() {} } ``` @@ -617,7 +617,7 @@ Use magic methods appropriately and document them. * @return mixed Property value. */ public function __get( $name ) { - return $this->data[ $name ] ?? null; + return $this->data[ $name ] ?? null; } ``` @@ -631,7 +631,7 @@ Space after `function` keyword. Space before and after `use`. ```php $closure = function ( $arg ) use ( $var ) { - return $arg . $var; + return $arg . $var; }; ``` From ed812872559b6dc8b39e39fbb2c75077117f3182 Mon Sep 17 00:00:00 2001 From: Sherv Date: Thu, 12 Feb 2026 14:25:52 +0300 Subject: [PATCH 54/90] feat(windsurf): add EditorConfig and markdownlint configuration for .windsurf directory to enforce WordPress tab-based indentation in rule markdown files Add .editorconfig override setting indent_style to tab and indent_size to 4 for .windsurf/*.md files (overriding root config's space/2 for code blocks matching WP standards). Add .markdownlint.json with ATX heading style, 4-space indent, disabled line length limit, and allowed hard tabs/whitespace for WordPress coding standards compliance in rule --- .windsurf/.editorconfig | 7 +++++++ .windsurf/.markdownlint.json | 8 ++++++++ 2 files changed, 15 insertions(+) create mode 100644 .windsurf/.editorconfig create mode 100644 .windsurf/.markdownlint.json diff --git a/.windsurf/.editorconfig b/.windsurf/.editorconfig new file mode 100644 index 0000000000..fe54d9f5fe --- /dev/null +++ b/.windsurf/.editorconfig @@ -0,0 +1,7 @@ +# Override parent EditorConfig for .windsurf/ markdown files +# Code blocks in these rule files must use WP coding standards (tabs, indent_size 4) +# while all other .md files in the project keep space/2 from the root config. + +[*.md] +indent_style = tab +indent_size = 4 diff --git a/.windsurf/.markdownlint.json b/.windsurf/.markdownlint.json new file mode 100644 index 0000000000..0d203db7f5 --- /dev/null +++ b/.windsurf/.markdownlint.json @@ -0,0 +1,8 @@ +{ + "default": true, + "MD003": { "style": "atx" }, + "MD007": { "indent": 4 }, + "MD013": { "line_length": 9999 }, + "no-hard-tabs": false, + "whitespace": false +} From 554da0a0ae96e3aac9331fc5d6233de8610a91f5 Mon Sep 17 00:00:00 2001 From: Sherv Date: Thu, 12 Feb 2026 14:26:02 +0300 Subject: [PATCH 55/90] feat(build): exclude .windsurf directory from plugin zip distribution Add .windsurf directory to zip exclusion list in bin/zip-plugin.sh to prevent AI coding assistant configuration files from being included in production plugin builds. Maintains alphabetical ordering of exclusion patterns. --- bin/zip-plugin.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/zip-plugin.sh b/bin/zip-plugin.sh index 974e91c274..47b19ccae1 100755 --- a/bin/zip-plugin.sh +++ b/bin/zip-plugin.sh @@ -43,6 +43,7 @@ zip -r $zipname $destination \ -x "*/.gitattributes" \ -x "*/.github/*" \ -x "*/.gitignore" \ + -x "*/.windsurf/*" \ -x "*/.jshintignore" \ -x "*/.php-cs-fixer.cache" \ -x "*/.php-cs-fixer.php" \ From c32bf0aed04bc27b574552fc8897b7a589d8b90b Mon Sep 17 00:00:00 2001 From: Sherv Date: Thu, 12 Feb 2026 15:21:29 +0300 Subject: [PATCH 56/90] feat(windsurf): expand fix-bug checklist with comprehensive analysis, implementation, and compatibility verification steps Add detailed analysis phase items (complete context analysis, parent hierarchy tracing, dependency mapping, pattern study, platform-specific docs). Add solution selection criteria (safety checks preference, pattern matching, testability verification). Add implementation requirements (minimal changes, signature preservation, no unrelated refactoring, defensive checks at data entry points --- .windsurf/skills/fix-bug/checklist.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.windsurf/skills/fix-bug/checklist.md b/.windsurf/skills/fix-bug/checklist.md index 4d70e4b762..96c394a6db 100644 --- a/.windsurf/skills/fix-bug/checklist.md +++ b/.windsurf/skills/fix-bug/checklist.md @@ -4,10 +4,15 @@ - [ ] Understood the complete issue - [ ] Identified root cause (not just symptoms) +- [ ] Analyzed complete context of changed class/file +- [ ] Traced parent hierarchy to plugin root - [ ] Mapped all affected locations +- [ ] Mapped dependencies (what calls this code, what it calls) +- [ ] Studied ALL places using the affected pattern - [ ] Checked Pro plugin requirement - [ ] Found existing patterns for the fix -- [ ] Searched WordPress docs for best practices +- [ ] Searched WordPress/VIP docs for best practices +- [ ] Searched platform-specific docs (performance, security) ## Solution Selection @@ -15,10 +20,17 @@ - [ ] Documented trade-offs for each - [ ] Selected minimal scope solution - [ ] Verified fix is at most specific location +- [ ] Preferred safety checks over refactoring - [ ] Confirmed not overkill if affecting multiple areas +- [ ] Verified approach matches existing codebase patterns +- [ ] Fix is testable without touching other code ## Implementation +- [ ] Makes the smallest change that solves the problem +- [ ] Method signatures, return types, and data structures unchanged +- [ ] No unrelated code refactored in the same commit +- [ ] Defensive checks added where data comes in - [ ] Follows WordPress PHP coding standards - [ ] Follows Formidable naming patterns - [ ] Uses existing helper methods From ae4b86f8c848d73ec2a557c73283bf233d03622e Mon Sep 17 00:00:00 2001 From: Sherv Date: Thu, 12 Feb 2026 15:21:39 +0300 Subject: [PATCH 57/90] feat(windsurf): add PR template for bug fixes with branch naming, conventional commits format, and testing guidelines Add comprehensive PR template covering branch naming format (fix/{issue-number}-{short-description} with kebab-case examples), PR title format following Conventional Commits (50 char limit, imperative mood, lowercase, no period with scope table for builder/entries/fields/api/admin/frontend/db/i18n/security/deps), and PR body structure with issue reference, description, and testing verification --- .windsurf/skills/fix-bug/pr-template.md | 53 +++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 .windsurf/skills/fix-bug/pr-template.md diff --git a/.windsurf/skills/fix-bug/pr-template.md b/.windsurf/skills/fix-bug/pr-template.md new file mode 100644 index 0000000000..6cf47e1ed7 --- /dev/null +++ b/.windsurf/skills/fix-bug/pr-template.md @@ -0,0 +1,53 @@ +# PR Template + +## Branch Name + +**Format:** `fix/{issue-number}-{short-description}` + +Use 2-4 word kebab-case description specific to the issue. + +**Examples:** + +- `fix/1234-date-validation-error` +- `fix/5678-entry-export-timeout` +- `fix/910-field-label-xss` + +--- + +## PR Title + +Follow Conventional Commits format: + +```text +fix(scope): brief description +``` + +**Rules:** + +- 50 characters or fewer +- Imperative mood ("fix", not "fixed") +- Lowercase after type/scope +- No period at end + +**Scopes:** builder, entries, fields, api, admin, frontend, db, i18n, security, deps + +**Examples:** + +- `fix(fields): resolve date validation error` +- `fix(entries): prevent export timeout on large data` +- `fix(security): escape HTML in field labels` + +--- + +## PR Body + +```markdown +Fixes #{issue_number} + +Brief description of what this PR fixes. + +### Testing + +- Clarify the steps to reproduce the issue +- Verify that the fix fully resolves the issue +``` From 8608f427cdf0b9bcbf794939492efc7e3f08c070 Mon Sep 17 00:00:00 2001 From: Sherv Date: Thu, 12 Feb 2026 15:21:49 +0300 Subject: [PATCH 58/90] feat(windsurf): add branch/PR section and improve formatting in fix-bug report template Add Branch & PR section with branch naming format (fix/{issue-number}-{short-description}), PR title format (fix(scope): brief description), and reference to pr-template.md. Improve Files Changed table formatting with aligned columns and proper spacing. Change commit message code block language from generic to text for proper syntax highlighting. --- .windsurf/skills/fix-bug/report-template.md | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/.windsurf/skills/fix-bug/report-template.md b/.windsurf/skills/fix-bug/report-template.md index 051a050580..eb3e58113c 100644 --- a/.windsurf/skills/fix-bug/report-template.md +++ b/.windsurf/skills/fix-bug/report-template.md @@ -22,10 +22,10 @@ ## Files Changed -| File | Change | -|------|--------| -| `path/to/file1.php` | [Brief description] | -| `path/to/file2.php` | [Brief description] | +| File | Change | +|----------------------|---------------------| +| `path/to/file1.php` | [Brief description] | +| `path/to/file2.php` | [Brief description] | --- @@ -45,9 +45,19 @@ --- +## Branch & PR + +**Branch:** `fix/{issue-number}-{short-description}` + +**PR Title:** `fix(scope): brief description` + +See [pr-template.md](pr-template.md) for full PR body format. + +--- + ## Commit Message -``` +```text fix(scope): brief description Detailed explanation of what was fixed and why. From bbf12e05676fa7c6c801b569d44ac6ca8ca204e8 Mon Sep 17 00:00:00 2001 From: Sherv Date: Thu, 12 Feb 2026 15:22:17 +0300 Subject: [PATCH 59/90] feat(windsurf): expand fix-bug workflow with core principles, detailed phase breakdown, and research methodology Add Core Principles section covering never guessing, minimal scope, backward compatibility, pattern usage, and user change authority. Expand workflow phases with comprehensive steps: Phase 1 (Understand) adds scope determination; Phase 2 (Locate) adds complete context analysis, parent hierarchy tracing, dependency mapping, and plugin requirement checks; Phase 3 (Research renamed from --- .windsurf/skills/fix-bug/SKILL.md | 93 ++++++++++++++++++++++++++----- 1 file changed, 80 insertions(+), 13 deletions(-) diff --git a/.windsurf/skills/fix-bug/SKILL.md b/.windsurf/skills/fix-bug/SKILL.md index 68bb7a688b..387062461c 100644 --- a/.windsurf/skills/fix-bug/SKILL.md +++ b/.windsurf/skills/fix-bug/SKILL.md @@ -14,26 +14,93 @@ Enterprise bug-fixing workflow for Formidable Forms following WordPress VIP stan - Investigating error logs - Resolving compatibility issues -## Workflow Phases +## Core Principles -1. **Understand** - Clarify issue, expected vs actual behavior, reproduction steps -2. **Locate** - Find root cause using code_search, trace execution flow -3. **Pattern** - Find existing patterns, research WordPress best practices -4. **Design** - Propose 2-3 solutions with trade-offs -5. **Implement** - Apply minimal fix following coding standards -6. **Clean** - Remove debug code, verify code style -7. **Test** - Verify fix, check Pro active/inactive scenarios -8. **Report** - Summarize root cause, solution, and files changed +1. **NEVER guess**: Always search and verify before making changes +2. **Minimal scope**: Fix at the most specific location, closest to the problem +3. **Backward compatibility**: Maintain 100% compatibility with existing callers +4. **No custom solutions**: Use existing patterns or follow official WordPress/VIP standards +5. **User changes are final**: If user makes manual changes, treat as authoritative + +--- + +## Workflow + +### Phase 1: Understand + +- Read and understand the complete issue +- Clarify expected vs actual behavior +- Identify reproduction steps +- Determine scope: which plugin(s), which feature(s) + +### Phase 2: Locate + +- Use code_search to find the root cause (not just symptoms) +- Trace execution flow from entry point to failure +- **Analyze complete context** of the class or file being changed — all features, logic, and flows +- **Trace parent hierarchy**: search parent classes and files up to plugin root +- Identify ALL affected locations in the codebase +- Map dependencies: what calls this code, what does this code call +- Check plugin requirements: must code work standalone or require Pro/addons + +### Phase 3: Research + +- Find existing patterns: search models, controllers, helpers, views for similar functionality +- Study pattern usage: search ALL places using the pattern +- Search official WordPress/VIP docs: function parameters, return types, deprecated alternatives +- Search platform-specific docs: performance and security best practices +- Verify alignment: ensure approach matches existing codebase patterns +- **Never invent custom solutions if existing patterns exist** +- **Iterate**: if a better pattern is found, repeat from Phase 2 + +### Phase 4: Design + +- Propose 2-3 solutions with trade-offs clearly stated +- Select the solution with minimal scope and lowest risk +- Fix at the most specific location, closest to root cause +- Prefer adding safety checks over refactoring +- Verify the fix is not overkill: if affecting multiple areas, analyze all to ensure it is not excessive +- Document with [solution-template.md](solution-template.md) + +### Phase 5: Implement + +- Make the smallest change that completely solves the problem +- Never change method signatures, return types, or data structures +- Never refactor unrelated code in the same commit +- Add defensive checks where data comes in, not where it is used everywhere +- Follow WordPress PHP/JS coding standards and Formidable naming patterns + +### Phase 6: Verify + +- Confirm fix resolves the reported issue +- Test with Pro plugin active AND inactive +- Test with empty data and missing keys +- Confirm no PHP warnings, notices, or errors in any scenario +- Confirm backward compatibility with existing callers +- Confirm fix is testable without touching other code +- Remove any debug code and verify code style +- Run through [checklist.md](checklist.md) + +### Phase 7: Report + +- Summarize root cause, solution, and files changed using [report-template.md](report-template.md) +- Provide branch name, PR title, and PR body using [pr-template.md](pr-template.md) + +--- ## Supporting Resources -- [checklist.md](checklist.md) - Verification checklist -- [solution-template.md](solution-template.md) - Solution comparison template -- [report-template.md](report-template.md) - Completion report template +- [checklist.md](checklist.md): Verification checklist +- [solution-template.md](solution-template.md): Solution comparison template +- [report-template.md](report-template.md): Completion report template +- [pr-template.md](pr-template.md): Branch name, PR title, and PR body template ## Invocation +Cascade automatically invokes this skill when your request matches bug-fixing tasks. + +To manually invoke: + ```text @fix-bug -/fix-bug ``` From 7120f950b329eeb3b9e901ee3add53eb1486548b Mon Sep 17 00:00:00 2001 From: Sherv Date: Thu, 12 Feb 2026 15:22:26 +0300 Subject: [PATCH 60/90] feat(windsurf): add change flow diagrams and improve solution comparison formatting in fix-bug solution template Add Change Flow section to each solution option showing code execution path from plugin root through method calls to fix location (with conditional branching example in Solution B). Improve pros/cons table formatting with aligned columns and proper spacing. Expand rationale section with additional criteria: minimal scope at most specific location, prefers safety checks over refactoring, --- .windsurf/skills/fix-bug/solution-template.md | 73 +++++++++++++++---- 1 file changed, 60 insertions(+), 13 deletions(-) diff --git a/.windsurf/skills/fix-bug/solution-template.md b/.windsurf/skills/fix-bug/solution-template.md index 9b21ed7ed2..7382c7c95e 100644 --- a/.windsurf/skills/fix-bug/solution-template.md +++ b/.windsurf/skills/fix-bug/solution-template.md @@ -18,15 +18,30 @@ **Location:** `path/to/file.php` +**Change Flow:** + +```text +formidable/ +↓ +classes/controllers/FrmEntriesController.php +↓ +process_entry() +↓ +FrmEntryValidate::validate() +↓ +validate_field() ← fix here +``` + **Change:** + ```php // Proposed code change ``` -| Pros | Cons | -|------|------| -| [Benefit 1] | [Drawback 1] | -| [Benefit 2] | [Drawback 2] | +| Pros | Cons | +|--------------|--------------| +| [Benefit 1] | [Drawback 1] | +| [Benefit 2] | [Drawback 2] | **Risk Level:** Low / Medium / High @@ -36,15 +51,32 @@ **Location:** `path/to/file.php` +**Change Flow:** + +```text +formidable/ +↓ +classes/models/FrmEntry.php +↓ +FrmEntry::create() +↓ +set_meta_value() +↓ +has_post_field +YES → sync_post_meta() ← fix here +NO → save_to_meta() +``` + **Change:** + ```php // Proposed code change ``` -| Pros | Cons | -|------|------| -| [Benefit 1] | [Drawback 1] | -| [Benefit 2] | [Drawback 2] | +| Pros | Cons | +|--------------|--------------| +| [Benefit 1] | [Drawback 1] | +| [Benefit 2] | [Drawback 2] | **Risk Level:** Low / Medium / High @@ -54,15 +86,26 @@ **Location:** `path/to/file.php` +**Change Flow:** + +```text +formidable/ +↓ +classes/helpers/FrmFieldsHelper.php +↓ +FrmFieldsHelper::prepare_value() ← fix here +``` + **Change:** + ```php // Proposed code change ``` -| Pros | Cons | -|------|------| -| [Benefit 1] | [Drawback 1] | -| [Benefit 2] | [Drawback 2] | +| Pros | Cons | +|--------------|--------------| +| [Benefit 1] | [Drawback 1] | +| [Benefit 2] | [Drawback 2] | **Risk Level:** Low / Medium / High @@ -73,8 +116,12 @@ **Selected Solution:** [A/B/C] **Rationale:** -- Minimal scope + +- Minimal scope at most specific location - Closest to root cause - Follows existing patterns +- Prefers safety checks over refactoring +- No method signature, return type, or data structure changes - Maintains backward compatibility - Works with Pro active AND inactive +- Not overkill if affecting multiple areas From a85cf32de36e0274238b0b53d0f7052d8a3256d4 Mon Sep 17 00:00:00 2001 From: Sherv Date: Thu, 12 Feb 2026 16:24:36 +0300 Subject: [PATCH 61/90] feat(windsurf): restructure fix-bug PR/commit templates to separate PR title from commit message format and consolidate reporting Clarify that PR title uses plain English (not conventional commit format) while commit message follows Conventional Commits. Add critical rules section in SKILL.md Phase 7 with visual hierarchy showing PR title goes in human-readable format, PR body contains issue reference, and commit message uses conventional format without issue number. Expand pr-template.md with comparison --- .windsurf/skills/fix-bug/SKILL.md | 23 ++++- .windsurf/skills/fix-bug/pr-template.md | 94 +++++++++++++++------ .windsurf/skills/fix-bug/report-template.md | 71 ++++++---------- 3 files changed, 116 insertions(+), 72 deletions(-) diff --git a/.windsurf/skills/fix-bug/SKILL.md b/.windsurf/skills/fix-bug/SKILL.md index 387062461c..a7b4c9464d 100644 --- a/.windsurf/skills/fix-bug/SKILL.md +++ b/.windsurf/skills/fix-bug/SKILL.md @@ -83,8 +83,27 @@ Enterprise bug-fixing workflow for Formidable Forms following WordPress VIP stan ### Phase 7: Report -- Summarize root cause, solution, and files changed using [report-template.md](report-template.md) -- Provide branch name, PR title, and PR body using [pr-template.md](pr-template.md) +Output a single concise report following [report-template.md](report-template.md). + +The report contains **all** deliverables in one place: + +```markdown +Report +├── Root Cause → Fix (1 sentence each) +├── Files Changed (file path + what changed) +├── PR Info +│ ├── Branch (fix/{issue}-{slug}) +│ ├── PR Title (human-readable, NOT conventional commit format) +│ ├── PR Body (Fixes #N + description + test steps) +│ └── Commit Msg (conventional commit, NO issue number) +└── Manual Test Steps (numbered reproduction/verification) +``` + +**Critical rules**: see [pr-template.md](pr-template.md) for details: + +- **PR title** = plain English summary (e.g. `Fix dropdown hidden behind panel`) +- **PR body** = where `Fixes #ISSUE` goes + description + test steps +- **Commit message** = conventional commit format, body explains *what/why*, NO issue ref --- diff --git a/.windsurf/skills/fix-bug/pr-template.md b/.windsurf/skills/fix-bug/pr-template.md index 6cf47e1ed7..f678142d0b 100644 --- a/.windsurf/skills/fix-bug/pr-template.md +++ b/.windsurf/skills/fix-bug/pr-template.md @@ -1,53 +1,95 @@ # PR Template -## Branch Name +> Three things to output: **branch**, **PR title + body**, **commit message**. +> PR title ≠ commit message. They follow different rules. -**Format:** `fix/{issue-number}-{short-description}` +--- -Use 2-4 word kebab-case description specific to the issue. +## Branch -**Examples:** +```text +fix/{issue-number}-{short-slug} +``` -- `fix/1234-date-validation-error` -- `fix/5678-entry-export-timeout` -- `fix/910-field-label-xss` +- 2-4 word kebab-case slug +- Examples: `fix/1234-date-validation`, `fix/5678-export-timeout` --- ## PR Title -Follow Conventional Commits format: +**Plain English. NOT conventional commit format.** -```text -fix(scope): brief description -``` +| Rule | Detail | +|----------------|---------------------------------------------| +| Format | Human-readable sentence fragment | +| Capitalization | Sentence case (first word capitalized) | +| Length | ≤ 72 characters | +| Mood | Imperative ("Fix", not "Fixed" or "Fixes") | +| No period | Do not end with `.` | -**Rules:** - -- 50 characters or fewer -- Imperative mood ("fix", not "fixed") -- Lowercase after type/scope -- No period at end +**Examples:** -**Scopes:** builder, entries, fields, api, admin, frontend, db, i18n, security, deps +- `Fix dropdown hidden behind panel in form builder` +- `Prevent date field validation error for non-US formats` +- `Escape HTML entities in field labels` -**Examples:** +**Wrong** (do NOT use conventional commit format for PR titles): -- `fix(fields): resolve date validation error` -- `fix(entries): prevent export timeout on large data` -- `fix(security): escape HTML in field labels` +- ~~`fix(builder): prevent dropdown clipping at edge`~~ +- ~~`fix(fields): resolve date validation error`~~ --- ## PR Body +The PR body **must** contain the issue reference and a testing section. + ```markdown Fixes #{issue_number} -Brief description of what this PR fixes. +[1-2 sentence description of the fix.] + +## Testing + +1. [Reproduction / verification step] +2. [Expected result after fix] +``` + +--- + +## Commit Message + +Follows **Conventional Commits**: separate from PR title. + +```text +type(scope): subject (imperative mood, ≤ 50 chars) + +[Optional body: what changed and why. Wrap at 72 chars.] +``` + +| Rule | Detail | +|--------------------|----------------------------------------------| +| Subject ≤ 50 chars | Lowercase after `type(scope):` | +| Body wraps at 72 | Explains *what* and *why*, not *how* | +| No issue ref | `Fixes #N` goes in the **PR body**, not here | +| No period | Subject line does not end with `.` | + +**Scopes:** builder, entries, fields, api, admin, frontend, db, i18n, security, deps + +**Examples:** + +```text +fix(builder): prevent dropdown clipping at edge + +When a row has many fields, the field action dropdown +extends beyond the container boundary and gets clipped. +Add a left-edge overflow check to reposition it. +``` -### Testing +```text +fix(fields): handle non-US date format validation -- Clarify the steps to reproduce the issue -- Verify that the fix fully resolves the issue +Date field was rejecting valid dates in dd/mm/yyyy +format. Use locale-aware parsing instead. ``` diff --git a/.windsurf/skills/fix-bug/report-template.md b/.windsurf/skills/fix-bug/report-template.md index eb3e58113c..6006c929cb 100644 --- a/.windsurf/skills/fix-bug/report-template.md +++ b/.windsurf/skills/fix-bug/report-template.md @@ -1,66 +1,49 @@ -# Bug Fix Report +# Bug Fix Report Template -## Summary - -**Issue:** [Brief description] - -**Status:** Fixed / Partially Fixed / Needs More Info +> Fill each placeholder. Keep every section short. --- ## Root Cause -[One paragraph explaining why the bug occurred] - ---- - -## Solution +[1-2 sentences: what is broken and why] -[One paragraph explaining the fix] +## Fix ---- +[1-2 sentences: what you changed to fix it] ## Files Changed -| File | Change | -|----------------------|---------------------| -| `path/to/file1.php` | [Brief description] | -| `path/to/file2.php` | [Brief description] | - ---- - -## Testing Done - -- [ ] Bug is fixed in reported scenario -- [ ] Works when Pro is ACTIVE -- [ ] Works when Pro is INACTIVE -- [ ] No PHP warnings or notices -- [ ] Backward compatible +- `path/to/file` — [what changed] ---- - -## Remaining Concerns - -[Any edge cases, follow-up items, or things to monitor] - ---- +## PR Info -## Branch & PR +- **Branch:** `fix/{issue-number}-{short-slug}` +- **PR Title:** [Human-readable summary — plain English, no conventional commit prefix] +- **PR Body:** -**Branch:** `fix/{issue-number}-{short-description}` +```markdown +Fixes #{issue_number} -**PR Title:** `fix(scope): brief description` +[1-2 sentence description of the fix] -See [pr-template.md](pr-template.md) for full PR body format. +## Testing ---- +1. [Step to reproduce / verify] +2. [Expected result after fix] +``` -## Commit Message +- **Commit Message:** ```text -fix(scope): brief description - -Detailed explanation of what was fixed and why. +type(scope): subject line (imperative, ≤50 chars) -Fixes #ISSUE_NUMBER +[Optional body: what changed and why. Wrap at 72 chars. +Do NOT put issue references here — they go in the PR body.] ``` + +## Manual Test Steps + +1. [Step] +2. [Step] +3. [Expected result] From d9a4f80499f2ca6204b1b634455d14304300bc52 Mon Sep 17 00:00:00 2001 From: Sherv Date: Thu, 12 Feb 2026 19:23:20 +0300 Subject: [PATCH 62/90] feat(windsurf): standardize writing style rules and issue reference format across fix-bug workflow templates Add Writing Style section to principles.md prohibiting em dashes/semicolons, requiring full GitHub URLs for issue references, and clarifying no hard-wrapping in PR bodies (72-char wrap applies only to commit message bodies). Update conventional-commits.md to explicitly forbid issue references in commit messages (they belong in PR body only). Update fix-bug SKILL.md, pr-template.md, and report- --- .windsurf/rules/enterprise/conventional-commits.md | 4 +--- .windsurf/rules/enterprise/principles.md | 11 +++++++++++ .windsurf/skills/fix-bug/SKILL.md | 9 ++++++++- .windsurf/skills/fix-bug/pr-template.md | 10 +++++++--- .windsurf/skills/fix-bug/report-template.md | 8 ++++---- 5 files changed, 31 insertions(+), 11 deletions(-) diff --git a/.windsurf/rules/enterprise/conventional-commits.md b/.windsurf/rules/enterprise/conventional-commits.md index 1fefd1d469..51d6272958 100644 --- a/.windsurf/rules/enterprise/conventional-commits.md +++ b/.windsurf/rules/enterprise/conventional-commits.md @@ -61,8 +61,6 @@ fix(fields): resolve date field validation error The date field was incorrectly validating dates in non-US formats. Added locale-aware date parsing. - -Fixes #1234 ``` ```text @@ -127,4 +125,4 @@ When generating commit messages: 2. Identify the scope from the files/directories changed. 3. Write a concise description in imperative mood. 4. Add body with details if the change is complex. -5. Include issue references if mentioned in the conversation. +5. Do NOT include issue references in commit messages. Issue references (`Fixes URL`) go in the **PR body** only. diff --git a/.windsurf/rules/enterprise/principles.md b/.windsurf/rules/enterprise/principles.md index 963be175b0..a0314ad648 100644 --- a/.windsurf/rules/enterprise/principles.md +++ b/.windsurf/rules/enterprise/principles.md @@ -68,6 +68,17 @@ Before ANY code change involving platform functions: --- +## Writing Style for Generated Text + +Applies to all generated text: PR titles, PR body, commit messages, branch names. + +- **No em dashes** (`—` or `–`): use commas, periods, or rewrite the sentence +- **No semicolons** (`;`): split into separate sentences instead +- **Full GitHub URL** for issue references (e.g., `https://github.com/Strategy11/formidable-pro/issues/3030`), never shorthand `#number` (PRs may target a different repo than the issue) +- **No hard-wrapping** in PR body text: let GitHub handle line wrapping. The 72-char wrap rule applies only to **commit message bodies** + +--- + ## Change Verification Checklist Before completing any change: diff --git a/.windsurf/skills/fix-bug/SKILL.md b/.windsurf/skills/fix-bug/SKILL.md index a7b4c9464d..a92df6e757 100644 --- a/.windsurf/skills/fix-bug/SKILL.md +++ b/.windsurf/skills/fix-bug/SKILL.md @@ -102,9 +102,16 @@ Report **Critical rules**: see [pr-template.md](pr-template.md) for details: - **PR title** = plain English summary (e.g. `Fix dropdown hidden behind panel`) -- **PR body** = where `Fixes #ISSUE` goes + description + test steps +- **PR body** = where `Fixes {full_github_issue_url}` goes + description + test steps (no hard-wrapping) - **Commit message** = conventional commit format, body explains *what/why*, NO issue ref +**Writing style** (applies to PR titles, PR body, and commit messages): + +- **No em dashes** (`—` or `–`): use commas, periods, or rewrite the sentence +- **No semicolons** (`;`): split into separate sentences instead +- **No hard-wrapping** in PR body text: let GitHub handle line wrapping (72-char wrap is for commit bodies only) +- **Full GitHub URL** for issue references (e.g., `https://github.com/Strategy11/formidable-pro/issues/3030`), not `#number` + --- ## Supporting Resources diff --git a/.windsurf/skills/fix-bug/pr-template.md b/.windsurf/skills/fix-bug/pr-template.md index f678142d0b..6cdfc71766 100644 --- a/.windsurf/skills/fix-bug/pr-template.md +++ b/.windsurf/skills/fix-bug/pr-template.md @@ -1,7 +1,7 @@ # PR Template > Three things to output: **branch**, **PR title + body**, **commit message**. -> PR title ≠ commit message. They follow different rules. +> PR title is not the same as commit message. They follow different rules. --- @@ -45,10 +45,12 @@ fix/{issue-number}-{short-slug} The PR body **must** contain the issue reference and a testing section. +**Do NOT hard-wrap PR body text.** Write natural paragraphs and let GitHub handle line wrapping. The 72-char wrap rule only applies to **commit message bodies**, not PR bodies. + ```markdown -Fixes #{issue_number} +Fixes {full_github_issue_url} -[1-2 sentence description of the fix.] +[1-2 sentence description of the fix. Do NOT hard-wrap lines.] ## Testing @@ -56,6 +58,8 @@ Fixes #{issue_number} 2. [Expected result after fix] ``` +**Issue reference format:** Always use the full GitHub issue URL (e.g., `https://github.com/Strategy11/formidable-pro/issues/3030`) instead of `#number`, because the PR may target a different repo than the issue. + --- ## Commit Message diff --git a/.windsurf/skills/fix-bug/report-template.md b/.windsurf/skills/fix-bug/report-template.md index 6006c929cb..d03e0d25d5 100644 --- a/.windsurf/skills/fix-bug/report-template.md +++ b/.windsurf/skills/fix-bug/report-template.md @@ -14,18 +14,18 @@ ## Files Changed -- `path/to/file` — [what changed] +- `path/to/file` - [what changed] ## PR Info - **Branch:** `fix/{issue-number}-{short-slug}` -- **PR Title:** [Human-readable summary — plain English, no conventional commit prefix] +- **PR Title:** [Human-readable summary, plain English, no conventional commit prefix] - **PR Body:** ```markdown -Fixes #{issue_number} +Fixes {full_github_issue_url} -[1-2 sentence description of the fix] +[1-2 sentence description of the fix. Do NOT hard-wrap lines.] ## Testing From 2e2c80ac830d7d6e0d3a5aa41dabc91c884c7fcb Mon Sep 17 00:00:00 2001 From: Sherv Date: Fri, 13 Feb 2026 18:57:19 +0300 Subject: [PATCH 63/90] feat(windsurf): add mandatory coding standards and cross-plugin research rules to fix-bug workflow Add Mandatory: Coding Standards section requiring developers to read applicable rules from `.windsurf/rules/` before writing code, with file-type-to-rules mapping table and enforcement in Phase 5. Add Cross-Plugin Research section requiring research across Lite/Pro/addons when working on any plugin in the ecosystem. Update Phase 1, 2, 3 with cross-plugin checkpoints. Update Phase 5 and 6 to reference --- .windsurf/skills/fix-bug/SKILL.md | 64 +++++++++++++++++++++++---- .windsurf/skills/fix-bug/checklist.md | 10 ++++- 2 files changed, 64 insertions(+), 10 deletions(-) diff --git a/.windsurf/skills/fix-bug/SKILL.md b/.windsurf/skills/fix-bug/SKILL.md index a92df6e757..08690454d7 100644 --- a/.windsurf/skills/fix-bug/SKILL.md +++ b/.windsurf/skills/fix-bug/SKILL.md @@ -24,6 +24,49 @@ Enterprise bug-fixing workflow for Formidable Forms following WordPress VIP stan --- +## Mandatory: Coding Standards + +Before writing or modifying ANY code, you MUST read and follow the applicable coding standards rules from `.windsurf/rules/`. These rules are the authoritative source for code style, syntax, and patterns. + +**Rules location:** `.windsurf/rules/` (relative to the formidable-master plugin root) + +**Rules by file type:** + +| File type | Rules to read | +| ------------------------ | ------------------------------------------------------------------------------ | +| `*.php` | `wordpress/php.md`, `formidable/frm-php.md`, `wordpress-vip/wpvip-security.md` | +| `*.js`, `*.jsx`, `*.mjs` | `wordpress/javascript.md`, `wordpress-vip/wpvip-security.md` | +| `*.css`, `*.scss` | `formidable/frm-css.md` | +| `*.html` | `wordpress/html.md` | +| Block editor code | `wordpress/block-editor.md` | +| UI/forms/user-facing | `wordpress/accessibility.md` | +| Tests | `formidable/testing.md` | +| Commit messages | `enterprise/conventional-commits.md` | +| General principles | `enterprise/principles.md` | + +**How to apply:** + +1. Before Phase 5 (Implement), read ALL rules that match the file types you will modify +2. Follow every rule in those files when writing new code or modifying existing code +3. If a rule conflicts with existing code in the file being modified, follow the rule for new code but do not refactor unrelated existing code +4. Use `@since x.x` as the version placeholder in docblocks (never guess the version number) + +--- + +## Cross-Plugin Research + +Formidable Forms is a multi-plugin ecosystem. When working on any plugin in this ecosystem, you MUST also research the related plugins to understand the full context. + +**Rules:** + +- **Working on an addon** (e.g., formidable-woocommerce, formidable-views, formidable-dates): Research must include formidable (Lite) AND formidable-pro. The feature, setting, or code path being fixed almost always has counterparts or dependencies in Lite and Pro. +- **Working on formidable-pro**: Research must include formidable (Lite). Pro extends Lite, so every Pro feature builds on Lite infrastructure. +- **Working on formidable (Lite)**: Check if the change affects Pro or any active addon. + +**Apply this in Phases 1, 2, and 3.** When tracing execution flow, mapping dependencies, or finding existing patterns, always search across the relevant plugins, not just the plugin being modified. + +--- + ## Workflow ### Phase 1: Understand @@ -32,6 +75,7 @@ Enterprise bug-fixing workflow for Formidable Forms following WordPress VIP stan - Clarify expected vs actual behavior - Identify reproduction steps - Determine scope: which plugin(s), which feature(s) +- **Cross-plugin**: Identify if the feature or setting exists in Lite, Pro, or both. Understand the full feature context across all relevant plugins ### Phase 2: Locate @@ -42,6 +86,7 @@ Enterprise bug-fixing workflow for Formidable Forms following WordPress VIP stan - Identify ALL affected locations in the codebase - Map dependencies: what calls this code, what does this code call - Check plugin requirements: must code work standalone or require Pro/addons +- **Cross-plugin**: Search for the same feature, helper, or code path in Lite and Pro (and addons if relevant). Understand how data flows between plugins ### Phase 3: Research @@ -50,6 +95,7 @@ Enterprise bug-fixing workflow for Formidable Forms following WordPress VIP stan - Search official WordPress/VIP docs: function parameters, return types, deprecated alternatives - Search platform-specific docs: performance and security best practices - Verify alignment: ensure approach matches existing codebase patterns +- **Cross-plugin**: Search for existing patterns and helpers in Lite and Pro that already solve the problem. Reuse them instead of inventing new solutions - **Never invent custom solutions if existing patterns exist** - **Iterate**: if a better pattern is found, repeat from Phase 2 @@ -64,11 +110,12 @@ Enterprise bug-fixing workflow for Formidable Forms following WordPress VIP stan ### Phase 5: Implement +- **Read all applicable coding standards rules** from `.windsurf/rules/` before writing any code (see Mandatory: Coding Standards section above) - Make the smallest change that completely solves the problem - Never change method signatures, return types, or data structures - Never refactor unrelated code in the same commit - Add defensive checks where data comes in, not where it is used everywhere -- Follow WordPress PHP/JS coding standards and Formidable naming patterns +- Follow the coding standards rules strictly: correct syntax, naming, formatting, and patterns for each file type ### Phase 6: Verify @@ -79,6 +126,7 @@ Enterprise bug-fixing workflow for Formidable Forms following WordPress VIP stan - Confirm backward compatibility with existing callers - Confirm fix is testable without touching other code - Remove any debug code and verify code style +- **Verify code follows all applicable `.windsurf/rules/`**: correct variable declarations, naming conventions, formatting, and patterns - Run through [checklist.md](checklist.md) ### Phase 7: Report @@ -89,13 +137,13 @@ The report contains **all** deliverables in one place: ```markdown Report -├── Root Cause → Fix (1 sentence each) -├── Files Changed (file path + what changed) +├── Root Cause → Fix (1 sentence each) +├── Files Changed (file path + what changed) ├── PR Info -│ ├── Branch (fix/{issue}-{slug}) -│ ├── PR Title (human-readable, NOT conventional commit format) -│ ├── PR Body (Fixes #N + description + test steps) -│ └── Commit Msg (conventional commit, NO issue number) +│ ├── Branch (fix/{issue}-{slug}) +│ ├── PR Title (human-readable, NOT conventional commit format) +│ ├── PR Body (Fixes #N + description + test steps) +│ └── Commit Msg (conventional commit, NO issue number) └── Manual Test Steps (numbered reproduction/verification) ``` @@ -103,7 +151,7 @@ Report - **PR title** = plain English summary (e.g. `Fix dropdown hidden behind panel`) - **PR body** = where `Fixes {full_github_issue_url}` goes + description + test steps (no hard-wrapping) -- **Commit message** = conventional commit format, body explains *what/why*, NO issue ref +- **Commit message** = conventional commit format, body explains _what/why_, NO issue ref **Writing style** (applies to PR titles, PR body, and commit messages): diff --git a/.windsurf/skills/fix-bug/checklist.md b/.windsurf/skills/fix-bug/checklist.md index 96c394a6db..8b8530336f 100644 --- a/.windsurf/skills/fix-bug/checklist.md +++ b/.windsurf/skills/fix-bug/checklist.md @@ -13,6 +13,8 @@ - [ ] Found existing patterns for the fix - [ ] Searched WordPress/VIP docs for best practices - [ ] Searched platform-specific docs (performance, security) +- [ ] **Cross-plugin**: Researched related code in Lite and Pro (and addons if relevant) +- [ ] **Cross-plugin**: Understood how data flows between plugins for this feature ## Solution Selection @@ -27,12 +29,16 @@ ## Implementation +- [ ] Read all applicable `.windsurf/rules/` for the file types being modified - [ ] Makes the smallest change that solves the problem - [ ] Method signatures, return types, and data structures unchanged - [ ] No unrelated code refactored in the same commit - [ ] Defensive checks added where data comes in -- [ ] Follows WordPress PHP coding standards -- [ ] Follows Formidable naming patterns +- [ ] Follows `.windsurf/rules/wordpress/php.md` (PHP files) +- [ ] Follows `.windsurf/rules/wordpress/javascript.md` (JS files) +- [ ] Follows `.windsurf/rules/formidable/frm-php.md` (PHP files) +- [ ] Follows `.windsurf/rules/wordpress-vip/wpvip-security.md` (PHP/JS files) +- [ ] Uses `@since x.x` in docblocks (never guess version numbers) - [ ] Uses existing helper methods - [ ] All input sanitized - [ ] All output escaped From e0aeaf3098dea74156b15349ca0d301f1897439b Mon Sep 17 00:00:00 2001 From: Sherv Date: Mon, 16 Feb 2026 19:41:56 +0300 Subject: [PATCH 64/90] feat(windsurf): split enterprise principles into code-change-principles and general principles files Extract code change workflow (Analysis, Solution, Change, Verification phases) from principles.md into new code-change-principles.md. Retain only Writing Style section in principles.md and update its description to "Baseline rules that apply across the entire Formidable Forms plugin ecosystem." Add cross-plugin research requirements to Phases 1, 2, 3 in code-change-principles.md. Add Phase 4 (Select --- .../enterprise/code-change-principles.md | 100 ++++++++++++++++++ .windsurf/rules/enterprise/principles.md | 82 +------------- 2 files changed, 104 insertions(+), 78 deletions(-) create mode 100644 .windsurf/rules/enterprise/code-change-principles.md diff --git a/.windsurf/rules/enterprise/code-change-principles.md b/.windsurf/rules/enterprise/code-change-principles.md new file mode 100644 index 0000000000..84ed6ae031 --- /dev/null +++ b/.windsurf/rules/enterprise/code-change-principles.md @@ -0,0 +1,100 @@ +--- +trigger: always_on +description: Enterprise code change principles for safe, minimal-scope modifications. +--- + +# Enterprise Code Change Principles + +Critical principles for enterprise-level plugin development. + +--- + +## Core Principles + +1. **NEVER guess**: Always search and verify before making changes +2. **Minimal scope**: Fix at the most specific location, closest to the problem +3. **Backward compatibility**: Maintain 100% compatibility with existing callers +4. **No custom solutions**: Never invent new patterns. Use existing ones or search the web to review best practices, then follow the official WordPress standards or VIP guidelines. +5. **User changes are final**: If user makes manual changes, treat as authoritative + +--- + +## Analysis Phase + +Before proposing solutions: + +### Phase 1: Understand + +- Read and understand the complete issue +- Clarify expected vs actual behavior +- Identify reproduction steps +- Determine scope: which plugin(s), which feature(s) +- **Cross-plugin**: Identify if the feature or setting exists in Lite, Pro, or both. Understand the full feature context across all relevant plugins + - Working on an addon: research must include Lite AND Pro + - Working on Pro: research must include Lite + - Working on Lite: check if the change affects Pro or any active addon + +### Phase 2: Locate + +- Use code_search to find the root cause (not just symptoms) +- Trace execution flow from entry point to failure +- **Analyze complete context** of the class or file being changed — all features, logic, and flows +- **Trace parent hierarchy**: search parent classes and files up to plugin root +- Identify ALL affected locations in the codebase +- Map dependencies: what calls this code, what does this code call +- Check plugin requirements: must code work standalone or require Pro/addons +- **Cross-plugin**: Search for the same feature, helper, or code path in Lite and Pro (and addons if relevant). Understand how data flows between plugins + +### Phase 3: Research + +- **Never invent custom solutions if existing patterns exist** +- Find existing patterns: search models, controllers, helpers, views for similar functionality +- Study pattern usage: search ALL places using the pattern +- Search official WordPress/VIP docs: function parameters, return types, deprecated alternatives +- Search platform-specific docs: performance and security best practices +- Verify alignment: ensure approach matches existing codebase patterns +- **Cross-plugin**: Search for existing patterns and helpers in Lite and Pro that already solve the problem. Reuse them instead of inventing new solutions +- **Iterate**: if a better pattern is found, repeat from Phase 2 + +--- + +## Solution Phase + +### Phase 4: Select Solution + +- Propose 2-3 solutions with trade-offs clearly stated +- Select the solution with minimal scope and lowest risk +- Fix at the most specific location, closest to root cause +- Prefer adding safety checks over refactoring +- Fix must be testable without touching other code +- **Verify changes are not overkill**: If affecting several areas, review everything carefully to make sure the fix is not excessive. If it is, go back to Phase 2 + +--- + +## Change Phase + +### Phase 5: Implement + +- Never refactor unrelated code in the same commit +- If a rule conflicts with existing code in the file being modified, follow the rule for new code but do not refactor unrelated existing code +- Make the smallest change that completely solves the problem +- Never change method signatures, return types, or data structures +- Add defensive checks where data comes in, not where used everywhere +- Add PHPDoc/JSDoc for new methods/properties/functions and comments for complex logic +- Never guess the version number and use `@since x.x` as the version placeholder in docblocks + +--- + +## Change Verification Phase + +### Phase 6: Verify + +- Confirm fix resolves the reported issue +- Confirm backward compatibility with existing callers +- Confirm this change does not break any existing functionality +- Test with Pro plugin active AND inactive +- Test with empty data and missing keys +- Confirm no PHP warnings, notices, or errors in any scenario +- Test edge cases +- Remove all debug code (error_log statements, debug comments, debug files) +- Verify code passes phpcs/linting checks diff --git a/.windsurf/rules/enterprise/principles.md b/.windsurf/rules/enterprise/principles.md index a0314ad648..b889727570 100644 --- a/.windsurf/rules/enterprise/principles.md +++ b/.windsurf/rules/enterprise/principles.md @@ -1,91 +1,17 @@ --- trigger: always_on -description: Enterprise code change principles for safe, minimal-scope modifications. +description: Baseline rules that apply across the entire Formidable Forms plugin ecosystem. --- -# Enterprise Code Change Principles +# General Principles -Critical principles for enterprise-level plugin development. - ---- - -## Core Principles - -1. **NEVER guess**: Always search and verify before making changes -2. **Minimal scope**: Fix at the most specific location, closest to the problem -3. **Backward compatibility**: Maintain 100% compatibility with existing callers -4. **No custom solutions**: Never invent new patterns. Use existing ones or search the web to review best practices, then follow the official WordPress standards or VIP guidelines. -5. **User changes are final**: If user makes manual changes, treat as authoritative - ---- - -## Analysis Phase - -Before proposing solutions: - -1. **Read and understand** the complete issue -2. **Find existing patterns**: Search models, controllers, helpers, views for similar functionality -3. **Study pattern usage**: Search ALL places using the pattern -4. **Trace parent hierarchy**: Search parent files up to plugin root -5. **Use Fast Context for codebase awareness**: Use the code_search tool to understand all relevant code locations, dependencies, and related functionality before making changes -6. **Analyze complete context**: Completely analyze the class or file being changed to understand all context around features, logic, and flows for complete understanding -7. **Identify ALL affected locations** in the codebase -8. **Map dependencies**: What calls this code? What does this code call? -9. **Check plugin requirements**: Must code work standalone or require dependencies? -10. **Iterate if needed**: If a better pattern is found, repeat from step 2 - -**Never invent custom solutions if existing patterns exist.** - ---- - -## Solution Selection - -- Propose 2-3 solutions with trade-offs clearly stated -- Choose solution with minimal scope and lowest risk -- Fix at the most specific location -- Prefer adding safety checks over refactoring -- **Verify changes are not overkill**: If affecting several areas, analyze all to ensure fix is not excessive - ---- - -## Change Execution - -- Make the smallest change that completely solves the problem -- Never change method signatures, return types, or data structures -- Never refactor unrelated code in the same commit -- Add defensive checks where data comes in, not where used everywhere - ---- - -## Mandatory Research - -Before ANY code change involving platform functions: - -1. **Search codebase first**: Understand existing patterns -2. **Search official docs**: Function parameters, return types, deprecated alternatives -3. **Search platform-specific docs**: Performance and security best practices -4. **Verify alignment**: Ensure approach matches existing patterns - ---- +Baseline rules that apply across the Formidable Forms plugin ecosystem. ## Writing Style for Generated Text -Applies to all generated text: PR titles, PR body, commit messages, branch names. +Applies to all generated text: PR titles, PR body, commit messages, branch names, and etc. - **No em dashes** (`—` or `–`): use commas, periods, or rewrite the sentence - **No semicolons** (`;`): split into separate sentences instead - **Full GitHub URL** for issue references (e.g., `https://github.com/Strategy11/formidable-pro/issues/3030`), never shorthand `#number` (PRs may target a different repo than the issue) - **No hard-wrapping** in PR body text: let GitHub handle line wrapping. The 72-char wrap rule applies only to **commit message bodies** - ---- - -## Change Verification Checklist - -Before completing any change: - -- [ ] Does this change break any existing functionality? -- [ ] Does this work with all plugin configurations? -- [ ] Are there warnings/errors in any scenario? -- [ ] Is the change backward compatible? -- [ ] Have you searched docs for best practices? -- [ ] Can this be tested without touching other code? From c81b1e41494bf413de84a0ded2f702ea09efba31 Mon Sep 17 00:00:00 2001 From: Sherv Date: Mon, 16 Feb 2026 19:42:17 +0300 Subject: [PATCH 65/90] feat(windsurf): consolidate WordPress coding rules by removing duplicate and VIP-specific content Remove duplicate performance patterns (Promise.all, DOM batching) from javascript.md that are already covered in block-editor.md. Remove React-specific patterns (memoization, lazy loading, compound components) from block-editor.md as they belong in VIP standards. Remove PHP version requirements section and forbidden functions table from php.md. Remove duplicate prepared statements section from php.md and --- .../rules/wordpress-vip/wpvip-block-editor.md | 16 --- .windsurf/rules/wordpress/block-editor.md | 106 +----------------- .windsurf/rules/wordpress/javascript.md | 31 ----- .windsurf/rules/wordpress/php.md | 104 ----------------- 4 files changed, 1 insertion(+), 256 deletions(-) diff --git a/.windsurf/rules/wordpress-vip/wpvip-block-editor.md b/.windsurf/rules/wordpress-vip/wpvip-block-editor.md index 6c60355076..86e6d3264a 100644 --- a/.windsurf/rules/wordpress-vip/wpvip-block-editor.md +++ b/.windsurf/rules/wordpress-vip/wpvip-block-editor.md @@ -305,22 +305,6 @@ module.exports = { }; ``` -### Lazy Loading Components - -```jsx -import { lazy, Suspense } from '@wordpress/element'; - -const HeavyComponent = lazy( () => import( './HeavyComponent' ) ); - -function MyBlock() { - return ( - }> - - - ); -} -``` - ### Caching in Data Layer ```jsx diff --git a/.windsurf/rules/wordpress/block-editor.md b/.windsurf/rules/wordpress/block-editor.md index a4646db885..69dd84ac2e 100644 --- a/.windsurf/rules/wordpress/block-editor.md +++ b/.windsurf/rules/wordpress/block-editor.md @@ -663,74 +663,7 @@ module.exports = { --- -## 13. React Best Practices (Aligned with WP) - -### Memoization - -```jsx -import { useMemo, useCallback } from '@wordpress/element'; - -function MyComponent( { items, onSelect } ) { - // Memoize expensive computations - const processedItems = useMemo( () => { - return items.map( ( item ) => expensiveProcess( item ) ); - }, [ items ] ); - - // Memoize callbacks passed to children - const handleSelect = useCallback( - ( id ) => { - onSelect( id ); - }, - [ onSelect ], - ); - - return ; -} -``` - -### Avoid Re-renders - -```jsx -// INCORRECT - Creates new object every render -; - -// CORRECT - Stable reference -const style = useMemo( () => ( { color: 'red' } ), [] ); -; -``` - -### State Initialization - -```jsx -// CORRECT - Lazy initialization for expensive values -const [ state, setState ] = useState( () => computeExpensiveValue() ); - -// INCORRECT - Runs on every render -const [ state, setState ] = useState( computeExpensiveValue() ); -``` - -### Derived State - -```jsx -// CORRECT - Derive during render -function MyComponent( { items } ) { - const filteredItems = items.filter( ( item ) => item.active ); - // ... -} - -// INCORRECT - Unnecessary state and effect -function MyComponent( { items } ) { - const [ filteredItems, setFilteredItems ] = useState( [] ); - - useEffect( () => { - setFilteredItems( items.filter( ( item ) => item.active ) ); - }, [ items ] ); -} -``` - ---- - -## 14. Performance +## 13. Performance ### Parallel Data Fetching @@ -787,43 +720,6 @@ function MyBlock() { --- -## 15. Composition Patterns - -### Compound Components - -```jsx -// Instead of boolean props - - -// Use composition - - - - - - -``` - -### Context for Shared State - -```jsx -import { createContext, useContext } from '@wordpress/element'; - -const BlockContext = createContext(); - -function BlockProvider( { children, value } ) { - return ( - { children } - ); -} - -function useBlockContext() { - return useContext( BlockContext ); -} -``` - ---- - ## VIP Standards For WordPress VIP-specific block editor standards including dynamic blocks, block bindings, Script Modules API, and enterprise performance patterns, see `wordpress-vip/wpvip-block-editor.md`. diff --git a/.windsurf/rules/wordpress/javascript.md b/.windsurf/rules/wordpress/javascript.md index 7e02ea69b1..a9d366ae85 100644 --- a/.windsurf/rules/wordpress/javascript.md +++ b/.windsurf/rules/wordpress/javascript.md @@ -993,17 +993,6 @@ function UserProfile( { user, isLoading } ) { ## 14. Performance Patterns -### Promise.all for Parallel Fetching - -```jsx -// CORRECT - Parallel execution -const [ users, posts ] = await Promise.all( [ fetchUsers(), fetchPosts() ] ); - -// INCORRECT - Sequential when parallel is possible -const users = await fetchUsers(); -const posts = await fetchPosts(); -``` - ### Cache Function Results ```jsx @@ -1032,26 +1021,6 @@ const isSelected = ( id ) => selectedIds.has( id ); const isSelected = ( id ) => selectedItems.some( ( item ) => item.id === id ); ``` -### Batch DOM Operations - -```jsx -// CORRECT - Single reflow -const fragment = document.createDocumentFragment(); -items.forEach( ( item ) => { - const li = document.createElement( 'li' ); - li.textContent = item.name; - fragment.appendChild( li ); -} ); -container.appendChild( fragment ); - -// INCORRECT - Multiple reflows -items.forEach( ( item ) => { - const li = document.createElement( 'li' ); - li.textContent = item.name; - container.appendChild( li ); // Reflow on each iteration -} ); -``` - ### Content Visibility for Long Lists ```css diff --git a/.windsurf/rules/wordpress/php.md b/.windsurf/rules/wordpress/php.md index 1c8f11c665..e723fc73c2 100644 --- a/.windsurf/rules/wordpress/php.md +++ b/.windsurf/rules/wordpress/php.md @@ -12,89 +12,8 @@ Based on WordPress Core Official Standards. Apply when maintaining, generating, --- -## PHP Version Requirement - -**Target Version: PHP 7.0** - -All new code must be compatible with PHP 7.0. Use modern PHP 7.0 syntax but do not use features from PHP 7.1 or higher. - -### PHP 7.0 Features (Use These) - -- Scalar type declarations: `function foo(int $id, string $name)` -- Return type declarations: `function foo(): bool` -- Null coalescing operator: `$value = $array['key'] ?? 'default'` -- Spaceship operator: `$result = $a <=> $b` -- Anonymous classes: `new class { ... }` -- Group use declarations: `use Some\Namespace\{ClassA, ClassB}` -- `define()` with arrays: `define('ITEMS', ['a', 'b'])` - -### PHP 7.1+ Features (Do NOT Use) - -- Nullable types: `?string` (PHP 7.1) -- Void return type: `: void` (PHP 7.1) -- Class constant visibility: `private const` (PHP 7.1) -- Iterable pseudo-type: `iterable` (PHP 7.1) -- Multi-catch exceptions: `catch (A | B $e)` (PHP 7.1) -- Negative string offsets: `$str[-1]` (PHP 7.1) -- Object type hint: `object` (PHP 7.2) -- Trailing commas in function calls (PHP 7.3) -- Arrow functions: `fn($x) => $x * 2` (PHP 7.4) -- Typed properties: `public int $id` (PHP 7.4) -- Null safe operator: `?->` (PHP 8.0) -- Named arguments (PHP 8.0) -- Match expression (PHP 8.0) -- Constructor property promotion (PHP 8.0) - -### Example PHP 7.0 Compatible Code - -```php -/** - * Process user data. - * - * @param int $user_id User ID. - * @param string $action Action to perform. - * @param array $options Optional settings. - * @return bool Whether the operation succeeded. - */ -function process_user_data( int $user_id, string $action, array $options = array() ): bool { - $timeout = $options['timeout'] ?? 30; - $result = $options['result'] ?? 'default'; - - if ( empty( $user_id ) ) { - return false; - } - - return true; -} -``` - ---- - ## 1. Security and Database -### Prepared Statements - -Always use `$wpdb->prepare()` for queries with variables. - -```php -$wpdb->query( - $wpdb->prepare( - "UPDATE $wpdb->posts SET post_title = %s WHERE ID = %d", - $var, - $id - ) -); -``` - -### Placeholders - -| Placeholder | Type | -| ----------- | ------------------------------ | -| `%d` | Integer (whole number) | -| `%f` | Float (decimal number) | -| `%s` | String | -| `%i` | Identifier (table/field names) | - ### SQL Formatting - Capitalize SQL keywords (SELECT, FROM, WHERE, etc.) @@ -347,20 +266,6 @@ Use `??` for null checks. $value = $array['key'] ?? 'default'; ``` -### Error Control Operator - -Never use `@` to suppress errors. - -```php -// Incorrect -$value = @file_get_contents( $file ); - -// Correct -if ( file_exists( $file ) && is_readable( $file ) ) { - $value = file_get_contents( $file ); -} -``` - ### Increment/Decrement Do not use pre-increment/decrement in standalone statements. @@ -432,15 +337,6 @@ function my_init_function() { } ``` -### Forbidden Functions - -| Function | Reason | -| ------------------- | ------------------------ | -| `extract()` | Makes code unpredictable | -| `eval()` | Security vulnerability | -| `create_function()` | Deprecated and insecure | -| `compact()` | Reduces readability | - ### Regular Expressions Use PCRE functions (`preg_match`, `preg_replace`, etc.) instead of POSIX functions. From e0e55aec8e05415c3b6cbbaee1a10816708148bf Mon Sep 17 00:00:00 2001 From: Sherv Date: Mon, 16 Feb 2026 19:42:26 +0300 Subject: [PATCH 66/90] feat(windsurf): streamline fix-bug skill by removing duplicate content covered in enterprise rules Remove duplicate content from fix-bug/SKILL.md that is already covered in enterprise/code-change-principles.md and enterprise/principles.md: Core Principles section, Cross-Plugin Research section, and detailed workflow phases (Understand, Locate, Research, Implement, Verify). Update description to reference that skill builds on always-on enterprise rules. Simplify Coding Standards table by removing commit --- .windsurf/skills/fix-bug/SKILL.md | 135 +++++--------------------- .windsurf/skills/fix-bug/checklist.md | 64 +----------- 2 files changed, 28 insertions(+), 171 deletions(-) diff --git a/.windsurf/skills/fix-bug/SKILL.md b/.windsurf/skills/fix-bug/SKILL.md index 08690454d7..180a3d927d 100644 --- a/.windsurf/skills/fix-bug/SKILL.md +++ b/.windsurf/skills/fix-bug/SKILL.md @@ -1,11 +1,13 @@ --- name: fix-bug -description: Enterprise bug-fixing workflow for Formidable Forms following WordPress VIP standards. Use when fixing bugs or debugging issues. +description: Bug-fixing workflow for Formidable Forms. Use when fixing bugs, debugging unexpected behavior, investigating error logs, or resolving compatibility issues. --- -# Fix Bug Skill +# Fix Bug -Enterprise bug-fixing workflow for Formidable Forms following WordPress VIP standards. +Structured bug-fixing workflow for the Formidable Forms plugin ecosystem, following WordPress, Formidable Forms, and WordPress VIP coding standards. + +This skill builds on the always-on rules in `.windsurf/rules/enterprise/` which define core principles, code change phases, writing style, and commit message format. Those rules apply automatically to every conversation. This skill extends them with bug-fix-specific steps. ## When to Use @@ -14,126 +16,46 @@ Enterprise bug-fixing workflow for Formidable Forms following WordPress VIP stan - Investigating error logs - Resolving compatibility issues -## Core Principles - -1. **NEVER guess**: Always search and verify before making changes -2. **Minimal scope**: Fix at the most specific location, closest to the problem -3. **Backward compatibility**: Maintain 100% compatibility with existing callers -4. **No custom solutions**: Use existing patterns or follow official WordPress/VIP standards -5. **User changes are final**: If user makes manual changes, treat as authoritative - --- -## Mandatory: Coding Standards +## Coding Standards -Before writing or modifying ANY code, you MUST read and follow the applicable coding standards rules from `.windsurf/rules/`. These rules are the authoritative source for code style, syntax, and patterns. +Before writing or modifying ANY code, read and follow the applicable rules from `.windsurf/rules/`: -**Rules location:** `.windsurf/rules/` (relative to the formidable-master plugin root) - -**Rules by file type:** - -| File type | Rules to read | -| ------------------------ | ------------------------------------------------------------------------------ | -| `*.php` | `wordpress/php.md`, `formidable/frm-php.md`, `wordpress-vip/wpvip-security.md` | -| `*.js`, `*.jsx`, `*.mjs` | `wordpress/javascript.md`, `wordpress-vip/wpvip-security.md` | -| `*.css`, `*.scss` | `formidable/frm-css.md` | -| `*.html` | `wordpress/html.md` | -| Block editor code | `wordpress/block-editor.md` | -| UI/forms/user-facing | `wordpress/accessibility.md` | -| Tests | `formidable/testing.md` | -| Commit messages | `enterprise/conventional-commits.md` | -| General principles | `enterprise/principles.md` | +| File type | Rules to read | +| ------------------------ | -------------------------------------------------------------------------------------------------------------------- | +| `*.php` | `formidable/frm-php.md`, `wordpress/php.md`, `wordpress-vip/wpvip-security.md`, `wordpress-vip/wpvip-performance.md` | +| `*.js`, `*.jsx`, `*.mjs` | `wordpress/javascript.md`, `wordpress-vip/wpvip-security.md` | +| `*.css`, `*.scss` | `formidable/frm-css.md` | +| `*.html` | `wordpress/html.md` | +| Block editor code | `wordpress/block-editor.md`, `wordpress-vip/wpvip-block-editor.md` | +| UI/forms/user-facing | `wordpress/accessibility.md` | +| Tests | `formidable/testing.md` | **How to apply:** -1. Before Phase 5 (Implement), read ALL rules that match the file types you will modify -2. Follow every rule in those files when writing new code or modifying existing code -3. If a rule conflicts with existing code in the file being modified, follow the rule for new code but do not refactor unrelated existing code -4. Use `@since x.x` as the version placeholder in docblocks (never guess the version number) - ---- - -## Cross-Plugin Research - -Formidable Forms is a multi-plugin ecosystem. When working on any plugin in this ecosystem, you MUST also research the related plugins to understand the full context. - -**Rules:** - -- **Working on an addon** (e.g., formidable-woocommerce, formidable-views, formidable-dates): Research must include formidable (Lite) AND formidable-pro. The feature, setting, or code path being fixed almost always has counterparts or dependencies in Lite and Pro. -- **Working on formidable-pro**: Research must include formidable (Lite). Pro extends Lite, so every Pro feature builds on Lite infrastructure. -- **Working on formidable (Lite)**: Check if the change affects Pro or any active addon. - -**Apply this in Phases 1, 2, and 3.** When tracing execution flow, mapping dependencies, or finding existing patterns, always search across the relevant plugins, not just the plugin being modified. +1. Before implementing, read ALL rules that match the file types you will modify +2. Follow every rule when writing new code or modifying existing code --- ## Workflow -### Phase 1: Understand - -- Read and understand the complete issue -- Clarify expected vs actual behavior -- Identify reproduction steps -- Determine scope: which plugin(s), which feature(s) -- **Cross-plugin**: Identify if the feature or setting exists in Lite, Pro, or both. Understand the full feature context across all relevant plugins - -### Phase 2: Locate - -- Use code_search to find the root cause (not just symptoms) -- Trace execution flow from entry point to failure -- **Analyze complete context** of the class or file being changed — all features, logic, and flows -- **Trace parent hierarchy**: search parent classes and files up to plugin root -- Identify ALL affected locations in the codebase -- Map dependencies: what calls this code, what does this code call -- Check plugin requirements: must code work standalone or require Pro/addons -- **Cross-plugin**: Search for the same feature, helper, or code path in Lite and Pro (and addons if relevant). Understand how data flows between plugins - -### Phase 3: Research - -- Find existing patterns: search models, controllers, helpers, views for similar functionality -- Study pattern usage: search ALL places using the pattern -- Search official WordPress/VIP docs: function parameters, return types, deprecated alternatives -- Search platform-specific docs: performance and security best practices -- Verify alignment: ensure approach matches existing codebase patterns -- **Cross-plugin**: Search for existing patterns and helpers in Lite and Pro that already solve the problem. Reuse them instead of inventing new solutions -- **Never invent custom solutions if existing patterns exist** -- **Iterate**: if a better pattern is found, repeat from Phase 2 +The always-on rule `enterprise/code-change-principles.md` defines the core phases: Understand, Locate, Research, Select Solution, Implement, and Verify. The steps below are **additional** bug-fix-specific requirements. ### Phase 4: Design -- Propose 2-3 solutions with trade-offs clearly stated -- Select the solution with minimal scope and lowest risk -- Fix at the most specific location, closest to root cause -- Prefer adding safety checks over refactoring -- Verify the fix is not overkill: if affecting multiple areas, analyze all to ensure it is not excessive -- Document with [solution-template.md](solution-template.md) - -### Phase 5: Implement - -- **Read all applicable coding standards rules** from `.windsurf/rules/` before writing any code (see Mandatory: Coding Standards section above) -- Make the smallest change that completely solves the problem -- Never change method signatures, return types, or data structures -- Never refactor unrelated code in the same commit -- Add defensive checks where data comes in, not where it is used everywhere -- Follow the coding standards rules strictly: correct syntax, naming, formatting, and patterns for each file type +- Document proposed solutions using [solution-template.md](solution-template.md) ### Phase 6: Verify -- Confirm fix resolves the reported issue -- Test with Pro plugin active AND inactive -- Test with empty data and missing keys -- Confirm no PHP warnings, notices, or errors in any scenario -- Confirm backward compatibility with existing callers -- Confirm fix is testable without touching other code -- Remove any debug code and verify code style -- **Verify code follows all applicable `.windsurf/rules/`**: correct variable declarations, naming conventions, formatting, and patterns - Run through [checklist.md](checklist.md) ### Phase 7: Report Output a single concise report following [report-template.md](report-template.md). -The report contains **all** deliverables in one place: +The report contains all deliverables: ```markdown Report @@ -142,23 +64,12 @@ Report ├── PR Info │ ├── Branch (fix/{issue}-{slug}) │ ├── PR Title (human-readable, NOT conventional commit format) -│ ├── PR Body (Fixes #N + description + test steps) +│ ├── PR Body (Fixes URL + description + test steps) │ └── Commit Msg (conventional commit, NO issue number) └── Manual Test Steps (numbered reproduction/verification) ``` -**Critical rules**: see [pr-template.md](pr-template.md) for details: - -- **PR title** = plain English summary (e.g. `Fix dropdown hidden behind panel`) -- **PR body** = where `Fixes {full_github_issue_url}` goes + description + test steps (no hard-wrapping) -- **Commit message** = conventional commit format, body explains _what/why_, NO issue ref - -**Writing style** (applies to PR titles, PR body, and commit messages): - -- **No em dashes** (`—` or `–`): use commas, periods, or rewrite the sentence -- **No semicolons** (`;`): split into separate sentences instead -- **No hard-wrapping** in PR body text: let GitHub handle line wrapping (72-char wrap is for commit bodies only) -- **Full GitHub URL** for issue references (e.g., `https://github.com/Strategy11/formidable-pro/issues/3030`), not `#number` +See [pr-template.md](pr-template.md) for PR title, PR body, and commit message formatting rules. --- diff --git a/.windsurf/skills/fix-bug/checklist.md b/.windsurf/skills/fix-bug/checklist.md index 8b8530336f..24869413ee 100644 --- a/.windsurf/skills/fix-bug/checklist.md +++ b/.windsurf/skills/fix-bug/checklist.md @@ -1,70 +1,16 @@ # Bug Fix Verification Checklist -## Before Implementation +> Supplements `enterprise/code-change-principles.md`. Use as a final check before completing a bug fix. -- [ ] Understood the complete issue -- [ ] Identified root cause (not just symptoms) -- [ ] Analyzed complete context of changed class/file -- [ ] Traced parent hierarchy to plugin root -- [ ] Mapped all affected locations -- [ ] Mapped dependencies (what calls this code, what it calls) -- [ ] Studied ALL places using the affected pattern -- [ ] Checked Pro plugin requirement -- [ ] Found existing patterns for the fix -- [ ] Searched WordPress/VIP docs for best practices -- [ ] Searched platform-specific docs (performance, security) -- [ ] **Cross-plugin**: Researched related code in Lite and Pro (and addons if relevant) -- [ ] **Cross-plugin**: Understood how data flows between plugins for this feature +## Coding Standards -## Solution Selection - -- [ ] Proposed 2-3 solutions -- [ ] Documented trade-offs for each -- [ ] Selected minimal scope solution -- [ ] Verified fix is at most specific location -- [ ] Preferred safety checks over refactoring -- [ ] Confirmed not overkill if affecting multiple areas -- [ ] Verified approach matches existing codebase patterns -- [ ] Fix is testable without touching other code - -## Implementation - -- [ ] Read all applicable `.windsurf/rules/` for the file types being modified -- [ ] Makes the smallest change that solves the problem -- [ ] Method signatures, return types, and data structures unchanged -- [ ] No unrelated code refactored in the same commit -- [ ] Defensive checks added where data comes in -- [ ] Follows `.windsurf/rules/wordpress/php.md` (PHP files) -- [ ] Follows `.windsurf/rules/wordpress/javascript.md` (JS files) - [ ] Follows `.windsurf/rules/formidable/frm-php.md` (PHP files) +- [ ] Follows `.windsurf/rules/wordpress/php.md` (PHP files) +- [ ] Follows `.windsurf/rules/wordpress-vip/wpvip-performance.md` (PHP files) - [ ] Follows `.windsurf/rules/wordpress-vip/wpvip-security.md` (PHP/JS files) -- [ ] Uses `@since x.x` in docblocks (never guess version numbers) -- [ ] Uses existing helper methods -- [ ] All input sanitized -- [ ] All output escaped -- [ ] Database queries use $wpdb->prepare() -- [ ] No forbidden functions (extract, eval, etc.) - -## Testing - -- [ ] Bug is fixed in reported scenario -- [ ] Works when Pro is ACTIVE -- [ ] Works when Pro is INACTIVE -- [ ] No PHP warnings or notices -- [ ] Empty data handled correctly -- [ ] Edge cases tested -- [ ] Backward compatible - -## Cleanup - -- [ ] Removed ALL error_log() statements -- [ ] Removed ALL debug comments -- [ ] No debug files created -- [ ] Code passes phpcs check +- [ ] Follows `.windsurf/rules/wordpress/javascript.md` (JS files) ## Documentation -- [ ] PHPDoc added for new methods -- [ ] Complex logic has comments - [ ] Root cause documented - [ ] Solution documented From 2a5e0b81ecfd57751107308a1c74331af9eb5060 Mon Sep 17 00:00:00 2001 From: Sherv Date: Tue, 17 Feb 2026 15:02:06 +0300 Subject: [PATCH 67/90] feat(windsurf): add Formidable JavaScript rules to fix-bug skill and update block editor example Add formidable/frm-javascript.md to JavaScript file rules mapping in fix-bug skill table and checklist. Update wpvip-block-editor.md render_callback example to use named function instead of anonymous function, following WordPress VIP best practices for better debugging and stack traces. --- .../rules/wordpress-vip/wpvip-block-editor.md | 18 ++++++++++-------- .windsurf/skills/fix-bug/SKILL.md | 2 +- .windsurf/skills/fix-bug/checklist.md | 1 + 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/.windsurf/rules/wordpress-vip/wpvip-block-editor.md b/.windsurf/rules/wordpress-vip/wpvip-block-editor.md index 86e6d3264a..94789bb62c 100644 --- a/.windsurf/rules/wordpress-vip/wpvip-block-editor.md +++ b/.windsurf/rules/wordpress-vip/wpvip-block-editor.md @@ -47,15 +47,17 @@ $wrapper_attributes = get_block_wrapper_attributes(); ### render_callback Method ```php +function render_my_block( $attributes, $content, $block ) { + $wrapper_attributes = get_block_wrapper_attributes(); + return sprintf( + '
%2$s
', + $wrapper_attributes, + esc_html( $attributes['title'] ) + ); +} + register_block_type( __DIR__ . '/build', array( - 'render_callback' => function( $attributes, $content, $block ) { - $wrapper_attributes = get_block_wrapper_attributes(); - return sprintf( - '
%2$s
', - $wrapper_attributes, - esc_html( $attributes['title'] ) - ); - }, + 'render_callback' => 'render_my_block', ) ); ``` diff --git a/.windsurf/skills/fix-bug/SKILL.md b/.windsurf/skills/fix-bug/SKILL.md index 180a3d927d..5321f34861 100644 --- a/.windsurf/skills/fix-bug/SKILL.md +++ b/.windsurf/skills/fix-bug/SKILL.md @@ -25,7 +25,7 @@ Before writing or modifying ANY code, read and follow the applicable rules from | File type | Rules to read | | ------------------------ | -------------------------------------------------------------------------------------------------------------------- | | `*.php` | `formidable/frm-php.md`, `wordpress/php.md`, `wordpress-vip/wpvip-security.md`, `wordpress-vip/wpvip-performance.md` | -| `*.js`, `*.jsx`, `*.mjs` | `wordpress/javascript.md`, `wordpress-vip/wpvip-security.md` | +| `*.js`, `*.jsx`, `*.mjs` | `formidable/frm-javascript.md`, `wordpress/javascript.md`, `wordpress-vip/wpvip-security.md` | | `*.css`, `*.scss` | `formidable/frm-css.md` | | `*.html` | `wordpress/html.md` | | Block editor code | `wordpress/block-editor.md`, `wordpress-vip/wpvip-block-editor.md` | diff --git a/.windsurf/skills/fix-bug/checklist.md b/.windsurf/skills/fix-bug/checklist.md index 24869413ee..28fb0cbddb 100644 --- a/.windsurf/skills/fix-bug/checklist.md +++ b/.windsurf/skills/fix-bug/checklist.md @@ -8,6 +8,7 @@ - [ ] Follows `.windsurf/rules/wordpress/php.md` (PHP files) - [ ] Follows `.windsurf/rules/wordpress-vip/wpvip-performance.md` (PHP files) - [ ] Follows `.windsurf/rules/wordpress-vip/wpvip-security.md` (PHP/JS files) +- [ ] Follows `.windsurf/rules/formidable/frm-javascript.md` (JS files) - [ ] Follows `.windsurf/rules/wordpress/javascript.md` (JS files) ## Documentation From a9c4386e80572e7df0eded73db70ec7cbcb1bd53 Mon Sep 17 00:00:00 2001 From: Sherv Date: Tue, 17 Feb 2026 20:14:41 +0300 Subject: [PATCH 68/90] feat(windsurf): add Big-O algorithm efficiency requirement to code change principles Add requirement to use Big-O notation for comparing algorithms and selecting most efficient option for large inputs and iterations in Phase 3 (Change Execution) of code-change-principles.md --- .windsurf/rules/enterprise/code-change-principles.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.windsurf/rules/enterprise/code-change-principles.md b/.windsurf/rules/enterprise/code-change-principles.md index 84ed6ae031..6f96d65438 100644 --- a/.windsurf/rules/enterprise/code-change-principles.md +++ b/.windsurf/rules/enterprise/code-change-principles.md @@ -78,6 +78,7 @@ Before proposing solutions: - Never refactor unrelated code in the same commit - If a rule conflicts with existing code in the file being modified, follow the rule for new code but do not refactor unrelated existing code - Make the smallest change that completely solves the problem +- Use Big-O to compare algorithms and choose the most efficient one for large inputs and iterations - Never change method signatures, return types, or data structures - Add defensive checks where data comes in, not where used everywhere - Add PHPDoc/JSDoc for new methods/properties/functions and comments for complex logic From ffd538ecadebd75723a845bf13727210b50ab1d3 Mon Sep 17 00:00:00 2001 From: Sherv Date: Tue, 17 Feb 2026 20:14:51 +0300 Subject: [PATCH 69/90] docs(windsurf): standardize scope formatting in conventional commits guide Change scope list from dash-space to colon format for consistency with conventional commits examples. Remove redundant instruction about issue references that duplicates earlier section. --- .../rules/enterprise/conventional-commits.md | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/.windsurf/rules/enterprise/conventional-commits.md b/.windsurf/rules/enterprise/conventional-commits.md index 51d6272958..84e34a50fd 100644 --- a/.windsurf/rules/enterprise/conventional-commits.md +++ b/.windsurf/rules/enterprise/conventional-commits.md @@ -36,16 +36,16 @@ All commit messages MUST follow the Conventional Commits 1.0.0 specification. The scope provides additional context about what part of the codebase the commit affects: -- `builder` - Form builder related changes -- `entries` - Entry management changes -- `fields` - Field types or field handling -- `api` - API endpoints or integrations -- `admin` - Admin UI changes -- `frontend` - Front-end form display -- `db` - Database operations -- `i18n` - Internationalization -- `security` - Security fixes or improvements -- `deps` - Dependencies +- `builder`: Form builder related changes +- `entries`: Entry management changes +- `fields`: Field types or field handling +- `api`: API endpoints or integrations +- `admin`: Admin UI changes +- `frontend`: Front-end form display +- `db`: Database operations +- `i18n`: Internationalization +- `security`: Security fixes or improvements +- `deps`: Dependencies ## Breaking Changes @@ -125,4 +125,3 @@ When generating commit messages: 2. Identify the scope from the files/directories changed. 3. Write a concise description in imperative mood. 4. Add body with details if the change is complex. -5. Do NOT include issue references in commit messages. Issue references (`Fixes URL`) go in the **PR body** only. From 6acbddcc3b209d1f3921845080a1d0986e41cd11 Mon Sep 17 00:00:00 2001 From: Sherv Date: Tue, 17 Feb 2026 20:15:04 +0300 Subject: [PATCH 70/90] docs(windsurf): add comments to writing style scope in enterprise principles Add "comments" to list of text types covered by writing style guidelines (PR titles, PR body, commit messages, etc.) to clarify that code comments should follow the same style rules. --- .windsurf/rules/enterprise/principles.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.windsurf/rules/enterprise/principles.md b/.windsurf/rules/enterprise/principles.md index b889727570..e146d09a59 100644 --- a/.windsurf/rules/enterprise/principles.md +++ b/.windsurf/rules/enterprise/principles.md @@ -9,7 +9,7 @@ Baseline rules that apply across the Formidable Forms plugin ecosystem. ## Writing Style for Generated Text -Applies to all generated text: PR titles, PR body, commit messages, branch names, and etc. +Applies to all generated text: PR titles, PR body, commit messages, comments, and etc. - **No em dashes** (`—` or `–`): use commas, periods, or rewrite the sentence - **No semicolons** (`;`): split into separate sentences instead From 4137437bbf6de01036cfb3ca3113de025b1ff730 Mon Sep 17 00:00:00 2001 From: Sherv Date: Tue, 17 Feb 2026 20:15:13 +0300 Subject: [PATCH 71/90] style(windsurf): standardize punctuation in CSS property grouping list Change property group labels from dash to colon format for consistency with other documentation formatting (Display and Box Model - to Display and Box Model:, etc.) --- .windsurf/rules/formidable/frm-css.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.windsurf/rules/formidable/frm-css.md b/.windsurf/rules/formidable/frm-css.md index 688bfce593..503c8ef4e9 100644 --- a/.windsurf/rules/formidable/frm-css.md +++ b/.windsurf/rules/formidable/frm-css.md @@ -190,12 +190,12 @@ Maximum 3-4 levels deep. Group related properties: -1. **Display and Box Model** - display, box-sizing, width, height, padding, margin, border -2. **Positioning** - position, top, right, bottom, left, z-index -3. **Typography** - font-family, font-size, font-weight, line-height, text-align, color -4. **Visual** - background, box-shadow, opacity, border-radius -5. **Animation** - transition, transform, animation -6. **Miscellaneous** - cursor, overflow +1. **Display and Box Model**: display, box-sizing, width, height, padding, margin, border +2. **Positioning**: position, top, right, bottom, left, z-index +3. **Typography**: font-family, font-size, font-weight, line-height, text-align, color +4. **Visual**: background, box-shadow, opacity, border-radius +5. **Animation**: transition, transform, animation +6. **Miscellaneous**: cursor, overflow ### Shorthand Properties From 965305a2043a5fc36a75c9289a012ccc5628c726 Mon Sep 17 00:00:00 2001 From: Sherv Date: Tue, 17 Feb 2026 20:15:23 +0300 Subject: [PATCH 72/90] feat(windsurf): add Formidable Forms JavaScript coding standards and patterns documentation Add comprehensive JavaScript coding standards for Formidable Forms including modern ES6+ patterns, DOM manipulation with frmDom helpers, async/await patterns, error handling, performance optimization, and architectural decisions. Document critical rules: no jQuery in new code, prefer functional over class-based, use ES modules except admin.js. Include frmDom.util event helpers, template system patterns, state --- .windsurf/rules/formidable/frm-javascript.md | 948 +++++++++++++++++++ 1 file changed, 948 insertions(+) create mode 100644 .windsurf/rules/formidable/frm-javascript.md diff --git a/.windsurf/rules/formidable/frm-javascript.md b/.windsurf/rules/formidable/frm-javascript.md new file mode 100644 index 0000000000..b37dfab6a5 --- /dev/null +++ b/.windsurf/rules/formidable/frm-javascript.md @@ -0,0 +1,948 @@ +--- +trigger: glob +globs: ["**/*.js", "**/*.jsx", "**/*.mjs"] +description: Formidable Forms JavaScript patterns, modern ES6+ practices, and architectural decisions. Auto-applies to JS files. +--- + +# Formidable Forms JavaScript Patterns + +JavaScript-specific patterns, coding standards, and architectural decisions for Formidable Forms. These extend the WordPress JavaScript Coding Standards with modern best practices. + +--- + +## Critical Rules for New Code + +### No jQuery + +New code must NOT use jQuery. Use native DOM APIs and modern JavaScript. + +```javascript +// INCORRECT: jQuery +$( '.my-element' ).addClass( 'active' ); +$( '.my-element' ).on( 'click', handler ); +$.ajax( { url: '/api' } ); + +// CORRECT: Native +document.querySelector( '.my-element' ).classList.add( 'active' ); +document.querySelector( '.my-element' ).addEventListener( 'click', handler ); +fetch( '/api' ); +``` + +### Functional Over Class-Based + +Prefer functional and modular implementations over classes. + +```javascript +// INCORRECT: Class-based +class TemplateManager { + constructor() { + this.templates = []; + } + + addTemplate( template ) { + this.templates.push( template ); + } +} + +// CORRECT: Functional/Modular +const createTemplateManager = () => { + const templates = []; + + const addTemplate = ( template ) => { + templates.push( template ); + }; + + const getTemplates = () => [ ...templates ]; + + return { addTemplate, getTemplates }; +}; +``` + +### ES6+ Modern Syntax + +All new code must use ES6+ features. Target stable ECMAScript features only. + +--- + +## 1. Variables and Declarations + +### Use const and let + +Never use `var`. Use `const` by default. Use `let` only when reassignment is needed. + +```javascript +// CORRECT +const MAX_COUNT = 100; +const config = { timeout: 30 }; +let currentIndex = 0; + +// INCORRECT +var count = 0; +``` + +### One Variable Per Declaration + +Each variable gets its own declaration statement. + +```javascript +// CORRECT +const a = 1; +const b = 2; +let c = 3; + +// INCORRECT +const a = 1, + b = 2; +``` + +### Declare Close to First Use + +Declare variables close to where they are first used, not at the top of functions. + +```javascript +// CORRECT +function processItems( items ) { + if ( ! items.length ) { + return []; + } + + const processed = []; + for ( const item of items ) { + const result = transform( item ); + processed.push( result ); + } + + return processed; +} +``` + +--- + +## 2. Functions + +### Prefer Arrow Functions + +Use arrow functions for callbacks and short functions. + +```javascript +// CORRECT +const numbers = [ 1, 2, 3 ]; +const doubled = numbers.map( ( n ) => n * 2 ); + +elements.forEach( ( element ) => { + element.classList.add( 'active' ); +} ); + +// Named function for complex logic +function processComplexData( data ) { + // Multiple statements +} +``` + +### Pure Functions + +Prefer pure functions without side effects. + +```javascript +// CORRECT: Pure function +const calculateTotal = ( items ) => + items.reduce( ( sum, item ) => sum + item.price, 0 ); + +// INCORRECT: Impure function with side effects +let total = 0; +const calculateTotal = ( items ) => { + items.forEach( ( item ) => { + total += item.price; // Mutates external state + } ); +}; +``` + +### Default Parameters + +Use default parameters instead of conditional logic. + +```javascript +// CORRECT +function createUser( name, role = 'subscriber' ) { + return { name, role }; +} + +// INCORRECT +function createUser( name, role ) { + role = role || 'subscriber'; + return { name, role }; +} +``` + +### Rest Parameters + +Use rest parameters instead of `arguments` object. + +```javascript +// CORRECT +function sum( ...numbers ) { + return numbers.reduce( ( total, n ) => total + n, 0 ); +} + +// INCORRECT +function sum() { + return Array.prototype.slice.call( arguments ).reduce( ( t, n ) => t + n, 0 ); +} +``` + +### Early Returns + +Use early returns to avoid deep nesting. + +```javascript +// CORRECT +function processUser( user ) { + if ( ! user ) { + return null; + } + + if ( ! user.isActive ) { + return { error: 'User inactive' }; + } + + // Do something else + + return { data: user.profile }; +} + +// INCORRECT +function processUser( user ) { + if ( user ) { + if ( user.isActive ) { + // Do something else + + return { data: user.profile }; + } else { + return { error: 'User inactive' }; + } + } else { + return null; + } +} +``` + +--- + +## 3. Objects and Arrays + +### Object Shorthand + +Use shorthand property and method syntax. + +```javascript +// CORRECT +const name = 'John'; +const age = 30; +const user = { + name, + age, + greet() { + return `Hello, ${ this.name }`; + }, +}; + +// INCORRECT +const user = { + name: name, + age: age, + greet: function () { + return 'Hello, ' + this.name; + }, +}; +``` + +### Destructuring + +Use destructuring for objects and arrays. + +```javascript +// CORRECT +const { name, email } = user; +const [ first, second ] = items; +const { data: userData } = response; + +function processUser( { name, email, role = 'user' } ) { + // Use destructured values +} + +// INCORRECT +const name = user.name; +const email = user.email; +const first = items[ 0 ]; +``` + +### Spread Operator + +Use spread for copying and merging. + +```javascript +// CORRECT +const newArray = [ ...oldArray, newItem ]; +const newObject = { ...oldObject, newProperty: value }; +const merged = { ...defaults, ...options }; + +// INCORRECT +const newArray = oldArray.concat( [ newItem ] ); +const newObject = Object.assign( {}, oldObject, { newProperty: value } ); +``` + +### Computed Property Names + +Use computed property names when needed. + +```javascript +// CORRECT +const key = 'dynamicKey'; +const obj = { + [ key ]: value, + [ `prefix_${ key }` ]: otherValue, +}; +``` + +--- + +## 4. Modules + +### ES Modules Only + +Use ES modules (import/export) for all new code. + +```javascript +// CORRECT +import { getState, setState } from './shared'; +import defaultExport from './module'; +export const myFunction = () => {}; +export default mainFunction; + +// INCORRECT +const module = require( './module' ); +module.exports = myFunction; +``` + +**Exception:** `js/src/admin/admin.js` uses `require()` syntax. This is mandatory for that file due to its build configuration. All other files should use ES modules. + +### Named Exports Preferred + +Prefer named exports over default exports for better refactoring. + +```javascript +// CORRECT +export const processData = ( data ) => {}; +export const validateData = ( data ) => {}; + +// Use +import { processData, validateData } from './utils'; +``` + +### Single Import Per Module + +Import from a module only once per file. + +```javascript +// CORRECT +import { foo, bar, baz } from './utils'; + +// INCORRECT +import { foo } from './utils'; +import { bar } from './utils'; +``` + +### No Wildcard Imports + +Avoid wildcard imports in production code. + +```javascript +// CORRECT +import { specificFunction } from './utils'; + +// INCORRECT +import * as utils from './utils'; +``` + +--- + +## 5. DOM Manipulation + +### Query Selectors + +Use native query selectors. + +```javascript +// Single element +const element = document.querySelector( '.my-class' ); +const byId = document.getElementById( 'my-id' ); + +// Multiple elements +const elements = document.querySelectorAll( '.my-class' ); + +// Scoped query +const child = parent.querySelector( '.child' ); +``` + +### Element Creation with frmDom + +Use `frmDom` helpers for creating DOM elements. These are available globally via `window.frmDom`. + +```javascript +const { div, span, tag, a, img, svg } = frmDom; + +// Create a div with class and text +const myDiv = div( { + className: 'my-class', + text: 'Hello' +} ); + +// Create a div with children +const container = div( { + className: 'container', + children: [ + span( { text: 'Label:' } ), + span( { className: 'value', text: 'Content' } ) + ] +} ); + +// Create any element with tag() +const input = tag( 'input', { + id: 'my-input', + className: 'frm-input' +} ); +input.type = 'text'; +input.setAttribute( 'name', 'field_name' ); + +// Create anchor with href +const link = a( { + href: 'https://example.com', + text: 'Click here', + target: '_blank' +} ); + +// Create image +const image = img( { + src: '/path/to/image.png', + alt: 'Description' +} ); + +// Create SVG icon +const icon = svg( { href: '#frm_close_icon' } ); + +// Append to container +container.appendChild( myDiv ); +``` + +### Event Handling with frmDom.util + +Use `frmDom.util` helpers for common event patterns. + +```javascript +const { util } = frmDom; + +// Click with preventDefault +util.onClickPreventDefault( element, ( event ) => { + // Handler logic +} ); + +// Event delegation (like jQuery's document.on) +util.documentOn( 'click', '.my-selector', ( event ) => { + // Handler logic: 'this' refers to matched element +} ); + +// Debounced function +const debouncedSearch = util.debounce( handleSearch, 300 ); +input.addEventListener( 'input', debouncedSearch ); +``` + +### Native Event Handling + +For cases not covered by `frmDom.util`, use native APIs: + +```javascript +// Add listener +element.addEventListener( 'click', handleClick ); + +// Remove listener +element.removeEventListener( 'click', handleClick ); + +// Custom events +element.dispatchEvent( new CustomEvent( 'custom-event', { detail: data } ) ); +``` + +### Class Manipulation + +```javascript +element.classList.add( 'active' ); +element.classList.remove( 'active' ); +element.classList.toggle( 'active' ); +element.classList.contains( 'active' ); +element.classList.replace( 'old', 'new' ); +``` + +### DOM Sanitization + +Use `frmDom.cleanNode` for sanitizing DOM nodes. + +```javascript +// CORRECT: Use existing Formidable helper +frmDom.cleanNode( element ); + +// INCORRECT: Do not add new dependencies +import DOMPurify from 'dompurify'; +element.innerHTML = DOMPurify.sanitize( userData ); +``` + +--- + +## 6. Async Operations + +### AJAX with frmDom.ajax + +Use `frmDom.ajax` helpers for WordPress AJAX requests. These automatically handle nonces. + +```javascript +const { ajax } = frmDom; + +// GET request: action becomes 'frm_' + action +try { + const data = await ajax.doJsonFetch( 'get_template&template_id=123' ); + // data is the response from WordPress +} catch ( error ) { + console.error( 'Request failed:', error ); +} + +// POST request with FormData +const formData = new FormData(); +formData.append( 'template_id', templateId ); +formData.append( 'name', templateName ); + +try { + const data = await ajax.doJsonPost( 'save_template', formData ); + // data is the response from WordPress +} catch ( error ) { + console.error( 'Request failed:', error ); +} + +// POST with abort signal +const controller = new AbortController(); +try { + const data = await ajax.doJsonPost( 'search', formData, { + signal: controller.signal + } ); +} catch ( error ) { + if ( error.name === 'AbortError' ) { + // Request was cancelled + } +} +// To cancel: controller.abort(); +``` + +### Native Fetch API + +For non-WordPress AJAX or external APIs, use native Fetch: + +```javascript +// GET request +const response = await fetch( '/api/data' ); +const data = await response.json(); + +// POST request +const response = await fetch( '/api/submit', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( payload ), +} ); +``` + +### Async/Await + +Prefer async/await over Promise chains. + +```javascript +// CORRECT +async function loadData() { + try { + const data = await frmDom.ajax.doJsonFetch( 'get_data' ); + return processData( data ); + } catch ( error ) { + handleError( error ); + return null; + } +} + +// INCORRECT: Promise chain for simple operations +function loadData() { + return fetch( url ) + .then( ( response ) => response.json() ) + .then( ( data ) => processData( data ) ) + .catch( ( error ) => handleError( error ) ); +} +``` + +### Parallel Async Operations + +Use Promise.all for independent parallel operations. + +```javascript +// CORRECT: Parallel execution +const [ templates, categories ] = await Promise.all( [ + frmDom.ajax.doJsonFetch( 'get_templates' ), + frmDom.ajax.doJsonFetch( 'get_categories' ), +] ); + +// INCORRECT: Sequential when parallel is possible +const templates = await frmDom.ajax.doJsonFetch( 'get_templates' ); +const categories = await frmDom.ajax.doJsonFetch( 'get_categories' ); +``` + +--- + +## 7. Error Handling + +### Narrow Try-Catch Scope + +Wrap only operations that can actually fail. Avoid wrapping logic that does not need error handling. + +```javascript +// CORRECT: Narrow scope around async operation +async function fetchData() { + let response; + try { + response = await fetch( url ); + } catch ( error ) { + console.error( 'Fetch failed:', error.message ); + return null; + } + + // Parsing logic outside try-catch (let errors surface if data is malformed) + return response.json(); +} + +// INCORRECT: Overly broad try-catch +async function fetchData() { + try { + const response = await fetch( url ); + const data = await response.json(); + const processed = transformData( data ); // This doesn't need try-catch + updateUI( processed ); // This doesn't need try-catch + return processed; + } catch ( error ) { + // Catches everything, including non-network errors + console.error( error ); + return null; + } +} +``` + +### When to Use Try-Catch + +- **Use for:** Network requests, file operations, JSON parsing, external API calls +- **Avoid for:** Simple logic, DOM operations, array methods, synchronous code that should fail loudly + +### Meaningful Error Messages + +Provide context in error messages. + +```javascript +// CORRECT +throw new Error( `Failed to load template: ${ templateId }` ); + +// INCORRECT +throw new Error( 'Error' ); +``` + +--- + +## 8. State Management + +### Encapsulated State + +Use closures or modules for state management. + +```javascript +// CORRECT: Module pattern from codebase +import { + getState, + getSingleState, + setState, + setSingleState, +} from 'core/page-skeleton'; + +// Initialize state +setState( { + currentView: 'list', + selectedItem: null, + isLoading: false, +} ); + +// Update state +setSingleState( 'isLoading', true ); + +// Read state +const { currentView, selectedItem } = getState(); +``` + +### Immutable Updates + +Never mutate state directly. + +```javascript +// CORRECT +const newState = { + ...state, + items: [ ...state.items, newItem ], +}; + +// INCORRECT +state.items.push( newItem ); +``` + +--- + +## 9. No Inline Event Handlers + +Do **not** use inline event handlers (`onclick`, `onkeydown`, etc.) in HTML. Keep all JS logic in JS files using `addEventListener`. + +```html + +
+ Custom Button +
+ + +
+ Custom Button +
+``` + +```javascript +// Attach events in JS files +const button = document.querySelector( '.js-custom-button' ); +button.addEventListener( 'click', handleClick ); +button.addEventListener( 'keydown', ( event ) => { + if ( event.key === 'Enter' || event.key === ' ' ) { + event.preventDefault(); + handleClick(); + } +} ); +``` + +--- + +## 10. Anti-Patterns to Avoid + +### Global Variable Pollution + +```javascript +// INCORRECT +myGlobalVar = 'value'; +window.myApp = {}; + +// CORRECT: Use modules +export const myValue = 'value'; +``` + +**Note:** Some `window` variables exist in `js/src/admin/admin.js` and other legacy files as architectural decisions. New code and updates should avoid this practice and use modules instead. + +### Callback Hell + +```javascript +// INCORRECT +getData( ( data ) => { + processData( data, ( result ) => { + saveData( result, ( response ) => { + // Deeply nested + } ); + } ); +} ); + +// CORRECT +const data = await getData(); +const result = await processData( data ); +const response = await saveData( result ); +``` + +### Modifying Built-in Prototypes + +```javascript +// NEVER DO THIS +Array.prototype.customMethod = function () {}; +String.prototype.myHelper = function () {}; +``` + +### Using eval() or Function() + +```javascript +// NEVER DO THIS +eval( userInput ); +new Function( userInput )(); +``` + +### Magic Numbers/Strings + +```javascript +// INCORRECT +if ( 3 === status ) { +} +element.style.width = '768px'; + +// CORRECT +const STATUS_COMPLETE = 3; +const TABLET_BREAKPOINT = '768px'; + +if ( STATUS_COMPLETE === status ) { +} +element.style.width = TABLET_BREAKPOINT; +``` + +### Excessive DOM Queries + +```javascript +// INCORRECT +document.querySelector( '.item' ).classList.add( 'active' ); +document.querySelector( '.item' ).textContent = 'Updated'; +document.querySelector( '.item' ).dataset.id = '123'; + +// CORRECT: Cache the reference +const item = document.querySelector( '.item' ); +item.classList.add( 'active' ); +item.textContent = 'Updated'; +item.dataset.id = '123'; +``` + +### DOM Manipulation in Loops + +```javascript +// INCORRECT: Causes reflow on each iteration +items.forEach( ( item ) => { + container.appendChild( createItem( item ) ); +} ); + +// CORRECT: Use DocumentFragment +const fragment = document.createDocumentFragment(); +items.forEach( ( item ) => { + fragment.appendChild( createItem( item ) ); +} ); +container.appendChild( fragment ); +``` + +--- + +## 11. Performance Patterns + +### Cache Function Results + +```javascript +// Module-level cache +const cache = new Map(); + +function expensiveOperation( key ) { + if ( cache.has( key ) ) { + return cache.get( key ); + } + + const result = /* expensive computation */; + cache.set( key, result ); + return result; +} +``` + +### Use Set/Map for Lookups + +```javascript +// CORRECT: O(1) lookup +const selectedIds = new Set( selectedItems.map( ( item ) => item.id ) ); +const isSelected = ( id ) => selectedIds.has( id ); + +// INCORRECT: O(n) lookup +const isSelected = ( id ) => selectedItems.some( ( item ) => item.id === id ); +``` + +--- + +## 12. Optional Chaining Best Practices + +Use optional chaining (`?.`) judiciously. Overuse can hide bugs and make code harder to debug. + +### When to Use + +```javascript +// CORRECT: Accessing deeply nested optional properties +const userName = response?.data?.user?.name; + +// CORRECT: Calling methods that may not exist +callback?.(); + +// CORRECT: Accessing properties on potentially null DOM elements +const value = document.getElementById( 'my-input' )?.value; +``` + +### When NOT to Use + +```javascript +// INCORRECT: NodeList.forEach already handles empty lists +const elements = document.querySelectorAll( '.item' ); +elements?.forEach( handleItem ); // Unnecessary - forEach on empty NodeList is safe +elements.forEach( handleItem ); // CORRECT + +// INCORRECT: Excessive chaining when explicit checks are clearer +if ( user?.profile?.settings?.notifications?.email ) { + // Hard to debug which part is null +} + +// CORRECT: Explicit checks for complex logic +if ( ! user || ! user.profile ) { + return handleMissingUser(); +} +const { settings } = user.profile; +if ( settings?.notifications?.email ) { + // Clear where the optional access starts +} + +// INCORRECT: Using ?. on values you control/expect to exist +const form = document.getElementById( 'main-form' ); +form?.submit(); // If form should exist, let it error so you know something is wrong + +// CORRECT: Let expected values error if missing +const form = document.getElementById( 'main-form' ); +form.submit(); // Errors if form doesn't exist (which is a bug to fix) +``` + +### Guidelines + +- **Do not use** `?.` on `querySelectorAll` results. Empty `NodeList` is safe to iterate. +- **Do not use** `?.` to silence errors on values that should always exist. +- **Use** `?.` for external data, API responses, and optional configurations. +- **Prefer** explicit `if` checks when null handling requires different logic paths. + +--- + +## Tooling + +```bash +# Install ESLint with WordPress config +npm install --save-dev @wordpress/eslint-plugin + +# .eslintrc.json +{ + "extends": [ "plugin:@wordpress/eslint-plugin/recommended" ], + "rules": { + "no-var": "error", + "prefer-const": "error", + "no-unused-vars": "error" + } +} +``` From bacf7e1845fc7b89e582d574a7940fcda5a784e4 Mon Sep 17 00:00:00 2001 From: Sherv Date: Tue, 17 Feb 2026 20:15:56 +0300 Subject: [PATCH 73/90] refactor(windsurf): standardize comment formatting in frm-testing.md from dash to colon Change Arrange-Act-Assert pattern comments and assertion examples from dash-space to colon format for consistency with other documentation (// Arrange - to // Arrange:, // CORRECT - to // CORRECT:, etc.). Rename testing.md to frm-testing.md to follow formidable file naming convention. --- .../rules/formidable/{testing.md => frm-testing.md} | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) rename .windsurf/rules/formidable/{testing.md => frm-testing.md} (97%) diff --git a/.windsurf/rules/formidable/testing.md b/.windsurf/rules/formidable/frm-testing.md similarity index 97% rename from .windsurf/rules/formidable/testing.md rename to .windsurf/rules/formidable/frm-testing.md index aaf7b17e8d..d15f344652 100644 --- a/.windsurf/rules/formidable/testing.md +++ b/.windsurf/rules/formidable/frm-testing.md @@ -80,14 +80,14 @@ Follow **Arrange-Act-Assert** pattern: ```php public function test_entry_creation_with_valid_data() { - // Arrange - Set up test data and conditions + // Arrange: Set up test data and conditions $form = $this->factory->form->create_and_get(); $field = $this->factory->field->create_and_get( array( 'form_id' => $form->id, 'type' => 'text', ) ); - // Act - Execute the code being tested + // Act: Execute the code being tested $entry_id = FrmEntry::create( array( 'form_id' => $form->id, 'item_meta' => array( @@ -95,7 +95,7 @@ public function test_entry_creation_with_valid_data() { ), ) ); - // Assert - Verify the results + // Assert: Verify the results $this->assertIsNumeric( $entry_id, 'Entry ID should be numeric.' ); $this->assertGreaterThan( 0, $entry_id, 'Entry ID should be positive.' ); } @@ -182,13 +182,13 @@ public function test_subscriber_cannot_delete_form() { ### Use Specific Assertions ```php -// CORRECT - Specific assertions +// CORRECT: Specific assertions $this->assertSame( $expected, $actual, 'Values should be identical.' ); $this->assertIsArray( $result, 'Result should be an array.' ); $this->assertArrayHasKey( 'id', $data, 'Data should have id key.' ); $this->assertStringContainsString( 'error', $message, 'Message should contain error.' ); -// INCORRECT - Generic assertions +// INCORRECT: Generic assertions $this->assertTrue( $expected === $actual ); $this->assertTrue( is_array( $result ) ); ``` From bf122bf0c90b450b1faf8ba996e286f5429307dd Mon Sep 17 00:00:00 2001 From: Sherv Date: Tue, 17 Feb 2026 20:16:08 +0300 Subject: [PATCH 74/90] refactor(windsurf): update PHP version requirements and clarify WordPress polyfills in frm-php.md Remove return type declarations from PHP 7.0 allowed features (moved to 7.1+ forbidden list). Update minimum WordPress version from 5.5 to 6.3. Add WordPress-polyfilled functions table showing str_contains/str_starts_with/str_ends_with are safe to use, while array_is_list and newer array functions require version checks. Clarify Lite subfolder classes use Frm prefix. Update Pro hook naming rule to only --- .windsurf/rules/formidable/frm-php.md | 39 ++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/.windsurf/rules/formidable/frm-php.md b/.windsurf/rules/formidable/frm-php.md index 4e3767e914..d8f7aa399a 100644 --- a/.windsurf/rules/formidable/frm-php.md +++ b/.windsurf/rules/formidable/frm-php.md @@ -17,7 +17,6 @@ Target **PHP 7.0** as the minimum version. Write code using PHP 7.0 features and ### PHP 7.0 Features (Allowed) - **Scalar type declarations**: `string`, `int`, `float`, `bool` for parameters -- **Return type declarations**: Specify return types after `)` - **Null coalescing operator**: `$value = $input ?? 'default';` - **Spaceship operator**: `$a <=> $b` returns -1, 0, or 1 - **Constant arrays with define()**: `define( 'ITEMS', array( 'a', 'b' ) );` @@ -31,7 +30,7 @@ Target **PHP 7.0** as the minimum version. Write code using PHP 7.0 features and | Feature | Version | Why Avoid | | ---------------------------------- | ------- | ---------------------- | | Nullable types (`?string`) | 7.1 | Syntax not available | -| `void` return type | 7.1 | Type not available | +| Return type declarations | 7.1 | Type not available | | `iterable` pseudo-type | 7.1 | Type not available | | Class constant visibility | 7.1 | Syntax not available | | Multi-catch exceptions (`\|`) | 7.1 | Syntax not available | @@ -48,11 +47,33 @@ Target **PHP 7.0** as the minimum version. Write code using PHP 7.0 features and | Union types | 8.0 | Syntax not available | | Attributes | 8.0 | Syntax not available | +### WordPress-Polyfilled Functions + +WordPress polyfills some newer PHP functions. The following are safe to use with **WordPress 6.3+** (our minimum version): + +| Function | PHP Version | WP Since | Safe to Use | +| ------------------- | ----------- | -------- | ----------- | +| `str_contains()` | 8.0 | 5.9 | ✓ | +| `str_starts_with()` | 8.0 | 5.9 | ✓ | +| `str_ends_with()` | 8.0 | 5.9 | ✓ | + +The following are polyfilled in **newer WordPress versions only** (do NOT use without version checks): + +| Function | PHP Version | WP Since | Notes | +| ------------------ | ----------- | -------- | ------------- | +| `array_is_list()` | 8.1 | 6.5 | Not in WP 6.3 | +| `array_find()` | 8.4 | 6.8 | Not in WP 6.3 | +| `array_find_key()` | 8.4 | 6.8 | Not in WP 6.3 | +| `array_any()` | 8.4 | 6.8 | Not in WP 6.3 | +| `array_all()` | 8.4 | 6.8 | Not in WP 6.3 | + +**Note:** `array_key_first()` and `array_key_last()` (PHP 7.3) are NOT polyfilled by WordPress. Do not use them without a manual fallback. + --- ## WordPress Version Requirement -Target **WordPress 5.5** as the minimum version. +Target **WordPress 6.3** as the minimum version. ### Version Compatibility @@ -64,8 +85,8 @@ When using newer WordPress functions or features: ```php // Example: Using a newer function with fallback -if ( version_compare( get_bloginfo( 'version' ), '5.9', '>=' ) ) { - // Use WordPress 5.9+ feature +if ( version_compare( get_bloginfo( 'version' ), '6.3', '>=' ) ) { + // Use WordPress 6.3+ feature $result = wp_new_function(); } else { // Fallback for older versions @@ -78,6 +99,7 @@ if ( version_compare( get_bloginfo( 'version' ), '5.9', '>=' ) ) { ## Class Naming - **Lite plugin:** `FrmClassName` (e.g., `FrmAppHelper`, `FrmDb`, `FrmField`) +- **Lite subfolders:** Classes in `/stripe/`, `/square/`, and similar subfolders also use the `Frm` prefix (e.g., `FrmStrpLiteConnectHelper`, `FrmSquareLiteAppHelper`) - **Pro plugin:** `FrmProClassName` (e.g., `FrmProAppHelper`, `FrmProField`) --- @@ -85,9 +107,14 @@ if ( version_compare( get_bloginfo( 'version' ), '5.9', '>=' ) ) { ## Hook Naming - **Lite hooks:** `frm_hook_name` -- **Pro hooks:** `frm_pro_hook_name` +- **Pro hooks:** Use `frm_hook_name` when the hook exists only in Pro (no need for a `pro` prefix). Use `frm_pro_hook_name` only if a Lite equivalent exists. - **Addons hooks:** `frm_addon_hook_name` +### Naming Rules + +- Names should be **descriptive enough** that there is no ambiguity +- Names should be **short**, removing anything redundant + --- ## PHPCS Sniffs Rules From 735b99d0c20a149b750ea52244e77f42f75b34d2 Mon Sep 17 00:00:00 2001 From: Sherv Date: Tue, 17 Feb 2026 20:16:17 +0300 Subject: [PATCH 75/90] feat(windsurf): add Formidable Forms React patterns and best practices documentation Add comprehensive React coding standards for Formidable Forms including functional components with hooks, memoization patterns, state management, composition patterns, and WordPress data layer integration. Document critical rules: functional components only (no classes), import hooks from @wordpress/element, use WordPress packages for i18n/API/components/data. Include useMemo/useCallback optimization patterns, derived --- .windsurf/rules/formidable/frm-react.md | 468 ++++++++++++++++++++++++ 1 file changed, 468 insertions(+) create mode 100644 .windsurf/rules/formidable/frm-react.md diff --git a/.windsurf/rules/formidable/frm-react.md b/.windsurf/rules/formidable/frm-react.md new file mode 100644 index 0000000000..8a01664a7b --- /dev/null +++ b/.windsurf/rules/formidable/frm-react.md @@ -0,0 +1,468 @@ +--- +trigger: glob +globs: ["**/*.jsx", "**/blocks/**/*.js"] +description: Formidable Forms React patterns and best practices for WordPress blocks and admin pages. Auto-applies to JSX files and block editor code. +--- + +# Formidable Forms React Patterns + +React-specific patterns for WordPress blocks, admin pages, and other React components in Formidable Forms. + +--- + +## Functional Components Only + +Never use class components. Use functional components with hooks. + +```jsx +// CORRECT +function MyComponent( { title, onSave } ) { + const [ isLoading, setIsLoading ] = useState( false ); + + return
{ title }
; +} + +// INCORRECT +class MyComponent extends Component { + render() { + return
{ this.props.title }
; + } +} +``` + +--- + +## Hooks Rules + +Here’s a cleaner, more concise version: + +--- + +## Hook & WordPress Packages Rules + +1. Call hooks only at the top level of React functions. +2. Use hooks only inside React function components or custom hooks. +3. Import React hooks from `@wordpress/element`. +4. Use `@wordpress/i18n` for internationalization. +5. Use `@wordpress/api-fetch` for REST API requests. +6. Use `@wordpress/components` for WordPress-styled UI components. +7. Use `@wordpress/data` for state and data management. +8. Use `@wordpress/blocks` for block utilities. +9. Use `@wordpress/editor` for Block Editor functionality. + +```jsx +import { useState, useEffect, useCallback, useMemo } from '@wordpress/element'; + +function MyComponent( { items } ) { + // CORRECT: Hooks at top level + const [ selected, setSelected ] = useState( null ); + + // INCORRECT: Conditional hook + if ( items.length ) { + const [ count, setCount ] = useState( 0 ); // Error! + } +} +``` + +--- + +## Memoization + +Use `useMemo` for expensive computations and `useCallback` for stable function references. + +```jsx +import { useMemo, useCallback } from '@wordpress/element'; + +function ItemList( { items, onSelect } ) { + // Memoize expensive computation + const processedItems = useMemo( () => { + return items.map( ( item ) => expensiveProcess( item ) ); + }, [ items ] ); + + // Stable callback reference + const handleSelect = useCallback( + ( id ) => { + onSelect( id ); + }, + [ onSelect ], + ); + + return ( +
    + { processedItems.map( ( item ) => ( +
  • handleSelect( item.id ) }> + { item.name } +
  • + ) ) } +
+ ); +} +``` + +--- + +## Avoid Unnecessary Re-renders + +```jsx +// INCORRECT: Creates new object every render + + i.active ) } /> + handleClick() } /> + +// CORRECT: Stable references +const style = useMemo( () => ( { color: 'red' } ), [] ); +const activeItems = useMemo( () => items.filter( ( i ) => i.active ), [ items ] ); +const handleClick = useCallback( () => { /* ... */ }, [] ); + + + + +``` + +--- + +## Derived State + +Derive values during render instead of syncing with effects. + +```jsx +// CORRECT: Derived during render +function FilteredList( { items, filter } ) { + const filteredItems = items.filter( ( item ) => item.type === filter ); + + return ; +} + +// INCORRECT: Unnecessary state and effect +function FilteredList( { items, filter } ) { + const [ filteredItems, setFilteredItems ] = useState( [] ); + + useEffect( () => { + setFilteredItems( items.filter( ( item ) => item.type === filter ) ); + }, [ items, filter ] ); + + return ; +} +``` + +--- + +## State Initialization + +Use lazy initialization for expensive values. + +```jsx +// CORRECT: Lazy initialization (function only called once) +const [ state, setState ] = useState( () => computeExpensiveValue( props ) ); + +// INCORRECT: Runs on every render +const [ state, setState ] = useState( computeExpensiveValue( props ) ); +``` + +--- + +## Functional setState + +Use functional updates when new state depends on previous state. + +```jsx +// CORRECT: Functional update +setCount( ( prevCount ) => prevCount + 1 ); +setItems( ( prevItems ) => [ ...prevItems, newItem ] ); + +// INCORRECT: May use stale state +setCount( count + 1 ); +``` + +--- + +## useRef for Transient Values + +Use refs for values that change frequently but do not need re-renders. + +```jsx +function Draggable() { + const positionRef = useRef( { x: 0, y: 0 } ); + + const handleMouseMove = ( event ) => { + // Update ref without re-render + positionRef.current = { x: event.clientX, y: event.clientY }; + }; + + return
; +} +``` + +--- + +## Early Returns in Components + +```jsx +function UserProfile( { user, isLoading } ) { + if ( isLoading ) { + return ; + } + + if ( ! user ) { + return ; + } + + return ( +
+

{ user.name }

+ { /* Rest of component */ } +
+ ); +} +``` + +--- + +## Composition Patterns + +### Compound Components + +Instead of boolean props, use composition. + +```jsx +// INCORRECT: Boolean prop proliferation + + +// CORRECT: Compound components + + Title + + Content + + + + +``` + +### Children Over Render Props + +```jsx +// CORRECT: Children for composition + + Title + { content } + + +// AVOID: Render props when children work +
Title
} + renderContent={ () => content } +/> +``` + +### Context for Shared State + +```jsx +import { createContext, useContext, useState } from '@wordpress/element'; + +const FormContext = createContext(); + +function FormProvider( { children } ) { + const [ values, setValues ] = useState( {} ); + const [ errors, setErrors ] = useState( {} ); + + const setValue = ( name, value ) => { + setValues( ( prev ) => ( { ...prev, [ name ]: value } ) ); + }; + + return ( + + { children } + + ); +} + +function useForm() { + const context = useContext( FormContext ); + if ( ! context ) { + throw new Error( 'useForm must be used within FormProvider' ); + } + return context; +} +``` + +--- + +## WordPress Data Layer (wp.data) + +The `@wordpress/data` package provides centralized state management for WordPress applications, inspired by Redux but tailored for the WordPress ecosystem. + +### Creating Custom Data Stores + +Use `createReduxStore` and `register` to define and register custom stores: + +```jsx +import { createReduxStore, register } from '@wordpress/data'; + +const DEFAULT_STATE = { + items: [], + isLoading: false, +}; + +const actions = { + addItem( item ) { + return { type: 'ADD_ITEM', item }; + }, + setLoading( isLoading ) { + return { type: 'SET_LOADING', isLoading }; + }, +}; + +function reducer( state = DEFAULT_STATE, action ) { + switch ( action.type ) { + case 'ADD_ITEM': + return { ...state, items: [ ...state.items, action.item ] }; + case 'SET_LOADING': + return { ...state, isLoading: action.isLoading }; + default: + return state; + } +} + +const selectors = { + getItems( state ) { + return state.items; + }, + isLoading( state ) { + return state.isLoading; + }, +}; + +const store = createReduxStore( 'formidable/my-feature', { + reducer, + actions, + selectors, +} ); + +register( store ); +``` + +### Using Selectors with useSelect + +```jsx +import { useSelect } from '@wordpress/data'; + +function ItemList() { + const { items, isLoading } = useSelect( ( select ) => ( { + items: select( 'formidable/my-feature' ).getItems(), + isLoading: select( 'formidable/my-feature' ).isLoading(), + } ), [] ); + + if ( isLoading ) { + return ; + } + + return ( +
    + { items.map( ( item ) => ( +
  • { item.name }
  • + ) ) } +
+ ); +} +``` + +### Dispatching Actions with useDispatch + +```jsx +import { useDispatch } from '@wordpress/data'; + +function AddItemButton() { + const { addItem } = useDispatch( 'formidable/my-feature' ); + + const handleClick = () => { + addItem( { id: Date.now(), name: 'New Item' } ); + }; + + return ; +} +``` + +### Resolvers for Async Data + +Resolvers fetch data asynchronously when a selector is first called: + +```jsx +const resolvers = { + getItems() { + return async ( { dispatch } ) => { + dispatch( actions.setLoading( true ) ); + try { + const items = await apiFetch( { path: '/formidable/v1/items' } ); + dispatch( { type: 'SET_ITEMS', items } ); + } finally { + dispatch( actions.setLoading( false ) ); + } + }; + }, +}; + +const store = createReduxStore( 'formidable/my-feature', { + reducer, + actions, + selectors, + resolvers, +} ); +``` + +### Thunks for Complex Actions + +Use thunks for actions that need async logic or access to other actions: + +```jsx +const actions = { + fetchAndProcessItems() { + return async ( { dispatch, select } ) => { + dispatch( { type: 'FETCH_START' } ); + try { + const response = await apiFetch( { path: '/api/items' } ); + const items = response.json(); + dispatch( { type: 'FETCH_SUCCESS', items } ); + } catch ( error ) { + dispatch( { type: 'FETCH_ERROR', error: error.message } ); + } + }; + }, +}; +``` + +### Batching Dispatches + +Use `registry.batch()` to avoid unnecessary re-renders when dispatching multiple actions: + +```jsx +import { useRegistry } from '@wordpress/data'; + +function BulkActions() { + const registry = useRegistry(); + + const handleBulkUpdate = () => { + registry.batch( () => { + registry.dispatch( 'formidable/my-feature' ).setLoading( true ); + registry.dispatch( 'formidable/my-feature' ).clearItems(); + registry.dispatch( 'formidable/my-feature' ).setLoading( false ); + } ); + }; + + return ; +} +``` + +### Store Naming Convention + +Use namespaced store names following the pattern `formidable/{feature-name}`: + +- `formidable/form-builder` +- `formidable/entries` +- `formidable/templates` From e235329bddd231cbe9b4e5ebf8abd8cc8d2c18b1 Mon Sep 17 00:00:00 2001 From: Sherv Date: Tue, 17 Feb 2026 20:16:26 +0300 Subject: [PATCH 76/90] feat(windsurf): add Formidable Forms security patterns and helper functions documentation Add comprehensive security guidelines for Formidable Forms including input handling with FrmAppHelper sanitization functions (get_post_param/simple_get/get_param), nonce verification patterns, safe serialization using maybe_unserialize_array, DOM sanitization with frmDom.cleanNode, authorization checks with current_user_can/permission_nonce_error/user_has_permission, and Formidable capabilities reference table --- .windsurf/rules/formidable/frm-security.md | 170 +++++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 .windsurf/rules/formidable/frm-security.md diff --git a/.windsurf/rules/formidable/frm-security.md b/.windsurf/rules/formidable/frm-security.md new file mode 100644 index 0000000000..5f77abe623 --- /dev/null +++ b/.windsurf/rules/formidable/frm-security.md @@ -0,0 +1,170 @@ +--- +trigger: glob +globs: ['**/*.php', '**/*.js'] +description: Formidable Forms security patterns and helper functions. Auto-applies to PHP and JS files. +--- + +# Formidable Forms Security Patterns + +Formidable-specific security helpers and patterns that extend WordPress VIP security standards. + +--- + +## Input Handling + +### Use Formidable Input Helpers + +Prefer these Formidable functions over accessing `$_POST` or `$_GET` directly: + +| Function | Use Case | +| -------- | -------- | +| `FrmAppHelper::get_post_param( $param, $default, $sanitize )` | Read from `$_POST` | +| `FrmAppHelper::simple_get( $param, $sanitize, $default )` | Read from `$_GET` | +| `FrmAppHelper::get_param( $param, $default, $src, $sanitize )` | Read from `$_GET` or `$_POST` | + +**Important:** You must explicitly specify the `$sanitize` parameter or the value will not be sanitized. + +### Examples + +```php +// Read from $_POST with sanitization +$title = FrmAppHelper::get_post_param( 'title', '', 'sanitize_text_field' ); +$email = FrmAppHelper::get_post_param( 'email', '', 'sanitize_email' ); +$url = FrmAppHelper::get_post_param( 'url', '', 'sanitize_url' ); +$key = FrmAppHelper::get_post_param( 'key', '', 'sanitize_key' ); + +// Read from $_GET with sanitization +$page = FrmAppHelper::simple_get( 'page', 'sanitize_text_field' ); + +// Read from either $_GET or $_POST (checks both) +$action = FrmAppHelper::get_param( 'action', '', 'get', 'sanitize_text_field' ); +``` + +### Common Sanitization Functions + +| Function | Use Case | +| -------- | -------- | +| `sanitize_text_field()` | Single line text | +| `sanitize_textarea_field()` | Multi-line text | +| `sanitize_email()` | Email addresses | +| `sanitize_file_name()` | File names | +| `sanitize_key()` | Keys and slugs | +| `sanitize_title()` | Titles and slugs | +| `sanitize_url()` | URLs | +| `absint()` | Positive integers | +| `intval()` | Integers | +| `wp_kses()` | HTML with allowed tags | + +--- + +## Nonce Verification + +Use Formidable helpers to read nonce values: + +```php +// In form +wp_nonce_field( 'my_action', 'my_nonce' ); + +// On submission: Use Formidable helper +$nonce = FrmAppHelper::get_post_param( 'my_nonce', '', 'sanitize_text_field' ); +if ( ! $nonce || ! wp_verify_nonce( $nonce, 'my_action' ) ) { + wp_die( 'Security check failed' ); +} +``` + +--- + +## Serialization + +### serialize() vs unserialize() + +- `serialize()` is safe to use +- `unserialize()` has object injection risk: **never use on user data** + +### Safe Alternatives + +```php +// CORRECT: Use Formidable helper +$data = FrmAppHelper::maybe_unserialize_array( $serialized_data ); + +// CORRECT: Use JSON instead +$data = json_decode( $json_string, true ); + +// INCORRECT: Security risk +$data = unserialize( $user_input ); +``` + +--- + +## DOM Sanitization (JavaScript) + +Use `frmDom.cleanNode` for sanitizing DOM nodes. Do **not** introduce DOMPurify as a dependency. + +```javascript +// CORRECT: Use existing Formidable helper +frmDom.cleanNode( element ); + +// INCORRECT: Do not add new dependencies +import DOMPurify from 'dompurify'; +element.innerHTML = DOMPurify.sanitize( userData ); +``` + +### Unsafe JavaScript Patterns + +```javascript +// UNSAFE: Never use +element.innerHTML = userData; +$( element ).html( userData ); +eval( userInput ); + +// SAFE: Programmatic DOM manipulation +const text = document.createTextNode( userData ); +element.appendChild( text ); +``` + +--- + +## Authorization + +### Capability Checks + +Formidable provides helper methods for permission checks: + +```php +// Check if user has a Formidable capability +if ( ! FrmAppHelper::current_user_can( 'frm_edit_forms' ) ) { + wp_die( esc_html( FrmAppHelper::get_settings()->admin_permission ) ); +} + +// Check permission and nonce together +$error = FrmAppHelper::permission_nonce_error( 'frm_edit_forms', 'frm_ajax', 'nonce' ); +if ( $error ) { + wp_die( esc_html( $error ) ); +} + +// Check if user has any of multiple roles +if ( ! FrmAppHelper::user_has_permission( array( 'frm_edit_forms', 'frm_view_forms' ) ) ) { + wp_die( 'Unauthorized access' ); +} +``` + +### Formidable Capabilities + +| Capability | Description | +| ---------- | ----------- | +| `frm_view_forms` | View forms list | +| `frm_edit_forms` | Create and edit forms | +| `frm_delete_forms` | Delete forms | +| `frm_change_settings` | Modify plugin settings | +| `frm_view_entries` | View form entries | +| `frm_edit_entries` | Edit form entries | +| `frm_delete_entries` | Delete form entries | + +--- + +## Forbidden Functions + +| Function | Reason | Alternative | +| -------- | ------ | ----------- | +| `unserialize()` | Object injection risk | `FrmAppHelper::maybe_unserialize_array()` or `json_decode()` | +| Direct `$_POST`/`$_GET` | Unsanitized input | Use `FrmAppHelper::get_post_param()`, `simple_get()`, `get_param()` | From 66c495d4f493dc1bfab0344b087083f86a0e466e Mon Sep 17 00:00:00 2001 From: Sherv Date: Tue, 17 Feb 2026 20:16:35 +0300 Subject: [PATCH 77/90] feat(windsurf): remove inline event handlers from accessibility keyboard support example Replace inline onclick/onkeydown attributes with addEventListener pattern in accessibility.md keyboard support example. Add explicit rule against inline event handlers and demonstrate proper event attachment in separate JavaScript file following separation of concerns best practices. --- .windsurf/rules/wordpress/accessibility.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.windsurf/rules/wordpress/accessibility.md b/.windsurf/rules/wordpress/accessibility.md index 5966e58815..dae10590de 100644 --- a/.windsurf/rules/wordpress/accessibility.md +++ b/.windsurf/rules/wordpress/accessibility.md @@ -167,24 +167,27 @@ All functionality must be available via keyboard. Link - +
Custom Button
``` +Do **not** use inline event handlers (`onclick`, `onkeydown`). Attach all events in JavaScript files. + ```javascript -function handleKeydown( event ) { +const button = document.querySelector( '.js-custom-button' ); +button.addEventListener( 'click', handleClick ); +button.addEventListener( 'keydown', function( event ) { if ( event.key === 'Enter' || event.key === ' ' ) { event.preventDefault(); handleClick(); } -} +} ); ``` **Focus Trap Prevention** From 1a055bd8540e775d0ec24f568e3fee1582df3b89 Mon Sep 17 00:00:00 2001 From: Sherv Date: Tue, 17 Feb 2026 20:16:43 +0300 Subject: [PATCH 78/90] feat(windsurf): remove VIP Standards and Tooling sections from block-editor.md Remove VIP Standards reference section and Tooling commands section from block-editor.md. VIP-specific patterns should be documented in dedicated VIP files, and tooling commands are better suited for setup/development guides rather than coding standards documentation. --- .windsurf/rules/wordpress/block-editor.md | 25 ----------------------- 1 file changed, 25 deletions(-) diff --git a/.windsurf/rules/wordpress/block-editor.md b/.windsurf/rules/wordpress/block-editor.md index 69dd84ac2e..949b8148a6 100644 --- a/.windsurf/rules/wordpress/block-editor.md +++ b/.windsurf/rules/wordpress/block-editor.md @@ -717,28 +717,3 @@ function MyBlock() { ); } ``` - ---- - -## VIP Standards - -For WordPress VIP-specific block editor standards including dynamic blocks, block bindings, Script Modules API, and enterprise performance patterns, see `wordpress-vip/wpvip-block-editor.md`. - ---- - -## Tooling - -```bash -# Create new block -npx @wordpress/create-block my-block - -# Create block in existing plugin -npx @wordpress/create-block my-block --no-plugin - -# With interactive mode -npx @wordpress/create-block - -# Install packages -npm install @wordpress/scripts --save-dev -npm install @wordpress/block-editor @wordpress/blocks @wordpress/components @wordpress/i18n -``` From 34c3e2d5440bd9ad4d2e87841ea7f2775b6b6c26 Mon Sep 17 00:00:00 2001 From: Sherv Date: Tue, 17 Feb 2026 20:16:55 +0300 Subject: [PATCH 79/90] refactor(windsurf): simplify WordPress JavaScript standards to match official documentation Remove extended best practices from Google/Airbnb style guides and modern ES6+ patterns. Focus exclusively on WordPress Core Official Standards covering spacing rules, semicolons, indentation/line breaks, variable declarations with const/let, globals documentation, and naming conventions. Remove sections on jQuery avoidance, functional vs class-based patterns, arrow functions, pure functions, destructuring, spread operators, ES --- .windsurf/rules/wordpress/javascript.md | 1166 +++++------------------ 1 file changed, 261 insertions(+), 905 deletions(-) diff --git a/.windsurf/rules/wordpress/javascript.md b/.windsurf/rules/wordpress/javascript.md index a9d366ae85..275a273a6c 100644 --- a/.windsurf/rules/wordpress/javascript.md +++ b/.windsurf/rules/wordpress/javascript.md @@ -1,738 +1,435 @@ --- trigger: glob globs: ["**/*.js", "**/*.jsx", "**/*.mjs"] -description: WordPress JavaScript coding standards with modern ES6+ best practices. Auto-applies when working with JS files. +description: WordPress JavaScript coding standards based on WordPress Core Official Standards. Auto-applies when working with JS files. --- # WordPress JavaScript Coding Standards -Based on WordPress Core Official Standards and modern best practices from Google, Airbnb, and major tech companies. +Based on WordPress Core Official Standards. -**References:** - -- [WordPress JavaScript Coding Standards](https://developer.wordpress.org/coding-standards/wordpress-coding-standards/javascript/) -- [Google JavaScript Style Guide](https://google.github.io/styleguide/jsguide.html) -- [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript) +**Reference:** [WordPress JavaScript Coding Standards](https://developer.wordpress.org/coding-standards/wordpress-coding-standards/javascript/) --- -## Critical Rules for New Code +## Code Refactoring -### No jQuery +While coding standards are important, refactoring older `.js` files simply to conform to the standards is not an urgent issue. "Whitespace-only" patches for older files are strongly discouraged. -New code must NOT use jQuery. Use native DOM APIs and modern JavaScript. +--- -```javascript -// INCORRECT - jQuery -$( '.my-element' ).addClass( 'active' ); -$( '.my-element' ).on( 'click', handler ); -$.ajax( { url: '/api' } ); - -// CORRECT - Native -document.querySelector( '.my-element' ).classList.add( 'active' ); -document.querySelector( '.my-element' ).addEventListener( 'click', handler ); -fetch( '/api' ); -``` +## 1. Spacing -### Functional Over Class-Based +Use spaces liberally throughout your code. "When in doubt, space it out." -Prefer functional and modular implementations over classes. +### General Rules -```javascript -// INCORRECT - Class-based -class TemplateManager { - constructor() { - this.templates = []; - } - - addTemplate( template ) { - this.templates.push( template ); - } -} +- Indentation with tabs +- No whitespace at the end of line or on blank lines +- Lines should usually be no longer than 80 characters, and should not exceed 100 +- `if`/`else`/`for`/`while`/`try` blocks should always use braces, and always go on multiple lines +- Unary special-character operators (e.g., `++`, `--`) must not have space next to their operand +- Any `,` and `;` must not have preceding space +- Any `;` used as a statement terminator must be at the end of the line +- Any `:` after a property name in an object definition must not have preceding space +- The `?` and `:` in a ternary conditional must have space on both sides +- No filler spaces in empty constructs (e.g., `{}`, `[]`, `fn()`) +- There should be a new line at the end of each file +- Any `!` negation operator should have a following space +- All function bodies are indented by one tab, even if the entire file is wrapped in a closure +- Spaces may align code within documentation blocks or within a line, but only tabs should be used at the start of a line -// CORRECT - Functional/Modular -const createTemplateManager = () => { - const templates = []; +### Object Declarations - const addTemplate = ( template ) => { - templates.push( template ); - }; +Object declarations can be made on a single line if they are short. When an object declaration is too long to fit on one line, there must be one property per line and each line ended by a comma. - const getTemplates = () => [ ...templates ]; - - return { addTemplate, getTemplates }; +```javascript +// Preferred +const obj = { + ready: 9, + when: 4, + 'you are': 15, }; -``` - -### ES6+ Modern Syntax - -All new code must use ES6+ features. Target stable ECMAScript features only. - ---- -## 1. Variables and Declarations +// Acceptable for small objects +const obj = { ready: 9, when: 4, 'you are': 15 }; +``` -### Use const and let +### Arrays and Function Calls -Never use `var`. Use `const` by default. Use `let` only when reassignment is needed. +Always include extra spaces around elements and arguments: ```javascript -// CORRECT -const MAX_COUNT = 100; -const config = { timeout: 30 }; -let currentIndex = 0; +array = [ a, b ]; -// INCORRECT -var count = 0; -``` +foo( arg ); +foo( 'string', object ); +foo( options, object[ property ] ); +foo( node, 'property', 2 ); -### One Variable Per Declaration +prop = object[ 'default' ]; +firstArrayElement = arr[ 0 ]; +``` -Each variable gets its own declaration statement. +### Examples of Good Spacing ```javascript -// CORRECT -const a = 1; -const b = 2; -let c = 3; - -// INCORRECT -const a = 1, - b = 2; -let c = 3, - d = 4; -``` +let i; -### Declare Close to First Use +if ( condition ) { + doSomething( 'with a string' ); +} else if ( otherCondition ) { + otherThing( { + key: value, + otherKey: otherValue, + } ); +} else { + somethingElse( true ); +} -Declare variables close to where they are first used, not at the top of functions. +// WordPress prefers a space after the ! negation operator. +while ( ! condition ) { + iterating++; +} -```javascript -// CORRECT -function processItems( items ) { - if ( ! items.length ) { - return []; - } +for ( i = 0; i < 100; i++ ) { + object[ array[ i ] ] = someFn( i ); +} - const processed = []; - for ( const item of items ) { - const result = transform( item ); - processed.push( result ); - } - return processed; +try { + // Expressions +} catch ( e ) { + // Expressions } ``` --- -## 2. Naming Conventions +## 2. Semicolons + +Use them. Never rely on Automatic Semicolon Insertion (ASI). -| Type | Convention | Example | -| -------------- | ---------------------- | ------------------------------ | -| Variables | camelCase | `currentUser`, `itemCount` | -| Functions | camelCase | `getUserData`, `handleClick` | -| Constants | SCREAMING_SNAKE_CASE | `MAX_ITEMS`, `API_URL` | -| Classes | PascalCase | `UserManager`, `FormValidator` | -| Private | Leading underscore | `_privateMethod` | -| Boolean | Prefix with is/has/can | `isActive`, `hasPermission` | -| Event handlers | Prefix with on/handle | `onClick`, `handleSubmit` | +--- -### Descriptive Names +## 3. Indentation and Line Breaks -Use clear, descriptive names. Avoid abbreviations except well-known ones. +Tabs should be used for indentation. For legacy code wrapped in an IIFE (immediately invoked function expression), the contents should be indented by one tab: ```javascript -// CORRECT -const userAuthenticationToken = getToken(); -const isFormValid = validateForm( formData ); -const handleFormSubmit = ( event ) => {}; - -// INCORRECT -const uat = getToken(); -const valid = validateForm( formData ); -const submit = ( e ) => {}; -``` +// Legacy IIFE pattern (use ES6 modules for new code) +( function () { + // Expressions indented ---- + function doSomething() { + // Expressions indented + } +} )(); +``` -## 3. Functions +**Note:** New code should use ES6 modules instead of IIFEs. -### Prefer Arrow Functions +### Blocks and Curly Braces -Use arrow functions for callbacks and short functions. +`if`, `else`, `for`, `while`, and `try` blocks should always use braces, and always go on multiple lines. The opening brace should be on the same line as the function definition, the conditional, or the loop. The closing brace should be on the line directly following the last statement of the block. ```javascript -// CORRECT -const numbers = [ 1, 2, 3 ]; -const doubled = numbers.map( ( n ) => n * 2 ); - -elements.forEach( ( element ) => { - element.classList.add( 'active' ); -} ); +let a, b, c; -// Named function for complex logic -function processComplexData( data ) { - // Multiple statements +if ( myFunction() ) { + // Expressions +} else if ( ( a && b ) || c ) { + // Expressions +} else { + // Expressions } ``` -### Pure Functions +### Multi-line Statements -Prefer pure functions without side effects. +When a statement is too long to fit on one line, line breaks must occur after an operator. ```javascript -// CORRECT - Pure function -const calculateTotal = ( items ) => { - return items.reduce( ( sum, item ) => sum + item.price, 0 ); -}; +// Bad +const html = '

The sum of ' + a + ' and ' + b + ' plus ' + c + + ' is ' + ( a + b + c ) + '

'; -// INCORRECT - Impure function with side effects -let total = 0; -const calculateTotal = ( items ) => { - items.forEach( ( item ) => { - total += item.price; // Mutates external state - } ); -}; +// Good +const html = '

The sum of ' + a + ' and ' + b + ' plus ' + c + + ' is ' + ( a + b + c ) + '

'; ``` -### Default Parameters - -Use default parameters instead of conditional logic. +Lines should be broken into logical groups if it improves readability: ```javascript -// CORRECT -function createUser( name, role = 'subscriber' ) { - return { name, role }; -} +// Acceptable +const baz = ( true === conditionalStatement() ) ? 'thing 1' : 'thing 2'; -// INCORRECT -function createUser( name, role ) { - role = role || 'subscriber'; - return { name, role }; -} +// Better +const baz = firstCondition( foo ) && secondCondition( bar ) ? + qux( foo, bar ) : + foo; ``` -### Rest Parameters - -Use rest parameters instead of `arguments` object. +When a conditional is too long to fit on one line, each operand of a logical operator in the boolean expression must appear on its own line: ```javascript -// CORRECT -function sum( ...numbers ) { - return numbers.reduce( ( total, n ) => total + n, 0 ); -} - -// INCORRECT -function sum() { - return Array.prototype.slice.call( arguments ).reduce( ( t, n ) => t + n, 0 ); +if ( + firstCondition() && + secondCondition() && + thirdCondition() +) { + doStuff(); } ``` -### Early Returns +### Chained Method Calls -Use early returns to avoid deep nesting. +When a chain of method calls is too long to fit on one line, there must be one call per line, with the first call on a separate line from the object the methods are called on: ```javascript -// CORRECT -function processUser( user ) { - if ( ! user ) { - return null; - } - - if ( ! user.isActive ) { - return { error: 'User inactive' }; - } - - return { data: user.profile }; -} - -// INCORRECT -function processUser( user ) { - if ( user ) { - if ( user.isActive ) { - return { data: user.profile }; - } else { - return { error: 'User inactive' }; - } - } else { - return null; - } -} +elements + .addClass( 'foo' ) + .children() + .html( 'hello' ) + .end() + .appendTo( 'body' ); ``` --- -## 4. Objects and Arrays - -### Object Shorthand +## 4. Assignments and Globals -Use shorthand property and method syntax. +### Declaring Variables with const and let -```javascript -// CORRECT -const name = 'John'; -const age = 30; -const user = { - name, - age, - greet() { - return `Hello, ${ this.name }`; - }, -}; +For code written using ES2015 or newer, `const` and `let` should always be used in place of `var`. A declaration should use `const` unless its value will be reassigned, in which case `let` is appropriate. -// INCORRECT -const user = { - name: name, - age: age, - greet: function () { - return 'Hello, ' + this.name; - }, -}; -``` +Unlike `var`, it is not necessary to declare all variables at the top of a function. Instead, they are to be declared at the point at which they are first used. -### Destructuring +### Globals -Use destructuring for objects and arrays. +All globals used within a file should be documented at the top of that file. Multiple globals can be comma-separated. ```javascript -// CORRECT -const { name, email } = user; -const [ first, second ] = items; -const { data: userData } = response; - -function processUser( { name, email, role = 'user' } ) { - // Use destructured values -} - -// INCORRECT -const name = user.name; -const email = user.email; -const first = items[ 0 ]; +/* global passwordStrength:true */ ``` -### Spread Operator +The "true" after `passwordStrength` means that this global is being defined within this file. If you are accessing a global which is defined elsewhere, omit `:true` to designate the global as read-only. -Use spread for copying and merging. +### Common Libraries -```javascript -// CORRECT -const newArray = [ ...oldArray, newItem ]; -const newObject = { ...oldObject, newProperty: value }; -const merged = { ...defaults, ...options }; - -// INCORRECT -const newArray = oldArray.concat( [ newItem ] ); -const newObject = Object.assign( {}, oldObject, { newProperty: value } ); -``` +The global `wp` object is registered as an allowed global. -### Computed Property Names +**Legacy Note:** Backbone, jQuery, and Underscore were common in older WordPress code but should be avoided in new code. Use native JavaScript and ES6+ features instead. -Use computed property names when needed. +Files which add to, or modify, the `wp` object must safely access the global: ```javascript -// CORRECT -const key = 'dynamicKey'; -const obj = { - [ key ]: value, - [ `prefix_${ key }` ]: otherValue, -}; +// At the top of the file, set "wp" to its existing value (if present) +window.wp = window.wp || {}; ``` --- -## 5. Modules - -### ES Modules Only +## 5. Naming Conventions -Use ES modules (import/export) for all new code. +Variable and function names should be full words, using camel case with a lowercase first letter. Names should be descriptive, but not excessively so. Exceptions are allowed for iterators, such as the use of `i` to represent the index in a loop. -```javascript -// CORRECT -import { getState, setState } from './shared'; -import defaultExport from './module'; -export const myFunction = () => {}; -export default mainFunction; - -// INCORRECT -const module = require( './module' ); -module.exports = myFunction; -``` +| Type | Convention | Example | +| ----------- | -------------------- | ---------------------------- | +| Variables | camelCase | `currentUser`, `itemCount` | +| Functions | camelCase | `getUserData`, `handleClick` | +| Classes | PascalCase | `UserManager`, `Earth` | +| Constants | SCREAMING_SNAKE_CASE | `MAX_ITEMS`, `API_URL` | -### Named Exports Preferred +### Abbreviations and Acronyms -Prefer named exports over default exports for better refactoring. +Acronyms must be written with each of its composing letters capitalized. All other abbreviations must be written as camel case. ```javascript -// CORRECT -export const processData = ( data ) => {}; -export const validateData = ( data ) => {}; +// "Id" is an abbreviation of "Identifier": +const userId = 1; + +// "DOM" is an acronym of "Document Object Model": +const currentDOMDocument = window.document; -// Use -import { processData, validateData } from './utils'; +// Acronyms at the start follow camelcase rules +const domDocument = window.document; +class DOMDocument {} +class IdCollection {} ``` -### Single Import Per Module +### Class Definitions -Import from a module only once per file. +Constructors intended for use with `new` should have a capital first letter (UpperCamelCase). A `class` definition must use the UpperCamelCase convention. ```javascript -// CORRECT -import { foo, bar, baz } from './utils'; +class Earth { + static addHuman( human ) { + Earth.humans.push( human ); + } -// INCORRECT -import { foo } from './utils'; -import { bar } from './utils'; + static getHumans() { + return Earth.humans; + } +} + +Earth.humans = []; ``` -### No Wildcard Imports +All `@wordpress/element` Components, including stateless function components, should be named using Class Definition naming rules. -Avoid wildcard imports in production code. +### Constants -```javascript -// CORRECT -import { specificFunction } from './utils'; - -// INCORRECT -import * as utils from './utils'; -``` +An exception to camel case is made for constant values which are never intended to be reassigned or mutated. Such variables must use the SCREAMING_SNAKE_CASE convention. --- -## 6. DOM Manipulation - -### Query Selectors +## 6. Comments -Use native query selectors. +Comments come before the code to which they refer, and should always be preceded by a blank line. Capitalize the first letter of the comment, and include a period at the end when writing full sentences. There must be a single space between the comment token (`//`) and the comment text. ```javascript -// Single element -const element = document.querySelector( '.my-class' ); -const byId = document.getElementById( 'my-id' ); +someStatement(); -// Multiple elements -const elements = document.querySelectorAll( '.my-class' ); +// Explanation of something complex on the next line +document.querySelector( 'p' ).doSomething(); -// Scoped query -const child = parent.querySelector( '.child' ); +// This is a comment that is long enough to warrant being stretched +// over the span of multiple lines. ``` -### Element Creation - -```javascript -// Create element -const div = document.createElement( 'div' ); -div.className = 'my-class'; -div.textContent = 'Hello'; -div.dataset.id = '123'; +JSDoc comments should use the `/**` multi-line comment opening. -// Append -container.appendChild( div ); +Inline comments are allowed as an exception when used to annotate special arguments: -// Insert HTML (use with caution, escape user input) -container.insertAdjacentHTML( 'beforeend', '
' ); +```javascript +function foo( types, selector, data, fn, /* INTERNAL */ one ) { + // Do stuff +} ``` -### Event Handling +--- -```javascript -// Add listener -element.addEventListener( 'click', handleClick ); +## 7. Equality -// Remove listener -element.removeEventListener( 'click', handleClick ); +Strict equality checks (`===`) must be used in favor of abstract equality checks (`==`). -// Event delegation -container.addEventListener( 'click', ( event ) => { - if ( event.target.matches( '.button' ) ) { - handleButtonClick( event ); - } -} ); +--- -// Custom events -element.dispatchEvent( new CustomEvent( 'custom-event', { detail: data } ) ); -``` +## 8. Type Checks -### Class Manipulation +These are the preferred ways of checking the type of an object: -```javascript -element.classList.add( 'active' ); -element.classList.remove( 'active' ); -element.classList.toggle( 'active' ); -element.classList.contains( 'active' ); -element.classList.replace( 'old', 'new' ); -``` +| Type | Check | +| --------- | -------------------------------------------- | +| String | `typeof object === 'string'` | +| Number | `typeof object === 'number'` | +| Boolean | `typeof object === 'boolean'` | +| Object | `typeof object === 'object'` | +| Function | `typeof object === 'function'` | +| Array | `Array.isArray( object )` | +| Element | `object.nodeType` | +| null | `object === null` | +| undefined | `typeof variable === 'undefined'` (globals) | +| undefined | `variable === undefined` (local) | --- -## 7. Async Operations - -### Fetch API +## 9. Strings -Use Fetch API instead of XMLHttpRequest or jQuery.ajax. +Use single-quotes for string literals: ```javascript -// GET request -const response = await fetch( '/api/data' ); -const data = await response.json(); - -// POST request -const response = await fetch( '/api/submit', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify( payload ), -} ); +const myStr = 'strings should be contained in single quotes'; ``` -### Async/Await - -Prefer async/await over Promise chains. +When a string contains single quotes, they need to be escaped with a backslash (`\`): ```javascript -// CORRECT -async function loadData() { - try { - const response = await fetch( url ); - if ( ! response.ok ) { - throw new Error( 'Network error' ); - } - const data = await response.json(); - return processData( data ); - } catch ( error ) { - handleError( error ); - return null; - } -} - -// INCORRECT - Promise chain for simple operations -function loadData() { - return fetch( url ) - .then( ( response ) => response.json() ) - .then( ( data ) => processData( data ) ) - .catch( ( error ) => handleError( error ) ); -} -``` - -### Parallel Async Operations - -Use Promise.all for independent parallel operations. - -```javascript -// CORRECT - Parallel execution -const [ users, posts, comments ] = await Promise.all( [ - fetchUsers(), - fetchPosts(), - fetchComments(), -] ); - -// INCORRECT - Sequential when parallel is possible -const users = await fetchUsers(); -const posts = await fetchPosts(); -const comments = await fetchComments(); +// Escape single quotes within strings: +'Note the backslash before the \'single quotes\''; ``` --- -## 8. Error Handling +## 10. Switch Statements -### Try-Catch for Async +The usage of `switch` statements is generally discouraged, but can be useful when there are a large number of cases. -Always wrap async operations in try-catch. +When using `switch` statements: -```javascript -async function fetchData() { - try { - const response = await fetch( url ); - return await response.json(); - } catch ( error ) { - console.error( 'Fetch failed:', error.message ); - return null; - } -} -``` +- Use a `break` for each case other than `default`. When allowing statements to "fall through," note that explicitly. +- Indent `case` statements one tab within the `switch`. -### Meaningful Error Messages +```javascript +switch ( event.key ) { -Provide context in error messages. + // ENTER and SPACE both trigger x() + case 'Enter': + case ' ': + x(); + break; -```javascript -// CORRECT -throw new Error( `Failed to load template: ${ templateId }` ); + case 'Escape': + y(); + break; -// INCORRECT -throw new Error( 'Error' ); + default: + z(); +} ``` +It is not recommended to return a value from within a switch statement: use the `case` blocks to set values, then `return` those values at the end. + --- -## 9. State Management +## 11. Best Practices -### Encapsulated State +### Arrays -Use closures or modules for state management. +Creating arrays in JavaScript should be done using the shorthand `[]` constructor rather than the `new Array()` notation. ```javascript -// CORRECT - Module pattern from codebase -import { - getState, - getSingleState, - setState, - setSingleState, -} from 'core/page-skeleton'; - -// Initialize state -setState( { - currentView: 'list', - selectedItem: null, - isLoading: false, -} ); - -// Update state -setSingleState( 'isLoading', true ); - -// Read state -const { currentView, selectedItem } = getState(); +const myArray = []; +const myArray = [ 1, 'WordPress', 2, 'Blog' ]; ``` -### Immutable Updates +### Objects -Never mutate state directly. +Object literal notation, `{}`, is both the most performant, and also the easiest to read. ```javascript -// CORRECT -const newState = { - ...state, - items: [ ...state.items, newItem ], -}; - -// INCORRECT -state.items.push( newItem ); +const myObj = {}; ``` ---- - -## 10. Anti-Patterns to Avoid +Object literal notation should be used unless the object requires a specific prototype, in which case the object should be created by calling a constructor function with `new`. -### Global Variable Pollution +Object properties should be accessed via dot notation, unless the key is a variable or a string that would not be a valid identifier: ```javascript -// INCORRECT -myGlobalVar = 'value'; -window.myApp = {}; - -// CORRECT - Use modules -export const myValue = 'value'; +prop = object.propertyName; +prop = object[ variableKey ]; +prop = object[ 'key-with-hyphens' ]; ``` -### Callback Hell +### Iteration -```javascript -// INCORRECT -getData( ( data ) => { - processData( data, ( result ) => { - saveData( result, ( response ) => { - // Deeply nested - } ); - } ); -} ); - -// CORRECT -const data = await getData(); -const result = await processData( data ); -const response = await saveData( result ); -``` - -### Modifying Built-in Prototypes - -```javascript -// NEVER DO THIS -Array.prototype.customMethod = function () {}; -String.prototype.myHelper = function () {}; -``` - -### Using eval() or Function() +When iterating over a large collection using a `for` loop, it is recommended to store the loop's max value as a variable rather than re-computing the maximum every time: ```javascript -// NEVER DO THIS -eval(userInput); -new Function(userInput)(); -``` - -### Magic Numbers/Strings +// Good & Efficient +const max = getItemCount(); -```javascript -// INCORRECT -if ( status === 3 ) { +// getItemCount() gets called once +for ( let i = 0; i < max; i++ ) { + // Do stuff } -element.style.width = '768px'; - -// CORRECT -const STATUS_COMPLETE = 3; -const TABLET_BREAKPOINT = '768px'; -if ( status === STATUS_COMPLETE ) { +// Bad & Potentially Inefficient: +// getItemCount() gets called every time +for ( let i = 0; i < getItemCount(); i++ ) { + // Do stuff } -element.style.width = TABLET_BREAKPOINT; -``` - -### Excessive DOM Queries - -```javascript -// INCORRECT -document.querySelector( '.item' ).classList.add( 'active' ); -document.querySelector( '.item' ).textContent = 'Updated'; -document.querySelector( '.item' ).dataset.id = '123'; - -// CORRECT - Cache the reference -const item = document.querySelector( '.item' ); -item.classList.add( 'active' ); -item.textContent = 'Updated'; -item.dataset.id = '123'; -``` - -### DOM Manipulation in Loops - -```javascript -// INCORRECT - Causes reflow on each iteration -items.forEach( ( item ) => { - container.appendChild( createItem( item ) ); -} ); - -// CORRECT - Use DocumentFragment -const fragment = document.createDocumentFragment(); -items.forEach( ( item ) => { - fragment.appendChild( createItem( item ) ); -} ); -container.appendChild( fragment ); -``` - ---- - -## 11. Project Structure - -Follow modular folder structure as in form-templates and onboarding-wizard. - -```text -module-name/ -├── index.js # Entry point, exports public API -├── initializeModule.js # Initialization logic -├── elements/ # DOM element references -│ ├── elements.js -│ └── index.js -├── events/ # Event listeners -│ ├── clickListener.js -│ └── index.js -├── shared/ # Shared state and constants -│ ├── constants.js -│ ├── pageState.js -│ └── index.js -├── ui/ # UI manipulation functions -│ ├── showModal.js -│ └── index.js -└── utils/ # Utility functions - ├── validation.js - └── index.js ``` --- @@ -743,7 +440,7 @@ module-name/ ```javascript // Actions -wp.hooks.doAction( 'myPlugin.beforeInit', { getState, setState } ); +wp.hooks.doAction( 'myPlugin.beforeInit', { data } ); wp.hooks.addAction( 'myPlugin.afterSave', 'myPlugin', handleAfterSave ); // Filters @@ -756,7 +453,6 @@ wp.hooks.addFilter( 'myPlugin.filterValue', 'myPlugin', modifyValue ); ```javascript import domReady from '@wordpress/dom-ready'; import { __ } from '@wordpress/i18n'; -import apiFetch from '@wordpress/api-fetch'; domReady( () => { initializeModule(); @@ -765,362 +461,22 @@ domReady( () => { --- -## Formatting - -### Spacing - -- Use tabs for indentation -- Space inside parentheses for function calls with arguments -- Space after `!` negation operator -- Spaces around operators - -```javascript -if ( condition ) { - doSomething( arg1, arg2 ); -} - -const isValid = ! isEmpty && hasValue; -const total = a + b * c; -``` +## JSHint -### Line Length +JSHint is an automated code quality tool, designed to catch errors in your JavaScript code. -Keep lines under 100 characters. Break after operators. +### JSHint Settings -```javascript -const result = - veryLongVariableName + anotherLongVariableName + yetAnotherVariable; -``` +The configuration options used for JSHint are stored within a `.jshintrc` file. -### Trailing Commas +### JSHint Overrides: Ignore Blocks -Use trailing commas in multiline structures. +To exclude a specific file region from being processed by JSHint, enclose it in JSHint directive comments: ```javascript -const obj = { - property1: 'value1', - property2: 'value2', -}; - -const arr = [ 'item1', 'item2' ]; -``` - ---- - -## 13. React Best Practices - -These patterns apply when writing React components for WordPress (blocks, admin pages, etc.). - -### Functional Components Only - -Never use class components. Use functional components with hooks. - -```jsx -// CORRECT -function MyComponent( { title, onSave } ) { - const [ isLoading, setIsLoading ] = useState( false ); - - return
{ title }
; -} - -// INCORRECT -class MyComponent extends Component { - render() { - return
{ this.props.title }
; - } -} -``` - -### Hooks Rules - -1. Only call hooks at the top level -2. Only call hooks from React functions -3. Use `@wordpress/element` for React hooks in WordPress - -```jsx -import { useState, useEffect, useCallback, useMemo } from '@wordpress/element'; - -function MyComponent( { items } ) { - // CORRECT - Hooks at top level - const [ selected, setSelected ] = useState( null ); - - // INCORRECT - Conditional hook - if ( items.length ) { - const [ count, setCount ] = useState( 0 ); // Error! - } -} -``` - -### Memoization - -Use `useMemo` for expensive computations and `useCallback` for stable function references. - -```jsx -import { useMemo, useCallback } from '@wordpress/element'; - -function ItemList( { items, onSelect } ) { - // Memoize expensive computation - const processedItems = useMemo( () => { - return items.map( ( item ) => expensiveProcess( item ) ); - }, [ items ] ); - - // Stable callback reference - const handleSelect = useCallback( - ( id ) => { - onSelect( id ); - }, - [ onSelect ], - ); - - return ( -
    - { processedItems.map( ( item ) => ( -
  • handleSelect( item.id ) }> - { item.name } -
  • - ) ) } -
- ); -} -``` - -### Avoid Unnecessary Re-renders - -```jsx -// INCORRECT - Creates new object every render - - i.active ) } /> - handleClick() } /> - -// CORRECT - Stable references -const style = useMemo( () => ( { color: 'red' } ), [] ); -const activeItems = useMemo( () => items.filter( ( i ) => i.active ), [ items ] ); -const handleClick = useCallback( () => { /* ... */ }, [] ); - - - - -``` - -### Derived State - -Derive values during render instead of syncing with effects. - -```jsx -// CORRECT - Derived during render -function FilteredList( { items, filter } ) { - const filteredItems = items.filter( ( item ) => item.type === filter ); - - return ; -} - -// INCORRECT - Unnecessary state and effect -function FilteredList( { items, filter } ) { - const [ filteredItems, setFilteredItems ] = useState( [] ); - - useEffect( () => { - setFilteredItems( items.filter( ( item ) => item.type === filter ) ); - }, [ items, filter ] ); - - return ; -} -``` - -### State Initialization - -Use lazy initialization for expensive values. - -```jsx -// CORRECT - Lazy initialization (function only called once) -const [ state, setState ] = useState( () => computeExpensiveValue( props ) ); - -// INCORRECT - Runs on every render -const [ state, setState ] = useState( computeExpensiveValue( props ) ); -``` - -### Functional setState - -Use functional updates when new state depends on previous state. - -```jsx -// CORRECT - Functional update -setCount( ( prevCount ) => prevCount + 1 ); -setItems( ( prevItems ) => [ ...prevItems, newItem ] ); - -// INCORRECT - May use stale state -setCount( count + 1 ); -``` - -### useRef for Transient Values - -Use refs for values that change frequently but do not need re-renders. - -```jsx -function Draggable() { - const positionRef = useRef( { x: 0, y: 0 } ); - - const handleMouseMove = ( event ) => { - // Update ref without re-render - positionRef.current = { x: event.clientX, y: event.clientY }; - }; - - return
; -} -``` - -### Early Returns in Components - -```jsx -function UserProfile( { user, isLoading } ) { - if ( isLoading ) { - return ; - } - - if ( ! user ) { - return ; - } - - return ( -
-

{ user.name }

- { /* Rest of component */ } -
- ); -} -``` - ---- - -## 14. Performance Patterns - -### Cache Function Results - -```jsx -// Module-level cache -const cache = new Map(); - -function expensiveOperation( key ) { - if ( cache.has( key ) ) { - return cache.get( key ); - } - - const result = /* expensive computation */; - cache.set( key, result ); - return result; -} -``` - -### Use Set/Map for Lookups - -```jsx -// CORRECT - O(1) lookup -const selectedIds = new Set( selectedItems.map( ( item ) => item.id ) ); -const isSelected = ( id ) => selectedIds.has( id ); - -// INCORRECT - O(n) lookup -const isSelected = ( id ) => selectedItems.some( ( item ) => item.id === id ); -``` - -### Content Visibility for Long Lists - -```css -.long-list-item { - content-visibility: auto; - contain-intrinsic-size: 0 50px; -} -``` - ---- - -## 15. Composition Patterns - -### Compound Components - -Instead of boolean props, use composition. - -```jsx -// INCORRECT - Boolean prop proliferation - - -// CORRECT - Compound components - - Title - - Content - - - - -``` - -### Children Over Render Props - -```jsx -// CORRECT - Children for composition - - Title - { content } - - -// AVOID - Render props when children work -
Title
} - renderContent={ () => content } -/> -``` - -### Context for Shared State - -```jsx -import { createContext, useContext, useState } from '@wordpress/element'; - -const FormContext = createContext(); - -function FormProvider( { children } ) { - const [ values, setValues ] = useState( {} ); - const [ errors, setErrors ] = useState( {} ); - - const setValue = ( name, value ) => { - setValues( ( prev ) => ( { ...prev, [ name ]: value } ) ); - }; - - return ( - - { children } - - ); -} - -function useForm() { - const context = useContext( FormContext ); - if ( ! context ) { - throw new Error( 'useForm must be used within FormProvider' ); - } - return context; -} -``` - ---- - -## Tooling - -```bash -# Install ESLint with WordPress config -npm install --save-dev @wordpress/eslint-plugin - -# .eslintrc.json -{ - "extends": [ "plugin:@wordpress/eslint-plugin/recommended" ], - "rules": { - "no-var": "error", - "prefer-const": "error", - "no-unused-vars": "error" - } +/* jshint ignore:start */ +if ( typeof thirdPartyLibrary === 'undefined' ) { + // Third-party code } +/* jshint ignore:end */ ``` From 303637ba15dad204df320be2d7191802f15df02b Mon Sep 17 00:00:00 2001 From: Sherv Date: Tue, 17 Feb 2026 20:17:04 +0300 Subject: [PATCH 80/90] refactor(windsurf): standardize comment formatting in wpvip-block-editor.md from dash to colon Change render.php and useSelect example comments from dash-space to colon format for consistency with other documentation (// render.php - to // render.php:, // CORRECT - to // CORRECT:). Remove Tooling section with npx/npm commands as tooling commands are better suited for setup/development guides rather than coding standards documentation. --- .../rules/wordpress-vip/wpvip-block-editor.md | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/.windsurf/rules/wordpress-vip/wpvip-block-editor.md b/.windsurf/rules/wordpress-vip/wpvip-block-editor.md index 94789bb62c..21b74a0859 100644 --- a/.windsurf/rules/wordpress-vip/wpvip-block-editor.md +++ b/.windsurf/rules/wordpress-vip/wpvip-block-editor.md @@ -36,7 +36,7 @@ Dynamic blocks generate content server-side using PHP, ideal for frequently upda ```php
> @@ -313,7 +313,7 @@ module.exports = { import { useSelect } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; -// CORRECT - Minimal dependencies, automatic caching +// CORRECT: Minimal dependencies, automatic caching const postTitle = useSelect( ( select ) => { return select( coreStore ).getEntityRecord( 'postType', 'post', postId )?.title; @@ -321,18 +321,3 @@ const postTitle = useSelect( [ postId ] ); ``` - ---- - -## Tooling - -```bash -# Create new block -npx @wordpress/create-block my-block - -# Build with modules support -npm run build -- --experimental-modules - -# Install VIP-compatible packages -npm install @wordpress/scripts --save-dev -``` From 0f97365f35a2d6e9a5d67c755fa9971f95249dc6 Mon Sep 17 00:00:00 2001 From: Sherv Date: Tue, 17 Feb 2026 20:17:13 +0300 Subject: [PATCH 81/90] refactor(windsurf): standardize comment formatting in wpvip-performance.md from dash to colon Change code example comments from dash-space to colon format for consistency with other documentation (// CORRECT - to // CORRECT:, // INCORRECT - to // INCORRECT:, // CAUTION - to // CAUTION:, // BETTER - to // BETTER:, # Crontab entry - to # Crontab entry:). Fix array formatting in batch processing example to use multi-line format. Remove Tooling section with Query Monitor, New Relic, and VIP CLI commands as tool --- .../rules/wordpress-vip/wpvip-performance.md | 47 +++++++------------ 1 file changed, 17 insertions(+), 30 deletions(-) diff --git a/.windsurf/rules/wordpress-vip/wpvip-performance.md b/.windsurf/rules/wordpress-vip/wpvip-performance.md index 416ebecf40..352431e71d 100644 --- a/.windsurf/rules/wordpress-vip/wpvip-performance.md +++ b/.windsurf/rules/wordpress-vip/wpvip-performance.md @@ -26,7 +26,7 @@ Each SQL query adds overhead and latency. Reduce the number of queries. ### Query Design ```php -// CORRECT - Efficient query with proper limits +// CORRECT: Efficient query with proper limits $posts = $wpdb->get_results( $wpdb->prepare( "SELECT ID, post_title FROM $wpdb->posts @@ -39,7 +39,7 @@ $posts = $wpdb->get_results( ) ); -// INCORRECT - Unbounded query +// INCORRECT: Unbounded query $posts = $wpdb->get_results( "SELECT * FROM $wpdb->posts" ); ``` @@ -143,14 +143,14 @@ add_action( 'save_post', function( $post_id ) { Each template requires additional processing and database queries. ```php -// CAUTION - Too many template parts +// CAUTION: Too many template parts get_template_part( 'header' ); get_template_part( 'nav' ); get_template_part( 'sidebar' ); get_template_part( 'content' ); get_template_part( 'footer' ); -// BETTER - Cache reusable partials +// BETTER: Cache reusable partials $nav = wp_cache_get( 'main_nav', 'partials' ); if ( false === $nav ) { ob_start(); @@ -170,10 +170,10 @@ echo $nav; Only use necessary hooks and avoid excessive callbacks. ```php -// INCORRECT - Running on every admin request +// INCORRECT: Running on every admin request add_action( 'admin_init', 'expensive_operation' ); -// CORRECT - Conditional execution +// CORRECT: Conditional execution add_action( 'admin_init', function() { if ( ! isset( $_GET['my_action'] ) ) { return; @@ -216,7 +216,7 @@ define( 'DISABLE_WP_CRON', true ); ### Use Server Cron ```bash -# Crontab entry - run every minute +# Crontab entry: run every minute * * * * * cd /path/to/wordpress && wp cron event run --due-now ``` @@ -231,19 +231,21 @@ On WordPress VIP, cron is handled by Automattic Cron Control plugin automaticall ### Avoid Loading All Posts ```php -// INCORRECT - Loads all posts into memory +// INCORRECT: Loads all posts into memory $posts = get_posts( array( 'numberposts' => -1 ) ); foreach ( $posts as $post ) { process( $post ); } -// CORRECT - Batch processing +// CORRECT: Batch processing $paged = 1; do { - $posts = get_posts( array( - 'numberposts' => 100, - 'paged' => $paged, - ) ); + $posts = get_posts( + array( + 'numberposts' => 100, + 'paged' => $paged, + ) + ); foreach ( $posts as $post ) { process( $post ); @@ -256,12 +258,12 @@ do { ### Avoid Remote Requests in Loops ```php -// INCORRECT - Multiple requests +// INCORRECT: Multiple requests foreach ( $items as $item ) { $data = wp_remote_get( $item['url'] ); } -// CORRECT - Batch or background process +// CORRECT: Batch or background process wp_schedule_single_event( time(), 'process_items_batch', array( $items ) ); ``` @@ -299,18 +301,3 @@ add_filter( 'script_loader_tag', function( $tag, $handle ) { return $tag; }, 10, 2 ); ``` - ---- - -## Tooling - -```bash -# Query Monitor - Identify slow queries -# Install Query Monitor plugin - -# New Relic - Performance monitoring -# Available on VIP platform - -# VIP Code Analysis -vip @mysite.production -- wp vip migration analyze -``` From bf4b545a632c8200e7079bbe035a1b2968001366 Mon Sep 17 00:00:00 2001 From: Sherv Date: Tue, 17 Feb 2026 20:17:24 +0300 Subject: [PATCH 82/90] refactor(windsurf): standardize comment formatting and update security patterns in wpvip-security.md Change code example comments from dash-space to colon format for consistency (// CORRECT - to // CORRECT:, // INCORRECT - to // INCORRECT:, // UNSAFE - to // UNSAFE:, // SAFE - to // SAFE:, // PREFERRED - to // PREFERRED:, // AVOID - to // AVOID:). Update JavaScript XSS prevention example to use textContent instead of DOMPurify, remove jQuery patterns. Replace wp_remote_get with wp_safe_remote_get for external --- .../rules/wordpress-vip/wpvip-security.md | 173 ++++++------------ 1 file changed, 55 insertions(+), 118 deletions(-) diff --git a/.windsurf/rules/wordpress-vip/wpvip-security.md b/.windsurf/rules/wordpress-vip/wpvip-security.md index d23e91ad93..0a2edea83f 100644 --- a/.windsurf/rules/wordpress-vip/wpvip-security.md +++ b/.windsurf/rules/wordpress-vip/wpvip-security.md @@ -1,6 +1,6 @@ --- trigger: glob -globs: ['**/*.php', '**/*.js'] +globs: ["**/*.php", "**/*.js"] description: WordPress VIP security standards based on OWASP guidelines. Auto-applies to PHP and JS files. --- @@ -19,10 +19,10 @@ Enterprise-level security for WordPress VIP platform based on OWASP Top 10. Escape data immediately before output, not at assignment. ```php -// CORRECT - Escape at output +// CORRECT: Escape at output echo esc_html( $title ); -// INCORRECT - Escape at assignment +// INCORRECT: Escape at assignment $title = esc_html( $raw_title ); // ... later echo $title; // May be double-escaped or bypassed @@ -30,15 +30,15 @@ echo $title; // May be double-escaped or bypassed ### Escaping Functions -| Function | Use Case | -| -------- | -------- | -| `esc_html()` | HTML element content | -| `esc_attr()` | HTML attributes | -| `esc_url()` | URLs and links | -| `esc_js()` | Inline JavaScript | -| `esc_textarea()` | Textarea content | -| `wp_kses_post()` | Allow safe HTML | -| `wp_kses()` | Custom allowed HTML | +| Function | Use Case | +| ---------------- | -------------------- | +| `esc_html()` | HTML element content | +| `esc_attr()` | HTML attributes | +| `esc_url()` | URLs and links | +| `esc_js()` | Inline JavaScript | +| `esc_textarea()` | Textarea content | +| `wp_kses_post()` | Allow safe HTML | +| `wp_kses()` | Custom allowed HTML | ### Output Examples @@ -65,18 +65,17 @@ wp_localize_script( 'my-script', 'myData', array( ``` ```javascript -// UNSAFE - Never use +// UNSAFE: Never use element.innerHTML = userData; -$( element ).html( userData ); -eval( userInput ); +$(element).html(userData); +eval(userInput); -// SAFE - Programmatic DOM manipulation -const text = document.createTextNode( userData ); -element.appendChild( text ); +// SAFE: Programmatic DOM manipulation +const text = document.createTextNode(userData); +element.appendChild(text); -// SAFE - Use DOMPurify for HTML -import DOMPurify from 'dompurify'; -element.innerHTML = DOMPurify.sanitize( userData ); +// SAFE: Use textContent for text +element.textContent = userData; ``` --- @@ -97,23 +96,23 @@ $wpdb->query( ### Prepare Placeholders -| Placeholder | Type | -| ----------- | ---- | -| `%d` | Integer | -| `%f` | Float | -| `%s` | String | -| `%i` | Identifier (table/field names) | +| Placeholder | Type | +| ----------- | ------------------------------ | +| `%d` | Integer | +| `%f` | Float | +| `%s` | String | +| `%i` | Identifier (table/field names) | ### Use WordPress APIs ```php -// PREFERRED - WordPress functions +// PREFERRED: WordPress functions $posts = get_posts( array( 'post_type' => 'post', 'posts_per_page' => 10, ) ); -// AVOID - Direct queries when possible +// AVOID: Direct queries when possible $posts = $wpdb->get_results( "SELECT * FROM $wpdb->posts LIMIT 10" ); ``` @@ -123,27 +122,24 @@ $posts = $wpdb->get_results( "SELECT * FROM $wpdb->posts LIMIT 10" ); ### Sanitize Early -```php -$title = sanitize_text_field( wp_unslash( $_POST['title'] ) ); -$email = sanitize_email( $_POST['email'] ); -$url = esc_url_raw( $_POST['url'] ); -$key = sanitize_key( $_POST['key'] ); -$ids = array_map( 'absint', $_POST['ids'] ); -``` +Sanitize all input immediately when reading from superglobals. + +**For Formidable:** Use `FrmAppHelper::get_post_param()`, `FrmAppHelper::simple_get()`, or `FrmAppHelper::get_param()` instead of direct `$_POST`/`$_GET` access. See `formidable/frm-security.md` for details. ### Sanitization Functions -| Function | Use Case | -| -------- | -------- | -| `sanitize_text_field()` | Single line text | -| `sanitize_textarea_field()` | Multi-line text | -| `sanitize_email()` | Email addresses | -| `sanitize_file_name()` | File names | -| `sanitize_key()` | Keys and slugs | -| `sanitize_title()` | Titles and slugs | -| `absint()` | Positive integers | -| `intval()` | Integers | -| `wp_kses()` | HTML with allowed tags | +| Function | Use Case | +| --------------------------- | ---------------------- | +| `sanitize_text_field()` | Single line text | +| `sanitize_textarea_field()` | Multi-line text | +| `sanitize_email()` | Email addresses | +| `sanitize_file_name()` | File names | +| `sanitize_key()` | Keys and slugs | +| `sanitize_title()` | Titles and slugs | +| `sanitize_url()` | URLs | +| `absint()` | Positive integers | +| `intval()` | Integers | +| `wp_kses()` | HTML with allowed tags | --- @@ -152,7 +148,7 @@ $ids = array_map( 'absint', $_POST['ids'] ); ### Validate Against Trusted Values ```php -// CORRECT - Validate against allowed list +// CORRECT: Validate against allowed list $allowed = array( 'draft', 'publish', 'pending' ); if ( ! in_array( $status, $allowed, true ) ) { wp_die( 'Invalid status' ); @@ -166,52 +162,6 @@ if ( $value === 'expected' ) { --- -## Authentication & Authorization - -### Nonce Verification - -```php -// In form -wp_nonce_field( 'my_action', 'my_nonce' ); - -// On submission -if ( ! isset( $_POST['my_nonce'] ) || - ! wp_verify_nonce( $_POST['my_nonce'], 'my_action' ) ) { - wp_die( 'Security check failed' ); -} -``` - -### AJAX Nonce - -```php -// Enqueue with nonce -wp_localize_script( 'my-script', 'myAjax', array( - 'ajaxurl' => admin_url( 'admin-ajax.php' ), - 'nonce' => wp_create_nonce( 'my_ajax_action' ), -) ); - -// Verify in handler -add_action( 'wp_ajax_my_action', function() { - check_ajax_referer( 'my_ajax_action', 'nonce' ); - // Process request -} ); -``` - -### Capability Checks - -```php -if ( ! current_user_can( 'edit_posts' ) ) { - wp_die( 'Unauthorized access' ); -} - -// Check specific post -if ( ! current_user_can( 'edit_post', $post_id ) ) { - wp_die( 'You cannot edit this post' ); -} -``` - ---- - ## File Operations ### Use WP_Filesystem @@ -259,7 +209,7 @@ if ( isset( $upload['error'] ) ) { ### Use WordPress HTTP API ```php -$response = wp_remote_get( 'https://api.example.com/data', array( +$response = wp_safe_remote_get( 'https://api.example.com/data', array( 'timeout' => 15, ) ); @@ -285,14 +235,16 @@ $response = wp_remote_get( $url, array( ## Forbidden Functions -| Function | Reason | Alternative | -| -------- | ------ | ----------- | -| `extract()` | Unpredictable | Access array keys directly | -| `eval()` | Security vulnerability | Refactor code logic | -| `create_function()` | Deprecated, insecure | Anonymous functions | -| `serialize()` for user data | Security risk | `json_encode()` | -| `file_get_contents()` for URLs | Unreliable | `wp_remote_get()` | -| `curl_*` functions | Inconsistent | WP HTTP API | +| Function | Reason | Alternative | +| ------------------------------ | ---------------------- | -------------------------------------- | +| `extract()` | Unpredictable | Access array keys directly | +| `eval()` | Security vulnerability | Refactor code logic | +| `create_function()` | Deprecated, insecure | Anonymous functions | +| `unserialize()` | Object injection risk | `json_decode()` or validate input type | +| `file_get_contents()` for URLs | Unreliable | `wp_safe_remote_get()` | +| `curl_*` functions | Inconsistent | WP HTTP API | + +**For Formidable:** Use `FrmAppHelper::maybe_unserialize_array()` instead of `unserialize()`. Note that `serialize()` is safe to use. --- @@ -334,18 +286,3 @@ error_log( 'Login: ' . $username . ' / ' . $password ); // CORRECT error_log( 'Login attempt: ' . $username ); ``` - ---- - -## Tooling - -```bash -# VIP PHPCS -composer require automattic/vipwpcs - -# Run check -./vendor/bin/phpcs --standard=WordPress-VIP-Go path/to/file.php - -# Snyk for vulnerability scanning -snyk test -``` From efac2502bd98a1a49a78ac96c009abb27521d29e Mon Sep 17 00:00:00 2001 From: Sherv Date: Tue, 17 Feb 2026 20:17:31 +0300 Subject: [PATCH 83/90] feat(windsurf): add .windsurf directory to export-ignore in .gitattributes Add .windsurf to export-ignore list to exclude Windsurf IDE configuration directory from Git archives and exports, following the same pattern as other development tool configurations (.vscode, .idea, .coderabbit.yaml). --- .gitattributes | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitattributes b/.gitattributes index 7f0751e7c6..d0cbed027c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -17,6 +17,7 @@ .php-cs-fixer.cache export-ignore .wp-env.json export-ignore .coderabbit.yaml export-ignore +.windsurf export-ignore cypress.config.js export-ignore /bin/ export-ignore changelog.txt export-ignore From f5975892a248ce9caa0dfdafca4445fc2723e0f8 Mon Sep 17 00:00:00 2001 From: Sherv Date: Wed, 18 Feb 2026 17:08:01 +0300 Subject: [PATCH 84/90] refactor(windsurf): add CSS-specific rules for shared classes and style cascade debugging Add CSS-Specific Rules section to code-change-principles.md with two critical guidelines: never modify shared CSS classes (add new specific classes instead), and trace complete style cascade by identifying all classes and ancestor rules before proposing changes. These rules prevent unintended side effects from CSS modifications across the plugin. --- .windsurf/rules/enterprise/code-change-principles.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.windsurf/rules/enterprise/code-change-principles.md b/.windsurf/rules/enterprise/code-change-principles.md index 6f96d65438..f54f889ccc 100644 --- a/.windsurf/rules/enterprise/code-change-principles.md +++ b/.windsurf/rules/enterprise/code-change-principles.md @@ -40,6 +40,7 @@ Before proposing solutions: - Trace execution flow from entry point to failure - **Analyze complete context** of the class or file being changed — all features, logic, and flows - **Trace parent hierarchy**: search parent classes and files up to plugin root +- **CSS-Specific Rules - Trace complete style cascade**: When debugging CSS on an element, identify ALL classes on the element and its ancestors, then search for ALL CSS rules affecting it (not just the obvious class). Understand the complete cascade before proposing changes - Identify ALL affected locations in the codebase - Map dependencies: what calls this code, what does this code call - Check plugin requirements: must code work standalone or require Pro/addons @@ -78,6 +79,7 @@ Before proposing solutions: - Never refactor unrelated code in the same commit - If a rule conflicts with existing code in the file being modified, follow the rule for new code but do not refactor unrelated existing code - Make the smallest change that completely solves the problem +- **CSS-Specific Rules - Never modify shared CSS classes**: If a CSS class is already used elsewhere in the plugin, do not change its behavior. Instead, add a new specific class for the feature and define new styles for it - Use Big-O to compare algorithms and choose the most efficient one for large inputs and iterations - Never change method signatures, return types, or data structures - Add defensive checks where data comes in, not where used everywhere From be7ae8761e0a52c47d24c1dae1ca04f7e75c2a11 Mon Sep 17 00:00:00 2001 From: Sherv Date: Fri, 20 Feb 2026 21:06:32 +0300 Subject: [PATCH 85/90] Add multi-issue fix principle to code change guidelines --- .windsurf/rules/enterprise/code-change-principles.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.windsurf/rules/enterprise/code-change-principles.md b/.windsurf/rules/enterprise/code-change-principles.md index f54f889ccc..1725730fe6 100644 --- a/.windsurf/rules/enterprise/code-change-principles.md +++ b/.windsurf/rules/enterprise/code-change-principles.md @@ -16,6 +16,7 @@ Critical principles for enterprise-level plugin development. 3. **Backward compatibility**: Maintain 100% compatibility with existing callers 4. **No custom solutions**: Never invent new patterns. Use existing ones or search the web to review best practices, then follow the official WordPress standards or VIP guidelines. 5. **User changes are final**: If user makes manual changes, treat as authoritative +6. **Multi-issue fixes**: When fixing multiple issues in one request, run all 6 phases independently for each issue. Findings from one issue's phases may inform the next, but every phase is mandatory for every issue, even when issues share a root cause. --- From 604f8e51196fe5af764223232b979f2b146268a0 Mon Sep 17 00:00:00 2001 From: Sherv Date: Fri, 20 Feb 2026 21:06:37 +0300 Subject: [PATCH 86/90] Add guideline for PHPDoc/JSDoc parameter descriptions --- .windsurf/rules/enterprise/principles.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.windsurf/rules/enterprise/principles.md b/.windsurf/rules/enterprise/principles.md index e146d09a59..7b99be3e95 100644 --- a/.windsurf/rules/enterprise/principles.md +++ b/.windsurf/rules/enterprise/principles.md @@ -15,3 +15,9 @@ Applies to all generated text: PR titles, PR body, commit messages, comments, an - **No semicolons** (`;`): split into separate sentences instead - **Full GitHub URL** for issue references (e.g., `https://github.com/Strategy11/formidable-pro/issues/3030`), never shorthand `#number` (PRs may target a different repo than the issue) - **No hard-wrapping** in PR body text: let GitHub handle line wrapping. The 72-char wrap rule applies only to **commit message bodies** + +--- + +## PHPDoc/JSdoc Parameter Descriptions + +PHPDoc/JSdoc `@param` descriptions must be concise and describe what the parameter contains. Never document a parameter as "Unused", always describe its content, even when it is present only for hook/filter signature compatibility. From 245d220a95f4e12688fed15e06566a3e22e22c00 Mon Sep 17 00:00:00 2001 From: Sherv Date: Fri, 20 Feb 2026 21:06:43 +0300 Subject: [PATCH 87/90] Add ESLint documentation with active plugins and usage commands --- .windsurf/rules/formidable/frm-javascript.md | 34 ++++++++++++-------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/.windsurf/rules/formidable/frm-javascript.md b/.windsurf/rules/formidable/frm-javascript.md index b37dfab6a5..726107c51a 100644 --- a/.windsurf/rules/formidable/frm-javascript.md +++ b/.windsurf/rules/formidable/frm-javascript.md @@ -930,19 +930,27 @@ form.submit(); // Errors if form doesn't exist (which is a bug to fix) --- -## Tooling +## ESLint + +ESLint is the linting tool used for this project. It statically analyzes code to catch errors and enforce coding standards. + +### ESLint Plugins + +The following ESLint plugins are active: + +- **`sonarjs`**: detects bugs and code smells (e.g., duplicated code, cognitive complexity) +- **`no-jquery`**: flags jQuery usage to enforce native DOM APIs +- **`jsdoc`**: enforces JSDoc comment format and completeness +- **`unicorn`**: enforces modern JavaScript best practices and consistent style + +### Running ESLint ```bash -# Install ESLint with WordPress config -npm install --save-dev @wordpress/eslint-plugin - -# .eslintrc.json -{ - "extends": [ "plugin:@wordpress/eslint-plugin/recommended" ], - "rules": { - "no-var": "error", - "prefer-const": "error", - "no-unused-vars": "error" - } -} +npm run lint +``` + +To auto-fix fixable issues: + +```bash +npm run lint:fix ``` From 9ac97a0aa2ec76561e1ca4664f316d65d6ae9e86 Mon Sep 17 00:00:00 2001 From: Sherv Date: Fri, 20 Feb 2026 21:06:50 +0300 Subject: [PATCH 88/90] Add explicit window assignment guideline for var-to-const/let refactoring --- .windsurf/rules/wordpress/javascript.md | 36 ++++++++++--------------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/.windsurf/rules/wordpress/javascript.md b/.windsurf/rules/wordpress/javascript.md index 275a273a6c..ea068cbe54 100644 --- a/.windsurf/rules/wordpress/javascript.md +++ b/.windsurf/rules/wordpress/javascript.md @@ -201,10 +201,23 @@ elements ### Declaring Variables with const and let -For code written using ES2015 or newer, `const` and `let` should always be used in place of `var`. A declaration should use `const` unless its value will be reassigned, in which case `let` is appropriate. +`const` and `let` should always be used in place of `var`. A declaration should use `const` unless its value will be reassigned, in which case `let` is appropriate. Unlike `var`, it is not necessary to declare all variables at the top of a function. Instead, they are to be declared at the point at which they are first used. +**Global scope difference:** `var` declarations at the top level become implicit properties of the `window` object, but `let` and `const` do not. In legacy files like `js/src/admin/admin.js`, some top-level variables are intentionally exposed as globals and accessed elsewhere as `window.varName`. When refactoring those, do not replace `var` with `const` or `let` that silently removes the global. Use an explicit `window` assignment instead: + +```javascript +// Legacy: top-level var is implicitly window.frmAdminBuild +var frmAdminBuild = frmAdminBuildJS(); + +// WRONG: removes it from window, other scripts lose access +const frmAdminBuild = frmAdminBuildJS(); + +// CORRECT: make the window assignment explicit +window.frmAdminBuild = frmAdminBuildJS(); +``` + ### Globals All globals used within a file should be documented at the top of that file. Multiple globals can be comma-separated. @@ -459,24 +472,3 @@ domReady( () => { } ); ``` ---- - -## JSHint - -JSHint is an automated code quality tool, designed to catch errors in your JavaScript code. - -### JSHint Settings - -The configuration options used for JSHint are stored within a `.jshintrc` file. - -### JSHint Overrides: Ignore Blocks - -To exclude a specific file region from being processed by JSHint, enclose it in JSHint directive comments: - -```javascript -/* jshint ignore:start */ -if ( typeof thirdPartyLibrary === 'undefined' ) { - // Third-party code -} -/* jshint ignore:end */ -``` From d822d9d35c37a908a367a19ec2b25b52cf9e5a7f Mon Sep 17 00:00:00 2001 From: Mike Letellier Date: Fri, 20 Feb 2026 15:24:37 -0400 Subject: [PATCH 89/90] Add new custom eslint rules --- .gitattributes | 1 + .windsurf/skills/eslint-rules/SKILL.md | 193 ++++++++++++++++++ bin/zip-plugin.sh | 1 + eslint-rules/index.js | 15 ++ .../rules/no-redundant-undefined-check.js | 84 ++++++++ eslint-rules/rules/no-typeof-undefined.js | 109 ++++++++++ eslint-rules/rules/prefer-includes.js | 115 +++++++++++ .../rules/prefer-strict-comparison.js | 96 +++++++++ eslint.config.mjs | 9 + js/admin/applications.js | 8 +- js/admin/dom.js | 16 +- js/admin/embed.js | 2 +- js/admin/legacy-views.js | 2 +- js/admin/style.js | 8 +- js/formidable.js | 14 +- js/src/admin/addon-state.js | 4 +- js/src/admin/admin.js | 50 ++--- js/src/admin/styles.js | 4 +- js/src/components/class-counter.js | 2 +- js/src/components/class-tabs-navigator.js | 4 +- .../components/slider-component.js | 4 +- .../frm-typography-component.js | 2 +- js/src/web-components/frm-web-component.js | 2 +- square/js/settings.js | 6 +- stripe/js/connect_settings.js | 6 +- stripe/js/frmstrp.js | 8 +- 26 files changed, 694 insertions(+), 71 deletions(-) create mode 100644 .windsurf/skills/eslint-rules/SKILL.md create mode 100644 eslint-rules/index.js create mode 100644 eslint-rules/rules/no-redundant-undefined-check.js create mode 100644 eslint-rules/rules/no-typeof-undefined.js create mode 100644 eslint-rules/rules/prefer-includes.js create mode 100644 eslint-rules/rules/prefer-strict-comparison.js diff --git a/.gitattributes b/.gitattributes index d0cbed027c..0b01cdfd6c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -46,6 +46,7 @@ mago.toml export-ignore /resources/ export-ignore webpack.dev.js export-ignore .browserslistrc export-ignore +/eslint-rules/ export-ignore /phpcs-sniffs/ export-ignore .deepsource.toml export-ignore .semgrepignore export-ignore diff --git a/.windsurf/skills/eslint-rules/SKILL.md b/.windsurf/skills/eslint-rules/SKILL.md new file mode 100644 index 0000000000..8b6f3c00d2 --- /dev/null +++ b/.windsurf/skills/eslint-rules/SKILL.md @@ -0,0 +1,193 @@ +--- +name: eslint-rules +description: Creating and maintaining custom ESLint rules for Formidable Forms. Use when adding new custom ESLint rules, modifying existing ones, or debugging rule behavior. +--- + +# Custom ESLint Rules + +Workflow for creating and maintaining custom ESLint rules in the Formidable Forms plugin. + +## When to Use + +- Adding a new custom ESLint rule +- Modifying an existing custom rule +- Debugging why a custom rule is not catching a pattern +- Running ESLint with custom rules + +--- + +## Architecture + +Custom ESLint rules live in `/eslint-rules/` at the project root. + +``` +eslint-rules/ +├── index.js # Plugin entry point, exports all rules +└── rules/ # Individual rule files + ├── prefer-strict-comparison.js + ├── no-redundant-undefined-check.js + ├── prefer-includes.js + └── no-typeof-undefined.js +``` + +The plugin is imported in `eslint.config.mjs` as `formidable` and rules are referenced as `formidable/`. + +### Release Exclusions + +The `/eslint-rules/` directory is excluded from releases via: +- `.gitattributes`: `export-ignore` +- `bin/zip-plugin.sh`: `-x "*/eslint-rules/*"` + +This mirrors the pattern used for `/phpcs-sniffs/`. + +--- + +## Existing Rules + +### formidable/prefer-strict-comparison + +Enforces `===` and `!==` instead of `==` and `!=` when comparing against non-empty, non-numeric string literals. Mirrors the PHP sniff `PreferStrictComparisonSniff`. + +- **Fixable:** Yes +- **Safe strings:** Non-empty and non-numeric (e.g., `'string'`, `'post'`) +- **Unsafe strings (skipped):** `''`, `'0'`, `'123'`, `'1.5'` + +### formidable/no-redundant-undefined-check + +Detects `x !== undefined && x` patterns where the undefined check is redundant because the truthy check already covers it. + +- **Fixable:** Yes +- **Pattern:** `expr !== undefined && expr` becomes just `expr` + +### formidable/prefer-includes + +Detects `.indexOf()` comparisons with `-1` and suggests `.includes()` instead. Catches yoda-style patterns that `unicorn/prefer-includes` misses (e.g., `-1 !== [].indexOf(x)`). + +- **Fixable:** Yes +- **Patterns caught:** + - `arr.indexOf(x) !== -1` and yoda `-1 !== arr.indexOf(x)` + - `arr.indexOf(x) === -1` and yoda `-1 === arr.indexOf(x)` + - `arr.indexOf(x) > -1` and yoda `-1 < arr.indexOf(x)` + - `arr.indexOf(x) >= 0` + +### formidable/no-typeof-undefined + +Detects `typeof x === 'undefined'` and yoda `'undefined' === typeof x` patterns. Replaces with direct `x === undefined` comparison. Catches yoda-style patterns that `unicorn/no-typeof-undefined` misses. + +- **Fixable:** Yes +- **Patterns caught:** + - `typeof x === 'undefined'` / `typeof x == 'undefined'` + - `'undefined' === typeof x` / `'undefined' == typeof x` (yoda) + - Both `===`/`!==` and `==`/`!=` variants + +--- + +## Adding a New Rule + +### Step 1: Create the Rule File + +Create a new file in `eslint-rules/rules/.js`. Follow the existing pattern: + +```javascript +'use strict'; + +module.exports = { + meta: { + type: 'suggestion', // 'suggestion', 'problem', or 'layout' + docs: { + description: 'Description of what the rule enforces.', + }, + fixable: 'code', // 'code' if auto-fixable, null otherwise + schema: [], // JSON Schema for rule options + messages: { + messageId: 'Error message with {{placeholder}}.', + }, + }, + + create( context ) { + const sourceCode = context.sourceCode; + + return { + // AST node visitor(s) + BinaryExpression( node ) { + // Rule logic + context.report({ + node, + messageId: 'messageId', + data: { placeholder: 'value' }, + fix( fixer ) { + return fixer.replaceText( node, 'replacement' ); + }, + }); + }, + }; + }, +}; +``` + +### Step 2: Register the Rule + +Add the rule to `eslint-rules/index.js`: + +```javascript +const newRule = require( './rules/new-rule' ); + +module.exports = { + rules: { + // ... existing rules + 'new-rule': newRule, + }, +}; +``` + +### Step 3: Enable in Config + +Add to the rules section in `eslint.config.mjs`: + +```javascript +'formidable/new-rule': 'error', +``` + +### Step 4: Verify + +```bash +# Check for violations (requires nvm for node) +export PATH="$HOME/.nvm/versions/node/v20.19.2/bin:$PATH" +./node_modules/.bin/eslint . + +# Auto-fix all violations +./node_modules/.bin/eslint . --fix +``` + +Or use the npm scripts: + +```bash +npm run lint +npm run lint:fix +``` + +--- + +## Design Principles + +1. **All rules must support `--fix`** so they can be applied to the existing codebase automatically +2. **Handle yoda-style comparisons** since the WordPress coding standard historically used yoda conditions +3. **Only enforce safe transformations** (e.g., prefer-strict-comparison skips empty and numeric strings) +4. **Complement existing plugins** by catching patterns that unicorn, sonarjs, etc. miss +5. **Mirror PHP sniffs where applicable** to maintain consistency between PHP and JS linting (see `/phpcs-sniffs/`) + +--- + +## AST Explorer + +Use https://astexplorer.net/ with the `espree` parser to inspect AST node types when developing rules. This helps identify the correct node visitors and property names. + +## Invocation + +Cascade automatically invokes this skill when your request involves custom ESLint rules. + +To manually invoke: + +```text +@eslint-rules +``` diff --git a/bin/zip-plugin.sh b/bin/zip-plugin.sh index b946e1afdf..806171a9cb 100755 --- a/bin/zip-plugin.sh +++ b/bin/zip-plugin.sh @@ -121,6 +121,7 @@ zip -r $zipname $destination \ -x "*/mago.toml" \ -x "formidable-ai/resources/*" \ -x "*/webpack.dev.js" \ + -x "*/eslint-rules/*" \ -x "*/phpcs-sniffs/*" \ -x "$source/venv/*" \ -x "formidable/resources/*" \ diff --git a/eslint-rules/index.js b/eslint-rules/index.js new file mode 100644 index 0000000000..76f6e8f772 --- /dev/null +++ b/eslint-rules/index.js @@ -0,0 +1,15 @@ +'use strict'; + +const preferStrictComparison = require( './rules/prefer-strict-comparison' ); +const noRedundantUndefinedCheck = require( './rules/no-redundant-undefined-check' ); +const preferIncludes = require( './rules/prefer-includes' ); +const noTypeofUndefined = require( './rules/no-typeof-undefined' ); + +module.exports = { + rules: { + 'prefer-strict-comparison': preferStrictComparison, + 'no-redundant-undefined-check': noRedundantUndefinedCheck, + 'prefer-includes': preferIncludes, + 'no-typeof-undefined': noTypeofUndefined, + }, +}; diff --git a/eslint-rules/rules/no-redundant-undefined-check.js b/eslint-rules/rules/no-redundant-undefined-check.js new file mode 100644 index 0000000000..9be577f211 --- /dev/null +++ b/eslint-rules/rules/no-redundant-undefined-check.js @@ -0,0 +1,84 @@ +'use strict'; + +/** + * Checks if two AST nodes represent the same expression. + * + * @param {Object} a First AST node. + * @param {Object} b Second AST node. + * @param {Object} sourceCode The source code object. + * @return {boolean} Whether the nodes represent the same expression. + */ +function isSameExpression( a, b, sourceCode ) { + return sourceCode.getText( a ) === sourceCode.getText( b ); +} + +/** + * Checks if a node is an undefined check (x !== undefined or undefined !== x). + * + * @param {Object} node The AST node. + * @return {Object|null} The expression being checked, or null. + */ +function getUndefinedCheckExpression( node ) { + if ( node.type !== 'BinaryExpression' || node.operator !== '!==' ) { + return null; + } + + if ( node.right.type === 'Identifier' && node.right.name === 'undefined' ) { + return node.left; + } + + if ( node.left.type === 'Identifier' && node.left.name === 'undefined' ) { + return node.right; + } + + return null; +} + +module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'Disallow redundant undefined checks before truthy checks (e.g., `x !== undefined && x` simplifies to `x`).', + }, + fixable: 'code', + schema: [], + messages: { + redundant: 'The `!== undefined` check is redundant because the truthy check already covers it. Use just `{{expression}}`.', + }, + }, + + create( context ) { + const sourceCode = context.sourceCode; + + return { + LogicalExpression( node ) { + if ( node.operator !== '&&' ) { + return; + } + + const { left, right } = node; + + // Pattern: x !== undefined && x + const checkedExpression = getUndefinedCheckExpression( left ); + if ( checkedExpression === null ) { + return; + } + + if ( ! isSameExpression( checkedExpression, right, sourceCode ) ) { + return; + } + + context.report({ + node, + messageId: 'redundant', + data: { + expression: sourceCode.getText( right ), + }, + fix( fixer ) { + return fixer.replaceText( node, sourceCode.getText( right ) ); + }, + }); + }, + }; + }, +}; diff --git a/eslint-rules/rules/no-typeof-undefined.js b/eslint-rules/rules/no-typeof-undefined.js new file mode 100644 index 0000000000..84635d6f79 --- /dev/null +++ b/eslint-rules/rules/no-typeof-undefined.js @@ -0,0 +1,109 @@ +'use strict'; + +/** + * Checks if a node is a typeof expression. + * + * @param {Object} node The AST node. + * @return {boolean} Whether the node is a typeof expression. + */ +function isTypeofExpression( node ) { + return node.type === 'UnaryExpression' && node.operator === 'typeof'; +} + +/** + * Checks if a node is the string literal 'undefined'. + * + * @param {Object} node The AST node. + * @return {boolean} Whether the node is the string 'undefined'. + */ +function isUndefinedString( node ) { + return node.type === 'Literal' && node.value === 'undefined'; +} + +module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'Disallow comparing `typeof` against the string `"undefined"`. Use direct `=== undefined` comparison instead.', + }, + fixable: 'code', + schema: [], + messages: { + noTypeofUndefined: 'Compare directly against `undefined` instead of using `typeof` with the string `"undefined"`.', + }, + }, + + create( context ) { + const sourceCode = context.sourceCode; + + /** + * Checks if a variable name is declared in the current scope chain. + * Variables declared via var/let/const, function params, imports, and + * /*global* / comments all count as declared. + * + * @param {Object} astNode The AST node where the check occurs. + * @param {string} name The variable name to look up. + * @return {boolean} Whether the variable is declared. + */ + function isDeclaredVariable( astNode, name ) { + const scope = sourceCode.getScope( astNode ); + let current = scope; + + while ( current ) { + const variable = current.set.get( name ); + if ( variable && variable.defs.length > 0 ) { + return true; + } + + current = current.upper; + } + + return false; + } + + return { + BinaryExpression( node ) { + const { operator, left, right } = node; + + if ( operator !== '===' && operator !== '!==' && operator !== '==' && operator !== '!=' ) { + return; + } + + let typeofNode; + + // typeof x === 'undefined' or typeof x == 'undefined' + if ( isTypeofExpression( left ) && isUndefinedString( right ) ) { + typeofNode = left; + // 'undefined' === typeof x or 'undefined' == typeof x (yoda) + } else if ( isUndefinedString( left ) && isTypeofExpression( right ) ) { + typeofNode = right; + } else { + return; + } + + // Skip bare identifiers that are not declared in scope. + // typeof is the only safe way to check for undeclared globals + // (e.g., typeof frmProForm !== 'undefined') without a ReferenceError. + // Member expressions (e.g., typeof obj.prop) are always safe to convert. + const argument = typeofNode.argument; + if ( argument.type === 'Identifier' && ! isDeclaredVariable( node, argument.name ) ) { + return; + } + + const argumentText = sourceCode.getText( argument ); + const isEqual = operator === '===' || operator === '=='; + const replacement = isEqual + ? `${ argumentText } === undefined` + : `${ argumentText } !== undefined`; + + context.report({ + node, + messageId: 'noTypeofUndefined', + fix( fixer ) { + return fixer.replaceText( node, replacement ); + }, + }); + }, + }; + }, +}; diff --git a/eslint-rules/rules/prefer-includes.js b/eslint-rules/rules/prefer-includes.js new file mode 100644 index 0000000000..050594c6a4 --- /dev/null +++ b/eslint-rules/rules/prefer-includes.js @@ -0,0 +1,115 @@ +'use strict'; + +/** + * Checks if a node is the literal value -1. + * + * @param {Object} node The AST node. + * @return {boolean} Whether the node is -1. + */ +function isNegativeOne( node ) { + if ( node.type === 'UnaryExpression' && node.operator === '-' && node.argument.type === 'Literal' && node.argument.value === 1 ) { + return true; + } + + if ( node.type === 'Literal' && node.value === -1 ) { + return true; + } + + return false; +} + +/** + * Checks if a node is a .indexOf() call expression. + * + * @param {Object} node The AST node. + * @return {boolean} Whether the node is a .indexOf() call. + */ +function isIndexOfCall( node ) { + return ( + node.type === 'CallExpression' && + node.callee.type === 'MemberExpression' && + node.callee.property.type === 'Identifier' && + node.callee.property.name === 'indexOf' && + node.arguments.length === 1 + ); +} + +module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'Prefer `.includes()` over `.indexOf()` comparisons with -1, including yoda-style.', + }, + fixable: 'code', + schema: [], + messages: { + preferIncludes: 'Use `.includes()` instead of `.indexOf()` comparison with -1.', + }, + }, + + create( context ) { + const sourceCode = context.sourceCode; + + return { + BinaryExpression( node ) { + const { operator, left, right } = node; + + let indexOfNode; + let negativeOneNode; + let isNegated; + + // Detect: expr.indexOf(x) !== -1 / expr.indexOf(x) != -1 + // Detect: expr.indexOf(x) === -1 / expr.indexOf(x) == -1 + // Detect: expr.indexOf(x) > -1 + // Detect: expr.indexOf(x) >= 0 + // Detect: -1 !== expr.indexOf(x) / -1 != expr.indexOf(x) (yoda) + // Detect: -1 === expr.indexOf(x) / -1 == expr.indexOf(x) (yoda) + // Detect: -1 < expr.indexOf(x) (yoda for > -1) + + if ( isIndexOfCall( left ) && isNegativeOne( right ) ) { + indexOfNode = left; + negativeOneNode = right; + + if ( operator === '!==' || operator === '!=' || operator === '>' ) { + isNegated = false; + } else if ( operator === '===' || operator === '==' ) { + isNegated = true; + } else { + return; + } + } else if ( isNegativeOne( left ) && isIndexOfCall( right ) ) { + // Yoda style: -1 !== expr.indexOf(x) + indexOfNode = right; + negativeOneNode = left; + + if ( operator === '!==' || operator === '!=' || operator === '<' ) { + isNegated = false; + } else if ( operator === '===' || operator === '==' ) { + isNegated = true; + } else { + return; + } + } else if ( isIndexOfCall( left ) && right.type === 'Literal' && right.value === 0 && operator === '>=' ) { + // expr.indexOf(x) >= 0 + indexOfNode = left; + isNegated = false; + } else { + return; + } + + const objectText = sourceCode.getText( indexOfNode.callee.object ); + const argumentText = sourceCode.getText( indexOfNode.arguments[0] ); + const includesCall = `${ objectText }.includes( ${ argumentText } )`; + const replacement = isNegated ? `! ${ includesCall }` : includesCall; + + context.report({ + node, + messageId: 'preferIncludes', + fix( fixer ) { + return fixer.replaceText( node, replacement ); + }, + }); + }, + }; + }, +}; diff --git a/eslint-rules/rules/prefer-strict-comparison.js b/eslint-rules/rules/prefer-strict-comparison.js new file mode 100644 index 0000000000..62e487fbef --- /dev/null +++ b/eslint-rules/rules/prefer-strict-comparison.js @@ -0,0 +1,96 @@ +'use strict'; + +/** + * Checks if a string literal value is safe for strict comparison. + * A string is safe if it is non-empty and non-numeric. + * + * @param {string} value The raw string value (without quotes). + * @return {boolean} Whether the string is safe for strict comparison. + */ +function isSafeString( value ) { + if ( value === '' ) { + return false; + } + + if ( ! isNaN( Number( value ) ) ) { + return false; + } + + return true; +} + +/** + * Gets the raw string value from a Literal AST node. + * + * @param {Object} node The AST node. + * @return {string|null} The string value, or null if not a string literal. + */ +function getStringValue( node ) { + if ( node.type === 'Literal' && typeof node.value === 'string' ) { + return node.value; + } + + if ( node.type === 'TemplateLiteral' && node.expressions.length === 0 && node.quasis.length === 1 ) { + return node.quasis[0].value.cooked; + } + + return null; +} + +module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'Enforce strict equality (===, !==) when comparing against non-empty, non-numeric string literals.', + }, + fixable: 'code', + schema: [], + messages: { + useStrict: 'Use {{strict}} instead of {{loose}} when comparing to string "{{value}}".', + }, + }, + + create( context ) { + return { + BinaryExpression( node ) { + const { operator, left, right } = node; + + if ( operator !== '==' && operator !== '!=' ) { + return; + } + + const leftString = getStringValue( left ); + const rightString = getStringValue( right ); + + // At least one side must be a safe string literal. + if ( leftString === null && rightString === null ) { + return; + } + + const stringValue = leftString !== null ? leftString : rightString; + + if ( ! isSafeString( stringValue ) ) { + return; + } + + const strictOperator = operator === '==' ? '===' : '!=='; + + context.report({ + node, + messageId: 'useStrict', + data: { + strict: strictOperator, + loose: operator, + value: stringValue, + }, + fix( fixer ) { + return fixer.replaceTextRange( + [ left.range[1], right.range[0] ], + context.sourceCode.getText().slice( left.range[1], right.range[0] ).replace( operator, strictOperator ) + ); + }, + }); + }, + }; + }, +}; diff --git a/eslint.config.mjs b/eslint.config.mjs index 9b294cbe53..addd0ce583 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -9,6 +9,7 @@ import compatPlugin from 'eslint-plugin-compat'; import jsdocPlugin from 'eslint-plugin-jsdoc'; import unicornPlugin from 'eslint-plugin-unicorn'; import importPlugin from 'eslint-plugin-import'; +import formidablePlugin from './eslint-rules/index.js'; import globals from 'globals'; export default [ @@ -36,6 +37,7 @@ export default [ '**/node_modules/**', '**/vendor/**', '**/venv/**', + 'eslint-rules/**', 'build/**', 'coverage/**', ], @@ -86,6 +88,7 @@ export default [ jsdoc: jsdocPlugin, unicorn: unicornPlugin, import: importPlugin, + formidable: formidablePlugin, }, settings: { 'import/resolver': { @@ -318,6 +321,12 @@ export default [ 'unicorn/prefer-global-this': 'off', 'unicorn/prefer-string-raw': 'off', 'unicorn/switch-case-braces': 'off', + + // Custom Formidable rules + 'formidable/prefer-strict-comparison': 'error', + 'formidable/no-redundant-undefined-check': 'error', + 'formidable/prefer-includes': 'error', + 'formidable/no-typeof-undefined': 'error', }, }, diff --git a/js/admin/applications.js b/js/admin/applications.js index ab61ddebf9..d9c4200cc1 100644 --- a/js/admin/applications.js +++ b/js/admin/applications.js @@ -231,7 +231,7 @@ addTemplatesToGrid( state.templates.filter( - template => -1 !== template.categories.indexOf( category ) + template => template.categories.includes( category ) ) ); } @@ -339,9 +339,9 @@ } function filterItemCount( counter, { data } ) { - const hasForms = 'undefined' !== typeof data.formCount && '0' !== data.formCount; - const hasViews = 'undefined' !== typeof data.viewCount && '0' !== data.viewCount; - const hasPages = 'undefined' !== typeof data.pageCount && '0' !== data.pageCount; + const hasForms = data.formCount !== undefined && '0' !== data.formCount; + const hasViews = data.viewCount !== undefined && '0' !== data.viewCount; + const hasPages = data.pageCount !== undefined && '0' !== data.pageCount; if ( ! hasForms && ! hasViews && ! hasPages ) { return counter; diff --git a/js/admin/dom.js b/js/admin/dom.js index 07fac9db10..3e13d1df67 100644 --- a/js/admin/dom.js +++ b/js/admin/dom.js @@ -3,7 +3,7 @@ let __; - if ( 'undefined' === typeof wp || 'undefined' === typeof wp.i18n || 'function' !== typeof wp.i18n.__ ) { + if ( 'undefined' === typeof wp || wp.i18n === undefined || 'function' !== typeof wp.i18n.__ ) { __ = text => text; } else { __ = wp.i18n.__; @@ -82,7 +82,7 @@ if ( args.buttonType ) { output.classList.add( 'button' ); - if ( ! args.noDismiss && -1 !== [ 'red', 'primary' ].indexOf( args.buttonType ) ) { + if ( ! args.noDismiss && [ 'red', 'primary' ].includes( args.buttonType ) ) { // Primary and red buttons close modals by default on click. // To disable this default behaviour you can use the noDismiss: 1 arg. output.classList.add( 'dismiss' ); @@ -111,7 +111,7 @@ const ajax = { async doJsonFetch( action ) { let targetUrl = ajaxurl + '?action=frm_' + action; - if ( -1 === targetUrl.indexOf( 'nonce=' ) ) { + if ( ! targetUrl.includes( 'nonce=' ) ) { targetUrl += '&nonce=' + frmGlobal.nonce; } const response = await fetch( targetUrl ); @@ -135,7 +135,7 @@ if ( ! json.success ) { return Promise.reject( json.data || 'JSON result is not successful' ); } - return Promise.resolve( 'undefined' !== typeof json.data ? json.data : json ); + return Promise.resolve( json.data !== undefined ? json.data : json ); } }; @@ -391,7 +391,7 @@ item.setAttribute( 'frm-search-text', itemText ); } - const hide = notEmptySearchText && -1 === itemText.indexOf( searchText ); + const hide = notEmptySearchText && ! itemText.includes( searchText ); item.classList.toggle( 'frm_hidden', hide ); const isSearchResult = ! hide && notEmptySearchText; @@ -434,7 +434,7 @@ * @param {boolean|Object} options Options to be added to `addEventListener()` method. Default is `false`. */ documentOn: ( event, selector, handler, options ) => { - if ( 'undefined' === typeof options ) { + if ( options === undefined ) { options = false; } @@ -828,7 +828,7 @@ }; function cleanNode( node ) { - if ( 'undefined' === typeof node.tagName ) { + if ( node.tagName === undefined ) { if ( '#text' === node.nodeName ) { return document.createTextNode( node.textContent ); } @@ -848,7 +848,7 @@ return svg( svgArgs ); } - if ( 'undefined' === typeof allowedHtml[ tagType ] ) { + if ( allowedHtml[ tagType ] === undefined ) { // Tag type is not allowed. return document.createTextNode( '' ); } diff --git a/js/admin/embed.js b/js/admin/embed.js index 92bc063e2b..30b40bdc9b 100644 --- a/js/admin/embed.js +++ b/js/admin/embed.js @@ -423,7 +423,7 @@ exampleElement.readOnly = true; exampleElement.setAttribute( 'tabindex', -1 ); - if ( 'undefined' !== typeof link && 'undefined' !== typeof linkLabel ) { + if ( link !== undefined && linkLabel !== undefined ) { const linkElement = tag( 'a' ); linkElement.href = link; linkElement.textContent = linkLabel; diff --git a/js/admin/legacy-views.js b/js/admin/legacy-views.js index 7ac04226b8..c4978b01e9 100644 --- a/js/admin/legacy-views.js +++ b/js/admin/legacy-views.js @@ -352,7 +352,7 @@ function getNewRowId( rows, replace, defaultValue ) { if ( ! rows.length ) { - return 'undefined' !== typeof defaultValue ? defaultValue : 0; + return defaultValue !== undefined ? defaultValue : 0; } return parseInt( rows[ rows.length - 1 ].id.replace( replace, '' ), 10 ) + 1; } diff --git a/js/admin/style.js b/js/admin/style.js index 172e6f72f9..0a5e32e4bb 100644 --- a/js/admin/style.js +++ b/js/admin/style.js @@ -634,7 +634,7 @@ hamburgerMenu.setAttribute( 'role', 'button' ); hamburgerMenu.setAttribute( 'tabindex', 0 ); - const isTemplate = 'undefined' !== typeof data.templateKey; + const isTemplate = data.templateKey !== undefined; let dropdownMenuOptions = []; if ( isListPage ) { @@ -1188,7 +1188,7 @@ } ); jQuery( '.wp-color-result-text' ).text( function( _, oldText ) { const container = jQuery( this ).closest( '.wp-picker-container' ); - if ( 'undefined' !== typeof container && container[ 0 ].parentElement.classList.contains( 'frm-colorpicker' ) ) { + if ( container !== undefined && container[ 0 ].parentElement.classList.contains( 'frm-colorpicker' ) ) { return container[ 0 ].querySelector( '.wp-color-picker' ).value; } return oldText === 'Select Color' ? 'Select' : oldText; @@ -1239,7 +1239,7 @@ */ function handleChangeStylingSuccess( css ) { // Validate the string response. A valid output will include rules with .with_frm_style - if ( -1 === css.indexOf( '.with_frm_style' ) ) { + if ( ! css.includes( '.with_frm_style' ) ) { // Handle error (possibly a permission error, or an outdated nonce). alert( css ); return; @@ -1417,7 +1417,7 @@ * @returns {void} */ function fillMissingSignatureValidationFunction() { - if ( 'undefined' === typeof window.__FRMSIG || 'undefined' !== typeof window.frmFrontForm ) { + if ( window.__FRMSIG === undefined || window.frmFrontForm !== undefined ) { return; } diff --git a/js/formidable.js b/js/formidable.js index 31a08b233b..a7d853bd51 100644 --- a/js/formidable.js +++ b/js/formidable.js @@ -256,7 +256,7 @@ function frmFrontFormJS() { } fieldID = getFieldId( field, true ); - if ( 'undefined' === typeof errors[ fieldID ] ) { + if ( errors[ fieldID ] === undefined ) { errors[ fieldID ] = getFieldValidationMessage( field, 'data-invmsg' ); } @@ -1040,7 +1040,7 @@ function frmFrontFormJS() { ajaxUrl = frm_js.ajax_url; action = form.getAttribute( 'action' ); - if ( 'string' === typeof action && -1 !== action.indexOf( '?action=frm_forms_preview' ) ) { + if ( 'string' === typeof action && action.includes( '?action=frm_forms_preview' ) ) { ajaxUrl = action.split( '?action=frm_forms_preview' )[ 0 ]; } @@ -1161,7 +1161,7 @@ function frmFrontFormJS() { frmThemeOverride_frmPlaceError( key, jsErrors ); } else { let errorHtml; - if ( -1 !== jsErrors[ key ].indexOf( ' { list.childNodes.forEach( child => { - if ( 'undefined' === typeof child.classList ) { + if ( child.classList === undefined ) { return; } @@ -1262,7 +1262,7 @@ window.frmAdminBuildJS = function() { return; } - if ( 'undefined' !== typeof child.classList && child.classList.contains( 'form-field' ) ) { + if ( child.classList !== undefined && child.classList.contains( 'form-field' ) ) { wrapFieldLiInPlace( child ); } } @@ -1341,7 +1341,7 @@ window.frmAdminBuildJS = function() { function syncLayoutClasses( $item, type ) { let $fields, size, layoutClasses, classToAddFunction; - if ( 'undefined' === typeof type ) { + if ( type === undefined ) { type = 'even'; } @@ -1354,7 +1354,7 @@ window.frmAdminBuildJS = function() { } else if ( 'clear' === type ) { $fields.each( getSyncLayoutClass( layoutClasses, '' ) ); } else { - if ( -1 !== [ 'left', 'right', 'middle', 'even' ].indexOf( type ) ) { + if ( [ 'left', 'right', 'middle', 'even' ].includes( type ) ) { classToAddFunction = function( index ) { return getClassForBlock( size, type, index ); }; @@ -1376,7 +1376,7 @@ window.frmAdminBuildJS = function() { rowOffset = $row.offset(); - if ( 'undefined' === typeof rowOffset ) { + if ( rowOffset === undefined ) { return; } @@ -1490,7 +1490,7 @@ window.frmAdminBuildJS = function() { fieldId = this.dataset.fid; - if ( 'undefined' === typeof fieldId ) { + if ( fieldId === undefined ) { // we are syncing the drag/drop placeholder before the actual field has loaded. // this will get called again afterward and the input will exist then. this.classList.add( currentClassToAdd ); @@ -2481,7 +2481,7 @@ window.frmAdminBuildJS = function() { } newFieldId = jQuery( newFieldHtml ).attr( 'data-fid' ); - if ( 'undefined' === typeof newFieldId ) { + if ( newFieldId === undefined ) { return; } @@ -2505,7 +2505,7 @@ window.frmAdminBuildJS = function() { return; } - if ( -1 === fieldOptionKeys.indexOf( key ) ) { + if ( ! fieldOptionKeys.includes( key ) ) { return; } @@ -3211,7 +3211,7 @@ window.frmAdminBuildJS = function() { let i, fields = [], allFields = document.querySelectorAll( 'li.frm_field_box' ), - checkType = 'undefined' !== typeof fieldType; + checkType = fieldType !== undefined; for ( i = 0; i < allFields.length; i++ ) { // data-ftype is better (than data-type) cos of fields loaded by AJAX - which might not be ready yet @@ -3220,7 +3220,7 @@ window.frmAdminBuildJS = function() { } const fieldId = allFields[ i ].getAttribute( 'data-fid' ); - if ( fieldId !== undefined && fieldId ) { + if ( fieldId ) { fields.push( { fieldId, fieldName: getPossibleValue( 'frm_name_' + fieldId ), @@ -3252,7 +3252,7 @@ window.frmAdminBuildJS = function() { for ( i = 0; i < products.length; i++ ) { // let's be double sure it's string, else indexOf will fail id = products[ i ].fieldId.toString(); - checked = auto || -1 !== current.indexOf( id ); + checked = auto || current.includes( id ); if ( isSelect ) { // This fallback can be removed after 4.05. checked = checked ? ' selected' : ''; @@ -3373,7 +3373,7 @@ window.frmAdminBuildJS = function() { field.setAttribute( att, newValue ); } - if ( -1 === [ 'value', 'min', 'max' ].indexOf( att ) ) { + if ( ! [ 'value', 'min', 'max' ].includes( att ) ) { return; } @@ -4225,7 +4225,7 @@ window.frmAdminBuildJS = function() { function makeTabbable( element, ariaLabel ) { element.setAttribute( 'tabindex', 0 ); element.setAttribute( 'role', 'button' ); - if ( 'undefined' !== typeof ariaLabel ) { + if ( ariaLabel !== undefined ) { element.setAttribute( 'aria-label', ariaLabel ); } } @@ -4381,10 +4381,10 @@ window.frmAdminBuildJS = function() { if ( size > 6 ) { return 'frm1'; } - if ( -1 !== [ 2, 3, 4, 6 ].indexOf( size ) ) { + if ( [ 2, 3, 4, 6 ].includes( size ) ) { return getLayoutClassForSize( 12 / size ); } - if ( 5 === size && 'undefined' !== typeof index ) { + if ( 5 === size && index !== undefined ) { return 0 === index ? 'frm4' : 'frm2'; } return 'frm12'; @@ -4924,7 +4924,7 @@ window.frmAdminBuildJS = function() { } function unselectFieldGroups( event ) { - if ( 'undefined' !== typeof event ) { + if ( event !== undefined ) { if ( null !== event.originalEvent.target.closest( '#frm-show-fields' ) ) { return; } @@ -5300,7 +5300,7 @@ window.frmAdminBuildJS = function() { function getNewRowId( rows, replace, defaultValue ) { if ( ! rows.length ) { - return 'undefined' !== typeof defaultValue ? defaultValue : 0; + return defaultValue !== undefined ? defaultValue : 0; } return parseInt( rows[ rows.length - 1 ].id.replace( replace, '' ), 10 ) + 1; } @@ -6663,7 +6663,7 @@ window.frmAdminBuildJS = function() { let self = this; this.initOnceInAllInstances = function() { - if ( 'undefined' !== typeof updateFieldOrder.prototype.orderFieldsObject ) { + if ( updateFieldOrder.prototype.orderFieldsObject !== undefined ) { return; } @@ -6682,7 +6682,7 @@ window.frmAdminBuildJS = function() { const orderFieldsObject = updateFieldOrder.prototype.orderFieldsObject; const fieldSettingsForm = updateFieldOrder.prototype.fieldSettingsForm; - if ( 'undefined' === typeof orderFieldsObject[ fieldId ] ) { + if ( orderFieldsObject[ fieldId ] === undefined ) { field = fieldSettingsForm.querySelector( 'input[name="field_options[field_order_' + fieldId + ']"]' ); if ( null === field ) { field = parent.querySelector( 'input[name="field_options[field_order_' + fieldId + ']"]' ); @@ -6854,7 +6854,7 @@ window.frmAdminBuildJS = function() { if ( classes.trim() === '' ) { replace = ' frmstart frmend '; - if ( -1 === field.className.indexOf( replace ) ) { + if ( ! field.className.includes( replace ) ) { replace = ' frmstart frmend '; } replaceWith = ' frmstart ' + replaceWith.trim() + ' frmend '; @@ -7752,7 +7752,7 @@ window.frmAdminBuildJS = function() { this.fragment = document.createDocumentFragment(); this.initOnceInAllInstances = function() { - if ( 'undefined' !== typeof moveFieldSettings.prototype.endMarker ) { + if ( moveFieldSettings.prototype.endMarker !== undefined ) { return; } // perform a single search in the DOM and use it across all moveFieldSettings instances @@ -7776,7 +7776,7 @@ window.frmAdminBuildJS = function() { // Move the field if function is called as function with a singleField passed as arg. // In this particular case only 1 field is needed to be moved so the field will get instantly moved. // "singleField" may be undefined when it's called as a constructor instead of a function. Use the constructor to add multiple fields which are passed through "append" and move these all at once via "moveFields". - if ( 'undefined' !== typeof singleField ) { + if ( singleField !== undefined ) { this.append( singleField ); this.moveFields(); return; @@ -7884,7 +7884,7 @@ window.frmAdminBuildJS = function() { let message = frmAdminJs.only_one_action; let limit = this.dataset.limit; - if ( 'undefined' !== typeof limit ) { + if ( limit !== undefined ) { limit = parseInt( limit ); if ( limit > 1 ) { message = message.replace( 1, limit ).trim(); @@ -8552,7 +8552,7 @@ window.frmAdminBuildJS = function() { * @returns {void} */ function showSaveAndReloadModal( message ) { - if ( 'undefined' === typeof message ) { + if ( message === undefined ) { message = __( 'You are changing the field type. Not all field settings will appear as expected until you reload the page. Would you like to reload the page now?', 'formidable' ); } frmDom.modal.maybeCreateModal( @@ -8597,7 +8597,7 @@ window.frmAdminBuildJS = function() { if ( target instanceof Event ) { const useElements = document.querySelectorAll( '.frm-single-settings .frm-show-box.frmsvg use' ); const openTrigger = Array.from( useElements ).find( use => use.getAttribute( 'href' ) === '#frm_close_icon' ); - if ( 'undefined' === typeof openTrigger ) { + if ( openTrigger === undefined ) { return; } moreIcon = openTrigger.parentElement; diff --git a/js/src/admin/styles.js b/js/src/admin/styles.js index 4e0298572e..5cd50be061 100644 --- a/js/src/admin/styles.js +++ b/js/src/admin/styles.js @@ -40,7 +40,7 @@ class frmStyleOptions { return; } - if ( 'undefined' === typeof window.frm_single_style_custom_css_wp_editor || 'undefined' === typeof window.frm_single_style_custom_css_wp_editor.codemirror ) { + if ( window.frm_single_style_custom_css_wp_editor === undefined || window.frm_single_style_custom_css_wp_editor.codemirror === undefined ) { setTimeout( () => { this.cssEditorOptions.retryCount++; this.initCustomCSSEditorInstance(); @@ -96,7 +96,7 @@ class frmStyleOptions { components.forEach( component => { const element = component.querySelector( 'input.hex' ); - const id = 'undefined' !== typeof element ? element.getAttribute( 'id' ) : null; + const id = element !== undefined ? element.getAttribute( 'id' ) : null; if ( null !== id ) { elements.push( { diff --git a/js/src/components/class-counter.js b/js/src/components/class-counter.js index a8e10c5b27..ff65b7bceb 100644 --- a/js/src/components/class-counter.js +++ b/js/src/components/class-counter.js @@ -17,7 +17,7 @@ export class frmCounter { this.activeCounter = 0; this.locale = element.dataset.locale ? element.dataset.locale.replace( '_', '-' ) : 'en-US'; this.timeoutInterval = 50; - this.timeToFinish = 'undefined' !== typeof options && 'undefined' !== typeof options.timetoFinish ? options.timetoFinish : 1400; + this.timeToFinish = options !== undefined && options.timetoFinish !== undefined ? options.timetoFinish : 1400; this.valueStep = this.value / Math.ceil( this.timeToFinish / this.timeoutInterval ); if ( 0 === this.value ) { diff --git a/js/src/components/class-tabs-navigator.js b/js/src/components/class-tabs-navigator.js index 616550c334..867c8f868f 100644 --- a/js/src/components/class-tabs-navigator.js +++ b/js/src/components/class-tabs-navigator.js @@ -1,6 +1,6 @@ export class frmTabsNavigator { constructor( wrapper ) { - if ( 'undefined' === typeof wrapper ) { + if ( wrapper === undefined ) { return; } @@ -61,7 +61,7 @@ export class frmTabsNavigator { } initSlideTrackUnderline( nav, index ) { this.slideTrackLine.classList.remove( 'frm-first', 'frm-last' ); - const activeNav = 'undefined' !== typeof nav ? nav : this.navs.filter( nav => nav.classList.contains( 'frm-active' ) ); + const activeNav = nav !== undefined ? nav : this.navs.filter( nav => nav.classList.contains( 'frm-active' ) ); this.positionUnderlineIndicator( activeNav ); } diff --git a/js/src/settings-components/components/slider-component.js b/js/src/settings-components/components/slider-component.js index a8414d4b93..99e65a1a95 100644 --- a/js/src/settings-components/components/slider-component.js +++ b/js/src/settings-components/components/slider-component.js @@ -121,7 +121,7 @@ export default class frmSliderComponent { expandSliderGroup( element ) { const svgIcon = element.querySelector( '.frmsvg' ); - if ( 'undefined' === typeof element.dataset.displaySliders || null === svgIcon ) { + if ( element.dataset.displaySliders === undefined || null === svgIcon ) { return; } @@ -219,7 +219,7 @@ export default class frmSliderComponent { * @returns {NodeList} - An array-like object containing the slider group items. */ getSliderGroupItems( element ) { - if ( 'undefined' === typeof element.dataset.displaySliders ) { + if ( element.dataset.displaySliders === undefined ) { return []; } const slidersGroup = element.dataset.displaySliders.split( ',' ); diff --git a/js/src/web-components/frm-typography-component/frm-typography-component.js b/js/src/web-components/frm-typography-component/frm-typography-component.js index 846a59fc54..f6222d9bf3 100644 --- a/js/src/web-components/frm-typography-component/frm-typography-component.js +++ b/js/src/web-components/frm-typography-component/frm-typography-component.js @@ -110,7 +110,7 @@ export class frmTypographyComponent extends frmWebComponent { * @returns {boolean} - True if the value is a custom font size, false otherwise. */ static isCustomFonSize( value ) { - return -1 === [ '', '18px', '21px', '26px', '32px' ].indexOf( value ); + return ! [ '', '18px', '21px', '26px', '32px' ].includes( value ); } /** diff --git a/js/src/web-components/frm-web-component.js b/js/src/web-components/frm-web-component.js index 6e75469a54..84b7c7d456 100644 --- a/js/src/web-components/frm-web-component.js +++ b/js/src/web-components/frm-web-component.js @@ -95,7 +95,7 @@ export class frmWebComponent extends HTMLElement { return new Promise( resolve => { - if ( 'undefined' === typeof window.IntersectionObserver ) { + if ( window.IntersectionObserver === undefined ) { requestAnimationFrame( () => resolve() ); return; } diff --git a/square/js/settings.js b/square/js/settings.js index 936f74edab..faf493e8df 100644 --- a/square/js/settings.js +++ b/square/js/settings.js @@ -20,7 +20,7 @@ formData.append( 'mode', mode ); frmDom.ajax.doJsonPost( 'square_oauth', formData ).then( function( response ) { - if ( 'undefined' !== typeof response.redirect_url ) { + if ( response.redirect_url !== undefined ) { window.location = response.redirect_url; } } @@ -40,7 +40,7 @@ formData.append( 'testMode', 'test' === event.target.id.replace( 'frm_disconnect_square_', '' ) ? 1 : 0 ); frmDom.ajax.doJsonPost( 'square_disconnect', formData ).then( function( response ) { - if ( 'undefined' !== typeof response.success && response.success ) { + if ( response.success ) { window.location.reload(); } } @@ -59,7 +59,7 @@ formData.append( 'mode', 'test' ); frmDom.ajax.doJsonPost( 'square_oauth', formData ).then( function( response ) { - if ( 'undefined' !== typeof response.redirect_url ) { + if ( response.redirect_url !== undefined ) { window.location = response.redirect_url; jQuery( modal ).dialog( 'close' ); } diff --git a/stripe/js/connect_settings.js b/stripe/js/connect_settings.js index 16ab52f5e7..e71f5bed69 100644 --- a/stripe/js/connect_settings.js +++ b/stripe/js/connect_settings.js @@ -47,7 +47,7 @@ strpSettingsAjaxRequest( 'frm_stripe_connect_reauth', function( data ) { - if ( 'undefined' !== typeof data.connect_url ) { + if ( data.connect_url !== undefined ) { window.location = data.connect_url; } else { renderStripeConnectSettingsButton(); @@ -62,7 +62,7 @@ strpSettingsAjaxRequest( 'frm_stripe_connect_oauth', function( data ) { - if ( 'undefined' !== typeof data.redirect_url ) { + if ( data.redirect_url !== undefined ) { window.location = data.redirect_url; } else { renderStripeConnectSettingsButton(); @@ -97,7 +97,7 @@ if ( response !== '' ) { response = JSON.parse( response ); if ( response.success ) { - if ( 'undefined' === typeof response.data ) { + if ( response.data === undefined ) { response.data = {}; } success( response.data ); diff --git a/stripe/js/frmstrp.js b/stripe/js/frmstrp.js index bd8f6acf1c..5c9a8be601 100644 --- a/stripe/js/frmstrp.js +++ b/stripe/js/frmstrp.js @@ -182,7 +182,7 @@ * @returns {boolean} True if no errors found in event data. */ function checkEventDataForError( event ) { - if ( ! event.frmData || ! event.frmData.content.length || -1 === event.frmData.content.indexOf( '
Date: Fri, 20 Feb 2026 15:31:05 -0400 Subject: [PATCH 90/90] Build and minimize js files --- js/formidable-web-components.js | 2 +- js/formidable.min.js | 58 ++++++++++++++++----------------- js/formidable_admin.js | 2 +- js/frm_testing_mode.js | 2 +- stripe/js/frmstrp.min.js | 30 ++++++++--------- 5 files changed, 47 insertions(+), 47 deletions(-) diff --git a/js/formidable-web-components.js b/js/formidable-web-components.js index a6dcca6ea1..24738ba74a 100644 --- a/js/formidable-web-components.js +++ b/js/formidable-web-components.js @@ -1 +1 @@ -(()=>{var e={8616:e=>{e.exports=function(e,t){var r,n,i=0;function o(){var o,a,s=r,l=arguments.length;e:for(;s;){if(s.args.length===arguments.length){for(a=0;a{var n;!function(){"use strict";var i={not_string:/[^s]/,not_bool:/[^t]/,not_type:/[^T]/,not_primitive:/[^v]/,number:/[diefg]/,numeric_arg:/[bcdiefguxX]/,json:/[j]/,not_json:/[^j]/,text:/^[^\x25]+/,modulo:/^\x25{2}/,placeholder:/^\x25(?:([1-9]\d*)\$|\(([^)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-gijostTuvxX])/,key:/^([a-z_][a-z_\d]*)/i,key_access:/^\.([a-z_][a-z_\d]*)/i,index_access:/^\[(\d+)\]/,sign:/^[+-]/};function o(e){return function(e,t){var r,n,a,s,l,u,p,c,d,f=1,m=e.length,h="";for(n=0;n=0),s.type){case"b":r=parseInt(r,10).toString(2);break;case"c":r=String.fromCharCode(parseInt(r,10));break;case"d":case"i":r=parseInt(r,10);break;case"j":r=JSON.stringify(r,null,s.width?parseInt(s.width):0);break;case"e":r=s.precision?parseFloat(r).toExponential(s.precision):parseFloat(r).toExponential();break;case"f":r=s.precision?parseFloat(r).toFixed(s.precision):parseFloat(r);break;case"g":r=s.precision?String(Number(r.toPrecision(s.precision))):parseFloat(r);break;case"o":r=(parseInt(r,10)>>>0).toString(8);break;case"s":r=String(r),r=s.precision?r.substring(0,s.precision):r;break;case"t":r=String(!!r),r=s.precision?r.substring(0,s.precision):r;break;case"T":r=Object.prototype.toString.call(r).slice(8,-1).toLowerCase(),r=s.precision?r.substring(0,s.precision):r;break;case"u":r=parseInt(r,10)>>>0;break;case"v":r=r.valueOf(),r=s.precision?r.substring(0,s.precision):r;break;case"x":r=(parseInt(r,10)>>>0).toString(16);break;case"X":r=(parseInt(r,10)>>>0).toString(16).toUpperCase()}i.json.test(s.type)?h+=r:(!i.number.test(s.type)||c&&!s.sign?d="":(d=c?"+":"-",r=r.toString().replace(i.sign,"")),u=s.pad_char?"0"===s.pad_char?"0":s.pad_char.charAt(1):" ",p=s.width-(d+r).length,l=s.width&&p>0?u.repeat(p):"",h+=s.align?d+r+l:"0"===u?d+l+r:l+d+r)}return h}(function(e){if(s[e])return s[e];for(var t,r=e,n=[],o=0;r;){if(null!==(t=i.text.exec(r)))n.push(t[0]);else if(null!==(t=i.modulo.exec(r)))n.push("%");else{if(null===(t=i.placeholder.exec(r)))throw new SyntaxError("[sprintf] unexpected placeholder");if(t[2]){o|=1;var a=[],l=t[2],u=[];if(null===(u=i.key.exec(l)))throw new SyntaxError("[sprintf] failed to parse named argument key");for(a.push(u[1]);""!==(l=l.substring(u[0].length));)if(null!==(u=i.key_access.exec(l)))a.push(u[1]);else{if(null===(u=i.index_access.exec(l)))throw new SyntaxError("[sprintf] failed to parse named argument key");a.push(u[1])}t[2]=a}else o|=2;if(3===o)throw new Error("[sprintf] mixing positional and named placeholders is not (yet) supported");n.push({placeholder:t[0],param_no:t[1],keys:t[2],sign:t[3],pad_char:t[4],align:t[5],width:t[6],precision:t[7],type:t[8]})}r=r.substring(t[0].length)}return s[e]=n}(e),arguments)}function a(e,t){return o.apply(null,[e].concat(t||[]))}var s=Object.create(null);"undefined"!=typeof window&&(window.sprintf=o,window.vsprintf=a,void 0===(n=function(){return{sprintf:o,vsprintf:a}}.call(t,r,t,e))||(e.exports=n))}()}},t={};function r(n){var i=t[n];if(void 0!==i)return i.exports;var o=t[n]={exports:{}};return e[n](o,o.exports,r),o.exports}r.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return r.d(t,{a:t}),t},r.d=(e,t)=>{for(var n in t)r.o(t,n)&&!r.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{"use strict";function e(t){return e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},e(t)}function t(e,t){for(var r=0;r li"),this.slideTrackLine=this.wrapper.querySelector(".frm-tabs-active-underline"),this.slideTrack=this.wrapper.querySelector(".frm-tabs-slide-track"),this.slides=this.wrapper.querySelectorAll(".frm-tabs-slide-track > div"),this.isRTL="rtl"===document.documentElement.dir||"rtl"===document.body.dir,this.resizeObserver=null,this.init()))},(r=[{key:"init",value:function(){var e=this;null!==this.wrapper&&this.navs.length&&null!==this.slideTrackLine&&null!==this.slideTrack&&this.slides.length&&(this.initDefaultSlideTrackerWidth(),this.navs.forEach(function(t,r){t.addEventListener("click",function(t){return e.onNavClick(t,r)})}),this.setupScrollbarObserver(),window.addEventListener("beforeunload",this.cleanupObservers))}},{key:"onNavClick",value:function(e,t){var r=e.currentTarget;e.preventDefault(),this.removeActiveClassnameFromNavs(),r.classList.add("frm-active"),this.initSlideTrackUnderline(r,t),this.changeSlide(t);var n,i,o=r.querySelector("a");o&&"frm_insert_fields_tab"===o.id&&!o.closest("#frm_adv_info")&&(null===(n=window.frmAdminBuild)||void 0===n||null===(i=n.clearSettingsBox)||void 0===i||i.call(n))}},{key:"initDefaultSlideTrackerWidth",value:function(){this.slideTrackLine.dataset.initialWidth&&(this.slideTrackLine.style.width="".concat(this.slideTrackLine.dataset.initialWidth,"px"))}},{key:"initSlideTrackUnderline",value:function(e,t){this.slideTrackLine.classList.remove("frm-first","frm-last");var r=void 0!==e?e:this.navs.filter(function(e){return e.classList.contains("frm-active")});this.positionUnderlineIndicator(r)}},{key:"setupScrollbarObserver",value:function(){var e=this,t=this.wrapper.closest(".frm-scrollbar-wrapper");t&&"ResizeObserver"in window&&(this.resizeObserver=new ResizeObserver(function(){var t=e.wrapper.querySelector(".frm-tabs-navs ul > li.frm-active");t&&e.positionUnderlineIndicator(t)}),this.resizeObserver.observe(t))}},{key:"cleanupObservers",value:function(){this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null)}},{key:"positionUnderlineIndicator",value:function(e){var t=this;requestAnimationFrame(function(){var r=t.isRTL?-(e.parentElement.offsetWidth-e.offsetLeft-e.offsetWidth):e.offsetLeft;t.slideTrackLine.style.transform="translateX(".concat(r,"px)"),t.slideTrackLine.style.width=e.clientWidth+"px"})}},{key:"changeSlide",value:function(e){this.removeActiveClassnameFromSlides();var t=0==e?"0px":"calc( ( ".concat(100*e,"% + ").concat(parseInt(this.flexboxSlidesGap,10)*e,"px ) * ").concat(this.isRTL?1:-1," )");"0px"!==t?this.slideTrack.style.transform="translateX(".concat(t,")"):this.slideTrack.style.removeProperty("transform"),e in this.slides&&this.slides[e].classList.add("frm-active")}},{key:"removeActiveClassnameFromSlides",value:function(){this.slides.forEach(function(e){return e.classList.remove("frm-active")})}},{key:"removeActiveClassnameFromNavs",value:function(){this.navs.forEach(function(e){return e.classList.remove("frm-active")})}}])&&t(e.prototype,r),Object.defineProperty(e,"prototype",{writable:!1}),e;var e,r}();function o(e){return o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},o(e)}function a(e,t){(null==t||t>e.length)&&(t=e.length);for(var r=0,n=Array(t);r0&&(r.disconnect(),requestAnimationFrame(function(){return t()}))})},{threshold:.1});(e.useShadowDom()?e.shadowRoot.host:e)&&r.observe(e)}else requestAnimationFrame(function(){return t()})})}},{key:"frmLabel",set:function(e){this._labelText=e}},{key:"afterViewInit",value:function(e){}},{key:"initView",value:function(){}},{key:"connectedCallback",value:function(){this.initOptions(),this.render()}},{key:"disconnectedCallback",value:function(){}}])&&s(r.prototype,n),Object.defineProperty(r,"prototype",{writable:!1}),r;var r,n}(u(HTMLElement));function m(e){return m="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},m(e)}function h(e,t){for(var r=0;rspan.frm-component-label{font-weight:500;font-size:var(--text-sm) !important;color:var(--grey-900) !important;width:40% !important;display:block !important;margin-right:12px !important}.frm-colorpicker-component .wp-picker-container button[type=button]{position:relative;height:36px !important;background-image:none !important;background-color:#fff !important;overflow:hidden}.frm-colorpicker-component .wp-picker-container button[type=button]:after{content:"";width:20px;height:20px;display:block;position:absolute;top:0;right:8px;bottom:0;margin:auto;background:url("--frm-plugin-url/images/style/small-arrow.svg") no-repeat;background-position:center;z-index:10}.frm-colorpicker-component .wp-picker-container button[type=button]:focus{border-color:var(--primary-500) !important}.frm-colorpicker-component .wp-color-result-text{line-height:36px !important;padding:0 12px;border:0}.frm-colorpicker-component .color-alpha{width:20px !important;height:20px !important;border-radius:50% !important;border:1px solid #d0d5dd;top:0;left:0;bottom:0;margin:auto;margin-left:12px}.frm-colorpicker-component .wp-picker-input-wrap input{width:calc(100% - 10px) !important;margin:1px 5px;height:32px;line-height:32px}\n',e.attachInternals(),e}return function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),Object.defineProperty(e,"prototype",{writable:!1}),t&&O(e,t)}(t,e),r=t,(n=[{key:"initView",value:function(){var e=document.createElement("div");return e.classList.add("frm-colorpicker-component","frm-colorpicker"),e.append(this.getInput()),e}},{key:"getInput",value:function(){var e=this;return this.input.type="text",this.input.classList.add("hex"),null!==this.fieldName&&(this.input.name=this.fieldName),null!==V(z,this)&&(this.input.value=V(z,this)),null!==this.componentId&&(this.input.id=this.componentId),this.input.addEventListener("blur",function(t){return V(A,e).call(e,t,null)}),this.input}},{key:"useShadowDom",value:function(){return!1}},{key:"afterViewInit",value:function(){var e=this,t={defaultColor:V(z,this)};"function"==typeof V(A,this)&&(t.change=function(t,r){return setTimeout(function(){return V(A,e).call(e,t,r)},20)}),jQuery(this.input).wpColorPicker(t)}},{key:"color",get:function(){return jQuery(this.input).wpColorPicker("color")},set:function(e){_(z,this,e),this.input.value=e}},{key:"onChange",set:function(e){if("function"!=typeof e)throw new TypeError("Expected a function, but received ".concat(w(e)));_(A,this,e)}}])&&k(r.prototype,n),Object.defineProperty(r,"prototype",{writable:!1}),r;var r,n}(f);T=W,C=!0,(P=I(P="formAssociated"))in T?Object.defineProperty(T,P,{value:C,enumerable:!0,configurable:!0,writable:!0}):T[P]=C;var M=window.frmColorpickerProComponent?window.frmColorpickerProComponent(W):W,D=window.frmGlobal;function R(e){return R="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},R(e)}function B(e,t){for(var r=0;r0&&void 0!==arguments[0]?arguments[0]:[],n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.loadedByWebComponent=r.length>0,this.sliderElements=r.length>0?r:document.querySelectorAll(".frm-slider-component"),this.settings=n,0!==this.sliderElements.length){this.sliderBulletWidth=16,this.sliderMarginRight=5,this.eventsChange=[];var i=frmDom.util.debounce;this.valueChangeDebouncer=i(function(e){return t.triggerValueChange(e)},25),this.initOptions(),this.init()}}return function(e,t,r){return t&&N(e.prototype,t),r&&N(e,r),Object.defineProperty(e,"prototype",{writable:!1}),e}(e,[{key:"initOptions",value:function(){var e=this;this.options=[],this.sliderElements.forEach(function(t,r){var n=t.classList.contains("frm-has-multiple-values")?t.closest(".frm-style-component"):t,i=e.settings.steps||(t.dataset.steps?JSON.parse(t.dataset.steps):null);e.options.push({dragging:!1,startX:0,translateX:0,maxValue:parseInt(t.dataset.maxValue,10),element:t,index:r,value:0,steps:i,dependentUpdater:n.classList.contains("frm-style-dependent-updater-component")?new q(n):null})})}},{key:"init",value:function(){this.initDraggable(),this.loadedByWebComponent?this.initSlidersPositionInsideWebComponent():this.initSlidersPosition()}},{key:"initDraggable",value:function(){var t=this;this.sliderElements.forEach(function(r,n){t.eventsChange[n]=new Event("change",{bubbles:!0,cancelable:!0});var i=r.querySelector(".frm-slider-bullet"),o=r.querySelector('.frm-slider-value input[type="text"]');o.addEventListener("change",function(e){var i=r.querySelector("select").value;t.getMaxValue(i,n)1&&void 0!==arguments[1]?arguments[1]:null;if(!t.classList.contains("frm-disabled")){var n=null!==r?r:this.getSliderIndex(t),i=t.querySelector(".frm-slider").offsetWidth-this.sliderBulletWidth,o=parseInt(t.querySelector('.frm-slider-value input[type="text"]').value,10),a=t.querySelector("select").value,s=this.options[n].steps,l=Math.ceil(o/this.options[n].maxValue*i);"%"===a?l=Math.round(i*o/100):s&&s.length>0&&(l=e.calculateDeltaXFromSteps(o,s,i)),t.querySelector(".frm-slider-active-track").style.width="".concat(l,"px"),this.options[n].translateX=l,this.options[n].value=o+a}}},{key:"initChildSlidersWidth",value:function(e,t,r,n){var i=this;(e.classList.contains("frm-has-independent-fields")||e.classList.contains("frm-has-multiple-values"))&&(e.classList.contains("frm-has-independent-fields")?e.querySelectorAll(".frm-independent-slider-field"):this.getSliderGroupItems(e)).forEach(function(e,o){e.querySelector(".frm-slider-active-track").style.width="".concat(t,"px"),i.options[r+o+1].translateX=t,i.options[r+o+1].value=n})}},{key:"getSliderIndex",value:function(e){return this.options.filter(function(t){return t.element===e})[0].index}},{key:"moveTracker",value:function(t,r){if(this.options[r].dragging){var n=t.clientX-this.options[r].startX,i=this.sliderElements[r],o=i.querySelector(".frm-slider").offsetWidth-this.sliderBulletWidth;n=Math.max(n,0),n=Math.min(n,o);var a=i.querySelector("select").value,s=e.calculateValue(o,n,this.getMaxValue(a,r),this.options[r].steps);i.querySelector('.frm-slider-value input[type="text"]').value=s,i.querySelector(".frm-slider-bullet .frm-slider-value-label").innerText=s,i.querySelector(".frm-slider-active-track").style.width="".concat(n,"px"),this.initChildSlidersWidth(i,n,r,s+a),this.options[r].translateX=n,this.options[r].value=s+a,this.options[r].fullValue=this.updateValue(i,this.options[r].value),this.valueChangeDebouncer(r)}}},{key:"getMaxValue",value:function(e,t){return"%"===e?100:this.options[t].maxValue}},{key:"enableDragging",value:function(e,t){e.target.classList.add("frm-dragging"),this.options[t].dragging=!0,this.options[t].startX=e.clientX-this.options[t].translateX}},{key:"disableDragging",value:function(e){!1!==this.options[e].dragging&&(this.sliderElements[e].querySelector(".frm-slider-bullet").classList.remove("frm-dragging"),this.options[e].dragging=!1,this.triggerValueChange(e))}},{key:"triggerValueChange",value:function(e){var t=this;if(null===this.options[e].dependentUpdater){var r=this.sliderElements[e].classList.contains("frm-has-multiple-values")?this.sliderElements[e].closest(".frm-style-component").querySelector('input[type="hidden"]'):this.sliderElements[e].querySelectorAll('.frm-slider-value input[type="hidden"]');r instanceof NodeList?r.forEach(function(r){r.dispatchEvent(t.eventsChange[e])}):r.dispatchEvent(this.eventsChange[e])}else this.options[e].dependentUpdater.updateAllDependentElements(this.options[e].fullValue)}},{key:"updateValue",value:function(e,t){var r=this;if(e.classList.contains("frm-base-font-size")){var n=document.querySelector('input[name="frm_style_setting[post_content][use_base_font_size]"]');null!==n&&(n.value="true")}if(e.classList.contains("frm-has-multiple-values")){var i=e.closest(".frm-style-component").querySelector('input[type="hidden"]'),o=i.value.split(" "),a=e.dataset.type;switch(o[2]||(o[2]="0px"),o[3]||(o[3]="0px"),a){case"vertical":o[0]=t,o[2]=t;break;case"horizontal":o[1]=t,o[3]=t;break;case"top":o[0]=t;break;case"bottom":o[2]=t;break;case"left":o[3]=t;break;case"right":o[1]=t}var s=o.join(" ");return i.value=s,this.getSliderGroupItems(e).forEach(function(e){var n=r.getUnitMeasureFromValue(t);e.querySelector('.frm-slider-value input[type="text"]').value=parseInt(t,10),e.querySelector("select").value=n}),s}if(e.classList.contains("frm-has-independent-fields")){var l=e.querySelectorAll('.frm-slider-value input[type="hidden"]'),u=e.querySelectorAll('.frm-slider-value input[type="text"]');return l.forEach(function(e,r){e.value=t,u[r+1].value=parseInt(t,10)}),t}return e.querySelector('.frm-slider-value input[type="hidden"]').value=t,t}},{key:"getUnitMeasureFromValue",value:function(e){return["%","px","em"].find(function(t){return e.includes(t)})||""}}],[{key:"maybeDisableUnitDropdown",value:function(e){var t=e.querySelector("select");t&&1>=Array.from(t.options).filter(function(e){return""!==e.value}).length&&(t.classList.add("frm-single-unit"),t.addEventListener("mousedown",function(e){return e.preventDefault()}))}},{key:"calculateDeltaXFromSteps",value:function(t,r,n){var i=r.indexOf(t);if(-1===i){var o=e.snapToStep(t,r),a=r.indexOf(o);return Math.round(a/(r.length-1)*n)}return Math.round(i/(r.length-1)*n)}},{key:"calculateValue",value:function(e,t,r){var n=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null;if(n&&n.length>0){var i=t/e,o=Math.round(i*(n.length-1));return n[Math.max(0,Math.min(o,n.length-1))]}var a=Math.round(t/e*r);return Math.min(a,r)}},{key:"snapToStep",value:function(e,t){for(var r=t[0],n=Math.abs(e-r),i=1;i":5,">=":5,"==":4,"!=":4,"&&":3,"||":2,"?":1,"?:":1},G=["(","?"],$={")":["("],":":["?","?:"]},J=/<=|>=|==|!=|&&|\|\||\?:|\(|!|\*|\/|%|\+|-|<|>|\?|\)|:/;var te={"!":function(e){return!e},"*":function(e,t){return e*t},"/":function(e,t){return e/t},"%":function(e,t){return e%t},"+":function(e,t){return e+t},"-":function(e,t){return e-t},"<":function(e,t){return e":function(e,t){return e>t},">=":function(e,t){return e>=t},"==":function(e,t){return e===t},"!=":function(e,t){return e!==t},"&&":function(e,t){return e&&t},"||":function(e,t){return e||t},"?:":function(e,t,r){if(e)throw t;return r}};var re={contextDelimiter:"",onMissingKey:null};function ne(e,t){var r;for(r in this.data=e,this.pluralForms={},this.options={},re)this.options[r]=void 0!==t&&r in t?t[r]:re[r]}function ie(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function oe(e){for(var t=1;t=0||X[i]3&&void 0!==arguments[3]?arguments[3]:10,a=e[t];if(ue(r)&&le(n))if("function"==typeof i)if("number"==typeof o){var s={callback:i,priority:o,namespace:n};if(a[r]){var l,u=a[r].handlers;for(l=u.length;l>0&&!(o>=u[l-1].priority);l--);l===u.length?u[l]=s:u.splice(l,0,s),a.__current.forEach(function(e){e.name===r&&e.currentIndex>=l&&e.currentIndex++})}else a[r]={handlers:[s],runs:0};"hookAdded"!==r&&e.doAction("hookAdded",r,n,i,o)}else console.error("If specified, the hook priority must be a number.");else console.error("The hook callback must be a function.")}},ce=function(e,t){var r=arguments.length>2&&void 0!==arguments[2]&&arguments[2];return function(n,i){var o=e[t];if(ue(n)&&(r||le(i))){if(!o[n])return 0;var a=0;if(r)a=o[n].handlers.length,o[n]={runs:o[n].runs,handlers:[]};else for(var s=o[n].handlers,l=function(e){s[e].namespace===i&&(s.splice(e,1),a++,o.__current.forEach(function(t){t.name===n&&t.currentIndex>=e&&t.currentIndex--}))},u=s.length-1;u>=0;u--)l(u);return"hookRemoved"!==n&&e.doAction("hookRemoved",n,i),a}}},de=function(e,t){return function(r,n){var i=e[t];return void 0!==n?r in i&&i[r].handlers.some(function(e){return e.namespace===n}):r in i}},fe=function(e,t){var r=arguments.length>2&&void 0!==arguments[2]&&arguments[2];return function(n){var i=e[t];i[n]||(i[n]={handlers:[],runs:0}),i[n].runs++;for(var o=i[n].handlers,a=arguments.length,s=new Array(a>1?a-1:0),l=1;l1&&void 0!==arguments[1]?arguments[1]:"default";n.data[t]=oe(oe(oe({},ae),n.data[t]),e),n.data[t][""]=oe(oe({},ae[""]),n.data[t][""])},s=function(e,t){a(e,t),o()},l=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"default",t=arguments.length>1?arguments[1]:void 0,r=arguments.length>2?arguments[2]:void 0,i=arguments.length>3?arguments[3]:void 0,o=arguments.length>4?arguments[4]:void 0;return n.data[e]||a(void 0,e),n.dcnpgettext(e,t,r,i,o)},u=function(){return arguments.length>0&&void 0!==arguments[0]?arguments[0]:"default"},p=function(e,t,n){var i=l(n,t,e);return r?(i=r.applyFilters("i18n.gettext_with_context",i,e,t,n),r.applyFilters("i18n.gettext_with_context_"+u(n),i,e,t,n)):i};if(r){var c=function(e){se.test(e)&&o()};r.addAction("hookAdded","core/i18n",c),r.addAction("hookRemoved","core/i18n",c)}return{getLocaleData:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"default";return n.data[e]},setLocaleData:s,resetLocaleData:function(e,t){n.data={},n.pluralForms={},s(e,t)},subscribe:function(e){return i.add(e),function(){return i.delete(e)}},__:function(e,t){var n=l(t,void 0,e);return r?(n=r.applyFilters("i18n.gettext",n,e,t),r.applyFilters("i18n.gettext_"+u(t),n,e,t)):n},_x:p,_n:function(e,t,n,i){var o=l(i,void 0,e,t,n);return r?(o=r.applyFilters("i18n.ngettext",o,e,t,n,i),r.applyFilters("i18n.ngettext_"+u(i),o,e,t,n,i)):o},_nx:function(e,t,n,i,o){var a=l(o,i,e,t,n);return r?(a=r.applyFilters("i18n.ngettext_with_context",a,e,t,n,i,o),r.applyFilters("i18n.ngettext_with_context_"+u(o),a,e,t,n,i,o)):a},isRTL:function(){return"rtl"===p("ltr","text direction")},hasTranslation:function(e,t,i){var o,a,s=t?t+""+e:e,l=!(null===(o=n.data)||void 0===o||null===(a=o[null!=i?i:"default"])||void 0===a||!a[s]);return r&&(l=r.applyFilters("i18n.has_translation",l,e,t,i),l=r.applyFilters("i18n.has_translation_"+u(i),l,e,t,i)),l}}}(0,0,ye));ve.getLocaleData.bind(ve),ve.setLocaleData.bind(ve),ve.resetLocaleData.bind(ve),ve.subscribe.bind(ve);var ge=ve.__.bind(ve);function xe(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function we(e){for(var t=1;t2&&void 0!==arguments[2]&&arguments[2],n=document.createElement("option");return n.value=e,n.textContent=t,n.selected=r,n}},{key:"createSvgIcon",value:function(e){var t=document.createElementNS("http://www.w3.org/2000/svg","svg");t.classList.add("frmsvg");var r=document.createElementNS("http://www.w3.org/2000/svg","use");return r.setAttribute("href","#".concat(e)),t.append(r),t}}])}(f),qe={_:0};function Ue(e){return Ue="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},Ue(e)}function Ne(e,t){for(var r=0;r\') no-repeat center center;background-size:20px}.frm-border-radius-component .frm-border-radius-container button.frm-active,.frm-border-radius-component .frm-border-radius-container button:hover{background-color:rgba(0,0,0,.05)}.frm-border-radius-component .frm-border-radius-container .frm-border-individual-inputs-wrapper{width:100%;justify-content:space-between;flex-wrap:wrap}.frm-border-radius-component .frm-border-radius-container .frm-border-individual-inputs-wrapper:not(.frm_hidden){display:flex}.frm-border-radius-component .frm-border-radius-container .frm-border-individual-inputs-wrapper span{position:relative;display:block;overflow:hidden;width:calc(50% - 6px);height:36px;border-radius:var(--small-radius);border:1px solid var(--grey-300);margin-top:12px}.frm-border-radius-component .frm-border-radius-container .frm-border-individual-inputs-wrapper span input{width:100%;height:100%;padding:0;font-size:var(--text-sm);color:#101828;padding:0 12px 0px 20px;box-sizing:border-box;border:none;text-align:right}.frm-border-radius-component .frm-border-radius-container .frm-border-individual-inputs-wrapper span input:focus{outline:none}.frm-border-radius-component .frm-border-radius-container .frm-border-individual-inputs-wrapper span:before{content:"";position:absolute;display:block;width:12px;height:12px;left:12px;top:0;bottom:0;right:auto;margin:auto;background:url(\'data:image/svg+xml,\') center center no-repeat;background-size:12px}.frm-border-radius-component .frm-border-radius-container .frm-border-individual-inputs-wrapper span.frm-border-input-top:before{transform:rotate(180deg)}.frm-border-radius-component .frm-border-radius-container .frm-border-individual-inputs-wrapper span.frm-border-input-bottom:before{transform:rotate(0deg)}.frm-border-radius-component .frm-border-radius-container .frm-border-individual-inputs-wrapper span.frm-border-input-left:before{transform:rotate(90deg)}.frm-border-radius-component .frm-border-radius-container .frm-border-individual-inputs-wrapper span.frm-border-input-right:before{transform:rotate(-90deg)}.frm-border-radius-component .frm-border-radius-container .frm-input-wrapper{width:calc(100% - 36px - 12px);height:36px;display:flex;justify-content:center;box-sizing:border-box;background:#fff;border-radius:var(--small-radius);border:1px solid var(--grey-300);overflow:hidden}.frm-border-radius-component .frm-border-radius-container .frm-input-wrapper>*{border:none}.frm-border-radius-component .frm-border-radius-container .frm-input-wrapper input{width:calc(100% - 44px);height:100%;padding:0;font-size:var(--text-sm);color:#101828;padding-left:12px;box-sizing:border-box}.frm-border-radius-component .frm-border-radius-container .frm-input-wrapper input:focus{outline:none}.frm-border-radius-component .frm-border-radius-container .frm-input-wrapper select{text-align:right;padding:0;font-size:var(--text-sm);color:#667085;width:44px;background:url("../../images/style/small-arrow.svg") no-repeat;background-position:center right 12px}.frm-border-radius-component .frm-border-radius-container .frm-input-wrapper select:focus{outline:none}\n',e}return function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),Object.defineProperty(e,"prototype",{writable:!1}),t&&Et(e,t)}(t,e),function(e,t,r){return t&>(e.prototype,t),r&>(e,r),Object.defineProperty(e,"prototype",{writable:!1}),e}(t,[{key:"initOptions",value:function(){var e;(function(e,t,r){var n=kt(St(e.prototype),"initOptions",r);return"function"==typeof n?function(e){return n.apply(r,e)}:n})(t,0,this)([]),null===this.componentId&&(this.componentId="frm-border-radius-web-component-".concat(zt._=(e=zt._,++e)))}},{key:"initView",value:function(){return this.wrapper=document.createElement("div"),this.container=document.createElement("div"),this.wrapper.classList.add("frm-border-radius-component"),this.container.classList.add("frm-border-radius-container"),this.container.append(this.getInputWrapper(),this.getButton(),this.getBorderIndividualInputsWrapper()),this.wrapper.append(this.container),this.wrapper}},{key:"parseDefaultValues",value:function(){if(!jt(Pt,this))return{top:{value:0,unit:"px"},bottom:{value:0,unit:"px"},left:{value:0,unit:"px"},right:{value:0,unit:"px"}};var e=jt(Pt,this).split(" ");return{top:t.parseValueUnit(e[0]||"0px"),bottom:t.parseValueUnit(e[2]||e[0]||"0px"),left:t.parseValueUnit(e[3]||e[1]||e[0]||"0px"),right:t.parseValueUnit(e[1]||e[0]||"0px")}}},{key:"getInputWrapper",value:function(){return this.inputWrapper=document.createElement("div"),this.inputWrapper.classList.add("frm-input-wrapper"),this.inputWrapper.append(this.getInputValue(),this.getInputUnit(),this.getHiddenInput()),this.inputWrapper}},{key:"getHiddenInput",value:function(){return this.hiddenInput=document.createElement("input"),this.hiddenInput.type="hidden",this.hiddenInput.value=jt(Tt,this),this.fieldName&&(this.hiddenInput.name=this.fieldName),this.hiddenInput}},{key:"getInputValue",value:function(){var e=this;return this.inputValue=document.createElement("input"),this.inputValue.type="text",this.inputValue.id="".concat(this.componentId,"-value"),this.inputValue.setAttribute("aria-label",ge("Border radius value","formidable")),this.inputValue.classList.add("frm-input-value"),jt(Ct,this)||(this.inputValue.value=parseInt(jt(Pt,this))||0),this.inputValue.addEventListener("change",function(){var t=e.inputValue.value+e.inputUnit.value;e.hiddenInput.value=t,e.borderInputBottom.value=e.inputValue.value,e.borderInputTop.value=e.inputValue.value,e.borderInputLeft.value=e.inputValue.value,e.borderInputRight.value=e.inputValue.value,e.updateValue(t)}),this.inputValue}},{key:"getInputUnit",value:function(){var e=this;return this.inputUnit=document.createElement("select"),this.inputUnit.id="".concat(this.componentId,"-unit"),this.inputUnit.setAttribute("aria-label",ge("Border radius unit","formidable")),this.inputUnit.classList.add("frm-input-unit"),jt(Lt,this).forEach(function(t){var r=document.createElement("option");r.value=t,r.textContent=t,e.inputUnit.append(r)}),this.inputUnit.addEventListener("change",function(){e.hiddenInput.value=e.inputValue.value+e.inputUnit.value}),this.inputUnit}},{key:"getBorderIndividualInputsWrapper",value:function(){return this.borderIndividualInputsWrapper=document.createElement("div"),this.borderIndividualInputsWrapper.classList.add("frm-border-individual-inputs-wrapper"),jt(Ct,this)||this.borderIndividualInputsWrapper.classList.add("frm_hidden"),this.borderIndividualInputsWrapper.append(this.getBorderInputTop(),this.getBorderInputRight(),this.getBorderInputLeft(),this.getBorderInputBottom()),this.borderIndividualInputsWrapper}},{key:"getBorderInputTop",value:function(){var e=this,t=this.parseDefaultValues(),r=document.createElement("span");return r.classList.add("frm-border-input-top"),this.borderInputTop=document.createElement("input"),this.borderInputTop.type="text",this.borderInputTop.id="".concat(this.componentId,"-top"),this.borderInputTop.setAttribute("aria-label",ge("Top border radius","formidable")),this.borderInputTop.value=parseInt(t.top.value),r.append(this.borderInputTop),this.borderInputTop.addEventListener("change",function(){return e.buildBorderRadiusIndividualValue()}),r}},{key:"getBorderInputBottom",value:function(){var e=this,t=this.parseDefaultValues(),r=document.createElement("span");return r.classList.add("frm-border-input-bottom"),this.borderInputBottom=document.createElement("input"),this.borderInputBottom.type="text",this.borderInputBottom.id="".concat(this.componentId,"-bottom"),this.borderInputBottom.setAttribute("aria-label",ge("Bottom border radius","formidable")),this.borderInputBottom.value=parseInt(t.bottom.value),r.append(this.borderInputBottom),this.borderInputBottom.addEventListener("change",function(){return e.buildBorderRadiusIndividualValue()}),r}},{key:"getBorderInputLeft",value:function(){var e=this,t=this.parseDefaultValues(),r=document.createElement("span");return r.classList.add("frm-border-input-left"),this.borderInputLeft=document.createElement("input"),this.borderInputLeft.type="text",this.borderInputLeft.id="".concat(this.componentId,"-left"),this.borderInputLeft.setAttribute("aria-label",ge("Left border radius","formidable")),this.borderInputLeft.value=parseInt(t.left.value),r.append(this.borderInputLeft),this.borderInputLeft.addEventListener("change",function(){return e.buildBorderRadiusIndividualValue()}),r}},{key:"getBorderInputRight",value:function(){var e=this,t=this.parseDefaultValues(),r=document.createElement("span");return r.classList.add("frm-border-input-right"),this.borderInputRight=document.createElement("input"),this.borderInputRight.type="text",this.borderInputRight.id="".concat(this.componentId,"-right"),this.borderInputRight.setAttribute("aria-label",ge("Right border radius","formidable")),this.borderInputRight.value=parseInt(t.right.value),r.append(this.borderInputRight),this.borderInputRight.addEventListener("change",function(){return e.buildBorderRadiusIndividualValue()}),r}},{key:"buildBorderRadiusIndividualValue",value:function(){var e=this.inputUnit.value,t="".concat(parseInt(this.borderInputTop.value,10)).concat(e," ").concat(parseInt(this.borderInputRight.value,10)).concat(e," ").concat(parseInt(this.borderInputBottom.value,10)).concat(e," ").concat(parseInt(this.borderInputLeft.value,10)).concat(e);this.updateValue(t)}},{key:"updateValue",value:function(e){this.hiddenInput.value=e,jt(Vt,this).call(this,e)}},{key:"getButton",value:function(){var e=this;return this.button=document.createElement("button"),this.button.type="button",this.button.textContent=ge("Border Radius","formidable"),jt(Ct,this)&&this.button.classList.add("frm-active"),this.button.addEventListener("click",function(){e.button.classList.toggle("frm-active"),e.borderIndividualInputsWrapper.classList.toggle("frm_hidden")}),this.button}},{key:"onChange",set:function(e){if("function"!=typeof e)throw new TypeError("Expected a function, but received ".concat(vt(e)));It(Vt,this,e)}},{key:"borderRadiusDefaultValue",set:function(e){It(Pt,this,e),It(Ct,this,!/^(\d+)(px|em|%)?$/.test(e)&&""!==e)}}],[{key:"parseValueUnit",value:function(e){var t=e.match(/^(\d+)(px|em|%)?$/);return t?{value:parseInt(t[1],10),unit:t[2]||"px"}:{value:0,unit:"px"}}}])}(f),zt={_:0};customElements.define("frm-tab-navigator-component",x),customElements.define("frm-colorpicker-component",M),customElements.define("frm-range-slider-component",Fe),customElements.define("frm-dropdown-component",et),customElements.define("frm-typography-component",bt),customElements.define("frm-border-radius-component",At)})()})(); \ No newline at end of file +(()=>{var e={8616:e=>{e.exports=function(e,t){var r,n,i=0;function o(){var o,a,s=r,l=arguments.length;e:for(;s;){if(s.args.length===arguments.length){for(a=0;a{var n;!function(){"use strict";var i={not_string:/[^s]/,not_bool:/[^t]/,not_type:/[^T]/,not_primitive:/[^v]/,number:/[diefg]/,numeric_arg:/[bcdiefguxX]/,json:/[j]/,not_json:/[^j]/,text:/^[^\x25]+/,modulo:/^\x25{2}/,placeholder:/^\x25(?:([1-9]\d*)\$|\(([^)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-gijostTuvxX])/,key:/^([a-z_][a-z_\d]*)/i,key_access:/^\.([a-z_][a-z_\d]*)/i,index_access:/^\[(\d+)\]/,sign:/^[+-]/};function o(e){return function(e,t){var r,n,a,s,l,u,p,c,d,f=1,m=e.length,h="";for(n=0;n=0),s.type){case"b":r=parseInt(r,10).toString(2);break;case"c":r=String.fromCharCode(parseInt(r,10));break;case"d":case"i":r=parseInt(r,10);break;case"j":r=JSON.stringify(r,null,s.width?parseInt(s.width):0);break;case"e":r=s.precision?parseFloat(r).toExponential(s.precision):parseFloat(r).toExponential();break;case"f":r=s.precision?parseFloat(r).toFixed(s.precision):parseFloat(r);break;case"g":r=s.precision?String(Number(r.toPrecision(s.precision))):parseFloat(r);break;case"o":r=(parseInt(r,10)>>>0).toString(8);break;case"s":r=String(r),r=s.precision?r.substring(0,s.precision):r;break;case"t":r=String(!!r),r=s.precision?r.substring(0,s.precision):r;break;case"T":r=Object.prototype.toString.call(r).slice(8,-1).toLowerCase(),r=s.precision?r.substring(0,s.precision):r;break;case"u":r=parseInt(r,10)>>>0;break;case"v":r=r.valueOf(),r=s.precision?r.substring(0,s.precision):r;break;case"x":r=(parseInt(r,10)>>>0).toString(16);break;case"X":r=(parseInt(r,10)>>>0).toString(16).toUpperCase()}i.json.test(s.type)?h+=r:(!i.number.test(s.type)||c&&!s.sign?d="":(d=c?"+":"-",r=r.toString().replace(i.sign,"")),u=s.pad_char?"0"===s.pad_char?"0":s.pad_char.charAt(1):" ",p=s.width-(d+r).length,l=s.width&&p>0?u.repeat(p):"",h+=s.align?d+r+l:"0"===u?d+l+r:l+d+r)}return h}(function(e){if(s[e])return s[e];for(var t,r=e,n=[],o=0;r;){if(null!==(t=i.text.exec(r)))n.push(t[0]);else if(null!==(t=i.modulo.exec(r)))n.push("%");else{if(null===(t=i.placeholder.exec(r)))throw new SyntaxError("[sprintf] unexpected placeholder");if(t[2]){o|=1;var a=[],l=t[2],u=[];if(null===(u=i.key.exec(l)))throw new SyntaxError("[sprintf] failed to parse named argument key");for(a.push(u[1]);""!==(l=l.substring(u[0].length));)if(null!==(u=i.key_access.exec(l)))a.push(u[1]);else{if(null===(u=i.index_access.exec(l)))throw new SyntaxError("[sprintf] failed to parse named argument key");a.push(u[1])}t[2]=a}else o|=2;if(3===o)throw new Error("[sprintf] mixing positional and named placeholders is not (yet) supported");n.push({placeholder:t[0],param_no:t[1],keys:t[2],sign:t[3],pad_char:t[4],align:t[5],width:t[6],precision:t[7],type:t[8]})}r=r.substring(t[0].length)}return s[e]=n}(e),arguments)}function a(e,t){return o.apply(null,[e].concat(t||[]))}var s=Object.create(null);"undefined"!=typeof window&&(window.sprintf=o,window.vsprintf=a,void 0===(n=function(){return{sprintf:o,vsprintf:a}}.call(t,r,t,e))||(e.exports=n))}()}},t={};function r(n){var i=t[n];if(void 0!==i)return i.exports;var o=t[n]={exports:{}};return e[n](o,o.exports,r),o.exports}r.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return r.d(t,{a:t}),t},r.d=(e,t)=>{for(var n in t)r.o(t,n)&&!r.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{"use strict";function e(t){return e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},e(t)}function t(e,t){for(var r=0;r li"),this.slideTrackLine=this.wrapper.querySelector(".frm-tabs-active-underline"),this.slideTrack=this.wrapper.querySelector(".frm-tabs-slide-track"),this.slides=this.wrapper.querySelectorAll(".frm-tabs-slide-track > div"),this.isRTL="rtl"===document.documentElement.dir||"rtl"===document.body.dir,this.resizeObserver=null,this.init()))},(r=[{key:"init",value:function(){var e=this;null!==this.wrapper&&this.navs.length&&null!==this.slideTrackLine&&null!==this.slideTrack&&this.slides.length&&(this.initDefaultSlideTrackerWidth(),this.navs.forEach(function(t,r){t.addEventListener("click",function(t){return e.onNavClick(t,r)})}),this.setupScrollbarObserver(),window.addEventListener("beforeunload",this.cleanupObservers))}},{key:"onNavClick",value:function(e,t){var r=e.currentTarget;e.preventDefault(),this.removeActiveClassnameFromNavs(),r.classList.add("frm-active"),this.initSlideTrackUnderline(r,t),this.changeSlide(t);var n,i,o=r.querySelector("a");o&&"frm_insert_fields_tab"===o.id&&!o.closest("#frm_adv_info")&&(null===(n=window.frmAdminBuild)||void 0===n||null===(i=n.clearSettingsBox)||void 0===i||i.call(n))}},{key:"initDefaultSlideTrackerWidth",value:function(){this.slideTrackLine.dataset.initialWidth&&(this.slideTrackLine.style.width="".concat(this.slideTrackLine.dataset.initialWidth,"px"))}},{key:"initSlideTrackUnderline",value:function(e,t){this.slideTrackLine.classList.remove("frm-first","frm-last");var r=void 0!==e?e:this.navs.filter(function(e){return e.classList.contains("frm-active")});this.positionUnderlineIndicator(r)}},{key:"setupScrollbarObserver",value:function(){var e=this,t=this.wrapper.closest(".frm-scrollbar-wrapper");t&&"ResizeObserver"in window&&(this.resizeObserver=new ResizeObserver(function(){var t=e.wrapper.querySelector(".frm-tabs-navs ul > li.frm-active");t&&e.positionUnderlineIndicator(t)}),this.resizeObserver.observe(t))}},{key:"cleanupObservers",value:function(){this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null)}},{key:"positionUnderlineIndicator",value:function(e){var t=this;requestAnimationFrame(function(){var r=t.isRTL?-(e.parentElement.offsetWidth-e.offsetLeft-e.offsetWidth):e.offsetLeft;t.slideTrackLine.style.transform="translateX(".concat(r,"px)"),t.slideTrackLine.style.width=e.clientWidth+"px"})}},{key:"changeSlide",value:function(e){this.removeActiveClassnameFromSlides();var t=0==e?"0px":"calc( ( ".concat(100*e,"% + ").concat(parseInt(this.flexboxSlidesGap,10)*e,"px ) * ").concat(this.isRTL?1:-1," )");"0px"!==t?this.slideTrack.style.transform="translateX(".concat(t,")"):this.slideTrack.style.removeProperty("transform"),e in this.slides&&this.slides[e].classList.add("frm-active")}},{key:"removeActiveClassnameFromSlides",value:function(){this.slides.forEach(function(e){return e.classList.remove("frm-active")})}},{key:"removeActiveClassnameFromNavs",value:function(){this.navs.forEach(function(e){return e.classList.remove("frm-active")})}}])&&t(e.prototype,r),Object.defineProperty(e,"prototype",{writable:!1}),e;var e,r}();function o(e){return o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},o(e)}function a(e,t){(null==t||t>e.length)&&(t=e.length);for(var r=0,n=Array(t);r0&&(r.disconnect(),requestAnimationFrame(function(){return t()}))})},{threshold:.1});(e.useShadowDom()?e.shadowRoot.host:e)&&r.observe(e)}else requestAnimationFrame(function(){return t()})})}},{key:"frmLabel",set:function(e){this._labelText=e}},{key:"afterViewInit",value:function(e){}},{key:"initView",value:function(){}},{key:"connectedCallback",value:function(){this.initOptions(),this.render()}},{key:"disconnectedCallback",value:function(){}}])&&s(r.prototype,n),Object.defineProperty(r,"prototype",{writable:!1}),r;var r,n}(u(HTMLElement));function m(e){return m="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},m(e)}function h(e,t){for(var r=0;rspan.frm-component-label{font-weight:500;font-size:var(--text-sm) !important;color:var(--grey-900) !important;width:40% !important;display:block !important;margin-right:12px !important}.frm-colorpicker-component .wp-picker-container button[type=button]{position:relative;height:36px !important;background-image:none !important;background-color:#fff !important;overflow:hidden}.frm-colorpicker-component .wp-picker-container button[type=button]:after{content:"";width:20px;height:20px;display:block;position:absolute;top:0;right:8px;bottom:0;margin:auto;background:url("--frm-plugin-url/images/style/small-arrow.svg") no-repeat;background-position:center;z-index:10}.frm-colorpicker-component .wp-picker-container button[type=button]:focus{border-color:var(--primary-500) !important}.frm-colorpicker-component .wp-color-result-text{line-height:36px !important;padding:0 12px;border:0}.frm-colorpicker-component .color-alpha{width:20px !important;height:20px !important;border-radius:50% !important;border:1px solid #d0d5dd;top:0;left:0;bottom:0;margin:auto;margin-left:12px}.frm-colorpicker-component .wp-picker-input-wrap input{width:calc(100% - 10px) !important;margin:1px 5px;height:32px;line-height:32px}\n',e.attachInternals(),e}return function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),Object.defineProperty(e,"prototype",{writable:!1}),t&&O(e,t)}(t,e),r=t,(n=[{key:"initView",value:function(){var e=document.createElement("div");return e.classList.add("frm-colorpicker-component","frm-colorpicker"),e.append(this.getInput()),e}},{key:"getInput",value:function(){var e=this;return this.input.type="text",this.input.classList.add("hex"),null!==this.fieldName&&(this.input.name=this.fieldName),null!==V(z,this)&&(this.input.value=V(z,this)),null!==this.componentId&&(this.input.id=this.componentId),this.input.addEventListener("blur",function(t){return V(A,e).call(e,t,null)}),this.input}},{key:"useShadowDom",value:function(){return!1}},{key:"afterViewInit",value:function(){var e=this,t={defaultColor:V(z,this)};"function"==typeof V(A,this)&&(t.change=function(t,r){return setTimeout(function(){return V(A,e).call(e,t,r)},20)}),jQuery(this.input).wpColorPicker(t)}},{key:"color",get:function(){return jQuery(this.input).wpColorPicker("color")},set:function(e){_(z,this,e),this.input.value=e}},{key:"onChange",set:function(e){if("function"!=typeof e)throw new TypeError("Expected a function, but received ".concat(w(e)));_(A,this,e)}}])&&k(r.prototype,n),Object.defineProperty(r,"prototype",{writable:!1}),r;var r,n}(f);T=W,C=!0,(P=I(P="formAssociated"))in T?Object.defineProperty(T,P,{value:C,enumerable:!0,configurable:!0,writable:!0}):T[P]=C;var M=window.frmColorpickerProComponent?window.frmColorpickerProComponent(W):W,D=window.frmGlobal;function R(e){return R="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},R(e)}function B(e,t){for(var r=0;r0&&void 0!==arguments[0]?arguments[0]:[],n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.loadedByWebComponent=r.length>0,this.sliderElements=r.length>0?r:document.querySelectorAll(".frm-slider-component"),this.settings=n,0!==this.sliderElements.length){this.sliderBulletWidth=16,this.sliderMarginRight=5,this.eventsChange=[];var i=frmDom.util.debounce;this.valueChangeDebouncer=i(function(e){return t.triggerValueChange(e)},25),this.initOptions(),this.init()}}return function(e,t,r){return t&&N(e.prototype,t),r&&N(e,r),Object.defineProperty(e,"prototype",{writable:!1}),e}(e,[{key:"initOptions",value:function(){var e=this;this.options=[],this.sliderElements.forEach(function(t,r){var n=t.classList.contains("frm-has-multiple-values")?t.closest(".frm-style-component"):t,i=e.settings.steps||(t.dataset.steps?JSON.parse(t.dataset.steps):null);e.options.push({dragging:!1,startX:0,translateX:0,maxValue:parseInt(t.dataset.maxValue,10),element:t,index:r,value:0,steps:i,dependentUpdater:n.classList.contains("frm-style-dependent-updater-component")?new q(n):null})})}},{key:"init",value:function(){this.initDraggable(),this.loadedByWebComponent?this.initSlidersPositionInsideWebComponent():this.initSlidersPosition()}},{key:"initDraggable",value:function(){var t=this;this.sliderElements.forEach(function(r,n){t.eventsChange[n]=new Event("change",{bubbles:!0,cancelable:!0});var i=r.querySelector(".frm-slider-bullet"),o=r.querySelector('.frm-slider-value input[type="text"]');o.addEventListener("change",function(e){var i=r.querySelector("select").value;t.getMaxValue(i,n)1&&void 0!==arguments[1]?arguments[1]:null;if(!t.classList.contains("frm-disabled")){var n=null!==r?r:this.getSliderIndex(t),i=t.querySelector(".frm-slider").offsetWidth-this.sliderBulletWidth,o=parseInt(t.querySelector('.frm-slider-value input[type="text"]').value,10),a=t.querySelector("select").value,s=this.options[n].steps,l=Math.ceil(o/this.options[n].maxValue*i);"%"===a?l=Math.round(i*o/100):s&&s.length>0&&(l=e.calculateDeltaXFromSteps(o,s,i)),t.querySelector(".frm-slider-active-track").style.width="".concat(l,"px"),this.options[n].translateX=l,this.options[n].value=o+a}}},{key:"initChildSlidersWidth",value:function(e,t,r,n){var i=this;(e.classList.contains("frm-has-independent-fields")||e.classList.contains("frm-has-multiple-values"))&&(e.classList.contains("frm-has-independent-fields")?e.querySelectorAll(".frm-independent-slider-field"):this.getSliderGroupItems(e)).forEach(function(e,o){e.querySelector(".frm-slider-active-track").style.width="".concat(t,"px"),i.options[r+o+1].translateX=t,i.options[r+o+1].value=n})}},{key:"getSliderIndex",value:function(e){return this.options.filter(function(t){return t.element===e})[0].index}},{key:"moveTracker",value:function(t,r){if(this.options[r].dragging){var n=t.clientX-this.options[r].startX,i=this.sliderElements[r],o=i.querySelector(".frm-slider").offsetWidth-this.sliderBulletWidth;n=Math.max(n,0),n=Math.min(n,o);var a=i.querySelector("select").value,s=e.calculateValue(o,n,this.getMaxValue(a,r),this.options[r].steps);i.querySelector('.frm-slider-value input[type="text"]').value=s,i.querySelector(".frm-slider-bullet .frm-slider-value-label").innerText=s,i.querySelector(".frm-slider-active-track").style.width="".concat(n,"px"),this.initChildSlidersWidth(i,n,r,s+a),this.options[r].translateX=n,this.options[r].value=s+a,this.options[r].fullValue=this.updateValue(i,this.options[r].value),this.valueChangeDebouncer(r)}}},{key:"getMaxValue",value:function(e,t){return"%"===e?100:this.options[t].maxValue}},{key:"enableDragging",value:function(e,t){e.target.classList.add("frm-dragging"),this.options[t].dragging=!0,this.options[t].startX=e.clientX-this.options[t].translateX}},{key:"disableDragging",value:function(e){!1!==this.options[e].dragging&&(this.sliderElements[e].querySelector(".frm-slider-bullet").classList.remove("frm-dragging"),this.options[e].dragging=!1,this.triggerValueChange(e))}},{key:"triggerValueChange",value:function(e){var t=this;if(null===this.options[e].dependentUpdater){var r=this.sliderElements[e].classList.contains("frm-has-multiple-values")?this.sliderElements[e].closest(".frm-style-component").querySelector('input[type="hidden"]'):this.sliderElements[e].querySelectorAll('.frm-slider-value input[type="hidden"]');r instanceof NodeList?r.forEach(function(r){r.dispatchEvent(t.eventsChange[e])}):r.dispatchEvent(this.eventsChange[e])}else this.options[e].dependentUpdater.updateAllDependentElements(this.options[e].fullValue)}},{key:"updateValue",value:function(e,t){var r=this;if(e.classList.contains("frm-base-font-size")){var n=document.querySelector('input[name="frm_style_setting[post_content][use_base_font_size]"]');null!==n&&(n.value="true")}if(e.classList.contains("frm-has-multiple-values")){var i=e.closest(".frm-style-component").querySelector('input[type="hidden"]'),o=i.value.split(" "),a=e.dataset.type;switch(o[2]||(o[2]="0px"),o[3]||(o[3]="0px"),a){case"vertical":o[0]=t,o[2]=t;break;case"horizontal":o[1]=t,o[3]=t;break;case"top":o[0]=t;break;case"bottom":o[2]=t;break;case"left":o[3]=t;break;case"right":o[1]=t}var s=o.join(" ");return i.value=s,this.getSliderGroupItems(e).forEach(function(e){var n=r.getUnitMeasureFromValue(t);e.querySelector('.frm-slider-value input[type="text"]').value=parseInt(t,10),e.querySelector("select").value=n}),s}if(e.classList.contains("frm-has-independent-fields")){var l=e.querySelectorAll('.frm-slider-value input[type="hidden"]'),u=e.querySelectorAll('.frm-slider-value input[type="text"]');return l.forEach(function(e,r){e.value=t,u[r+1].value=parseInt(t,10)}),t}return e.querySelector('.frm-slider-value input[type="hidden"]').value=t,t}},{key:"getUnitMeasureFromValue",value:function(e){return["%","px","em"].find(function(t){return e.includes(t)})||""}}],[{key:"maybeDisableUnitDropdown",value:function(e){var t=e.querySelector("select");t&&1>=Array.from(t.options).filter(function(e){return""!==e.value}).length&&(t.classList.add("frm-single-unit"),t.addEventListener("mousedown",function(e){return e.preventDefault()}))}},{key:"calculateDeltaXFromSteps",value:function(t,r,n){var i=r.indexOf(t);if(-1===i){var o=e.snapToStep(t,r),a=r.indexOf(o);return Math.round(a/(r.length-1)*n)}return Math.round(i/(r.length-1)*n)}},{key:"calculateValue",value:function(e,t,r){var n=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null;if(n&&n.length>0){var i=t/e,o=Math.round(i*(n.length-1));return n[Math.max(0,Math.min(o,n.length-1))]}var a=Math.round(t/e*r);return Math.min(a,r)}},{key:"snapToStep",value:function(e,t){for(var r=t[0],n=Math.abs(e-r),i=1;i":5,">=":5,"==":4,"!=":4,"&&":3,"||":2,"?":1,"?:":1},G=["(","?"],$={")":["("],":":["?","?:"]},J=/<=|>=|==|!=|&&|\|\||\?:|\(|!|\*|\/|%|\+|-|<|>|\?|\)|:/;var te={"!":function(e){return!e},"*":function(e,t){return e*t},"/":function(e,t){return e/t},"%":function(e,t){return e%t},"+":function(e,t){return e+t},"-":function(e,t){return e-t},"<":function(e,t){return e":function(e,t){return e>t},">=":function(e,t){return e>=t},"==":function(e,t){return e===t},"!=":function(e,t){return e!==t},"&&":function(e,t){return e&&t},"||":function(e,t){return e||t},"?:":function(e,t,r){if(e)throw t;return r}};var re={contextDelimiter:"",onMissingKey:null};function ne(e,t){var r;for(r in this.data=e,this.pluralForms={},this.options={},re)this.options[r]=void 0!==t&&r in t?t[r]:re[r]}function ie(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function oe(e){for(var t=1;t=0||X[i]3&&void 0!==arguments[3]?arguments[3]:10,a=e[t];if(ue(r)&&le(n))if("function"==typeof i)if("number"==typeof o){var s={callback:i,priority:o,namespace:n};if(a[r]){var l,u=a[r].handlers;for(l=u.length;l>0&&!(o>=u[l-1].priority);l--);l===u.length?u[l]=s:u.splice(l,0,s),a.__current.forEach(function(e){e.name===r&&e.currentIndex>=l&&e.currentIndex++})}else a[r]={handlers:[s],runs:0};"hookAdded"!==r&&e.doAction("hookAdded",r,n,i,o)}else console.error("If specified, the hook priority must be a number.");else console.error("The hook callback must be a function.")}},ce=function(e,t){var r=arguments.length>2&&void 0!==arguments[2]&&arguments[2];return function(n,i){var o=e[t];if(ue(n)&&(r||le(i))){if(!o[n])return 0;var a=0;if(r)a=o[n].handlers.length,o[n]={runs:o[n].runs,handlers:[]};else for(var s=o[n].handlers,l=function(e){s[e].namespace===i&&(s.splice(e,1),a++,o.__current.forEach(function(t){t.name===n&&t.currentIndex>=e&&t.currentIndex--}))},u=s.length-1;u>=0;u--)l(u);return"hookRemoved"!==n&&e.doAction("hookRemoved",n,i),a}}},de=function(e,t){return function(r,n){var i=e[t];return void 0!==n?r in i&&i[r].handlers.some(function(e){return e.namespace===n}):r in i}},fe=function(e,t){var r=arguments.length>2&&void 0!==arguments[2]&&arguments[2];return function(n){var i=e[t];i[n]||(i[n]={handlers:[],runs:0}),i[n].runs++;for(var o=i[n].handlers,a=arguments.length,s=new Array(a>1?a-1:0),l=1;l1&&void 0!==arguments[1]?arguments[1]:"default";n.data[t]=oe(oe(oe({},ae),n.data[t]),e),n.data[t][""]=oe(oe({},ae[""]),n.data[t][""])},s=function(e,t){a(e,t),o()},l=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"default",t=arguments.length>1?arguments[1]:void 0,r=arguments.length>2?arguments[2]:void 0,i=arguments.length>3?arguments[3]:void 0,o=arguments.length>4?arguments[4]:void 0;return n.data[e]||a(void 0,e),n.dcnpgettext(e,t,r,i,o)},u=function(){return arguments.length>0&&void 0!==arguments[0]?arguments[0]:"default"},p=function(e,t,n){var i=l(n,t,e);return r?(i=r.applyFilters("i18n.gettext_with_context",i,e,t,n),r.applyFilters("i18n.gettext_with_context_"+u(n),i,e,t,n)):i};if(r){var c=function(e){se.test(e)&&o()};r.addAction("hookAdded","core/i18n",c),r.addAction("hookRemoved","core/i18n",c)}return{getLocaleData:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"default";return n.data[e]},setLocaleData:s,resetLocaleData:function(e,t){n.data={},n.pluralForms={},s(e,t)},subscribe:function(e){return i.add(e),function(){return i.delete(e)}},__:function(e,t){var n=l(t,void 0,e);return r?(n=r.applyFilters("i18n.gettext",n,e,t),r.applyFilters("i18n.gettext_"+u(t),n,e,t)):n},_x:p,_n:function(e,t,n,i){var o=l(i,void 0,e,t,n);return r?(o=r.applyFilters("i18n.ngettext",o,e,t,n,i),r.applyFilters("i18n.ngettext_"+u(i),o,e,t,n,i)):o},_nx:function(e,t,n,i,o){var a=l(o,i,e,t,n);return r?(a=r.applyFilters("i18n.ngettext_with_context",a,e,t,n,i,o),r.applyFilters("i18n.ngettext_with_context_"+u(o),a,e,t,n,i,o)):a},isRTL:function(){return"rtl"===p("ltr","text direction")},hasTranslation:function(e,t,i){var o,a,s=t?t+""+e:e,l=!(null===(o=n.data)||void 0===o||null===(a=o[null!=i?i:"default"])||void 0===a||!a[s]);return r&&(l=r.applyFilters("i18n.has_translation",l,e,t,i),l=r.applyFilters("i18n.has_translation_"+u(i),l,e,t,i)),l}}}(0,0,ye));ve.getLocaleData.bind(ve),ve.setLocaleData.bind(ve),ve.resetLocaleData.bind(ve),ve.subscribe.bind(ve);var ge=ve.__.bind(ve);function xe(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function we(e){for(var t=1;t2&&void 0!==arguments[2]&&arguments[2],n=document.createElement("option");return n.value=e,n.textContent=t,n.selected=r,n}},{key:"createSvgIcon",value:function(e){var t=document.createElementNS("http://www.w3.org/2000/svg","svg");t.classList.add("frmsvg");var r=document.createElementNS("http://www.w3.org/2000/svg","use");return r.setAttribute("href","#".concat(e)),t.append(r),t}}])}(f),qe={_:0};function Ue(e){return Ue="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},Ue(e)}function Ne(e,t){for(var r=0;r\') no-repeat center center;background-size:20px}.frm-border-radius-component .frm-border-radius-container button.frm-active,.frm-border-radius-component .frm-border-radius-container button:hover{background-color:rgba(0,0,0,.05)}.frm-border-radius-component .frm-border-radius-container .frm-border-individual-inputs-wrapper{width:100%;justify-content:space-between;flex-wrap:wrap}.frm-border-radius-component .frm-border-radius-container .frm-border-individual-inputs-wrapper:not(.frm_hidden){display:flex}.frm-border-radius-component .frm-border-radius-container .frm-border-individual-inputs-wrapper span{position:relative;display:block;overflow:hidden;width:calc(50% - 6px);height:36px;border-radius:var(--small-radius);border:1px solid var(--grey-300);margin-top:12px}.frm-border-radius-component .frm-border-radius-container .frm-border-individual-inputs-wrapper span input{width:100%;height:100%;padding:0;font-size:var(--text-sm);color:#101828;padding:0 12px 0px 20px;box-sizing:border-box;border:none;text-align:right}.frm-border-radius-component .frm-border-radius-container .frm-border-individual-inputs-wrapper span input:focus{outline:none}.frm-border-radius-component .frm-border-radius-container .frm-border-individual-inputs-wrapper span:before{content:"";position:absolute;display:block;width:12px;height:12px;left:12px;top:0;bottom:0;right:auto;margin:auto;background:url(\'data:image/svg+xml,\') center center no-repeat;background-size:12px}.frm-border-radius-component .frm-border-radius-container .frm-border-individual-inputs-wrapper span.frm-border-input-top:before{transform:rotate(180deg)}.frm-border-radius-component .frm-border-radius-container .frm-border-individual-inputs-wrapper span.frm-border-input-bottom:before{transform:rotate(0deg)}.frm-border-radius-component .frm-border-radius-container .frm-border-individual-inputs-wrapper span.frm-border-input-left:before{transform:rotate(90deg)}.frm-border-radius-component .frm-border-radius-container .frm-border-individual-inputs-wrapper span.frm-border-input-right:before{transform:rotate(-90deg)}.frm-border-radius-component .frm-border-radius-container .frm-input-wrapper{width:calc(100% - 36px - 12px);height:36px;display:flex;justify-content:center;box-sizing:border-box;background:#fff;border-radius:var(--small-radius);border:1px solid var(--grey-300);overflow:hidden}.frm-border-radius-component .frm-border-radius-container .frm-input-wrapper>*{border:none}.frm-border-radius-component .frm-border-radius-container .frm-input-wrapper input{width:calc(100% - 44px);height:100%;padding:0;font-size:var(--text-sm);color:#101828;padding-left:12px;box-sizing:border-box}.frm-border-radius-component .frm-border-radius-container .frm-input-wrapper input:focus{outline:none}.frm-border-radius-component .frm-border-radius-container .frm-input-wrapper select{text-align:right;padding:0;font-size:var(--text-sm);color:#667085;width:44px;background:url("../../images/style/small-arrow.svg") no-repeat;background-position:center right 12px}.frm-border-radius-component .frm-border-radius-container .frm-input-wrapper select:focus{outline:none}\n',e}return function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),Object.defineProperty(e,"prototype",{writable:!1}),t&&Et(e,t)}(t,e),function(e,t,r){return t&>(e.prototype,t),r&>(e,r),Object.defineProperty(e,"prototype",{writable:!1}),e}(t,[{key:"initOptions",value:function(){var e;(function(e,t,r){var n=kt(St(e.prototype),"initOptions",r);return"function"==typeof n?function(e){return n.apply(r,e)}:n})(t,0,this)([]),null===this.componentId&&(this.componentId="frm-border-radius-web-component-".concat(zt._=(e=zt._,++e)))}},{key:"initView",value:function(){return this.wrapper=document.createElement("div"),this.container=document.createElement("div"),this.wrapper.classList.add("frm-border-radius-component"),this.container.classList.add("frm-border-radius-container"),this.container.append(this.getInputWrapper(),this.getButton(),this.getBorderIndividualInputsWrapper()),this.wrapper.append(this.container),this.wrapper}},{key:"parseDefaultValues",value:function(){if(!jt(Pt,this))return{top:{value:0,unit:"px"},bottom:{value:0,unit:"px"},left:{value:0,unit:"px"},right:{value:0,unit:"px"}};var e=jt(Pt,this).split(" ");return{top:t.parseValueUnit(e[0]||"0px"),bottom:t.parseValueUnit(e[2]||e[0]||"0px"),left:t.parseValueUnit(e[3]||e[1]||e[0]||"0px"),right:t.parseValueUnit(e[1]||e[0]||"0px")}}},{key:"getInputWrapper",value:function(){return this.inputWrapper=document.createElement("div"),this.inputWrapper.classList.add("frm-input-wrapper"),this.inputWrapper.append(this.getInputValue(),this.getInputUnit(),this.getHiddenInput()),this.inputWrapper}},{key:"getHiddenInput",value:function(){return this.hiddenInput=document.createElement("input"),this.hiddenInput.type="hidden",this.hiddenInput.value=jt(Tt,this),this.fieldName&&(this.hiddenInput.name=this.fieldName),this.hiddenInput}},{key:"getInputValue",value:function(){var e=this;return this.inputValue=document.createElement("input"),this.inputValue.type="text",this.inputValue.id="".concat(this.componentId,"-value"),this.inputValue.setAttribute("aria-label",ge("Border radius value","formidable")),this.inputValue.classList.add("frm-input-value"),jt(Ct,this)||(this.inputValue.value=parseInt(jt(Pt,this))||0),this.inputValue.addEventListener("change",function(){var t=e.inputValue.value+e.inputUnit.value;e.hiddenInput.value=t,e.borderInputBottom.value=e.inputValue.value,e.borderInputTop.value=e.inputValue.value,e.borderInputLeft.value=e.inputValue.value,e.borderInputRight.value=e.inputValue.value,e.updateValue(t)}),this.inputValue}},{key:"getInputUnit",value:function(){var e=this;return this.inputUnit=document.createElement("select"),this.inputUnit.id="".concat(this.componentId,"-unit"),this.inputUnit.setAttribute("aria-label",ge("Border radius unit","formidable")),this.inputUnit.classList.add("frm-input-unit"),jt(Lt,this).forEach(function(t){var r=document.createElement("option");r.value=t,r.textContent=t,e.inputUnit.append(r)}),this.inputUnit.addEventListener("change",function(){e.hiddenInput.value=e.inputValue.value+e.inputUnit.value}),this.inputUnit}},{key:"getBorderIndividualInputsWrapper",value:function(){return this.borderIndividualInputsWrapper=document.createElement("div"),this.borderIndividualInputsWrapper.classList.add("frm-border-individual-inputs-wrapper"),jt(Ct,this)||this.borderIndividualInputsWrapper.classList.add("frm_hidden"),this.borderIndividualInputsWrapper.append(this.getBorderInputTop(),this.getBorderInputRight(),this.getBorderInputLeft(),this.getBorderInputBottom()),this.borderIndividualInputsWrapper}},{key:"getBorderInputTop",value:function(){var e=this,t=this.parseDefaultValues(),r=document.createElement("span");return r.classList.add("frm-border-input-top"),this.borderInputTop=document.createElement("input"),this.borderInputTop.type="text",this.borderInputTop.id="".concat(this.componentId,"-top"),this.borderInputTop.setAttribute("aria-label",ge("Top border radius","formidable")),this.borderInputTop.value=parseInt(t.top.value),r.append(this.borderInputTop),this.borderInputTop.addEventListener("change",function(){return e.buildBorderRadiusIndividualValue()}),r}},{key:"getBorderInputBottom",value:function(){var e=this,t=this.parseDefaultValues(),r=document.createElement("span");return r.classList.add("frm-border-input-bottom"),this.borderInputBottom=document.createElement("input"),this.borderInputBottom.type="text",this.borderInputBottom.id="".concat(this.componentId,"-bottom"),this.borderInputBottom.setAttribute("aria-label",ge("Bottom border radius","formidable")),this.borderInputBottom.value=parseInt(t.bottom.value),r.append(this.borderInputBottom),this.borderInputBottom.addEventListener("change",function(){return e.buildBorderRadiusIndividualValue()}),r}},{key:"getBorderInputLeft",value:function(){var e=this,t=this.parseDefaultValues(),r=document.createElement("span");return r.classList.add("frm-border-input-left"),this.borderInputLeft=document.createElement("input"),this.borderInputLeft.type="text",this.borderInputLeft.id="".concat(this.componentId,"-left"),this.borderInputLeft.setAttribute("aria-label",ge("Left border radius","formidable")),this.borderInputLeft.value=parseInt(t.left.value),r.append(this.borderInputLeft),this.borderInputLeft.addEventListener("change",function(){return e.buildBorderRadiusIndividualValue()}),r}},{key:"getBorderInputRight",value:function(){var e=this,t=this.parseDefaultValues(),r=document.createElement("span");return r.classList.add("frm-border-input-right"),this.borderInputRight=document.createElement("input"),this.borderInputRight.type="text",this.borderInputRight.id="".concat(this.componentId,"-right"),this.borderInputRight.setAttribute("aria-label",ge("Right border radius","formidable")),this.borderInputRight.value=parseInt(t.right.value),r.append(this.borderInputRight),this.borderInputRight.addEventListener("change",function(){return e.buildBorderRadiusIndividualValue()}),r}},{key:"buildBorderRadiusIndividualValue",value:function(){var e=this.inputUnit.value,t="".concat(parseInt(this.borderInputTop.value,10)).concat(e," ").concat(parseInt(this.borderInputRight.value,10)).concat(e," ").concat(parseInt(this.borderInputBottom.value,10)).concat(e," ").concat(parseInt(this.borderInputLeft.value,10)).concat(e);this.updateValue(t)}},{key:"updateValue",value:function(e){this.hiddenInput.value=e,jt(Vt,this).call(this,e)}},{key:"getButton",value:function(){var e=this;return this.button=document.createElement("button"),this.button.type="button",this.button.textContent=ge("Border Radius","formidable"),jt(Ct,this)&&this.button.classList.add("frm-active"),this.button.addEventListener("click",function(){e.button.classList.toggle("frm-active"),e.borderIndividualInputsWrapper.classList.toggle("frm_hidden")}),this.button}},{key:"onChange",set:function(e){if("function"!=typeof e)throw new TypeError("Expected a function, but received ".concat(vt(e)));It(Vt,this,e)}},{key:"borderRadiusDefaultValue",set:function(e){It(Pt,this,e),It(Ct,this,!/^(\d+)(px|em|%)?$/.test(e)&&""!==e)}}],[{key:"parseValueUnit",value:function(e){var t=e.match(/^(\d+)(px|em|%)?$/);return t?{value:parseInt(t[1],10),unit:t[2]||"px"}:{value:0,unit:"px"}}}])}(f),zt={_:0};customElements.define("frm-tab-navigator-component",x),customElements.define("frm-colorpicker-component",M),customElements.define("frm-range-slider-component",Fe),customElements.define("frm-dropdown-component",et),customElements.define("frm-typography-component",bt),customElements.define("frm-border-radius-component",At)})()})(); \ No newline at end of file diff --git a/js/formidable.min.js b/js/formidable.min.js index d37b1b2f92..96d1e30596 100644 --- a/js/formidable.min.js +++ b/js/formidable.min.js @@ -4,8 +4,8 @@ nameParts[3].replace("[","");else fieldId=nameParts[1].replace("[","");if(fullID true)}function enableSubmitButton(form){form.querySelectorAll('input[type="submit"], input[type="button"], button[type="submit"]').forEach(button=>button.disabled=false)}function disableSaveDraft($form){const form=$form instanceof jQuery?$form.get(0):$form;if(!form)return;form.querySelectorAll("a.frm_save_draft").forEach(link=>link.style.pointerEvents="none")}function enableSaveDraft($form){const form=$form instanceof jQuery?$form.get(0):$form;if(!form)return;form.querySelectorAll(".frm_save_draft").forEach(saveDraftButton=> {saveDraftButton.disabled=false;saveDraftButton.style.pointerEvents=""})}function validateForm(object){let errors=[];const vanillaJsObject="function"===typeof object.get?object.get(0):object;vanillaJsObject?.querySelectorAll(".frm_required_field").forEach(requiredField=>{const isVisible=requiredField.offsetParent!==null;if(!isVisible)return;requiredField.querySelectorAll("input, select, textarea").forEach(requiredInput=>{if(hasClass(requiredInput,"frm_optional")||hasClass(requiredInput,"ed_button"))return; errors=checkRequiredField(requiredInput,errors)})});vanillaJsObject?.querySelectorAll("input,select,textarea").forEach(field=>{if(""===field.value){if("number"===field.type)checkValidity(field,errors);const isConfirmationField=field.name&&0===field.name.indexOf("item_meta[conf_");if(!isConfirmationField)return}validateFieldValue(field,errors,true);checkValidity(field,errors)});if(!hasInvisibleRecaptcha(object))errors=validateRecaptcha(object,errors);return errors}function checkValidity(field,errors){let fieldID; -if("object"!==typeof field.validity||false!==field.validity.valid)return;fieldID=getFieldId(field,true);if("undefined"===typeof errors[fieldID])errors[fieldID]=getFieldValidationMessage(field,"data-invmsg");if("function"===typeof field.reportValidity)field.reportValidity()}function hasClass(element,targetClass){return element.classList&&element.classList.contains(targetClass)}function maybeValidateChange(field){if(field.type==="url")maybeAddHttpsToUrl(field);const form=field.closest("form");if(form&& -hasClass(form,"frm_js_validate"))validateField(field)}function maybeAddHttpsToUrl(field){const url=field.value;const matches=url.match(/^(https?|ftps?|mailto|news|feed|telnet):/);if(field.value!==""&&matches===null)field.value="https://"+url}function validateField(field){let errors,key;errors=[];const fieldContainer=field.closest(".frm_form_field");if(!fieldContainer)return;if(hasClass(fieldContainer,"frm_required_field")&&!hasClass(field,"frm_optional"))errors=checkRequiredField(field,errors);if(errors.length< +if("object"!==typeof field.validity||false!==field.validity.valid)return;fieldID=getFieldId(field,true);if(errors[fieldID]===undefined)errors[fieldID]=getFieldValidationMessage(field,"data-invmsg");if("function"===typeof field.reportValidity)field.reportValidity()}function hasClass(element,targetClass){return element.classList&&element.classList.contains(targetClass)}function maybeValidateChange(field){if(field.type==="url")maybeAddHttpsToUrl(field);const form=field.closest("form");if(form&&hasClass(form, +"frm_js_validate"))validateField(field)}function maybeAddHttpsToUrl(field){const url=field.value;const matches=url.match(/^(https?|ftps?|mailto|news|feed|telnet):/);if(field.value!==""&&matches===null)field.value="https://"+url}function validateField(field){let errors,key;errors=[];const fieldContainer=field.closest(".frm_form_field");if(!fieldContainer)return;if(hasClass(fieldContainer,"frm_required_field")&&!hasClass(field,"frm_optional"))errors=checkRequiredField(field,errors);if(errors.length< 1)validateFieldValue(field,errors,false);removeFieldError(fieldContainer);if(Object.keys(errors).length>0)for(key in errors)addFieldError(fieldContainer,key,errors)}function validateFieldValue(field,errors,onSubmit){if(field.type==="hidden");else if(field.type==="number")checkNumberField(field,errors);else if(field.type==="email")checkEmailField(field,errors,onSubmit);else if(field.type==="password")checkPasswordField(field,errors,onSubmit);else if(field.type==="url")checkUrlField(field,errors);else if(field.pattern!== null)checkPatternField(field,errors);if("tel"===field.type&&shouldCheckConfirmField(field,onSubmit))confirmField(field,errors);triggerCustomEvent(document,"frm_validate_field_value",{field,errors,onSubmit})}function checkRequiredField(field,errors){let tempVal,i,placeholder,val="",fieldID="",fileID=field.getAttribute("data-frmfile");if(field.type==="hidden"&&fileID===null&&!isAppointmentField(field)&&!isInlineDatepickerField(field))return errors;if(field.type==="checkbox"||field.type==="radio")document.querySelectorAll('input[name="'+ field.name+'"]').forEach(function(input){const requiredField=input.closest(".frm_required_field");if(!requiredField)return;const checkedInputs=requiredField.querySelectorAll("input:checked");checkedInputs.forEach(function(checkedInput){val=checkedInput.value})});else if(field.type==="file"||fileID){if(fileID===undefined){fileID=getFieldId(field,true);fileID=fileID.replace("file","")}if(errors[fileID]===undefined)val=getFileVals(fileID);fieldID=fileID}else{if(hasClass(field,"frm_pos_none"))return errors; @@ -31,11 +31,11 @@ formID+'"]');pageOrder=pageOrderInput?pageOrderInput.value:"";const tempDiv=docu $fieldCont=null;for(key in response.errors){const fieldContEl=object.querySelector("#frm_field_"+key+"_container");$fieldCont=fieldContEl?jQuery(fieldContEl):jQuery();if($fieldCont.length){if(!$fieldCont.is(":visible")){inCollapsedSection=$fieldCont.closest(".frm_toggle_container");if(inCollapsedSection.length){frmTrigger=inCollapsedSection.prev();if(!frmTrigger.hasClass("frm_trigger"))frmTrigger=frmTrigger.prev(".frm_trigger");frmTrigger.trigger("click")}}if($fieldCont.is(":visible")){addFieldError($fieldCont, key,response.errors);contSubmit=false}}}object.querySelectorAll(".frm-g-recaptcha, .g-recaptcha, .h-captcha").forEach(function(captchaEl){const recaptchaID=captchaEl.dataset.rid;if(typeof grecaptcha!=="undefined"&&grecaptcha)if(recaptchaID)grecaptcha.reset(recaptchaID);else grecaptcha.reset();if(typeof hcaptcha!=="undefined"&&hcaptcha)hcaptcha.reset()});if(window.turnstile)object.querySelectorAll(".frm-cf-turnstile").forEach(turnstileField=>turnstileField.dataset.rid&&turnstile.reset(turnstileField.dataset.rid)); jQuery(document).trigger("frmFormErrors",[object,response]);fieldsets.forEach(field=>field.classList.remove("frm_doing_ajax"));scrollToFirstField(object);if(contSubmit)object.submit();else{object.insertAdjacentHTML("afterbegin",response.error_message);checkForErrorsAndMaybeSetFocus()}}else{showFileLoading(object);object.submit()}};error=function(){object.querySelectorAll('input[type="submit"], input[type="button"]').forEach(button=>button.disabled=false);object.submit()};postToAjaxUrl(object,data, -success,error)}function postToAjaxUrl(form,data,success,error){let ajaxUrl,action,ajaxParams;ajaxUrl=frm_js.ajax_url;action=form.getAttribute("action");if("string"===typeof action&&-1!==action.indexOf("?action=frm_forms_preview"))ajaxUrl=action.split("?action=frm_forms_preview")[0];ajaxParams={type:"POST",url:ajaxUrl,data,success};if("function"===typeof error)ajaxParams.error=error;jQuery.ajax(ajaxParams)}function afterFormSubmitted(object,response){const tempDiv=document.createElement("div");tempDiv.innerHTML= +success,error)}function postToAjaxUrl(form,data,success,error){let ajaxUrl,action,ajaxParams;ajaxUrl=frm_js.ajax_url;action=form.getAttribute("action");if("string"===typeof action&&action.includes("?action=frm_forms_preview"))ajaxUrl=action.split("?action=frm_forms_preview")[0];ajaxParams={type:"POST",url:ajaxUrl,data,success};if("function"===typeof error)ajaxParams.error=error;jQuery.ajax(ajaxParams)}function afterFormSubmitted(object,response){const tempDiv=document.createElement("div");tempDiv.innerHTML= response.content;const formCompleted=tempDiv.querySelector(".frm_message");if(formCompleted)jQuery(document).trigger("frmFormComplete",[object,response]);else jQuery(document).trigger("frmPageChanged",[object,response])}function afterFormSubmittedBeforeReplace(object,response){const tempDiv=document.createElement("div");tempDiv.innerHTML=response.content;const formCompleted=tempDiv.querySelector(".frm_message");if(formCompleted)triggerCustomEvent(document,"frmFormCompleteBeforeReplace",{object,response})} function removeAddedScripts(formContainer,formID){const endReplace=document.querySelectorAll(".frm_end_ajax_"+formID);if(endReplace.length){formContainer.nextUntil(".frm_end_ajax_"+formID).remove();endReplace.forEach(el=>el.remove())}}function maybeSlideOut(oldContent,newContent){let c,newClass="frm_slideout";if(newContent.includes(" frm_slide")){c=oldContent.children();if(newContent.includes(" frm_going_back"))newClass+=" frm_going_back";c.removeClass("frm_going_back");c.addClass(newClass);return 300}return 0} function addUrlParam(response){let url;if(history.pushState&&response.page!==undefined){url=addQueryVar("frm_page",response.page);window.history.pushState({html:response.html},"","?"+url)}}function addQueryVar(key,value){let kvp,i,x;key=encodeURI(key);value=encodeURI(value);kvp=document.location.search.substr(1).split("&");i=kvp.length;while(i--){x=kvp[i].split("=");if(x[0]==key){x[1]=value;kvp[i]=x.join("=");break}}if(i<0)kvp[kvp.length]=[key,value].join("=");return kvp.join("&")}function addFieldError($fieldCont, -key,jsErrors){let id,describedBy,roleString;const container=$fieldCont instanceof jQuery?$fieldCont.get(0):$fieldCont;if(!container||container.offsetParent===null)return;container.classList.add("frm_blank_field");const input=container.querySelector("input, select, textarea");id=getErrorElementId(key,input);describedBy=input?input.getAttribute("aria-describedby"):null;if(typeof frmThemeOverride_frmPlaceError==="function")frmThemeOverride_frmPlaceError(key,jsErrors);else{let errorHtml;if(-1!==jsErrors[key].indexOf("'+jsErrors[key]+"
"}container.insertAdjacentHTML("beforeend",errorHtml);if(input){if(!describedBy)describedBy=id;else if(!describedBy.includes(id)&&!describedBy.includes("frm_error_field_")){const errorFirst=input.dataset.errorFirst;if(errorFirst==="0")describedBy=describedBy+" "+id;else describedBy=id+" "+describedBy}input.setAttribute("aria-describedby",describedBy)}}if(input)if(["radio", "checkbox"].includes(input.type)){const group=input.closest('[role="radiogroup"], [role="group"]');if(group)group.setAttribute("aria-invalid","true")}else input.setAttribute("aria-invalid","true");jQuery(document).trigger("frmAddFieldError",[jQuery(container),key,jsErrors])}function getErrorElementId(key,input){if(isNaN(key)||!input||!input.id)return"frm_error_field_"+key;return"frm_error_"+input.id}function removeFieldError(fieldCont){const container=fieldCont instanceof jQuery?fieldCont.get(0): fieldCont;if(!container)return;const errorMessage=container.querySelector(".frm_error");const errorId=errorMessage?errorMessage.id:"";const input=container.querySelector("input, select, textarea");let describedBy=input?input.getAttribute("aria-describedby"):null;container.classList.remove("frm_blank_field","has-error");if(input)if("true"===input.getAttribute("aria-invalid"))input.setAttribute("aria-invalid","false");else if(["radio","checkbox"].includes(input.type)){const group=input.closest('[role="radiogroup"], [role="group"]'); @@ -45,31 +45,31 @@ function addLoadingClass($object){const loadingClass=isGoingToPrevPage($object)? if(enable==="enable"){enableSubmitButton(form);enableSaveDraft(form)}})}function showFileLoading(object){const loading=document.getElementById("frm_loading");if(loading===null)return;const fileInput=object.querySelector("input[type=file]");const fileval=fileInput?fileInput.value:"";if(fileval!=="")setTimeout(function(){jQuery(loading).fadeIn("slow")},2E3)}function confirmClick(){const message=this.dataset.frmconfirm;return confirm(message)}function onHoneypotFieldChange(){const css=window.getComputedStyle(this).boxShadow; if(css&&css.match(/inset/))this.remove()}function changeFocusWhenClickComboFieldLabel(){let label;const comboInputsContainer=document.querySelectorAll(".frm_combo_inputs_container");comboInputsContainer.forEach(function(inputsContainer){if(!inputsContainer.closest(".frm_form_field"))return;label=inputsContainer.closest(".frm_form_field").querySelector(".frm_primary_label");if(!label)return;label.addEventListener("click",function(){inputsContainer.querySelector(".frm_form_field:first-child input, .frm_form_field:first-child select, .frm_form_field:first-child textarea").focus()})})} function maybeFocusOnComboSubField(element){if("FIELDSET"!==element.nodeName)return false;if(!element.querySelector(".frm_combo_inputs_container"))return false;const comboSubfield=element.querySelector('[aria-invalid="true"]');if(comboSubfield){focusInput(comboSubfield);return true}return false}function checkForErrorsAndMaybeSetFocus(){let errors,element,timeoutCallback;if(!frm_js.focus_first_error)return;errors=document.querySelectorAll(".frm_form_field .frm_error");if(!errors.length)return;element= -errors[0];do{element=element.previousSibling;if(-1!==["input","select","textarea"].indexOf(element.nodeName.toLowerCase())){focusInput(element);break}if(maybeFocusOnComboSubField(element))break;if("undefined"!==typeof element.classList){if(element.classList.contains("html-active"))timeoutCallback=function(){const textarea=element.querySelector("textarea");if(null!==textarea)textarea.focus()};else if(element.classList.contains("tmce-active"))timeoutCallback=function(){tinyMCE.activeEditor.focus()}; -else if(element.classList.contains("frm_opt_container")){const firstInput=element.querySelector("input");if(firstInput){focusInput(firstInput);break}}if("function"===typeof timeoutCallback){setTimeout(timeoutCallback,0);break}}}while(element.previousSibling)}function focusInput(input){if(input.offsetParent!==null)input.focus();else triggerCustomEvent(document,"frmMaybeDelayFocus",{input})}function documentOn(event,selector,handler,options){if("undefined"===typeof options)options=false;document.addEventListener(event, -function(e){let target;for(target=e.target;target&&target!=this;target=target.parentNode)if(target.matches&&target.matches(selector)){handler.call(target,e);break}},options)}function initFloatingLabels(){let checkFloatLabel,checkDropdownLabel,runOnLoad,selector,floatClass;selector=".frm-show-form .frm_inside_container input, .frm-show-form .frm_inside_container select, .frm-show-form .frm_inside_container textarea";floatClass="frm_label_float_top";checkFloatLabel=function(input){let container,shouldFloatTop, -firstOpt;container=input.closest(".frm_inside_container");if(!container)return;shouldFloatTop=input.value||document.activeElement===input;container.classList.toggle(floatClass,shouldFloatTop);if("SELECT"===input.tagName){firstOpt=input.querySelector("option:first-child");if(shouldFloatTop){if(firstOpt.hasAttribute("data-label")){firstOpt.textContent=firstOpt.getAttribute("data-label");firstOpt.removeAttribute("data-label")}}else if(firstOpt.textContent){firstOpt.setAttribute("data-label",firstOpt.textContent); -firstOpt.textContent=""}}};checkDropdownLabel=function(){document.querySelectorAll(".frm-show-form .frm_inside_container:not(."+floatClass+") select").forEach(function(input){const firstOpt=input.querySelector("option:first-child");if(firstOpt.textContent){firstOpt.setAttribute("data-label",firstOpt.textContent);firstOpt.textContent=""}})};["focus","blur","change"].forEach(function(eventName){documentOn(eventName,selector,function(event){checkFloatLabel(event.target)},true)});runOnLoad=function(firstLoad){if(firstLoad&& -document.activeElement&&-1!==["INPUT","SELECT","TEXTAREA"].indexOf(document.activeElement.tagName))checkFloatLabel(document.activeElement);else if(firstLoad)document.querySelectorAll(".frm_inside_container").forEach(function(container){const input=container.querySelector("input, select, textarea");if(input&&""!==input.value)checkFloatLabel(input)});checkDropdownLabel()};runOnLoad(true);jQuery(document).on("frmPageChanged",function(event){runOnLoad()});document.addEventListener("frm_after_start_over", -function(event){runOnLoad()})}function shouldUpdateValidityMessage(target){if("INPUT"!==target.nodeName)return false;if(!target.dataset.invmsg)return false;if("text"!==target.getAttribute("type"))return false;if(target.classList.contains("frm_verify"))return false;return true}function maybeClearCustomValidityMessage(event,field){let key,isInvalid=false;if(!shouldUpdateValidityMessage(field))return;for(key in field.validity){if("customError"===key)continue;if("valid"!==key&&field.validity[key]===true){isInvalid= -true;break}}if(!isInvalid)field.setCustomValidity("")}function maybeShowNewTabFallbackMessage(){let messageEl;if(!window.frmShowNewTabFallback)return;messageEl=document.querySelector("#frm_form_"+frmShowNewTabFallback.formId+"_container .frm_message");if(!messageEl)return;messageEl.insertAdjacentHTML("beforeend"," "+frmShowNewTabFallback.message)}function setCustomValidityMessage(){let forms,length,index;forms=document.getElementsByClassName("frm-show-form");length=forms.length;for(index=0;index< -length;++index)forms[index].addEventListener("invalid",function(event){const target=event.target;if(shouldUpdateValidityMessage(target))target.setCustomValidity(target.dataset.invmsg)},true)}function enableSubmitButtonOnBackButtonPress(){window.addEventListener("pageshow",function(event){if(event.persisted){document.querySelectorAll(".frm_loading_form").forEach(function(form){enableSubmitButton(form)});removeSubmitLoading()}})}function destroyhCaptcha(){if(!window.hasOwnProperty("hcaptcha")||!document.querySelector(".frm-show-form .h-captcha"))return; -window.hcaptcha=null}function getUniqueKey(){const uniqueKey=Array.from(window.crypto.getRandomValues(new Uint8Array(8))).map(b=>b.toString(16).padStart(2,"0")).join("");const timestamp=Date.now().toString(16);return uniqueKey+"-"+timestamp}function animateScroll(start,end,duration){if(!window.hasOwnProperty("performance")||!window.hasOwnProperty("requestAnimationFrame")){document.documentElement.scrollTop=end;return}const startTime=performance.now();const step=currentTime=>{const progress=Math.min((currentTime- -startTime)/duration,1);document.documentElement.scrollTop=start+(end-start)*progress;if(progress<1)requestAnimationFrame(step)};requestAnimationFrame(step)}function maybeFixCaptchaLabel(captcha){const form=captcha.closest("form");if(!form)return;const label=form.querySelector('label[for="g-recaptcha-response"], label[for="cf-turnstile-response"]');const captchaResponse=form.querySelector('[name="g-recaptcha-response"], [name="cf-turnstile-response"]');if(label&&captchaResponse)label.htmlFor=captchaResponse.id} -return{init(){jQuery(document).off("submit.formidable",".frm-show-form");jQuery(document).on("submit.formidable",".frm-show-form",frmFrontForm.submitForm);jQuery(document).on("change",'.frm-show-form input[name^="item_meta"], .frm-show-form select[name^="item_meta"], .frm-show-form textarea[name^="item_meta"]',frmFrontForm.fieldValueChanged);jQuery(document).on("change",".frm_verify[id^=field_]",onHoneypotFieldChange);jQuery(document).on("click","a[data-frmconfirm]",confirmClick);checkForErrorsAndMaybeSetFocus(); -changeFocusWhenClickComboFieldLabel();initFloatingLabels();maybeShowNewTabFallbackMessage();jQuery(document).on("frmAfterAddRow",setCustomValidityMessage);setCustomValidityMessage();jQuery(document).on("frmFieldChanged",maybeClearCustomValidityMessage);setSelectPlaceholderColor();jQuery(document).on("elementor/popup/show",frmRecaptcha);enableSubmitButtonOnBackButtonPress();jQuery(document).on("frmPageChanged",destroyhCaptcha)},getFieldId,renderCaptcha(captcha,captchaSelector){const rendered=captcha.getAttribute("data-rid")!== -null;if(rendered)return;const size=captcha.getAttribute("data-size");const params={sitekey:captcha.getAttribute("data-sitekey"),size,theme:captcha.getAttribute("data-theme")};if(size==="invisible"){const formID=captcha.closest("form")?.querySelector('input[name="form_id"]')?.value;const captchaLabel=captcha.closest(".frm_form_field")?.querySelector(".frm_primary_label");if(captchaLabel)captchaLabel.style.display="none";params.callback=function(token){frmFrontForm.afterRecaptcha(token,formID)}}const activeCaptcha= -getSelectedCaptcha(captchaSelector);const captchaContainer=typeof turnstile!=="undefined"&&turnstile===activeCaptcha?"#"+captcha.id:captcha.id;const captchaID=activeCaptcha.render(captchaContainer,params);captcha.setAttribute("data-rid",captchaID);maybeFixCaptchaLabel(captcha)},afterSingleRecaptcha(){const recaptcha=document.querySelector(".frm-show-form .g-recaptcha");const object=recaptcha?recaptcha.closest("form"):null;frmFrontForm.submitFormNow(object)},afterRecaptcha(_,formID){const object=document.querySelector("#frm_form_"+ -formID+"_container form");frmFrontForm.submitFormNow(object)},submitForm(e){frmFrontForm.submitFormManual(e,this)},submitFormManual(e,object){if(document.body.classList.contains("wp-admin")&&!object.closest(".frmapi-form"))return;e.preventDefault();if(typeof frmProForm!=="undefined"&&typeof frmProForm.submitAllowed==="function"&&!frmProForm.submitAllowed(object))return;const errors=frmFrontForm.validateFormSubmit(object);if(Object.keys(errors).length!==0)return;const invisibleRecaptcha=hasInvisibleRecaptcha(object); -if(invisibleRecaptcha){showLoadingIndicator(jQuery(object));executeInvisibleRecaptcha(invisibleRecaptcha)}else{showSubmitLoading(jQuery(object));frmFrontForm.submitFormNow(object)}},submitFormNow(object){let hasFileFields,antispamInput,classList=object.className.trim().split(/\s+/gi);if(object.hasAttribute("data-token")&&null===object.querySelector('[name="antispam_token"]')){antispamInput=document.createElement("input");antispamInput.type="hidden";antispamInput.name="antispam_token";antispamInput.value= -object.getAttribute("data-token");object.append(antispamInput)}const uniqueIDInput=document.createElement("input");uniqueIDInput.type="hidden";uniqueIDInput.name="unique_id";uniqueIDInput.value=getUniqueKey();object.append(uniqueIDInput);if(classList.includes("frm_ajax_submit")){const fileInputs=object.querySelectorAll('input[type="file"]');hasFileFields=Array.from(fileInputs).filter(input=>!!input.value).length;if(hasFileFields<1){const actionInput=object.querySelector('input[name="frm_action"]'); -const action=actionInput?actionInput.value:"";frmFrontForm.checkFormErrors(object,action)}else object.submit()}else object.submit()},validateFormSubmit(object){const form=object instanceof jQuery?object.get(0):object;if(typeof tinyMCE!=="undefined"&&form&&form.querySelector(".wp-editor-wrap"))tinyMCE.triggerSave();jsErrors=[];if(shouldJSValidate(object)){frmFrontForm.getAjaxFormErrors(object);if(Object.keys(jsErrors).length)frmFrontForm.addAjaxFormErrors(object)}return jsErrors},getAjaxFormErrors(object){let customErrors, -key;const form=object instanceof jQuery?object.get(0):object;jsErrors=validateForm(object);if(typeof frmThemeOverride_jsErrors==="function"){const actionInput=form?form.querySelector('input[name="frm_action"]'):null;const action=actionInput?actionInput.value:"";customErrors=frmThemeOverride_jsErrors(action,object);if(Object.keys(customErrors).length)for(key in customErrors)jsErrors[key]=customErrors[key]}triggerCustomEvent(document,"frm_get_ajax_form_errors",{formEl:object,errors:jsErrors});return jsErrors}, -addAjaxFormErrors(object){let key;const form=object instanceof jQuery?object.get(0):object;removeAllErrors();for(key in jsErrors){const fieldCont=form?form.querySelector("#frm_field_"+key+"_container"):null;if(fieldCont)addFieldError(fieldCont,key,jsErrors);else delete jsErrors[key]}scrollToFirstField(object);checkForErrorsAndMaybeSetFocus()},checkFormErrors:getFormErrors,checkRequiredField,showSubmitLoading,removeSubmitLoading,scrollToID(id){const object=jQuery(document.getElementById(id));frmFrontForm.scrollMsg(object, -false)},scrollMsg(id,object,animate){let newPos,m,b,screenTop,screenBottom,scrollObj="";if(object===undefined){scrollObj=jQuery(document.getElementById("frm_form_"+id+"_container"));if(scrollObj.length<1)return}else if(typeof id==="string"){const formEl=object instanceof jQuery?object.get(0):object;const fieldEl=formEl?formEl.querySelector("#frm_field_"+id+"_container"):null;scrollObj=fieldEl?jQuery(fieldEl):jQuery()}else scrollObj=id;jQuery(scrollObj).trigger("focus");newPos=scrollObj.offset().top; -if(!newPos||frm_js.offset==="-1")return;newPos=newPos-frm_js.offset;m=getComputedStyle(document.documentElement).marginTop;b=getComputedStyle(document.body).marginTop;if(m||b)newPos=newPos-parseInt(m)-parseInt(b);if(newPos&&window.innerHeight){screenTop=document.documentElement.scrollTop||document.body.scrollTop;screenBottom=screenTop+window.innerHeight;if(newPos>screenBottom||newPos/g,">").replace(/"/g,""").replace(/'/g,"'")},triggerCustomEvent,documentOn}} -window.frmFrontForm=frmFrontFormJS();jQuery(document).ready(function(){frmFrontForm.init()});function frmRecaptcha(){frmCaptcha(".frm-g-recaptcha")}function frmHcaptcha(){frmCaptcha(".h-captcha")}function frmTurnstile(){frmCaptcha(".frm-cf-turnstile")} +errors[0];do{element=element.previousSibling;if(["input","select","textarea"].includes(element.nodeName.toLowerCase())){focusInput(element);break}if(maybeFocusOnComboSubField(element))break;if(element.classList!==undefined){if(element.classList.contains("html-active"))timeoutCallback=function(){const textarea=element.querySelector("textarea");if(null!==textarea)textarea.focus()};else if(element.classList.contains("tmce-active"))timeoutCallback=function(){tinyMCE.activeEditor.focus()};else if(element.classList.contains("frm_opt_container")){const firstInput= +element.querySelector("input");if(firstInput){focusInput(firstInput);break}}if("function"===typeof timeoutCallback){setTimeout(timeoutCallback,0);break}}}while(element.previousSibling)}function focusInput(input){if(input.offsetParent!==null)input.focus();else triggerCustomEvent(document,"frmMaybeDelayFocus",{input})}function documentOn(event,selector,handler,options){if(options===undefined)options=false;document.addEventListener(event,function(e){let target;for(target=e.target;target&&target!=this;target= +target.parentNode)if(target.matches&&target.matches(selector)){handler.call(target,e);break}},options)}function initFloatingLabels(){let checkFloatLabel,checkDropdownLabel,runOnLoad,selector,floatClass;selector=".frm-show-form .frm_inside_container input, .frm-show-form .frm_inside_container select, .frm-show-form .frm_inside_container textarea";floatClass="frm_label_float_top";checkFloatLabel=function(input){let container,shouldFloatTop,firstOpt;container=input.closest(".frm_inside_container");if(!container)return; +shouldFloatTop=input.value||document.activeElement===input;container.classList.toggle(floatClass,shouldFloatTop);if("SELECT"===input.tagName){firstOpt=input.querySelector("option:first-child");if(shouldFloatTop){if(firstOpt.hasAttribute("data-label")){firstOpt.textContent=firstOpt.getAttribute("data-label");firstOpt.removeAttribute("data-label")}}else if(firstOpt.textContent){firstOpt.setAttribute("data-label",firstOpt.textContent);firstOpt.textContent=""}}};checkDropdownLabel=function(){document.querySelectorAll(".frm-show-form .frm_inside_container:not(."+ +floatClass+") select").forEach(function(input){const firstOpt=input.querySelector("option:first-child");if(firstOpt.textContent){firstOpt.setAttribute("data-label",firstOpt.textContent);firstOpt.textContent=""}})};["focus","blur","change"].forEach(function(eventName){documentOn(eventName,selector,function(event){checkFloatLabel(event.target)},true)});runOnLoad=function(firstLoad){if(firstLoad&&document.activeElement&&["INPUT","SELECT","TEXTAREA"].includes(document.activeElement.tagName))checkFloatLabel(document.activeElement); +else if(firstLoad)document.querySelectorAll(".frm_inside_container").forEach(function(container){const input=container.querySelector("input, select, textarea");if(input&&""!==input.value)checkFloatLabel(input)});checkDropdownLabel()};runOnLoad(true);jQuery(document).on("frmPageChanged",function(event){runOnLoad()});document.addEventListener("frm_after_start_over",function(event){runOnLoad()})}function shouldUpdateValidityMessage(target){if("INPUT"!==target.nodeName)return false;if(!target.dataset.invmsg)return false; +if("text"!==target.getAttribute("type"))return false;if(target.classList.contains("frm_verify"))return false;return true}function maybeClearCustomValidityMessage(event,field){let key,isInvalid=false;if(!shouldUpdateValidityMessage(field))return;for(key in field.validity){if("customError"===key)continue;if("valid"!==key&&field.validity[key]===true){isInvalid=true;break}}if(!isInvalid)field.setCustomValidity("")}function maybeShowNewTabFallbackMessage(){let messageEl;if(!window.frmShowNewTabFallback)return; +messageEl=document.querySelector("#frm_form_"+frmShowNewTabFallback.formId+"_container .frm_message");if(!messageEl)return;messageEl.insertAdjacentHTML("beforeend"," "+frmShowNewTabFallback.message)}function setCustomValidityMessage(){let forms,length,index;forms=document.getElementsByClassName("frm-show-form");length=forms.length;for(index=0;index +b.toString(16).padStart(2,"0")).join("");const timestamp=Date.now().toString(16);return uniqueKey+"-"+timestamp}function animateScroll(start,end,duration){if(!window.hasOwnProperty("performance")||!window.hasOwnProperty("requestAnimationFrame")){document.documentElement.scrollTop=end;return}const startTime=performance.now();const step=currentTime=>{const progress=Math.min((currentTime-startTime)/duration,1);document.documentElement.scrollTop=start+(end-start)*progress;if(progress<1)requestAnimationFrame(step)}; +requestAnimationFrame(step)}function maybeFixCaptchaLabel(captcha){const form=captcha.closest("form");if(!form)return;const label=form.querySelector('label[for="g-recaptcha-response"], label[for="cf-turnstile-response"]');const captchaResponse=form.querySelector('[name="g-recaptcha-response"], [name="cf-turnstile-response"]');if(label&&captchaResponse)label.htmlFor=captchaResponse.id}return{init(){jQuery(document).off("submit.formidable",".frm-show-form");jQuery(document).on("submit.formidable",".frm-show-form", +frmFrontForm.submitForm);jQuery(document).on("change",'.frm-show-form input[name^="item_meta"], .frm-show-form select[name^="item_meta"], .frm-show-form textarea[name^="item_meta"]',frmFrontForm.fieldValueChanged);jQuery(document).on("change",".frm_verify[id^=field_]",onHoneypotFieldChange);jQuery(document).on("click","a[data-frmconfirm]",confirmClick);checkForErrorsAndMaybeSetFocus();changeFocusWhenClickComboFieldLabel();initFloatingLabels();maybeShowNewTabFallbackMessage();jQuery(document).on("frmAfterAddRow", +setCustomValidityMessage);setCustomValidityMessage();jQuery(document).on("frmFieldChanged",maybeClearCustomValidityMessage);setSelectPlaceholderColor();jQuery(document).on("elementor/popup/show",frmRecaptcha);enableSubmitButtonOnBackButtonPress();jQuery(document).on("frmPageChanged",destroyhCaptcha)},getFieldId,renderCaptcha(captcha,captchaSelector){const rendered=captcha.getAttribute("data-rid")!==null;if(rendered)return;const size=captcha.getAttribute("data-size");const params={sitekey:captcha.getAttribute("data-sitekey"), +size,theme:captcha.getAttribute("data-theme")};if(size==="invisible"){const formID=captcha.closest("form")?.querySelector('input[name="form_id"]')?.value;const captchaLabel=captcha.closest(".frm_form_field")?.querySelector(".frm_primary_label");if(captchaLabel)captchaLabel.style.display="none";params.callback=function(token){frmFrontForm.afterRecaptcha(token,formID)}}const activeCaptcha=getSelectedCaptcha(captchaSelector);const captchaContainer=typeof turnstile!=="undefined"&&turnstile===activeCaptcha? +"#"+captcha.id:captcha.id;const captchaID=activeCaptcha.render(captchaContainer,params);captcha.setAttribute("data-rid",captchaID);maybeFixCaptchaLabel(captcha)},afterSingleRecaptcha(){const recaptcha=document.querySelector(".frm-show-form .g-recaptcha");const object=recaptcha?recaptcha.closest("form"):null;frmFrontForm.submitFormNow(object)},afterRecaptcha(_,formID){const object=document.querySelector("#frm_form_"+formID+"_container form");frmFrontForm.submitFormNow(object)},submitForm(e){frmFrontForm.submitFormManual(e, +this)},submitFormManual(e,object){if(document.body.classList.contains("wp-admin")&&!object.closest(".frmapi-form"))return;e.preventDefault();if(typeof frmProForm!=="undefined"&&typeof frmProForm.submitAllowed==="function"&&!frmProForm.submitAllowed(object))return;const errors=frmFrontForm.validateFormSubmit(object);if(Object.keys(errors).length!==0)return;const invisibleRecaptcha=hasInvisibleRecaptcha(object);if(invisibleRecaptcha){showLoadingIndicator(jQuery(object));executeInvisibleRecaptcha(invisibleRecaptcha)}else{showSubmitLoading(jQuery(object)); +frmFrontForm.submitFormNow(object)}},submitFormNow(object){let hasFileFields,antispamInput,classList=object.className.trim().split(/\s+/gi);if(object.hasAttribute("data-token")&&null===object.querySelector('[name="antispam_token"]')){antispamInput=document.createElement("input");antispamInput.type="hidden";antispamInput.name="antispam_token";antispamInput.value=object.getAttribute("data-token");object.append(antispamInput)}const uniqueIDInput=document.createElement("input");uniqueIDInput.type="hidden"; +uniqueIDInput.name="unique_id";uniqueIDInput.value=getUniqueKey();object.append(uniqueIDInput);if(classList.includes("frm_ajax_submit")){const fileInputs=object.querySelectorAll('input[type="file"]');hasFileFields=Array.from(fileInputs).filter(input=>!!input.value).length;if(hasFileFields<1){const actionInput=object.querySelector('input[name="frm_action"]');const action=actionInput?actionInput.value:"";frmFrontForm.checkFormErrors(object,action)}else object.submit()}else object.submit()},validateFormSubmit(object){const form= +object instanceof jQuery?object.get(0):object;if(typeof tinyMCE!=="undefined"&&form&&form.querySelector(".wp-editor-wrap"))tinyMCE.triggerSave();jsErrors=[];if(shouldJSValidate(object)){frmFrontForm.getAjaxFormErrors(object);if(Object.keys(jsErrors).length)frmFrontForm.addAjaxFormErrors(object)}return jsErrors},getAjaxFormErrors(object){let customErrors,key;const form=object instanceof jQuery?object.get(0):object;jsErrors=validateForm(object);if(typeof frmThemeOverride_jsErrors==="function"){const actionInput= +form?form.querySelector('input[name="frm_action"]'):null;const action=actionInput?actionInput.value:"";customErrors=frmThemeOverride_jsErrors(action,object);if(Object.keys(customErrors).length)for(key in customErrors)jsErrors[key]=customErrors[key]}triggerCustomEvent(document,"frm_get_ajax_form_errors",{formEl:object,errors:jsErrors});return jsErrors},addAjaxFormErrors(object){let key;const form=object instanceof jQuery?object.get(0):object;removeAllErrors();for(key in jsErrors){const fieldCont=form? +form.querySelector("#frm_field_"+key+"_container"):null;if(fieldCont)addFieldError(fieldCont,key,jsErrors);else delete jsErrors[key]}scrollToFirstField(object);checkForErrorsAndMaybeSetFocus()},checkFormErrors:getFormErrors,checkRequiredField,showSubmitLoading,removeSubmitLoading,scrollToID(id){const object=jQuery(document.getElementById(id));frmFrontForm.scrollMsg(object,false)},scrollMsg(id,object,animate){let newPos,m,b,screenTop,screenBottom,scrollObj="";if(object===undefined){scrollObj=jQuery(document.getElementById("frm_form_"+ +id+"_container"));if(scrollObj.length<1)return}else if(typeof id==="string"){const formEl=object instanceof jQuery?object.get(0):object;const fieldEl=formEl?formEl.querySelector("#frm_field_"+id+"_container"):null;scrollObj=fieldEl?jQuery(fieldEl):jQuery()}else scrollObj=id;jQuery(scrollObj).trigger("focus");newPos=scrollObj.offset().top;if(!newPos||frm_js.offset==="-1")return;newPos=newPos-frm_js.offset;m=getComputedStyle(document.documentElement).marginTop;b=getComputedStyle(document.body).marginTop; +if(m||b)newPos=newPos-parseInt(m)-parseInt(b);if(newPos&&window.innerHeight){screenTop=document.documentElement.scrollTop||document.body.scrollTop;screenBottom=screenTop+window.innerHeight;if(newPos>screenBottom||newPos/g,">").replace(/"/g,""").replace(/'/g,"'")},triggerCustomEvent,documentOn}}window.frmFrontForm=frmFrontFormJS();jQuery(document).ready(function(){frmFrontForm.init()});function frmRecaptcha(){frmCaptcha(".frm-g-recaptcha")} +function frmHcaptcha(){frmCaptcha(".h-captcha")}function frmTurnstile(){frmCaptcha(".frm-cf-turnstile")} function frmCaptcha(captchaSelector){if(".h-captcha"===captchaSelector){const captchaLabels=document.querySelectorAll('label[for="h-captcha-response"]');if(captchaLabels.length)captchaLabels.forEach(label=>{const captchaResponse=label.closest("form")?.querySelector('[name="h-captcha-response"]');if(captchaResponse)label.htmlFor=captchaResponse.id});return}let c;const captchas=document.querySelectorAll(captchaSelector);const cl=captchas.length;for(c=0;c{var e={65:(e,t,r)=>{"use strict";r.d(t,{A:()=>o});var n=r(8793),i=r(1323);function o(e){var t=(0,n.A)(e);return function(e){return(0,i.A)(t,e)}}},1323:(e,t,r)=>{"use strict";r.d(t,{A:()=>i});var n={"!":function(e){return!e},"*":function(e,t){return e*t},"/":function(e,t){return e/t},"%":function(e,t){return e%t},"+":function(e,t){return e+t},"-":function(e,t){return e-t},"<":function(e,t){return e":function(e,t){return e>t},">=":function(e,t){return e>=t},"==":function(e,t){return e===t},"!=":function(e,t){return e!==t},"&&":function(e,t){return e&&t},"||":function(e,t){return e||t},"?:":function(e,t,r){if(e)throw t;return r}};function i(e,t){var r,i,o,a,l,s,d=[];for(r=0;r{"use strict";r.d(t,{A:()=>i});var n=r(65);function i(e){var t=(0,n.A)(e);return function(e){return+t({n:e})}}},8793:(e,t,r)=>{"use strict";var n,i,o,a;function l(e){for(var t,r,l,s,d=[],c=[];t=e.match(a);){for(r=t[0],(l=e.substr(0,t.index).trim())&&d.push(l);s=c.pop();){if(o[r]){if(o[r][0]===s){r=o[r][1]||r;break}}else if(i.indexOf(s)>=0||n[s]l}),n={"(":9,"!":8,"*":7,"/":7,"%":7,"+":6,"-":6,"<":5,"<=":5,">":5,">=":5,"==":4,"!=":4,"&&":3,"||":2,"?":1,"?:":1},i=["(","?"],o={")":["("],":":["?","?:"]},a=/<=|>=|==|!=|&&|\|\||\?:|\(|!|\*|\/|%|\+|-|<|>|\?|\)|:/},7521:(e,t,r)=>{"use strict";r.d(t,{A:()=>o});var n=r(6956),i=r(7395);const o=function(e,t){return function(r,o,a){var l=arguments.length>3&&void 0!==arguments[3]?arguments[3]:10,s=e[t];if((0,i.A)(r)&&(0,n.A)(o))if("function"==typeof a)if("number"==typeof l){var d={callback:a,priority:l,namespace:o};if(s[r]){var c,u=s[r].handlers;for(c=u.length;c>0&&!(l>=u[c-1].priority);c--);c===u.length?u[c]=d:u.splice(c,0,d),s.__current.forEach(function(e){e.name===r&&e.currentIndex>=c&&e.currentIndex++})}else s[r]={handlers:[d],runs:0};"hookAdded"!==r&&e.doAction("hookAdded",r,o,a,l)}else console.error("If specified, the hook priority must be a number.");else console.error("The hook callback must be a function.")}}},11:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});const n=function(e,t){return function(){var r,n,i=e[t];return null!==(r=null===(n=i.__current[i.__current.length-1])||void 0===n?void 0:n.name)&&void 0!==r?r:null}}},5375:(e,t,r)=>{"use strict";r.d(t,{A:()=>i});var n=r(7395);const i=function(e,t){return function(r){var i=e[t];if((0,n.A)(r))return i[r]&&i[r].runs?i[r].runs:0}}},3561:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});const n=function(e,t){return function(r){var n=e[t];return void 0===r?void 0!==n.__current[0]:!!n.__current[0]&&r===n.__current[0].name}}},8830:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});const n=function(e,t){return function(r,n){var i=e[t];return void 0!==n?r in i&&i[r].handlers.some(function(e){return e.namespace===n}):r in i}}},7765:(e,t,r)=>{"use strict";r.d(t,{A:()=>f});var n=r(3029),i=r(7521),o=r(4194),a=r(8830),l=r(6763),s=r(11),d=r(3561),c=r(5375),u=function e(){(0,n.A)(this,e),this.actions=Object.create(null),this.actions.__current=[],this.filters=Object.create(null),this.filters.__current=[],this.addAction=(0,i.A)(this,"actions"),this.addFilter=(0,i.A)(this,"filters"),this.removeAction=(0,o.A)(this,"actions"),this.removeFilter=(0,o.A)(this,"filters"),this.hasAction=(0,a.A)(this,"actions"),this.hasFilter=(0,a.A)(this,"filters"),this.removeAllActions=(0,o.A)(this,"actions",!0),this.removeAllFilters=(0,o.A)(this,"filters",!0),this.doAction=(0,l.A)(this,"actions"),this.applyFilters=(0,l.A)(this,"filters",!0),this.currentAction=(0,s.A)(this,"actions"),this.currentFilter=(0,s.A)(this,"filters"),this.doingAction=(0,d.A)(this,"actions"),this.doingFilter=(0,d.A)(this,"filters"),this.didAction=(0,c.A)(this,"actions"),this.didFilter=(0,c.A)(this,"filters")};const f=function(){return new u}},4194:(e,t,r)=>{"use strict";r.d(t,{A:()=>o});var n=r(6956),i=r(7395);const o=function(e,t){var r=arguments.length>2&&void 0!==arguments[2]&&arguments[2];return function(o,a){var l=e[t];if((0,i.A)(o)&&(r||(0,n.A)(a))){if(!l[o])return 0;var s=0;if(r)s=l[o].handlers.length,l[o]={runs:l[o].runs,handlers:[]};else for(var d=l[o].handlers,c=function(e){d[e].namespace===a&&(d.splice(e,1),s++,l.__current.forEach(function(t){t.name===o&&t.currentIndex>=e&&t.currentIndex--}))},u=d.length-1;u>=0;u--)c(u);return"hookRemoved"!==o&&e.doAction("hookRemoved",o,a),s}}}},6763:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});const n=function(e,t){var r=arguments.length>2&&void 0!==arguments[2]&&arguments[2];return function(n){var i=e[t];i[n]||(i[n]={handlers:[],runs:0}),i[n].runs++;for(var o=i[n].handlers,a=arguments.length,l=new Array(a>1?a-1:0),s=1;s{"use strict";r.d(t,{se:()=>n});var n=(0,r(7765).A)();n.addAction,n.addFilter,n.removeAction,n.removeFilter,n.hasAction,n.hasFilter,n.removeAllActions,n.removeAllFilters,n.doAction,n.applyFilters,n.currentAction,n.currentFilter,n.doingAction,n.doingFilter,n.didAction,n.didFilter,n.actions,n.filters},7395:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});const n=function(e){return"string"!=typeof e||""===e?(console.error("The hook name must be a non-empty string."),!1):/^__/.test(e)?(console.error("The hook name cannot begin with `__`."),!1):!!/^[a-zA-Z][a-zA-Z0-9_.-]*$/.test(e)||(console.error("The hook name can only contain numbers, letters, dashes, periods and underscores."),!1)}},6956:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});const n=function(e){return"string"!=typeof e||""===e?(console.error("The namespace must be a non-empty string."),!1):!!/^[a-zA-Z][a-zA-Z0-9_.\-\/]*$/.test(e)||(console.error("The namespace can only contain numbers, letters, dashes, periods, underscores and slashes."),!1)}},772:(e,t,r)=>{"use strict";r.d(t,{h:()=>d});var n=r(4467),i=r(5397);function o(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function a(e){for(var t=1;t1&&void 0!==arguments[1]?arguments[1]:"default";n.data[t]=a(a(a({},l),n.data[t]),e),n.data[t][""]=a(a({},l[""]),n.data[t][""])},u=function(e,t){c(e,t),d()},f=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"default",t=arguments.length>1?arguments[1]:void 0,r=arguments.length>2?arguments[2]:void 0,i=arguments.length>3?arguments[3]:void 0,o=arguments.length>4?arguments[4]:void 0;return n.data[e]||c(void 0,e),n.dcnpgettext(e,t,r,i,o)},m=function(){return arguments.length>0&&void 0!==arguments[0]?arguments[0]:"default"},_=function(e,t,n){var i=f(n,t,e);return r?(i=r.applyFilters("i18n.gettext_with_context",i,e,t,n),r.applyFilters("i18n.gettext_with_context_"+m(n),i,e,t,n)):i};if(e&&u(e,t),r){var p=function(e){s.test(e)&&d()};r.addAction("hookAdded","core/i18n",p),r.addAction("hookRemoved","core/i18n",p)}return{getLocaleData:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"default";return n.data[e]},setLocaleData:u,resetLocaleData:function(e,t){n.data={},n.pluralForms={},u(e,t)},subscribe:function(e){return o.add(e),function(){return o.delete(e)}},__:function(e,t){var n=f(t,void 0,e);return r?(n=r.applyFilters("i18n.gettext",n,e,t),r.applyFilters("i18n.gettext_"+m(t),n,e,t)):n},_x:_,_n:function(e,t,n,i){var o=f(i,void 0,e,t,n);return r?(o=r.applyFilters("i18n.ngettext",o,e,t,n,i),r.applyFilters("i18n.ngettext_"+m(i),o,e,t,n,i)):o},_nx:function(e,t,n,i,o){var a=f(o,i,e,t,n);return r?(a=r.applyFilters("i18n.ngettext_with_context",a,e,t,n,i,o),r.applyFilters("i18n.ngettext_with_context_"+m(o),a,e,t,n,i,o)):a},isRTL:function(){return"rtl"===_("ltr","text direction")},hasTranslation:function(e,t,i){var o,a,l=t?t+""+e:e,s=!(null===(o=n.data)||void 0===o||null===(a=o[null!=i?i:"default"])||void 0===a||!a[l]);return r&&(s=r.applyFilters("i18n.has_translation",s,e,t,i),s=r.applyFilters("i18n.has_translation_"+m(i),s,e,t,i)),s}}}},5839:(e,t,r)=>{"use strict";r.d(t,{__:()=>a});var n=r(772),i=r(2133),o=(0,n.h)(void 0,void 0,i.se),a=(o.getLocaleData.bind(o),o.setLocaleData.bind(o),o.resetLocaleData.bind(o),o.subscribe.bind(o),o.__.bind(o));o._x.bind(o),o._n.bind(o),o._nx.bind(o),o.isRTL.bind(o),o.hasTranslation.bind(o)},9575:(e,t,r)=>{"use strict";r.d(t,{__:()=>n.__}),r(181),r(772);var n=r(5839)},181:(e,t,r)=>{"use strict";var n=r(8616),i=r.n(n);r(7604),i()(console.error)},1105:(e,t,r)=>{"use strict";r.r(t),r.d(t,{addonError:()=>c,afterAddonInstall:()=>d,extractErrorFromAddOnResponse:()=>s,toggleAddonState:()=>l});var n=r(9575),i=frmDom,o=i.div,a=i.svg;function l(e,t){var r,n=null!==(r=window.ajaxurl)&&void 0!==r?r:frm_js.ajax_url;jQuery(".frm-addon-error").remove();var i=jQuery(e),o=i.attr("rel"),a=i.parent(),l=a.parent().find(".addon-status-label");i.addClass("frm_loading_button"),jQuery.ajax({url:n,type:"POST",async:!0,cache:!1,dataType:"json",data:{action:t,nonce:frmGlobal.nonce,plugin:o},success:function(e){var r,n,o;"string"!=typeof(e=null!==(r=null===(n=e)||void 0===n?void 0:n.data)&&void 0!==r?r:e)&&"string"==typeof e.message&&(void 0!==e.saveAndReload&&(o=e.saveAndReload),e=e.message);var u=s(e);u?c(u,a,i):(d(e,i,l,a,o,t),wp.hooks.doAction("frm_update_addon_state",e))},error:function(){i.removeClass("frm_loading_button")}})}function s(e){return"string"!=typeof e&&(void 0===e.success||!e.success)&&(e.form&&jQuery(e.form).is("#message")?{message:jQuery(e.form).find("p").html()}:e)}function d(e,t,r,i,l){var s=arguments.length>5&&void 0!==arguments[5]?arguments[5]:"frm_activate_addon",d=frm_admin_js,c=document.querySelectorAll(".frm-addon-status");c.forEach(function(t){t.textContent=e,t.style.display="block"}),t.css({opacity:"0"}),document.querySelectorAll(".frm-oneclick").forEach(function(e){e.style.display="none"}),function(){var e=document.getElementById("frm_upgrade_modal");if(e){e.classList.add("frm-success");var t=e.querySelector(".frm-upgrade-message");if(t){var r=t.querySelector("img");t.replaceChildren((0,n.__)("Great! Everything's ready to go!","formidable"),document.createElement("br"),(0,n.__)("You just need to refresh the builder so the new field becomes available.","formidable")),r&&t.append(r)}var i=document.querySelector(".frm-addon-status");i&&(i.textContent="");var o,l=e.querySelector(".frm-circled-icon");if(l)l.classList.add("frm-circled-icon-green"),null===(o=l.querySelector("svg"))||void 0===o||o.replaceWith(a({href:"#frm_checkmark_icon"}))}}();var f={frm_activate_addon:{class:"frm-addon-active",message:d.active},frm_deactivate_addon:{class:"frm-addon-installed",message:d.installed},frm_uninstall_addon:{class:"frm-addon-not-installed",message:d.not_installed}};f.frm_install_addon=f.frm_activate_addon;var m=r[0];m&&(m.textContent=f[s].message);var _=i[0].parentElement;_.classList.remove("frm-addon-not-installed","frm-addon-installed","frm-addon-active"),_.classList.add(f[s].class),t[0].classList.remove("frm_loading_button"),document.querySelectorAll(".frm-admin-page-import, #frm-admin-smtp, #frm-welcome").length>0?window.location.reload():["settings","form_builder"].includes(l)&&c.forEach(function(e){var t=null!==e.closest("#frm_upgrade_modal");e.append(function(e,t){var r,i=[u(e)];return t&&i.push(((r=document.createElement("a")).setAttribute("href","#"),r.classList.add("button","button-secondary","frm-button-secondary","dismiss"),r.textContent=(0,n.__)("Not Now","formidable"),r)),o({className:"frm-save-and-reload-options",children:i})}(l,t))})}function c(e,t,r){e.form?(jQuery(".frm-inline-error").remove(),r.closest(".frm-card").html(e.form).css({padding:5}).find("#upgrade").attr("rel",r.attr("rel")).on("click",f)):(t.append('

'+e.message+"

"),r.removeClass("frm_loading_button"),jQuery(".frm-addon-error").delay(4e3).fadeOut())}function u(e){var t=document.createElement("button");return t.classList.add("frm-save-and-reload","button","button-primary","frm-button-primary"),t.textContent=(0,n.__)("Save and Reload","formidable"),t.addEventListener("click",function(){var t;"form_builder"===e?((t=document.getElementById("frm_submit_side_top")).classList.contains("frm_submit_ajax")&&t.setAttribute("data-new-addon-installed",!0),t.click()):"settings"===e&&function(){var e=document.getElementById("form_settings_page");if(null!==e){var t=e.querySelector("form.frm_form_settings");null!==t&&(wp.hooks.doAction("frm_reset_fields_updated"),t.submit())}}()}),t}function f(e){e.preventDefault();var t=jQuery(this),r=t.parent().parent(),n=t.attr("rel");t.addClass("frm_loading_button"),jQuery.ajax({url:ajaxurl,type:"POST",async:!0,cache:!1,dataType:"json",data:{action:"frm_install_addon",nonce:frmAdminJs.nonce,plugin:n,hostname:r.find("#hostname").val(),username:r.find("#username").val(),password:r.find("#password").val()},success:function(e){var n,i,o=s(e=null!==(n=null===(i=e)||void 0===i?void 0:i.data)&&void 0!==n?n:e);o?c(o,r,t):d(e,t,message,r)},error:function(){t.removeClass("frm_loading_button")}})}},4260:(e,t,r)=>{"use strict";r.r(t),r.d(t,{addOneClick:()=>o,initModal:()=>a,initUpgradeModal:()=>l});var n=r(9575),i=frmDom.svg;function o(e,t,r){var o;if("modal"===t)o=document.getElementById("frm_upgrade_modal");else{if("tab"!==t)return;o=document.getElementById(e.getAttribute("href").substr(1))}var a,l=o.querySelector(".frm-oneclick"),s=o.querySelector(".frm-upgrade-message"),d=o.querySelector(".frm-upgrade-link"),c=o.querySelector(".frm-oneclick-button"),u=o.querySelector(".frm-addon-status"),f=e.getAttribute("data-oneclick"),m=e.getAttribute("data-message"),_="block",p="block",g="none",y=o.querySelector(".frm-circled-icon");y&&(y.classList.remove("frm-circled-icon-green"),null===(a=y.querySelector("svg"))||void 0===a||a.replaceWith(i({href:"#frm_filled_lock_icon"})));var h=o.querySelector(".frm-learn-more");if(h&&(h.href=e.dataset.learnMore),null!==l&&null!==c&&void 0!==f&&f){null===m&&(p="none"),_="none",g="block",f=JSON.parse(f),c.className=c.className.replace(" frm-install-addon","").replace(" frm-activate-addon",""),c.className=c.className+" "+f.class,c.rel=f.url,l.textContent=(0,n.__)("This plugin is not activated. Would you like to activate it now?","formidable"),c.textContent=(0,n.__)("Activate","formidable");var v=e.querySelector("use");v&&(null==y||y.querySelector("svg").replaceWith(i({href:v.getAttribute("href")||v.getAttribute("xlink:href"),classList:["frm_svg32"]})))}m||(m=s.getAttribute("data-default")),void 0!==r&&(m=m.replace('',r)),s.innerHTML=m,e.dataset.upsellImage&&s.append(frmDom.img({src:e.dataset.upsellImage,alt:e.dataset.upgrade})),d.href=function(e,t){var r=e.getAttribute("data-link");return null!=r&&""!==r||(r=t.getAttribute("data-default")),r}(e,d),u.style.display="none",l&&(l.style.display=g),c&&(c.style.display="block"===g?"inline-block":g),s.style.display=p,d.style.display="block"===_?"inline-block":_;var b=d.closest(".frm-upgrade-modal-actions");b&&(b.style.display="block"===_?"flex":_)}function a(e,t){var r=jQuery(e);if(!r.length)return!1;void 0===t&&(t="552px");var n={dialogClass:"frm-dialog",modal:!0,autoOpen:!1,closeOnEscape:!0,width:t,resizable:!1,draggable:!1,open:function(){var e,t;jQuery(".ui-dialog-titlebar").addClass("frm_hidden").removeClass("ui-helper-clearfix"),jQuery("#wpwrap").addClass("frm_overlay"),jQuery(".frm-dialog").removeClass("ui-widget ui-widget-content ui-corner-all"),r.removeClass("ui-dialog-content ui-widget-content"),e=r,t=function(){e.dialog("close")},jQuery(".ui-widget-overlay").on("click",t),e.on("click","a.dismiss",t)},close:function(){jQuery("#wpwrap").removeClass("frm_overlay"),jQuery(".spinner").css("visibility","hidden"),this.removeAttribute("data-option-type");var e=document.getElementById("bulk-option-type");e&&(e.value="")}};return r.dialog(n),r}function l(){var e=a("#frm_upgrade_modal");function t(t){var r,n,i;if((r=t.target).classList){var a=r.classList.contains("frm_show_expired_modal")||null!==r.querySelector(".frm_show_expired_modal")||r.closest(".frm_show_expired_modal");if("change"===t.type&&r.classList.contains("frm_select_with_upgrade")){var l=r.options[r.selectedIndex];l&&l.dataset.upgrade&&(r=l)}if(!r.dataset.upgrade){var s=r.closest("[data-upgrade]");if(!s){if(!(s=r.closest(".frm_field_box")))return;r.dataset.upgrade=""}r=s}if(a)wp.hooks.doAction("frm_show_expired_modal",r);else{var d=r.dataset.upgrade;if(d&&!r.classList.contains("frm_show_upgrade_tab")){t.preventDefault();var c=e.get(0),u=c.querySelector(".frm_lock_icon");u&&(u.style.display="block",u.classList.remove("frm_lock_open_icon"),u.querySelector("use").setAttribute("href","#frm_lock_icon"));var f="frm_upgrade_modal_image",m=document.getElementById(f);m&&m.remove(),r.dataset.image&&u&&(u.style.display="none",u.parentNode.insertBefore(frmDom.img({id:f,src:frmGlobal.url+"/images/"+r.dataset.image}),u));var _=c.querySelector(".license-level");_&&(_.textContent=function(e){return e.dataset.requires?e.dataset.requires:"Pro"}(r)),o(r,"modal",d),c.querySelector(".frm_are_not_installed").style.display=r.dataset.image||r.dataset.oneclick?"none":"inline-block",c.querySelector(".frm-upgrade-modal-title-prefix").style.display=r.dataset.oneclick?"inline":"none",c.querySelector(".frm_feature_label").textContent=d,c.querySelector(".frm-upgrade-modal-title-suffix").style.display="none",c.querySelector("h2").style.display="block",e.dialog("open");var p=c.querySelector(".button-primary:not(.frm-oneclick-button)");n=p.getAttribute("href").replace(/(medium=)[a-z_-]+/gi,"$1"+r.getAttribute("data-medium")),null===(i=r.getAttribute("data-content"))&&(i=""),n=n.replace(/(content=)[a-z_-]+/gi,"$1"+i),p.setAttribute("href",n)}}}}!1!==e&&(document.addEventListener("click",t),frmDom.util.documentOn("change","select.frm_select_with_upgrade",t))}},8616:e=>{e.exports=function(e,t){var r,n,i=0;function o(){var o,a,l=r,s=arguments.length;e:for(;l;){if(l.args.length===arguments.length){for(a=0;a{var n;!function(){"use strict";var i={not_string:/[^s]/,not_bool:/[^t]/,not_type:/[^T]/,not_primitive:/[^v]/,number:/[diefg]/,numeric_arg:/[bcdiefguxX]/,json:/[j]/,not_json:/[^j]/,text:/^[^\x25]+/,modulo:/^\x25{2}/,placeholder:/^\x25(?:([1-9]\d*)\$|\(([^)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-gijostTuvxX])/,key:/^([a-z_][a-z_\d]*)/i,key_access:/^\.([a-z_][a-z_\d]*)/i,index_access:/^\[(\d+)\]/,sign:/^[+-]/};function o(e){return function(e,t){var r,n,a,l,s,d,c,u,f,m=1,_=e.length,p="";for(n=0;n<_;n++)if("string"==typeof e[n])p+=e[n];else if("object"==typeof e[n]){if((l=e[n]).keys)for(r=t[m],a=0;a=0),l.type){case"b":r=parseInt(r,10).toString(2);break;case"c":r=String.fromCharCode(parseInt(r,10));break;case"d":case"i":r=parseInt(r,10);break;case"j":r=JSON.stringify(r,null,l.width?parseInt(l.width):0);break;case"e":r=l.precision?parseFloat(r).toExponential(l.precision):parseFloat(r).toExponential();break;case"f":r=l.precision?parseFloat(r).toFixed(l.precision):parseFloat(r);break;case"g":r=l.precision?String(Number(r.toPrecision(l.precision))):parseFloat(r);break;case"o":r=(parseInt(r,10)>>>0).toString(8);break;case"s":r=String(r),r=l.precision?r.substring(0,l.precision):r;break;case"t":r=String(!!r),r=l.precision?r.substring(0,l.precision):r;break;case"T":r=Object.prototype.toString.call(r).slice(8,-1).toLowerCase(),r=l.precision?r.substring(0,l.precision):r;break;case"u":r=parseInt(r,10)>>>0;break;case"v":r=r.valueOf(),r=l.precision?r.substring(0,l.precision):r;break;case"x":r=(parseInt(r,10)>>>0).toString(16);break;case"X":r=(parseInt(r,10)>>>0).toString(16).toUpperCase()}i.json.test(l.type)?p+=r:(!i.number.test(l.type)||u&&!l.sign?f="":(f=u?"+":"-",r=r.toString().replace(i.sign,"")),d=l.pad_char?"0"===l.pad_char?"0":l.pad_char.charAt(1):" ",c=l.width-(f+r).length,s=l.width&&c>0?d.repeat(c):"",p+=l.align?f+r+s:"0"===d?f+s+r:s+f+r)}return p}(function(e){if(l[e])return l[e];for(var t,r=e,n=[],o=0;r;){if(null!==(t=i.text.exec(r)))n.push(t[0]);else if(null!==(t=i.modulo.exec(r)))n.push("%");else{if(null===(t=i.placeholder.exec(r)))throw new SyntaxError("[sprintf] unexpected placeholder");if(t[2]){o|=1;var a=[],s=t[2],d=[];if(null===(d=i.key.exec(s)))throw new SyntaxError("[sprintf] failed to parse named argument key");for(a.push(d[1]);""!==(s=s.substring(d[0].length));)if(null!==(d=i.key_access.exec(s)))a.push(d[1]);else{if(null===(d=i.index_access.exec(s)))throw new SyntaxError("[sprintf] failed to parse named argument key");a.push(d[1])}t[2]=a}else o|=2;if(3===o)throw new Error("[sprintf] mixing positional and named placeholders is not (yet) supported");n.push({placeholder:t[0],param_no:t[1],keys:t[2],sign:t[3],pad_char:t[4],align:t[5],width:t[6],precision:t[7],type:t[8]})}r=r.substring(t[0].length)}return l[e]=n}(e),arguments)}function a(e,t){return o.apply(null,[e].concat(t||[]))}var l=Object.create(null);"undefined"!=typeof window&&(window.sprintf=o,window.vsprintf=a,void 0===(n=function(){return{sprintf:o,vsprintf:a}}.call(t,r,t,e))||(e.exports=n))}()},5397:(e,t,r)=>{"use strict";r.d(t,{A:()=>o});var n=r(1364),i={contextDelimiter:"",onMissingKey:null};function o(e,t){var r;for(r in this.data=e,this.pluralForms={},this.options={},i)this.options[r]=void 0!==t&&r in t?t[r]:i[r]}o.prototype.getPluralForm=function(e,t){var r,i,o,a=this.pluralForms[e];return a||("function"!=typeof(o=(r=this.data[e][""])["Plural-Forms"]||r["plural-forms"]||r.plural_forms)&&(i=function(e){var t,r,n;for(t=e.split(";"),r=0;r{"use strict";function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}r.d(t,{A:()=>n})},4467:(e,t,r)=>{"use strict";r.d(t,{A:()=>i});var n=r(9922);function i(e,t,r){return(t=(0,n.A)(t))in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}},2327:(e,t,r)=>{"use strict";r.d(t,{A:()=>i});var n=r(2284);function i(e,t){if("object"!=(0,n.A)(e)||!e)return e;var r=e[Symbol.toPrimitive];if(void 0!==r){var i=r.call(e,t||"default");if("object"!=(0,n.A)(i))return i;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===t?String:Number)(e)}},9922:(e,t,r)=>{"use strict";r.d(t,{A:()=>o});var n=r(2284),i=r(2327);function o(e){var t=(0,i.A)(e,"string");return"symbol"==(0,n.A)(t)?t:t+""}},2284:(e,t,r)=>{"use strict";function n(e){return n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},n(e)}r.d(t,{A:()=>n})}},t={};function r(n){var i=t[n];if(void 0!==i)return i.exports;var o=t[n]={exports:{}};return e[n](o,o.exports,r),o.exports}function n(e){return function(e){if(Array.isArray(e))return o(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||i(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function i(e,t){if(e){if("string"==typeof e)return o(e,t);var r={}.toString.call(e).slice(8,-1);return"Object"===r&&e.constructor&&(r=e.constructor.name),"Map"===r||"Set"===r?Array.from(e):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?o(e,t):void 0}}function o(e,t){(null==t||t>e.length)&&(t=e.length);for(var r=0,n=Array(t);r{var t=e&&e.__esModule?()=>e.default:()=>e;return r.d(t,{a:t}),t},r.d=(e,t)=>{for(var n in t)r.o(t,n)&&!r.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},window.FrmFormsConnect=window.FrmFormsConnect||function(e,t,r){var n={messageBox:null,reset:null,setElements:function(){n.messageBox=e.querySelector(".frm_pro_license_msg"),n.reset=e.getElementById("frm_reconnect_link")}},i={init:function(){n.setElements(),r(e.getElementById("frm_deauthorize_link")).on("click",i.deauthorize),r(".frm_authorize_link").on("click",i.authorize),r(".frm-dashboard-license-options").on("click","#frm_deauthorize_link",i.deauthorize),r(".frm-dashboard-license-options").on("click","#frm_reconnect_link",i.reauthorize),null!==n.reset&&r(n.reset).on("click",i.reauthorize)},authorize:function(){var t=this,n=this.getAttribute("data-plugin"),o=e.getElementById("edd_"+n+"_license_key"),a=o.value,l=e.getElementById("proplug-wpmu");this.classList.add("frm_loading_button"),l=null===l?0:l.checked?1:0,r.ajax({type:"POST",url:ajaxurl,dataType:"json",data:{action:"frm_addon_activate",license:a,plugin:n,wpmu:l,nonce:frmGlobal.nonce},success:function(e){i.afterAuthorize(e,o),t.classList.remove("frm_loading_button")}})},afterAuthorize:function(e,t){!0===e.success&&(t.value="•••••••••••••••••••"),wp.hooks.doAction("frm_after_authorize",e),i.showMessage(e)},showProgress:function(e){null===n.messageBox&&n.setElements();var t=n.messageBox;null!==t&&(!0===e.success?(t.classList.remove("frm_error_style"),t.classList.add("frm_message","frm_updated_message")):(t.classList.add("frm_error_style"),t.classList.remove("frm_message","frm_updated_message")),t.classList.remove("frm_hidden"),t.innerHTML=e.message)},showMessage:function(r){null===n.messageBox&&n.setElements();var o=n.messageBox;!0===r.success&&(i.showAuthorized(!0),i.showInlineSuccess(),wp.hooks.doAction("frmAdmin.afterLicenseAuthorizeSuccess",{msg:r})),i.showProgress(r),""!==r.message&&(setTimeout(function(){o.innerHTML="",o.classList.add("frm_hidden"),o.classList.remove("frm_error_style","frm_message","frm_updated_message")},1e4),e.querySelector(".frm-admin-page-dashboard")&&setTimeout(function(){t.location.reload()},1e3))},showAuthorized:function(t){var r=t?"unauthorized":"authorized",n=t?"authorized":"unauthorized",i=e.querySelectorAll(".frm_"+r+"_box");i.length&&i.forEach(function(e){e.className=e.className.replace("frm_"+r+"_box","frm_"+n+"_box")})},showInlineSuccess:function(){var t=e.querySelectorAll(".frm-confirm-msg [data-success]");t.length&&t.forEach(function(e){e.innerHTML=frmAdminBuild.purifyHtml(e.getAttribute("data-success"))})},reauthorize:function(){return this.innerHTML='',r.ajax({type:"POST",url:ajaxurl,dataType:"json",data:{action:"frm_reset_cache",plugin:"formidable_pro",nonce:frmGlobal.nonce},success:function(e){n.reset.textContent=e.message,"1"===n.reset.getAttribute("data-refresh")&&t.location.reload()}}),!1},deauthorize:function(){if(!confirm(frmGlobal.deauthorize))return!1;var t=this.getAttribute("data-plugin"),n=e.getElementById("edd_"+t+"_license_key"),o=n.value,a=this;return this.innerHTML='',r.ajax({type:"POST",url:ajaxurl,data:{action:"frm_addon_deactivate",license:o,plugin:t,nonce:frmGlobal.nonce},success:function(){i.showAuthorized(!1),n.value="",a.replaceWith("Disconnected"),wp.hooks.doAction("frmAdmin.afterLicenseDeauthorizeSuccess",{})}}),!1}};return i}(document,window,jQuery),window.frmAdminBuildJS=function(){var e,t,o=frm_admin_js,l=frmDom,s=l.tag,d=l.div,c=l.span,u=l.a,f=l.svg,m=l.img,_=frmDom.util.onClickPreventDefault,p=frmDom.ajax.doJsonPost;o.contextualShortcodes=(t=null===(e=document.getElementById("frm_adv_info"))||void 0===e?void 0:e.dataset.contextualShortcodes)?((t=JSON.parse(t)).addressSelector="[id^=email_to], [id^=from_], [id^=cc], [id^=bcc]",t.bodySelector="[id^=email_message_]",t):[];var g,y,h,v={save:f({href:"#frm_save_icon"}),drag:f({href:"#frm_drag_icon",classList:["frm_drag_icon","frm-drag"]})},b=jQuery(document.getElementById("frm-show-fields")),j=document.getElementById("new_fields"),w=document.getElementById("form_id"),Q=!1,x=0,E=0,k=0,A={},S=0,L=wp.i18n,I=L.__,B=L.sprintf,q={dragging:!1};null!==w&&(E=w.value);var C,N=new URL(window.location.href),T=N.searchParams,O=document.getElementById("frm_builder_page");function F(e){e.stopPropagation(),e.preventDefault(),D(this)}function D(e){var t=e.getAttribute("data-frmverify"),r=e.getAttribute("data-loaded-from");return null===t||"frm-confirmed-click"===e.id||("entries-list"===r?wp.hooks.applyFilters("frm_on_multiple_entries_delete",{link:e,initModal:Bo}):function(e){var t,r,n,i,o,a=Bo("#frm_confirm_modal","400px"),l=document.getElementById("frm-confirmed-click");if(!1===a)return!1;if(l&&(l.style.display="block"),o=(t=e.getAttribute("data-frmverify"))?e.getAttribute("data-frmverify-btn"):"",(r=jQuery(".frm-confirm-msg")).empty(),t&&(r.append(document.createTextNode(t)),o&&(null==l||l.classList.add(o))),i=e.dataset,l){for(n in l.dataset)l.removeAttribute("data-"+n);for(n in i)"frmverify"!==n&&l.setAttribute("data-"+n,i[n])}return wp.hooks.doAction("frmAdmin.beforeOpenConfirmModal",{$info:a,link:e}),a.dialog("open"),null==l||l.setAttribute("href",e.getAttribute("href")||e.getAttribute("data-href")),!1}(e))}function M(e){var t=Bo("#frm_info_modal","400px");return!1===t||(jQuery(".frm-info-msg").html(e),t.dialog("open")),!1}function P(e){var t=this.getAttribute("data-frmtoggle"),r=this.getAttribute("data-toggletext"),n=jQuery(t);return e.preventDefault(),n.toggle(),null!==r&&""!==r&&(this.setAttribute("data-toggletext",this.innerHTML),this.textContent=r),!1}function H(e){var t=this.getAttribute("data-frmhide"),r=this.getAttribute("data-frmshow"),n=this.getAttribute("data-frmuncheck"),i=n?n.split(","):[];"INPUT"!==this.nodeName||"checkbox"!==this.type||this.checked||(null!==t?(r=t,t=null):null!==r&&(t=r,r=null)),e.preventDefault();var o=this.getAttribute("data-toggleclass")||"frm_hidden";null!==t&&jQuery(t).addClass(o),null!==r&&jQuery(r).removeClass(o);var a=this.parentNode.querySelectorAll("a.current");if(null!==a){for(var l=0;l1&&(e="",t=""):0===i.indexOf("frm_postmeta_")&&(jQuery("#frm_postmeta_rows .frm_postmeta_row").length<2&&(e=".frm_add_postmeta_row.button"),jQuery(".frm_toggle_cf_opts").length&&jQuery("#frm_postmeta_rows .frm_postmeta_row:not(#"+i+")").last().length&&(""!==e&&(e+=","),e+="#"+jQuery("#frm_postmeta_rows .frm_postmeta_row:not(#"+i+")").last().attr("id")+" .frm_toggle_cf_opts"));var o=document.getElementById(i),a=jQuery(o);return a.fadeOut(300,function(){var r;a.remove(),Mi(),""!==t&&jQuery(t).hide(),""!==e&&jQuery(e+" a,"+e).removeClass("frm_hidden").fadeIn("slow"),this.closest(".frm_form_action_settings")&&function(e){si(e);var t={type:e};wp.hooks.doAction("frm_after_action_removed",t)}(this.closest(".frm_form_action_settings").querySelector(".frm_action_name").value),null===(r=document.querySelector(".tooltip"))||void 0===r||r.remove()}),void 0!==r&&(r=jQuery(r)).fadeOut(400,function(){r.remove()}),""!==e&&jQuery(this).closest(".frm_logic_rows").fadeOut("slow"),wp.hooks.doAction("frm_admin_tag_removed",i,o),!1}}function G(e,t){void 0===t&&(t=this),Ze(t,!1);var r=jQuery(t).closest(".frm_form_action_settings"),n=e.target;if(r.length&&void 0!==n){var i=n.parentElement.className;if("string"==typeof i&&(i.includes("frm_email_icons")||i.includes("frm_toggle")))return void e.stopPropagation()}var o=r.children(".widget-inside");if(r.length&&o.find("p, div, table").length<1){var a=r.find('input[name$="[ID]"]').val(),l=r.find('input[name$="[post_excerpt]"]').val();l&&(o.html(''),r.find(".spinner").fadeIn("slow"),jQuery.ajax({type:"POST",url:ajaxurl,data:{action:"frm_form_action_fill",action_id:a,action_type:l,nonce:frmGlobal.nonce},success:function(e){o.html(e),so(),Xn("#"+r.attr("id")),yo(o),jQuery(t).trigger("frm-action-loaded"),wp.hooks.doAction("frm_filled_form_action",o)}}))}jQuery(t).closest(".frm_field_box").siblings().find(".widget-inside").slideUp("fast"),void 0!==t.className&&t.className.includes("widget-action")||jQuery(t).closest(".start_divider").length<1||((o=jQuery(t).closest("div.widget").children(".widget-inside")).is(":hidden")?o.slideDown("fast"):o.slideUp("fast"))}function W(){var e=this.getAttribute("href");if(void 0===e)return!1;var t=e.replace("#","."),r=jQuery(this);r.closest("li").addClass("frm-tabs active").siblings("li").removeClass("frm-tabs active starttab"),r.closest("div").children(".tabs-panel").not(e).not(t).hide();var n=document.getElementById(e.replace("#",""));return n&&(n.style.display="block"),"frm_insert_fields_tab"!==this.id||this.closest("#frm_adv_info")||$e(),!1}function U(e,t){var r=(e=jQuery(e)).attr("href");if(void 0!==r){var n,i,o=r.replace("#",".");if(e.closest("li").addClass("frm-tabs active").siblings("li").removeClass("frm-tabs active starttab"),e.closest("div").find(".tabs-panel").length)e.closest("div").children(".tabs-panel").not(r).not(o).hide();else if(null!==document.getElementById("form_global_settings")){var a=e.data("frmajax");e.closest(".frm_wrap").find(".tabs-panel, .hide_with_tabs").hide(),void 0!==a&&"1"==a&&(n=r.replace("#",""),(i=jQuery(".frm_"+n+"_ajax")).length&&jQuery.ajax({type:"POST",url:ajaxurl,data:{action:"frm_settings_tab",tab:n.replace("_settings",""),nonce:frmGlobal.nonce},success:function(e){i.replaceWith(e)}}))}else jQuery("#frm-categorydiv .tabs-panel, .hide_with_tabs").hide();jQuery(r).show(),jQuery(o).show(),Vi(),"auto"!==t&&(jQuery(".frm_updated_message").hide(),jQuery(".frm_warning_style").hide()),jQuery(e).closest("#frm_adv_info").length||(jQuery(".frm_form_settings").length?jQuery(".frm_form_settings").attr("action","?page=formidable&frm_action=settings&id="+jQuery('.frm_form_settings input[name="id"]').val()+"&t="+r.replace("#","")):jQuery(".frm_settings_form").attr("action","?page=formidable-settings&t="+r.replace("#","")))}}function V(e){var t,r;document.querySelectorAll(e).forEach(function(e){$(e),Array.from(e.children).forEach(function(e){return X(e,".frm-move")});var t=jQuery(e).children('[data-type="divider"]').children(".divider_section_only");t.length&&$(t)}),t=jQuery("#frm_builder_page"),r={items:".frm_sortable_field_opts li",axis:"y",opacity:.65,forcePlaceholderSize:!1,handle:".frm-drag",helper:function(e,t){return Q=t.clone().insertAfter(t),t.clone()},stop:function(e,t){Q&&Q.remove(),on(t.item.attr("id").replace("frm_delete_field_","").replace("-"+t.item.data("optkey")+"_container","")),Mi()}},jQuery(t).sortable(r)}function $(e){jQuery(e).droppable({accept:".frmbutton, li.frm_field_box",deactivate:re,over:K,out:J,tolerance:"pointer"})}function K(e,t){var r=function(e){return e.classList.contains("divider_section_only")&&(e=jQuery(e).nextAll(".start_divider.frm_sorting").get(0)),e}(e.target);if(!je(t.draggable[0],r,e))return r.classList.remove("frm-over-droppable"),void jQuery(r).parents("ul.frm_sorting").addClass("frm-over-droppable");document.querySelectorAll(".frm-over-droppable").forEach(function(e){return e.classList.remove("frm-over-droppable")}),r.classList.add("frm-over-droppable"),jQuery(r).parents("ul.frm_sorting").addClass("frm-over-droppable")}function J(e){e.target.classList.remove("frm-over-droppable")}function X(e,t){var r={helper:Y,revert:"invalid",delay:10,start:Z,stop:ee,drag:te,cursor:"grabbing",refreshPositions:!0,cursorAt:{top:0,left:90}};"string"==typeof t&&(r.handle=t),jQuery(e).draggable(r)}function Y(e){var t,r=e.delegateTarget;if(Qe(r)){var n=document.getElementById("frm-insert-fields").querySelector(".frm_ttext").cloneNode(!0);return n.querySelector("use").setAttributeNS("http://www.w3.org/1999/xlink","href","#frm_field_group_layout_icon"),n.querySelector("span").textContent=I("Field Group","formidable"),n.classList.add("frm_field_box"),n.classList.add("ui-sortable-helper"),n}if(r.classList.contains("frmbutton"))return(t=r.cloneNode(!0)).classList.add("ui-sortable-helper"),r.classList.add("frm-new-field"),t;if(r.hasAttribute("data-ftype")){var i=r.getAttribute("data-ftype");if(t=document.getElementById("frm-insert-fields").querySelector(".frm_t"+i))return(t=t.cloneNode(!0)).classList.add("form-field"),t.classList.add("ui-sortable-helper"),t.cloneNode(!0)}return d({className:"frmbutton"})}function Z(e,t){if(e.target.classList.contains("frm_at_limit"))return Se(),!1;q.dragging=!0;var r,n=y;n.classList.add("frm-dragging-field"),document.body.classList.add("frm-dragging"),t.helper.addClass("frm-sortable-helper"),t.helper.initialOffset=n.scrollTop,e.target.classList.add("frm-drag-fade"),yr(),(r=document.querySelectorAll("ul.start_divider")).length&&r.forEach(function(e){[].slice.call(e.children).forEach(function(e){(0===e.children.length||1===e.children.length&&"ul"===e.firstElementChild.nodeName.toLowerCase()&&0===e.firstElementChild.children.length)&&e.remove()})}),Fe(),Ne(),z()}function ee(){y.classList.remove("frm-dragging-field"),document.body.classList.remove("frm-dragging");var e=document.querySelector(".frm-drag-fade");e&&e.classList.remove("frm-drag-fade")}function te(e,t){!function(e){h.scrollTop(function(t,r){var n=e.clientY,i=y.offsetHeight,o=e.clientY-y.offsetTop,a=o-i/2;return o>i-50&&n>5?r+.1*a:o<70&&n<130?r-Math.abs(.1*a):r})}(e);var r=e.target,n=function(){for(var e=document.getElementById("frm-show-fields");e.querySelector(".frm-over-droppable");)e=e.querySelector(".frm-over-droppable");return"frm-show-fields"!==e.id||e.classList.contains("frm-over-droppable")||(e=!1),e}(),i=document.getElementById("frm_drag_placeholder");if(je(r,n,e)){i||(i=s("li",{id:"frm_drag_placeholder",className:"sortable-placeholder"}));var o,a=t.helper.get(0);if((a.classList.contains("form-field")||a.classList.contains("frm_field_box"))&&(a.style.transform="translateY("+(o=t.helper,y.scrollTop-o.initialOffset+"px)")),"frm-show-fields"===n.id||n.classList.contains("start_divider"))return i.style.left=0,void function(e){var t,r=e.y,n=e.placeholder,i=jQuery(e.droppable),o=i.children().not(".edit_field_type_end_divider");if(0===o.length)i.prepend(n),t=0;else{var a=ne(i,r);if(a===o.length){var l=jQuery(o.get(a-1));t=l.offset().top+l.outerHeight(),i.append(n);var s=i.children(".edit_field_type_end_divider");s.length&&i.append(s)}else t=jQuery(o.get(a)).offset().top,jQuery(o.get(a)).before(n)}t-=i.offset().top,n.style.top=t+"px"}({droppable:n,y:e.clientY,placeholder:i});i.style.top="",function(e){var t,r=e.x,n=e.placeholder,i=jQuery(e.droppable),o=oe(i);if(o.length){var a=function(e,t){var r,n,i,o,a=oe(e);for(o=0,r=a.length-1;r>=0;--r)if(n=a.get(r),t>(i=jQuery(n).offset().left)){o=r,t>i+jQuery(n).outerWidth()/2&&(o=r+1);break}return o}(i,r);if(a===o.length){var l=jQuery(o.get(a-1));t=l.offset().left+l.outerWidth(),i.append(n)}else t=jQuery(o.get(a)).offset().left,jQuery(o.get(a)).before(n),t-=0===a?4:8;t-=i.offset().left,n.style.left=t+"px"}}({droppable:n,x:e.clientX,placeholder:i})}else i&&i.remove()}function re(e,t){if(q.dragging){q.dragging=!1;var r=t.draggable[0],n=document.getElementById("frm_drag_placeholder");if(!n)return t.helper.remove(),void g();!function(e){if(e.previousElementSibling&&e.previousElementSibling.classList.contains("frm-is-collapsed")){var t=jQuery(e).prevUntil('[data-type="break"]');if(t.length){var r=t.find(".frm-collapse-page").get(0);r&&r.click()}}}(n);var i=t.helper.parent(),o=t.helper.get(0).closest("ul.start_divider"),a=n.closest("ul.start_divider");r.classList.contains("frm-new-field")?function(e){if(pe(e))wp.hooks.doAction("frm_stopped_inserting_by_dragging",e);else{var t=document.getElementById("frm_drag_placeholder"),r=e.replace("|","-")+"_"+be(),n=s("li",{id:r,className:"frm-wait frmbutton_loadingnow"}),i=jQuery(n),o=ce(jQuery(t)),a=ue(o),l=fe(o);t.parentNode.insertBefore(n,t),t.remove(),ae(i);var d=0;"summary"===e&&(d=jQuery(".frmbutton_loadingnow#"+r).prevAll('li[data-type="break"]').length?1:0),jQuery.ajax({type:"POST",url:ajaxurl,data:_e(e,l,a,d),success:function(t){ge(t,i);var r=ye(t);r&&wp.hooks.doAction("frm_after_field_added_in_form_builder",{field:t,fieldId:r,fieldType:e,form_id:a})},error:ve})}}(r.id):(function(e,t){t.parentNode.insertBefore(e,t)}(r,n),function(e){if("UL"===e.nodeName&&!e.classList.contains("start_divider")&&"frm-show-fields"!==e.id){var t=e.closest("li");t&&!t.classList.contains("ui-draggable")&&X(t,".frm-move")}}(n.parentElement));var l=o?parseInt(o.closest(".edit_field_type_divider").getAttribute("data-fid")):0,d=a?parseInt(a.closest(".edit_field_type_divider").getAttribute("data-fid")):0;n.remove(),t.helper.remove();var c=i.length?oe(i):[];!function(e,t){var r;e.length&&(t.length?ae(t.first()):(r=e.get(0).closest("li.frm_field_box"))&&!r.classList.contains("edit_field_type_divider")&&r.remove())}(i,c),function(e,t){0===t.length&&1===oe(jQuery(e.parentNode)).length||ae(jQuery(e))}(r,c),l!==d&&me(jQuery(r),o),g()}}function ne(e,t){var r,n,i,o,a=e.children().not(".edit_field_type_end_divider"),l=a.length;if(!document.querySelector(".frm-has-fields .frm_no_fields"))return 0;for(o=0,r=l-1;r>=0;--r)if(n=a.get(r),t>(i=jQuery(n).offset().top)){o=r,t>i+jQuery(n).outerHeight()/2&&(o=r+1);break}return o}function ie(){document.querySelectorAll("ul#frm-show-fields, ul.start_divider").forEach(function(e){e.childNodes.forEach(function(e){void 0!==e.classList&&(e.classList.contains("edit_field_type_end_divider")||void 0!==e.classList&&e.classList.contains("form-field")&&We(e))})}),kn(),document.querySelectorAll(".edit_field_type_end_divider").forEach(function(e){return e.parentNode.append(e)}),document.querySelectorAll("li.form_field_box:not(.form-field)").forEach(function(e){return!e.children.length&&e.remove()}),En();var e=new Event("frm_sync_after_drag_and_drop",{bubbles:!1});document.dispatchEvent(e)}function oe(e){var t=jQuery(),r=e.get(0);return r.children?(Array.from(r.children).forEach(function(e){if("none"!==e.style.display){var r=e.classList;!r.contains("form-field")||r.contains("edit_field_type_end_divider")||r.contains("frm-sortable-helper")||(t=t.add(e))}}),t):t}function ae(e,t){var r,n,i,o;void 0===t&&(t="even"),r=e.parent().children("li.form-field, li.frmbutton_loadingnow").not(".edit_field_type_end_divider"),n=r.length,i=["frm_full","frm_half","frm_third","frm_fourth","frm_sixth","frm_two_thirds","frm_three_fourths","frm1","frm2","frm3","frm4","frm5","frm6","frm7","frm8","frm9","frm10","frm11","frm12"],"even"===t&&5!==n?r.each(de(i,$t(n))):"clear"===t?r.each(de(i,"")):(o=-1!==["left","right","middle","even"].indexOf(t)?function(e){return Vt(n,t,e)}:function(e){return lr(t[e])},r.each(de(i,o))),le(e.parent(),r.length)}function le(e,t){var r,n;if(void 0!==e.offset()){if(r=t>=2,null===(n=document.getElementById("frm_field_group_controls"))){if(!r)return;(n=d()).id="frm_field_group_controls",n.setAttribute("role","group"),n.setAttribute("tabindex",0),function(e){var t,r;(t=document.createElement("span")).innerHTML='';var n=I("Set Row Layout","formidable");se(t,n),zt(t,n),(r=document.createElement("span")).innerHTML='',r.classList.add("frm-move");var i=I("Move Field Group","formidable");se(r,i),zt(r,i),e.innerHTML="",e.append(t),e.append(r),e.append(function(){var e=c({className:"dropdown"}),t=u({className:"frm_bstooltip frm-hover-icon frm-dropdown-toggle dropdown-toggle",children:[c({child:f({href:"#frm_thick_more_vert_icon"})}),c({className:"screen-reader-text",text:I("Toggle More Options Dropdown","formidable")})]});frmDom.setAttributes(t,{title:I("More Options","formidable"),"data-bs-toggle":"dropdown","data-bs-container":"body","data-bs-display":"static"}),zt(t,I("More Options","formidable")),e.append(t);var r=d({className:"frm-dropdown-menu dropdown-menu dropdown-menu-right"});return r.setAttribute("role","menu"),e.append(r),e}())}(n),O.append(n)}e.append(n),n.style.display=r?"block":"none"}}function se(e,t){e.setAttribute("data-bs-toggle","tooltip"),e.setAttribute("data-bs-container","body"),e.setAttribute("title",t),e.addEventListener("mouseover",function(){null===e.getAttribute("data-original-title")&&jQuery(e).tooltip()})}function de(e,t){return function(r){var n,i,o,a,l,s,d;for(n="function"==typeof t?t(r):t,i=e.length,l=!1,o=0;o0&&document.getElementById("form_id").value!==r||(i.last_row_field_ids=function(){var e=document.querySelector(".edit_field_type_submit");if(!e)return[];for(var t=e.parentNode.children,r=[],n=0;nt.childElementCount-1:o<=jQuery(t.querySelector(".edit_field_type_submit").closest("#frm-show-fields > li")).index()}if(n)return!(t.classList.contains("start_divider")||!we(t.parentElement)&&(!we(t.parentElement.nextElementSibling)||e.parentElement.querySelector("li.frm_field_box:not(.edit_field_type_submit)")));var a,l,s,d=t.classList.contains("start_divider")&&null!==t.closest(".repeat_section"),c=null!==t.closest(".repeat_section");if(d||c){if(e.classList.contains("edit_field_type_gdpr")||"gdpr"===e.id)return!1;if(wp.hooks.applyFilters("frm_deny_drop_in_repeater",!1,e))return!1}if(!d){if(a=oe(jQuery(t)),l=jQuery(e),!(a.length<12)&&(a.length>12||(s=l.attr("data-fid"),1!==jQuery(a).filter('[data-fid="'+s+'"]').length)))return!1;if("divider"===e.id&&t.closest(".start_divider"))return!1}return e.classList.contains("frm-new-field")?function(e,t){var r=e.classList,n=r.contains("frm_tbreak"),i=r.contains("frm_thidden"),o=r.contains("frm_tdivider"),a=r.contains("frm_tform"),l=r.contains("frm_tuser_id");return"frm-show-fields"===t.id||t.classList.contains("start_divider")?!(n||i||o||a)||(!(t.classList.contains("start_divider")||null!==t.closest(".start_divider"))||!a&&!o):!(xe(t)||i||n||l)}(e,t):function(e,t){if(Qe(e))return function(e,t){return!(!t.classList.contains("start_divider")||null!==e.querySelector(".start_divider"))}(e,t);if(e.classList.contains("edit_field_type_break"))return!1;if(t.classList.contains("start_divider"))return function(e){return!e.classList.contains("edit_field_type_form")&&!e.querySelector(".edit_field_type_form")&&!(e.classList.contains("edit_field_type_divider")||e.querySelector(".edit_field_type_divider"))}(e);var r=e.classList.contains("edit_field_type_hidden"),n=e.classList.contains("edit_field_type_user_id");return!r&&!n&&function(e,t){if(xe(t))return!1;if(jQuery(e).children("ul.frm_sorting").not(".start_divider").length>0)return!1;var r=e.classList.contains("edit_field_type_divider")||e.querySelector(".edit_field_type_divider"),n=e.classList.contains("edit_field_type_form");return null===t.closest(".start_divider")||!r&&!n}(e,t)}(e,t)}function we(e){return e&&e.matches("#frm-show-fields > li:last-child")}function Qe(e){return e.classList.contains("frm_field_box")&&!e.classList.contains("form-field")}function xe(e){return null!==e.querySelector(".edit_field_type_break, .edit_field_type_hidden, .edit_field_type_user_id")}function Ee(e){var t=document.getElementById(e),r=jQuery(t),n=[],i=function(e){var t=e.querySelector(".frm_hidden_fdata");e.classList.add("frm_load_now"),null!==t&&n.push(t.innerHTML)};i(t);for(var o=ke(t);o&&n.length<15;)i(o),o=ke(o);jQuery.ajax({type:"POST",url:ajaxurl,data:{action:"frm_load_field",field:n,form_id:E,nonce:frmGlobal.nonce},success:function(e){return function(e,t,r){var n,i;if(0===(e=e.replace(/^\s+|\s+$/g,"")).indexOf("{")){for(n in e=JSON.parse(e)){jQuery("#frm_field_id_"+n).replaceWith(e[n]);var o=document.getElementById("frm_field_id_"+n);o&&(o.querySelectorAll("[data-toggle]").forEach(function(e){return e.setAttribute("data-bs-toggle",e.getAttribute("data-toggle"))}),o.querySelectorAll(".frm-dropdown-menu").forEach(function(e){return e.classList.add("dropdown-menu")})),V("#frm_field_id_"+n+".edit_field_type_divider ul.frm_sorting"),X(document.getElementById("frm_field_id_"+n))}((i=t.nextAll(".frm_field_loading:not(.frm_load_now)")).length||(i=jQuery(document.getElementById("frm-show-fields")).find(".frm_field_loading:not(.frm_load_now)")).length)&&Ee(i.attr("id")),so(),Fr(),Ie();var a=new Event("frm_ajax_loaded_field",{bubbles:!1});a.frmFields=r.map(function(e){return JSON.parse(e)}),document.dispatchEvent(a)}else jQuery(".frm_load_now").removeClass(".frm_load_now").html("Error")}(e,r,n)}})}function ke(e){var t;return e.nextElementSibling?e.nextElementSibling:null===(t=e.parentNode)||void 0===t||null===(t=t.closest(".frm_field_box"))||void 0===t||null===(t=t.nextElementSibling)||void 0===t?void 0:t.querySelector(".form-field")}function Ae(){var e=jQuery(this);if(e.hasClass("disabled"))return!1;var t=e.closest(".frmbutton"),r=t.attr("id");if(t.hasClass("frm_at_limit"))return Se(),!1;if(!pe(r)){var n=0;"summary"===r&&(n=b.children('li[data-type="break"]').length>0?1:0);var i=E;return jQuery.ajax({type:"POST",url:ajaxurl,data:_e(r,0,i,n),success:function(e){Le(e);var t=ye(e);t&&wp.hooks.doAction("frm_after_field_added_in_form_builder",{field:e,fieldId:t,fieldType:r,form_id:i})},error:ve}),!1}}function Se(){var e=document.querySelector(".frm_wrap");if(e){var t=document.createElement("a");t.setAttribute("data-frmverify",I("This field type has reached its limit.","formidable")),e.append(t),t.click(),t.remove();var r=document.getElementById("frm-confirmed-click");r&&(r.style.display="none")}}function Le(e){document.getElementById("frm_form_editor_container").classList.add("frm-has-fields");var t=Ge(e),r=b[0].querySelector(".edit_field_type_submit");r?jQuery(r.closest(".frm_field_box:not(.form-field)")).before(t):b.append(t),Ue(e,!0),t.each(function(){$(this.querySelector("ul.frm_sorting")),X(this.querySelector(".form-field"),".frm-move")})}function Ie(){var e=!0,t=document.querySelectorAll(".frmjs_prod_field_opt_cont");b.find("li.edit_field_type_product").length>1&&(e=!1);for(var r=0;r',i.append(document.createTextNode(" ")),i.append(o),n.append(i),e.append(n)})}(t,!0===e),(r=jQuery(t)).offset().left>jQuery(window).width()-r.outerWidth()?t.style.left=-r.outerWidth()+"px":y&&r.offset().left").addClass("frm_field_box").html(jQuery("
    ").addClass("frm_grid_container frm_sorting").append(e)))}),r}function We(e){var t=s("ul",{className:"frm_grid_container frm_sorting"}),r=s("li",{className:"frm_field_box",child:t});e.replaceWith(r),t.append(e),$(t),X(r,".frm-move")}function Ue(e,t){var r,n,i=/id="(\S+)"/.exec(e),o=document.getElementById(i[1]),l="#"+i[1]+".edit_field_type_divider ul.frm_sorting.start_divider",s=jQuery(l),c=o.getAttribute("data-type");r=e,(n=d()).innerHTML=r,n.querySelectorAll(".form-field").forEach(Ve);var u,f,m=!1;if(Mi(),V(l),"quantity"===c&&function(e){var t=e.getAttribute("data-fid"),r=document.getElementById("field_options[product_field_"+t+"]");null!==r&&(rt(r),ii(document.getElementById("frm-single-settings-"+t)))}(o),"product"!==c&&"quantity"!==c||Ie(),s.length)s.parent(".frm_field_box").children(".frm_no_section_fields").addClass("frm_block");else{var _=jQuery(o).closest("ul.frm_sorting.start_divider");_.length&&(An(_),m=!0)}e.includes("frm-collapse-page")&&Fr(),f="frm-newly-added",(u=o).classList?u.classList.add(f):u.className+=" "+f,setTimeout(function(){o.classList.remove("frm-newly-added")},1e3);var p,g=o.querySelector("#frm-last-row-fields-order");if(g&&((p=JSON.parse(g.value))&&"object"===a(p)&&Object.keys(p).forEach(function(e){var t=document.querySelector('input[name="field_options[field_order_'+e+']"]');t&&(t.value=p[e])})),t){var y=o.getBoundingClientRect(),h=document.getElementById("post-body-content");y.top>=0&&y.left>=0&&y.right<=(window.innerWidth||document.documentElement.clientWidth)&&y.bottom<=(window.innerHeight||document.documentElement.clientHeight)||h.scroll({top:h.scrollHeight,left:0,behavior:"smooth"}),!1===m&&An(s)}Ke(),so(),document.getElementById("frm-show-fields").classList.remove("frm-over-droppable"),function(e){var t=document.getElementById(e);t&&t.dataset.limit&&kr(e)>=t.dataset.limit&&t.classList.add("frm_at_limit")}(c),o.querySelectorAll("[data-toggle]").forEach(function(e){return e.setAttribute("data-bs-toggle",e.getAttribute("data-toggle"))}),o.querySelectorAll(".frm-dropdown-menu").forEach(function(e){return e.classList.add("dropdown-menu")});var v=new Event("frm_added_field",{bubbles:!1});v.frmField=o,v.frmSection=l,v.frmType=c,v.frmToggles=m,document.dispatchEvent(v)}function Ve(e){if(e.dataset.fid){var t=document.getElementById("draft_fields");t&&(""===t.value?t.value=e.dataset.fid:t.value.split(",").includes(e.dataset.fid)||(t.value+=","+e.dataset.fid))}}function $e(e){jQuery("#new_fields .frm-single-settings").addClass("frm_hidden"),jQuery("#frm-options-panel > .frm-single-settings").removeClass("frm_hidden"),Ke(e)}function Ke(e){jQuery("li.ui-state-default.selected").removeClass("selected"),jQuery(".frm-show-field-settings.selected").removeClass("selected"),e||yr()}function Je(){var e=this.value,t=function(e){var t,r=[],n=e.split(""),i=n.length,a=["{","[","("],l={"}":"{",")":"(","]":"["},s=!1;for(t=0;t0||s?o.unmatched_parens+"\n\n":""}(e);t+=function(e,t){var r=function(e,t){var r="";return function(e){return jQuery(e).siblings('label[for^="calc_type"]').children("input").prop("checked")}(t)||/\[(date|time|email|ip)\]/.test(e)&&(r=o.text_shortcodes+"\n\n"),r}(e,t);return r+=function(e){var t="";return/\[id\]|\[key\]|\[if\s\w+\]|\[foreach\s\w+\]|\[created-at(\s*)?/g.test(e)&&(t+=o.view_shortcodes+"\n\n"),t}(e)}(e,this),""!==t&&M(e+"\n\n"+t)}function Xe(e,t){for(var r=!1,n=0;n"+l[t].fieldName+"")):(r=r?" checked":"",i.push('"));e.innerHTML=i.join("")}function nt(){for(var e=document.querySelectorAll(".frmjs_prod_field_opt"),t=0;t'):(c.innerHTML=_n(d),"TEXTAREA"===c.nodeName&&c.classList.contains("wp-editor-area")&&jQuery(c).trigger("change"),c.classList.contains("frm_primary_label")&&"break"===c.nextElementSibling.getAttribute("data-ftype")&&(c.nextElementSibling.querySelector(".frm_button_submit").textContent=d)))}function at(e){var t=parseFloat(e.getAttribute("max")),r=parseFloat(e.getAttribute("min"));return(t-r)/2+r}function lt(){var e,t=this.getAttribute("data-fid"),r="";["field_options_max_","frm_format_"].forEach(function(e){var n=document.getElementById(e+t);n&&(r+=n.value)}),"text"===(e=document.getElementsByName("field_options[type_"+t+"]")[0]).options[e.selectedIndex].value&&dt(""!==r,".frm_invalid_msg"+t)}function st(){var e=this.id.replace("frm_","").replace("req_field_",""),t=this.checked,r=jQuery("#field_label_"+e+" .frm_required");if(dt(t,".frm_required_details"+e),t){var n=jQuery('input[name="field_options[required_indicator_'+e+']"]');""===n.val()&&n.val("*"),r.removeClass("frm_hidden")}else r.addClass("frm_hidden")}function dt(e,t){var r=jQuery(t);if(e)r.fadeIn("fast").closest(".frm_validation_msg").fadeIn("fast");else{var n=r.fadeOut("fast").closest(".frm_validation_box"),i=n.css("display","block").children(":not("+t+"):visible").length;n.css("display",""),0===i&&r.closest(".frm_validation_msg").fadeOut("fast")}}function ct(){var e=jQuery(this).closest(".frm-single-settings").data("fid"),t=jQuery(".frm_unique_details"+e);if(this.checked){t.fadeIn("fast").closest(".frm_validation_msg").fadeIn("fast");var r=jQuery(".frm_unique_details"+e+" input");""===r.val()&&r.val(o.default_unique)}else{var n=t.fadeOut("fast").closest(".frm_validation_box"),i=n.css("display","block").children(":not(.frm_unique_details"+e+"):visible").length;n.css("display",""),0===i&&t.closest(".frm_validation_msg").fadeOut("fast")}}function ut(){var e=jQuery(this).closest(".frm-single-settings").data("fid"),t=jQuery(this).val(),r=jQuery(document.getElementById("frm_field_id_"+e));if(dt(""!==t,".frm_conf_details"+e),""!==t){var n=jQuery(".frm_validation_box .frm_conf_details"+e+" input");""===n.val()&&n.val(o.default_conf),function(e){var t=document.getElementsByName("field_options[type_"+e+"]")[0].value;ft(document.getElementById("field_description_"+e),"field_options[description_"+e+"]",o["enter_"+t]),ft(document.getElementById("conf_field_description_"+e),"field_options[conf_desc_"+e+"]",o["confirm_"+t])}(e),"inline"===t?r.removeClass("frm_conf_below").addClass("frm_conf_inline"):"below"===t&&r.removeClass("frm_conf_inline").addClass("frm_conf_below"),jQuery(".frm-conf-box-"+e).removeClass("frm_hidden")}else jQuery(".frm-conf-box-"+e).addClass("frm_hidden"),setTimeout(function(){r.removeClass("frm_conf_inline frm_conf_below")},200)}function ft(e,t,r){e.innerHTML===o.desc&&(e.innerHTML=r,document.getElementsByName(t)[0].value=r)}function mt(e){var t=JSON.parse(this.getAttribute("data-opts"));return e.preventDefault(),document.getElementById("frm_bulk_options").value=t.join("\n"),!1}function _t(){var e,t,r,n,i=jQuery(this).closest(".frm-single-settings").data("fid"),o=jQuery("#frm_field_"+i+"_opts .frm_option_template").prop("outerHTML"),a=jQuery(this).data("opttype"),l=0,s=function(e){for(var t=0,r=0,n=jQuery("#frm_field_"+e+"_opts li"),i=0;ti||"000"===i)&&(i=r)}return i}(i);if("000"!==s&&(l=s+1),"other"===a){document.getElementById("other_input_"+i).value=1;var d=jQuery(this).data("ftype");"radio"!==d&&"select"!==d||jQuery(this).fadeOut("slow");var c={action:"frm_add_field_option",field_id:i,opt_key:l,opt_type:a,nonce:frmGlobal.nonce};jQuery.post(ajaxurl,c,function(e){jQuery(document.getElementById("frm_field_"+i+"_opts")).append(e),on(i)})}else{o=(o=(o=(o=(o=o.replace(new RegExp('optkey="000"',"g"),'optkey="'+l+'"')).replace(new RegExp("-000_","g"),"-"+l+"_")).replace(new RegExp('-000"',"g"),"-"+l+'"')).replace(new RegExp("\\[000\\]","g"),"["+l+"]")).replace("frm_hidden frm_option_template",""),Do(i,o={newOption:o});var u=this.closest(".frm_single_option");u?u.after(o.newOption):jQuery("#frm_field_".concat(i,"_opts")).append(o.newOption),on(i)}null==(n=(e=this).classList.contains("frm-add-option-legacy")?null===(t=e.closest(".frm-collapse-me"))||void 0===t?void 0:t.querySelector(".frm_sortable_field_opts"):e.closest(".frm_sortable_field_opts"))||null===(r=n.querySelectorAll(".frm_remove_tag.frm_disabled"))||void 0===r||r.forEach(function(e){return e.classList.remove("frm_disabled")}),Mi()}function pt(){gt(jQuery(this).closest(".frm-single-settings").data("fid"),this.value)}function gt(e,t){var r=jQuery(".frm_multiple_cont_"+e);"select"===t?r.fadeIn("fast"):r.fadeOut("fast")}function yt(){var e=jQuery(this).closest(".frm-single-settings").data("fid");qo(jQuery(".field_"+e+"_option_key")),jQuery(".field_"+e+"_option").toggleClass("frm_with_key")}function ht(){var e,t,r=jQuery(this).closest(".frm-single-settings"),n=r.data("fid"),i=document.getElementById("frm_field_id_"+n);wt(jQuery(this)),qo(jQuery(".field_"+n+"_image_id")),qo(jQuery(".frm_toggle_image_options_"+n)),qo(jQuery(".frm_image_size_"+n)),qo(jQuery(".frm_alignment_"+n)),qo(jQuery(".frm-add-other#frm_add_field_"+n)),(e=hn(n))?(bt(n,"inline"),vt(i),t=nn(n),i.classList.add("frm_image_options"),i.classList.add("frm_image_size_"+t),r.find(".frm-bulk-edit-link").hide()):(i.classList.remove("frm_image_options"),vt(i),bt(n,"block"),r.find(".frm-bulk-edit-link").show()),wp.hooks.doAction("frm_image_options_toggled",r[0],e)}function vt(e){e.classList.remove("frm_image_size_","frm_image_size_small","frm_image_size_medium","frm_image_size_large","frm_image_size_xlarge")}function bt(e,t){jQuery("#field_options_align_"+e).val(t).trigger("change")}function jt(){var e=jQuery(this).closest(".frm-single-settings").data("fid"),t=document.getElementById("frm_field_id_"+e);Qt(),hn(e)&&(vt(t),t.classList.add("frm_image_options"),t.classList.add("frm_image_size_"+nn(e)))}function wt(e){var t=e.closest(".frm-single-settings").data("fid");jQuery(".field_"+t+"_option").trigger("change")}function Qt(){wt(jQuery(this))}function xt(e){var t,r=e.target.closest(".frm_image_preview_wrapper");if(null!==(t=wp)&&void 0!==t&&t.media&&(null==r||!r.dataset.upgrade)){e.preventDefault(),wp.media.model.settings.post.id=0;var n=wp.media.frames.file_frame=wp.media({multiple:!1,library:{type:["image"]}});n.on("select",function(){var e=n.state().get("selection").first().toJSON(),t=r.querySelector("img");t.setAttribute("src",e.url),t.classList.remove("frm_hidden"),t.removeAttribute("srcset"),r.querySelector(".frm_image_preview_frame").style.display="block",r.querySelector(".frm_image_preview_title").textContent=e.filename,r.querySelector(".frm_choose_image_box").style.display="none";var i=jQuery(r);i.siblings('input[name*="[label]"]').data("frmimgurl",e.url),i.find("input.frm_image_id").val(e.id).trigger("change"),wp.media.model.settings.post.id=0}),n.open()}}function Et(e){var t=jQuery(this).closest(".frm_image_preview_wrapper");e.preventDefault(),e.stopPropagation(),t.find("img").attr("src",""),t.find(".frm_image_preview_frame").hide(),t.find(".frm_choose_image_box").show(),t.find("input.frm_image_id").val(0).trigger("change")}function kt(){var e=jQuery(this).closest("li").find(".frm_form_fields select");this.checked?e.attr("multiple","multiple"):e.removeAttr("multiple")}function At(){var e=document.getElementById("dropform-search-input");null!==e&&setTimeout(function(){e.focus()},100)}function St(e){var t=e.target,r=t.closest(".frm_warning_style");jQuery(r).fadeOut(400,function(){return r.remove()});var n=t.dataset.action,i=new FormData;p(n,i)}function Lt(e){e.preventDefault()}function It(){var e,t=this.parentNode,r=t.parentNode,n=r.querySelectorAll("li:not(.frm_hidden)");2===n.length&&(null===(e=Array.from(n).find(function(e){return e!==t}).querySelector(".frm_remove_tag"))||void 0===e||e.classList.add("frm_disabled"));var i,o=this.getAttribute("data-fid");jQuery(t).fadeOut("fast",function(){wp.hooks.doAction("frm_before_delete_field_option",this),jQuery(t).remove(),jQuery(r).find(".frm_other_option").length<1&&(null!==(i=document.getElementById("other_input_"+o))&&(i.value=0),jQuery("#other_button_"+o).fadeIn("fast"))}),Mi()}function Bt(){var e,t,r,n;(e=jQuery(this)).is(":checked")&&(t=function(){setTimeout(function(){e.prop("checked",!1)},0)},r=function(){e.off("mouseup",n)},n=function(){t(),r()},e.on("mouseup",n),e.one("mouseout",r))}function qt(){this.value===o.new_option&&(this.setAttribute("data-value-on-focus",this.value),this.value="")}function Ct(e){return B(I("Are you sure you want to delete these %1$s selected field(s)?","formidable"),e)}function Nt(){var e=o.conf_delete,t=this.parentNode.parentNode.parentNode.parentNode.parentNode,r=t.parentNode,n=jQuery(this).closest("li.form-field"),i=n.data("fid");if("divider"===n.data("ftype")){var a=document.querySelectorAll(".frm-field-group-hover-target .start_divider .frm_field_box"),l=0;a.forEach(function(e){var t=e.querySelectorAll("li.form-field");t&&(l+=t.length)}),l&&(e=Ct(++l))}return r.classList.contains("frm-section-collapsed")||r.classList.contains("frm-page-collapsed")||("divider_section_only"===t.className&&(e=o.conf_delete_sec),this.setAttribute("data-frmverify",e),this.setAttribute("data-frmverify-btn","frm-button-red"),this.setAttribute("data-deletefield",i),Ne(),D(this)),!1}function Tt(){this.closest("li.form-field").click()}function Ot(){var e,t;null!==(e=document.querySelector(".frm-field-group-hover-target"))&&(e.classList.add("frm-selected-field-group"),(t=document.createElement("div")).classList.add("frm-delete-field-groups","frm_hidden"),document.body.append(t),t.click())}function Ft(){var e=document.querySelector(".frm-field-group-hover-target");if(null!==e){var t="frm_field_group_"+be(),r=document.createTextNode("");We(r);var n=jQuery(r).closest("li").get(0);n.classList.add("frm_hidden");var i=n.querySelector("ul");i.id=t,jQuery(e.closest("li.frm_field_box")).after(n);var o=oe(jQuery(e)),a=[],l=[],s=o.length,d={},c=0;jQuery(n).on("frm_added_duplicated_field_to_row",function(e,t){if(d[jQuery(t.duplicatedFieldHtml).attr("data-fid")]=t.originalFieldId,!(s>++c)){var r=jQuery(i),o=oe(r);l.forEach(function(e){e.remove()});for(var u=0;u6?(t.append(Gt(e,"even")),t):(5!==e&&t.append(Gt(e,"even")),e%2==1&&t.append(Gt(e,"middle")),e<6?(t.append(Gt(e,"left")),t.append(Gt(e,"right"))):((r=d()).classList.add("frm_fourth"),t.prepend(r)),t)}(e),null!==(o=t.closest("ul.frm_sorting"))&&function(e,t){var r,n,i;for(r=t.children.length,n=0;n6?"frm_full":e%2==1?"frm_fourth":"frm_third"}return r.classList.add(n),r.setAttribute("layout-type",t),r.append(function(e,t){var r,n,i;for(r=Xt(),n=0;n6?"frm1":-1!==[2,3,4,6].indexOf(e)?lr(12/e):5===e&&void 0!==t?0===t?"frm4":"frm2":"frm12"}function Kt(e){switch(e){case 2:case 3:return"frm3";case 4:case 5:return"frm2";case 6:return"frm1"}return"frm12"}function Jt(e){switch(e){case 2:return"frm9";case 3:case 4:return"frm6";case 5:return"frm4";case 6:return"frm7"}return"frm12"}function Xt(){var e=d();return e.classList.add("frm_grid_container"),e}function Yt(){var e=document.querySelector(".frm-field-group-hover-target");if(e){var t=this.getAttribute("layout-type");ae(oe(jQuery(e)).first(),t),ur()}}function Zt(){var e,t;e=er(),t=this.getAttribute("layout-type"),ae(oe(e).first(),t),yr()}function er(){var e=jQuery(".frm-selected-field-group"),t=e.first();return e.not(t).each(function(){oe(jQuery(this)).each(function(){var e=this.parentNode;oe(t).last().after(this),jQuery(e).children("li.form-field").length||e.closest("li.frm_field_box").remove()})}),En(),ae(oe(t).first()),t}function tr(){null===this.closest(".frm-merge-fields-into-row")&&rr(oe(jQuery(".frm-field-group-hover-target")))}function rr(e){var t,r,n,i,o,a,l,s,c,u,f,m,_,p,g;for(t=e.length,(r=document.getElementById("frm_field_group_popup")).innerHTML="",(n=d()).style.padding="0 24px",i=$t(5===t?6:t),(o=d()).style.padding="20px 0",o.classList.add("frm_grid_container"),5===t&&((a=document.createElement("span")).classList.add("frm1"),o.append(a)),!1!==(l=jr()>0&&or($t(t)))&&l>=12&&(l=Math.floor(12/t)),s=0;s',""),t);e.prepend(r),document.getElementById("frm-field-group-message-dismiss").addEventListener("click",function(){_r(document.getElementById("frm-field-group-message"))})}}(),"ul"===e.originalEvent.target.nodeName.toLowerCase()){var t=document.querySelector(".frm-field-group-hover-target");if(t){var r=e.ctrlKey||e.metaKey,n=e.shiftKey,i=t.classList.contains("frm-selected-field-group"),o=function(){var e=jQuery(".frm-selected-field-group");if(e.length)return e;var t=pr();if(t){var r=t.closest("ul");if(r&&1===oe(jQuery(r)).length)return r.classList.add("frm-selected-field-group"),jQuery(r)}return jQuery()}(),a=o.length;if(r||n){var l=pr();if(null===l||jQuery(l).siblings("li.form-field").length||(l.parentNode.classList.add("frm-selected-field-group"),++a),r){if(i)return--a,t.classList.remove("frm-selected-field-group"),void gr(a);++a}else if(n&&!i){++a;var s=o.first();(s.parent().index()=2||1===e&&oe(jQuery(document.querySelector(".frm-selected-field-group"))).length>1?function(){var e,t,r,n,i;if(null!==(e=document.getElementById("frm_field_multiselect_popup")))return e.classList.toggle("frm-unmergable",!vr()),e;(e=d()).id="frm_field_multiselect_popup",vr()||e.classList.add("frm-unmergable"),(t=d()).classList.add("frm-merge-fields-into-row"),t.textContent=I("Merge into row","formidable"),(r=document.createElement("a")).style.marginLeft="5px",r.classList.add("frm_icon_font","frm_arrowdown6_icon"),r.setAttribute("href","#"),t.append(r),e.append(t),(n=d()).classList.add("frm-multiselect-popup-separator"),e.append(n),(i=d()).classList.add("frm-delete-field-groups"),i.append(Rt("frm_trash_svg")),e.append(i),document.getElementById("post-body-content").append(e),jQuery(e).hide().fadeIn()}():hr(),Fe()}function yr(e){if(void 0!==e){if(null!==e.originalEvent.target.closest("#frm-show-fields"))return;if(e.originalEvent.target.classList.contains("frm-merge-fields-into-row"))return;if(null!==e.originalEvent.target.closest(".frm-merge-fields-into-row"))return;if(e.originalEvent.target.classList.contains("frm-custom-field-group-layout"))return;if(e.originalEvent.target.classList.contains("frm-cancel-custom-field-group-layout"))return}jQuery(".frm-selected-field-group").removeClass("frm-selected-field-group"),jQuery(document).off("click",yr),hr()}function hr(){var e=document.getElementById("frm_field_multiselect_popup");null!==e&&e.remove()}function vr(){var e,t,r,n,i;if(1===(r=(e=document.querySelectorAll(".frm-selected-field-group")).length))return!1;for(t=0,n=0;n12)return!1}return!0}function br(e){var t;null===e.originalEvent.target.closest("#frm_field_group_popup")&&(e.originalEvent.target.classList.contains("frm-custom-field-group-layout")||(t=Ht(jr(),document.querySelector(".frm-selected-field-group").firstChild),this.append(t)))}function jr(){var e=0;return jQuery(document.querySelectorAll(".frm-selected-field-group")).each(function(){e+=oe(jQuery(this)).length}),e}function wr(){var e,t,r,n;n=[],jQuery(".frm-selected-field-group > li.form-field").each(function(){n.push(this.dataset.fid)}),t=function(e){return function(t){t.preventDefault(),function(e){e.forEach(function(e){xr(e)})}(e)}}(e=n),null!==(r=document.getElementById("frm_field_multiselect_popup"))&&r.remove(),this.setAttribute("data-frmverify",Ct(e.length)),D(this);var i=document.getElementById("frm-confirmed-click");null==i||i.removeAttribute("data-deletefield"),jQuery(i).on("click",t),jQuery("#frm_confirm_modal").one("dialogclose",function(){jQuery(i).off("click",t)})}function Qr(){xr(this.getAttribute("data-deletefield"))}function xr(e){var t=jQuery("#frm_field_id_"+e);Er(e),t.hasClass("edit_field_type_divider")&&t.find("li.frm_field_box[data-fid]").each(function(){Er(this.getAttribute("data-fid"))}),kn()}function Er(e){jQuery.ajax({type:"POST",url:ajaxurl,data:{action:"frm_delete_field",field_id:e,nonce:frmGlobal.nonce},success:function(){var t,r,n,i=jQuery(document.getElementById("frm_field_id_"+e)),o=jQuery("#frm-single-settings-"+e);o.is(":visible")&&(null===(t=document.querySelector(".frm-settings-panel .frm-tabs-navs ul > li:first-child"))||void 0===t||t.click(),document.querySelector("#frm-options-panel .frm-single-settings").classList.remove("frm_hidden")),function(e){var t=e[0].querySelectorAll(".frm-inline-modal[data-fills]");t.length&&t.forEach(function(e){e.classList.add("frm_hidden"),e.removeAttribute("data-fills"),e.closest("form").append(e)})}(o),o.remove(),i.fadeOut("slow",function(){var e,t=i.closest(".start_divider"),r=i.data("type"),n=i.siblings("li.form-field");if(n.length||(i.is(".edit_field_type_end_divider")?n.length=i.closest("li.form-field").siblings():e=i.closest("ul.frm_sorting").parent()),i.remove(),"break"===r?Fr():"product"===r&&(Ie(),nt()),n.length?ae(n.first()):e.remove(),0===jQuery("#frm-show-fields li").length||function(){if(b.get(0).childElementCount>1)return!1;var e=b.get(0).firstElementChild.firstElementChild.querySelectorAll("li.frm_field_box");return!(e.length>1)&&e[0].classList.contains("edit_field_type_submit")}()){var o=document.getElementById("frm_form_editor_container");o.classList.remove("frm-has-fields"),o.classList.add("frm-empty-fields")}else t.length&&An(t);z()}),i.length&&(r=i.data("type"),(n=document.getElementById(r))&&n.dataset.limit&&kr(r)-11)for(document.getElementById("frm-fake-page").style.display="block",e=0;e200)&&(M(o.repeat_limit_min),this.value="")}function Yr(){var e=this.value;""!==e&&(e<1||e>200)&&(M(o.checkbox_limit),this.value="")}function Zr(e,t){jQuery(e).closest(".frm_field_box").find(".frm_"+t+"_form_row .frm_repeat_label").text(e.value)}function en(){var e=jQuery(this).closest(".frm-single-settings").data("fid"),t=this.value,r=document.getElementById("frm_show_selected_fields_"+e),n=document.getElementById("frm_show_selected_forms_"+e);jQuery(n).find("select").val(""),"form"===t?(n.style.display="inline",function(e){if(null!==e)for(;e.firstChild;)e.firstChild.remove()}(r)):(r.style.display="none",n.style.display="none",xn(t,e))}function tn(){var e,t;(e=rn(this))&&(t=jQuery(this).closest(".frm_single_option"),function(e,t,r){var n,i,o,a,l,s,c=r.data("optkey"),u=yn(e),f=jQuery('label[for="field_'+t+"-"+c+'"]'),m="field_options[options_"+e+"]["+c+"]",_=jQuery('input[name="'+m+'[label]"]');if(f.length<1)return on(e),void((o=r.find('input[name^="default_value_"]')).is(":checked")&&_.length>0&&jQuery('select[name^="item_meta['+e+']"]').val(_.val()));if(a=f.children("input"),n=_.length<1?(_=jQuery('input[name="'+m+'"]')).val():u?jQuery('input[name="'+m+'[value]"]').val():_.val(),!(_.length<1)){if(i=f[0].childNodes,hn(e))l=function(e,t,r){var n,i,o;return(n=e.find("img"))&&(i=n.attr("src")),o=vn(t),pn(r.val(),o,i)}(r,e,_),(s=f.find(".frm_image_option_container")).length>0?s.replaceWith(l):(i[i.length-1].nodeValue="",f.append(l));else{var p=!1;i.forEach(function(t,r){if(!1===p)"INPUT"===t.tagName&&(p=r);else if(r===p+1){var n="";!function(e){var t=document.getElementsByName("field_options[image_options_"+e+"]"),r=Array.from(t).find(function(e){return e.checked&&"buttons"===e.value});return void 0!==r}(e)?t.nodeValue=" "+_.val():(n=d({className:"frm_label_button_container",text:" "+_.val()}),f[0].replaceChild(n,t))}else t.remove()})}a.val(n),o=r.find('input[name^="default_value_"]'),a.prop("checked",!!o.is(":checked"))}}(e.fieldId,e.fieldKey,t))}function rn(e){var t;return!!(t=jQuery(e).closest(".frm_sortable_field_opts")).length&&{fieldId:t.attr("id").replace("frm_field_","").replace("_opts",""),fieldKey:t.data("key")}}function nn(e){var t,r=document.getElementById("field_options_image_size_"+e),n="";return null!==r&&""!==(t=r.value)&&(n=t),n}function on(e){var t,r,n,i,o,a=jQuery('[name^="item_meta['+e+']"]');if(!(a.length<1)){if(a.is("select"))null===(i=document.getElementById("frm_placeholder_"+e))||""===i.value?cn(a[0],{sourceID:e}):cn(a[0],{sourceID:e,placeholder:i.value});else{r=fn(e),jQuery("#field_"+e+"_inner_container > .frm_form_fields").html(""),o=rn(jQuery("#frm_delete_field_"+e+"-000_container"));var l=jQuery("#field_"+e+"_inner_container > .frm_form_fields"),s=hn(e),d=s?nn(e):"",c=s?"frm_image_option frm_image_"+d+" ":"",u=Oo(e);for(n="hidden"===a.attr("type")?a.data("field-type"):a.attr("type"),t=0;t=0;a--){var m;l=d[a];var _=null===(m=document.getElementById("frm_field_"+e+"_opts").querySelector('.frm_option_key input[type="text"]'))||void 0===m?void 0:m.value;_||(_=l),s=i.querySelector('option[value="'+_+'"]');var p=an(e,l),g=p.newValue,y=p.newLabel,h=document.querySelectorAll("#frm_field_"+e+"_opts input[data-value-on-focus]"),v=Array.from(h).find(function(e){return e.value===l});if(v){var b=v.dataset.valueOnFocus;if(b&&i.querySelector('option[value="'+b+'"]'))continue}sn(i,s,g,y)}null!==(s=i.querySelector('option[value=""]'))&&i.prepend(s)}}function sn(e,t,r,n){null!==t||e.querySelector('option[value="'+r+'"]')||((t=frmDom.tag("option",{text:n})).value=r),e.prepend(t)}function dn(e,t,r,n,i,o){var a,l="",s=t.key.includes("other"),d="field_"+n+"-"+t.key,c="scale"===e?"radio":e;return a='',this.getSingle=function(){return""!==(l=wp.hooks.applyFilters("frm_admin.build_single_option_template",l,{opt:t,type:e,fieldId:r,classes:o,id:d}))?l:'
    "+(s?a:"")+"
    "},this.getSingle()}function cn(e,t){if(null!==e){var r=t.sourceID,n=t.placeholder,i=Oo(r),o=t.other;!function(e){var t;if(void 0!==e.options)for(t=e.options.length-1;t>=0;t--)e.remove(t)}(e);for(var a=fn(r,e.id.includes("frm_field_logic_opt")),l=void 0!==n,s=0;s1&&void 0!==arguments[1]&&arguments[1],s=[],d=jQuery('input[name^="field_options[options_'+e+']"]').filter('[name$="[label]"], [name*="[other_"]'),c=Oo(e),u=vn(e),f=hn(e),m=yn(e);for(t=0;t0||(i=r=d[t].value,o=d[t].name.replace("field_options[options_"+e+"][","").replace("[label]","").replace("]",""),m&&(n=d[t].name.replace("[label]","[value]"),r=jQuery('input[name="'+n+'"]').val(),l&&""===i&&(i=""!==r?r:frm_admin_js.no_label)),f&&(i=pn(i,u,mn(d[t]))),a={saved:r,label:i=frmAdminBuild.hooks.applyFilters("frm_choice_field_label",i,e,d[t],f),checked:gn(d[t].id),key:o},c&&(n=d[t].name.replace("[label]","[price]"),a.price=jQuery('input[name="'+n+'"]').val()),s.push(a));return s}function mn(e){var t,r=jQuery(e).siblings(".frm_image_preview_wrapper");return r.length&&(t=r.find("img")).length?t.attr("src"):""}function _n(e){(e instanceof Element||e instanceof Document)&&(e=e.outerHTML);var t=jQuery.parseHTML(e).reduce(function(e,t){var r=frmDom.cleanNode(t);return"#text"===r.nodeName?e+r.textContent:e+r.outerHTML},"");return t!==e?_n(t):t}function pn(e,t,r){var n,i,a,l=e;return l=_n(l),r?i=m({src:r,alt:l}):(i=d({className:"frm_empty_url"})).innerHTML=o.image_placeholder_icon,n=t?" frm_label_with_image":"",(a=s("span",{className:"frm_text_label_for_image_inner"})).innerHTML=l,s("span",{className:"frm_image_option_container"+n,children:[i,s("span",{className:"frm_text_label_for_image",child:a})]})}function gn(e){var t=jQuery("#"+e);if(0===t.length)return!1;var r=t.siblings("input[type=checkbox]");return r.length&&r.prop("checked")}function yn(e){return bn("separate_value_"+e)}function hn(e){for(var t=!1,r=document.getElementsByName("field_options[image_options_"+e+"]"),n=0;n=0&&(r.splice(t,1),e.val(r),e.next(".btn-group").find('.multiselect-container input[value=""]').prop("checked",!1))}(jQuery(this))}function Bn(e){e.val(""),e.next(".btn-group").find('.multiselect-container input[value!=""]').prop("checked",!1)}function qn(){jQuery(".frm-hide-empty").each(function(){0===jQuery(this).text().trim().length&&jQuery(this).remove()})}function Cn(e){e.preventDefault(),function(e,t,r){var n=document.getElementById(e.getAttribute("data-open")),i=jQuery(e).closest("p,ul"),o=void 0!==t;if(i.hasClass("frm-open"))i.removeClass("frm-open"),n.classList.add("frm_hidden");else{if(o||(t=Wi(e)),null!==t){if(!o){var a=r.key;"Enter"!==a&&" "!==a&&t.focus()}i.after(n),n.setAttribute("data-fills",t.id.replace("-proxy-input","")),0===n.id.indexOf("frm-calc-box")&&Ze(n,!0)}i.addClass("frm-open"),n.classList.remove("frm_hidden"),wp.hooks.doAction("frm_show_inline_modal",n,e)}}(this,void 0,e)}function Nn(e){e.preventDefault(),this.parentNode.classList.add("frm_hidden"),jQuery('.frm-open [data-open="'+this.parentNode.id+'"]').closest(".frm-open").removeClass("frm-open")}function Tn(e){var t=e.target;t.closest(".frm-inline-modal.frm-modal-no-dismiss")||t.closest(".frm-show-inline-modal")||t.closest("#frm_adv_info")||t.closest(".frm-token-proxy-input")||document.querySelectorAll(".frm-inline-modal.frm-modal-no-dismiss:not(.frm_hidden)").forEach(function(e){e.classList.add("frm_hidden"),e.previousElementSibling.classList.remove("frm-open")})}function On(){var e,t=this.getAttribute("data-frmchange").split(",");for(e=0;e').before('')}function Yn(){var e="success";"options[edit_action]"===this.name&&(e="edit");var t=jQuery(this).val();jQuery("."+e+"_action_box").hide(),"redirect"===t?jQuery("."+e+"_action_redirect_box."+e+"_action_box").fadeIn("slow"):"page"===t?jQuery("."+e+"_action_page_box."+e+"_action_box").fadeIn("slow"):jQuery("."+e+"_action_message_box."+e+"_action_box").fadeIn("slow")}function Zn(e){if(m=e.target,p=jQuery(m),g=p.closest(".frm_form_action_settings"),(y=g.find(".widget-inside")).find("p, div, table").length||((_=g.find(".widget-top")).on("frm-action-loaded",function(){p.trigger("click"),g.removeClass("open"),y.hide()}),_.trigger("click"),0)){var t=e.target.closest(".frm_form_action_settings"),r=t.querySelectorAll(".wp-editor-area");r.length&&r.forEach(function(e){tinymce.EditorManager.execCommand("mceRemoveEditor",!0,e.id)});var n=jQuery(t).clone(),i=n.attr("id").replace("frm_form_action_",""),o=ei(i);n.find(".frm_action_id, .frm-btn-group").remove(),n.find('input[name$="['+i+'][ID]"]').val(""),n.find(".widget-inside").hide(),n.find("input[type=text], textarea, input[type=number]").prop("defaultValue",function(){return this.value}),n.find("input[type=checkbox], input[type=radio]").prop("defaultChecked",function(){return this.checked});var a=new RegExp("\\["+i+"\\]","g"),l=new RegExp("_"+i+'"',"g"),s=new RegExp("-"+i+'"',"g"),c=new RegExp('"'+i+'"',"g"),u=n.html().replace(a,"["+o+"]").replace(l,"_"+o+'"');u=u.replace(s,"-"+o+'"').replace(c,'"'+o+'"');var f=d({id:"frm_form_action_"+o,className:n.get(0).className});f.setAttribute("data-actionkey",o),f.innerHTML=u,f.querySelectorAll(".wp-editor-wrap, .wp-editor-wrap *").forEach(function(e){"string"==typeof e.className&&(e.className=e.className.replace(i,o)),e.id=e.id.replace(i,o)}),f.classList.remove("open"),document.getElementById("frm_notification_settings").append(f),r.length&&(r.forEach(function(e){frmDom.wysiwyg.init(e)}),f.querySelectorAll(".wp-editor-area").forEach(function(e){frmDom.wysiwyg.init(e)})),f.classList.contains("frm_single_on_submit_settings")&&f.querySelector("input.frm-page-search")&&yo(f),so(),wp.hooks.doAction("frm_after_duplicate_action",f)}var m,_,p,g,y}function ei(e){var t=parseInt(e,10)+11;return null!==document.getElementById("frm_form_action_"+t)&&(t=ei(++t)),t}function ti(){var e,t=jQuery(this).data("actiontype");if(!di(t)){var r=(e=Sr(document.querySelectorAll(".frm_form_action_settings"),"frm_form_action_"),void 0!==document.getElementById("frm_form_action_"+e)&&(e+=100),S>=e&&(e=S+1),S=e,e),n=E,i=document.createElement("div");i.classList.add("frm_single_"+t+"_settings");var o=document.getElementById("frm_notification_settings");o.append(i),jQuery.ajax({type:"POST",url:ajaxurl,data:{action:"frm_add_form_action",type:t,list_id:r,form_id:n,nonce:frmGlobal.nonce},success:function(e){Mi(),i.remove(),document.querySelectorAll(".frm_form_action_settings.open").forEach(function(e){return e.classList.remove("open")});var n=d();n.innerHTML=e;var a=n.querySelector(".widget-top");Array.from(n.children).forEach(function(e){return o.append(e)}),jQuery(".frm_form_action_settings").fadeIn("slow");var l=document.getElementById("frm_form_action_"+r);l.classList.add("open"),document.getElementById("post-body-content").scroll({top:l.offsetTop+10,left:0,behavior:"smooth"}),si(t),Xn("#frm_form_action_"+r),so(),yo(l),a&&jQuery(a).trigger("frm-action-loaded"),frmAdminBuild.hooks.doAction("frm_added_form_action",l)}})}}function ri(){var e=document.getElementById("frm_email_addon_menu").classList,t=document.getElementById("actions-search-input");e.contains("frm-all-actions")?(e.remove("frm-all-actions"),e.add("frm-limited-actions")):(e.add("frm-all-actions"),e.remove("frm-limited-actions")),t.value="",ko(t,"input")}function ni(e){e.on("Change",function(){!function(e){var t,r;(t=document.querySelector(".frm-single-settings:not(.frm_hidden)"))&&null!==(r=t.querySelector(".wp-editor-wrap"))&&r.classList.contains("tmce-active")&&!tinyMCE.activeEditor.isHidden()&&(e.targetElm.value=e.getContent(),jQuery(e.targetElm).trigger("change"))}(e)})}function ii(e){var t=this;if(null!==e)return this.fragment=document.createDocumentFragment(),this.initOnceInAllInstances=function(){void 0===ii.prototype.endMarker&&(ii.prototype.endMarker=document.getElementById("frm-end-form-marker"))},this.append=function(e){var r=null!==e?e.parentElement.classList:"";null!==e&&(r.contains("frm_field_box")||r.contains("divider_section_only"))&&t.fragment.append(e)},this.moveFields=function(){j.insertBefore(t.fragment,ii.prototype.endMarker)},this.initOnceInAllInstances(),void 0!==e?(this.append(e),void this.moveFields()):{append:this.append,moveFields:this.moveFields}}function oi(){var e=jQuery(this).closest(".frm_form_action_settings").data("actionkey"),t=this.getAttribute("data-emailrow");jQuery("#frm_form_action_"+e+" .frm_"+t+"_row").fadeIn("slow"),jQuery(this).fadeOut("slow")}function ai(){var e=jQuery(this).closest(".frm_form_action_settings"),t=this.getAttribute("data-emailrow"),r=".frm_"+t+"_row",n=".frm_"+t+"_button";jQuery(e).find(n).fadeIn("slow"),jQuery(e).find(r).fadeOut("slow",function(){jQuery(e).find(r+" input").val("")})}function li(){var e=jQuery(this).closest(".frm_form_action_settings"),t=".frm_from_to_match_row";e.find('input[name$="[post_content][from]"]').val()===e.find('input[name$="[post_content][email_to]"]').val()?jQuery(e).find(t).fadeIn("slow"):jQuery(e).find(t).fadeOut("slow")}function si(e){var t,r,n=document.querySelectorAll(".frm_"+e+"_action");di(e)?(t=n,r=ci(e)>0,t.forEach(function(e){e.classList.remove("frm_active_action"),e.classList.add("frm_inactive_action"),r&&e.classList.add("frm_already_used")})):n.forEach(function(e){e.querySelector(".frm_show_upgrade")||(e.classList.remove("frm_inactive_action","frm_already_used"),e.classList.add("frm_active_action"))})}function di(e){var t=function(e){return jQuery(".frm_single_"+e+"_settings").length}(e)>=ci(e),r={type:e};return wp.hooks.applyFilters("frm_action_at_limit",t,r)}function ci(e){return parseInt(jQuery(".frm_"+e+"_action").data("limit"),10)}function ui(){var e=o.only_one_action,t=this.dataset.limit;void 0!==t&&((t=parseInt(t))>1?e=e.replace(1,t).trim():e+=" "+o.edit_action_text),M(e)}function fi(){var e=jQuery(this).data("emailkey"),t=jQuery(this).closest(".frm_form_action_settings").find(".frm_action_name").val(),r=document.getElementById("form_id").value,n=document.getElementById("frm_logic_row_"+e),i=Sr(n.querySelectorAll(".frm_logic_row"),"frm_logic_"+e+"_"),o=d({id:"frm_logic_"+e+"_"+i,className:"frm_logic_row frm_hidden"});return n.append(o),jQuery.ajax({type:"POST",url:ajaxurl,data:{action:"frm_add_form_logic_row",email_id:e,form_id:r,meta_name:i,type:t,nonce:frmGlobal.nonce},success:function(t){jQuery(document.getElementById("logic_link_"+e)).fadeOut("slow",function(){o.insertAdjacentHTML("beforebegin",t),o.remove(),jQuery(n).parent(".frm_logic_rows").fadeIn("slow")})}}),!1}function mi(){var e=jQuery("select.frm_single_post_field");e.css("border-color","");var t=this,r=jQuery(t).val();if(""===r||"checkbox"===r)return!1;e.each(function(){if(jQuery(this).val()===r&&this.name!==t.name)return this.style.borderColor="red",jQuery(t).val(""),M(o.field_already_used),!1})}function _i(){var e=jQuery(this).val();""===e?(jQuery(".frm_post_content_opt, select.frm_dyncontent_opt").hide().val(""),jQuery(".frm_dyncontent_opt").hide()):"post_content"===e?(jQuery(".frm_post_content_opt").show(),jQuery(".frm_dyncontent_opt").hide(),jQuery("select.frm_dyncontent_opt").val("")):(jQuery(".frm_post_content_opt").hide().val(""),jQuery("select.frm_dyncontent_opt, .frm_form_field.frm_dyncontent_opt").show())}function pi(){var e=jQuery(this).val(),t=jQuery(document.getElementById("frm_dyncontent"));""===e||"new"===e?(t.val(""),jQuery(".frm_dyncontent_opt").show()):jQuery.ajax({type:"POST",url:ajaxurl,data:{action:"frm_display_get_content",id:e,nonce:frmGlobal.nonce},success:function(e){t.val(e),jQuery(".frm_dyncontent_opt").show()}})}function gi(){var e,t,r=document.getElementById("frm_posttax_rows").childNodes,n=document.querySelector(".frm_post_parent_field"),i=document.querySelector(".frm_post_menu_order_field"),o=this.value;jQuery.ajax({type:"POST",url:ajaxurl,data:{action:"frm_replace_posttax_options",post_type:o,nonce:frmGlobal.nonce},success:function(n){for(var i=0;i
');var e=jQuery(this).closest(".frm_form_action_settings").find('select[name$="[post_content][post_type]"]').val(),t=jQuery(this).closest(".frm_form_action_settings").data("actionkey"),r=jQuery(this).closest(".frm_posttax_row").attr("id").replace("frm_posttax_",""),n=jQuery(this).val(),i=jQuery(document.getElementById(r+"_show_exclude")).is(":checked")?1:0,o=jQuery('select[name$="[post_category]['+r+'][field_id]"]').val(),a=jQuery('input[name="id"]').val();jQuery.ajax({type:"POST",url:ajaxurl,data:{action:"frm_add_posttax_row",form_id:a,post_type:e,tax_key:r,action_key:t,meta_name:n,field_id:o,show_exclude:i,nonce:frmGlobal.nonce},success:function(e){jQuery(document.getElementById("frm_posttax_"+r)).replaceWith(e)}})}}function wi(){var e=jQuery(this).closest(".frm_postmeta_row"),t=e.find(".frm_cancelnew"),r=e.find(".frm_enternew");return e.find("select.frm_cancelnew").is(":visible")?(t.hide(),r.show()):(t.show(),r.hide()),e.find("input.frm_enternew, select.frm_cancelnew").val(""),!1}function Qi(){var e=jQuery(this),t=e.val();"checkbox"===e.attr("type")&&!1===this.checked&&(t="");var r=e.data("toggleclass");""===t?jQuery("."+r).hide():(jQuery("."+r).show(),jQuery(".hide_"+r+"_"+t).hide())}function xi(){Wn()||($n(this),zn(document.querySelector(".frm_form_settings")))}function Ei(e){return e.preventDefault(),ki(jQuery(this),this.getAttribute("data-code")),!1}function ki(e,t){var r=!1,n=e;if("object"===a(e)){if(e.hasClass("frm_noallow"))return;void 0===(n=jQuery(e).closest("[data-fills]").attr("data-fills"))&&void 0!==(n=e.closest("div").attr("class"))&&(n=n.split(" ")[1])}if(void 0===n){var i=document.activeElement;"search"===i.type?null===(n=i.id.replace("-search-input","")).match(/\d/gi)&&(n=(i=jQuery(".frm-single-settings:visible ."+n)).attr("id")):n=i.id}n&&(r=jQuery("#wp-"+n+"-wrap.wp-editor-wrap").length>0);var o=jQuery(document.getElementById(n));if(void 0===e.attr("data-shortcode")&&(!o.length||void 0===o.attr("data-shortcode"))){var l=e.parents("ul.frm_code_list").attr("data-shortcode");"undefined"!==l&&"no"===l||(t="["+t+"]")}if(r&&(wpActiveEditor=n),!o.length)return!1;if("[default-html]"===t||"[default-plain]"===t){var s=0;"[default-plain]"===t&&(s=1),jQuery.ajax({type:"POST",url:ajaxurl,data:{action:"frm_get_default_html",form_id:jQuery('input[name="id"]').val(),plain_text:s,nonce:frmGlobal.nonce},elementId:n,success:function(e){if(r){var t=document.createElement("p");t.innerText=e,send_to_editor(t.innerHTML)}else Ai(o,e)}})}else t=function(e,t,r){return"object"===a(t)&&t instanceof jQuery&&0===r[0].id.indexOf("success_url_")&&(t=t[0]).closest("#frm-insert-fields-box")?(t.parentNode.classList.contains("frm_insert_url")||(e=e.replace("]"," sanitize_url=1]")),e):e}(t,e,o),r?send_to_editor(t):Ai(o,t);return!1}function Ai(e,t){if(document.selection)e[0].focus(),document.selection.createRange().text=t;else{var r=e[0],n=r.selectionEnd;t=function(e,t,r,n){var i=e.data("sep");if(void 0===i)return t;var o=e.val();if(!o.trim().length)return t;var a=new RegExp(i+"\\s*$"),l=new RegExp("^\\s*"+i);return o.substr(0,r).trim().length&&!1===a.test(o.substr(0,r))&&(t=i+t),o.substr(n,o.length).trim().length&&!1===l.test(o.substr(n,o.length))&&(t+=i),t}(e,t,r.selectionStart,n),r.value=r.value.substr(0,r.selectionStart)+t+r.value.substr(r.selectionEnd,r.value.length);var i=n+t.length;!function(e,t){if(e.classList.contains("frm_classes")&&Si(t)){var r=e.value.split(" ").filter(Si);r.length&&(e.value=function(e,t,r){var n=e.split(" ").filter(function(e){return(e=e.trim()).length&&!t.includes(e)});return n.includes(r)||n.push(r),n.join(" ")}(e.value,r,t.trim()))}}(r,t),r.focus(),r.setSelectionRange(i,i)}Rn(e)}function Si(e){return["frm_half","frm_third","frm_two_thirds","frm_fourth","frm_three_fourths","frm_fifth","frm_sixth","frm2","frm3","frm4","frm6","frm8","frm9","frm10","frm12"].includes(e.trim())}function Li(){var e=document.getElementById("frm-id-condition"),t=document.getElementById("frm-key-condition");"id"===this.value?(e.classList.remove("frm_hidden"),t.classList.add("frm_hidden"),ko(t,"change")):(e.classList.add("frm_hidden"),t.classList.remove("frm_hidden"),ko(e,"change"))}function Ii(){var e,t,r=document.getElementById("frm-id-key-condition-id").checked?"frm-id-condition":"frm-key-condition",n=document.getElementById("frm-is-condition").value,i=document.getElementById("frm-text-condition").value,a=document.getElementById("frm-insert-condition");t="if "+(e=(r=document.getElementById(r)).options[r.selectedIndex].value)+" "+n+'="'+i+'"]',a.setAttribute("data-code",t+o.conditional_text+"[/if "+e),a.innerHTML="["+t+"[/if "+e+"]"}function Bi(e){return e.getAttribute("href")||e.getAttributeNS("http://www.w3.org/1999/xlink","href")}function qi(e){var t;e.parentNode.parentNode.classList.contains("frm_has_shortcodes")&&(Vi(),"use"===(t=Ui(e)).tagName?Bi(t=t.firstElementChild).includes("frm_close_icon")||Fi(t,"nofocus"):t.classList.contains("frm_close_icon")||Fi(t,"nofocus"))}function Ci(e){e.preventDefault(),e.stopPropagation(),Fi(this)}function Ni(e){!function(e){var t;if(e.id.startsWith("field_options_type_")){var r=e.id.split("_"),n=r.length&&r[r.length-1];null!==(t=document.querySelector("#frm-single-settings-".concat(n)))&&void 0!==t&&t.classList.contains("frm-type-".concat(e.value))||Ti()}}(e.target)}function Ti(e){var t;void 0===e&&(e=I("You are changing the field type. Not all field settings will appear as expected until you reload the page. Would you like to reload the page now?","formidable")),frmDom.modal.maybeCreateModal("frmSaveAndReloadModal",{title:I("Save and Reload?","formidable"),content:(t=d(e),t.style.padding="var(--gap-md)",t),footer:function(){var e=frmDom.modal.footerButton({text:I("Save and Reload","formidable"),buttonType:"primary"});_(e,function(){var e;(e=document.getElementById("frm_submit_side_top")).classList.contains("frm_submit_ajax")&&e.setAttribute("data-new-addon-installed",!0),e.click()});var t=frmDom.modal.footerButton({text:I("Cancel","formidable"),buttonType:"cancel"});return t.classList.add("dismiss"),frmDom.div({children:[t,e]})}()})}function Oi(e){var t;if(e instanceof Event){var r=document.querySelectorAll(".frm-single-settings .frm-show-box.frmsvg use"),n=Array.from(r).find(function(e){return"#frm_close_icon"===e.getAttribute("href")});if(void 0===n)return;t=n.parentElement}else t=e;var i=t.getBoundingClientRect(),o=document.getElementById("frm_adv_info"),a=o.parentElement.getBoundingClientRect();o.style.top=i.top-a.top+32+"px",o.style.left=i.left-a.left-280+"px"}function Fi(e,t){var r=Wi(e),n=document.getElementById("frm_adv_info"),a=e.className;if("svg"===e.tagName&&(e=e.firstElementChild),"use"===e.tagName&&(a=Bi(e)),a.includes("frm_close_icon"))Vi(n);else{if(Oi(e),jQuery(".frm_code_list a").removeClass("frm_noallow"),r.classList.contains("frm_not_email_to")?jQuery("#frm-insert-fields-box .frm_code_list li:not(.show_frm_not_email_to) a").addClass("frm_noallow"):r.classList.contains("frm_not_email_subject")&&jQuery(".frm_code_list li.hide_frm_not_email_subject a").addClass("frm_noallow"),n.setAttribute("data-fills",r.id),n.style.display="block","use"===e.tagName)if(e.hasAttributeNS("http://www.w3.org/1999/xlink","href"))e.setAttributeNS("http://www.w3.org/1999/xlink","href","#frm_close_icon");else{var l=document.createElementNS("http://www.w3.org/2000/svg","use");l.setAttributeNS("http://www.w3.org/1999/xlink","href","#frm_close_icon"),e.parentNode.replaceChild(l,e)}else e.className=a.replace("frm_more_horiz_solid_icon","frm_close_icon");"nofocus"!==t&&("none"!==r.style.display?r.focus():jQuery(tinymce.get(r.id)).trigger("focus")),function(e){["address","body"].forEach(function(t){!function(e,t){var r,n;r=o.contextualShortcodes[t+"Selector"],n=o.contextualShortcodes[t];var a,l=e.matches(r),s=function(e){var t="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(!t){if(Array.isArray(e)||(t=i(e))){t&&(e=t);var r=0,n=function(){};return{s:n,n:function(){return r>=e.length?{done:!0}:{done:!1,value:e[r++]}},e:function(e){throw e},f:n}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var o,a=!0,l=!1;return{s:function(){t=t.call(e)},n:function(){var e=t.next();return a=e.done,e},e:function(e){l=!0,o=e},f:function(){try{a||null==t.return||t.return()}finally{if(l)throw o}}}}(n);try{for(s.s();!(a=s.n()).done;){var d,c=a.value,u=null===(d=document.querySelector('#frm-adv-info-tab .frm_code_list [data-code="'+c+'"]'))||void 0===d?void 0:d.closest("li");null==u||u.classList.toggle("frm_hidden",!l)}}catch(e){s.e(e)}finally{s.f()}}(e,t)})}(r)}}function Di(e){return 0===o.contextualShortcodes.length||!function(e){var t=e.querySelector("a");if(!t)return!1;var r=t.dataset.code;return o.contextualShortcodes.address.includes(r)||o.contextualShortcodes.body.includes(r)}(e)||function(e){var t=e.querySelector("a").dataset.code,r=document.getElementById("frm_adv_info").dataset.fills,n=document.getElementById(r),i=o.contextualShortcodes;return i.address.includes(t)?n.matches(i.addressSelector):n.matches(i.bodySelector)}(e)}function Mi(){x||(x=1,window.addEventListener("beforeunload",Ri))}function Pi(){x=0}function Hi(){x=0}function zi(){x=0}function Ri(e){x&&(e.preventDefault(),e.returnValue="")}function Gi(e,t){var r={my:"top",at:"top+"+t,of:window};e.dialog("option","position",r)}function Wi(e){if(e.classList.contains("frm-input-icon"))return e.previousElementSibling;for(var t,r=e.nextElementSibling;null!==r&&("INPUT"!==r.tagName&&"TEXTAREA"!==r.tagName||r.classList.contains("frm-token-input-field"));)r=Wi(r);return r||(r=null===(t=e.closest(".frm-field-formula"))||void 0===t?void 0:t.querySelector(".frm-calc-field")),r}function Ui(e){var t;if(null!==(t=e.nextElementSibling)&&void 0!==t&&t.classList.contains("frm-input-icon"))return e.nextElementSibling;for(var r=e.previousElementSibling;null!==r&&"I"!==r.tagName&&"svg"!==r.tagName;)r=Ui(r);return r}function Vi(e){var t,r,n,i;if((void 0!==e||null!==(e=document.getElementById("frm_adv_info")))&&null===document.getElementById("frm_dyncontent")){for(e.style.display="none",n=document.querySelectorAll(".frm-show-box.frm_close_icon"),t=0;t"+r.data.name+": "+r.data.msg+"

":'

Imported '+r.data.name+"

",e.find(".status").prepend(n),e.find(".status").show(),C.importQueue=jQuery.grep(C.importQueue,function(e){return e!=t}),C.imported++,0===C.importQueue.length?(e.find(".process-count").hide(),e.find(".forms-completed").text(C.imported),e.find(".process-completed").show()):(e.find(".form-current").text(C.imported+1),Zi(e)))})}function eo(e){e.preventDefault();var t=!1,r=jQuery('input[name="frm_export_forms[]"]');jQuery('input[name="frm_export_forms[]"]:checked').val()||(r.closest(".frm-table-box").addClass("frm_blank_field"),t="stop");var n=jQuery('input[name="type[]"]');if(jQuery('input[name="type[]"]:checked').val()||"checkbox"!==n.attr("type")||(n.closest("p").addClass("frm_blank_field"),t="stop"),"stop"===t)return!1;e.stopPropagation(),this.submit()}function to(){var e=jQuery(this).closest(".frm_blank_field");if(void 0!==e){var t=this.name;("type[]"===t&&jQuery('input[name="type[]"]:checked').val()||"frm_export_forms[]"===t&&jQuery(this).val())&&e.removeClass("frm_blank_field")}}function ro(){null!==jQuery(this).val().match(/\.csv$/i)?jQuery(".show_csv").fadeIn():jQuery(".show_csv").fadeOut()}function no(){var e=document.querySelector('select[name="format"]');return e?e.value:""}function io(e){var t,r,n=e.target.value;ao(n),oo.call(e.target),t=n,r=document.getElementById("frm-export-select-all"),"csv"===t?(r.checked=!1,r.disabled=!0):r.disabled=!1}function oo(){var e=jQuery(this),t=e.find(":selected"),r=t.data("support"),n=r.indexOf("|");jQuery('input[name="type[]"]').each(function(){this.checked=!1,r.includes(this.value)?(this.disabled=!1,-1===n&&(this.checked=!0)):this.disabled=!0}),"csv"===e.val()?(jQuery(".csv_opts").show(),jQuery(".xml_opts").hide()):(jQuery(".csv_opts").hide(),jQuery(".xml_opts").show());var i=t.data("count"),o=jQuery('input[name="frm_export_forms[]"]');"single"===i?(o.prop("multiple",!1),o.prop("checked",!1)):(o.prop("multiple",!0),o.prop("disabled",!1)),e.trigger("change")}function ao(e){if(""!==e){var t=document.querySelectorAll(".frm-is-repeater");t.length&&("csv"===e?t.forEach(function(e){e.classList.remove("frm_hidden")}):t.forEach(function(e){e.classList.add("frm_hidden")}),Qo.call(document.querySelector(".frm-auto-search")))}}function lo(){var e=jQuery("select[name=format]").find(":selected").data("count"),t=jQuery('input[name="frm_export_forms[]"]');"single"===e&&this.checked?(t.prop("disabled",!0),this.removeAttribute("disabled")):t.prop("disabled",!1)}function so(){jQuery(".frm_multiselect").hide().each(frmDom.bootstrap.multiselect.init)}function co(e){e.preventDefault(),mo(this,"frm_multiple_addons")}function uo(e){e.preventDefault(),mo(this,"frm_activate_addon")}function fo(e){e.preventDefault(),mo(this,"frm_install_addon")}function mo(e,t){r(1105).toggleAddonState(e,t)}function _o(){go()}function po(e){!function(e,t,r){var n=jQuery("#frm_leave_email_error");n.removeClass("frm_hidden").attr("frm-error",r),jQuery("#frm_leave_email").one("keyup",function(){n.addClass("frm_hidden")})}(0,0,e)}function go(){var e=document.getElementById("frmapi-email-form");jQuery.ajax({dataType:"json",url:e.getAttribute("data-url"),success:function(t){var r=t.renderedHtml;r=r.replace(/]*(formidableforms.css|action=frmpro_css)[^>]*>/gi,""),e.innerHTML=r}})}function yo(e){frmDom.autocomplete.initSelectionAutocomplete(e)}function ho(e){var t=this.parentNode.parentNode,r=t.elements.type.value;e.preventDefault(),this.classList.add("frm_loading_button"),bo(t,r,this)}function vo(e){var t=this.elements.type.value,r=this.querySelector("button");e.preventDefault(),r.classList.add("frm_loading_button"),bo(this,t,r)}function bo(e,t,r){var n=function(e){var t,r,n={},i=e.elements;for(r=0;r .frm-with-line").forEach(function(e){var t=e.nextElementSibling;if(t){var r=t.querySelectorAll(":scope > li.frmbutton"),n=Array.from(r).every(function(e){return e.classList.contains("frm_hidden")});e.classList.toggle("frm_hidden",n)}}),jQuery(this).trigger("frmAfterSearch")}function xo(e,t){return"s"!==t&&"s"!==e[e.length-1]&&(e+"s").includes(t)}function Eo(e){e.stopPropagation()}function ko(e,t){var r=document.createEvent("HTMLEvents");r.initEvent(t,!1,!0),e.dispatchEvent(r)}function Ao(e,t){var r,n=new XMLHttpRequest,i="string"==typeof e?e:Object.keys(e).map(function(t){return encodeURIComponent(t)+"="+encodeURIComponent(e[t])}).join("&");return n.open("post",ajaxurl,!0),n.onreadystatechange=function(){if(n.readyState>3&&200==n.status){r=n.responseText;try{r=JSON.parse(r)}catch(e){}t(r)}},n.setRequestHeader("X-Requested-With","XMLHttpRequest"),n.setRequestHeader("Content-type","application/x-www-form-urlencoded"),n.send(i),n}function So(e,t){e.classList.add("frm-fade"),setTimeout(t,1e3)}function Lo(e){jQuery(e).css("visibility","hidden")}function Io(e){jQuery(e).css("visibility","visible")}function Bo(e,t){return r(4260).initModal(e,t)}function qo(e,t){if("#"===t){var r=document.getElementById(e),n=r.style.display;r.style.display="none"===n?"block":"none"}else e.is(":visible")?e.hide():e.show()}function Co(){window.onbeforeunload=null;var e=jQuery(window);e.off("beforeunload.widgets"),e.off("beforeunload.edit-post")}function No(){var e=jQuery(this).closest(".frm-single-settings").data("fid"),t=document.getElementById("frm_field_id_"+e);if(null!==t&&"form"===t.dataset.type)if(t=jQuery(t),this.options[this.selectedIndex].value){t.find(".frm-not-set")[0].classList.add("frm_hidden");var r=t.find(".frm-embed-message");r.html(r.data("embedmsg")+this.options[this.selectedIndex].text),t.find(".frm-embed-field-placeholder")[0].classList.remove("frm_hidden")}else t.find(".frm-not-set")[0].classList.remove("frm_hidden"),t.find(".frm-embed-field-placeholder")[0].classList.add("frm_hidden")}function To(){var e=jQuery(this).closest(".frm-single-settings"),t=e.find(".frmjs_product_choices"),r=e.find(".frm_prod_options_heading"),n=this.options[this.selectedIndex].value;t.removeClass("frm_prod_type_single frm_prod_type_user_def"),r.removeClass("frm_prod_user_def"),"single"===n?t.addClass("frm_prod_type_single"):"user_def"===n&&(t.addClass("frm_prod_type_user_def"),r.addClass("frm_prod_user_def"))}function Oo(e){var t=document.getElementById("frm_field_id_"+e);return null!==t&&"product"===t.getAttribute("data-type")}function Fo(){var e=function(e,t){return window.frmCachedSubFields=window.frmCachedSubFields||{},window.frmCachedSubFields[e]=window.frmCachedSubFields[e]||{},window.frmCachedSubFields[e][t]},t=function(e,t,r){window.frmCachedSubFields=window.frmCachedSubFields||{},window.frmCachedSubFields[e]=window.frmCachedSubFields[e]||{},window.frmCachedSubFields[e][t]=r},r=[1,2,3,4,5,6,7,8,9,10,11,12].map(function(e){return"frm"+e}),i=["first","middle","last"];document.addEventListener("change",function(o){o.target.matches(".frm_name_layout_dropdown")&&function(o){var a,l=o.target.value.split("_"),s=o.target.dataset.fieldId,d=document.querySelector("#field_"+s+"_inner_container .frm_combo_inputs_container"),c=(a=l.length,"frm"+parseInt(12/a));i.forEach(function(e){var i,o=d.querySelector('[data-sub-field-name="'+e+'"]');o&&(o.classList.add("frm_hidden"),(i=o.classList).remove.apply(i,n(r)),t(s,e,o))}),l.forEach(function(t){var r=e(s,t);r&&(r.classList.remove("frm_hidden"),r.classList.add(c),d.append(r))}),i.forEach(function(e){var r=document.querySelector(".frm_sub_field_options-"+e+'[data-field-id="'+s+'"]');r&&(r.classList.add("frm_hidden"),t(s,e+"_options",r))}),l.forEach(function(t){var r=e(s,t+"_options");r&&r.classList.remove("frm_hidden")})}(o)},!1)}function Do(e,t){var r,n,i,o=!1,a=!1;(r=t.newOption?(new DOMParser).parseFromString(t.newOption,"text/html").body.childNodes[0]:t).querySelectorAll("svg").forEach(function(e,t){(n=e.getElementsByTagNameNS("http://www.w3.org/2000/svg","use")[0])&&("#frm_drag_icon"===(i=Bi(n))&&(o=!0),"#frm_save_icon"===i&&(a=!0))}),o||r.prepend(v.drag.cloneNode(!0)),r.querySelector("[id^=field_key_".concat(e,"-]"))&&!a&&r.querySelector("[id^=field_key_".concat(e,"-]")).after(v.save.cloneNode(!0)),t.newOption&&(t.newOption=r)}function Mo(){var e=document.getElementById("frm_leave_email").value.trim();if(""!==e)if(!1!==/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i.test(e)){var t=jQuery("#frmapi-email-form").find("form"),r=t.find('[type="email"]').not(".frm_verify");if(r.length){if(document.getElementById("frm_empty_inbox")){document.getElementById("frm-add-my-email-address").remove();var n=document.getElementById("frm_leave_email_wrapper");if(n){n.classList.add("frm_hidden");var i=c({className:"frm-wait frm_spinner"});i.style.visibility="visible",i.style.float="none",i.style.width="unset",n.parentElement.insertBefore(i,n.nextElementSibling)}}r.val(e),jQuery.ajax({type:"POST",url:t.attr("action"),data:t.serialize()+"&action=frm_forms_preview"}).done(function(e){if(jQuery(e).find(".frm_message").text().trim().includes("Thanks!")){var t=document.getElementById("frmapi-email-form").parentElement.querySelector(".frm_spinner");t&&t.remove(),wp.hooks.applyFilters("frm_thank_you_on_signup",!0)&&document.getElementById("frm_leave_email_wrapper").replaceWith(c(I("Thank you for signing up!","formidable")))}else po("invalid")})}}else po("invalid");else po("empty")}function Po(e){if(O||e.stopPropagation(),!(e.target.classList.contains("frm-show-box")||e.target.parentElement&&e.target.parentElement.classList.contains("frm-show-box"))){var t=document.getElementById("frm_adv_info");t&&(t.dataset.fills===e.target.id&&void 0!==e.target.id||e.target.closest("#frm_adv_info")||"none"===t.style.display||Vi(t))}}return{init:function(){var e,t,i,o,a,l,s;!function(){jQuery(document).on("click","#frm-add-my-email-address",function(e){e.preventDefault(),Mo()});var e=document.getElementById("frm_empty_inbox"),t=document.getElementById("frm_leave_email");if(e&&t){var r=document.getElementById("frm-leave-email-modal");r.classList.remove("frm_hidden"),r.querySelector(".frm_modal_footer").classList.add("frm_hidden"),t.addEventListener("keyup",function(e){if("Enter"===e.key){var t=document.getElementById("frm-add-my-email-address");t&&t.click()}})}}(),t=document.querySelector(".frm-admin-footer-links"),i=null!==(e=document.querySelector(".frm_page_container"))&&void 0!==e?e:document.getElementById("wpbody-content"),t&&i&&(i.append(t),t.classList.remove("frm_hidden")),document.addEventListener("show.bs.dropdown",function(){z()}),C={},jQuery(".wp-admin").on("click",function(e){var t=jQuery(e.target),r=jQuery(".dropdown.open");!r.length||t.hasClass("dropdown")||t.closest(".dropdown").length||r.removeClass("open")}),jQuery("#frm_bs_dropdown:not(.open) a").on("click",At),void 0===E&&(E=jQuery(document.getElementById("form_id")).val()),document.querySelectorAll(".frm-warning-dismiss").forEach(function(e){_(e,St)}),frmAdminBuild.inboxBannerInit(),b.length>0?frmAdminBuild.buildInit():null!==document.getElementById("frm_notification_settings")?frmAdminBuild.settingsInit():null!==document.getElementById("frm_styling_form")?frmAdminBuild.styleInit():null!==document.getElementById("form_global_settings")?frmAdminBuild.globalSettingsInit():null!==document.getElementById("frm_export_xml")?frmAdminBuild.exportInit():null!==document.querySelector(".frm-inbox-wrapper")?frmAdminBuild.inboxInit():null!==document.getElementById("frm-welcome")?frmAdminBuild.solutionInit():(function(){if(document.body.classList.contains("frm-admin-page-entries")){var e=document.getElementById("screen-options-wrap");if(e){var t=d({className:"frm_warning_style",text:I("Only 10 columns can be selected at a time.","formidable")});t.style.margin=0;var r=e.querySelector("legend");r.parentNode.insertBefore(t,r.nextElementSibling);var n=Array.from(e.querySelectorAll('input[type="checkbox"]')),i=function(){n.reduce(function(e,t){return t.checked?e+1:e},0)>=10?(t.classList.remove("frm_hidden"),n.forEach(function(e){e.checked||(e.parentNode.classList.add("frm_noallow"),e.disabled=!0)})):t.classList.add("frm_hidden")};i(),n.forEach(function(e){e.addEventListener("change",function(e){e.target.checked?i():(t.classList.add("frm_hidden"),n.forEach(function(e){e.parentNode.classList.remove("frm_noallow"),e.disabled=!1}))})})}}}(),yo(),jQuery("[data-frmprint]").on("click",function(){return window.print(),!1})),jQuery(document).on("change","select[data-toggleclass], input[data-toggleclass]",Qi),function(){function e(e){var t=e.options[e.selectedIndex];e.querySelectorAll("option[data-dependency]:not([data-dependency-skip])").forEach(function(e){var r=document.querySelector(e.dataset.dependency);null==r||r.classList.toggle("frm_hidden",t!==e)})}document.querySelectorAll("select.frm_select_with_dependency").forEach(e),frmDom.util.documentOn("change","select.frm_select_with_dependency",function(t){return e(t.target)})}(),(jQuery(document.getElementById("frm_adv_info")).length>0||jQuery(".frm_field_list").length>0)&&frmAdminBuild.panelInit(),o=jQuery(".wrap, .frm_wrap"),a=document.getElementById("frm_confirm_modal"),l=!1,s=!1,jQuery(a).on("click","[data-deletefield]",Qr),jQuery(a).on("click","[data-removeid]",R),jQuery(a).on("click","[data-trashtemplate]",wo),o.on("click",".frm_remove_tag, .frm_remove_form_action",R),o.on("click","a[data-frmverify]",F),o.on("click","a[data-frmtoggle]",P),o.on("click","a[data-frmhide], a[data-frmshow]",H),o.on("change","input[data-frmhide], input[data-frmshow]",H),o.on("click",".widget-top,a.widget-action",G),o.on("mouseenter.frm",".frm_bstooltip, .frm_help",function(){jQuery(this).off("mouseenter.frm"),function(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1],r=e;(r.hasAttribute("data-toggle")||r.hasAttribute("data-bs-toggle"))&&(r.parentElement.setAttribute("title",r.getAttribute("title")),r.removeAttribute("title"),r.classList.remove("frm_bstooltip"),r.parentElement.classList.add("frm_bstooltip"),r=r.parentElement),jQuery(r).tooltip(),t&&(z(),jQuery(r).tooltip("show"))}(this,!0)}),jQuery(document).on("click","#doaction, #doaction2",function(e){var t="doaction"===this.id?"top":"bottom",r=document.getElementById("bulk-action-selector-"+t),n=document.getElementById("confirm-bulk-delete-"+t);if(null!==r&&null!==n){if(l=this,!s&&"bulk_delete"===r.value)return e.preventDefault(),D(n),!1}else l=!1}),jQuery(document).on("click","#frm-confirmed-click",function(e){if(!1!==l&&!e.target.classList.contains("frm-btn-inactive"))return"confirm-bulk-delete"===this.getAttribute("href")?(e.preventDefault(),s=!0,l.click(),!1):void 0}),r(4260).initUpgradeModal(),frmDom.util.documentOn("click","[data-modal-title]",Kn);var c=jQuery(document.getElementById("frm_shortcodediv"));c.length>0&&(jQuery("a.edit-frm_shortcode").on("click",function(){return c.is(":hidden")&&(c.slideDown("fast"),this.style.display="none"),!1}),jQuery(".cancel-frm_shortcode","#frm_shortcodediv").on("click",function(){return c.slideUp("fast"),c.siblings("a.edit-frm_shortcode").show(),!1})),jQuery(document).on("click","#frm-nav-tabs a",W),jQuery(".post-type-frm_display .frm-nav-tabs a, .frm-category-tabs a").on("click",function(){var e=this.classList.contains("frm_show_upgrade_tab");if(!this.classList.contains("frm_noallow")||e)return e&&Jn(this),U(this),!1}),U(jQuery(".starttab a"),"auto"),jQuery(document).on("click","#frm-fid-search-menu a",function(){var e=this.id.replace("fid-","");return jQuery('select[name="fid"]').val(e),zn(document.getElementById("posts-filter")),!1}),jQuery(".frm_select_box").on("click focus",function(){this.select()}),jQuery(document).on("input search change",".frm-auto-search:not(#frm-form-templates-page #template-search-input)",Qo),jQuery(document).on("focusin click",".frm-auto-search",Eo);var u=jQuery(".frm-auto-search");""!==u.val()&&u.trigger("keyup"),FrmFormsConnect.init(),jQuery(document).on("click",".frm-install-addon",fo),jQuery(document).on("click",".frm-activate-addon",uo),jQuery(document).on("click",".frm-solution-multiple",co),jQuery("button, input[type=submit]").on("click",Co),document.addEventListener("click",function(e){if("LABEL"===e.target.nodeName){var t=e.target.getAttribute("for");if(t){var r=document.getElementById(t);if(r&&r.nextElementSibling){var n=r.nextElementSibling.querySelector("button.dropdown-toggle.multiselect");n&&setTimeout(function(){return n.click()},0)}}}}),frmAdminBuild.hooks.addFilter("frm_before_embed_modal",function(e,t){var r,n,i=t.element;if("form"!==t.type)return e;var o=i.closest("tr");if(o)r=parseInt(o.querySelector(".column-id").textContent),n=o.querySelector(".column-form_key").textContent;else{r=document.getElementById("form_id").value;var a=document.getElementById("frm_form_key");if(a)n=a.value;else{var l=document.getElementById("frm-previewDrop");l&&(n=l.nextElementSibling.querySelector(".dropdown-item a").getAttribute("href").split("form=")[1])}}return[r,n]}),document.querySelectorAll("#frm-show-fields > li, .frm_grid_container li").forEach(function(e,t){e.addEventListener("click",function(){var e,t,r;t=(null===(e=this.querySelector("li"))||void 0===e?void 0:e.dataset.fid)||this.dataset.fid,(r=document.querySelectorAll("[id^=frm_delete_field_".concat(t,"-]"))).length<2||n(r).slice(1).forEach(function(e,r){e.classList.contains("frm_other_option")||Do(t,e)})})});var f=document.getElementById("frm_small_screen_proceed_button");f&&_(f,function(){var e;null===(e=document.getElementById("frm_small_device_message_container"))||void 0===e||e.remove(),p("small_screen_proceed",new FormData)});var m=document.getElementById("frm_sale_banner"),g=null==m?void 0:m.querySelector(".dismiss");m&&(_(m,function(e){e.target.closest(".dismiss")||(window.location.href=m.getAttribute("data-url"))}),g&&_(g,function(){m.remove();var e=new FormData;p("sale_banner_dismiss",e)}))},buildInit:function(){var e,t,r;jQuery("#frm_builder_page").on("mouseup","*:not(.frm-show-box)",Po),g=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:100;return frmDom.util.debounce(e,t)}(ie,10),y=document.getElementById("post-body-content"),h=jQuery(y),jQuery(".frm_field_loading").length&&Ee(jQuery(".frm_field_loading").first().attr("id")),V("ul.frm_sorting"),document.querySelectorAll(".field_type_list > li:not(.frm_show_upgrade)").forEach(X),jQuery("ul.field_type_list, .field_type_list li, ul.frm_code_list, .frm_code_list li, .frm_code_list li a, #frm_adv_info #category-tabs li, #frm_adv_info #category-tabs li a").disableSelection(),jQuery(".frm_submit_ajax").on("click",Hn),jQuery(".frm_submit_no_ajax").on("click",Gn),Un(),jQuery("a.edit-form-status").on("click",Sn),jQuery(".cancel-form-status").on("click",Ln),jQuery(".save-form-status").on("click",function(){var e=jQuery(document.getElementById("form_change_status")).val();return jQuery('input[name="new_status"]').val(e),jQuery(document.getElementById("form-status-display")).html(e),jQuery(".cancel-form-status").trigger("click"),!1}),jQuery(".frm_form_builder form").first().on("submit",function(){jQuery(".inplace_field").trigger("blur")}),so(),Fr(),e=jQuery(j),t=document.getElementById("frm_form_editor_container"),e.on("click",".frm_add_logic_row",Ar),e.on("click",".frm_add_watch_lookup_row",Lr),e.on("change",".frm_get_values_form",Tr),e.on("change",".frm_logic_field_opts",wn),e.on("frm-multiselect-changed",'select[name^="field_options[admin_only_"]',In),jQuery(document.getElementById("frm-insert-fields")).on("click",".frm_add_field",Ae),b.on("click",".frm_clone_field",Be),e.on("blur",'input[id^="frm_calc"]',Je),e.on("change","input.frm_format_opt, input.frm_max_length_opt",lt),e.on("change click","[data-changeme]",ot),e.on("click","input.frm_req_field",st),e.on("click",".frm_mark_unique",ct),e.on("change",".frm_repeat_format",Jr),e.on("change",".frm_repeat_limit",Xr),e.on("change",".frm_js_checkbox_limit",Yr),e.on("input",'input[name^="field_options[add_label_"]',function(){Zr(this,"add")}),e.on("input",'input[name^="field_options[remove_label_"]',function(){Zr(this,"remove")}),e.on("change",'select[name^="field_options[data_type_"]',Or),jQuery(t).on("click",".frm-collapse-page",Dr),jQuery(t).on("click",".frm-collapse-section",Hr),e.on("click",".frm-single-settings h3, .frm-single-settings h4.frm-collapsible",zr),e.on("keydown",".frm-single-settings h3, .frm-single-settings h4.frm-collapsible",function(e){"Enter"!==e.key&&" "!==e.key||(e.preventDefault(),zr.call(this,e))}),jQuery(t).on("show.bs.dropdown hide.bs.dropdown",He),e.on("click",".frm_toggle_sep_values",yt),e.on("click",".frm_toggle_image_options",ht),e.on("click",".frm_remove_image_option",Et),e.on("click",".frm_choose_image_box",xt),e.on("change",".frm_hide_image_text",Qt),e.on("change",".frm_field_options_image_size",jt),e.on("click",".frm_multiselect_opt",kt),b.on("mousedown","input, textarea, select",Lt),b.on("click","input[type=radio], input[type=checkbox]",Lt),b.on("click",".frm_delete_field",Nt),b.on("click",".frm_select_field",Tt),jQuery(document).on("click",".frm_delete_field_group",Ot),jQuery(document).on("click",".frm_clone_field_group",Ft),jQuery(document).on("click","#frm_field_group_controls > span:first-child",Dt),jQuery(document).on("click",".frm-row-layout-option",Yt),jQuery(document).on("click",".frm-merge-fields-into-row .frm-row-layout-option",Zt),jQuery(document).on("click",".frm-custom-field-group-layout",tr),jQuery(document).on("click",".frm-merge-fields-into-row .frm-custom-field-group-layout",nr),jQuery(document).on("click",".frm-break-field-group",sr),b.on("click","#frm_field_group_popup .frm_grid_container input",dr),jQuery(document).on("click",".frm-cancel-custom-field-group-layout",cr),jQuery(document).on("click",".frm-save-custom-field-group-layout",fr),b.on("click","ul.frm_sorting",mr),jQuery(document).on("click",".frm-merge-fields-into-row",br),jQuery(document).on("click",".frm-delete-field-groups",wr),b.on("click",'.frm-field-action-icons [data-toggle="dropdown"]',function(){this.closest("li.form-field").classList.add("frm-field-settings-open"),jQuery(document).on("click","#frm_builder_page",Te)}),b.on("mousemove","ul.frm_sorting",Oe),b.on("show.bs.dropdown",".frm-field-action-icons",Me),jQuery(document).on("show.bs.dropdown","#frm_field_group_controls",Pe),e.on("click",".frm_single_option a[data-removeid]",It),e.on("mousedown",".frm_single_option input[type=radio]",Bt),e.on("focusin",".frm_single_option input[type=text]",qt),e.on("click",".frm_add_opt",_t),e.on("change",".frm_single_option input",tn),e.on("change",".frm_image_id",tn),e.on("change",".frm_toggle_mult_sel",pt),b.on("click",".frm_primary_label",Gr),b.on("click",".frm_description",Wr),b.on("click","li.ui-state-default:not(.frm_noallow)",Vr),b.on("dblclick","li.ui-state-default",Kr),e.on("change",".frm_tax_form_select",en),e.on("change","select.conf_field",ut),e.on("change",".frm_get_field_selection",Qn),e.on("click",".frm-show-inline-modal",Cn),e.on("keydown",".frm-show-inline-modal",function(e){var t=e.key;"Enter"!==t&&" "!==t||(e.preventDefault(),Cn.call(this,e))}),e.on("click",".frm-inline-modal .dismiss",Nn),jQuery(document).on("change","[data-frmchange]",On),document.addEventListener("click",Tn),e.on("change",".frm_include_extras_field",et),e.on("change",'select[name^="field_options[form_select_"]',No),jQuery(document).on("submit","#frm_js_build_form",Pi),jQuery(document).on("change","#frm_builder_page input:not(.frm-search-input):not(.frm-custom-grid-size-input), #frm_builder_page select, #frm_builder_page textarea",Mi),nt(),jQuery(document).on("change",".frmjs_prod_data_type_opt",To),jQuery(document).on("focus",'.frm-single-settings ul input[type="text"][name^="field_options[options_"]',Br),jQuery(document).on("blur",'.frm-single-settings ul input[type="text"][name^="field_options[options_"]',Cr),frmDom.util.documentOn("click",".frm-show-field-settings",Vr),frmDom.util.documentOn("change","select.frm_format_dropdown, select.frm_phone_type_dropdown",$r),e.on("keydown",'.frm_single_option input[name^="field_options["], .frm_single_option input[name^="rows_"]',function(e){"Enter"===e.key&&function(e){var t=e.closest(".frm_single_option").parentElement.querySelectorAll('.frm_single_option input[name^="field_options[" ], .frm_single_option input[name^="rows_"]'),r=Array.from(t),n=r.indexOf(e);if(!(n<0)){var i=r.slice(n+1).find(function(e){return null!==e.offsetParent});if(i){i.focus();var o=i.value.length;i.setSelectionRange(o,o)}}}(e.currentTarget)}),!1!==(r=Bo("#frm-bulk-modal","700px"))&&(jQuery(".frm-insert-preset").on("click",mt),jQuery(j).on("click","a.frm-bulk-edit-link",function(e){e.preventDefault();var t,n,i,o,a,l="",s=jQuery(this).closest("[data-fid]").data("fid"),d=yn(s),c=Oo(s);if(o=document.getElementById("frm_field_"+s+"_opts")){for(a=o.getElementsByTagName("li"),document.getElementById("bulk-field-id").value=s,t=0;t=a.length-1&&(document.getElementById("frm_bulk_options").value=l);return r.dialog("open"),!1}}),jQuery("#frm-update-bulk-opts").on("click",function(){var e=document.getElementById("bulk-field-id").value;document.getElementById("bulk-option-type").value||(this.classList.add("frm_loading_button"),frmAdminBuild.updateOpts(e,document.getElementById("frm_bulk_options").value,r),Mi())})),qn(),document.addEventListener("frm_added_field",qn),Ie(),Fo(),kn(),frmDom.util.documentOn("change",".frm_show_password_setting_input",function(e){var t=e.target.getAttribute("data-fid"),r=document.getElementById("frm_field_id_"+t);r&&r.classList.toggle("frm_disabled_show_password",!e.target.checked)}),document.addEventListener("scroll",Oi,!0),document.addEventListener("change",Ni),document.querySelector(".frm_form_builder").addEventListener("mousedown",function(e){e.shiftKey&&e.preventDefault()}),wp.hooks.addAction("frmShowedFieldSettings","formidableAdmin",function(e,t){t.querySelectorAll(".frm-collapse-me").forEach(Rr)},9999)},settingsInit:function(){var e,t,r,n,i=jQuery(document.getElementById("frm_notification_settings"));i.on("click",".frm_email_buttons",oi),i.on("click",".frm_remove_field",ai),i.on("change",".frm_to_row, .frm_from_row",li),i.on("change",".frm_tax_selector",ji),i.on("change","select.frm_single_post_field",mi),i.on("change","select.frm_toggle_post_content",_i),i.on("change","select.frm_dyncontent_opt",pi),i.on("change",".frm_post_type",gi),i.on("click",".frm_add_postmeta_row",vi),i.on("click",".frm_add_posttax_row",hi),i.on("click",".frm_toggle_cf_opts",wi),i.on("click",".frm_duplicate_form_action",Zn),jQuery(".frm_actions_list").on("click",".frm_active_action",ti),jQuery("#frm-show-groups, #frm-hide-groups").on("click",ri),so(),jQuery("ul.frm_actions_list li").each(function(){si(jQuery(this).children("a").data("actiontype"));var e=jQuery(this).find("i");"none"!==e.css("background-image")&&e.addClass("frm-inverse")}),jQuery(".frm_submit_settings_btn").on("click",xi),Un(),(e=jQuery(".frm_form_settings")).on("click",".frm_add_form_logic",fi),e.on("click",".frm_already_used",ui),document.addEventListener("click",function(e){var t=e.target;t.closest(".frm_image_preview_wrapper")&&(t.closest(".frm_choose_image_box")?xt.bind(t)(e):t.closest(".frm_remove_image_option")&&Et.bind(t)(e))}),e.on("mouseup","*:not(.frm-show-box)",Po),jQuery(document.getElementById("no_save")).on("change",function(){this.checked&&!0!==confirm(o.no_save_warning)&&jQuery(this).attr("checked",!1)}),jQuery('select[name="options[edit_action]"]').on("change",Yn),t=document.getElementById("logged_in"),jQuery(t).on("change",function(){this.checked?Io(".hide_logged_in"):Lo(".hide_logged_in")}),r=jQuery(document.getElementById("frm_cookie_expiration")),jQuery(document.getElementById("frm_single_entry_type")).on("change",function(){"cookie"===this.value?r.fadeIn("slow"):r.fadeOut("slow")});var a=document.getElementById("single_entry");jQuery(a).on("change",function(){this.checked?Io(".hide_single_entry"):Lo(".hide_single_entry"),this.checked&&"cookie"===jQuery(document.getElementById("frm_single_entry_type")).val()?r.fadeIn("slow"):r.fadeOut("slow")}),jQuery(".hide_save_draft").hide();var l=jQuery(document.getElementById("save_draft"));l.on("change",function(){this.checked?jQuery(".hide_save_draft").fadeIn("slow"):jQuery(".hide_save_draft").fadeOut("slow")}),Rn(l),n=document.getElementById("editable"),jQuery(n).on("change",function(){this.checked?(jQuery(".hide_editable").fadeIn("slow"),Rn(document.getElementById("edit_action"))):(jQuery(".hide_editable").fadeOut("slow"),jQuery(".edit_action_message_box").fadeOut("slow"))}),jQuery(document).on("change","#protect_files",function(){this.checked?jQuery(".hide_protect_files").fadeIn("slow"):jQuery(".hide_protect_files").fadeOut("slow")}),jQuery(document).on("frm-multiselect-changed","#protect_files_role",In),jQuery(document).on("submit",".frm_form_settings",Hi),jQuery(document).on("change","#form_settings_page input:not(.frm-search-input), #form_settings_page select, #form_settings_page textarea",Mi),yo(),jQuery(document).on("frm-action-loaded",Ki),frmDom.util.documentOn("change",'.frm_on_submit_type input[type="radio"]',function(e){if(e.target.checked){var t=e.target.closest(".frm_form_action_settings");t.querySelectorAll(".frm_on_submit_dependent_setting:not(.frm_hidden)").forEach(function(e){e.classList.add("frm_hidden")}),t.querySelectorAll(".frm_on_submit_dependent_setting[data-show-if-"+e.target.value+"]").forEach(function(e){e.classList.remove("frm_hidden")}),t.setAttribute("data-on-submit-type",e.target.value)}}),wp.hooks.addAction("frm_reset_fields_updated","formidableAdmin",zi)},panelInit:function(){var e,t,r,n;jQuery(".frm_wrap, #postbox-container-1").on("click",".frm_insert_code",Ei),jQuery(document).on("change",".frm_insert_val",function(){ki(jQuery(this).data("target"),jQuery(this).val()),jQuery(this).val("")}),jQuery(document).on("click change",'[name="frm-id-key-condition"]',Li),jQuery(document).on("keyup change",".frm-build-logic",Ii),Xn(),jQuery(document).on("frmElementAdded",function(e,t){Xn(t)}),jQuery(document).on("mousedown",".frm-show-box",Ci),t=document.getElementById("form_settings_page"),r=document.body.classList.contains("post-type-frm_display"),n=document.getElementById("frm_insert_fields_tab"),(null!==t||r||O)&&jQuery(document).on("focusin","form input, form textarea",function(e){var i,o,a,l;if(e.stopPropagation(),qi(this),jQuery(this).is(":not(:submit, input[type=button], .frm-search-input, input[type=checkbox])")){if(jQuery(e.target).closest("#frm_adv_info").length)return;if(null!==t||O)i=jQuery("#frm_html_tab"),jQuery(this).closest("#html_settings").length>0?(i.show(),i.siblings().hide(),jQuery("#frm_html_tab a").trigger("click"),void 0===(l=this.id)||l.includes("-search-input")||(jQuery("#frm-adv-info-tab").attr("data-fills",l.trim()),this.classList.contains("field_custom_html")&&(l="field_custom_html"),a=["after_html","before_html","submit_html","field_custom_html"],jQuery.inArray(l,a)>=0&&(jQuery(".frm_code_list li:not(.show_"+l+")").addClass("frm_hidden"),jQuery(".frm_code_list li.show_"+l).removeClass("frm_hidden")))):((o=jQuery(".frm-category-tabs li"))[0]&&(o[0].style.display=""),n.click(),i.hide(),i.siblings().show());else if(r){var s=new CustomEvent("frm_legacy_views_handle_field_focus");s.frmData={idAttrValue:this.id},document.dispatchEvent(s)}}}),jQuery(".frm_wrap, #postbox-container-1").on("mousedown","#frm_adv_info a, .frm_field_list a",function(e){e.preventDefault()}),(e=jQuery("#frm_adv_info")).on("click",".subsubsub a.frmids",function(e){$i("frmids",e)}),e.on("click",".subsubsub a.frmkeys",function(e){$i("frmkeys",e)})},inboxInit:function(){var e;jQuery(".frm_inbox_dismiss").on("click",function(e){var t=this.parentNode.parentNode,r=t.getAttribute("data-message"),n=this.getAttribute("href"),i=t.cloneNode(!0),o=document.querySelector(".frm-dismissed-inbox-messages");if("free_templates"!==r||this.classList.contains("frm_inbox_dismiss")){e.preventDefault();var a={action:"frm_inbox_dismiss",key:r,nonce:frmGlobal.nonce},l="frm_inbox_slide_in"===t.id;l&&(t.classList.remove("s11-fadein"),t.classList.add("s11-fadeout"),t.addEventListener("animationend",function(){return t.remove()},{once:!0})),Ao(a,function(){if(!l)return"#"!==n?(window.location=n,!0):void So(t,function(){var e;null!==o&&(i.classList.remove("frm-fade"),null===(e=i.querySelector(".frm-inbox-message-heading .frm_inbox_dismiss"))||void 0===e||e.remove(),o.append(i)),1===t.parentNode.querySelectorAll(".frm-inbox-message-container").length&&(document.getElementById("frm_empty_inbox").classList.remove("frm_hidden"),t.parentNode.closest(".frm-active").classList.add("frm-empty-inbox"),_o()),t.remove()})})}}),!1===(null===(e=document.getElementById("frm_empty_inbox"))||void 0===e?void 0:e.classList.contains("frm_hidden"))&&_o()},solutionInit:function(){jQuery(document).on("submit","#frm-new-template",vo)},styleInit:function(){var e=jQuery(".frm_image_preview_wrapper");e.on("click",".frm_choose_image_box",xt),e.on("click",".frm_remove_image_option",Et),wp.hooks.doAction("frm_style_editor_init")},customCSSInit:function(){console.warn("Calling frmAdminBuild.customCSSInit is deprecated.")},globalSettingsInit:function(){var e;jQuery(document).on("click","[data-frmuninstall]",Ji),so(),null!==(e=document.getElementById("licenses_settings"))&&jQuery(e).on("click",".edd_frm_save_license",Xi),jQuery(document).on("click","#frm-new-template button",ho),jQuery("#frm-dismissable-cta .dismiss").on("click",function(e){e.preventDefault(),jQuery.post(ajaxurl,{action:"frm_lite_settings_upgrade",nonce:frmGlobal.nonce}),jQuery(".settings-lite-cta").remove()});var t=document.getElementById("frm_re_type");t&&t.addEventListener("change",jo),document.querySelector(".frm_captchas").addEventListener("change",function(e){var t,r=null===(t=document.querySelector('.frm_captchas input[checked="checked"]'))||void 0===t?void 0:t.value,n=e.target.value!==r;document.querySelector(".captcha_settings .frm_note_style").classList.toggle("frm_hidden",!n)}),frmDom.util.documentOn("submit",".frm_settings_form",function(){return x=0});var r=document.getElementById("manage_styles_settings");r&&r.addEventListener("change",function(e){var t=e.target;"SELECT"===t.nodeName&&t.dataset.name&&!t.getAttribute("name")&&t.setAttribute("name",t.dataset.name)});var n=document.getElementById("payments_settings"),i=null==n?void 0:n.querySelectorAll('[name="frm_payment_section"]');i&&i.forEach(function(e){e.addEventListener("change",function(){if(e.checked){var t=n.querySelector('label[for="'.concat(e.id,'"]'));t&&t.setAttribute("aria-selected","true"),i.forEach(function(t){if(t!==e){var r=n.querySelector('label[for="'.concat(t.id,'"]'));r&&r.setAttribute("aria-selected","false")}})}})})},exportInit:function(){jQuery(".frm_form_importer").on("submit",Yi),jQuery(document.getElementById("frm_export_xml")).on("submit",eo),jQuery("#frm_export_xml input, #frm_export_xml select").on("change",to),jQuery('input[name="frm_import_file"]').on("change",ro),document.querySelector('select[name="format"]').addEventListener("change",io),jQuery('input[name="frm_export_forms[]"]').on("click",lo),so(),jQuery(".frm-feature-banner .dismiss").on("click",function(e){e.preventDefault(),jQuery.post(ajaxurl,{action:"frm_dismiss_migrator",plugin:this.id,nonce:frmGlobal.nonce}),this.parentElement.remove()}),ao(no()),document.querySelector("#frm-export-select-all").addEventListener("change",function(e){document.querySelectorAll('[name="frm_export_forms[]"]').forEach(function(t){return t.checked=e.target.checked})})},inboxBannerInit:function(){var e=document.getElementById("frm_banner");if(e){var t=e.querySelector(".frm-banner-dismiss");document.addEventListener("click",function(r){r.target===t&&Ao({action:"frm_inbox_dismiss",key:e.dataset.key,nonce:frmGlobal.nonce},function(){jQuery(e).fadeOut(400,function(){e.remove()})})})}},updateOpts:function(e,t,r){var n=yn(e),i=Oo(e)?"frm_bulk_products":"frm_import_options";jQuery.ajax({type:"POST",url:ajaxurl,data:{action:i,field_id:e,opts:t,separate:n,nonce:frmGlobal.nonce},success:function(t){document.getElementById("frm_field_"+e+"_opts").innerHTML=t,wp.hooks.doAction("frm_after_bulk_edit_opts",e),on(e),void 0!==r&&(r.dialog("close"),document.getElementById("frm-update-bulk-opts").classList.remove("frm_loading_button"))}})},triggerRemoveLogic:function(e,t){jQuery("#frm_logic_"+e+"_"+t+" .frm_remove_tag").trigger("click")},downloadXML:function(e,t,r){var n=ajaxurl+"?action=frm_"+e+"_xml&ids="+t;null!==r&&(n=n+"&is_template="+r),location.href=n},hooks:{applyFilters:function(e){for(var t,r=arguments.length,n=new Array(r>1?r-1:0),i=1;i1?r-1:0),i=1;i1&&void 0!==arguments[1]?arguments[1]:{};return new Promise(function(r){var n=E,i=0;"summary"===e&&(i=b.children('li[data-type="break"]').length>0?1:0),jQuery.ajax({type:"POST",url:ajaxurl,data:Object.assign(_e(e,0,n,i),{field_options:t}),success:function(t){r(t),setTimeout(function(){En(),Ue(t,!0);var r=ye(t);r&&wp.hooks.doAction("frm_after_field_added_in_form_builder",{field:t,fieldId:r,fieldType:e,form_id:n})},10)},error:ve})})},confirmLinkClick:D,handleInsertFieldByDraggingResponse:ge,handleAddFieldClickResponse:Le,syncLayoutClasses:ae,moveFieldSettings:ii,maybeCollapseSettings:zr}},window.frmAdminBuild=frmAdminBuildJS(),jQuery(document).ready(function(){var e;frmAdminBuild.init(),document.querySelectorAll(".frm-dropdown-menu").forEach(function(e){e.classList.add("dropdown-menu");var t,r,n=e.querySelector(".frm-dropdown-toggle");n&&(n.hasAttribute("role")||n.setAttribute("role","button"),n.hasAttribute("tabindex")||n.setAttribute("tabindex",0)),"UL"===e.tagName&&(r=(r=(r=(r=(r=(r=(t=e).outerHTML).replace("
    ","
")).replaceAll("
  • ",'
  • ","
  • "),t.outerHTML=r)}),null===(e=document.querySelector(".preview.dropdown .frm-dropdown-toggle"))||void 0===e||e.setAttribute("data-bs-toggle","dropdown"),document.querySelectorAll("[data-toggle]").forEach(function(e){return e.setAttribute("data-bs-toggle",e.getAttribute("data-toggle"))})}),window.frm_show_div=function(e,t,r,n){t==r?jQuery(n+e).fadeIn("slow").css("visibility","visible"):jQuery(n+e).fadeOut("slow")},window.frmCheckAll=function(e,t){jQuery('input[name^="'+t+'"]').prop("checked",!!e)},window.frmCheckAllLevel=function(e,t,r){jQuery(".frm_catlevel_"+r).children(".frm_checkbox").children("label").children('input[name^="'+t+'"]').prop("checked",!!e)},window.frmGetFieldValues=function(e,t,r,n,i,o){e&&jQuery.ajax({type:"POST",url:ajaxurl,data:"action=frm_get_field_values¤t_field="+t+"&field_id="+e+"&name="+i+"&t="+n+"&form_action="+jQuery('input[name="frm_action"]').val()+"&nonce="+frmGlobal.nonce,success:function(e){document.getElementById("frm_show_selected_values_"+t+"_"+r).innerHTML=e,"function"==typeof o&&o()}})},window.frmImportCsv=function(e){var t="";"undefined"!=typeof __FRMURLVARS&&(t=__FRMURLVARS),jQuery.ajax({type:"POST",url:ajaxurl,data:"action=frm_import_csv&nonce="+frmGlobal.nonce+"&frm_skip_cookie=1"+t,success:function(t){var r=jQuery(".frm_admin_progress_bar").attr("aria-valuemax"),n=r-t,i=n/r*100;jQuery(".frm_admin_progress_bar").css("width",i+"%").attr("aria-valuenow",n),parseInt(t,10)>0?(jQuery(".frm_csv_remaining").html(t),frmImportCsv(e)):(jQuery(document.getElementById("frm_import_message")).html(frm_admin_js.import_complete),setTimeout(function(){location.href="?page=formidable-entries&frm_action=list&form="+e+"&import-message=1"},2e3))}})}})(); \ No newline at end of file +(()=>{var e={65:(e,t,r)=>{"use strict";r.d(t,{A:()=>o});var n=r(8793),i=r(1323);function o(e){var t=(0,n.A)(e);return function(e){return(0,i.A)(t,e)}}},1323:(e,t,r)=>{"use strict";r.d(t,{A:()=>i});var n={"!":function(e){return!e},"*":function(e,t){return e*t},"/":function(e,t){return e/t},"%":function(e,t){return e%t},"+":function(e,t){return e+t},"-":function(e,t){return e-t},"<":function(e,t){return e":function(e,t){return e>t},">=":function(e,t){return e>=t},"==":function(e,t){return e===t},"!=":function(e,t){return e!==t},"&&":function(e,t){return e&&t},"||":function(e,t){return e||t},"?:":function(e,t,r){if(e)throw t;return r}};function i(e,t){var r,i,o,a,l,s,d=[];for(r=0;r{"use strict";r.d(t,{A:()=>i});var n=r(65);function i(e){var t=(0,n.A)(e);return function(e){return+t({n:e})}}},8793:(e,t,r)=>{"use strict";var n,i,o,a;function l(e){for(var t,r,l,s,d=[],c=[];t=e.match(a);){for(r=t[0],(l=e.substr(0,t.index).trim())&&d.push(l);s=c.pop();){if(o[r]){if(o[r][0]===s){r=o[r][1]||r;break}}else if(i.indexOf(s)>=0||n[s]l}),n={"(":9,"!":8,"*":7,"/":7,"%":7,"+":6,"-":6,"<":5,"<=":5,">":5,">=":5,"==":4,"!=":4,"&&":3,"||":2,"?":1,"?:":1},i=["(","?"],o={")":["("],":":["?","?:"]},a=/<=|>=|==|!=|&&|\|\||\?:|\(|!|\*|\/|%|\+|-|<|>|\?|\)|:/},7521:(e,t,r)=>{"use strict";r.d(t,{A:()=>o});var n=r(6956),i=r(7395);const o=function(e,t){return function(r,o,a){var l=arguments.length>3&&void 0!==arguments[3]?arguments[3]:10,s=e[t];if((0,i.A)(r)&&(0,n.A)(o))if("function"==typeof a)if("number"==typeof l){var d={callback:a,priority:l,namespace:o};if(s[r]){var c,u=s[r].handlers;for(c=u.length;c>0&&!(l>=u[c-1].priority);c--);c===u.length?u[c]=d:u.splice(c,0,d),s.__current.forEach(function(e){e.name===r&&e.currentIndex>=c&&e.currentIndex++})}else s[r]={handlers:[d],runs:0};"hookAdded"!==r&&e.doAction("hookAdded",r,o,a,l)}else console.error("If specified, the hook priority must be a number.");else console.error("The hook callback must be a function.")}}},11:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});const n=function(e,t){return function(){var r,n,i=e[t];return null!==(r=null===(n=i.__current[i.__current.length-1])||void 0===n?void 0:n.name)&&void 0!==r?r:null}}},5375:(e,t,r)=>{"use strict";r.d(t,{A:()=>i});var n=r(7395);const i=function(e,t){return function(r){var i=e[t];if((0,n.A)(r))return i[r]&&i[r].runs?i[r].runs:0}}},3561:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});const n=function(e,t){return function(r){var n=e[t];return void 0===r?void 0!==n.__current[0]:!!n.__current[0]&&r===n.__current[0].name}}},8830:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});const n=function(e,t){return function(r,n){var i=e[t];return void 0!==n?r in i&&i[r].handlers.some(function(e){return e.namespace===n}):r in i}}},7765:(e,t,r)=>{"use strict";r.d(t,{A:()=>f});var n=r(3029),i=r(7521),o=r(4194),a=r(8830),l=r(6763),s=r(11),d=r(3561),c=r(5375),u=function e(){(0,n.A)(this,e),this.actions=Object.create(null),this.actions.__current=[],this.filters=Object.create(null),this.filters.__current=[],this.addAction=(0,i.A)(this,"actions"),this.addFilter=(0,i.A)(this,"filters"),this.removeAction=(0,o.A)(this,"actions"),this.removeFilter=(0,o.A)(this,"filters"),this.hasAction=(0,a.A)(this,"actions"),this.hasFilter=(0,a.A)(this,"filters"),this.removeAllActions=(0,o.A)(this,"actions",!0),this.removeAllFilters=(0,o.A)(this,"filters",!0),this.doAction=(0,l.A)(this,"actions"),this.applyFilters=(0,l.A)(this,"filters",!0),this.currentAction=(0,s.A)(this,"actions"),this.currentFilter=(0,s.A)(this,"filters"),this.doingAction=(0,d.A)(this,"actions"),this.doingFilter=(0,d.A)(this,"filters"),this.didAction=(0,c.A)(this,"actions"),this.didFilter=(0,c.A)(this,"filters")};const f=function(){return new u}},4194:(e,t,r)=>{"use strict";r.d(t,{A:()=>o});var n=r(6956),i=r(7395);const o=function(e,t){var r=arguments.length>2&&void 0!==arguments[2]&&arguments[2];return function(o,a){var l=e[t];if((0,i.A)(o)&&(r||(0,n.A)(a))){if(!l[o])return 0;var s=0;if(r)s=l[o].handlers.length,l[o]={runs:l[o].runs,handlers:[]};else for(var d=l[o].handlers,c=function(e){d[e].namespace===a&&(d.splice(e,1),s++,l.__current.forEach(function(t){t.name===o&&t.currentIndex>=e&&t.currentIndex--}))},u=d.length-1;u>=0;u--)c(u);return"hookRemoved"!==o&&e.doAction("hookRemoved",o,a),s}}}},6763:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});const n=function(e,t){var r=arguments.length>2&&void 0!==arguments[2]&&arguments[2];return function(n){var i=e[t];i[n]||(i[n]={handlers:[],runs:0}),i[n].runs++;for(var o=i[n].handlers,a=arguments.length,l=new Array(a>1?a-1:0),s=1;s{"use strict";r.d(t,{se:()=>n});var n=(0,r(7765).A)();n.addAction,n.addFilter,n.removeAction,n.removeFilter,n.hasAction,n.hasFilter,n.removeAllActions,n.removeAllFilters,n.doAction,n.applyFilters,n.currentAction,n.currentFilter,n.doingAction,n.doingFilter,n.didAction,n.didFilter,n.actions,n.filters},7395:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});const n=function(e){return"string"!=typeof e||""===e?(console.error("The hook name must be a non-empty string."),!1):/^__/.test(e)?(console.error("The hook name cannot begin with `__`."),!1):!!/^[a-zA-Z][a-zA-Z0-9_.-]*$/.test(e)||(console.error("The hook name can only contain numbers, letters, dashes, periods and underscores."),!1)}},6956:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});const n=function(e){return"string"!=typeof e||""===e?(console.error("The namespace must be a non-empty string."),!1):!!/^[a-zA-Z][a-zA-Z0-9_.\-\/]*$/.test(e)||(console.error("The namespace can only contain numbers, letters, dashes, periods, underscores and slashes."),!1)}},772:(e,t,r)=>{"use strict";r.d(t,{h:()=>d});var n=r(4467),i=r(5397);function o(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function a(e){for(var t=1;t1&&void 0!==arguments[1]?arguments[1]:"default";n.data[t]=a(a(a({},l),n.data[t]),e),n.data[t][""]=a(a({},l[""]),n.data[t][""])},u=function(e,t){c(e,t),d()},f=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"default",t=arguments.length>1?arguments[1]:void 0,r=arguments.length>2?arguments[2]:void 0,i=arguments.length>3?arguments[3]:void 0,o=arguments.length>4?arguments[4]:void 0;return n.data[e]||c(void 0,e),n.dcnpgettext(e,t,r,i,o)},m=function(){return arguments.length>0&&void 0!==arguments[0]?arguments[0]:"default"},_=function(e,t,n){var i=f(n,t,e);return r?(i=r.applyFilters("i18n.gettext_with_context",i,e,t,n),r.applyFilters("i18n.gettext_with_context_"+m(n),i,e,t,n)):i};if(e&&u(e,t),r){var p=function(e){s.test(e)&&d()};r.addAction("hookAdded","core/i18n",p),r.addAction("hookRemoved","core/i18n",p)}return{getLocaleData:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"default";return n.data[e]},setLocaleData:u,resetLocaleData:function(e,t){n.data={},n.pluralForms={},u(e,t)},subscribe:function(e){return o.add(e),function(){return o.delete(e)}},__:function(e,t){var n=f(t,void 0,e);return r?(n=r.applyFilters("i18n.gettext",n,e,t),r.applyFilters("i18n.gettext_"+m(t),n,e,t)):n},_x:_,_n:function(e,t,n,i){var o=f(i,void 0,e,t,n);return r?(o=r.applyFilters("i18n.ngettext",o,e,t,n,i),r.applyFilters("i18n.ngettext_"+m(i),o,e,t,n,i)):o},_nx:function(e,t,n,i,o){var a=f(o,i,e,t,n);return r?(a=r.applyFilters("i18n.ngettext_with_context",a,e,t,n,i,o),r.applyFilters("i18n.ngettext_with_context_"+m(o),a,e,t,n,i,o)):a},isRTL:function(){return"rtl"===_("ltr","text direction")},hasTranslation:function(e,t,i){var o,a,l=t?t+""+e:e,s=!(null===(o=n.data)||void 0===o||null===(a=o[null!=i?i:"default"])||void 0===a||!a[l]);return r&&(s=r.applyFilters("i18n.has_translation",s,e,t,i),s=r.applyFilters("i18n.has_translation_"+m(i),s,e,t,i)),s}}}},5839:(e,t,r)=>{"use strict";r.d(t,{__:()=>a});var n=r(772),i=r(2133),o=(0,n.h)(void 0,void 0,i.se),a=(o.getLocaleData.bind(o),o.setLocaleData.bind(o),o.resetLocaleData.bind(o),o.subscribe.bind(o),o.__.bind(o));o._x.bind(o),o._n.bind(o),o._nx.bind(o),o.isRTL.bind(o),o.hasTranslation.bind(o)},9575:(e,t,r)=>{"use strict";r.d(t,{__:()=>n.__}),r(181),r(772);var n=r(5839)},181:(e,t,r)=>{"use strict";var n=r(8616),i=r.n(n);r(7604),i()(console.error)},1105:(e,t,r)=>{"use strict";r.r(t),r.d(t,{addonError:()=>c,afterAddonInstall:()=>d,extractErrorFromAddOnResponse:()=>s,toggleAddonState:()=>l});var n=r(9575),i=frmDom,o=i.div,a=i.svg;function l(e,t){var r,n=null!==(r=window.ajaxurl)&&void 0!==r?r:frm_js.ajax_url;jQuery(".frm-addon-error").remove();var i=jQuery(e),o=i.attr("rel"),a=i.parent(),l=a.parent().find(".addon-status-label");i.addClass("frm_loading_button"),jQuery.ajax({url:n,type:"POST",async:!0,cache:!1,dataType:"json",data:{action:t,nonce:frmGlobal.nonce,plugin:o},success:function(e){var r,n,o;"string"!=typeof(e=null!==(r=null===(n=e)||void 0===n?void 0:n.data)&&void 0!==r?r:e)&&"string"==typeof e.message&&(void 0!==e.saveAndReload&&(o=e.saveAndReload),e=e.message);var u=s(e);u?c(u,a,i):(d(e,i,l,a,o,t),wp.hooks.doAction("frm_update_addon_state",e))},error:function(){i.removeClass("frm_loading_button")}})}function s(e){return"string"!=typeof e&&!e.success&&(e.form&&jQuery(e.form).is("#message")?{message:jQuery(e.form).find("p").html()}:e)}function d(e,t,r,i,l){var s=arguments.length>5&&void 0!==arguments[5]?arguments[5]:"frm_activate_addon",d=frm_admin_js,c=document.querySelectorAll(".frm-addon-status");c.forEach(function(t){t.textContent=e,t.style.display="block"}),t.css({opacity:"0"}),document.querySelectorAll(".frm-oneclick").forEach(function(e){e.style.display="none"}),function(){var e=document.getElementById("frm_upgrade_modal");if(e){e.classList.add("frm-success");var t=e.querySelector(".frm-upgrade-message");if(t){var r=t.querySelector("img");t.replaceChildren((0,n.__)("Great! Everything's ready to go!","formidable"),document.createElement("br"),(0,n.__)("You just need to refresh the builder so the new field becomes available.","formidable")),r&&t.append(r)}var i=document.querySelector(".frm-addon-status");i&&(i.textContent="");var o,l=e.querySelector(".frm-circled-icon");if(l)l.classList.add("frm-circled-icon-green"),null===(o=l.querySelector("svg"))||void 0===o||o.replaceWith(a({href:"#frm_checkmark_icon"}))}}();var f={frm_activate_addon:{class:"frm-addon-active",message:d.active},frm_deactivate_addon:{class:"frm-addon-installed",message:d.installed},frm_uninstall_addon:{class:"frm-addon-not-installed",message:d.not_installed}};f.frm_install_addon=f.frm_activate_addon;var m=r[0];m&&(m.textContent=f[s].message);var _=i[0].parentElement;_.classList.remove("frm-addon-not-installed","frm-addon-installed","frm-addon-active"),_.classList.add(f[s].class),t[0].classList.remove("frm_loading_button"),document.querySelectorAll(".frm-admin-page-import, #frm-admin-smtp, #frm-welcome").length>0?window.location.reload():["settings","form_builder"].includes(l)&&c.forEach(function(e){var t=null!==e.closest("#frm_upgrade_modal");e.append(function(e,t){var r,i=[u(e)];return t&&i.push(((r=document.createElement("a")).setAttribute("href","#"),r.classList.add("button","button-secondary","frm-button-secondary","dismiss"),r.textContent=(0,n.__)("Not Now","formidable"),r)),o({className:"frm-save-and-reload-options",children:i})}(l,t))})}function c(e,t,r){e.form?(jQuery(".frm-inline-error").remove(),r.closest(".frm-card").html(e.form).css({padding:5}).find("#upgrade").attr("rel",r.attr("rel")).on("click",f)):(t.append('

    '+e.message+"

    "),r.removeClass("frm_loading_button"),jQuery(".frm-addon-error").delay(4e3).fadeOut())}function u(e){var t=document.createElement("button");return t.classList.add("frm-save-and-reload","button","button-primary","frm-button-primary"),t.textContent=(0,n.__)("Save and Reload","formidable"),t.addEventListener("click",function(){var t;"form_builder"===e?((t=document.getElementById("frm_submit_side_top")).classList.contains("frm_submit_ajax")&&t.setAttribute("data-new-addon-installed",!0),t.click()):"settings"===e&&function(){var e=document.getElementById("form_settings_page");if(null!==e){var t=e.querySelector("form.frm_form_settings");null!==t&&(wp.hooks.doAction("frm_reset_fields_updated"),t.submit())}}()}),t}function f(e){e.preventDefault();var t=jQuery(this),r=t.parent().parent(),n=t.attr("rel");t.addClass("frm_loading_button"),jQuery.ajax({url:ajaxurl,type:"POST",async:!0,cache:!1,dataType:"json",data:{action:"frm_install_addon",nonce:frmAdminJs.nonce,plugin:n,hostname:r.find("#hostname").val(),username:r.find("#username").val(),password:r.find("#password").val()},success:function(e){var n,i,o=s(e=null!==(n=null===(i=e)||void 0===i?void 0:i.data)&&void 0!==n?n:e);o?c(o,r,t):d(e,t,message,r)},error:function(){t.removeClass("frm_loading_button")}})}},4260:(e,t,r)=>{"use strict";r.r(t),r.d(t,{addOneClick:()=>o,initModal:()=>a,initUpgradeModal:()=>l});var n=r(9575),i=frmDom.svg;function o(e,t,r){var o;if("modal"===t)o=document.getElementById("frm_upgrade_modal");else{if("tab"!==t)return;o=document.getElementById(e.getAttribute("href").substr(1))}var a,l=o.querySelector(".frm-oneclick"),s=o.querySelector(".frm-upgrade-message"),d=o.querySelector(".frm-upgrade-link"),c=o.querySelector(".frm-oneclick-button"),u=o.querySelector(".frm-addon-status"),f=e.getAttribute("data-oneclick"),m=e.getAttribute("data-message"),_="block",p="block",g="none",y=o.querySelector(".frm-circled-icon");y&&(y.classList.remove("frm-circled-icon-green"),null===(a=y.querySelector("svg"))||void 0===a||a.replaceWith(i({href:"#frm_filled_lock_icon"})));var h=o.querySelector(".frm-learn-more");if(h&&(h.href=e.dataset.learnMore),null!==l&&null!==c&&void 0!==f&&f){null===m&&(p="none"),_="none",g="block",f=JSON.parse(f),c.className=c.className.replace(" frm-install-addon","").replace(" frm-activate-addon",""),c.className=c.className+" "+f.class,c.rel=f.url,l.textContent=(0,n.__)("This plugin is not activated. Would you like to activate it now?","formidable"),c.textContent=(0,n.__)("Activate","formidable");var v=e.querySelector("use");v&&(null==y||y.querySelector("svg").replaceWith(i({href:v.getAttribute("href")||v.getAttribute("xlink:href"),classList:["frm_svg32"]})))}m||(m=s.getAttribute("data-default")),void 0!==r&&(m=m.replace('',r)),s.innerHTML=m,e.dataset.upsellImage&&s.append(frmDom.img({src:e.dataset.upsellImage,alt:e.dataset.upgrade})),d.href=function(e,t){var r=e.getAttribute("data-link");return null!=r&&""!==r||(r=t.getAttribute("data-default")),r}(e,d),u.style.display="none",l&&(l.style.display=g),c&&(c.style.display="block"===g?"inline-block":g),s.style.display=p,d.style.display="block"===_?"inline-block":_;var b=d.closest(".frm-upgrade-modal-actions");b&&(b.style.display="block"===_?"flex":_)}function a(e,t){var r=jQuery(e);if(!r.length)return!1;void 0===t&&(t="552px");var n={dialogClass:"frm-dialog",modal:!0,autoOpen:!1,closeOnEscape:!0,width:t,resizable:!1,draggable:!1,open:function(){var e,t;jQuery(".ui-dialog-titlebar").addClass("frm_hidden").removeClass("ui-helper-clearfix"),jQuery("#wpwrap").addClass("frm_overlay"),jQuery(".frm-dialog").removeClass("ui-widget ui-widget-content ui-corner-all"),r.removeClass("ui-dialog-content ui-widget-content"),e=r,t=function(){e.dialog("close")},jQuery(".ui-widget-overlay").on("click",t),e.on("click","a.dismiss",t)},close:function(){jQuery("#wpwrap").removeClass("frm_overlay"),jQuery(".spinner").css("visibility","hidden"),this.removeAttribute("data-option-type");var e=document.getElementById("bulk-option-type");e&&(e.value="")}};return r.dialog(n),r}function l(){var e=a("#frm_upgrade_modal");function t(t){var r,n,i;if((r=t.target).classList){var a=r.classList.contains("frm_show_expired_modal")||null!==r.querySelector(".frm_show_expired_modal")||r.closest(".frm_show_expired_modal");if("change"===t.type&&r.classList.contains("frm_select_with_upgrade")){var l=r.options[r.selectedIndex];l&&l.dataset.upgrade&&(r=l)}if(!r.dataset.upgrade){var s=r.closest("[data-upgrade]");if(!s){if(!(s=r.closest(".frm_field_box")))return;r.dataset.upgrade=""}r=s}if(a)wp.hooks.doAction("frm_show_expired_modal",r);else{var d=r.dataset.upgrade;if(d&&!r.classList.contains("frm_show_upgrade_tab")){t.preventDefault();var c=e.get(0),u=c.querySelector(".frm_lock_icon");u&&(u.style.display="block",u.classList.remove("frm_lock_open_icon"),u.querySelector("use").setAttribute("href","#frm_lock_icon"));var f="frm_upgrade_modal_image",m=document.getElementById(f);m&&m.remove(),r.dataset.image&&u&&(u.style.display="none",u.parentNode.insertBefore(frmDom.img({id:f,src:frmGlobal.url+"/images/"+r.dataset.image}),u));var _=c.querySelector(".license-level");_&&(_.textContent=function(e){return e.dataset.requires?e.dataset.requires:"Pro"}(r)),o(r,"modal",d),c.querySelector(".frm_are_not_installed").style.display=r.dataset.image||r.dataset.oneclick?"none":"inline-block",c.querySelector(".frm-upgrade-modal-title-prefix").style.display=r.dataset.oneclick?"inline":"none",c.querySelector(".frm_feature_label").textContent=d,c.querySelector(".frm-upgrade-modal-title-suffix").style.display="none",c.querySelector("h2").style.display="block",e.dialog("open");var p=c.querySelector(".button-primary:not(.frm-oneclick-button)");n=p.getAttribute("href").replace(/(medium=)[a-z_-]+/gi,"$1"+r.getAttribute("data-medium")),null===(i=r.getAttribute("data-content"))&&(i=""),n=n.replace(/(content=)[a-z_-]+/gi,"$1"+i),p.setAttribute("href",n)}}}}!1!==e&&(document.addEventListener("click",t),frmDom.util.documentOn("change","select.frm_select_with_upgrade",t))}},8616:e=>{e.exports=function(e,t){var r,n,i=0;function o(){var o,a,l=r,s=arguments.length;e:for(;l;){if(l.args.length===arguments.length){for(a=0;a{var n;!function(){"use strict";var i={not_string:/[^s]/,not_bool:/[^t]/,not_type:/[^T]/,not_primitive:/[^v]/,number:/[diefg]/,numeric_arg:/[bcdiefguxX]/,json:/[j]/,not_json:/[^j]/,text:/^[^\x25]+/,modulo:/^\x25{2}/,placeholder:/^\x25(?:([1-9]\d*)\$|\(([^)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-gijostTuvxX])/,key:/^([a-z_][a-z_\d]*)/i,key_access:/^\.([a-z_][a-z_\d]*)/i,index_access:/^\[(\d+)\]/,sign:/^[+-]/};function o(e){return function(e,t){var r,n,a,l,s,d,c,u,f,m=1,_=e.length,p="";for(n=0;n<_;n++)if("string"==typeof e[n])p+=e[n];else if("object"==typeof e[n]){if((l=e[n]).keys)for(r=t[m],a=0;a=0),l.type){case"b":r=parseInt(r,10).toString(2);break;case"c":r=String.fromCharCode(parseInt(r,10));break;case"d":case"i":r=parseInt(r,10);break;case"j":r=JSON.stringify(r,null,l.width?parseInt(l.width):0);break;case"e":r=l.precision?parseFloat(r).toExponential(l.precision):parseFloat(r).toExponential();break;case"f":r=l.precision?parseFloat(r).toFixed(l.precision):parseFloat(r);break;case"g":r=l.precision?String(Number(r.toPrecision(l.precision))):parseFloat(r);break;case"o":r=(parseInt(r,10)>>>0).toString(8);break;case"s":r=String(r),r=l.precision?r.substring(0,l.precision):r;break;case"t":r=String(!!r),r=l.precision?r.substring(0,l.precision):r;break;case"T":r=Object.prototype.toString.call(r).slice(8,-1).toLowerCase(),r=l.precision?r.substring(0,l.precision):r;break;case"u":r=parseInt(r,10)>>>0;break;case"v":r=r.valueOf(),r=l.precision?r.substring(0,l.precision):r;break;case"x":r=(parseInt(r,10)>>>0).toString(16);break;case"X":r=(parseInt(r,10)>>>0).toString(16).toUpperCase()}i.json.test(l.type)?p+=r:(!i.number.test(l.type)||u&&!l.sign?f="":(f=u?"+":"-",r=r.toString().replace(i.sign,"")),d=l.pad_char?"0"===l.pad_char?"0":l.pad_char.charAt(1):" ",c=l.width-(f+r).length,s=l.width&&c>0?d.repeat(c):"",p+=l.align?f+r+s:"0"===d?f+s+r:s+f+r)}return p}(function(e){if(l[e])return l[e];for(var t,r=e,n=[],o=0;r;){if(null!==(t=i.text.exec(r)))n.push(t[0]);else if(null!==(t=i.modulo.exec(r)))n.push("%");else{if(null===(t=i.placeholder.exec(r)))throw new SyntaxError("[sprintf] unexpected placeholder");if(t[2]){o|=1;var a=[],s=t[2],d=[];if(null===(d=i.key.exec(s)))throw new SyntaxError("[sprintf] failed to parse named argument key");for(a.push(d[1]);""!==(s=s.substring(d[0].length));)if(null!==(d=i.key_access.exec(s)))a.push(d[1]);else{if(null===(d=i.index_access.exec(s)))throw new SyntaxError("[sprintf] failed to parse named argument key");a.push(d[1])}t[2]=a}else o|=2;if(3===o)throw new Error("[sprintf] mixing positional and named placeholders is not (yet) supported");n.push({placeholder:t[0],param_no:t[1],keys:t[2],sign:t[3],pad_char:t[4],align:t[5],width:t[6],precision:t[7],type:t[8]})}r=r.substring(t[0].length)}return l[e]=n}(e),arguments)}function a(e,t){return o.apply(null,[e].concat(t||[]))}var l=Object.create(null);"undefined"!=typeof window&&(window.sprintf=o,window.vsprintf=a,void 0===(n=function(){return{sprintf:o,vsprintf:a}}.call(t,r,t,e))||(e.exports=n))}()},5397:(e,t,r)=>{"use strict";r.d(t,{A:()=>o});var n=r(1364),i={contextDelimiter:"",onMissingKey:null};function o(e,t){var r;for(r in this.data=e,this.pluralForms={},this.options={},i)this.options[r]=void 0!==t&&r in t?t[r]:i[r]}o.prototype.getPluralForm=function(e,t){var r,i,o,a=this.pluralForms[e];return a||("function"!=typeof(o=(r=this.data[e][""])["Plural-Forms"]||r["plural-forms"]||r.plural_forms)&&(i=function(e){var t,r,n;for(t=e.split(";"),r=0;r{"use strict";function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}r.d(t,{A:()=>n})},4467:(e,t,r)=>{"use strict";r.d(t,{A:()=>i});var n=r(9922);function i(e,t,r){return(t=(0,n.A)(t))in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}},2327:(e,t,r)=>{"use strict";r.d(t,{A:()=>i});var n=r(2284);function i(e,t){if("object"!=(0,n.A)(e)||!e)return e;var r=e[Symbol.toPrimitive];if(void 0!==r){var i=r.call(e,t||"default");if("object"!=(0,n.A)(i))return i;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===t?String:Number)(e)}},9922:(e,t,r)=>{"use strict";r.d(t,{A:()=>o});var n=r(2284),i=r(2327);function o(e){var t=(0,i.A)(e,"string");return"symbol"==(0,n.A)(t)?t:t+""}},2284:(e,t,r)=>{"use strict";function n(e){return n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},n(e)}r.d(t,{A:()=>n})}},t={};function r(n){var i=t[n];if(void 0!==i)return i.exports;var o=t[n]={exports:{}};return e[n](o,o.exports,r),o.exports}function n(e){return function(e){if(Array.isArray(e))return o(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||i(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function i(e,t){if(e){if("string"==typeof e)return o(e,t);var r={}.toString.call(e).slice(8,-1);return"Object"===r&&e.constructor&&(r=e.constructor.name),"Map"===r||"Set"===r?Array.from(e):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?o(e,t):void 0}}function o(e,t){(null==t||t>e.length)&&(t=e.length);for(var r=0,n=Array(t);r{var t=e&&e.__esModule?()=>e.default:()=>e;return r.d(t,{a:t}),t},r.d=(e,t)=>{for(var n in t)r.o(t,n)&&!r.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},window.FrmFormsConnect=window.FrmFormsConnect||function(e,t,r){var n={messageBox:null,reset:null,setElements:function(){n.messageBox=e.querySelector(".frm_pro_license_msg"),n.reset=e.getElementById("frm_reconnect_link")}},i={init:function(){n.setElements(),r(e.getElementById("frm_deauthorize_link")).on("click",i.deauthorize),r(".frm_authorize_link").on("click",i.authorize),r(".frm-dashboard-license-options").on("click","#frm_deauthorize_link",i.deauthorize),r(".frm-dashboard-license-options").on("click","#frm_reconnect_link",i.reauthorize),null!==n.reset&&r(n.reset).on("click",i.reauthorize)},authorize:function(){var t=this,n=this.getAttribute("data-plugin"),o=e.getElementById("edd_"+n+"_license_key"),a=o.value,l=e.getElementById("proplug-wpmu");this.classList.add("frm_loading_button"),l=null===l?0:l.checked?1:0,r.ajax({type:"POST",url:ajaxurl,dataType:"json",data:{action:"frm_addon_activate",license:a,plugin:n,wpmu:l,nonce:frmGlobal.nonce},success:function(e){i.afterAuthorize(e,o),t.classList.remove("frm_loading_button")}})},afterAuthorize:function(e,t){!0===e.success&&(t.value="•••••••••••••••••••"),wp.hooks.doAction("frm_after_authorize",e),i.showMessage(e)},showProgress:function(e){null===n.messageBox&&n.setElements();var t=n.messageBox;null!==t&&(!0===e.success?(t.classList.remove("frm_error_style"),t.classList.add("frm_message","frm_updated_message")):(t.classList.add("frm_error_style"),t.classList.remove("frm_message","frm_updated_message")),t.classList.remove("frm_hidden"),t.innerHTML=e.message)},showMessage:function(r){null===n.messageBox&&n.setElements();var o=n.messageBox;!0===r.success&&(i.showAuthorized(!0),i.showInlineSuccess(),wp.hooks.doAction("frmAdmin.afterLicenseAuthorizeSuccess",{msg:r})),i.showProgress(r),""!==r.message&&(setTimeout(function(){o.innerHTML="",o.classList.add("frm_hidden"),o.classList.remove("frm_error_style","frm_message","frm_updated_message")},1e4),e.querySelector(".frm-admin-page-dashboard")&&setTimeout(function(){t.location.reload()},1e3))},showAuthorized:function(t){var r=t?"unauthorized":"authorized",n=t?"authorized":"unauthorized",i=e.querySelectorAll(".frm_"+r+"_box");i.length&&i.forEach(function(e){e.className=e.className.replace("frm_"+r+"_box","frm_"+n+"_box")})},showInlineSuccess:function(){var t=e.querySelectorAll(".frm-confirm-msg [data-success]");t.length&&t.forEach(function(e){e.innerHTML=frmAdminBuild.purifyHtml(e.getAttribute("data-success"))})},reauthorize:function(){return this.innerHTML='',r.ajax({type:"POST",url:ajaxurl,dataType:"json",data:{action:"frm_reset_cache",plugin:"formidable_pro",nonce:frmGlobal.nonce},success:function(e){n.reset.textContent=e.message,"1"===n.reset.getAttribute("data-refresh")&&t.location.reload()}}),!1},deauthorize:function(){if(!confirm(frmGlobal.deauthorize))return!1;var t=this.getAttribute("data-plugin"),n=e.getElementById("edd_"+t+"_license_key"),o=n.value,a=this;return this.innerHTML='',r.ajax({type:"POST",url:ajaxurl,data:{action:"frm_addon_deactivate",license:o,plugin:t,nonce:frmGlobal.nonce},success:function(){i.showAuthorized(!1),n.value="",a.replaceWith("Disconnected"),wp.hooks.doAction("frmAdmin.afterLicenseDeauthorizeSuccess",{})}}),!1}};return i}(document,window,jQuery),window.frmAdminBuildJS=function(){var e,t,o=frm_admin_js,l=frmDom,s=l.tag,d=l.div,c=l.span,u=l.a,f=l.svg,m=l.img,_=frmDom.util.onClickPreventDefault,p=frmDom.ajax.doJsonPost;o.contextualShortcodes=(t=null===(e=document.getElementById("frm_adv_info"))||void 0===e?void 0:e.dataset.contextualShortcodes)?((t=JSON.parse(t)).addressSelector="[id^=email_to], [id^=from_], [id^=cc], [id^=bcc]",t.bodySelector="[id^=email_message_]",t):[];var g,y,h,v={save:f({href:"#frm_save_icon"}),drag:f({href:"#frm_drag_icon",classList:["frm_drag_icon","frm-drag"]})},b=jQuery(document.getElementById("frm-show-fields")),j=document.getElementById("new_fields"),w=document.getElementById("form_id"),Q=!1,x=0,E=0,k=0,A={},S=0,L=wp.i18n,I=L.__,B=L.sprintf,q={dragging:!1};null!==w&&(E=w.value);var C,N=new URL(window.location.href),T=N.searchParams,O=document.getElementById("frm_builder_page");function F(e){e.stopPropagation(),e.preventDefault(),D(this)}function D(e){var t=e.getAttribute("data-frmverify"),r=e.getAttribute("data-loaded-from");return null===t||"frm-confirmed-click"===e.id||("entries-list"===r?wp.hooks.applyFilters("frm_on_multiple_entries_delete",{link:e,initModal:Bo}):function(e){var t,r,n,i,o,a=Bo("#frm_confirm_modal","400px"),l=document.getElementById("frm-confirmed-click");if(!1===a)return!1;if(l&&(l.style.display="block"),o=(t=e.getAttribute("data-frmverify"))?e.getAttribute("data-frmverify-btn"):"",(r=jQuery(".frm-confirm-msg")).empty(),t&&(r.append(document.createTextNode(t)),o&&(null==l||l.classList.add(o))),i=e.dataset,l){for(n in l.dataset)l.removeAttribute("data-"+n);for(n in i)"frmverify"!==n&&l.setAttribute("data-"+n,i[n])}return wp.hooks.doAction("frmAdmin.beforeOpenConfirmModal",{$info:a,link:e}),a.dialog("open"),null==l||l.setAttribute("href",e.getAttribute("href")||e.getAttribute("data-href")),!1}(e))}function M(e){var t=Bo("#frm_info_modal","400px");return!1===t||(jQuery(".frm-info-msg").html(e),t.dialog("open")),!1}function P(e){var t=this.getAttribute("data-frmtoggle"),r=this.getAttribute("data-toggletext"),n=jQuery(t);return e.preventDefault(),n.toggle(),null!==r&&""!==r&&(this.setAttribute("data-toggletext",this.innerHTML),this.textContent=r),!1}function H(e){var t=this.getAttribute("data-frmhide"),r=this.getAttribute("data-frmshow"),n=this.getAttribute("data-frmuncheck"),i=n?n.split(","):[];"INPUT"!==this.nodeName||"checkbox"!==this.type||this.checked||(null!==t?(r=t,t=null):null!==r&&(t=r,r=null)),e.preventDefault();var o=this.getAttribute("data-toggleclass")||"frm_hidden";null!==t&&jQuery(t).addClass(o),null!==r&&jQuery(r).removeClass(o);var a=this.parentNode.querySelectorAll("a.current");if(null!==a){for(var l=0;l1&&(e="",t=""):0===i.indexOf("frm_postmeta_")&&(jQuery("#frm_postmeta_rows .frm_postmeta_row").length<2&&(e=".frm_add_postmeta_row.button"),jQuery(".frm_toggle_cf_opts").length&&jQuery("#frm_postmeta_rows .frm_postmeta_row:not(#"+i+")").last().length&&(""!==e&&(e+=","),e+="#"+jQuery("#frm_postmeta_rows .frm_postmeta_row:not(#"+i+")").last().attr("id")+" .frm_toggle_cf_opts"));var o=document.getElementById(i),a=jQuery(o);return a.fadeOut(300,function(){var r;a.remove(),Mi(),""!==t&&jQuery(t).hide(),""!==e&&jQuery(e+" a,"+e).removeClass("frm_hidden").fadeIn("slow"),this.closest(".frm_form_action_settings")&&function(e){si(e);var t={type:e};wp.hooks.doAction("frm_after_action_removed",t)}(this.closest(".frm_form_action_settings").querySelector(".frm_action_name").value),null===(r=document.querySelector(".tooltip"))||void 0===r||r.remove()}),void 0!==r&&(r=jQuery(r)).fadeOut(400,function(){r.remove()}),""!==e&&jQuery(this).closest(".frm_logic_rows").fadeOut("slow"),wp.hooks.doAction("frm_admin_tag_removed",i,o),!1}}function G(e,t){void 0===t&&(t=this),Ze(t,!1);var r=jQuery(t).closest(".frm_form_action_settings"),n=e.target;if(r.length&&void 0!==n){var i=n.parentElement.className;if("string"==typeof i&&(i.includes("frm_email_icons")||i.includes("frm_toggle")))return void e.stopPropagation()}var o=r.children(".widget-inside");if(r.length&&o.find("p, div, table").length<1){var a=r.find('input[name$="[ID]"]').val(),l=r.find('input[name$="[post_excerpt]"]').val();l&&(o.html(''),r.find(".spinner").fadeIn("slow"),jQuery.ajax({type:"POST",url:ajaxurl,data:{action:"frm_form_action_fill",action_id:a,action_type:l,nonce:frmGlobal.nonce},success:function(e){o.html(e),so(),Xn("#"+r.attr("id")),yo(o),jQuery(t).trigger("frm-action-loaded"),wp.hooks.doAction("frm_filled_form_action",o)}}))}jQuery(t).closest(".frm_field_box").siblings().find(".widget-inside").slideUp("fast"),void 0!==t.className&&t.className.includes("widget-action")||jQuery(t).closest(".start_divider").length<1||((o=jQuery(t).closest("div.widget").children(".widget-inside")).is(":hidden")?o.slideDown("fast"):o.slideUp("fast"))}function W(){var e=this.getAttribute("href");if(void 0===e)return!1;var t=e.replace("#","."),r=jQuery(this);r.closest("li").addClass("frm-tabs active").siblings("li").removeClass("frm-tabs active starttab"),r.closest("div").children(".tabs-panel").not(e).not(t).hide();var n=document.getElementById(e.replace("#",""));return n&&(n.style.display="block"),"frm_insert_fields_tab"!==this.id||this.closest("#frm_adv_info")||$e(),!1}function U(e,t){var r=(e=jQuery(e)).attr("href");if(void 0!==r){var n,i,o=r.replace("#",".");if(e.closest("li").addClass("frm-tabs active").siblings("li").removeClass("frm-tabs active starttab"),e.closest("div").find(".tabs-panel").length)e.closest("div").children(".tabs-panel").not(r).not(o).hide();else if(null!==document.getElementById("form_global_settings")){var a=e.data("frmajax");e.closest(".frm_wrap").find(".tabs-panel, .hide_with_tabs").hide(),void 0!==a&&"1"==a&&(n=r.replace("#",""),(i=jQuery(".frm_"+n+"_ajax")).length&&jQuery.ajax({type:"POST",url:ajaxurl,data:{action:"frm_settings_tab",tab:n.replace("_settings",""),nonce:frmGlobal.nonce},success:function(e){i.replaceWith(e)}}))}else jQuery("#frm-categorydiv .tabs-panel, .hide_with_tabs").hide();jQuery(r).show(),jQuery(o).show(),Vi(),"auto"!==t&&(jQuery(".frm_updated_message").hide(),jQuery(".frm_warning_style").hide()),jQuery(e).closest("#frm_adv_info").length||(jQuery(".frm_form_settings").length?jQuery(".frm_form_settings").attr("action","?page=formidable&frm_action=settings&id="+jQuery('.frm_form_settings input[name="id"]').val()+"&t="+r.replace("#","")):jQuery(".frm_settings_form").attr("action","?page=formidable-settings&t="+r.replace("#","")))}}function V(e){var t,r;document.querySelectorAll(e).forEach(function(e){$(e),Array.from(e.children).forEach(function(e){return X(e,".frm-move")});var t=jQuery(e).children('[data-type="divider"]').children(".divider_section_only");t.length&&$(t)}),t=jQuery("#frm_builder_page"),r={items:".frm_sortable_field_opts li",axis:"y",opacity:.65,forcePlaceholderSize:!1,handle:".frm-drag",helper:function(e,t){return Q=t.clone().insertAfter(t),t.clone()},stop:function(e,t){Q&&Q.remove(),on(t.item.attr("id").replace("frm_delete_field_","").replace("-"+t.item.data("optkey")+"_container","")),Mi()}},jQuery(t).sortable(r)}function $(e){jQuery(e).droppable({accept:".frmbutton, li.frm_field_box",deactivate:re,over:K,out:J,tolerance:"pointer"})}function K(e,t){var r=function(e){return e.classList.contains("divider_section_only")&&(e=jQuery(e).nextAll(".start_divider.frm_sorting").get(0)),e}(e.target);if(!je(t.draggable[0],r,e))return r.classList.remove("frm-over-droppable"),void jQuery(r).parents("ul.frm_sorting").addClass("frm-over-droppable");document.querySelectorAll(".frm-over-droppable").forEach(function(e){return e.classList.remove("frm-over-droppable")}),r.classList.add("frm-over-droppable"),jQuery(r).parents("ul.frm_sorting").addClass("frm-over-droppable")}function J(e){e.target.classList.remove("frm-over-droppable")}function X(e,t){var r={helper:Y,revert:"invalid",delay:10,start:Z,stop:ee,drag:te,cursor:"grabbing",refreshPositions:!0,cursorAt:{top:0,left:90}};"string"==typeof t&&(r.handle=t),jQuery(e).draggable(r)}function Y(e){var t,r=e.delegateTarget;if(Qe(r)){var n=document.getElementById("frm-insert-fields").querySelector(".frm_ttext").cloneNode(!0);return n.querySelector("use").setAttributeNS("http://www.w3.org/1999/xlink","href","#frm_field_group_layout_icon"),n.querySelector("span").textContent=I("Field Group","formidable"),n.classList.add("frm_field_box"),n.classList.add("ui-sortable-helper"),n}if(r.classList.contains("frmbutton"))return(t=r.cloneNode(!0)).classList.add("ui-sortable-helper"),r.classList.add("frm-new-field"),t;if(r.hasAttribute("data-ftype")){var i=r.getAttribute("data-ftype");if(t=document.getElementById("frm-insert-fields").querySelector(".frm_t"+i))return(t=t.cloneNode(!0)).classList.add("form-field"),t.classList.add("ui-sortable-helper"),t.cloneNode(!0)}return d({className:"frmbutton"})}function Z(e,t){if(e.target.classList.contains("frm_at_limit"))return Se(),!1;q.dragging=!0;var r,n=y;n.classList.add("frm-dragging-field"),document.body.classList.add("frm-dragging"),t.helper.addClass("frm-sortable-helper"),t.helper.initialOffset=n.scrollTop,e.target.classList.add("frm-drag-fade"),yr(),(r=document.querySelectorAll("ul.start_divider")).length&&r.forEach(function(e){[].slice.call(e.children).forEach(function(e){(0===e.children.length||1===e.children.length&&"ul"===e.firstElementChild.nodeName.toLowerCase()&&0===e.firstElementChild.children.length)&&e.remove()})}),Fe(),Ne(),z()}function ee(){y.classList.remove("frm-dragging-field"),document.body.classList.remove("frm-dragging");var e=document.querySelector(".frm-drag-fade");e&&e.classList.remove("frm-drag-fade")}function te(e,t){!function(e){h.scrollTop(function(t,r){var n=e.clientY,i=y.offsetHeight,o=e.clientY-y.offsetTop,a=o-i/2;return o>i-50&&n>5?r+.1*a:o<70&&n<130?r-Math.abs(.1*a):r})}(e);var r=e.target,n=function(){for(var e=document.getElementById("frm-show-fields");e.querySelector(".frm-over-droppable");)e=e.querySelector(".frm-over-droppable");return"frm-show-fields"!==e.id||e.classList.contains("frm-over-droppable")||(e=!1),e}(),i=document.getElementById("frm_drag_placeholder");if(je(r,n,e)){i||(i=s("li",{id:"frm_drag_placeholder",className:"sortable-placeholder"}));var o,a=t.helper.get(0);if((a.classList.contains("form-field")||a.classList.contains("frm_field_box"))&&(a.style.transform="translateY("+(o=t.helper,y.scrollTop-o.initialOffset+"px)")),"frm-show-fields"===n.id||n.classList.contains("start_divider"))return i.style.left=0,void function(e){var t,r=e.y,n=e.placeholder,i=jQuery(e.droppable),o=i.children().not(".edit_field_type_end_divider");if(0===o.length)i.prepend(n),t=0;else{var a=ne(i,r);if(a===o.length){var l=jQuery(o.get(a-1));t=l.offset().top+l.outerHeight(),i.append(n);var s=i.children(".edit_field_type_end_divider");s.length&&i.append(s)}else t=jQuery(o.get(a)).offset().top,jQuery(o.get(a)).before(n)}t-=i.offset().top,n.style.top=t+"px"}({droppable:n,y:e.clientY,placeholder:i});i.style.top="",function(e){var t,r=e.x,n=e.placeholder,i=jQuery(e.droppable),o=oe(i);if(o.length){var a=function(e,t){var r,n,i,o,a=oe(e);for(o=0,r=a.length-1;r>=0;--r)if(n=a.get(r),t>(i=jQuery(n).offset().left)){o=r,t>i+jQuery(n).outerWidth()/2&&(o=r+1);break}return o}(i,r);if(a===o.length){var l=jQuery(o.get(a-1));t=l.offset().left+l.outerWidth(),i.append(n)}else t=jQuery(o.get(a)).offset().left,jQuery(o.get(a)).before(n),t-=0===a?4:8;t-=i.offset().left,n.style.left=t+"px"}}({droppable:n,x:e.clientX,placeholder:i})}else i&&i.remove()}function re(e,t){if(q.dragging){q.dragging=!1;var r=t.draggable[0],n=document.getElementById("frm_drag_placeholder");if(!n)return t.helper.remove(),void g();!function(e){if(e.previousElementSibling&&e.previousElementSibling.classList.contains("frm-is-collapsed")){var t=jQuery(e).prevUntil('[data-type="break"]');if(t.length){var r=t.find(".frm-collapse-page").get(0);r&&r.click()}}}(n);var i=t.helper.parent(),o=t.helper.get(0).closest("ul.start_divider"),a=n.closest("ul.start_divider");r.classList.contains("frm-new-field")?function(e){if(pe(e))wp.hooks.doAction("frm_stopped_inserting_by_dragging",e);else{var t=document.getElementById("frm_drag_placeholder"),r=e.replace("|","-")+"_"+be(),n=s("li",{id:r,className:"frm-wait frmbutton_loadingnow"}),i=jQuery(n),o=ce(jQuery(t)),a=ue(o),l=fe(o);t.parentNode.insertBefore(n,t),t.remove(),ae(i);var d=0;"summary"===e&&(d=jQuery(".frmbutton_loadingnow#"+r).prevAll('li[data-type="break"]').length?1:0),jQuery.ajax({type:"POST",url:ajaxurl,data:_e(e,l,a,d),success:function(t){ge(t,i);var r=ye(t);r&&wp.hooks.doAction("frm_after_field_added_in_form_builder",{field:t,fieldId:r,fieldType:e,form_id:a})},error:ve})}}(r.id):(function(e,t){t.parentNode.insertBefore(e,t)}(r,n),function(e){if("UL"===e.nodeName&&!e.classList.contains("start_divider")&&"frm-show-fields"!==e.id){var t=e.closest("li");t&&!t.classList.contains("ui-draggable")&&X(t,".frm-move")}}(n.parentElement));var l=o?parseInt(o.closest(".edit_field_type_divider").getAttribute("data-fid")):0,d=a?parseInt(a.closest(".edit_field_type_divider").getAttribute("data-fid")):0;n.remove(),t.helper.remove();var c=i.length?oe(i):[];!function(e,t){var r;e.length&&(t.length?ae(t.first()):(r=e.get(0).closest("li.frm_field_box"))&&!r.classList.contains("edit_field_type_divider")&&r.remove())}(i,c),function(e,t){0===t.length&&1===oe(jQuery(e.parentNode)).length||ae(jQuery(e))}(r,c),l!==d&&me(jQuery(r),o),g()}}function ne(e,t){var r,n,i,o,a=e.children().not(".edit_field_type_end_divider"),l=a.length;if(!document.querySelector(".frm-has-fields .frm_no_fields"))return 0;for(o=0,r=l-1;r>=0;--r)if(n=a.get(r),t>(i=jQuery(n).offset().top)){o=r,t>i+jQuery(n).outerHeight()/2&&(o=r+1);break}return o}function ie(){document.querySelectorAll("ul#frm-show-fields, ul.start_divider").forEach(function(e){e.childNodes.forEach(function(e){void 0!==e.classList&&(e.classList.contains("edit_field_type_end_divider")||void 0!==e.classList&&e.classList.contains("form-field")&&We(e))})}),kn(),document.querySelectorAll(".edit_field_type_end_divider").forEach(function(e){return e.parentNode.append(e)}),document.querySelectorAll("li.form_field_box:not(.form-field)").forEach(function(e){return!e.children.length&&e.remove()}),En();var e=new Event("frm_sync_after_drag_and_drop",{bubbles:!1});document.dispatchEvent(e)}function oe(e){var t=jQuery(),r=e.get(0);return r.children?(Array.from(r.children).forEach(function(e){if("none"!==e.style.display){var r=e.classList;!r.contains("form-field")||r.contains("edit_field_type_end_divider")||r.contains("frm-sortable-helper")||(t=t.add(e))}}),t):t}function ae(e,t){var r,n,i,o;void 0===t&&(t="even"),r=e.parent().children("li.form-field, li.frmbutton_loadingnow").not(".edit_field_type_end_divider"),n=r.length,i=["frm_full","frm_half","frm_third","frm_fourth","frm_sixth","frm_two_thirds","frm_three_fourths","frm1","frm2","frm3","frm4","frm5","frm6","frm7","frm8","frm9","frm10","frm11","frm12"],"even"===t&&5!==n?r.each(de(i,$t(n))):"clear"===t?r.each(de(i,"")):(o=["left","right","middle","even"].includes(t)?function(e){return Vt(n,t,e)}:function(e){return lr(t[e])},r.each(de(i,o))),le(e.parent(),r.length)}function le(e,t){var r,n;if(void 0!==e.offset()){if(r=t>=2,null===(n=document.getElementById("frm_field_group_controls"))){if(!r)return;(n=d()).id="frm_field_group_controls",n.setAttribute("role","group"),n.setAttribute("tabindex",0),function(e){var t,r;(t=document.createElement("span")).innerHTML='';var n=I("Set Row Layout","formidable");se(t,n),zt(t,n),(r=document.createElement("span")).innerHTML='',r.classList.add("frm-move");var i=I("Move Field Group","formidable");se(r,i),zt(r,i),e.innerHTML="",e.append(t),e.append(r),e.append(function(){var e=c({className:"dropdown"}),t=u({className:"frm_bstooltip frm-hover-icon frm-dropdown-toggle dropdown-toggle",children:[c({child:f({href:"#frm_thick_more_vert_icon"})}),c({className:"screen-reader-text",text:I("Toggle More Options Dropdown","formidable")})]});frmDom.setAttributes(t,{title:I("More Options","formidable"),"data-bs-toggle":"dropdown","data-bs-container":"body","data-bs-display":"static"}),zt(t,I("More Options","formidable")),e.append(t);var r=d({className:"frm-dropdown-menu dropdown-menu dropdown-menu-right"});return r.setAttribute("role","menu"),e.append(r),e}())}(n),O.append(n)}e.append(n),n.style.display=r?"block":"none"}}function se(e,t){e.setAttribute("data-bs-toggle","tooltip"),e.setAttribute("data-bs-container","body"),e.setAttribute("title",t),e.addEventListener("mouseover",function(){null===e.getAttribute("data-original-title")&&jQuery(e).tooltip()})}function de(e,t){return function(r){var n,i,o,a,l,s,d;for(n="function"==typeof t?t(r):t,i=e.length,l=!1,o=0;o0&&document.getElementById("form_id").value!==r||(i.last_row_field_ids=function(){var e=document.querySelector(".edit_field_type_submit");if(!e)return[];for(var t=e.parentNode.children,r=[],n=0;nt.childElementCount-1:o<=jQuery(t.querySelector(".edit_field_type_submit").closest("#frm-show-fields > li")).index()}if(n)return!(t.classList.contains("start_divider")||!we(t.parentElement)&&(!we(t.parentElement.nextElementSibling)||e.parentElement.querySelector("li.frm_field_box:not(.edit_field_type_submit)")));var a,l,s,d=t.classList.contains("start_divider")&&null!==t.closest(".repeat_section"),c=null!==t.closest(".repeat_section");if(d||c){if(e.classList.contains("edit_field_type_gdpr")||"gdpr"===e.id)return!1;if(wp.hooks.applyFilters("frm_deny_drop_in_repeater",!1,e))return!1}if(!d){if(a=oe(jQuery(t)),l=jQuery(e),!(a.length<12)&&(a.length>12||(s=l.attr("data-fid"),1!==jQuery(a).filter('[data-fid="'+s+'"]').length)))return!1;if("divider"===e.id&&t.closest(".start_divider"))return!1}return e.classList.contains("frm-new-field")?function(e,t){var r=e.classList,n=r.contains("frm_tbreak"),i=r.contains("frm_thidden"),o=r.contains("frm_tdivider"),a=r.contains("frm_tform"),l=r.contains("frm_tuser_id");return"frm-show-fields"===t.id||t.classList.contains("start_divider")?!(n||i||o||a)||(!(t.classList.contains("start_divider")||null!==t.closest(".start_divider"))||!a&&!o):!(xe(t)||i||n||l)}(e,t):function(e,t){if(Qe(e))return function(e,t){return!(!t.classList.contains("start_divider")||null!==e.querySelector(".start_divider"))}(e,t);if(e.classList.contains("edit_field_type_break"))return!1;if(t.classList.contains("start_divider"))return function(e){return!e.classList.contains("edit_field_type_form")&&!e.querySelector(".edit_field_type_form")&&!(e.classList.contains("edit_field_type_divider")||e.querySelector(".edit_field_type_divider"))}(e);var r=e.classList.contains("edit_field_type_hidden"),n=e.classList.contains("edit_field_type_user_id");return!r&&!n&&function(e,t){if(xe(t))return!1;if(jQuery(e).children("ul.frm_sorting").not(".start_divider").length>0)return!1;var r=e.classList.contains("edit_field_type_divider")||e.querySelector(".edit_field_type_divider"),n=e.classList.contains("edit_field_type_form");return null===t.closest(".start_divider")||!r&&!n}(e,t)}(e,t)}function we(e){return e&&e.matches("#frm-show-fields > li:last-child")}function Qe(e){return e.classList.contains("frm_field_box")&&!e.classList.contains("form-field")}function xe(e){return null!==e.querySelector(".edit_field_type_break, .edit_field_type_hidden, .edit_field_type_user_id")}function Ee(e){var t=document.getElementById(e),r=jQuery(t),n=[],i=function(e){var t=e.querySelector(".frm_hidden_fdata");e.classList.add("frm_load_now"),null!==t&&n.push(t.innerHTML)};i(t);for(var o=ke(t);o&&n.length<15;)i(o),o=ke(o);jQuery.ajax({type:"POST",url:ajaxurl,data:{action:"frm_load_field",field:n,form_id:E,nonce:frmGlobal.nonce},success:function(e){return function(e,t,r){var n,i;if(0===(e=e.replace(/^\s+|\s+$/g,"")).indexOf("{")){for(n in e=JSON.parse(e)){jQuery("#frm_field_id_"+n).replaceWith(e[n]);var o=document.getElementById("frm_field_id_"+n);o&&(o.querySelectorAll("[data-toggle]").forEach(function(e){return e.setAttribute("data-bs-toggle",e.getAttribute("data-toggle"))}),o.querySelectorAll(".frm-dropdown-menu").forEach(function(e){return e.classList.add("dropdown-menu")})),V("#frm_field_id_"+n+".edit_field_type_divider ul.frm_sorting"),X(document.getElementById("frm_field_id_"+n))}((i=t.nextAll(".frm_field_loading:not(.frm_load_now)")).length||(i=jQuery(document.getElementById("frm-show-fields")).find(".frm_field_loading:not(.frm_load_now)")).length)&&Ee(i.attr("id")),so(),Fr(),Ie();var a=new Event("frm_ajax_loaded_field",{bubbles:!1});a.frmFields=r.map(function(e){return JSON.parse(e)}),document.dispatchEvent(a)}else jQuery(".frm_load_now").removeClass(".frm_load_now").html("Error")}(e,r,n)}})}function ke(e){var t;return e.nextElementSibling?e.nextElementSibling:null===(t=e.parentNode)||void 0===t||null===(t=t.closest(".frm_field_box"))||void 0===t||null===(t=t.nextElementSibling)||void 0===t?void 0:t.querySelector(".form-field")}function Ae(){var e=jQuery(this);if(e.hasClass("disabled"))return!1;var t=e.closest(".frmbutton"),r=t.attr("id");if(t.hasClass("frm_at_limit"))return Se(),!1;if(!pe(r)){var n=0;"summary"===r&&(n=b.children('li[data-type="break"]').length>0?1:0);var i=E;return jQuery.ajax({type:"POST",url:ajaxurl,data:_e(r,0,i,n),success:function(e){Le(e);var t=ye(e);t&&wp.hooks.doAction("frm_after_field_added_in_form_builder",{field:e,fieldId:t,fieldType:r,form_id:i})},error:ve}),!1}}function Se(){var e=document.querySelector(".frm_wrap");if(e){var t=document.createElement("a");t.setAttribute("data-frmverify",I("This field type has reached its limit.","formidable")),e.append(t),t.click(),t.remove();var r=document.getElementById("frm-confirmed-click");r&&(r.style.display="none")}}function Le(e){document.getElementById("frm_form_editor_container").classList.add("frm-has-fields");var t=Ge(e),r=b[0].querySelector(".edit_field_type_submit");r?jQuery(r.closest(".frm_field_box:not(.form-field)")).before(t):b.append(t),Ue(e,!0),t.each(function(){$(this.querySelector("ul.frm_sorting")),X(this.querySelector(".form-field"),".frm-move")})}function Ie(){var e=!0,t=document.querySelectorAll(".frmjs_prod_field_opt_cont");b.find("li.edit_field_type_product").length>1&&(e=!1);for(var r=0;r',i.append(document.createTextNode(" ")),i.append(o),n.append(i),e.append(n)})}(t,!0===e),(r=jQuery(t)).offset().left>jQuery(window).width()-r.outerWidth()?t.style.left=-r.outerWidth()+"px":y&&r.offset().left").addClass("frm_field_box").html(jQuery("
      ").addClass("frm_grid_container frm_sorting").append(e)))}),r}function We(e){var t=s("ul",{className:"frm_grid_container frm_sorting"}),r=s("li",{className:"frm_field_box",child:t});e.replaceWith(r),t.append(e),$(t),X(r,".frm-move")}function Ue(e,t){var r,n,i=/id="(\S+)"/.exec(e),o=document.getElementById(i[1]),l="#"+i[1]+".edit_field_type_divider ul.frm_sorting.start_divider",s=jQuery(l),c=o.getAttribute("data-type");r=e,(n=d()).innerHTML=r,n.querySelectorAll(".form-field").forEach(Ve);var u,f,m=!1;if(Mi(),V(l),"quantity"===c&&function(e){var t=e.getAttribute("data-fid"),r=document.getElementById("field_options[product_field_"+t+"]");null!==r&&(rt(r),ii(document.getElementById("frm-single-settings-"+t)))}(o),"product"!==c&&"quantity"!==c||Ie(),s.length)s.parent(".frm_field_box").children(".frm_no_section_fields").addClass("frm_block");else{var _=jQuery(o).closest("ul.frm_sorting.start_divider");_.length&&(An(_),m=!0)}e.includes("frm-collapse-page")&&Fr(),f="frm-newly-added",(u=o).classList?u.classList.add(f):u.className+=" "+f,setTimeout(function(){o.classList.remove("frm-newly-added")},1e3);var p,g=o.querySelector("#frm-last-row-fields-order");if(g&&((p=JSON.parse(g.value))&&"object"===a(p)&&Object.keys(p).forEach(function(e){var t=document.querySelector('input[name="field_options[field_order_'+e+']"]');t&&(t.value=p[e])})),t){var y=o.getBoundingClientRect(),h=document.getElementById("post-body-content");y.top>=0&&y.left>=0&&y.right<=(window.innerWidth||document.documentElement.clientWidth)&&y.bottom<=(window.innerHeight||document.documentElement.clientHeight)||h.scroll({top:h.scrollHeight,left:0,behavior:"smooth"}),!1===m&&An(s)}Ke(),so(),document.getElementById("frm-show-fields").classList.remove("frm-over-droppable"),function(e){var t=document.getElementById(e);t&&t.dataset.limit&&kr(e)>=t.dataset.limit&&t.classList.add("frm_at_limit")}(c),o.querySelectorAll("[data-toggle]").forEach(function(e){return e.setAttribute("data-bs-toggle",e.getAttribute("data-toggle"))}),o.querySelectorAll(".frm-dropdown-menu").forEach(function(e){return e.classList.add("dropdown-menu")});var v=new Event("frm_added_field",{bubbles:!1});v.frmField=o,v.frmSection=l,v.frmType=c,v.frmToggles=m,document.dispatchEvent(v)}function Ve(e){if(e.dataset.fid){var t=document.getElementById("draft_fields");t&&(""===t.value?t.value=e.dataset.fid:t.value.split(",").includes(e.dataset.fid)||(t.value+=","+e.dataset.fid))}}function $e(e){jQuery("#new_fields .frm-single-settings").addClass("frm_hidden"),jQuery("#frm-options-panel > .frm-single-settings").removeClass("frm_hidden"),Ke(e)}function Ke(e){jQuery("li.ui-state-default.selected").removeClass("selected"),jQuery(".frm-show-field-settings.selected").removeClass("selected"),e||yr()}function Je(){var e=this.value,t=function(e){var t,r=[],n=e.split(""),i=n.length,a=["{","[","("],l={"}":"{",")":"(","]":"["},s=!1;for(t=0;t0||s?o.unmatched_parens+"\n\n":""}(e);t+=function(e,t){var r=function(e,t){var r="";return function(e){return jQuery(e).siblings('label[for^="calc_type"]').children("input").prop("checked")}(t)||/\[(date|time|email|ip)\]/.test(e)&&(r=o.text_shortcodes+"\n\n"),r}(e,t);return r+=function(e){var t="";return/\[id\]|\[key\]|\[if\s\w+\]|\[foreach\s\w+\]|\[created-at(\s*)?/g.test(e)&&(t+=o.view_shortcodes+"\n\n"),t}(e)}(e,this),""!==t&&M(e+"\n\n"+t)}function Xe(e,t){for(var r=!1,n=0;n"+l[t].fieldName+"")):(r=r?" checked":"",i.push('"));e.innerHTML=i.join("")}function nt(){for(var e=document.querySelectorAll(".frmjs_prod_field_opt"),t=0;t'):(c.innerHTML=_n(d),"TEXTAREA"===c.nodeName&&c.classList.contains("wp-editor-area")&&jQuery(c).trigger("change"),c.classList.contains("frm_primary_label")&&"break"===c.nextElementSibling.getAttribute("data-ftype")&&(c.nextElementSibling.querySelector(".frm_button_submit").textContent=d)))}function at(e){var t=parseFloat(e.getAttribute("max")),r=parseFloat(e.getAttribute("min"));return(t-r)/2+r}function lt(){var e,t=this.getAttribute("data-fid"),r="";["field_options_max_","frm_format_"].forEach(function(e){var n=document.getElementById(e+t);n&&(r+=n.value)}),"text"===(e=document.getElementsByName("field_options[type_"+t+"]")[0]).options[e.selectedIndex].value&&dt(""!==r,".frm_invalid_msg"+t)}function st(){var e=this.id.replace("frm_","").replace("req_field_",""),t=this.checked,r=jQuery("#field_label_"+e+" .frm_required");if(dt(t,".frm_required_details"+e),t){var n=jQuery('input[name="field_options[required_indicator_'+e+']"]');""===n.val()&&n.val("*"),r.removeClass("frm_hidden")}else r.addClass("frm_hidden")}function dt(e,t){var r=jQuery(t);if(e)r.fadeIn("fast").closest(".frm_validation_msg").fadeIn("fast");else{var n=r.fadeOut("fast").closest(".frm_validation_box"),i=n.css("display","block").children(":not("+t+"):visible").length;n.css("display",""),0===i&&r.closest(".frm_validation_msg").fadeOut("fast")}}function ct(){var e=jQuery(this).closest(".frm-single-settings").data("fid"),t=jQuery(".frm_unique_details"+e);if(this.checked){t.fadeIn("fast").closest(".frm_validation_msg").fadeIn("fast");var r=jQuery(".frm_unique_details"+e+" input");""===r.val()&&r.val(o.default_unique)}else{var n=t.fadeOut("fast").closest(".frm_validation_box"),i=n.css("display","block").children(":not(.frm_unique_details"+e+"):visible").length;n.css("display",""),0===i&&t.closest(".frm_validation_msg").fadeOut("fast")}}function ut(){var e=jQuery(this).closest(".frm-single-settings").data("fid"),t=jQuery(this).val(),r=jQuery(document.getElementById("frm_field_id_"+e));if(dt(""!==t,".frm_conf_details"+e),""!==t){var n=jQuery(".frm_validation_box .frm_conf_details"+e+" input");""===n.val()&&n.val(o.default_conf),function(e){var t=document.getElementsByName("field_options[type_"+e+"]")[0].value;ft(document.getElementById("field_description_"+e),"field_options[description_"+e+"]",o["enter_"+t]),ft(document.getElementById("conf_field_description_"+e),"field_options[conf_desc_"+e+"]",o["confirm_"+t])}(e),"inline"===t?r.removeClass("frm_conf_below").addClass("frm_conf_inline"):"below"===t&&r.removeClass("frm_conf_inline").addClass("frm_conf_below"),jQuery(".frm-conf-box-"+e).removeClass("frm_hidden")}else jQuery(".frm-conf-box-"+e).addClass("frm_hidden"),setTimeout(function(){r.removeClass("frm_conf_inline frm_conf_below")},200)}function ft(e,t,r){e.innerHTML===o.desc&&(e.innerHTML=r,document.getElementsByName(t)[0].value=r)}function mt(e){var t=JSON.parse(this.getAttribute("data-opts"));return e.preventDefault(),document.getElementById("frm_bulk_options").value=t.join("\n"),!1}function _t(){var e,t,r,n,i=jQuery(this).closest(".frm-single-settings").data("fid"),o=jQuery("#frm_field_"+i+"_opts .frm_option_template").prop("outerHTML"),a=jQuery(this).data("opttype"),l=0,s=function(e){for(var t=0,r=0,n=jQuery("#frm_field_"+e+"_opts li"),i=0;ti||"000"===i)&&(i=r)}return i}(i);if("000"!==s&&(l=s+1),"other"===a){document.getElementById("other_input_"+i).value=1;var d=jQuery(this).data("ftype");"radio"!==d&&"select"!==d||jQuery(this).fadeOut("slow");var c={action:"frm_add_field_option",field_id:i,opt_key:l,opt_type:a,nonce:frmGlobal.nonce};jQuery.post(ajaxurl,c,function(e){jQuery(document.getElementById("frm_field_"+i+"_opts")).append(e),on(i)})}else{o=(o=(o=(o=(o=o.replace(new RegExp('optkey="000"',"g"),'optkey="'+l+'"')).replace(new RegExp("-000_","g"),"-"+l+"_")).replace(new RegExp('-000"',"g"),"-"+l+'"')).replace(new RegExp("\\[000\\]","g"),"["+l+"]")).replace("frm_hidden frm_option_template",""),Do(i,o={newOption:o});var u=this.closest(".frm_single_option");u?u.after(o.newOption):jQuery("#frm_field_".concat(i,"_opts")).append(o.newOption),on(i)}null==(n=(e=this).classList.contains("frm-add-option-legacy")?null===(t=e.closest(".frm-collapse-me"))||void 0===t?void 0:t.querySelector(".frm_sortable_field_opts"):e.closest(".frm_sortable_field_opts"))||null===(r=n.querySelectorAll(".frm_remove_tag.frm_disabled"))||void 0===r||r.forEach(function(e){return e.classList.remove("frm_disabled")}),Mi()}function pt(){gt(jQuery(this).closest(".frm-single-settings").data("fid"),this.value)}function gt(e,t){var r=jQuery(".frm_multiple_cont_"+e);"select"===t?r.fadeIn("fast"):r.fadeOut("fast")}function yt(){var e=jQuery(this).closest(".frm-single-settings").data("fid");qo(jQuery(".field_"+e+"_option_key")),jQuery(".field_"+e+"_option").toggleClass("frm_with_key")}function ht(){var e,t,r=jQuery(this).closest(".frm-single-settings"),n=r.data("fid"),i=document.getElementById("frm_field_id_"+n);wt(jQuery(this)),qo(jQuery(".field_"+n+"_image_id")),qo(jQuery(".frm_toggle_image_options_"+n)),qo(jQuery(".frm_image_size_"+n)),qo(jQuery(".frm_alignment_"+n)),qo(jQuery(".frm-add-other#frm_add_field_"+n)),(e=hn(n))?(bt(n,"inline"),vt(i),t=nn(n),i.classList.add("frm_image_options"),i.classList.add("frm_image_size_"+t),r.find(".frm-bulk-edit-link").hide()):(i.classList.remove("frm_image_options"),vt(i),bt(n,"block"),r.find(".frm-bulk-edit-link").show()),wp.hooks.doAction("frm_image_options_toggled",r[0],e)}function vt(e){e.classList.remove("frm_image_size_","frm_image_size_small","frm_image_size_medium","frm_image_size_large","frm_image_size_xlarge")}function bt(e,t){jQuery("#field_options_align_"+e).val(t).trigger("change")}function jt(){var e=jQuery(this).closest(".frm-single-settings").data("fid"),t=document.getElementById("frm_field_id_"+e);Qt(),hn(e)&&(vt(t),t.classList.add("frm_image_options"),t.classList.add("frm_image_size_"+nn(e)))}function wt(e){var t=e.closest(".frm-single-settings").data("fid");jQuery(".field_"+t+"_option").trigger("change")}function Qt(){wt(jQuery(this))}function xt(e){var t,r=e.target.closest(".frm_image_preview_wrapper");if(null!==(t=wp)&&void 0!==t&&t.media&&(null==r||!r.dataset.upgrade)){e.preventDefault(),wp.media.model.settings.post.id=0;var n=wp.media.frames.file_frame=wp.media({multiple:!1,library:{type:["image"]}});n.on("select",function(){var e=n.state().get("selection").first().toJSON(),t=r.querySelector("img");t.setAttribute("src",e.url),t.classList.remove("frm_hidden"),t.removeAttribute("srcset"),r.querySelector(".frm_image_preview_frame").style.display="block",r.querySelector(".frm_image_preview_title").textContent=e.filename,r.querySelector(".frm_choose_image_box").style.display="none";var i=jQuery(r);i.siblings('input[name*="[label]"]').data("frmimgurl",e.url),i.find("input.frm_image_id").val(e.id).trigger("change"),wp.media.model.settings.post.id=0}),n.open()}}function Et(e){var t=jQuery(this).closest(".frm_image_preview_wrapper");e.preventDefault(),e.stopPropagation(),t.find("img").attr("src",""),t.find(".frm_image_preview_frame").hide(),t.find(".frm_choose_image_box").show(),t.find("input.frm_image_id").val(0).trigger("change")}function kt(){var e=jQuery(this).closest("li").find(".frm_form_fields select");this.checked?e.attr("multiple","multiple"):e.removeAttr("multiple")}function At(){var e=document.getElementById("dropform-search-input");null!==e&&setTimeout(function(){e.focus()},100)}function St(e){var t=e.target,r=t.closest(".frm_warning_style");jQuery(r).fadeOut(400,function(){return r.remove()});var n=t.dataset.action,i=new FormData;p(n,i)}function Lt(e){e.preventDefault()}function It(){var e,t=this.parentNode,r=t.parentNode,n=r.querySelectorAll("li:not(.frm_hidden)");2===n.length&&(null===(e=Array.from(n).find(function(e){return e!==t}).querySelector(".frm_remove_tag"))||void 0===e||e.classList.add("frm_disabled"));var i,o=this.getAttribute("data-fid");jQuery(t).fadeOut("fast",function(){wp.hooks.doAction("frm_before_delete_field_option",this),jQuery(t).remove(),jQuery(r).find(".frm_other_option").length<1&&(null!==(i=document.getElementById("other_input_"+o))&&(i.value=0),jQuery("#other_button_"+o).fadeIn("fast"))}),Mi()}function Bt(){var e,t,r,n;(e=jQuery(this)).is(":checked")&&(t=function(){setTimeout(function(){e.prop("checked",!1)},0)},r=function(){e.off("mouseup",n)},n=function(){t(),r()},e.on("mouseup",n),e.one("mouseout",r))}function qt(){this.value===o.new_option&&(this.setAttribute("data-value-on-focus",this.value),this.value="")}function Ct(e){return B(I("Are you sure you want to delete these %1$s selected field(s)?","formidable"),e)}function Nt(){var e=o.conf_delete,t=this.parentNode.parentNode.parentNode.parentNode.parentNode,r=t.parentNode,n=jQuery(this).closest("li.form-field"),i=n.data("fid");if("divider"===n.data("ftype")){var a=document.querySelectorAll(".frm-field-group-hover-target .start_divider .frm_field_box"),l=0;a.forEach(function(e){var t=e.querySelectorAll("li.form-field");t&&(l+=t.length)}),l&&(e=Ct(++l))}return r.classList.contains("frm-section-collapsed")||r.classList.contains("frm-page-collapsed")||("divider_section_only"===t.className&&(e=o.conf_delete_sec),this.setAttribute("data-frmverify",e),this.setAttribute("data-frmverify-btn","frm-button-red"),this.setAttribute("data-deletefield",i),Ne(),D(this)),!1}function Tt(){this.closest("li.form-field").click()}function Ot(){var e,t;null!==(e=document.querySelector(".frm-field-group-hover-target"))&&(e.classList.add("frm-selected-field-group"),(t=document.createElement("div")).classList.add("frm-delete-field-groups","frm_hidden"),document.body.append(t),t.click())}function Ft(){var e=document.querySelector(".frm-field-group-hover-target");if(null!==e){var t="frm_field_group_"+be(),r=document.createTextNode("");We(r);var n=jQuery(r).closest("li").get(0);n.classList.add("frm_hidden");var i=n.querySelector("ul");i.id=t,jQuery(e.closest("li.frm_field_box")).after(n);var o=oe(jQuery(e)),a=[],l=[],s=o.length,d={},c=0;jQuery(n).on("frm_added_duplicated_field_to_row",function(e,t){if(d[jQuery(t.duplicatedFieldHtml).attr("data-fid")]=t.originalFieldId,!(s>++c)){var r=jQuery(i),o=oe(r);l.forEach(function(e){e.remove()});for(var u=0;u6?(t.append(Gt(e,"even")),t):(5!==e&&t.append(Gt(e,"even")),e%2==1&&t.append(Gt(e,"middle")),e<6?(t.append(Gt(e,"left")),t.append(Gt(e,"right"))):((r=d()).classList.add("frm_fourth"),t.prepend(r)),t)}(e),null!==(o=t.closest("ul.frm_sorting"))&&function(e,t){var r,n,i;for(r=t.children.length,n=0;n6?"frm_full":e%2==1?"frm_fourth":"frm_third"}return r.classList.add(n),r.setAttribute("layout-type",t),r.append(function(e,t){var r,n,i;for(r=Xt(),n=0;n6?"frm1":[2,3,4,6].includes(e)?lr(12/e):5===e&&void 0!==t?0===t?"frm4":"frm2":"frm12"}function Kt(e){switch(e){case 2:case 3:return"frm3";case 4:case 5:return"frm2";case 6:return"frm1"}return"frm12"}function Jt(e){switch(e){case 2:return"frm9";case 3:case 4:return"frm6";case 5:return"frm4";case 6:return"frm7"}return"frm12"}function Xt(){var e=d();return e.classList.add("frm_grid_container"),e}function Yt(){var e=document.querySelector(".frm-field-group-hover-target");if(e){var t=this.getAttribute("layout-type");ae(oe(jQuery(e)).first(),t),ur()}}function Zt(){var e,t;e=er(),t=this.getAttribute("layout-type"),ae(oe(e).first(),t),yr()}function er(){var e=jQuery(".frm-selected-field-group"),t=e.first();return e.not(t).each(function(){oe(jQuery(this)).each(function(){var e=this.parentNode;oe(t).last().after(this),jQuery(e).children("li.form-field").length||e.closest("li.frm_field_box").remove()})}),En(),ae(oe(t).first()),t}function tr(){null===this.closest(".frm-merge-fields-into-row")&&rr(oe(jQuery(".frm-field-group-hover-target")))}function rr(e){var t,r,n,i,o,a,l,s,c,u,f,m,_,p,g;for(t=e.length,(r=document.getElementById("frm_field_group_popup")).innerHTML="",(n=d()).style.padding="0 24px",i=$t(5===t?6:t),(o=d()).style.padding="20px 0",o.classList.add("frm_grid_container"),5===t&&((a=document.createElement("span")).classList.add("frm1"),o.append(a)),!1!==(l=jr()>0&&or($t(t)))&&l>=12&&(l=Math.floor(12/t)),s=0;s',""),t);e.prepend(r),document.getElementById("frm-field-group-message-dismiss").addEventListener("click",function(){_r(document.getElementById("frm-field-group-message"))})}}(),"ul"===e.originalEvent.target.nodeName.toLowerCase()){var t=document.querySelector(".frm-field-group-hover-target");if(t){var r=e.ctrlKey||e.metaKey,n=e.shiftKey,i=t.classList.contains("frm-selected-field-group"),o=function(){var e=jQuery(".frm-selected-field-group");if(e.length)return e;var t=pr();if(t){var r=t.closest("ul");if(r&&1===oe(jQuery(r)).length)return r.classList.add("frm-selected-field-group"),jQuery(r)}return jQuery()}(),a=o.length;if(r||n){var l=pr();if(null===l||jQuery(l).siblings("li.form-field").length||(l.parentNode.classList.add("frm-selected-field-group"),++a),r){if(i)return--a,t.classList.remove("frm-selected-field-group"),void gr(a);++a}else if(n&&!i){++a;var s=o.first();(s.parent().index()=2||1===e&&oe(jQuery(document.querySelector(".frm-selected-field-group"))).length>1?function(){var e,t,r,n,i;if(null!==(e=document.getElementById("frm_field_multiselect_popup")))return e.classList.toggle("frm-unmergable",!vr()),e;(e=d()).id="frm_field_multiselect_popup",vr()||e.classList.add("frm-unmergable"),(t=d()).classList.add("frm-merge-fields-into-row"),t.textContent=I("Merge into row","formidable"),(r=document.createElement("a")).style.marginLeft="5px",r.classList.add("frm_icon_font","frm_arrowdown6_icon"),r.setAttribute("href","#"),t.append(r),e.append(t),(n=d()).classList.add("frm-multiselect-popup-separator"),e.append(n),(i=d()).classList.add("frm-delete-field-groups"),i.append(Rt("frm_trash_svg")),e.append(i),document.getElementById("post-body-content").append(e),jQuery(e).hide().fadeIn()}():hr(),Fe()}function yr(e){if(void 0!==e){if(null!==e.originalEvent.target.closest("#frm-show-fields"))return;if(e.originalEvent.target.classList.contains("frm-merge-fields-into-row"))return;if(null!==e.originalEvent.target.closest(".frm-merge-fields-into-row"))return;if(e.originalEvent.target.classList.contains("frm-custom-field-group-layout"))return;if(e.originalEvent.target.classList.contains("frm-cancel-custom-field-group-layout"))return}jQuery(".frm-selected-field-group").removeClass("frm-selected-field-group"),jQuery(document).off("click",yr),hr()}function hr(){var e=document.getElementById("frm_field_multiselect_popup");null!==e&&e.remove()}function vr(){var e,t,r,n,i;if(1===(r=(e=document.querySelectorAll(".frm-selected-field-group")).length))return!1;for(t=0,n=0;n12)return!1}return!0}function br(e){var t;null===e.originalEvent.target.closest("#frm_field_group_popup")&&(e.originalEvent.target.classList.contains("frm-custom-field-group-layout")||(t=Ht(jr(),document.querySelector(".frm-selected-field-group").firstChild),this.append(t)))}function jr(){var e=0;return jQuery(document.querySelectorAll(".frm-selected-field-group")).each(function(){e+=oe(jQuery(this)).length}),e}function wr(){var e,t,r,n;n=[],jQuery(".frm-selected-field-group > li.form-field").each(function(){n.push(this.dataset.fid)}),t=function(e){return function(t){t.preventDefault(),function(e){e.forEach(function(e){xr(e)})}(e)}}(e=n),null!==(r=document.getElementById("frm_field_multiselect_popup"))&&r.remove(),this.setAttribute("data-frmverify",Ct(e.length)),D(this);var i=document.getElementById("frm-confirmed-click");null==i||i.removeAttribute("data-deletefield"),jQuery(i).on("click",t),jQuery("#frm_confirm_modal").one("dialogclose",function(){jQuery(i).off("click",t)})}function Qr(){xr(this.getAttribute("data-deletefield"))}function xr(e){var t=jQuery("#frm_field_id_"+e);Er(e),t.hasClass("edit_field_type_divider")&&t.find("li.frm_field_box[data-fid]").each(function(){Er(this.getAttribute("data-fid"))}),kn()}function Er(e){jQuery.ajax({type:"POST",url:ajaxurl,data:{action:"frm_delete_field",field_id:e,nonce:frmGlobal.nonce},success:function(){var t,r,n,i=jQuery(document.getElementById("frm_field_id_"+e)),o=jQuery("#frm-single-settings-"+e);o.is(":visible")&&(null===(t=document.querySelector(".frm-settings-panel .frm-tabs-navs ul > li:first-child"))||void 0===t||t.click(),document.querySelector("#frm-options-panel .frm-single-settings").classList.remove("frm_hidden")),function(e){var t=e[0].querySelectorAll(".frm-inline-modal[data-fills]");t.length&&t.forEach(function(e){e.classList.add("frm_hidden"),e.removeAttribute("data-fills"),e.closest("form").append(e)})}(o),o.remove(),i.fadeOut("slow",function(){var e,t=i.closest(".start_divider"),r=i.data("type"),n=i.siblings("li.form-field");if(n.length||(i.is(".edit_field_type_end_divider")?n.length=i.closest("li.form-field").siblings():e=i.closest("ul.frm_sorting").parent()),i.remove(),"break"===r?Fr():"product"===r&&(Ie(),nt()),n.length?ae(n.first()):e.remove(),0===jQuery("#frm-show-fields li").length||function(){if(b.get(0).childElementCount>1)return!1;var e=b.get(0).firstElementChild.firstElementChild.querySelectorAll("li.frm_field_box");return!(e.length>1)&&e[0].classList.contains("edit_field_type_submit")}()){var o=document.getElementById("frm_form_editor_container");o.classList.remove("frm-has-fields"),o.classList.add("frm-empty-fields")}else t.length&&An(t);z()}),i.length&&(r=i.data("type"),(n=document.getElementById(r))&&n.dataset.limit&&kr(r)-11)for(document.getElementById("frm-fake-page").style.display="block",e=0;e200)&&(M(o.repeat_limit_min),this.value="")}function Yr(){var e=this.value;""!==e&&(e<1||e>200)&&(M(o.checkbox_limit),this.value="")}function Zr(e,t){jQuery(e).closest(".frm_field_box").find(".frm_"+t+"_form_row .frm_repeat_label").text(e.value)}function en(){var e=jQuery(this).closest(".frm-single-settings").data("fid"),t=this.value,r=document.getElementById("frm_show_selected_fields_"+e),n=document.getElementById("frm_show_selected_forms_"+e);jQuery(n).find("select").val(""),"form"===t?(n.style.display="inline",function(e){if(null!==e)for(;e.firstChild;)e.firstChild.remove()}(r)):(r.style.display="none",n.style.display="none",xn(t,e))}function tn(){var e,t;(e=rn(this))&&(t=jQuery(this).closest(".frm_single_option"),function(e,t,r){var n,i,o,a,l,s,c=r.data("optkey"),u=yn(e),f=jQuery('label[for="field_'+t+"-"+c+'"]'),m="field_options[options_"+e+"]["+c+"]",_=jQuery('input[name="'+m+'[label]"]');if(f.length<1)return on(e),void((o=r.find('input[name^="default_value_"]')).is(":checked")&&_.length>0&&jQuery('select[name^="item_meta['+e+']"]').val(_.val()));if(a=f.children("input"),n=_.length<1?(_=jQuery('input[name="'+m+'"]')).val():u?jQuery('input[name="'+m+'[value]"]').val():_.val(),!(_.length<1)){if(i=f[0].childNodes,hn(e))l=function(e,t,r){var n,i,o;return(n=e.find("img"))&&(i=n.attr("src")),o=vn(t),pn(r.val(),o,i)}(r,e,_),(s=f.find(".frm_image_option_container")).length>0?s.replaceWith(l):(i[i.length-1].nodeValue="",f.append(l));else{var p=!1;i.forEach(function(t,r){if(!1===p)"INPUT"===t.tagName&&(p=r);else if(r===p+1){var n="";!function(e){var t=document.getElementsByName("field_options[image_options_"+e+"]"),r=Array.from(t).find(function(e){return e.checked&&"buttons"===e.value});return void 0!==r}(e)?t.nodeValue=" "+_.val():(n=d({className:"frm_label_button_container",text:" "+_.val()}),f[0].replaceChild(n,t))}else t.remove()})}a.val(n),o=r.find('input[name^="default_value_"]'),a.prop("checked",!!o.is(":checked"))}}(e.fieldId,e.fieldKey,t))}function rn(e){var t;return!!(t=jQuery(e).closest(".frm_sortable_field_opts")).length&&{fieldId:t.attr("id").replace("frm_field_","").replace("_opts",""),fieldKey:t.data("key")}}function nn(e){var t,r=document.getElementById("field_options_image_size_"+e),n="";return null!==r&&""!==(t=r.value)&&(n=t),n}function on(e){var t,r,n,i,o,a=jQuery('[name^="item_meta['+e+']"]');if(!(a.length<1)){if(a.is("select"))null===(i=document.getElementById("frm_placeholder_"+e))||""===i.value?cn(a[0],{sourceID:e}):cn(a[0],{sourceID:e,placeholder:i.value});else{r=fn(e),jQuery("#field_"+e+"_inner_container > .frm_form_fields").html(""),o=rn(jQuery("#frm_delete_field_"+e+"-000_container"));var l=jQuery("#field_"+e+"_inner_container > .frm_form_fields"),s=hn(e),d=s?nn(e):"",c=s?"frm_image_option frm_image_"+d+" ":"",u=Oo(e);for(n="hidden"===a.attr("type")?a.data("field-type"):a.attr("type"),t=0;t=0;a--){var m;l=d[a];var _=null===(m=document.getElementById("frm_field_"+e+"_opts").querySelector('.frm_option_key input[type="text"]'))||void 0===m?void 0:m.value;_||(_=l),s=i.querySelector('option[value="'+_+'"]');var p=an(e,l),g=p.newValue,y=p.newLabel,h=document.querySelectorAll("#frm_field_"+e+"_opts input[data-value-on-focus]"),v=Array.from(h).find(function(e){return e.value===l});if(v){var b=v.dataset.valueOnFocus;if(b&&i.querySelector('option[value="'+b+'"]'))continue}sn(i,s,g,y)}null!==(s=i.querySelector('option[value=""]'))&&i.prepend(s)}}function sn(e,t,r,n){null!==t||e.querySelector('option[value="'+r+'"]')||((t=frmDom.tag("option",{text:n})).value=r),e.prepend(t)}function dn(e,t,r,n,i,o){var a,l="",s=t.key.includes("other"),d="field_"+n+"-"+t.key,c="scale"===e?"radio":e;return a='',this.getSingle=function(){return""!==(l=wp.hooks.applyFilters("frm_admin.build_single_option_template",l,{opt:t,type:e,fieldId:r,classes:o,id:d}))?l:'
      "+(s?a:"")+"
      "},this.getSingle()}function cn(e,t){if(null!==e){var r=t.sourceID,n=t.placeholder,i=Oo(r),o=t.other;!function(e){var t;if(void 0!==e.options)for(t=e.options.length-1;t>=0;t--)e.remove(t)}(e);for(var a=fn(r,e.id.includes("frm_field_logic_opt")),l=void 0!==n,s=0;s1&&void 0!==arguments[1]&&arguments[1],s=[],d=jQuery('input[name^="field_options[options_'+e+']"]').filter('[name$="[label]"], [name*="[other_"]'),c=Oo(e),u=vn(e),f=hn(e),m=yn(e);for(t=0;t0||(i=r=d[t].value,o=d[t].name.replace("field_options[options_"+e+"][","").replace("[label]","").replace("]",""),m&&(n=d[t].name.replace("[label]","[value]"),r=jQuery('input[name="'+n+'"]').val(),l&&""===i&&(i=""!==r?r:frm_admin_js.no_label)),f&&(i=pn(i,u,mn(d[t]))),a={saved:r,label:i=frmAdminBuild.hooks.applyFilters("frm_choice_field_label",i,e,d[t],f),checked:gn(d[t].id),key:o},c&&(n=d[t].name.replace("[label]","[price]"),a.price=jQuery('input[name="'+n+'"]').val()),s.push(a));return s}function mn(e){var t,r=jQuery(e).siblings(".frm_image_preview_wrapper");return r.length&&(t=r.find("img")).length?t.attr("src"):""}function _n(e){(e instanceof Element||e instanceof Document)&&(e=e.outerHTML);var t=jQuery.parseHTML(e).reduce(function(e,t){var r=frmDom.cleanNode(t);return"#text"===r.nodeName?e+r.textContent:e+r.outerHTML},"");return t!==e?_n(t):t}function pn(e,t,r){var n,i,a,l=e;return l=_n(l),r?i=m({src:r,alt:l}):(i=d({className:"frm_empty_url"})).innerHTML=o.image_placeholder_icon,n=t?" frm_label_with_image":"",(a=s("span",{className:"frm_text_label_for_image_inner"})).innerHTML=l,s("span",{className:"frm_image_option_container"+n,children:[i,s("span",{className:"frm_text_label_for_image",child:a})]})}function gn(e){var t=jQuery("#"+e);if(0===t.length)return!1;var r=t.siblings("input[type=checkbox]");return r.length&&r.prop("checked")}function yn(e){return bn("separate_value_"+e)}function hn(e){for(var t=!1,r=document.getElementsByName("field_options[image_options_"+e+"]"),n=0;n=0&&(r.splice(t,1),e.val(r),e.next(".btn-group").find('.multiselect-container input[value=""]').prop("checked",!1))}(jQuery(this))}function Bn(e){e.val(""),e.next(".btn-group").find('.multiselect-container input[value!=""]').prop("checked",!1)}function qn(){jQuery(".frm-hide-empty").each(function(){0===jQuery(this).text().trim().length&&jQuery(this).remove()})}function Cn(e){e.preventDefault(),function(e,t,r){var n=document.getElementById(e.getAttribute("data-open")),i=jQuery(e).closest("p,ul"),o=void 0!==t;if(i.hasClass("frm-open"))i.removeClass("frm-open"),n.classList.add("frm_hidden");else{if(o||(t=Wi(e)),null!==t){if(!o){var a=r.key;"Enter"!==a&&" "!==a&&t.focus()}i.after(n),n.setAttribute("data-fills",t.id.replace("-proxy-input","")),0===n.id.indexOf("frm-calc-box")&&Ze(n,!0)}i.addClass("frm-open"),n.classList.remove("frm_hidden"),wp.hooks.doAction("frm_show_inline_modal",n,e)}}(this,void 0,e)}function Nn(e){e.preventDefault(),this.parentNode.classList.add("frm_hidden"),jQuery('.frm-open [data-open="'+this.parentNode.id+'"]').closest(".frm-open").removeClass("frm-open")}function Tn(e){var t=e.target;t.closest(".frm-inline-modal.frm-modal-no-dismiss")||t.closest(".frm-show-inline-modal")||t.closest("#frm_adv_info")||t.closest(".frm-token-proxy-input")||document.querySelectorAll(".frm-inline-modal.frm-modal-no-dismiss:not(.frm_hidden)").forEach(function(e){e.classList.add("frm_hidden"),e.previousElementSibling.classList.remove("frm-open")})}function On(){var e,t=this.getAttribute("data-frmchange").split(",");for(e=0;e').before('')}function Yn(){var e="success";"options[edit_action]"===this.name&&(e="edit");var t=jQuery(this).val();jQuery("."+e+"_action_box").hide(),"redirect"===t?jQuery("."+e+"_action_redirect_box."+e+"_action_box").fadeIn("slow"):"page"===t?jQuery("."+e+"_action_page_box."+e+"_action_box").fadeIn("slow"):jQuery("."+e+"_action_message_box."+e+"_action_box").fadeIn("slow")}function Zn(e){if(m=e.target,p=jQuery(m),g=p.closest(".frm_form_action_settings"),(y=g.find(".widget-inside")).find("p, div, table").length||((_=g.find(".widget-top")).on("frm-action-loaded",function(){p.trigger("click"),g.removeClass("open"),y.hide()}),_.trigger("click"),0)){var t=e.target.closest(".frm_form_action_settings"),r=t.querySelectorAll(".wp-editor-area");r.length&&r.forEach(function(e){tinymce.EditorManager.execCommand("mceRemoveEditor",!0,e.id)});var n=jQuery(t).clone(),i=n.attr("id").replace("frm_form_action_",""),o=ei(i);n.find(".frm_action_id, .frm-btn-group").remove(),n.find('input[name$="['+i+'][ID]"]').val(""),n.find(".widget-inside").hide(),n.find("input[type=text], textarea, input[type=number]").prop("defaultValue",function(){return this.value}),n.find("input[type=checkbox], input[type=radio]").prop("defaultChecked",function(){return this.checked});var a=new RegExp("\\["+i+"\\]","g"),l=new RegExp("_"+i+'"',"g"),s=new RegExp("-"+i+'"',"g"),c=new RegExp('"'+i+'"',"g"),u=n.html().replace(a,"["+o+"]").replace(l,"_"+o+'"');u=u.replace(s,"-"+o+'"').replace(c,'"'+o+'"');var f=d({id:"frm_form_action_"+o,className:n.get(0).className});f.setAttribute("data-actionkey",o),f.innerHTML=u,f.querySelectorAll(".wp-editor-wrap, .wp-editor-wrap *").forEach(function(e){"string"==typeof e.className&&(e.className=e.className.replace(i,o)),e.id=e.id.replace(i,o)}),f.classList.remove("open"),document.getElementById("frm_notification_settings").append(f),r.length&&(r.forEach(function(e){frmDom.wysiwyg.init(e)}),f.querySelectorAll(".wp-editor-area").forEach(function(e){frmDom.wysiwyg.init(e)})),f.classList.contains("frm_single_on_submit_settings")&&f.querySelector("input.frm-page-search")&&yo(f),so(),wp.hooks.doAction("frm_after_duplicate_action",f)}var m,_,p,g,y}function ei(e){var t=parseInt(e,10)+11;return null!==document.getElementById("frm_form_action_"+t)&&(t=ei(++t)),t}function ti(){var e,t=jQuery(this).data("actiontype");if(!di(t)){var r=(e=Sr(document.querySelectorAll(".frm_form_action_settings"),"frm_form_action_"),void 0!==document.getElementById("frm_form_action_"+e)&&(e+=100),S>=e&&(e=S+1),S=e,e),n=E,i=document.createElement("div");i.classList.add("frm_single_"+t+"_settings");var o=document.getElementById("frm_notification_settings");o.append(i),jQuery.ajax({type:"POST",url:ajaxurl,data:{action:"frm_add_form_action",type:t,list_id:r,form_id:n,nonce:frmGlobal.nonce},success:function(e){Mi(),i.remove(),document.querySelectorAll(".frm_form_action_settings.open").forEach(function(e){return e.classList.remove("open")});var n=d();n.innerHTML=e;var a=n.querySelector(".widget-top");Array.from(n.children).forEach(function(e){return o.append(e)}),jQuery(".frm_form_action_settings").fadeIn("slow");var l=document.getElementById("frm_form_action_"+r);l.classList.add("open"),document.getElementById("post-body-content").scroll({top:l.offsetTop+10,left:0,behavior:"smooth"}),si(t),Xn("#frm_form_action_"+r),so(),yo(l),a&&jQuery(a).trigger("frm-action-loaded"),frmAdminBuild.hooks.doAction("frm_added_form_action",l)}})}}function ri(){var e=document.getElementById("frm_email_addon_menu").classList,t=document.getElementById("actions-search-input");e.contains("frm-all-actions")?(e.remove("frm-all-actions"),e.add("frm-limited-actions")):(e.add("frm-all-actions"),e.remove("frm-limited-actions")),t.value="",ko(t,"input")}function ni(e){e.on("Change",function(){!function(e){var t,r;(t=document.querySelector(".frm-single-settings:not(.frm_hidden)"))&&null!==(r=t.querySelector(".wp-editor-wrap"))&&r.classList.contains("tmce-active")&&!tinyMCE.activeEditor.isHidden()&&(e.targetElm.value=e.getContent(),jQuery(e.targetElm).trigger("change"))}(e)})}function ii(e){var t=this;if(null!==e)return this.fragment=document.createDocumentFragment(),this.initOnceInAllInstances=function(){void 0===ii.prototype.endMarker&&(ii.prototype.endMarker=document.getElementById("frm-end-form-marker"))},this.append=function(e){var r=null!==e?e.parentElement.classList:"";null!==e&&(r.contains("frm_field_box")||r.contains("divider_section_only"))&&t.fragment.append(e)},this.moveFields=function(){j.insertBefore(t.fragment,ii.prototype.endMarker)},this.initOnceInAllInstances(),void 0!==e?(this.append(e),void this.moveFields()):{append:this.append,moveFields:this.moveFields}}function oi(){var e=jQuery(this).closest(".frm_form_action_settings").data("actionkey"),t=this.getAttribute("data-emailrow");jQuery("#frm_form_action_"+e+" .frm_"+t+"_row").fadeIn("slow"),jQuery(this).fadeOut("slow")}function ai(){var e=jQuery(this).closest(".frm_form_action_settings"),t=this.getAttribute("data-emailrow"),r=".frm_"+t+"_row",n=".frm_"+t+"_button";jQuery(e).find(n).fadeIn("slow"),jQuery(e).find(r).fadeOut("slow",function(){jQuery(e).find(r+" input").val("")})}function li(){var e=jQuery(this).closest(".frm_form_action_settings"),t=".frm_from_to_match_row";e.find('input[name$="[post_content][from]"]').val()===e.find('input[name$="[post_content][email_to]"]').val()?jQuery(e).find(t).fadeIn("slow"):jQuery(e).find(t).fadeOut("slow")}function si(e){var t,r,n=document.querySelectorAll(".frm_"+e+"_action");di(e)?(t=n,r=ci(e)>0,t.forEach(function(e){e.classList.remove("frm_active_action"),e.classList.add("frm_inactive_action"),r&&e.classList.add("frm_already_used")})):n.forEach(function(e){e.querySelector(".frm_show_upgrade")||(e.classList.remove("frm_inactive_action","frm_already_used"),e.classList.add("frm_active_action"))})}function di(e){var t=function(e){return jQuery(".frm_single_"+e+"_settings").length}(e)>=ci(e),r={type:e};return wp.hooks.applyFilters("frm_action_at_limit",t,r)}function ci(e){return parseInt(jQuery(".frm_"+e+"_action").data("limit"),10)}function ui(){var e=o.only_one_action,t=this.dataset.limit;void 0!==t&&((t=parseInt(t))>1?e=e.replace(1,t).trim():e+=" "+o.edit_action_text),M(e)}function fi(){var e=jQuery(this).data("emailkey"),t=jQuery(this).closest(".frm_form_action_settings").find(".frm_action_name").val(),r=document.getElementById("form_id").value,n=document.getElementById("frm_logic_row_"+e),i=Sr(n.querySelectorAll(".frm_logic_row"),"frm_logic_"+e+"_"),o=d({id:"frm_logic_"+e+"_"+i,className:"frm_logic_row frm_hidden"});return n.append(o),jQuery.ajax({type:"POST",url:ajaxurl,data:{action:"frm_add_form_logic_row",email_id:e,form_id:r,meta_name:i,type:t,nonce:frmGlobal.nonce},success:function(t){jQuery(document.getElementById("logic_link_"+e)).fadeOut("slow",function(){o.insertAdjacentHTML("beforebegin",t),o.remove(),jQuery(n).parent(".frm_logic_rows").fadeIn("slow")})}}),!1}function mi(){var e=jQuery("select.frm_single_post_field");e.css("border-color","");var t=this,r=jQuery(t).val();if(""===r||"checkbox"===r)return!1;e.each(function(){if(jQuery(this).val()===r&&this.name!==t.name)return this.style.borderColor="red",jQuery(t).val(""),M(o.field_already_used),!1})}function _i(){var e=jQuery(this).val();""===e?(jQuery(".frm_post_content_opt, select.frm_dyncontent_opt").hide().val(""),jQuery(".frm_dyncontent_opt").hide()):"post_content"===e?(jQuery(".frm_post_content_opt").show(),jQuery(".frm_dyncontent_opt").hide(),jQuery("select.frm_dyncontent_opt").val("")):(jQuery(".frm_post_content_opt").hide().val(""),jQuery("select.frm_dyncontent_opt, .frm_form_field.frm_dyncontent_opt").show())}function pi(){var e=jQuery(this).val(),t=jQuery(document.getElementById("frm_dyncontent"));""===e||"new"===e?(t.val(""),jQuery(".frm_dyncontent_opt").show()):jQuery.ajax({type:"POST",url:ajaxurl,data:{action:"frm_display_get_content",id:e,nonce:frmGlobal.nonce},success:function(e){t.val(e),jQuery(".frm_dyncontent_opt").show()}})}function gi(){var e,t,r=document.getElementById("frm_posttax_rows").childNodes,n=document.querySelector(".frm_post_parent_field"),i=document.querySelector(".frm_post_menu_order_field"),o=this.value;jQuery.ajax({type:"POST",url:ajaxurl,data:{action:"frm_replace_posttax_options",post_type:o,nonce:frmGlobal.nonce},success:function(n){for(var i=0;i
    ');var e=jQuery(this).closest(".frm_form_action_settings").find('select[name$="[post_content][post_type]"]').val(),t=jQuery(this).closest(".frm_form_action_settings").data("actionkey"),r=jQuery(this).closest(".frm_posttax_row").attr("id").replace("frm_posttax_",""),n=jQuery(this).val(),i=jQuery(document.getElementById(r+"_show_exclude")).is(":checked")?1:0,o=jQuery('select[name$="[post_category]['+r+'][field_id]"]').val(),a=jQuery('input[name="id"]').val();jQuery.ajax({type:"POST",url:ajaxurl,data:{action:"frm_add_posttax_row",form_id:a,post_type:e,tax_key:r,action_key:t,meta_name:n,field_id:o,show_exclude:i,nonce:frmGlobal.nonce},success:function(e){jQuery(document.getElementById("frm_posttax_"+r)).replaceWith(e)}})}}function wi(){var e=jQuery(this).closest(".frm_postmeta_row"),t=e.find(".frm_cancelnew"),r=e.find(".frm_enternew");return e.find("select.frm_cancelnew").is(":visible")?(t.hide(),r.show()):(t.show(),r.hide()),e.find("input.frm_enternew, select.frm_cancelnew").val(""),!1}function Qi(){var e=jQuery(this),t=e.val();"checkbox"===e.attr("type")&&!1===this.checked&&(t="");var r=e.data("toggleclass");""===t?jQuery("."+r).hide():(jQuery("."+r).show(),jQuery(".hide_"+r+"_"+t).hide())}function xi(){Wn()||($n(this),zn(document.querySelector(".frm_form_settings")))}function Ei(e){return e.preventDefault(),ki(jQuery(this),this.getAttribute("data-code")),!1}function ki(e,t){var r=!1,n=e;if("object"===a(e)){if(e.hasClass("frm_noallow"))return;void 0===(n=jQuery(e).closest("[data-fills]").attr("data-fills"))&&void 0!==(n=e.closest("div").attr("class"))&&(n=n.split(" ")[1])}if(void 0===n){var i=document.activeElement;"search"===i.type?null===(n=i.id.replace("-search-input","")).match(/\d/gi)&&(n=(i=jQuery(".frm-single-settings:visible ."+n)).attr("id")):n=i.id}n&&(r=jQuery("#wp-"+n+"-wrap.wp-editor-wrap").length>0);var o=jQuery(document.getElementById(n));if(void 0===e.attr("data-shortcode")&&(!o.length||void 0===o.attr("data-shortcode"))){var l=e.parents("ul.frm_code_list").attr("data-shortcode");"undefined"!==l&&"no"===l||(t="["+t+"]")}if(r&&(wpActiveEditor=n),!o.length)return!1;if("[default-html]"===t||"[default-plain]"===t){var s=0;"[default-plain]"===t&&(s=1),jQuery.ajax({type:"POST",url:ajaxurl,data:{action:"frm_get_default_html",form_id:jQuery('input[name="id"]').val(),plain_text:s,nonce:frmGlobal.nonce},elementId:n,success:function(e){if(r){var t=document.createElement("p");t.innerText=e,send_to_editor(t.innerHTML)}else Ai(o,e)}})}else t=function(e,t,r){return"object"===a(t)&&t instanceof jQuery&&0===r[0].id.indexOf("success_url_")&&(t=t[0]).closest("#frm-insert-fields-box")?(t.parentNode.classList.contains("frm_insert_url")||(e=e.replace("]"," sanitize_url=1]")),e):e}(t,e,o),r?send_to_editor(t):Ai(o,t);return!1}function Ai(e,t){if(document.selection)e[0].focus(),document.selection.createRange().text=t;else{var r=e[0],n=r.selectionEnd;t=function(e,t,r,n){var i=e.data("sep");if(void 0===i)return t;var o=e.val();if(!o.trim().length)return t;var a=new RegExp(i+"\\s*$"),l=new RegExp("^\\s*"+i);return o.substr(0,r).trim().length&&!1===a.test(o.substr(0,r))&&(t=i+t),o.substr(n,o.length).trim().length&&!1===l.test(o.substr(n,o.length))&&(t+=i),t}(e,t,r.selectionStart,n),r.value=r.value.substr(0,r.selectionStart)+t+r.value.substr(r.selectionEnd,r.value.length);var i=n+t.length;!function(e,t){if(e.classList.contains("frm_classes")&&Si(t)){var r=e.value.split(" ").filter(Si);r.length&&(e.value=function(e,t,r){var n=e.split(" ").filter(function(e){return(e=e.trim()).length&&!t.includes(e)});return n.includes(r)||n.push(r),n.join(" ")}(e.value,r,t.trim()))}}(r,t),r.focus(),r.setSelectionRange(i,i)}Rn(e)}function Si(e){return["frm_half","frm_third","frm_two_thirds","frm_fourth","frm_three_fourths","frm_fifth","frm_sixth","frm2","frm3","frm4","frm6","frm8","frm9","frm10","frm12"].includes(e.trim())}function Li(){var e=document.getElementById("frm-id-condition"),t=document.getElementById("frm-key-condition");"id"===this.value?(e.classList.remove("frm_hidden"),t.classList.add("frm_hidden"),ko(t,"change")):(e.classList.add("frm_hidden"),t.classList.remove("frm_hidden"),ko(e,"change"))}function Ii(){var e,t,r=document.getElementById("frm-id-key-condition-id").checked?"frm-id-condition":"frm-key-condition",n=document.getElementById("frm-is-condition").value,i=document.getElementById("frm-text-condition").value,a=document.getElementById("frm-insert-condition");t="if "+(e=(r=document.getElementById(r)).options[r.selectedIndex].value)+" "+n+'="'+i+'"]',a.setAttribute("data-code",t+o.conditional_text+"[/if "+e),a.innerHTML="["+t+"[/if "+e+"]"}function Bi(e){return e.getAttribute("href")||e.getAttributeNS("http://www.w3.org/1999/xlink","href")}function qi(e){var t;e.parentNode.parentNode.classList.contains("frm_has_shortcodes")&&(Vi(),"use"===(t=Ui(e)).tagName?Bi(t=t.firstElementChild).includes("frm_close_icon")||Fi(t,"nofocus"):t.classList.contains("frm_close_icon")||Fi(t,"nofocus"))}function Ci(e){e.preventDefault(),e.stopPropagation(),Fi(this)}function Ni(e){!function(e){var t;if(e.id.startsWith("field_options_type_")){var r=e.id.split("_"),n=r.length&&r[r.length-1];null!==(t=document.querySelector("#frm-single-settings-".concat(n)))&&void 0!==t&&t.classList.contains("frm-type-".concat(e.value))||Ti()}}(e.target)}function Ti(e){var t;void 0===e&&(e=I("You are changing the field type. Not all field settings will appear as expected until you reload the page. Would you like to reload the page now?","formidable")),frmDom.modal.maybeCreateModal("frmSaveAndReloadModal",{title:I("Save and Reload?","formidable"),content:(t=d(e),t.style.padding="var(--gap-md)",t),footer:function(){var e=frmDom.modal.footerButton({text:I("Save and Reload","formidable"),buttonType:"primary"});_(e,function(){var e;(e=document.getElementById("frm_submit_side_top")).classList.contains("frm_submit_ajax")&&e.setAttribute("data-new-addon-installed",!0),e.click()});var t=frmDom.modal.footerButton({text:I("Cancel","formidable"),buttonType:"cancel"});return t.classList.add("dismiss"),frmDom.div({children:[t,e]})}()})}function Oi(e){var t;if(e instanceof Event){var r=document.querySelectorAll(".frm-single-settings .frm-show-box.frmsvg use"),n=Array.from(r).find(function(e){return"#frm_close_icon"===e.getAttribute("href")});if(void 0===n)return;t=n.parentElement}else t=e;var i=t.getBoundingClientRect(),o=document.getElementById("frm_adv_info"),a=o.parentElement.getBoundingClientRect();o.style.top=i.top-a.top+32+"px",o.style.left=i.left-a.left-280+"px"}function Fi(e,t){var r=Wi(e),n=document.getElementById("frm_adv_info"),a=e.className;if("svg"===e.tagName&&(e=e.firstElementChild),"use"===e.tagName&&(a=Bi(e)),a.includes("frm_close_icon"))Vi(n);else{if(Oi(e),jQuery(".frm_code_list a").removeClass("frm_noallow"),r.classList.contains("frm_not_email_to")?jQuery("#frm-insert-fields-box .frm_code_list li:not(.show_frm_not_email_to) a").addClass("frm_noallow"):r.classList.contains("frm_not_email_subject")&&jQuery(".frm_code_list li.hide_frm_not_email_subject a").addClass("frm_noallow"),n.setAttribute("data-fills",r.id),n.style.display="block","use"===e.tagName)if(e.hasAttributeNS("http://www.w3.org/1999/xlink","href"))e.setAttributeNS("http://www.w3.org/1999/xlink","href","#frm_close_icon");else{var l=document.createElementNS("http://www.w3.org/2000/svg","use");l.setAttributeNS("http://www.w3.org/1999/xlink","href","#frm_close_icon"),e.parentNode.replaceChild(l,e)}else e.className=a.replace("frm_more_horiz_solid_icon","frm_close_icon");"nofocus"!==t&&("none"!==r.style.display?r.focus():jQuery(tinymce.get(r.id)).trigger("focus")),function(e){["address","body"].forEach(function(t){!function(e,t){var r,n;r=o.contextualShortcodes[t+"Selector"],n=o.contextualShortcodes[t];var a,l=e.matches(r),s=function(e){var t="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(!t){if(Array.isArray(e)||(t=i(e))){t&&(e=t);var r=0,n=function(){};return{s:n,n:function(){return r>=e.length?{done:!0}:{done:!1,value:e[r++]}},e:function(e){throw e},f:n}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var o,a=!0,l=!1;return{s:function(){t=t.call(e)},n:function(){var e=t.next();return a=e.done,e},e:function(e){l=!0,o=e},f:function(){try{a||null==t.return||t.return()}finally{if(l)throw o}}}}(n);try{for(s.s();!(a=s.n()).done;){var d,c=a.value,u=null===(d=document.querySelector('#frm-adv-info-tab .frm_code_list [data-code="'+c+'"]'))||void 0===d?void 0:d.closest("li");null==u||u.classList.toggle("frm_hidden",!l)}}catch(e){s.e(e)}finally{s.f()}}(e,t)})}(r)}}function Di(e){return 0===o.contextualShortcodes.length||!function(e){var t=e.querySelector("a");if(!t)return!1;var r=t.dataset.code;return o.contextualShortcodes.address.includes(r)||o.contextualShortcodes.body.includes(r)}(e)||function(e){var t=e.querySelector("a").dataset.code,r=document.getElementById("frm_adv_info").dataset.fills,n=document.getElementById(r),i=o.contextualShortcodes;return i.address.includes(t)?n.matches(i.addressSelector):n.matches(i.bodySelector)}(e)}function Mi(){x||(x=1,window.addEventListener("beforeunload",Ri))}function Pi(){x=0}function Hi(){x=0}function zi(){x=0}function Ri(e){x&&(e.preventDefault(),e.returnValue="")}function Gi(e,t){var r={my:"top",at:"top+"+t,of:window};e.dialog("option","position",r)}function Wi(e){if(e.classList.contains("frm-input-icon"))return e.previousElementSibling;for(var t,r=e.nextElementSibling;null!==r&&("INPUT"!==r.tagName&&"TEXTAREA"!==r.tagName||r.classList.contains("frm-token-input-field"));)r=Wi(r);return r||(r=null===(t=e.closest(".frm-field-formula"))||void 0===t?void 0:t.querySelector(".frm-calc-field")),r}function Ui(e){var t;if(null!==(t=e.nextElementSibling)&&void 0!==t&&t.classList.contains("frm-input-icon"))return e.nextElementSibling;for(var r=e.previousElementSibling;null!==r&&"I"!==r.tagName&&"svg"!==r.tagName;)r=Ui(r);return r}function Vi(e){var t,r,n,i;if((void 0!==e||null!==(e=document.getElementById("frm_adv_info")))&&null===document.getElementById("frm_dyncontent")){for(e.style.display="none",n=document.querySelectorAll(".frm-show-box.frm_close_icon"),t=0;t"+r.data.name+": "+r.data.msg+"

    ":'

    Imported '+r.data.name+"

    ",e.find(".status").prepend(n),e.find(".status").show(),C.importQueue=jQuery.grep(C.importQueue,function(e){return e!=t}),C.imported++,0===C.importQueue.length?(e.find(".process-count").hide(),e.find(".forms-completed").text(C.imported),e.find(".process-completed").show()):(e.find(".form-current").text(C.imported+1),Zi(e)))})}function eo(e){e.preventDefault();var t=!1,r=jQuery('input[name="frm_export_forms[]"]');jQuery('input[name="frm_export_forms[]"]:checked').val()||(r.closest(".frm-table-box").addClass("frm_blank_field"),t="stop");var n=jQuery('input[name="type[]"]');if(jQuery('input[name="type[]"]:checked').val()||"checkbox"!==n.attr("type")||(n.closest("p").addClass("frm_blank_field"),t="stop"),"stop"===t)return!1;e.stopPropagation(),this.submit()}function to(){var e=jQuery(this).closest(".frm_blank_field");if(void 0!==e){var t=this.name;("type[]"===t&&jQuery('input[name="type[]"]:checked').val()||"frm_export_forms[]"===t&&jQuery(this).val())&&e.removeClass("frm_blank_field")}}function ro(){null!==jQuery(this).val().match(/\.csv$/i)?jQuery(".show_csv").fadeIn():jQuery(".show_csv").fadeOut()}function no(){var e=document.querySelector('select[name="format"]');return e?e.value:""}function io(e){var t,r,n=e.target.value;ao(n),oo.call(e.target),t=n,r=document.getElementById("frm-export-select-all"),"csv"===t?(r.checked=!1,r.disabled=!0):r.disabled=!1}function oo(){var e=jQuery(this),t=e.find(":selected"),r=t.data("support"),n=r.indexOf("|");jQuery('input[name="type[]"]').each(function(){this.checked=!1,r.includes(this.value)?(this.disabled=!1,-1===n&&(this.checked=!0)):this.disabled=!0}),"csv"===e.val()?(jQuery(".csv_opts").show(),jQuery(".xml_opts").hide()):(jQuery(".csv_opts").hide(),jQuery(".xml_opts").show());var i=t.data("count"),o=jQuery('input[name="frm_export_forms[]"]');"single"===i?(o.prop("multiple",!1),o.prop("checked",!1)):(o.prop("multiple",!0),o.prop("disabled",!1)),e.trigger("change")}function ao(e){if(""!==e){var t=document.querySelectorAll(".frm-is-repeater");t.length&&("csv"===e?t.forEach(function(e){e.classList.remove("frm_hidden")}):t.forEach(function(e){e.classList.add("frm_hidden")}),Qo.call(document.querySelector(".frm-auto-search")))}}function lo(){var e=jQuery("select[name=format]").find(":selected").data("count"),t=jQuery('input[name="frm_export_forms[]"]');"single"===e&&this.checked?(t.prop("disabled",!0),this.removeAttribute("disabled")):t.prop("disabled",!1)}function so(){jQuery(".frm_multiselect").hide().each(frmDom.bootstrap.multiselect.init)}function co(e){e.preventDefault(),mo(this,"frm_multiple_addons")}function uo(e){e.preventDefault(),mo(this,"frm_activate_addon")}function fo(e){e.preventDefault(),mo(this,"frm_install_addon")}function mo(e,t){r(1105).toggleAddonState(e,t)}function _o(){go()}function po(e){!function(e,t,r){var n=jQuery("#frm_leave_email_error");n.removeClass("frm_hidden").attr("frm-error",r),jQuery("#frm_leave_email").one("keyup",function(){n.addClass("frm_hidden")})}(0,0,e)}function go(){var e=document.getElementById("frmapi-email-form");jQuery.ajax({dataType:"json",url:e.getAttribute("data-url"),success:function(t){var r=t.renderedHtml;r=r.replace(/]*(formidableforms.css|action=frmpro_css)[^>]*>/gi,""),e.innerHTML=r}})}function yo(e){frmDom.autocomplete.initSelectionAutocomplete(e)}function ho(e){var t=this.parentNode.parentNode,r=t.elements.type.value;e.preventDefault(),this.classList.add("frm_loading_button"),bo(t,r,this)}function vo(e){var t=this.elements.type.value,r=this.querySelector("button");e.preventDefault(),r.classList.add("frm_loading_button"),bo(this,t,r)}function bo(e,t,r){var n=function(e){var t,r,n={},i=e.elements;for(r=0;r .frm-with-line").forEach(function(e){var t=e.nextElementSibling;if(t){var r=t.querySelectorAll(":scope > li.frmbutton"),n=Array.from(r).every(function(e){return e.classList.contains("frm_hidden")});e.classList.toggle("frm_hidden",n)}}),jQuery(this).trigger("frmAfterSearch")}function xo(e,t){return"s"!==t&&"s"!==e[e.length-1]&&(e+"s").includes(t)}function Eo(e){e.stopPropagation()}function ko(e,t){var r=document.createEvent("HTMLEvents");r.initEvent(t,!1,!0),e.dispatchEvent(r)}function Ao(e,t){var r,n=new XMLHttpRequest,i="string"==typeof e?e:Object.keys(e).map(function(t){return encodeURIComponent(t)+"="+encodeURIComponent(e[t])}).join("&");return n.open("post",ajaxurl,!0),n.onreadystatechange=function(){if(n.readyState>3&&200==n.status){r=n.responseText;try{r=JSON.parse(r)}catch(e){}t(r)}},n.setRequestHeader("X-Requested-With","XMLHttpRequest"),n.setRequestHeader("Content-type","application/x-www-form-urlencoded"),n.send(i),n}function So(e,t){e.classList.add("frm-fade"),setTimeout(t,1e3)}function Lo(e){jQuery(e).css("visibility","hidden")}function Io(e){jQuery(e).css("visibility","visible")}function Bo(e,t){return r(4260).initModal(e,t)}function qo(e,t){if("#"===t){var r=document.getElementById(e),n=r.style.display;r.style.display="none"===n?"block":"none"}else e.is(":visible")?e.hide():e.show()}function Co(){window.onbeforeunload=null;var e=jQuery(window);e.off("beforeunload.widgets"),e.off("beforeunload.edit-post")}function No(){var e=jQuery(this).closest(".frm-single-settings").data("fid"),t=document.getElementById("frm_field_id_"+e);if(null!==t&&"form"===t.dataset.type)if(t=jQuery(t),this.options[this.selectedIndex].value){t.find(".frm-not-set")[0].classList.add("frm_hidden");var r=t.find(".frm-embed-message");r.html(r.data("embedmsg")+this.options[this.selectedIndex].text),t.find(".frm-embed-field-placeholder")[0].classList.remove("frm_hidden")}else t.find(".frm-not-set")[0].classList.remove("frm_hidden"),t.find(".frm-embed-field-placeholder")[0].classList.add("frm_hidden")}function To(){var e=jQuery(this).closest(".frm-single-settings"),t=e.find(".frmjs_product_choices"),r=e.find(".frm_prod_options_heading"),n=this.options[this.selectedIndex].value;t.removeClass("frm_prod_type_single frm_prod_type_user_def"),r.removeClass("frm_prod_user_def"),"single"===n?t.addClass("frm_prod_type_single"):"user_def"===n&&(t.addClass("frm_prod_type_user_def"),r.addClass("frm_prod_user_def"))}function Oo(e){var t=document.getElementById("frm_field_id_"+e);return null!==t&&"product"===t.getAttribute("data-type")}function Fo(){var e=function(e,t){return window.frmCachedSubFields=window.frmCachedSubFields||{},window.frmCachedSubFields[e]=window.frmCachedSubFields[e]||{},window.frmCachedSubFields[e][t]},t=function(e,t,r){window.frmCachedSubFields=window.frmCachedSubFields||{},window.frmCachedSubFields[e]=window.frmCachedSubFields[e]||{},window.frmCachedSubFields[e][t]=r},r=[1,2,3,4,5,6,7,8,9,10,11,12].map(function(e){return"frm"+e}),i=["first","middle","last"];document.addEventListener("change",function(o){o.target.matches(".frm_name_layout_dropdown")&&function(o){var a,l=o.target.value.split("_"),s=o.target.dataset.fieldId,d=document.querySelector("#field_"+s+"_inner_container .frm_combo_inputs_container"),c=(a=l.length,"frm"+parseInt(12/a));i.forEach(function(e){var i,o=d.querySelector('[data-sub-field-name="'+e+'"]');o&&(o.classList.add("frm_hidden"),(i=o.classList).remove.apply(i,n(r)),t(s,e,o))}),l.forEach(function(t){var r=e(s,t);r&&(r.classList.remove("frm_hidden"),r.classList.add(c),d.append(r))}),i.forEach(function(e){var r=document.querySelector(".frm_sub_field_options-"+e+'[data-field-id="'+s+'"]');r&&(r.classList.add("frm_hidden"),t(s,e+"_options",r))}),l.forEach(function(t){var r=e(s,t+"_options");r&&r.classList.remove("frm_hidden")})}(o)},!1)}function Do(e,t){var r,n,i,o=!1,a=!1;(r=t.newOption?(new DOMParser).parseFromString(t.newOption,"text/html").body.childNodes[0]:t).querySelectorAll("svg").forEach(function(e,t){(n=e.getElementsByTagNameNS("http://www.w3.org/2000/svg","use")[0])&&("#frm_drag_icon"===(i=Bi(n))&&(o=!0),"#frm_save_icon"===i&&(a=!0))}),o||r.prepend(v.drag.cloneNode(!0)),r.querySelector("[id^=field_key_".concat(e,"-]"))&&!a&&r.querySelector("[id^=field_key_".concat(e,"-]")).after(v.save.cloneNode(!0)),t.newOption&&(t.newOption=r)}function Mo(){var e=document.getElementById("frm_leave_email").value.trim();if(""!==e)if(!1!==/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i.test(e)){var t=jQuery("#frmapi-email-form").find("form"),r=t.find('[type="email"]').not(".frm_verify");if(r.length){if(document.getElementById("frm_empty_inbox")){document.getElementById("frm-add-my-email-address").remove();var n=document.getElementById("frm_leave_email_wrapper");if(n){n.classList.add("frm_hidden");var i=c({className:"frm-wait frm_spinner"});i.style.visibility="visible",i.style.float="none",i.style.width="unset",n.parentElement.insertBefore(i,n.nextElementSibling)}}r.val(e),jQuery.ajax({type:"POST",url:t.attr("action"),data:t.serialize()+"&action=frm_forms_preview"}).done(function(e){if(jQuery(e).find(".frm_message").text().trim().includes("Thanks!")){var t=document.getElementById("frmapi-email-form").parentElement.querySelector(".frm_spinner");t&&t.remove(),wp.hooks.applyFilters("frm_thank_you_on_signup",!0)&&document.getElementById("frm_leave_email_wrapper").replaceWith(c(I("Thank you for signing up!","formidable")))}else po("invalid")})}}else po("invalid");else po("empty")}function Po(e){if(O||e.stopPropagation(),!(e.target.classList.contains("frm-show-box")||e.target.parentElement&&e.target.parentElement.classList.contains("frm-show-box"))){var t=document.getElementById("frm_adv_info");t&&(t.dataset.fills===e.target.id&&void 0!==e.target.id||e.target.closest("#frm_adv_info")||"none"===t.style.display||Vi(t))}}return{init:function(){var e,t,i,o,a,l,s;!function(){jQuery(document).on("click","#frm-add-my-email-address",function(e){e.preventDefault(),Mo()});var e=document.getElementById("frm_empty_inbox"),t=document.getElementById("frm_leave_email");if(e&&t){var r=document.getElementById("frm-leave-email-modal");r.classList.remove("frm_hidden"),r.querySelector(".frm_modal_footer").classList.add("frm_hidden"),t.addEventListener("keyup",function(e){if("Enter"===e.key){var t=document.getElementById("frm-add-my-email-address");t&&t.click()}})}}(),t=document.querySelector(".frm-admin-footer-links"),i=null!==(e=document.querySelector(".frm_page_container"))&&void 0!==e?e:document.getElementById("wpbody-content"),t&&i&&(i.append(t),t.classList.remove("frm_hidden")),document.addEventListener("show.bs.dropdown",function(){z()}),C={},jQuery(".wp-admin").on("click",function(e){var t=jQuery(e.target),r=jQuery(".dropdown.open");!r.length||t.hasClass("dropdown")||t.closest(".dropdown").length||r.removeClass("open")}),jQuery("#frm_bs_dropdown:not(.open) a").on("click",At),void 0===E&&(E=jQuery(document.getElementById("form_id")).val()),document.querySelectorAll(".frm-warning-dismiss").forEach(function(e){_(e,St)}),frmAdminBuild.inboxBannerInit(),b.length>0?frmAdminBuild.buildInit():null!==document.getElementById("frm_notification_settings")?frmAdminBuild.settingsInit():null!==document.getElementById("frm_styling_form")?frmAdminBuild.styleInit():null!==document.getElementById("form_global_settings")?frmAdminBuild.globalSettingsInit():null!==document.getElementById("frm_export_xml")?frmAdminBuild.exportInit():null!==document.querySelector(".frm-inbox-wrapper")?frmAdminBuild.inboxInit():null!==document.getElementById("frm-welcome")?frmAdminBuild.solutionInit():(function(){if(document.body.classList.contains("frm-admin-page-entries")){var e=document.getElementById("screen-options-wrap");if(e){var t=d({className:"frm_warning_style",text:I("Only 10 columns can be selected at a time.","formidable")});t.style.margin=0;var r=e.querySelector("legend");r.parentNode.insertBefore(t,r.nextElementSibling);var n=Array.from(e.querySelectorAll('input[type="checkbox"]')),i=function(){n.reduce(function(e,t){return t.checked?e+1:e},0)>=10?(t.classList.remove("frm_hidden"),n.forEach(function(e){e.checked||(e.parentNode.classList.add("frm_noallow"),e.disabled=!0)})):t.classList.add("frm_hidden")};i(),n.forEach(function(e){e.addEventListener("change",function(e){e.target.checked?i():(t.classList.add("frm_hidden"),n.forEach(function(e){e.parentNode.classList.remove("frm_noallow"),e.disabled=!1}))})})}}}(),yo(),jQuery("[data-frmprint]").on("click",function(){return window.print(),!1})),jQuery(document).on("change","select[data-toggleclass], input[data-toggleclass]",Qi),function(){function e(e){var t=e.options[e.selectedIndex];e.querySelectorAll("option[data-dependency]:not([data-dependency-skip])").forEach(function(e){var r=document.querySelector(e.dataset.dependency);null==r||r.classList.toggle("frm_hidden",t!==e)})}document.querySelectorAll("select.frm_select_with_dependency").forEach(e),frmDom.util.documentOn("change","select.frm_select_with_dependency",function(t){return e(t.target)})}(),(jQuery(document.getElementById("frm_adv_info")).length>0||jQuery(".frm_field_list").length>0)&&frmAdminBuild.panelInit(),o=jQuery(".wrap, .frm_wrap"),a=document.getElementById("frm_confirm_modal"),l=!1,s=!1,jQuery(a).on("click","[data-deletefield]",Qr),jQuery(a).on("click","[data-removeid]",R),jQuery(a).on("click","[data-trashtemplate]",wo),o.on("click",".frm_remove_tag, .frm_remove_form_action",R),o.on("click","a[data-frmverify]",F),o.on("click","a[data-frmtoggle]",P),o.on("click","a[data-frmhide], a[data-frmshow]",H),o.on("change","input[data-frmhide], input[data-frmshow]",H),o.on("click",".widget-top,a.widget-action",G),o.on("mouseenter.frm",".frm_bstooltip, .frm_help",function(){jQuery(this).off("mouseenter.frm"),function(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1],r=e;(r.hasAttribute("data-toggle")||r.hasAttribute("data-bs-toggle"))&&(r.parentElement.setAttribute("title",r.getAttribute("title")),r.removeAttribute("title"),r.classList.remove("frm_bstooltip"),r.parentElement.classList.add("frm_bstooltip"),r=r.parentElement),jQuery(r).tooltip(),t&&(z(),jQuery(r).tooltip("show"))}(this,!0)}),jQuery(document).on("click","#doaction, #doaction2",function(e){var t="doaction"===this.id?"top":"bottom",r=document.getElementById("bulk-action-selector-"+t),n=document.getElementById("confirm-bulk-delete-"+t);if(null!==r&&null!==n){if(l=this,!s&&"bulk_delete"===r.value)return e.preventDefault(),D(n),!1}else l=!1}),jQuery(document).on("click","#frm-confirmed-click",function(e){if(!1!==l&&!e.target.classList.contains("frm-btn-inactive"))return"confirm-bulk-delete"===this.getAttribute("href")?(e.preventDefault(),s=!0,l.click(),!1):void 0}),r(4260).initUpgradeModal(),frmDom.util.documentOn("click","[data-modal-title]",Kn);var c=jQuery(document.getElementById("frm_shortcodediv"));c.length>0&&(jQuery("a.edit-frm_shortcode").on("click",function(){return c.is(":hidden")&&(c.slideDown("fast"),this.style.display="none"),!1}),jQuery(".cancel-frm_shortcode","#frm_shortcodediv").on("click",function(){return c.slideUp("fast"),c.siblings("a.edit-frm_shortcode").show(),!1})),jQuery(document).on("click","#frm-nav-tabs a",W),jQuery(".post-type-frm_display .frm-nav-tabs a, .frm-category-tabs a").on("click",function(){var e=this.classList.contains("frm_show_upgrade_tab");if(!this.classList.contains("frm_noallow")||e)return e&&Jn(this),U(this),!1}),U(jQuery(".starttab a"),"auto"),jQuery(document).on("click","#frm-fid-search-menu a",function(){var e=this.id.replace("fid-","");return jQuery('select[name="fid"]').val(e),zn(document.getElementById("posts-filter")),!1}),jQuery(".frm_select_box").on("click focus",function(){this.select()}),jQuery(document).on("input search change",".frm-auto-search:not(#frm-form-templates-page #template-search-input)",Qo),jQuery(document).on("focusin click",".frm-auto-search",Eo);var u=jQuery(".frm-auto-search");""!==u.val()&&u.trigger("keyup"),FrmFormsConnect.init(),jQuery(document).on("click",".frm-install-addon",fo),jQuery(document).on("click",".frm-activate-addon",uo),jQuery(document).on("click",".frm-solution-multiple",co),jQuery("button, input[type=submit]").on("click",Co),document.addEventListener("click",function(e){if("LABEL"===e.target.nodeName){var t=e.target.getAttribute("for");if(t){var r=document.getElementById(t);if(r&&r.nextElementSibling){var n=r.nextElementSibling.querySelector("button.dropdown-toggle.multiselect");n&&setTimeout(function(){return n.click()},0)}}}}),frmAdminBuild.hooks.addFilter("frm_before_embed_modal",function(e,t){var r,n,i=t.element;if("form"!==t.type)return e;var o=i.closest("tr");if(o)r=parseInt(o.querySelector(".column-id").textContent),n=o.querySelector(".column-form_key").textContent;else{r=document.getElementById("form_id").value;var a=document.getElementById("frm_form_key");if(a)n=a.value;else{var l=document.getElementById("frm-previewDrop");l&&(n=l.nextElementSibling.querySelector(".dropdown-item a").getAttribute("href").split("form=")[1])}}return[r,n]}),document.querySelectorAll("#frm-show-fields > li, .frm_grid_container li").forEach(function(e,t){e.addEventListener("click",function(){var e,t,r;t=(null===(e=this.querySelector("li"))||void 0===e?void 0:e.dataset.fid)||this.dataset.fid,(r=document.querySelectorAll("[id^=frm_delete_field_".concat(t,"-]"))).length<2||n(r).slice(1).forEach(function(e,r){e.classList.contains("frm_other_option")||Do(t,e)})})});var f=document.getElementById("frm_small_screen_proceed_button");f&&_(f,function(){var e;null===(e=document.getElementById("frm_small_device_message_container"))||void 0===e||e.remove(),p("small_screen_proceed",new FormData)});var m=document.getElementById("frm_sale_banner"),g=null==m?void 0:m.querySelector(".dismiss");m&&(_(m,function(e){e.target.closest(".dismiss")||(window.location.href=m.getAttribute("data-url"))}),g&&_(g,function(){m.remove();var e=new FormData;p("sale_banner_dismiss",e)}))},buildInit:function(){var e,t,r;jQuery("#frm_builder_page").on("mouseup","*:not(.frm-show-box)",Po),g=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:100;return frmDom.util.debounce(e,t)}(ie,10),y=document.getElementById("post-body-content"),h=jQuery(y),jQuery(".frm_field_loading").length&&Ee(jQuery(".frm_field_loading").first().attr("id")),V("ul.frm_sorting"),document.querySelectorAll(".field_type_list > li:not(.frm_show_upgrade)").forEach(X),jQuery("ul.field_type_list, .field_type_list li, ul.frm_code_list, .frm_code_list li, .frm_code_list li a, #frm_adv_info #category-tabs li, #frm_adv_info #category-tabs li a").disableSelection(),jQuery(".frm_submit_ajax").on("click",Hn),jQuery(".frm_submit_no_ajax").on("click",Gn),Un(),jQuery("a.edit-form-status").on("click",Sn),jQuery(".cancel-form-status").on("click",Ln),jQuery(".save-form-status").on("click",function(){var e=jQuery(document.getElementById("form_change_status")).val();return jQuery('input[name="new_status"]').val(e),jQuery(document.getElementById("form-status-display")).html(e),jQuery(".cancel-form-status").trigger("click"),!1}),jQuery(".frm_form_builder form").first().on("submit",function(){jQuery(".inplace_field").trigger("blur")}),so(),Fr(),e=jQuery(j),t=document.getElementById("frm_form_editor_container"),e.on("click",".frm_add_logic_row",Ar),e.on("click",".frm_add_watch_lookup_row",Lr),e.on("change",".frm_get_values_form",Tr),e.on("change",".frm_logic_field_opts",wn),e.on("frm-multiselect-changed",'select[name^="field_options[admin_only_"]',In),jQuery(document.getElementById("frm-insert-fields")).on("click",".frm_add_field",Ae),b.on("click",".frm_clone_field",Be),e.on("blur",'input[id^="frm_calc"]',Je),e.on("change","input.frm_format_opt, input.frm_max_length_opt",lt),e.on("change click","[data-changeme]",ot),e.on("click","input.frm_req_field",st),e.on("click",".frm_mark_unique",ct),e.on("change",".frm_repeat_format",Jr),e.on("change",".frm_repeat_limit",Xr),e.on("change",".frm_js_checkbox_limit",Yr),e.on("input",'input[name^="field_options[add_label_"]',function(){Zr(this,"add")}),e.on("input",'input[name^="field_options[remove_label_"]',function(){Zr(this,"remove")}),e.on("change",'select[name^="field_options[data_type_"]',Or),jQuery(t).on("click",".frm-collapse-page",Dr),jQuery(t).on("click",".frm-collapse-section",Hr),e.on("click",".frm-single-settings h3, .frm-single-settings h4.frm-collapsible",zr),e.on("keydown",".frm-single-settings h3, .frm-single-settings h4.frm-collapsible",function(e){"Enter"!==e.key&&" "!==e.key||(e.preventDefault(),zr.call(this,e))}),jQuery(t).on("show.bs.dropdown hide.bs.dropdown",He),e.on("click",".frm_toggle_sep_values",yt),e.on("click",".frm_toggle_image_options",ht),e.on("click",".frm_remove_image_option",Et),e.on("click",".frm_choose_image_box",xt),e.on("change",".frm_hide_image_text",Qt),e.on("change",".frm_field_options_image_size",jt),e.on("click",".frm_multiselect_opt",kt),b.on("mousedown","input, textarea, select",Lt),b.on("click","input[type=radio], input[type=checkbox]",Lt),b.on("click",".frm_delete_field",Nt),b.on("click",".frm_select_field",Tt),jQuery(document).on("click",".frm_delete_field_group",Ot),jQuery(document).on("click",".frm_clone_field_group",Ft),jQuery(document).on("click","#frm_field_group_controls > span:first-child",Dt),jQuery(document).on("click",".frm-row-layout-option",Yt),jQuery(document).on("click",".frm-merge-fields-into-row .frm-row-layout-option",Zt),jQuery(document).on("click",".frm-custom-field-group-layout",tr),jQuery(document).on("click",".frm-merge-fields-into-row .frm-custom-field-group-layout",nr),jQuery(document).on("click",".frm-break-field-group",sr),b.on("click","#frm_field_group_popup .frm_grid_container input",dr),jQuery(document).on("click",".frm-cancel-custom-field-group-layout",cr),jQuery(document).on("click",".frm-save-custom-field-group-layout",fr),b.on("click","ul.frm_sorting",mr),jQuery(document).on("click",".frm-merge-fields-into-row",br),jQuery(document).on("click",".frm-delete-field-groups",wr),b.on("click",'.frm-field-action-icons [data-toggle="dropdown"]',function(){this.closest("li.form-field").classList.add("frm-field-settings-open"),jQuery(document).on("click","#frm_builder_page",Te)}),b.on("mousemove","ul.frm_sorting",Oe),b.on("show.bs.dropdown",".frm-field-action-icons",Me),jQuery(document).on("show.bs.dropdown","#frm_field_group_controls",Pe),e.on("click",".frm_single_option a[data-removeid]",It),e.on("mousedown",".frm_single_option input[type=radio]",Bt),e.on("focusin",".frm_single_option input[type=text]",qt),e.on("click",".frm_add_opt",_t),e.on("change",".frm_single_option input",tn),e.on("change",".frm_image_id",tn),e.on("change",".frm_toggle_mult_sel",pt),b.on("click",".frm_primary_label",Gr),b.on("click",".frm_description",Wr),b.on("click","li.ui-state-default:not(.frm_noallow)",Vr),b.on("dblclick","li.ui-state-default",Kr),e.on("change",".frm_tax_form_select",en),e.on("change","select.conf_field",ut),e.on("change",".frm_get_field_selection",Qn),e.on("click",".frm-show-inline-modal",Cn),e.on("keydown",".frm-show-inline-modal",function(e){var t=e.key;"Enter"!==t&&" "!==t||(e.preventDefault(),Cn.call(this,e))}),e.on("click",".frm-inline-modal .dismiss",Nn),jQuery(document).on("change","[data-frmchange]",On),document.addEventListener("click",Tn),e.on("change",".frm_include_extras_field",et),e.on("change",'select[name^="field_options[form_select_"]',No),jQuery(document).on("submit","#frm_js_build_form",Pi),jQuery(document).on("change","#frm_builder_page input:not(.frm-search-input):not(.frm-custom-grid-size-input), #frm_builder_page select, #frm_builder_page textarea",Mi),nt(),jQuery(document).on("change",".frmjs_prod_data_type_opt",To),jQuery(document).on("focus",'.frm-single-settings ul input[type="text"][name^="field_options[options_"]',Br),jQuery(document).on("blur",'.frm-single-settings ul input[type="text"][name^="field_options[options_"]',Cr),frmDom.util.documentOn("click",".frm-show-field-settings",Vr),frmDom.util.documentOn("change","select.frm_format_dropdown, select.frm_phone_type_dropdown",$r),e.on("keydown",'.frm_single_option input[name^="field_options["], .frm_single_option input[name^="rows_"]',function(e){"Enter"===e.key&&function(e){var t=e.closest(".frm_single_option").parentElement.querySelectorAll('.frm_single_option input[name^="field_options[" ], .frm_single_option input[name^="rows_"]'),r=Array.from(t),n=r.indexOf(e);if(!(n<0)){var i=r.slice(n+1).find(function(e){return null!==e.offsetParent});if(i){i.focus();var o=i.value.length;i.setSelectionRange(o,o)}}}(e.currentTarget)}),!1!==(r=Bo("#frm-bulk-modal","700px"))&&(jQuery(".frm-insert-preset").on("click",mt),jQuery(j).on("click","a.frm-bulk-edit-link",function(e){e.preventDefault();var t,n,i,o,a,l="",s=jQuery(this).closest("[data-fid]").data("fid"),d=yn(s),c=Oo(s);if(o=document.getElementById("frm_field_"+s+"_opts")){for(a=o.getElementsByTagName("li"),document.getElementById("bulk-field-id").value=s,t=0;t=a.length-1&&(document.getElementById("frm_bulk_options").value=l);return r.dialog("open"),!1}}),jQuery("#frm-update-bulk-opts").on("click",function(){var e=document.getElementById("bulk-field-id").value;document.getElementById("bulk-option-type").value||(this.classList.add("frm_loading_button"),frmAdminBuild.updateOpts(e,document.getElementById("frm_bulk_options").value,r),Mi())})),qn(),document.addEventListener("frm_added_field",qn),Ie(),Fo(),kn(),frmDom.util.documentOn("change",".frm_show_password_setting_input",function(e){var t=e.target.getAttribute("data-fid"),r=document.getElementById("frm_field_id_"+t);r&&r.classList.toggle("frm_disabled_show_password",!e.target.checked)}),document.addEventListener("scroll",Oi,!0),document.addEventListener("change",Ni),document.querySelector(".frm_form_builder").addEventListener("mousedown",function(e){e.shiftKey&&e.preventDefault()}),wp.hooks.addAction("frmShowedFieldSettings","formidableAdmin",function(e,t){t.querySelectorAll(".frm-collapse-me").forEach(Rr)},9999)},settingsInit:function(){var e,t,r,n,i=jQuery(document.getElementById("frm_notification_settings"));i.on("click",".frm_email_buttons",oi),i.on("click",".frm_remove_field",ai),i.on("change",".frm_to_row, .frm_from_row",li),i.on("change",".frm_tax_selector",ji),i.on("change","select.frm_single_post_field",mi),i.on("change","select.frm_toggle_post_content",_i),i.on("change","select.frm_dyncontent_opt",pi),i.on("change",".frm_post_type",gi),i.on("click",".frm_add_postmeta_row",vi),i.on("click",".frm_add_posttax_row",hi),i.on("click",".frm_toggle_cf_opts",wi),i.on("click",".frm_duplicate_form_action",Zn),jQuery(".frm_actions_list").on("click",".frm_active_action",ti),jQuery("#frm-show-groups, #frm-hide-groups").on("click",ri),so(),jQuery("ul.frm_actions_list li").each(function(){si(jQuery(this).children("a").data("actiontype"));var e=jQuery(this).find("i");"none"!==e.css("background-image")&&e.addClass("frm-inverse")}),jQuery(".frm_submit_settings_btn").on("click",xi),Un(),(e=jQuery(".frm_form_settings")).on("click",".frm_add_form_logic",fi),e.on("click",".frm_already_used",ui),document.addEventListener("click",function(e){var t=e.target;t.closest(".frm_image_preview_wrapper")&&(t.closest(".frm_choose_image_box")?xt.bind(t)(e):t.closest(".frm_remove_image_option")&&Et.bind(t)(e))}),e.on("mouseup","*:not(.frm-show-box)",Po),jQuery(document.getElementById("no_save")).on("change",function(){this.checked&&!0!==confirm(o.no_save_warning)&&jQuery(this).attr("checked",!1)}),jQuery('select[name="options[edit_action]"]').on("change",Yn),t=document.getElementById("logged_in"),jQuery(t).on("change",function(){this.checked?Io(".hide_logged_in"):Lo(".hide_logged_in")}),r=jQuery(document.getElementById("frm_cookie_expiration")),jQuery(document.getElementById("frm_single_entry_type")).on("change",function(){"cookie"===this.value?r.fadeIn("slow"):r.fadeOut("slow")});var a=document.getElementById("single_entry");jQuery(a).on("change",function(){this.checked?Io(".hide_single_entry"):Lo(".hide_single_entry"),this.checked&&"cookie"===jQuery(document.getElementById("frm_single_entry_type")).val()?r.fadeIn("slow"):r.fadeOut("slow")}),jQuery(".hide_save_draft").hide();var l=jQuery(document.getElementById("save_draft"));l.on("change",function(){this.checked?jQuery(".hide_save_draft").fadeIn("slow"):jQuery(".hide_save_draft").fadeOut("slow")}),Rn(l),n=document.getElementById("editable"),jQuery(n).on("change",function(){this.checked?(jQuery(".hide_editable").fadeIn("slow"),Rn(document.getElementById("edit_action"))):(jQuery(".hide_editable").fadeOut("slow"),jQuery(".edit_action_message_box").fadeOut("slow"))}),jQuery(document).on("change","#protect_files",function(){this.checked?jQuery(".hide_protect_files").fadeIn("slow"):jQuery(".hide_protect_files").fadeOut("slow")}),jQuery(document).on("frm-multiselect-changed","#protect_files_role",In),jQuery(document).on("submit",".frm_form_settings",Hi),jQuery(document).on("change","#form_settings_page input:not(.frm-search-input), #form_settings_page select, #form_settings_page textarea",Mi),yo(),jQuery(document).on("frm-action-loaded",Ki),frmDom.util.documentOn("change",'.frm_on_submit_type input[type="radio"]',function(e){if(e.target.checked){var t=e.target.closest(".frm_form_action_settings");t.querySelectorAll(".frm_on_submit_dependent_setting:not(.frm_hidden)").forEach(function(e){e.classList.add("frm_hidden")}),t.querySelectorAll(".frm_on_submit_dependent_setting[data-show-if-"+e.target.value+"]").forEach(function(e){e.classList.remove("frm_hidden")}),t.setAttribute("data-on-submit-type",e.target.value)}}),wp.hooks.addAction("frm_reset_fields_updated","formidableAdmin",zi)},panelInit:function(){var e,t,r,n;jQuery(".frm_wrap, #postbox-container-1").on("click",".frm_insert_code",Ei),jQuery(document).on("change",".frm_insert_val",function(){ki(jQuery(this).data("target"),jQuery(this).val()),jQuery(this).val("")}),jQuery(document).on("click change",'[name="frm-id-key-condition"]',Li),jQuery(document).on("keyup change",".frm-build-logic",Ii),Xn(),jQuery(document).on("frmElementAdded",function(e,t){Xn(t)}),jQuery(document).on("mousedown",".frm-show-box",Ci),t=document.getElementById("form_settings_page"),r=document.body.classList.contains("post-type-frm_display"),n=document.getElementById("frm_insert_fields_tab"),(null!==t||r||O)&&jQuery(document).on("focusin","form input, form textarea",function(e){var i,o,a,l;if(e.stopPropagation(),qi(this),jQuery(this).is(":not(:submit, input[type=button], .frm-search-input, input[type=checkbox])")){if(jQuery(e.target).closest("#frm_adv_info").length)return;if(null!==t||O)i=jQuery("#frm_html_tab"),jQuery(this).closest("#html_settings").length>0?(i.show(),i.siblings().hide(),jQuery("#frm_html_tab a").trigger("click"),void 0===(l=this.id)||l.includes("-search-input")||(jQuery("#frm-adv-info-tab").attr("data-fills",l.trim()),this.classList.contains("field_custom_html")&&(l="field_custom_html"),a=["after_html","before_html","submit_html","field_custom_html"],jQuery.inArray(l,a)>=0&&(jQuery(".frm_code_list li:not(.show_"+l+")").addClass("frm_hidden"),jQuery(".frm_code_list li.show_"+l).removeClass("frm_hidden")))):((o=jQuery(".frm-category-tabs li"))[0]&&(o[0].style.display=""),n.click(),i.hide(),i.siblings().show());else if(r){var s=new CustomEvent("frm_legacy_views_handle_field_focus");s.frmData={idAttrValue:this.id},document.dispatchEvent(s)}}}),jQuery(".frm_wrap, #postbox-container-1").on("mousedown","#frm_adv_info a, .frm_field_list a",function(e){e.preventDefault()}),(e=jQuery("#frm_adv_info")).on("click",".subsubsub a.frmids",function(e){$i("frmids",e)}),e.on("click",".subsubsub a.frmkeys",function(e){$i("frmkeys",e)})},inboxInit:function(){var e;jQuery(".frm_inbox_dismiss").on("click",function(e){var t=this.parentNode.parentNode,r=t.getAttribute("data-message"),n=this.getAttribute("href"),i=t.cloneNode(!0),o=document.querySelector(".frm-dismissed-inbox-messages");if("free_templates"!==r||this.classList.contains("frm_inbox_dismiss")){e.preventDefault();var a={action:"frm_inbox_dismiss",key:r,nonce:frmGlobal.nonce},l="frm_inbox_slide_in"===t.id;l&&(t.classList.remove("s11-fadein"),t.classList.add("s11-fadeout"),t.addEventListener("animationend",function(){return t.remove()},{once:!0})),Ao(a,function(){if(!l)return"#"!==n?(window.location=n,!0):void So(t,function(){var e;null!==o&&(i.classList.remove("frm-fade"),null===(e=i.querySelector(".frm-inbox-message-heading .frm_inbox_dismiss"))||void 0===e||e.remove(),o.append(i)),1===t.parentNode.querySelectorAll(".frm-inbox-message-container").length&&(document.getElementById("frm_empty_inbox").classList.remove("frm_hidden"),t.parentNode.closest(".frm-active").classList.add("frm-empty-inbox"),_o()),t.remove()})})}}),!1===(null===(e=document.getElementById("frm_empty_inbox"))||void 0===e?void 0:e.classList.contains("frm_hidden"))&&_o()},solutionInit:function(){jQuery(document).on("submit","#frm-new-template",vo)},styleInit:function(){var e=jQuery(".frm_image_preview_wrapper");e.on("click",".frm_choose_image_box",xt),e.on("click",".frm_remove_image_option",Et),wp.hooks.doAction("frm_style_editor_init")},customCSSInit:function(){console.warn("Calling frmAdminBuild.customCSSInit is deprecated.")},globalSettingsInit:function(){var e;jQuery(document).on("click","[data-frmuninstall]",Ji),so(),null!==(e=document.getElementById("licenses_settings"))&&jQuery(e).on("click",".edd_frm_save_license",Xi),jQuery(document).on("click","#frm-new-template button",ho),jQuery("#frm-dismissable-cta .dismiss").on("click",function(e){e.preventDefault(),jQuery.post(ajaxurl,{action:"frm_lite_settings_upgrade",nonce:frmGlobal.nonce}),jQuery(".settings-lite-cta").remove()});var t=document.getElementById("frm_re_type");t&&t.addEventListener("change",jo),document.querySelector(".frm_captchas").addEventListener("change",function(e){var t,r=null===(t=document.querySelector('.frm_captchas input[checked="checked"]'))||void 0===t?void 0:t.value,n=e.target.value!==r;document.querySelector(".captcha_settings .frm_note_style").classList.toggle("frm_hidden",!n)}),frmDom.util.documentOn("submit",".frm_settings_form",function(){return x=0});var r=document.getElementById("manage_styles_settings");r&&r.addEventListener("change",function(e){var t=e.target;"SELECT"===t.nodeName&&t.dataset.name&&!t.getAttribute("name")&&t.setAttribute("name",t.dataset.name)});var n=document.getElementById("payments_settings"),i=null==n?void 0:n.querySelectorAll('[name="frm_payment_section"]');i&&i.forEach(function(e){e.addEventListener("change",function(){if(e.checked){var t=n.querySelector('label[for="'.concat(e.id,'"]'));t&&t.setAttribute("aria-selected","true"),i.forEach(function(t){if(t!==e){var r=n.querySelector('label[for="'.concat(t.id,'"]'));r&&r.setAttribute("aria-selected","false")}})}})})},exportInit:function(){jQuery(".frm_form_importer").on("submit",Yi),jQuery(document.getElementById("frm_export_xml")).on("submit",eo),jQuery("#frm_export_xml input, #frm_export_xml select").on("change",to),jQuery('input[name="frm_import_file"]').on("change",ro),document.querySelector('select[name="format"]').addEventListener("change",io),jQuery('input[name="frm_export_forms[]"]').on("click",lo),so(),jQuery(".frm-feature-banner .dismiss").on("click",function(e){e.preventDefault(),jQuery.post(ajaxurl,{action:"frm_dismiss_migrator",plugin:this.id,nonce:frmGlobal.nonce}),this.parentElement.remove()}),ao(no()),document.querySelector("#frm-export-select-all").addEventListener("change",function(e){document.querySelectorAll('[name="frm_export_forms[]"]').forEach(function(t){return t.checked=e.target.checked})})},inboxBannerInit:function(){var e=document.getElementById("frm_banner");if(e){var t=e.querySelector(".frm-banner-dismiss");document.addEventListener("click",function(r){r.target===t&&Ao({action:"frm_inbox_dismiss",key:e.dataset.key,nonce:frmGlobal.nonce},function(){jQuery(e).fadeOut(400,function(){e.remove()})})})}},updateOpts:function(e,t,r){var n=yn(e),i=Oo(e)?"frm_bulk_products":"frm_import_options";jQuery.ajax({type:"POST",url:ajaxurl,data:{action:i,field_id:e,opts:t,separate:n,nonce:frmGlobal.nonce},success:function(t){document.getElementById("frm_field_"+e+"_opts").innerHTML=t,wp.hooks.doAction("frm_after_bulk_edit_opts",e),on(e),void 0!==r&&(r.dialog("close"),document.getElementById("frm-update-bulk-opts").classList.remove("frm_loading_button"))}})},triggerRemoveLogic:function(e,t){jQuery("#frm_logic_"+e+"_"+t+" .frm_remove_tag").trigger("click")},downloadXML:function(e,t,r){var n=ajaxurl+"?action=frm_"+e+"_xml&ids="+t;null!==r&&(n=n+"&is_template="+r),location.href=n},hooks:{applyFilters:function(e){for(var t,r=arguments.length,n=new Array(r>1?r-1:0),i=1;i1?r-1:0),i=1;i1&&void 0!==arguments[1]?arguments[1]:{};return new Promise(function(r){var n=E,i=0;"summary"===e&&(i=b.children('li[data-type="break"]').length>0?1:0),jQuery.ajax({type:"POST",url:ajaxurl,data:Object.assign(_e(e,0,n,i),{field_options:t}),success:function(t){r(t),setTimeout(function(){En(),Ue(t,!0);var r=ye(t);r&&wp.hooks.doAction("frm_after_field_added_in_form_builder",{field:t,fieldId:r,fieldType:e,form_id:n})},10)},error:ve})})},confirmLinkClick:D,handleInsertFieldByDraggingResponse:ge,handleAddFieldClickResponse:Le,syncLayoutClasses:ae,moveFieldSettings:ii,maybeCollapseSettings:zr}},window.frmAdminBuild=frmAdminBuildJS(),jQuery(document).ready(function(){var e;frmAdminBuild.init(),document.querySelectorAll(".frm-dropdown-menu").forEach(function(e){e.classList.add("dropdown-menu");var t,r,n=e.querySelector(".frm-dropdown-toggle");n&&(n.hasAttribute("role")||n.setAttribute("role","button"),n.hasAttribute("tabindex")||n.setAttribute("tabindex",0)),"UL"===e.tagName&&(r=(r=(r=(r=(r=(r=(t=e).outerHTML).replace("
      ","
    ")).replaceAll("
  • ",'
  • ","
  • "),t.outerHTML=r)}),null===(e=document.querySelector(".preview.dropdown .frm-dropdown-toggle"))||void 0===e||e.setAttribute("data-bs-toggle","dropdown"),document.querySelectorAll("[data-toggle]").forEach(function(e){return e.setAttribute("data-bs-toggle",e.getAttribute("data-toggle"))})}),window.frm_show_div=function(e,t,r,n){t==r?jQuery(n+e).fadeIn("slow").css("visibility","visible"):jQuery(n+e).fadeOut("slow")},window.frmCheckAll=function(e,t){jQuery('input[name^="'+t+'"]').prop("checked",!!e)},window.frmCheckAllLevel=function(e,t,r){jQuery(".frm_catlevel_"+r).children(".frm_checkbox").children("label").children('input[name^="'+t+'"]').prop("checked",!!e)},window.frmGetFieldValues=function(e,t,r,n,i,o){e&&jQuery.ajax({type:"POST",url:ajaxurl,data:"action=frm_get_field_values¤t_field="+t+"&field_id="+e+"&name="+i+"&t="+n+"&form_action="+jQuery('input[name="frm_action"]').val()+"&nonce="+frmGlobal.nonce,success:function(e){document.getElementById("frm_show_selected_values_"+t+"_"+r).innerHTML=e,"function"==typeof o&&o()}})},window.frmImportCsv=function(e){var t="";"undefined"!=typeof __FRMURLVARS&&(t=__FRMURLVARS),jQuery.ajax({type:"POST",url:ajaxurl,data:"action=frm_import_csv&nonce="+frmGlobal.nonce+"&frm_skip_cookie=1"+t,success:function(t){var r=jQuery(".frm_admin_progress_bar").attr("aria-valuemax"),n=r-t,i=n/r*100;jQuery(".frm_admin_progress_bar").css("width",i+"%").attr("aria-valuenow",n),parseInt(t,10)>0?(jQuery(".frm_csv_remaining").html(t),frmImportCsv(e)):(jQuery(document.getElementById("frm_import_message")).html(frm_admin_js.import_complete),setTimeout(function(){location.href="?page=formidable-entries&frm_action=list&form="+e+"&import-message=1"},2e3))}})}})(); \ No newline at end of file diff --git a/js/frm_testing_mode.js b/js/frm_testing_mode.js index 6159aee96c..96b0b8496e 100644 --- a/js/frm_testing_mode.js +++ b/js/frm_testing_mode.js @@ -1,2 +1,2 @@ /*! For license information please see frm_testing_mode.js.LICENSE.txt */ -(()=>{var e={65:(e,t,r)=>{"use strict";r.d(t,{A:()=>i});var n=r(8793),o=r(1323);function i(e){var t=(0,n.A)(e);return function(e){return(0,o.A)(t,e)}}},1323:(e,t,r)=>{"use strict";r.d(t,{A:()=>o});var n={"!":function(e){return!e},"*":function(e,t){return e*t},"/":function(e,t){return e/t},"%":function(e,t){return e%t},"+":function(e,t){return e+t},"-":function(e,t){return e-t},"<":function(e,t){return e":function(e,t){return e>t},">=":function(e,t){return e>=t},"==":function(e,t){return e===t},"!=":function(e,t){return e!==t},"&&":function(e,t){return e&&t},"||":function(e,t){return e||t},"?:":function(e,t,r){if(e)throw t;return r}};function o(e,t){var r,o,i,a,s,l,c=[];for(r=0;r{"use strict";r.d(t,{A:()=>o});var n=r(65);function o(e){var t=(0,n.A)(e);return function(e){return+t({n:e})}}},8793:(e,t,r)=>{"use strict";var n,o,i,a;function s(e){for(var t,r,s,l,c=[],u=[];t=e.match(a);){for(r=t[0],(s=e.substr(0,t.index).trim())&&c.push(s);l=u.pop();){if(i[r]){if(i[r][0]===l){r=i[r][1]||r;break}}else if(o.indexOf(l)>=0||n[l]s}),n={"(":9,"!":8,"*":7,"/":7,"%":7,"+":6,"-":6,"<":5,"<=":5,">":5,">=":5,"==":4,"!=":4,"&&":3,"||":2,"?":1,"?:":1},o=["(","?"],i={")":["("],":":["?","?:"]},a=/<=|>=|==|!=|&&|\|\||\?:|\(|!|\*|\/|%|\+|-|<|>|\?|\)|:/},7521:(e,t,r)=>{"use strict";r.d(t,{A:()=>i});var n=r(6956),o=r(7395);const i=function(e,t){return function(r,i,a){var s=arguments.length>3&&void 0!==arguments[3]?arguments[3]:10,l=e[t];if((0,o.A)(r)&&(0,n.A)(i))if("function"==typeof a)if("number"==typeof s){var c={callback:a,priority:s,namespace:i};if(l[r]){var u,d=l[r].handlers;for(u=d.length;u>0&&!(s>=d[u-1].priority);u--);u===d.length?d[u]=c:d.splice(u,0,c),l.__current.forEach(function(e){e.name===r&&e.currentIndex>=u&&e.currentIndex++})}else l[r]={handlers:[c],runs:0};"hookAdded"!==r&&e.doAction("hookAdded",r,i,a,s)}else console.error("If specified, the hook priority must be a number.");else console.error("The hook callback must be a function.")}}},11:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});const n=function(e,t){return function(){var r,n,o=e[t];return null!==(r=null===(n=o.__current[o.__current.length-1])||void 0===n?void 0:n.name)&&void 0!==r?r:null}}},5375:(e,t,r)=>{"use strict";r.d(t,{A:()=>o});var n=r(7395);const o=function(e,t){return function(r){var o=e[t];if((0,n.A)(r))return o[r]&&o[r].runs?o[r].runs:0}}},3561:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});const n=function(e,t){return function(r){var n=e[t];return void 0===r?void 0!==n.__current[0]:!!n.__current[0]&&r===n.__current[0].name}}},8830:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});const n=function(e,t){return function(r,n){var o=e[t];return void 0!==n?r in o&&o[r].handlers.some(function(e){return e.namespace===n}):r in o}}},7765:(e,t,r)=>{"use strict";r.d(t,{A:()=>f});var n=r(3029),o=r(7521),i=r(4194),a=r(8830),s=r(6763),l=r(11),c=r(3561),u=r(5375),d=function e(){(0,n.A)(this,e),this.actions=Object.create(null),this.actions.__current=[],this.filters=Object.create(null),this.filters.__current=[],this.addAction=(0,o.A)(this,"actions"),this.addFilter=(0,o.A)(this,"filters"),this.removeAction=(0,i.A)(this,"actions"),this.removeFilter=(0,i.A)(this,"filters"),this.hasAction=(0,a.A)(this,"actions"),this.hasFilter=(0,a.A)(this,"filters"),this.removeAllActions=(0,i.A)(this,"actions",!0),this.removeAllFilters=(0,i.A)(this,"filters",!0),this.doAction=(0,s.A)(this,"actions"),this.applyFilters=(0,s.A)(this,"filters",!0),this.currentAction=(0,l.A)(this,"actions"),this.currentFilter=(0,l.A)(this,"filters"),this.doingAction=(0,c.A)(this,"actions"),this.doingFilter=(0,c.A)(this,"filters"),this.didAction=(0,u.A)(this,"actions"),this.didFilter=(0,u.A)(this,"filters")};const f=function(){return new d}},4194:(e,t,r)=>{"use strict";r.d(t,{A:()=>i});var n=r(6956),o=r(7395);const i=function(e,t){var r=arguments.length>2&&void 0!==arguments[2]&&arguments[2];return function(i,a){var s=e[t];if((0,o.A)(i)&&(r||(0,n.A)(a))){if(!s[i])return 0;var l=0;if(r)l=s[i].handlers.length,s[i]={runs:s[i].runs,handlers:[]};else for(var c=s[i].handlers,u=function(e){c[e].namespace===a&&(c.splice(e,1),l++,s.__current.forEach(function(t){t.name===i&&t.currentIndex>=e&&t.currentIndex--}))},d=c.length-1;d>=0;d--)u(d);return"hookRemoved"!==i&&e.doAction("hookRemoved",i,a),l}}}},6763:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});const n=function(e,t){var r=arguments.length>2&&void 0!==arguments[2]&&arguments[2];return function(n){var o=e[t];o[n]||(o[n]={handlers:[],runs:0}),o[n].runs++;for(var i=o[n].handlers,a=arguments.length,s=new Array(a>1?a-1:0),l=1;l{"use strict";r.d(t,{se:()=>n});var n=(0,r(7765).A)();n.addAction,n.addFilter,n.removeAction,n.removeFilter,n.hasAction,n.hasFilter,n.removeAllActions,n.removeAllFilters,n.doAction,n.applyFilters,n.currentAction,n.currentFilter,n.doingAction,n.doingFilter,n.didAction,n.didFilter,n.actions,n.filters},7395:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});const n=function(e){return"string"!=typeof e||""===e?(console.error("The hook name must be a non-empty string."),!1):/^__/.test(e)?(console.error("The hook name cannot begin with `__`."),!1):!!/^[a-zA-Z][a-zA-Z0-9_.-]*$/.test(e)||(console.error("The hook name can only contain numbers, letters, dashes, periods and underscores."),!1)}},6956:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});const n=function(e){return"string"!=typeof e||""===e?(console.error("The namespace must be a non-empty string."),!1):!!/^[a-zA-Z][a-zA-Z0-9_.\-\/]*$/.test(e)||(console.error("The namespace can only contain numbers, letters, dashes, periods, underscores and slashes."),!1)}},772:(e,t,r)=>{"use strict";r.d(t,{h:()=>c});var n=r(4467),o=r(5397);function i(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function a(e){for(var t=1;t1&&void 0!==arguments[1]?arguments[1]:"default";n.data[t]=a(a(a({},s),n.data[t]),e),n.data[t][""]=a(a({},s[""]),n.data[t][""])},d=function(e,t){u(e,t),c()},f=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"default",t=arguments.length>1?arguments[1]:void 0,r=arguments.length>2?arguments[2]:void 0,o=arguments.length>3?arguments[3]:void 0,i=arguments.length>4?arguments[4]:void 0;return n.data[e]||u(void 0,e),n.dcnpgettext(e,t,r,o,i)},p=function(){return arguments.length>0&&void 0!==arguments[0]?arguments[0]:"default"},m=function(e,t,n){var o=f(n,t,e);return r?(o=r.applyFilters("i18n.gettext_with_context",o,e,t,n),r.applyFilters("i18n.gettext_with_context_"+p(n),o,e,t,n)):o};if(e&&d(e,t),r){var v=function(e){l.test(e)&&c()};r.addAction("hookAdded","core/i18n",v),r.addAction("hookRemoved","core/i18n",v)}return{getLocaleData:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"default";return n.data[e]},setLocaleData:d,resetLocaleData:function(e,t){n.data={},n.pluralForms={},d(e,t)},subscribe:function(e){return i.add(e),function(){return i.delete(e)}},__:function(e,t){var n=f(t,void 0,e);return r?(n=r.applyFilters("i18n.gettext",n,e,t),r.applyFilters("i18n.gettext_"+p(t),n,e,t)):n},_x:m,_n:function(e,t,n,o){var i=f(o,void 0,e,t,n);return r?(i=r.applyFilters("i18n.ngettext",i,e,t,n,o),r.applyFilters("i18n.ngettext_"+p(o),i,e,t,n,o)):i},_nx:function(e,t,n,o,i){var a=f(i,o,e,t,n);return r?(a=r.applyFilters("i18n.ngettext_with_context",a,e,t,n,o,i),r.applyFilters("i18n.ngettext_with_context_"+p(i),a,e,t,n,o,i)):a},isRTL:function(){return"rtl"===m("ltr","text direction")},hasTranslation:function(e,t,o){var i,a,s=t?t+""+e:e,l=!(null===(i=n.data)||void 0===i||null===(a=i[null!=o?o:"default"])||void 0===a||!a[s]);return r&&(l=r.applyFilters("i18n.has_translation",l,e,t,o),l=r.applyFilters("i18n.has_translation_"+p(o),l,e,t,o)),l}}}},5839:(e,t,r)=>{"use strict";r.d(t,{__:()=>a});var n=r(772),o=r(2133),i=(0,n.h)(void 0,void 0,o.se),a=(i.getLocaleData.bind(i),i.setLocaleData.bind(i),i.resetLocaleData.bind(i),i.subscribe.bind(i),i.__.bind(i));i._x.bind(i),i._n.bind(i),i._nx.bind(i),i.isRTL.bind(i),i.hasTranslation.bind(i)},9575:(e,t,r)=>{"use strict";r.d(t,{__:()=>n.__}),r(181),r(772);var n=r(5839)},181:(e,t,r)=>{"use strict";var n=r(8616),o=r.n(n);r(7604),o()(console.error)},1105:(e,t,r)=>{"use strict";r.r(t),r.d(t,{addonError:()=>u,afterAddonInstall:()=>c,extractErrorFromAddOnResponse:()=>l,toggleAddonState:()=>s});var n=r(9575),o=frmDom,i=o.div,a=o.svg;function s(e,t){var r,n=null!==(r=window.ajaxurl)&&void 0!==r?r:frm_js.ajax_url;jQuery(".frm-addon-error").remove();var o=jQuery(e),i=o.attr("rel"),a=o.parent(),s=a.parent().find(".addon-status-label");o.addClass("frm_loading_button"),jQuery.ajax({url:n,type:"POST",async:!0,cache:!1,dataType:"json",data:{action:t,nonce:frmGlobal.nonce,plugin:i},success:function(e){var r,n,i;"string"!=typeof(e=null!==(r=null===(n=e)||void 0===n?void 0:n.data)&&void 0!==r?r:e)&&"string"==typeof e.message&&(void 0!==e.saveAndReload&&(i=e.saveAndReload),e=e.message);var d=l(e);d?u(d,a,o):(c(e,o,s,a,i,t),wp.hooks.doAction("frm_update_addon_state",e))},error:function(){o.removeClass("frm_loading_button")}})}function l(e){return"string"!=typeof e&&(void 0===e.success||!e.success)&&(e.form&&jQuery(e.form).is("#message")?{message:jQuery(e.form).find("p").html()}:e)}function c(e,t,r,o,s){var l=arguments.length>5&&void 0!==arguments[5]?arguments[5]:"frm_activate_addon",c=frm_admin_js,u=document.querySelectorAll(".frm-addon-status");u.forEach(function(t){t.textContent=e,t.style.display="block"}),t.css({opacity:"0"}),document.querySelectorAll(".frm-oneclick").forEach(function(e){e.style.display="none"}),function(){var e=document.getElementById("frm_upgrade_modal");if(e){e.classList.add("frm-success");var t=e.querySelector(".frm-upgrade-message");if(t){var r=t.querySelector("img");t.replaceChildren((0,n.__)("Great! Everything's ready to go!","formidable"),document.createElement("br"),(0,n.__)("You just need to refresh the builder so the new field becomes available.","formidable")),r&&t.append(r)}var o=document.querySelector(".frm-addon-status");o&&(o.textContent="");var i,s=e.querySelector(".frm-circled-icon");if(s)s.classList.add("frm-circled-icon-green"),null===(i=s.querySelector("svg"))||void 0===i||i.replaceWith(a({href:"#frm_checkmark_icon"}))}}();var f={frm_activate_addon:{class:"frm-addon-active",message:c.active},frm_deactivate_addon:{class:"frm-addon-installed",message:c.installed},frm_uninstall_addon:{class:"frm-addon-not-installed",message:c.not_installed}};f.frm_install_addon=f.frm_activate_addon;var p=r[0];p&&(p.textContent=f[l].message);var m=o[0].parentElement;m.classList.remove("frm-addon-not-installed","frm-addon-installed","frm-addon-active"),m.classList.add(f[l].class),t[0].classList.remove("frm_loading_button"),document.querySelectorAll(".frm-admin-page-import, #frm-admin-smtp, #frm-welcome").length>0?window.location.reload():["settings","form_builder"].includes(s)&&u.forEach(function(e){var t=null!==e.closest("#frm_upgrade_modal");e.append(function(e,t){var r,o=[d(e)];return t&&o.push(((r=document.createElement("a")).setAttribute("href","#"),r.classList.add("button","button-secondary","frm-button-secondary","dismiss"),r.textContent=(0,n.__)("Not Now","formidable"),r)),i({className:"frm-save-and-reload-options",children:o})}(s,t))})}function u(e,t,r){e.form?(jQuery(".frm-inline-error").remove(),r.closest(".frm-card").html(e.form).css({padding:5}).find("#upgrade").attr("rel",r.attr("rel")).on("click",f)):(t.append('

    '+e.message+"

    "),r.removeClass("frm_loading_button"),jQuery(".frm-addon-error").delay(4e3).fadeOut())}function d(e){var t=document.createElement("button");return t.classList.add("frm-save-and-reload","button","button-primary","frm-button-primary"),t.textContent=(0,n.__)("Save and Reload","formidable"),t.addEventListener("click",function(){var t;"form_builder"===e?((t=document.getElementById("frm_submit_side_top")).classList.contains("frm_submit_ajax")&&t.setAttribute("data-new-addon-installed",!0),t.click()):"settings"===e&&function(){var e=document.getElementById("form_settings_page");if(null!==e){var t=e.querySelector("form.frm_form_settings");null!==t&&(wp.hooks.doAction("frm_reset_fields_updated"),t.submit())}}()}),t}function f(e){e.preventDefault();var t=jQuery(this),r=t.parent().parent(),n=t.attr("rel");t.addClass("frm_loading_button"),jQuery.ajax({url:ajaxurl,type:"POST",async:!0,cache:!1,dataType:"json",data:{action:"frm_install_addon",nonce:frmAdminJs.nonce,plugin:n,hostname:r.find("#hostname").val(),username:r.find("#username").val(),password:r.find("#password").val()},success:function(e){var n,o,i=l(e=null!==(n=null===(o=e)||void 0===o?void 0:o.data)&&void 0!==n?n:e);i?u(i,r,t):c(e,t,message,r)},error:function(){t.removeClass("frm_loading_button")}})}},4260:(e,t,r)=>{"use strict";r.r(t),r.d(t,{addOneClick:()=>i,initModal:()=>a,initUpgradeModal:()=>s});var n=r(9575),o=frmDom.svg;function i(e,t,r){var i;if("modal"===t)i=document.getElementById("frm_upgrade_modal");else{if("tab"!==t)return;i=document.getElementById(e.getAttribute("href").substr(1))}var a,s=i.querySelector(".frm-oneclick"),l=i.querySelector(".frm-upgrade-message"),c=i.querySelector(".frm-upgrade-link"),u=i.querySelector(".frm-oneclick-button"),d=i.querySelector(".frm-addon-status"),f=e.getAttribute("data-oneclick"),p=e.getAttribute("data-message"),m="block",v="block",h="none",g=i.querySelector(".frm-circled-icon");g&&(g.classList.remove("frm-circled-icon-green"),null===(a=g.querySelector("svg"))||void 0===a||a.replaceWith(o({href:"#frm_filled_lock_icon"})));var y=i.querySelector(".frm-learn-more");if(y&&(y.href=e.dataset.learnMore),null!==s&&null!==u&&void 0!==f&&f){null===p&&(v="none"),m="none",h="block",f=JSON.parse(f),u.className=u.className.replace(" frm-install-addon","").replace(" frm-activate-addon",""),u.className=u.className+" "+f.class,u.rel=f.url,s.textContent=(0,n.__)("This plugin is not activated. Would you like to activate it now?","formidable"),u.textContent=(0,n.__)("Activate","formidable");var _=e.querySelector("use");_&&(null==g||g.querySelector("svg").replaceWith(o({href:_.getAttribute("href")||_.getAttribute("xlink:href"),classList:["frm_svg32"]})))}p||(p=l.getAttribute("data-default")),void 0!==r&&(p=p.replace('',r)),l.innerHTML=p,e.dataset.upsellImage&&l.append(frmDom.img({src:e.dataset.upsellImage,alt:e.dataset.upgrade})),c.href=function(e,t){var r=e.getAttribute("data-link");return null!=r&&""!==r||(r=t.getAttribute("data-default")),r}(e,c),d.style.display="none",s&&(s.style.display=h),u&&(u.style.display="block"===h?"inline-block":h),l.style.display=v,c.style.display="block"===m?"inline-block":m;var b=c.closest(".frm-upgrade-modal-actions");b&&(b.style.display="block"===m?"flex":m)}function a(e,t){var r=jQuery(e);if(!r.length)return!1;void 0===t&&(t="552px");var n={dialogClass:"frm-dialog",modal:!0,autoOpen:!1,closeOnEscape:!0,width:t,resizable:!1,draggable:!1,open:function(){var e,t;jQuery(".ui-dialog-titlebar").addClass("frm_hidden").removeClass("ui-helper-clearfix"),jQuery("#wpwrap").addClass("frm_overlay"),jQuery(".frm-dialog").removeClass("ui-widget ui-widget-content ui-corner-all"),r.removeClass("ui-dialog-content ui-widget-content"),e=r,t=function(){e.dialog("close")},jQuery(".ui-widget-overlay").on("click",t),e.on("click","a.dismiss",t)},close:function(){jQuery("#wpwrap").removeClass("frm_overlay"),jQuery(".spinner").css("visibility","hidden"),this.removeAttribute("data-option-type");var e=document.getElementById("bulk-option-type");e&&(e.value="")}};return r.dialog(n),r}function s(){var e=a("#frm_upgrade_modal");function t(t){var r,n,o;if((r=t.target).classList){var a=r.classList.contains("frm_show_expired_modal")||null!==r.querySelector(".frm_show_expired_modal")||r.closest(".frm_show_expired_modal");if("change"===t.type&&r.classList.contains("frm_select_with_upgrade")){var s=r.options[r.selectedIndex];s&&s.dataset.upgrade&&(r=s)}if(!r.dataset.upgrade){var l=r.closest("[data-upgrade]");if(!l){if(!(l=r.closest(".frm_field_box")))return;r.dataset.upgrade=""}r=l}if(a)wp.hooks.doAction("frm_show_expired_modal",r);else{var c=r.dataset.upgrade;if(c&&!r.classList.contains("frm_show_upgrade_tab")){t.preventDefault();var u=e.get(0),d=u.querySelector(".frm_lock_icon");d&&(d.style.display="block",d.classList.remove("frm_lock_open_icon"),d.querySelector("use").setAttribute("href","#frm_lock_icon"));var f="frm_upgrade_modal_image",p=document.getElementById(f);p&&p.remove(),r.dataset.image&&d&&(d.style.display="none",d.parentNode.insertBefore(frmDom.img({id:f,src:frmGlobal.url+"/images/"+r.dataset.image}),d));var m=u.querySelector(".license-level");m&&(m.textContent=function(e){return e.dataset.requires?e.dataset.requires:"Pro"}(r)),i(r,"modal",c),u.querySelector(".frm_are_not_installed").style.display=r.dataset.image||r.dataset.oneclick?"none":"inline-block",u.querySelector(".frm-upgrade-modal-title-prefix").style.display=r.dataset.oneclick?"inline":"none",u.querySelector(".frm_feature_label").textContent=c,u.querySelector(".frm-upgrade-modal-title-suffix").style.display="none",u.querySelector("h2").style.display="block",e.dialog("open");var v=u.querySelector(".button-primary:not(.frm-oneclick-button)");n=v.getAttribute("href").replace(/(medium=)[a-z_-]+/gi,"$1"+r.getAttribute("data-medium")),null===(o=r.getAttribute("data-content"))&&(o=""),n=n.replace(/(content=)[a-z_-]+/gi,"$1"+o),v.setAttribute("href",n)}}}}!1!==e&&(document.addEventListener("click",t),frmDom.util.documentOn("change","select.frm_select_with_upgrade",t))}},8616:e=>{e.exports=function(e,t){var r,n,o=0;function i(){var i,a,s=r,l=arguments.length;e:for(;s;){if(s.args.length===arguments.length){for(a=0;a{var n;!function(){"use strict";var o={not_string:/[^s]/,not_bool:/[^t]/,not_type:/[^T]/,not_primitive:/[^v]/,number:/[diefg]/,numeric_arg:/[bcdiefguxX]/,json:/[j]/,not_json:/[^j]/,text:/^[^\x25]+/,modulo:/^\x25{2}/,placeholder:/^\x25(?:([1-9]\d*)\$|\(([^)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-gijostTuvxX])/,key:/^([a-z_][a-z_\d]*)/i,key_access:/^\.([a-z_][a-z_\d]*)/i,index_access:/^\[(\d+)\]/,sign:/^[+-]/};function i(e){return function(e,t){var r,n,a,s,l,c,u,d,f,p=1,m=e.length,v="";for(n=0;n=0),s.type){case"b":r=parseInt(r,10).toString(2);break;case"c":r=String.fromCharCode(parseInt(r,10));break;case"d":case"i":r=parseInt(r,10);break;case"j":r=JSON.stringify(r,null,s.width?parseInt(s.width):0);break;case"e":r=s.precision?parseFloat(r).toExponential(s.precision):parseFloat(r).toExponential();break;case"f":r=s.precision?parseFloat(r).toFixed(s.precision):parseFloat(r);break;case"g":r=s.precision?String(Number(r.toPrecision(s.precision))):parseFloat(r);break;case"o":r=(parseInt(r,10)>>>0).toString(8);break;case"s":r=String(r),r=s.precision?r.substring(0,s.precision):r;break;case"t":r=String(!!r),r=s.precision?r.substring(0,s.precision):r;break;case"T":r=Object.prototype.toString.call(r).slice(8,-1).toLowerCase(),r=s.precision?r.substring(0,s.precision):r;break;case"u":r=parseInt(r,10)>>>0;break;case"v":r=r.valueOf(),r=s.precision?r.substring(0,s.precision):r;break;case"x":r=(parseInt(r,10)>>>0).toString(16);break;case"X":r=(parseInt(r,10)>>>0).toString(16).toUpperCase()}o.json.test(s.type)?v+=r:(!o.number.test(s.type)||d&&!s.sign?f="":(f=d?"+":"-",r=r.toString().replace(o.sign,"")),c=s.pad_char?"0"===s.pad_char?"0":s.pad_char.charAt(1):" ",u=s.width-(f+r).length,l=s.width&&u>0?c.repeat(u):"",v+=s.align?f+r+l:"0"===c?f+l+r:l+f+r)}return v}(function(e){if(s[e])return s[e];for(var t,r=e,n=[],i=0;r;){if(null!==(t=o.text.exec(r)))n.push(t[0]);else if(null!==(t=o.modulo.exec(r)))n.push("%");else{if(null===(t=o.placeholder.exec(r)))throw new SyntaxError("[sprintf] unexpected placeholder");if(t[2]){i|=1;var a=[],l=t[2],c=[];if(null===(c=o.key.exec(l)))throw new SyntaxError("[sprintf] failed to parse named argument key");for(a.push(c[1]);""!==(l=l.substring(c[0].length));)if(null!==(c=o.key_access.exec(l)))a.push(c[1]);else{if(null===(c=o.index_access.exec(l)))throw new SyntaxError("[sprintf] failed to parse named argument key");a.push(c[1])}t[2]=a}else i|=2;if(3===i)throw new Error("[sprintf] mixing positional and named placeholders is not (yet) supported");n.push({placeholder:t[0],param_no:t[1],keys:t[2],sign:t[3],pad_char:t[4],align:t[5],width:t[6],precision:t[7],type:t[8]})}r=r.substring(t[0].length)}return s[e]=n}(e),arguments)}function a(e,t){return i.apply(null,[e].concat(t||[]))}var s=Object.create(null);"undefined"!=typeof window&&(window.sprintf=i,window.vsprintf=a,void 0===(n=function(){return{sprintf:i,vsprintf:a}}.call(t,r,t,e))||(e.exports=n))}()},5397:(e,t,r)=>{"use strict";r.d(t,{A:()=>i});var n=r(1364),o={contextDelimiter:"",onMissingKey:null};function i(e,t){var r;for(r in this.data=e,this.pluralForms={},this.options={},o)this.options[r]=void 0!==t&&r in t?t[r]:o[r]}i.prototype.getPluralForm=function(e,t){var r,o,i,a=this.pluralForms[e];return a||("function"!=typeof(i=(r=this.data[e][""])["Plural-Forms"]||r["plural-forms"]||r.plural_forms)&&(o=function(e){var t,r,n;for(t=e.split(";"),r=0;r{"use strict";function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}r.d(t,{A:()=>n})},4467:(e,t,r)=>{"use strict";r.d(t,{A:()=>o});var n=r(9922);function o(e,t,r){return(t=(0,n.A)(t))in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}},2327:(e,t,r)=>{"use strict";r.d(t,{A:()=>o});var n=r(2284);function o(e,t){if("object"!=(0,n.A)(e)||!e)return e;var r=e[Symbol.toPrimitive];if(void 0!==r){var o=r.call(e,t||"default");if("object"!=(0,n.A)(o))return o;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===t?String:Number)(e)}},9922:(e,t,r)=>{"use strict";r.d(t,{A:()=>i});var n=r(2284),o=r(2327);function i(e){var t=(0,o.A)(e,"string");return"symbol"==(0,n.A)(t)?t:t+""}},2284:(e,t,r)=>{"use strict";function n(e){return n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},n(e)}r.d(t,{A:()=>n})}},t={};function r(n){var o=t[n];if(void 0!==o)return o.exports;var i=t[n]={exports:{}};return e[n](i,i.exports,r),i.exports}function n(){var e,t,r="function"==typeof Symbol?Symbol:{},i=r.iterator||"@@iterator",a=r.toStringTag||"@@toStringTag";function s(r,n,i,a){var s=n&&n.prototype instanceof c?n:c,u=Object.create(s.prototype);return o(u,"_invoke",function(r,n,o){var i,a,s,c=0,u=o||[],d=!1,f={p:0,n:0,v:e,a:p,f:p.bind(e,4),d:function(t,r){return i=t,a=0,s=e,f.n=r,l}};function p(r,n){for(a=r,s=n,t=0;!d&&c&&!o&&t3?(o=m===n)&&(s=i[(a=i[4])?5:(a=3,3)],i[4]=i[5]=e):i[0]<=p&&((o=r<2&&pn||n>m)&&(i[4]=r,i[5]=n,f.n=m,a=0))}if(o||r>1)return l;throw d=!0,n}return function(o,u,m){if(c>1)throw TypeError("Generator is already running");for(d&&1===u&&p(u,m),a=u,s=m;(t=a<2?e:s)||!d;){i||(a?a<3?(a>1&&(f.n=-1),p(a,s)):f.n=s:f.v=s);try{if(c=2,i){if(a||(o="next"),t=i[o]){if(!(t=t.call(i,s)))throw TypeError("iterator result is not an object");if(!t.done)return t;s=t.value,a<2&&(a=0)}else 1===a&&(t=i.return)&&t.call(i),a<2&&(s=TypeError("The iterator does not provide a '"+o+"' method"),a=1);i=e}else if((t=(d=f.n<0)?s:r.call(n,f))!==l)break}catch(t){i=e,a=1,s=t}finally{c=1}}return{value:t,done:d}}}(r,i,a),!0),u}var l={};function c(){}function u(){}function d(){}t=Object.getPrototypeOf;var f=[][i]?t(t([][i]())):(o(t={},i,function(){return this}),t),p=d.prototype=c.prototype=Object.create(f);function m(e){return Object.setPrototypeOf?Object.setPrototypeOf(e,d):(e.__proto__=d,o(e,a,"GeneratorFunction")),e.prototype=Object.create(p),e}return u.prototype=d,o(p,"constructor",d),o(d,"constructor",u),u.displayName="GeneratorFunction",o(d,a,"GeneratorFunction"),o(p),o(p,a,"Generator"),o(p,i,function(){return this}),o(p,"toString",function(){return"[object Generator]"}),(n=function(){return{w:s,m}})()}function o(e,t,r,n){var i=Object.defineProperty;try{i({},"",{})}catch(e){i=0}o=function(e,t,r,n){function a(t,r){o(e,t,function(e){return this._invoke(t,r,e)})}t?i?i(e,t,{value:r,enumerable:!n,configurable:!n,writable:!n}):e[t]=r:(a("next",0),a("throw",1),a("return",2))},o(e,t,r,n)}function i(e,t,r,n,o,i,a){try{var s=e[i](a),l=s.value}catch(e){return void r(e)}s.done?t(l):Promise.resolve(l).then(n,o)}r.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return r.d(t,{a:t}),t},r.d=(e,t)=>{for(var n in t)r.o(t,n)&&!r.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},function(){var e=r(1105).toggleAddonState;function t(){jQuery(document).on("click","#frm_upgrade_modal .frm-install-addon",function(t){t.preventDefault(),e(this,"frm_install_addon")}),jQuery(document).on("click","#frm_upgrade_modal .frm-activate-addon",function(t){t.preventDefault(),e(this,"frm_activate_addon")}),function(){o.apply(this,arguments)}(),a(),jQuery(document).on("mouseenter.frm",".frm_help",function(){jQuery(this).off("mouseenter.frm"),jQuery(this).tooltip("show")})}function o(){var e;return e=n().m(function e(){return n().w(function(e){for(;;)switch(e.n){case 0:return e.n=1,Promise.resolve().then(r.bind(r,4260));case 1:e.v.initUpgradeModal();case 2:return e.a(2)}},e)}),o=function(){var t=this,r=arguments;return new Promise(function(n,o){var a=e.apply(t,r);function s(e){i(a,n,o,s,l,"next",e)}function l(e){i(a,n,o,s,l,"throw",e)}s(void 0)})},o.apply(this,arguments)}function a(){var e=window.frmDom;e.bootstrap.setupBootstrapDropdowns(function(){var e=document.querySelector("#frm_testmode_enabled_form_actions .dropdown-toggle");e&&(e.classList.add("frm-dropdown-toggle"),e.hasAttribute("role")||e.setAttribute("role","button"),e.hasAttribute("tabindex")||e.setAttribute("tabindex",0))});var t=document.getElementById("frm_testmode_enabled_form_actions");t&&(t.style.display="none",e.bootstrap.multiselect.init.bind(t)(),t.disabled&&t.parentElement.querySelector(".dropdown-toggle").classList.add("frm_noallow"))}"complete"===document.readyState?t():document.addEventListener("DOMContentLoaded",t),document.addEventListener("frm_after_start_over",function(){a()}),jQuery(document).on("frmPageChanged frmFormComplete",function(){a()})}()})(); \ No newline at end of file +(()=>{var e={65:(e,t,r)=>{"use strict";r.d(t,{A:()=>i});var n=r(8793),o=r(1323);function i(e){var t=(0,n.A)(e);return function(e){return(0,o.A)(t,e)}}},1323:(e,t,r)=>{"use strict";r.d(t,{A:()=>o});var n={"!":function(e){return!e},"*":function(e,t){return e*t},"/":function(e,t){return e/t},"%":function(e,t){return e%t},"+":function(e,t){return e+t},"-":function(e,t){return e-t},"<":function(e,t){return e":function(e,t){return e>t},">=":function(e,t){return e>=t},"==":function(e,t){return e===t},"!=":function(e,t){return e!==t},"&&":function(e,t){return e&&t},"||":function(e,t){return e||t},"?:":function(e,t,r){if(e)throw t;return r}};function o(e,t){var r,o,i,a,s,l,c=[];for(r=0;r{"use strict";r.d(t,{A:()=>o});var n=r(65);function o(e){var t=(0,n.A)(e);return function(e){return+t({n:e})}}},8793:(e,t,r)=>{"use strict";var n,o,i,a;function s(e){for(var t,r,s,l,c=[],u=[];t=e.match(a);){for(r=t[0],(s=e.substr(0,t.index).trim())&&c.push(s);l=u.pop();){if(i[r]){if(i[r][0]===l){r=i[r][1]||r;break}}else if(o.indexOf(l)>=0||n[l]s}),n={"(":9,"!":8,"*":7,"/":7,"%":7,"+":6,"-":6,"<":5,"<=":5,">":5,">=":5,"==":4,"!=":4,"&&":3,"||":2,"?":1,"?:":1},o=["(","?"],i={")":["("],":":["?","?:"]},a=/<=|>=|==|!=|&&|\|\||\?:|\(|!|\*|\/|%|\+|-|<|>|\?|\)|:/},7521:(e,t,r)=>{"use strict";r.d(t,{A:()=>i});var n=r(6956),o=r(7395);const i=function(e,t){return function(r,i,a){var s=arguments.length>3&&void 0!==arguments[3]?arguments[3]:10,l=e[t];if((0,o.A)(r)&&(0,n.A)(i))if("function"==typeof a)if("number"==typeof s){var c={callback:a,priority:s,namespace:i};if(l[r]){var u,d=l[r].handlers;for(u=d.length;u>0&&!(s>=d[u-1].priority);u--);u===d.length?d[u]=c:d.splice(u,0,c),l.__current.forEach(function(e){e.name===r&&e.currentIndex>=u&&e.currentIndex++})}else l[r]={handlers:[c],runs:0};"hookAdded"!==r&&e.doAction("hookAdded",r,i,a,s)}else console.error("If specified, the hook priority must be a number.");else console.error("The hook callback must be a function.")}}},11:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});const n=function(e,t){return function(){var r,n,o=e[t];return null!==(r=null===(n=o.__current[o.__current.length-1])||void 0===n?void 0:n.name)&&void 0!==r?r:null}}},5375:(e,t,r)=>{"use strict";r.d(t,{A:()=>o});var n=r(7395);const o=function(e,t){return function(r){var o=e[t];if((0,n.A)(r))return o[r]&&o[r].runs?o[r].runs:0}}},3561:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});const n=function(e,t){return function(r){var n=e[t];return void 0===r?void 0!==n.__current[0]:!!n.__current[0]&&r===n.__current[0].name}}},8830:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});const n=function(e,t){return function(r,n){var o=e[t];return void 0!==n?r in o&&o[r].handlers.some(function(e){return e.namespace===n}):r in o}}},7765:(e,t,r)=>{"use strict";r.d(t,{A:()=>f});var n=r(3029),o=r(7521),i=r(4194),a=r(8830),s=r(6763),l=r(11),c=r(3561),u=r(5375),d=function e(){(0,n.A)(this,e),this.actions=Object.create(null),this.actions.__current=[],this.filters=Object.create(null),this.filters.__current=[],this.addAction=(0,o.A)(this,"actions"),this.addFilter=(0,o.A)(this,"filters"),this.removeAction=(0,i.A)(this,"actions"),this.removeFilter=(0,i.A)(this,"filters"),this.hasAction=(0,a.A)(this,"actions"),this.hasFilter=(0,a.A)(this,"filters"),this.removeAllActions=(0,i.A)(this,"actions",!0),this.removeAllFilters=(0,i.A)(this,"filters",!0),this.doAction=(0,s.A)(this,"actions"),this.applyFilters=(0,s.A)(this,"filters",!0),this.currentAction=(0,l.A)(this,"actions"),this.currentFilter=(0,l.A)(this,"filters"),this.doingAction=(0,c.A)(this,"actions"),this.doingFilter=(0,c.A)(this,"filters"),this.didAction=(0,u.A)(this,"actions"),this.didFilter=(0,u.A)(this,"filters")};const f=function(){return new d}},4194:(e,t,r)=>{"use strict";r.d(t,{A:()=>i});var n=r(6956),o=r(7395);const i=function(e,t){var r=arguments.length>2&&void 0!==arguments[2]&&arguments[2];return function(i,a){var s=e[t];if((0,o.A)(i)&&(r||(0,n.A)(a))){if(!s[i])return 0;var l=0;if(r)l=s[i].handlers.length,s[i]={runs:s[i].runs,handlers:[]};else for(var c=s[i].handlers,u=function(e){c[e].namespace===a&&(c.splice(e,1),l++,s.__current.forEach(function(t){t.name===i&&t.currentIndex>=e&&t.currentIndex--}))},d=c.length-1;d>=0;d--)u(d);return"hookRemoved"!==i&&e.doAction("hookRemoved",i,a),l}}}},6763:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});const n=function(e,t){var r=arguments.length>2&&void 0!==arguments[2]&&arguments[2];return function(n){var o=e[t];o[n]||(o[n]={handlers:[],runs:0}),o[n].runs++;for(var i=o[n].handlers,a=arguments.length,s=new Array(a>1?a-1:0),l=1;l{"use strict";r.d(t,{se:()=>n});var n=(0,r(7765).A)();n.addAction,n.addFilter,n.removeAction,n.removeFilter,n.hasAction,n.hasFilter,n.removeAllActions,n.removeAllFilters,n.doAction,n.applyFilters,n.currentAction,n.currentFilter,n.doingAction,n.doingFilter,n.didAction,n.didFilter,n.actions,n.filters},7395:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});const n=function(e){return"string"!=typeof e||""===e?(console.error("The hook name must be a non-empty string."),!1):/^__/.test(e)?(console.error("The hook name cannot begin with `__`."),!1):!!/^[a-zA-Z][a-zA-Z0-9_.-]*$/.test(e)||(console.error("The hook name can only contain numbers, letters, dashes, periods and underscores."),!1)}},6956:(e,t,r)=>{"use strict";r.d(t,{A:()=>n});const n=function(e){return"string"!=typeof e||""===e?(console.error("The namespace must be a non-empty string."),!1):!!/^[a-zA-Z][a-zA-Z0-9_.\-\/]*$/.test(e)||(console.error("The namespace can only contain numbers, letters, dashes, periods, underscores and slashes."),!1)}},772:(e,t,r)=>{"use strict";r.d(t,{h:()=>c});var n=r(4467),o=r(5397);function i(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function a(e){for(var t=1;t1&&void 0!==arguments[1]?arguments[1]:"default";n.data[t]=a(a(a({},s),n.data[t]),e),n.data[t][""]=a(a({},s[""]),n.data[t][""])},d=function(e,t){u(e,t),c()},f=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"default",t=arguments.length>1?arguments[1]:void 0,r=arguments.length>2?arguments[2]:void 0,o=arguments.length>3?arguments[3]:void 0,i=arguments.length>4?arguments[4]:void 0;return n.data[e]||u(void 0,e),n.dcnpgettext(e,t,r,o,i)},p=function(){return arguments.length>0&&void 0!==arguments[0]?arguments[0]:"default"},m=function(e,t,n){var o=f(n,t,e);return r?(o=r.applyFilters("i18n.gettext_with_context",o,e,t,n),r.applyFilters("i18n.gettext_with_context_"+p(n),o,e,t,n)):o};if(e&&d(e,t),r){var v=function(e){l.test(e)&&c()};r.addAction("hookAdded","core/i18n",v),r.addAction("hookRemoved","core/i18n",v)}return{getLocaleData:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"default";return n.data[e]},setLocaleData:d,resetLocaleData:function(e,t){n.data={},n.pluralForms={},d(e,t)},subscribe:function(e){return i.add(e),function(){return i.delete(e)}},__:function(e,t){var n=f(t,void 0,e);return r?(n=r.applyFilters("i18n.gettext",n,e,t),r.applyFilters("i18n.gettext_"+p(t),n,e,t)):n},_x:m,_n:function(e,t,n,o){var i=f(o,void 0,e,t,n);return r?(i=r.applyFilters("i18n.ngettext",i,e,t,n,o),r.applyFilters("i18n.ngettext_"+p(o),i,e,t,n,o)):i},_nx:function(e,t,n,o,i){var a=f(i,o,e,t,n);return r?(a=r.applyFilters("i18n.ngettext_with_context",a,e,t,n,o,i),r.applyFilters("i18n.ngettext_with_context_"+p(i),a,e,t,n,o,i)):a},isRTL:function(){return"rtl"===m("ltr","text direction")},hasTranslation:function(e,t,o){var i,a,s=t?t+""+e:e,l=!(null===(i=n.data)||void 0===i||null===(a=i[null!=o?o:"default"])||void 0===a||!a[s]);return r&&(l=r.applyFilters("i18n.has_translation",l,e,t,o),l=r.applyFilters("i18n.has_translation_"+p(o),l,e,t,o)),l}}}},5839:(e,t,r)=>{"use strict";r.d(t,{__:()=>a});var n=r(772),o=r(2133),i=(0,n.h)(void 0,void 0,o.se),a=(i.getLocaleData.bind(i),i.setLocaleData.bind(i),i.resetLocaleData.bind(i),i.subscribe.bind(i),i.__.bind(i));i._x.bind(i),i._n.bind(i),i._nx.bind(i),i.isRTL.bind(i),i.hasTranslation.bind(i)},9575:(e,t,r)=>{"use strict";r.d(t,{__:()=>n.__}),r(181),r(772);var n=r(5839)},181:(e,t,r)=>{"use strict";var n=r(8616),o=r.n(n);r(7604),o()(console.error)},1105:(e,t,r)=>{"use strict";r.r(t),r.d(t,{addonError:()=>u,afterAddonInstall:()=>c,extractErrorFromAddOnResponse:()=>l,toggleAddonState:()=>s});var n=r(9575),o=frmDom,i=o.div,a=o.svg;function s(e,t){var r,n=null!==(r=window.ajaxurl)&&void 0!==r?r:frm_js.ajax_url;jQuery(".frm-addon-error").remove();var o=jQuery(e),i=o.attr("rel"),a=o.parent(),s=a.parent().find(".addon-status-label");o.addClass("frm_loading_button"),jQuery.ajax({url:n,type:"POST",async:!0,cache:!1,dataType:"json",data:{action:t,nonce:frmGlobal.nonce,plugin:i},success:function(e){var r,n,i;"string"!=typeof(e=null!==(r=null===(n=e)||void 0===n?void 0:n.data)&&void 0!==r?r:e)&&"string"==typeof e.message&&(void 0!==e.saveAndReload&&(i=e.saveAndReload),e=e.message);var d=l(e);d?u(d,a,o):(c(e,o,s,a,i,t),wp.hooks.doAction("frm_update_addon_state",e))},error:function(){o.removeClass("frm_loading_button")}})}function l(e){return"string"!=typeof e&&!e.success&&(e.form&&jQuery(e.form).is("#message")?{message:jQuery(e.form).find("p").html()}:e)}function c(e,t,r,o,s){var l=arguments.length>5&&void 0!==arguments[5]?arguments[5]:"frm_activate_addon",c=frm_admin_js,u=document.querySelectorAll(".frm-addon-status");u.forEach(function(t){t.textContent=e,t.style.display="block"}),t.css({opacity:"0"}),document.querySelectorAll(".frm-oneclick").forEach(function(e){e.style.display="none"}),function(){var e=document.getElementById("frm_upgrade_modal");if(e){e.classList.add("frm-success");var t=e.querySelector(".frm-upgrade-message");if(t){var r=t.querySelector("img");t.replaceChildren((0,n.__)("Great! Everything's ready to go!","formidable"),document.createElement("br"),(0,n.__)("You just need to refresh the builder so the new field becomes available.","formidable")),r&&t.append(r)}var o=document.querySelector(".frm-addon-status");o&&(o.textContent="");var i,s=e.querySelector(".frm-circled-icon");if(s)s.classList.add("frm-circled-icon-green"),null===(i=s.querySelector("svg"))||void 0===i||i.replaceWith(a({href:"#frm_checkmark_icon"}))}}();var f={frm_activate_addon:{class:"frm-addon-active",message:c.active},frm_deactivate_addon:{class:"frm-addon-installed",message:c.installed},frm_uninstall_addon:{class:"frm-addon-not-installed",message:c.not_installed}};f.frm_install_addon=f.frm_activate_addon;var p=r[0];p&&(p.textContent=f[l].message);var m=o[0].parentElement;m.classList.remove("frm-addon-not-installed","frm-addon-installed","frm-addon-active"),m.classList.add(f[l].class),t[0].classList.remove("frm_loading_button"),document.querySelectorAll(".frm-admin-page-import, #frm-admin-smtp, #frm-welcome").length>0?window.location.reload():["settings","form_builder"].includes(s)&&u.forEach(function(e){var t=null!==e.closest("#frm_upgrade_modal");e.append(function(e,t){var r,o=[d(e)];return t&&o.push(((r=document.createElement("a")).setAttribute("href","#"),r.classList.add("button","button-secondary","frm-button-secondary","dismiss"),r.textContent=(0,n.__)("Not Now","formidable"),r)),i({className:"frm-save-and-reload-options",children:o})}(s,t))})}function u(e,t,r){e.form?(jQuery(".frm-inline-error").remove(),r.closest(".frm-card").html(e.form).css({padding:5}).find("#upgrade").attr("rel",r.attr("rel")).on("click",f)):(t.append('

    '+e.message+"

    "),r.removeClass("frm_loading_button"),jQuery(".frm-addon-error").delay(4e3).fadeOut())}function d(e){var t=document.createElement("button");return t.classList.add("frm-save-and-reload","button","button-primary","frm-button-primary"),t.textContent=(0,n.__)("Save and Reload","formidable"),t.addEventListener("click",function(){var t;"form_builder"===e?((t=document.getElementById("frm_submit_side_top")).classList.contains("frm_submit_ajax")&&t.setAttribute("data-new-addon-installed",!0),t.click()):"settings"===e&&function(){var e=document.getElementById("form_settings_page");if(null!==e){var t=e.querySelector("form.frm_form_settings");null!==t&&(wp.hooks.doAction("frm_reset_fields_updated"),t.submit())}}()}),t}function f(e){e.preventDefault();var t=jQuery(this),r=t.parent().parent(),n=t.attr("rel");t.addClass("frm_loading_button"),jQuery.ajax({url:ajaxurl,type:"POST",async:!0,cache:!1,dataType:"json",data:{action:"frm_install_addon",nonce:frmAdminJs.nonce,plugin:n,hostname:r.find("#hostname").val(),username:r.find("#username").val(),password:r.find("#password").val()},success:function(e){var n,o,i=l(e=null!==(n=null===(o=e)||void 0===o?void 0:o.data)&&void 0!==n?n:e);i?u(i,r,t):c(e,t,message,r)},error:function(){t.removeClass("frm_loading_button")}})}},4260:(e,t,r)=>{"use strict";r.r(t),r.d(t,{addOneClick:()=>i,initModal:()=>a,initUpgradeModal:()=>s});var n=r(9575),o=frmDom.svg;function i(e,t,r){var i;if("modal"===t)i=document.getElementById("frm_upgrade_modal");else{if("tab"!==t)return;i=document.getElementById(e.getAttribute("href").substr(1))}var a,s=i.querySelector(".frm-oneclick"),l=i.querySelector(".frm-upgrade-message"),c=i.querySelector(".frm-upgrade-link"),u=i.querySelector(".frm-oneclick-button"),d=i.querySelector(".frm-addon-status"),f=e.getAttribute("data-oneclick"),p=e.getAttribute("data-message"),m="block",v="block",h="none",g=i.querySelector(".frm-circled-icon");g&&(g.classList.remove("frm-circled-icon-green"),null===(a=g.querySelector("svg"))||void 0===a||a.replaceWith(o({href:"#frm_filled_lock_icon"})));var y=i.querySelector(".frm-learn-more");if(y&&(y.href=e.dataset.learnMore),null!==s&&null!==u&&void 0!==f&&f){null===p&&(v="none"),m="none",h="block",f=JSON.parse(f),u.className=u.className.replace(" frm-install-addon","").replace(" frm-activate-addon",""),u.className=u.className+" "+f.class,u.rel=f.url,s.textContent=(0,n.__)("This plugin is not activated. Would you like to activate it now?","formidable"),u.textContent=(0,n.__)("Activate","formidable");var _=e.querySelector("use");_&&(null==g||g.querySelector("svg").replaceWith(o({href:_.getAttribute("href")||_.getAttribute("xlink:href"),classList:["frm_svg32"]})))}p||(p=l.getAttribute("data-default")),void 0!==r&&(p=p.replace('',r)),l.innerHTML=p,e.dataset.upsellImage&&l.append(frmDom.img({src:e.dataset.upsellImage,alt:e.dataset.upgrade})),c.href=function(e,t){var r=e.getAttribute("data-link");return null!=r&&""!==r||(r=t.getAttribute("data-default")),r}(e,c),d.style.display="none",s&&(s.style.display=h),u&&(u.style.display="block"===h?"inline-block":h),l.style.display=v,c.style.display="block"===m?"inline-block":m;var b=c.closest(".frm-upgrade-modal-actions");b&&(b.style.display="block"===m?"flex":m)}function a(e,t){var r=jQuery(e);if(!r.length)return!1;void 0===t&&(t="552px");var n={dialogClass:"frm-dialog",modal:!0,autoOpen:!1,closeOnEscape:!0,width:t,resizable:!1,draggable:!1,open:function(){var e,t;jQuery(".ui-dialog-titlebar").addClass("frm_hidden").removeClass("ui-helper-clearfix"),jQuery("#wpwrap").addClass("frm_overlay"),jQuery(".frm-dialog").removeClass("ui-widget ui-widget-content ui-corner-all"),r.removeClass("ui-dialog-content ui-widget-content"),e=r,t=function(){e.dialog("close")},jQuery(".ui-widget-overlay").on("click",t),e.on("click","a.dismiss",t)},close:function(){jQuery("#wpwrap").removeClass("frm_overlay"),jQuery(".spinner").css("visibility","hidden"),this.removeAttribute("data-option-type");var e=document.getElementById("bulk-option-type");e&&(e.value="")}};return r.dialog(n),r}function s(){var e=a("#frm_upgrade_modal");function t(t){var r,n,o;if((r=t.target).classList){var a=r.classList.contains("frm_show_expired_modal")||null!==r.querySelector(".frm_show_expired_modal")||r.closest(".frm_show_expired_modal");if("change"===t.type&&r.classList.contains("frm_select_with_upgrade")){var s=r.options[r.selectedIndex];s&&s.dataset.upgrade&&(r=s)}if(!r.dataset.upgrade){var l=r.closest("[data-upgrade]");if(!l){if(!(l=r.closest(".frm_field_box")))return;r.dataset.upgrade=""}r=l}if(a)wp.hooks.doAction("frm_show_expired_modal",r);else{var c=r.dataset.upgrade;if(c&&!r.classList.contains("frm_show_upgrade_tab")){t.preventDefault();var u=e.get(0),d=u.querySelector(".frm_lock_icon");d&&(d.style.display="block",d.classList.remove("frm_lock_open_icon"),d.querySelector("use").setAttribute("href","#frm_lock_icon"));var f="frm_upgrade_modal_image",p=document.getElementById(f);p&&p.remove(),r.dataset.image&&d&&(d.style.display="none",d.parentNode.insertBefore(frmDom.img({id:f,src:frmGlobal.url+"/images/"+r.dataset.image}),d));var m=u.querySelector(".license-level");m&&(m.textContent=function(e){return e.dataset.requires?e.dataset.requires:"Pro"}(r)),i(r,"modal",c),u.querySelector(".frm_are_not_installed").style.display=r.dataset.image||r.dataset.oneclick?"none":"inline-block",u.querySelector(".frm-upgrade-modal-title-prefix").style.display=r.dataset.oneclick?"inline":"none",u.querySelector(".frm_feature_label").textContent=c,u.querySelector(".frm-upgrade-modal-title-suffix").style.display="none",u.querySelector("h2").style.display="block",e.dialog("open");var v=u.querySelector(".button-primary:not(.frm-oneclick-button)");n=v.getAttribute("href").replace(/(medium=)[a-z_-]+/gi,"$1"+r.getAttribute("data-medium")),null===(o=r.getAttribute("data-content"))&&(o=""),n=n.replace(/(content=)[a-z_-]+/gi,"$1"+o),v.setAttribute("href",n)}}}}!1!==e&&(document.addEventListener("click",t),frmDom.util.documentOn("change","select.frm_select_with_upgrade",t))}},8616:e=>{e.exports=function(e,t){var r,n,o=0;function i(){var i,a,s=r,l=arguments.length;e:for(;s;){if(s.args.length===arguments.length){for(a=0;a{var n;!function(){"use strict";var o={not_string:/[^s]/,not_bool:/[^t]/,not_type:/[^T]/,not_primitive:/[^v]/,number:/[diefg]/,numeric_arg:/[bcdiefguxX]/,json:/[j]/,not_json:/[^j]/,text:/^[^\x25]+/,modulo:/^\x25{2}/,placeholder:/^\x25(?:([1-9]\d*)\$|\(([^)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-gijostTuvxX])/,key:/^([a-z_][a-z_\d]*)/i,key_access:/^\.([a-z_][a-z_\d]*)/i,index_access:/^\[(\d+)\]/,sign:/^[+-]/};function i(e){return function(e,t){var r,n,a,s,l,c,u,d,f,p=1,m=e.length,v="";for(n=0;n=0),s.type){case"b":r=parseInt(r,10).toString(2);break;case"c":r=String.fromCharCode(parseInt(r,10));break;case"d":case"i":r=parseInt(r,10);break;case"j":r=JSON.stringify(r,null,s.width?parseInt(s.width):0);break;case"e":r=s.precision?parseFloat(r).toExponential(s.precision):parseFloat(r).toExponential();break;case"f":r=s.precision?parseFloat(r).toFixed(s.precision):parseFloat(r);break;case"g":r=s.precision?String(Number(r.toPrecision(s.precision))):parseFloat(r);break;case"o":r=(parseInt(r,10)>>>0).toString(8);break;case"s":r=String(r),r=s.precision?r.substring(0,s.precision):r;break;case"t":r=String(!!r),r=s.precision?r.substring(0,s.precision):r;break;case"T":r=Object.prototype.toString.call(r).slice(8,-1).toLowerCase(),r=s.precision?r.substring(0,s.precision):r;break;case"u":r=parseInt(r,10)>>>0;break;case"v":r=r.valueOf(),r=s.precision?r.substring(0,s.precision):r;break;case"x":r=(parseInt(r,10)>>>0).toString(16);break;case"X":r=(parseInt(r,10)>>>0).toString(16).toUpperCase()}o.json.test(s.type)?v+=r:(!o.number.test(s.type)||d&&!s.sign?f="":(f=d?"+":"-",r=r.toString().replace(o.sign,"")),c=s.pad_char?"0"===s.pad_char?"0":s.pad_char.charAt(1):" ",u=s.width-(f+r).length,l=s.width&&u>0?c.repeat(u):"",v+=s.align?f+r+l:"0"===c?f+l+r:l+f+r)}return v}(function(e){if(s[e])return s[e];for(var t,r=e,n=[],i=0;r;){if(null!==(t=o.text.exec(r)))n.push(t[0]);else if(null!==(t=o.modulo.exec(r)))n.push("%");else{if(null===(t=o.placeholder.exec(r)))throw new SyntaxError("[sprintf] unexpected placeholder");if(t[2]){i|=1;var a=[],l=t[2],c=[];if(null===(c=o.key.exec(l)))throw new SyntaxError("[sprintf] failed to parse named argument key");for(a.push(c[1]);""!==(l=l.substring(c[0].length));)if(null!==(c=o.key_access.exec(l)))a.push(c[1]);else{if(null===(c=o.index_access.exec(l)))throw new SyntaxError("[sprintf] failed to parse named argument key");a.push(c[1])}t[2]=a}else i|=2;if(3===i)throw new Error("[sprintf] mixing positional and named placeholders is not (yet) supported");n.push({placeholder:t[0],param_no:t[1],keys:t[2],sign:t[3],pad_char:t[4],align:t[5],width:t[6],precision:t[7],type:t[8]})}r=r.substring(t[0].length)}return s[e]=n}(e),arguments)}function a(e,t){return i.apply(null,[e].concat(t||[]))}var s=Object.create(null);"undefined"!=typeof window&&(window.sprintf=i,window.vsprintf=a,void 0===(n=function(){return{sprintf:i,vsprintf:a}}.call(t,r,t,e))||(e.exports=n))}()},5397:(e,t,r)=>{"use strict";r.d(t,{A:()=>i});var n=r(1364),o={contextDelimiter:"",onMissingKey:null};function i(e,t){var r;for(r in this.data=e,this.pluralForms={},this.options={},o)this.options[r]=void 0!==t&&r in t?t[r]:o[r]}i.prototype.getPluralForm=function(e,t){var r,o,i,a=this.pluralForms[e];return a||("function"!=typeof(i=(r=this.data[e][""])["Plural-Forms"]||r["plural-forms"]||r.plural_forms)&&(o=function(e){var t,r,n;for(t=e.split(";"),r=0;r{"use strict";function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}r.d(t,{A:()=>n})},4467:(e,t,r)=>{"use strict";r.d(t,{A:()=>o});var n=r(9922);function o(e,t,r){return(t=(0,n.A)(t))in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}},2327:(e,t,r)=>{"use strict";r.d(t,{A:()=>o});var n=r(2284);function o(e,t){if("object"!=(0,n.A)(e)||!e)return e;var r=e[Symbol.toPrimitive];if(void 0!==r){var o=r.call(e,t||"default");if("object"!=(0,n.A)(o))return o;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===t?String:Number)(e)}},9922:(e,t,r)=>{"use strict";r.d(t,{A:()=>i});var n=r(2284),o=r(2327);function i(e){var t=(0,o.A)(e,"string");return"symbol"==(0,n.A)(t)?t:t+""}},2284:(e,t,r)=>{"use strict";function n(e){return n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},n(e)}r.d(t,{A:()=>n})}},t={};function r(n){var o=t[n];if(void 0!==o)return o.exports;var i=t[n]={exports:{}};return e[n](i,i.exports,r),i.exports}function n(){var e,t,r="function"==typeof Symbol?Symbol:{},i=r.iterator||"@@iterator",a=r.toStringTag||"@@toStringTag";function s(r,n,i,a){var s=n&&n.prototype instanceof c?n:c,u=Object.create(s.prototype);return o(u,"_invoke",function(r,n,o){var i,a,s,c=0,u=o||[],d=!1,f={p:0,n:0,v:e,a:p,f:p.bind(e,4),d:function(t,r){return i=t,a=0,s=e,f.n=r,l}};function p(r,n){for(a=r,s=n,t=0;!d&&c&&!o&&t3?(o=m===n)&&(s=i[(a=i[4])?5:(a=3,3)],i[4]=i[5]=e):i[0]<=p&&((o=r<2&&pn||n>m)&&(i[4]=r,i[5]=n,f.n=m,a=0))}if(o||r>1)return l;throw d=!0,n}return function(o,u,m){if(c>1)throw TypeError("Generator is already running");for(d&&1===u&&p(u,m),a=u,s=m;(t=a<2?e:s)||!d;){i||(a?a<3?(a>1&&(f.n=-1),p(a,s)):f.n=s:f.v=s);try{if(c=2,i){if(a||(o="next"),t=i[o]){if(!(t=t.call(i,s)))throw TypeError("iterator result is not an object");if(!t.done)return t;s=t.value,a<2&&(a=0)}else 1===a&&(t=i.return)&&t.call(i),a<2&&(s=TypeError("The iterator does not provide a '"+o+"' method"),a=1);i=e}else if((t=(d=f.n<0)?s:r.call(n,f))!==l)break}catch(t){i=e,a=1,s=t}finally{c=1}}return{value:t,done:d}}}(r,i,a),!0),u}var l={};function c(){}function u(){}function d(){}t=Object.getPrototypeOf;var f=[][i]?t(t([][i]())):(o(t={},i,function(){return this}),t),p=d.prototype=c.prototype=Object.create(f);function m(e){return Object.setPrototypeOf?Object.setPrototypeOf(e,d):(e.__proto__=d,o(e,a,"GeneratorFunction")),e.prototype=Object.create(p),e}return u.prototype=d,o(p,"constructor",d),o(d,"constructor",u),u.displayName="GeneratorFunction",o(d,a,"GeneratorFunction"),o(p),o(p,a,"Generator"),o(p,i,function(){return this}),o(p,"toString",function(){return"[object Generator]"}),(n=function(){return{w:s,m}})()}function o(e,t,r,n){var i=Object.defineProperty;try{i({},"",{})}catch(e){i=0}o=function(e,t,r,n){function a(t,r){o(e,t,function(e){return this._invoke(t,r,e)})}t?i?i(e,t,{value:r,enumerable:!n,configurable:!n,writable:!n}):e[t]=r:(a("next",0),a("throw",1),a("return",2))},o(e,t,r,n)}function i(e,t,r,n,o,i,a){try{var s=e[i](a),l=s.value}catch(e){return void r(e)}s.done?t(l):Promise.resolve(l).then(n,o)}r.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return r.d(t,{a:t}),t},r.d=(e,t)=>{for(var n in t)r.o(t,n)&&!r.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},function(){var e=r(1105).toggleAddonState;function t(){jQuery(document).on("click","#frm_upgrade_modal .frm-install-addon",function(t){t.preventDefault(),e(this,"frm_install_addon")}),jQuery(document).on("click","#frm_upgrade_modal .frm-activate-addon",function(t){t.preventDefault(),e(this,"frm_activate_addon")}),function(){o.apply(this,arguments)}(),a(),jQuery(document).on("mouseenter.frm",".frm_help",function(){jQuery(this).off("mouseenter.frm"),jQuery(this).tooltip("show")})}function o(){var e;return e=n().m(function e(){return n().w(function(e){for(;;)switch(e.n){case 0:return e.n=1,Promise.resolve().then(r.bind(r,4260));case 1:e.v.initUpgradeModal();case 2:return e.a(2)}},e)}),o=function(){var t=this,r=arguments;return new Promise(function(n,o){var a=e.apply(t,r);function s(e){i(a,n,o,s,l,"next",e)}function l(e){i(a,n,o,s,l,"throw",e)}s(void 0)})},o.apply(this,arguments)}function a(){var e=window.frmDom;e.bootstrap.setupBootstrapDropdowns(function(){var e=document.querySelector("#frm_testmode_enabled_form_actions .dropdown-toggle");e&&(e.classList.add("frm-dropdown-toggle"),e.hasAttribute("role")||e.setAttribute("role","button"),e.hasAttribute("tabindex")||e.setAttribute("tabindex",0))});var t=document.getElementById("frm_testmode_enabled_form_actions");t&&(t.style.display="none",e.bootstrap.multiselect.init.bind(t)(),t.disabled&&t.parentElement.querySelector(".dropdown-toggle").classList.add("frm_noallow"))}"complete"===document.readyState?t():document.addEventListener("DOMContentLoaded",t),document.addEventListener("frm_after_start_over",function(){a()}),jQuery(document).on("frmPageChanged frmFormComplete",function(){a()})}()})(); \ No newline at end of file diff --git a/stripe/js/frmstrp.min.js b/stripe/js/frmstrp.min.js index 8bfbc839b2..0ad56dfcc2 100644 --- a/stripe/js/frmstrp.min.js +++ b/stripe/js/frmstrp.min.js @@ -3,29 +3,29 @@ function shouldProcessForm(){if(formID!=frm_stripe_vars.form_id)return false;if( return window.frmProForm.currentActionTypeShouldBeProcessed(action,{thisForm})}function processForm(){const $form=jQuery(thisForm);$form.addClass("frm_js_validate");if(!validateFormSubmit($form))return;frmFrontForm.showSubmitLoading($form);const meta=addName($form);if("object"===typeof window.frmProForm&&"function"===typeof window.frmProForm.addAddressMeta)window.frmProForm.addAddressMeta($form,meta);if(!isStripeLink)return;stripeLinkSubmit($form.get(0),meta)}function stripeLinkSubmit(object,meta){object.classList.add("frm_trigger_event_on_submit", "frm_ajax_submit");object.addEventListener("frmSubmitEvent",confirmPayment);running=0;submitForm();function confirmPayment(event){if(!checkEventDataForError(event))return;window.onpageshow=function(event){if(event.persisted||window.performance&&window.performance.getEntriesByType("navigation")[0].type==="back_forward")window.location.reload()};let params={elements,confirmParams:{return_url:getReturnUrl()}};if("object"===typeof window.frmProForm&&"function"===typeof frmProForm.beforeConfirmPayment)params= frmProForm.beforeConfirmPayment(params,meta);const confirmFunction=isRecurring()?"confirmSetup":"confirmPayment";frmstripe[confirmFunction](params).then(handleConfirmPromise)}function getReturnUrl(){const url=new URL(frm_stripe_vars.ajax);url.searchParams.append("action","frmstrplinkreturn");return url.toString()}function handleConfirmPromise(result){if(result.error)handleConfirmPaymentError(result.error)}function handleConfirmPaymentError(error){running--;enableSubmit();const fieldset=jQuery(object).find(".frm_form_field"); -fieldset.removeClass("frm_doing_ajax");object.classList.remove("frm_loading_form");if("card_error"===error.type||"invalid_request_error"===error.type||"form_submit_error"===error.type){const cardErrors=object.querySelector(".frm-card-errors");if(cardErrors)cardErrors.textContent=error.message}}function checkEventDataForError(event){if(!event.frmData||!event.frmData.content.length||-1===event.frmData.content.indexOf('
    0)return;frmFrontForm.submitFormManual(event,thisForm)}function enableSubmit(){if(running>0)return;thisForm.classList.add("frm_loading_form");frmFrontForm.removeSubmitLoading(jQuery(thisForm),"enable",0);triggerCustomEvent(document,"frmStripeLiteEnableSubmit",{form:thisForm})}function getPriceFields(){const priceFields=[];function checkStripeSettingForPriceFields(setting){if(-1!==setting.fields)each(setting.fields,addFieldDataToPriceFieldsArray)} -function addFieldDataToPriceFieldsArray(field){if(isNaN(field))priceFields.push("field_"+field);else priceFields.push(field)}each(getStripeSettings(),checkStripeSettingForPriceFields);return priceFields}function getStripeSettings(){const stripeSettings=[];each(frm_stripe_vars.settings,function(setting){if(-1!==setting.gateways.indexOf("stripe"))stripeSettings.push(setting)});return stripeSettings}function priceChanged(_,field,fieldId){let i;let data;const price=getPriceFields();let run=price.includes(fieldId)|| -price.includes(field.id);if(!run)for(i=0;i3&&xmlHttp.status==200){response=xmlHttp.responseText;if(response!=="")try{response=JSON.parse(response)}catch(error){response=""}success(response)}};xmlHttp.setRequestHeader("X-Requested-With","XMLHttpRequest");xmlHttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");xmlHttp.send(params);return xmlHttp}function loadElements(){if(document.getElementsByClassName("frm-card-element").length)maybeLoadStripeLink()} +function addFieldDataToPriceFieldsArray(field){if(isNaN(field))priceFields.push("field_"+field);else priceFields.push(field)}each(getStripeSettings(),checkStripeSettingForPriceFields);return priceFields}function getStripeSettings(){const stripeSettings=[];each(frm_stripe_vars.settings,function(setting){if(setting.gateways.includes("stripe"))stripeSettings.push(setting)});return stripeSettings}function priceChanged(_,field,fieldId){let i;let data;const price=getPriceFields();let run=price.includes(fieldId)|| +price.includes(field.id);if(!run)for(i=0;i3&&xmlHttp.status==200){response=xmlHttp.responseText;if(response!=="")try{response=JSON.parse(response)}catch(error){response=""}success(response)}};xmlHttp.setRequestHeader("X-Requested-With","XMLHttpRequest");xmlHttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");xmlHttp.send(params);return xmlHttp}function loadElements(){if(document.getElementsByClassName("frm-card-element").length)maybeLoadStripeLink()} function maybeLoadStripeLink(){const stripeLinkForm=document.querySelector("form.frm_stripe_link_form");if(!stripeLinkForm)return false;const formId=parseInt(stripeLinkForm.querySelector('input[name="form_id"]').value);const intentField=stripeLinkForm.querySelector('input[name="frmintent'+formId+'[]"]');if(!intentField)return false;disableSubmit(stripeLinkForm);loadStripeLinkElements(intentField.value);triggerCustomEvent(document,"frmStripeLiteLoad",{form:stripeLinkForm});return true}function disableSubmit(form){jQuery(form).find('input[type="submit"],input[type="button"],button[type="submit"]').not(".frm_prev_page").attr("disabled", "disabled");triggerCustomEvent(document,"frmStripeLiteDisableSubmit",{form})}function loadStripeLinkElements(clientSecret){const cardElement=document.querySelector(".frm-card-element");if(!cardElement)return;const appearance={theme:"stripe",variables:{fontSizeBase:frm_stripe_vars.baseFontSize,colorText:maybeAdjustColorForStripe(frm_stripe_vars.appearanceRules[".Input"].color),colorBackground:maybeAdjustColorForStripe(frm_stripe_vars.appearanceRules[".Input"].backgroundColor),fontSmooth:"auto"},rules:frm_stripe_vars.appearanceRules}; elements=frmstripe.elements({clientSecret,appearance});isStripeLink=true;insertAuthenticationElement(cardElement);insertPaymentElement(cardElement);triggerCustomEvent(document,"frmStripeLiteLoadElements",{cardElement})}function maybeAdjustColorForStripe(color){if(0!==color.indexOf("rgba"))return color;const rgba=color.replace(/^rgba?\(|\s+|\)$/g,"").split(",");return`#${((1<<24)+(parseInt(rgba[0],10)<<16)+(parseInt(rgba[1],10)<<8)+parseInt(rgba[2],10)).toString(16).slice(1)}`}function insertAuthenticationElement(cardElement){let emailInput, cardFieldContainer;let addAboveCardElement=true;const emailField=checkForEmailField();const authenticationMountTarget=createMountTarget("frm-link-authentication-element");if(false!==emailField)if("hidden"===emailField.getAttribute("type"))emailInput=emailField;else{addAboveCardElement=false;emailInput=emailField.querySelector("input");replaceEmailField(emailField,emailInput,authenticationMountTarget)}if(addAboveCardElement){cardFieldContainer=cardElement.closest(".frm_form_field");cardFieldContainer.parentNode.insertBefore(authenticationMountTarget, cardFieldContainer);triggerCustomEvent(document,"frmStripeLiteAddAuthElementAboveCardElement",{cardElement,cardFieldContainer,authenticationMountTarget})}const defaultEmailValue=false!==emailField?getSettingFieldValue(emailField):"";const authenticationElement=elements.create("linkAuthentication",{defaultValues:{email:defaultEmailValue}});authenticationElement.mount(".frm-link-authentication-element");authenticationElement.on("change",getAuthenticationChangeHandler(cardElement,emailInput))}function getAuthenticationChangeHandler(cardElement, -emailInput){function syncEmailInput(emailValue){if("string"===typeof emailValue&&emailValue.length)emailInput.value=emailValue}return function(event){linkAuthenticationElementIsComplete=event.complete;if(linkAuthenticationElementIsComplete&&"undefined"!==typeof emailInput)syncEmailInput(event.value.email);const form=cardElement.closest("form");if("object"===typeof window.frmChatForm&&"function"===typeof frmChatForm.maybeHandleAuthenticationChange&&frmChatForm.maybeHandleAuthenticationChange(form, -event.complete))return;if(readyToSubmitStripeLink(form)){thisForm=form;running=0;enableSubmit()}else disableSubmit(form)}}function replaceEmailField(emailField,emailInput,authenticationMountTarget){emailInput.before(authenticationMountTarget);emailInput.type="hidden";const emailLabel=emailField.querySelector(".frm_primary_label");if(emailLabel)emailLabel.style.display="none"}function getLayout(){const settings=getStripeSettings()[0];return settings.hasOwnProperty("layout")&&settings.layout||"tabs"} -function insertPaymentElement(cardElement){cardElement.parentNode.insertBefore(createMountTarget("frm-payment-element"),cardElement);const paymentElement=elements.create("payment",{layout:{type:getLayout()},defaultValues:{billingDetails:{name:getFullNameValueDefault(),phone:""}}});paymentElement.mount(".frm-payment-element");paymentElement.on("change",handlePaymentElementChange);function handlePaymentElementChange(event){stripeLinkElementIsComplete=event.complete;toggleButtonsOnPaymentElementChange(cardElement); -triggerCustomEvent(document,"frmStripeLitePaymentElementChange",{complete:event.complete})}}function toggleButtonsOnPaymentElementChange(cardElement){const form=cardElement.closest(".frm-show-form");if("object"===typeof window.frmChatForm&&"function"===typeof frmChatForm.maybeHandlePaymentChange&&frmChatForm.maybeHandlePaymentChange(form,stripeLinkElementIsComplete))return;if(readyToSubmitStripeLink(form)){thisForm=form;running=0;enableSubmit()}else disableSubmit(form)}function readyToSubmitStripeLink(form){if(!linkAuthenticationElementIsComplete|| -!stripeLinkElementIsComplete)return false;if("object"!==typeof window.frmProForm||"function"!==typeof window.frmProForm.submitButtonIsConditionallyDisabled)return true;return!window.frmProForm.submitButtonIsConditionallyDisabled(form)}function getFullNameValueDefault(){const nameValues=[];const firstNameField=checkForStripeSettingField("first_name");if(false!==firstNameField)nameValues.push(getSettingFieldValue(firstNameField));const lastNameField=checkForStripeSettingField("last_name");if(false!== -lastNameField)nameValues.push(getSettingFieldValue(lastNameField));return nameValues.join(" ")}function getSettingFieldValue(field){let value;if("hidden"===field.getAttribute("type"))value=field.value;else value=field.querySelector("input").value;return value}function checkForEmailField(){return checkForStripeSettingField("email")}function checkForStripeSettingField(settingKey){let settingField=false;each(getStripeSettings(),checkStripeSettingForField);function checkStripeSettingForField(currentSetting){let currentFieldId, -fieldMatchByKey,fieldContainer,hiddenInput;if("string"!==typeof currentSetting[settingKey]||!currentSetting[settingKey].length)return;const currentSettingValue=currentSetting[settingKey];const settingIsWrappedAsShortcode="["===currentSettingValue[0]&&"]"===currentSettingValue[currentSettingValue.length-1];if(settingIsWrappedAsShortcode){currentFieldId=currentSettingValue.substr(1,currentSettingValue.length-2);if(isNaN(currentFieldId))fieldMatchByKey=document.getElementById("field_"+currentFieldId)}else currentFieldId= -currentSettingValue;if(fieldMatchByKey)fieldContainer=fieldMatchByKey.closest(".frm_form_field");else fieldContainer=document.getElementById("frm_field_"+currentFieldId+"_container");if(!fieldContainer){hiddenInput=document.querySelector('input[name="item_meta['+currentFieldId+']"]');if(!hiddenInput)if("first_name"===settingKey)hiddenInput=document.querySelector('input[name="item_meta['+currentFieldId+'][first]"]');else if("last_name"===settingKey)hiddenInput=document.querySelector('input[name="item_meta['+ -currentFieldId+'][last]"]');if(hiddenInput){settingField=hiddenInput;return false}return}settingField=fieldContainer;return false}return settingField}function createMountTarget(className){const newElement=document.createElement("div");newElement.className=className+" frm_form_field form-field";return newElement}function each(items,callback){let index;const length=items.length;for(index=0;index