ima-claude 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +463 -0
- package/dist/cli.js +1064 -0
- package/package.json +49 -0
- package/platforms/claude/adapter.ts +115 -0
- package/platforms/junie/adapter.ts +254 -0
- package/platforms/junie/agents-template.md +113 -0
- package/platforms/junie/hook-translations.md +84 -0
- package/platforms/shared/detector.ts +27 -0
- package/platforms/shared/installer.ts +202 -0
- package/platforms/shared/types.ts +78 -0
- package/plugins/ima-claude/.claude-plugin/plugin.json +25 -0
- package/plugins/ima-claude/agents/explorer.md +30 -0
- package/plugins/ima-claude/agents/implementer.md +30 -0
- package/plugins/ima-claude/agents/memory.md +42 -0
- package/plugins/ima-claude/agents/reviewer.md +53 -0
- package/plugins/ima-claude/agents/tester.md +33 -0
- package/plugins/ima-claude/agents/wp-developer.md +46 -0
- package/plugins/ima-claude/hooks/README.md +145 -0
- package/plugins/ima-claude/hooks/atlassian_prereqs.py +112 -0
- package/plugins/ima-claude/hooks/block_sed_edits.py +59 -0
- package/plugins/ima-claude/hooks/bootstrap.sh +90 -0
- package/plugins/ima-claude/hooks/bootstrap_utility_check.py +94 -0
- package/plugins/ima-claude/hooks/composer_autoload_check.py +70 -0
- package/plugins/ima-claude/hooks/docs_organization.py +104 -0
- package/plugins/ima-claude/hooks/enforce_rg_over_grep.py +56 -0
- package/plugins/ima-claude/hooks/fp_utility_check.py +90 -0
- package/plugins/ima-claude/hooks/hook_logger.py +69 -0
- package/plugins/ima-claude/hooks/hooks.json +239 -0
- package/plugins/ima-claude/hooks/jira_issue_fetch.py +79 -0
- package/plugins/ima-claude/hooks/jquery_in_wordpress.py +92 -0
- package/plugins/ima-claude/hooks/memory_bootstrap.py +79 -0
- package/plugins/ima-claude/hooks/memory_store_reminder.py +75 -0
- package/plugins/ima-claude/hooks/prompt_coach.py +125 -0
- package/plugins/ima-claude/hooks/prompt_coach_digest.md +48 -0
- package/plugins/ima-claude/hooks/prompt_coach_system.md +30 -0
- package/plugins/ima-claude/hooks/sequential_thinking_check.py +81 -0
- package/plugins/ima-claude/hooks/serena_over_grep.py +96 -0
- package/plugins/ima-claude/hooks/serena_over_read.py +66 -0
- package/plugins/ima-claude/hooks/serena_project_check.py +133 -0
- package/plugins/ima-claude/hooks/sql_injection_check.py +73 -0
- package/plugins/ima-claude/hooks/task_master_after_plan.py +31 -0
- package/plugins/ima-claude/hooks/task_master_before_impl.py +93 -0
- package/plugins/ima-claude/hooks/tavily_extract_advanced.py +48 -0
- package/plugins/ima-claude/hooks/vestige_before_external.py +86 -0
- package/plugins/ima-claude/hooks/webfetch_to_tavily.py +42 -0
- package/plugins/ima-claude/hooks/websearch_to_tavily.py +41 -0
- package/plugins/ima-claude/hooks/wp_security_check.py +150 -0
- package/plugins/ima-claude/personalities/README.md +45 -0
- package/plugins/ima-claude/personalities/enable-40k.md +69 -0
- package/plugins/ima-claude/personalities/enable-templars.md +69 -0
- package/plugins/ima-claude/skills/.research-summary.md +340 -0
- package/plugins/ima-claude/skills/architect/SKILL.md +304 -0
- package/plugins/ima-claude/skills/compound-bridge/SKILL.md +200 -0
- package/plugins/ima-claude/skills/discourse/SKILL.md +440 -0
- package/plugins/ima-claude/skills/discourse-admin/SKILL.md +192 -0
- package/plugins/ima-claude/skills/discourse-admin/references/api-endpoints.md +441 -0
- package/plugins/ima-claude/skills/discourse-admin/references/gotchas.md +107 -0
- package/plugins/ima-claude/skills/discourse-admin/references/staging-defaults.md +98 -0
- package/plugins/ima-claude/skills/discourse-admin/scripts/discourse-admin.py +319 -0
- package/plugins/ima-claude/skills/docs-organize/SKILL.md +254 -0
- package/plugins/ima-claude/skills/docs-organize/templates/active-README.md +50 -0
- package/plugins/ima-claude/skills/docs-organize/templates/archive-README.md +57 -0
- package/plugins/ima-claude/skills/docs-organize/templates/docs-README.md +43 -0
- package/plugins/ima-claude/skills/docs-organize/templates/phase-archive-README.md +83 -0
- package/plugins/ima-claude/skills/docs-organize/templates/section-README.md +48 -0
- package/plugins/ima-claude/skills/docs-organize/templates/transient-README.md +79 -0
- package/plugins/ima-claude/skills/docs-organize/templates/transient-gitignore +9 -0
- package/plugins/ima-claude/skills/ember-discourse/SKILL.md +496 -0
- package/plugins/ima-claude/skills/functional-programmer/SKILL.md +258 -0
- package/plugins/ima-claude/skills/ima-bootstrap/SKILL.md +278 -0
- package/plugins/ima-claude/skills/ima-bootstrap/references/bootstrap-patterns.md +356 -0
- package/plugins/ima-claude/skills/ima-bootstrap/references/ima-brand.md +273 -0
- package/plugins/ima-claude/skills/ima-bootstrap/references/theme-integration.md +212 -0
- package/plugins/ima-claude/skills/ima-brand/SKILL.md +108 -0
- package/plugins/ima-claude/skills/ima-brand/references/brand-identity.md +140 -0
- package/plugins/ima-claude/skills/ima-brand/references/digital-standards.md +180 -0
- package/plugins/ima-claude/skills/ima-brand/references/visual-system.md +173 -0
- package/plugins/ima-claude/skills/ima-forms-expert/SKILL.md +175 -0
- package/plugins/ima-claude/skills/ima-forms-expert/references/container-components.md +154 -0
- package/plugins/ima-claude/skills/ima-forms-expert/references/examples.md +328 -0
- package/plugins/ima-claude/skills/ima-forms-expert/references/field-components.md +298 -0
- package/plugins/ima-claude/skills/ima-forms-expert/references/form-factory.md +193 -0
- package/plugins/ima-claude/skills/ima-forms-expert/references/quick-reference.md +153 -0
- package/plugins/ima-claude/skills/ima-forms-expert/references/validation-engine.md +336 -0
- package/plugins/ima-claude/skills/jira-checkpoint/SKILL.md +178 -0
- package/plugins/ima-claude/skills/jquery/SKILL.md +413 -0
- package/plugins/ima-claude/skills/js-fp/SKILL.md +463 -0
- package/plugins/ima-claude/skills/js-fp/core-principles.md +487 -0
- package/plugins/ima-claude/skills/js-fp/examples/pure-functions.js +260 -0
- package/plugins/ima-claude/skills/js-fp/examples/tests/pure-functions.test.js +262 -0
- package/plugins/ima-claude/skills/js-fp/references/anti-patterns.md +120 -0
- package/plugins/ima-claude/skills/js-fp/references/performance-patterns.md +116 -0
- package/plugins/ima-claude/skills/js-fp/references/testing-patterns.md +134 -0
- package/plugins/ima-claude/skills/js-fp-api/SKILL.md +280 -0
- package/plugins/ima-claude/skills/js-fp-api/examples/crud-endpoint.js +258 -0
- package/plugins/ima-claude/skills/js-fp-api/references/middleware-patterns.md +134 -0
- package/plugins/ima-claude/skills/js-fp-api/references/security-sql.md +110 -0
- package/plugins/ima-claude/skills/js-fp-api/references/validation-patterns.md +165 -0
- package/plugins/ima-claude/skills/js-fp-react/SKILL.md +447 -0
- package/plugins/ima-claude/skills/js-fp-react/examples/ProductCard.tsx +65 -0
- package/plugins/ima-claude/skills/js-fp-react/references/hooks-advanced.md +136 -0
- package/plugins/ima-claude/skills/js-fp-react/references/performance-patterns.md +175 -0
- package/plugins/ima-claude/skills/js-fp-vue/SKILL.md +322 -0
- package/plugins/ima-claude/skills/js-fp-vue/references/complete-examples.md +397 -0
- package/plugins/ima-claude/skills/js-fp-vue/references/composables-advanced.md +282 -0
- package/plugins/ima-claude/skills/js-fp-vue/references/reactivity-patterns.md +348 -0
- package/plugins/ima-claude/skills/js-fp-vue/references/testing.md +314 -0
- package/plugins/ima-claude/skills/js-fp-wordpress/SKILL.md +301 -0
- package/plugins/ima-claude/skills/js-fp-wordpress/references/ajax-patterns.md +192 -0
- package/plugins/ima-claude/skills/js-fp-wordpress/references/event-patterns.md +136 -0
- package/plugins/ima-claude/skills/js-fp-wordpress/references/wp-integration.md +248 -0
- package/plugins/ima-claude/skills/livecanvas/SKILL.md +209 -0
- package/plugins/ima-claude/skills/livecanvas/references/livecanvas-features.md +311 -0
- package/plugins/ima-claude/skills/livecanvas/references/loops-and-logic.md +730 -0
- package/plugins/ima-claude/skills/livecanvas/references/picostrap.md +227 -0
- package/plugins/ima-claude/skills/mcp-atlassian/SKILL.md +339 -0
- package/plugins/ima-claude/skills/mcp-context7/SKILL.md +109 -0
- package/plugins/ima-claude/skills/mcp-memory/SKILL.md +182 -0
- package/plugins/ima-claude/skills/mcp-qdrant/SKILL.md +233 -0
- package/plugins/ima-claude/skills/mcp-sequential/SKILL.md +149 -0
- package/plugins/ima-claude/skills/mcp-serena/SKILL.md +174 -0
- package/plugins/ima-claude/skills/mcp-tavily/SKILL.md +118 -0
- package/plugins/ima-claude/skills/mcp-vestige/SKILL.md +259 -0
- package/plugins/ima-claude/skills/php-authnet/SKILL.md +275 -0
- package/plugins/ima-claude/skills/php-authnet/references/api-reference.md +624 -0
- package/plugins/ima-claude/skills/php-authnet/references/sandbox-testing.md +424 -0
- package/plugins/ima-claude/skills/php-fp/SKILL.md +333 -0
- package/plugins/ima-claude/skills/php-fp/examples/pure-functions.php +403 -0
- package/plugins/ima-claude/skills/php-fp/examples/tests/PureFunctionsTest.php +515 -0
- package/plugins/ima-claude/skills/php-fp/references/core-principles.md +277 -0
- package/plugins/ima-claude/skills/php-fp/references/testing-patterns.md +374 -0
- package/plugins/ima-claude/skills/php-fp-wordpress/SKILL.md +216 -0
- package/plugins/ima-claude/skills/php-fp-wordpress/references/fp-patterns.md +275 -0
- package/plugins/ima-claude/skills/php-fp-wordpress/references/plugin-architecture.md +295 -0
- package/plugins/ima-claude/skills/php-fp-wordpress/references/security-examples.md +203 -0
- package/plugins/ima-claude/skills/php-fp-wordpress/references/testing-strategy.md +259 -0
- package/plugins/ima-claude/skills/phpunit-wp/SKILL.md +716 -0
- package/plugins/ima-claude/skills/playwright/SKILL.md +434 -0
- package/plugins/ima-claude/skills/playwright/references/accessibility-testing.md +153 -0
- package/plugins/ima-claude/skills/playwright/references/ci-cd.md +268 -0
- package/plugins/ima-claude/skills/playwright/references/network-mocking.md +270 -0
- package/plugins/ima-claude/skills/playwright/references/visual-regression.md +215 -0
- package/plugins/ima-claude/skills/py-fp/SKILL.md +663 -0
- package/plugins/ima-claude/skills/py-fp/examples/pure-functions.py +185 -0
- package/plugins/ima-claude/skills/py-fp/examples/tests/test_pure_functions.py +244 -0
- package/plugins/ima-claude/skills/py-fp/references/core-principles.md +381 -0
- package/plugins/ima-claude/skills/py-fp/references/testing-patterns.md +283 -0
- package/plugins/ima-claude/skills/quasar-fp/SKILL.md +327 -0
- package/plugins/ima-claude/skills/quasar-fp/metadata.json +85 -0
- package/plugins/ima-claude/skills/quasar-fp/references/component-patterns.md +257 -0
- package/plugins/ima-claude/skills/quasar-fp/references/theme-integration.md +233 -0
- package/plugins/ima-claude/skills/quasar-fp/references/utility-classes.md +237 -0
- package/plugins/ima-claude/skills/quickstart/SKILL.md +129 -0
- package/plugins/ima-claude/skills/rails/SKILL.md +359 -0
- package/plugins/ima-claude/skills/resume-session/SKILL.md +68 -0
- package/plugins/ima-claude/skills/rg/SKILL.md +205 -0
- package/plugins/ima-claude/skills/ruby-fp/SKILL.md +336 -0
- package/plugins/ima-claude/skills/save-session/SKILL.md +81 -0
- package/plugins/ima-claude/skills/scorecard/SKILL.md +96 -0
- package/plugins/ima-claude/skills/skill-analyzer/SKILL.md +127 -0
- package/plugins/ima-claude/skills/skill-analyzer/references/advanced-checklist.md +44 -0
- package/plugins/ima-claude/skills/skill-analyzer/references/core-checklist.md +60 -0
- package/plugins/ima-claude/skills/skill-analyzer/scripts/analyze_skill.py +418 -0
- package/plugins/ima-claude/skills/skill-creator/LICENSE.txt +202 -0
- package/plugins/ima-claude/skills/skill-creator/SKILL.md +343 -0
- package/plugins/ima-claude/skills/skill-creator/references/output-patterns.md +82 -0
- package/plugins/ima-claude/skills/skill-creator/references/workflows.md +28 -0
- package/plugins/ima-claude/skills/skill-creator/scripts/init_skill.py +303 -0
- package/plugins/ima-claude/skills/skill-creator/scripts/package_skill.py +110 -0
- package/plugins/ima-claude/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/plugins/ima-claude/skills/task-master/SKILL.md +51 -0
- package/plugins/ima-claude/skills/task-planner/SKILL.md +228 -0
- package/plugins/ima-claude/skills/task-runner/SKILL.md +192 -0
- package/plugins/ima-claude/skills/unit-testing/SKILL.md +198 -0
- package/plugins/ima-claude/skills/unit-testing/references/mock-patterns.md +181 -0
- package/plugins/ima-claude/skills/unit-testing/references/tdd-workflow.md +177 -0
- package/plugins/ima-claude/skills/unit-testing/references/test-strategy.md +126 -0
- package/plugins/ima-claude/skills/wp-local/SKILL.md +246 -0
- package/plugins/ima-claude/skills/wp-local/references/configuration.md +198 -0
- package/plugins/ima-claude/skills/wp-local/references/wp-cli-reference.md +406 -0
- package/plugins/ima-claude/skills/wp-local/scripts/wp-local.sh +61 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
# Complete Form Examples
|
|
2
|
+
|
|
3
|
+
## Contents
|
|
4
|
+
|
|
5
|
+
- [Understanding Validators-at-Registration](#understanding-validators-at-registration)
|
|
6
|
+
- [Contact Form with Validation Engine](#contact-form-with-validation-engine)
|
|
7
|
+
- [Form with Custom Cross-Field Validation](#form-with-custom-cross-field-validation)
|
|
8
|
+
- [Testing Patterns](#testing-patterns)
|
|
9
|
+
- [Anti-Patterns to Avoid](#anti-patterns-to-avoid)
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Understanding Validators-at-Registration
|
|
14
|
+
|
|
15
|
+
**Key v1.4.0 Architecture**: When you call `ima_forms_email_field()`, the field's `_register()` function builds validators as closures and stores them:
|
|
16
|
+
|
|
17
|
+
```php
|
|
18
|
+
// What happens inside ima_forms_email_field_register():
|
|
19
|
+
$validators = [];
|
|
20
|
+
|
|
21
|
+
if ($required) {
|
|
22
|
+
// Validator closure captures $label via use()
|
|
23
|
+
$validators[] = fn($v) => ima_forms_validate_required($v, $label);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Email format validator (only runs if value provided)
|
|
27
|
+
$validators[] = function($v) use ($label, $enhanced) {
|
|
28
|
+
if (empty($v)) return null;
|
|
29
|
+
return ima_forms_validate_email_enhanced((string) $v, $label, $enhanced);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Spec stores the closures directly
|
|
33
|
+
ima_forms_register_field_spec($name, [
|
|
34
|
+
'type' => 'email',
|
|
35
|
+
'validators' => $validators, // Array of closures!
|
|
36
|
+
]);
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**When validation runs** (inside `ima_forms_run_validators()`):
|
|
40
|
+
```php
|
|
41
|
+
foreach ($specs as $field_name => $spec) {
|
|
42
|
+
$value = $sanitized[$field_name] ?? null;
|
|
43
|
+
|
|
44
|
+
foreach ($spec['validators'] as $validator) {
|
|
45
|
+
$error = $validator($value); // Just call the closure!
|
|
46
|
+
if ($error !== null) {
|
|
47
|
+
$errors[$field_name] = $error;
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Benefits**:
|
|
55
|
+
- No type switching - validators are already built
|
|
56
|
+
- Single source of truth - field function defines validation once
|
|
57
|
+
- Testable - validators are pure functions
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Contact Form with Validation Engine
|
|
62
|
+
|
|
63
|
+
### Template File
|
|
64
|
+
|
|
65
|
+
```php
|
|
66
|
+
// Theme: templates/forms/contact-form.php
|
|
67
|
+
<?php
|
|
68
|
+
if (!defined('ABSPATH')) exit;
|
|
69
|
+
|
|
70
|
+
$errors = $args['errors'] ?? [];
|
|
71
|
+
$data = $args['data'] ?? [];
|
|
72
|
+
?>
|
|
73
|
+
|
|
74
|
+
<?php
|
|
75
|
+
ima_forms_form([
|
|
76
|
+
'id' => 'contact-form',
|
|
77
|
+
'action' => 'contact_form_submit',
|
|
78
|
+
], function() use ($data, $errors) {
|
|
79
|
+
|
|
80
|
+
ima_forms_card(['title' => 'Contact Us'], function() use ($data, $errors) {
|
|
81
|
+
|
|
82
|
+
ima_forms_text_field([
|
|
83
|
+
'name' => 'name',
|
|
84
|
+
'label' => 'Your Name',
|
|
85
|
+
'required' => true,
|
|
86
|
+
'value' => $data['name'] ?? '',
|
|
87
|
+
'error' => $errors['name'] ?? '',
|
|
88
|
+
]);
|
|
89
|
+
|
|
90
|
+
ima_forms_email_field([
|
|
91
|
+
'name' => 'email',
|
|
92
|
+
'label' => 'Email Address',
|
|
93
|
+
'required' => true,
|
|
94
|
+
'enhanced_validation' => true,
|
|
95
|
+
'value' => $data['email'] ?? '',
|
|
96
|
+
'error' => $errors['email'] ?? '',
|
|
97
|
+
]);
|
|
98
|
+
|
|
99
|
+
ima_forms_textarea_field([
|
|
100
|
+
'name' => 'message',
|
|
101
|
+
'label' => 'Message',
|
|
102
|
+
'required' => true,
|
|
103
|
+
'rows' => 5,
|
|
104
|
+
'maxlength' => 1000,
|
|
105
|
+
'value' => $data['message'] ?? '',
|
|
106
|
+
'error' => $errors['message'] ?? '',
|
|
107
|
+
]);
|
|
108
|
+
|
|
109
|
+
ima_forms_submit_button([
|
|
110
|
+
'text' => 'Send Message',
|
|
111
|
+
'processing_text' => 'Sending...',
|
|
112
|
+
]);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
?>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### AJAX Handler
|
|
119
|
+
|
|
120
|
+
```php
|
|
121
|
+
// Theme: inc/forms/contact-form.php
|
|
122
|
+
|
|
123
|
+
function contact_form_ajax_handler() {
|
|
124
|
+
// Nonce verification
|
|
125
|
+
if (!wp_verify_nonce($_POST['nonce'], 'ima_forms_ajax')) {
|
|
126
|
+
wp_send_json_error(['message' => 'Security check failed.'], 403);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Validate using template
|
|
130
|
+
$result = ima_forms_validate_form(
|
|
131
|
+
'templates/forms/contact-form',
|
|
132
|
+
$_POST,
|
|
133
|
+
'contact-form'
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
if (!$result['valid']) {
|
|
137
|
+
wp_send_json_error([
|
|
138
|
+
'message' => 'Please correct the errors below.',
|
|
139
|
+
'errors' => $result['errors']
|
|
140
|
+
], 400);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Process validated data
|
|
144
|
+
$data = $result['sanitized'];
|
|
145
|
+
|
|
146
|
+
$sent = wp_mail(
|
|
147
|
+
get_option('admin_email'),
|
|
148
|
+
'New Contact Form Submission',
|
|
149
|
+
"Name: {$data['name']}\nEmail: {$data['email']}\n\n{$data['message']}"
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
if (!$sent) {
|
|
153
|
+
wp_send_json_error(['message' => 'Failed to send message.'], 500);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
wp_send_json_success([
|
|
157
|
+
'message' => 'Thank you! Your message has been sent.',
|
|
158
|
+
'redirect' => home_url('/thank-you/')
|
|
159
|
+
]);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
add_action('wp_ajax_contact_form_submit', 'contact_form_ajax_handler');
|
|
163
|
+
add_action('wp_ajax_nopriv_contact_form_submit', 'contact_form_ajax_handler');
|
|
164
|
+
|
|
165
|
+
// Shortcode
|
|
166
|
+
add_shortcode('contact_form', function() {
|
|
167
|
+
ob_start();
|
|
168
|
+
get_template_part('templates/forms/contact-form');
|
|
169
|
+
return ob_get_clean();
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Form with Custom Cross-Field Validation
|
|
174
|
+
|
|
175
|
+
```php
|
|
176
|
+
// Add custom validation via filter
|
|
177
|
+
add_filter('ima_forms_validate_registration-form', function($errors, $sanitized, $specs) {
|
|
178
|
+
// Cross-field validation: passwords must match
|
|
179
|
+
if ($sanitized['password'] !== $sanitized['password_confirm']) {
|
|
180
|
+
$errors['password_confirm'] = 'Passwords must match.';
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Business rule: age requirement
|
|
184
|
+
if (!empty($sanitized['age']) && $sanitized['age'] < 18) {
|
|
185
|
+
$errors['age'] = 'Must be 18 or older to register.';
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Conditional requirement
|
|
189
|
+
if ($sanitized['specialty'] === 'Other' && empty($sanitized['specialty_other'])) {
|
|
190
|
+
$errors['specialty_other'] = 'Please specify your specialty.';
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return $errors;
|
|
194
|
+
}, 10, 3);
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Testing Patterns
|
|
198
|
+
|
|
199
|
+
### Unit Tests for Validators
|
|
200
|
+
|
|
201
|
+
```php
|
|
202
|
+
<?php
|
|
203
|
+
declare(strict_types=1);
|
|
204
|
+
|
|
205
|
+
use PHPUnit\Framework\TestCase;
|
|
206
|
+
|
|
207
|
+
class ValidatorTest extends TestCase {
|
|
208
|
+
/**
|
|
209
|
+
* @dataProvider emailProvider
|
|
210
|
+
*/
|
|
211
|
+
public function test_validate_email_enhanced($email, $expected_valid, $expected_message) {
|
|
212
|
+
$result = ima_forms_validate_email_enhanced($email, 'Email', true);
|
|
213
|
+
|
|
214
|
+
if ($expected_valid) {
|
|
215
|
+
$this->assertNull($result);
|
|
216
|
+
} else {
|
|
217
|
+
$this->assertIsString($result);
|
|
218
|
+
if ($expected_message) {
|
|
219
|
+
$this->assertStringContainsString($expected_message, $result);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
public function emailProvider() {
|
|
225
|
+
return [
|
|
226
|
+
'valid email' => ['test@example.com', true, null],
|
|
227
|
+
'disposable domain' => ['test@mailinator.com', false, 'not allowed'],
|
|
228
|
+
'gmail typo' => ['test@gmial.com', false, 'gmail.com'],
|
|
229
|
+
'invalid format' => ['not-an-email', false, 'valid'],
|
|
230
|
+
];
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Testing Validation Engine
|
|
236
|
+
|
|
237
|
+
```php
|
|
238
|
+
public function test_validation_engine_with_template() {
|
|
239
|
+
$_POST = [
|
|
240
|
+
'name' => 'Test User',
|
|
241
|
+
'email' => 'test@example.com',
|
|
242
|
+
'message' => 'Test message',
|
|
243
|
+
];
|
|
244
|
+
|
|
245
|
+
$result = ima_forms_validate_form(
|
|
246
|
+
'templates/forms/contact-form',
|
|
247
|
+
$_POST,
|
|
248
|
+
'contact-form'
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
$this->assertTrue($result['valid']);
|
|
252
|
+
$this->assertEmpty($result['errors']);
|
|
253
|
+
$this->assertEquals('Test User', $result['sanitized']['name']);
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## Anti-Patterns to Avoid
|
|
258
|
+
|
|
259
|
+
### ❌ Don't Skip Template-Driven Validation
|
|
260
|
+
|
|
261
|
+
```php
|
|
262
|
+
// ❌ WRONG - Manual field map creation
|
|
263
|
+
$field_map = [
|
|
264
|
+
'name' => 'text',
|
|
265
|
+
'email' => 'email',
|
|
266
|
+
'phone' => 'phone',
|
|
267
|
+
];
|
|
268
|
+
$sanitized = ima_forms_sanitize_data($_POST, $field_map);
|
|
269
|
+
$validation = my_manual_validation($sanitized);
|
|
270
|
+
|
|
271
|
+
// ✅ CORRECT - Use Validation Engine
|
|
272
|
+
$result = ima_forms_validate_form('templates/forms/my-form', $_POST, 'my-form');
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### ❌ Don't Duplicate Validation Logic
|
|
276
|
+
|
|
277
|
+
```php
|
|
278
|
+
// ❌ WRONG - Manual validators when field already registers specs
|
|
279
|
+
function my_form_validate($data) {
|
|
280
|
+
$errors = [];
|
|
281
|
+
if (empty($data['email'])) {
|
|
282
|
+
$errors['email'] = 'Email is required.';
|
|
283
|
+
}
|
|
284
|
+
if (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
|
|
285
|
+
$errors['email'] = 'Invalid email format.';
|
|
286
|
+
}
|
|
287
|
+
// ... more duplicated validation
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// ✅ CORRECT - Let field registration handle it
|
|
291
|
+
$result = ima_forms_validate_form('templates/forms/my-form', $_POST, 'my-form');
|
|
292
|
+
|
|
293
|
+
// Add ONLY custom cross-field validation via filter
|
|
294
|
+
add_filter('ima_forms_validate_my-form', function($errors, $data) {
|
|
295
|
+
if ($data['password'] !== $data['password_confirm']) {
|
|
296
|
+
$errors['password_confirm'] = 'Passwords must match.';
|
|
297
|
+
}
|
|
298
|
+
return $errors;
|
|
299
|
+
}, 10, 2);
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### ❌ Don't Forget Enhanced Validation
|
|
303
|
+
|
|
304
|
+
```php
|
|
305
|
+
// ❌ WRONG - Basic email validation for user-facing forms
|
|
306
|
+
ima_forms_email_field([
|
|
307
|
+
'name' => 'email',
|
|
308
|
+
'enhanced_validation' => false, // Misses disposable domains!
|
|
309
|
+
]);
|
|
310
|
+
|
|
311
|
+
// ✅ CORRECT - Use enhanced validation (default)
|
|
312
|
+
ima_forms_email_field([
|
|
313
|
+
'name' => 'email',
|
|
314
|
+
'enhanced_validation' => true, // Blocks spam domains, detects typos
|
|
315
|
+
]);
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### ❌ Don't Use Old Factory Pattern for Simple Forms
|
|
319
|
+
|
|
320
|
+
```php
|
|
321
|
+
// ❌ WRONG - Unnecessary factory pattern complexity
|
|
322
|
+
['render' => $render, 'validate' => $validate] = ima_forms_create_form(...);
|
|
323
|
+
|
|
324
|
+
// ✅ CORRECT - Use Validation Engine for simpler approach
|
|
325
|
+
$result = ima_forms_validate_form('templates/forms/my-form', $_POST, 'my-form');
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
**Note**: Form factory pattern is still useful for programmatic field generation or complex dynamic validation composition.
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
# Field Components Reference
|
|
2
|
+
|
|
3
|
+
## Contents
|
|
4
|
+
|
|
5
|
+
- [Text Input Field](#text-input-field)
|
|
6
|
+
- [Email Field](#email-field)
|
|
7
|
+
- [Password Field](#password-field)
|
|
8
|
+
- [Hidden Field](#hidden-field)
|
|
9
|
+
- [Textarea Field](#textarea-field)
|
|
10
|
+
- [Select Field](#select-field)
|
|
11
|
+
- [Checkbox Field](#checkbox-field)
|
|
12
|
+
- [Checkbox Group](#checkbox-group)
|
|
13
|
+
- [Phone Field](#phone-field)
|
|
14
|
+
- [URL Field](#url-field)
|
|
15
|
+
- [Address Fieldset](#address-fieldset)
|
|
16
|
+
- [Consent Agreement Field](#consent-agreement-field)
|
|
17
|
+
- [Repeater (Dynamic Rows)](#repeater-dynamic-rows)
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
All field components auto-register validation specs when rendered. Use `required => true` to enable required validation.
|
|
22
|
+
|
|
23
|
+
## Text Input Field
|
|
24
|
+
|
|
25
|
+
```php
|
|
26
|
+
ima_forms_text_field([
|
|
27
|
+
'name' => 'field_name',
|
|
28
|
+
'label' => 'Field Label',
|
|
29
|
+
'value' => $data['field_name'] ?? '',
|
|
30
|
+
'required' => true,
|
|
31
|
+
'placeholder' => 'Enter value...',
|
|
32
|
+
'maxlength' => 100,
|
|
33
|
+
'minlength' => 3,
|
|
34
|
+
'pattern' => '[A-Za-z]+', // HTML5 pattern
|
|
35
|
+
'wrapper_class' => 'mb-3',
|
|
36
|
+
'input_class' => 'form-control',
|
|
37
|
+
'help_text' => 'Additional guidance',
|
|
38
|
+
'error' => $errors['field_name'] ?? '',
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
// Auto-registers: type, required, minlength, maxlength validation
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Email Field
|
|
45
|
+
|
|
46
|
+
```php
|
|
47
|
+
ima_forms_email_field([
|
|
48
|
+
'name' => 'email',
|
|
49
|
+
'label' => 'Email Address',
|
|
50
|
+
'value' => $data['email'] ?? '',
|
|
51
|
+
'required' => true,
|
|
52
|
+
'placeholder' => 'user@example.com',
|
|
53
|
+
'enhanced_validation' => true, // Disposable domains + typo detection
|
|
54
|
+
'wrapper_class' => 'mb-3',
|
|
55
|
+
'help_text' => 'We will never share your email',
|
|
56
|
+
'error' => $errors['email'] ?? '',
|
|
57
|
+
]);
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Enhanced validation includes**:
|
|
61
|
+
- RFC 5322 format validation
|
|
62
|
+
- Disposable domain blocking (mailinator, tempmail, etc.)
|
|
63
|
+
- Common typo detection (gmial.com → gmail.com)
|
|
64
|
+
|
|
65
|
+
## Password Field
|
|
66
|
+
|
|
67
|
+
```php
|
|
68
|
+
ima_forms_password_field([
|
|
69
|
+
'name' => 'password',
|
|
70
|
+
'label' => 'Password',
|
|
71
|
+
'required' => true,
|
|
72
|
+
'minlength' => 8,
|
|
73
|
+
'placeholder' => 'Enter password',
|
|
74
|
+
'wrapper_class' => 'mb-3',
|
|
75
|
+
'help_text' => 'Minimum 8 characters',
|
|
76
|
+
'error' => $errors['password'] ?? '',
|
|
77
|
+
'attributes' => [
|
|
78
|
+
'autocomplete' => 'new-password',
|
|
79
|
+
],
|
|
80
|
+
]);
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Hidden Field
|
|
84
|
+
|
|
85
|
+
```php
|
|
86
|
+
ima_forms_hidden_field([
|
|
87
|
+
'name' => 'form_id',
|
|
88
|
+
'value' => 'contact-form',
|
|
89
|
+
'label' => 'Form ID', // For debugging only
|
|
90
|
+
]);
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Textarea Field
|
|
94
|
+
|
|
95
|
+
```php
|
|
96
|
+
ima_forms_textarea_field([
|
|
97
|
+
'name' => 'message',
|
|
98
|
+
'label' => 'Message',
|
|
99
|
+
'value' => $data['message'] ?? '',
|
|
100
|
+
'required' => true,
|
|
101
|
+
'rows' => 5,
|
|
102
|
+
'maxlength' => 1000,
|
|
103
|
+
'placeholder' => 'Enter your message...',
|
|
104
|
+
'wrapper_class' => 'mb-3',
|
|
105
|
+
'error' => $errors['message'] ?? '',
|
|
106
|
+
]);
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Select Field
|
|
110
|
+
|
|
111
|
+
```php
|
|
112
|
+
ima_forms_select_field([
|
|
113
|
+
'name' => 'category',
|
|
114
|
+
'label' => 'Category',
|
|
115
|
+
'value' => $data['category'] ?? '',
|
|
116
|
+
'required' => true,
|
|
117
|
+
'options' => [
|
|
118
|
+
'' => 'Select a category...',
|
|
119
|
+
'general' => 'General Inquiry',
|
|
120
|
+
'support' => 'Technical Support',
|
|
121
|
+
'sales' => 'Sales Question',
|
|
122
|
+
],
|
|
123
|
+
'wrapper_class' => 'mb-3',
|
|
124
|
+
'error' => $errors['category'] ?? '',
|
|
125
|
+
]);
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### State Select (Pre-populated)
|
|
129
|
+
|
|
130
|
+
```php
|
|
131
|
+
ima_forms_state_select_field([
|
|
132
|
+
'name' => 'state',
|
|
133
|
+
'label' => 'State',
|
|
134
|
+
'value' => $data['state'] ?? '',
|
|
135
|
+
'required' => true,
|
|
136
|
+
'wrapper_class' => 'mb-3',
|
|
137
|
+
'error' => $errors['state'] ?? '',
|
|
138
|
+
]);
|
|
139
|
+
|
|
140
|
+
// Uses ima_forms_get_us_states() for options
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Checkbox Field
|
|
144
|
+
|
|
145
|
+
```php
|
|
146
|
+
ima_forms_checkbox_field([
|
|
147
|
+
'name' => 'subscribe',
|
|
148
|
+
'label' => 'Subscribe to newsletter',
|
|
149
|
+
'checked' => !empty($data['subscribe']),
|
|
150
|
+
'required' => false,
|
|
151
|
+
'wrapper_class' => 'mb-3',
|
|
152
|
+
'error' => $errors['subscribe'] ?? '',
|
|
153
|
+
]);
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Checkbox Group
|
|
157
|
+
|
|
158
|
+
```php
|
|
159
|
+
ima_forms_checkbox_group([
|
|
160
|
+
'name' => 'interests',
|
|
161
|
+
'label' => 'Areas of Interest',
|
|
162
|
+
'values' => $data['interests'] ?? [],
|
|
163
|
+
'required' => true,
|
|
164
|
+
'options' => [
|
|
165
|
+
'web' => 'Web Development',
|
|
166
|
+
'mobile' => 'Mobile Apps',
|
|
167
|
+
'design' => 'UI/UX Design',
|
|
168
|
+
'marketing' => 'Digital Marketing',
|
|
169
|
+
],
|
|
170
|
+
'wrapper_class' => 'mb-3',
|
|
171
|
+
'errors' => $errors['interests'] ?? '',
|
|
172
|
+
]);
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Phone Field
|
|
176
|
+
|
|
177
|
+
```php
|
|
178
|
+
ima_forms_phone_field([
|
|
179
|
+
'name' => 'phone',
|
|
180
|
+
'label' => 'Phone Number',
|
|
181
|
+
'value' => $data['phone'] ?? '',
|
|
182
|
+
'required' => true,
|
|
183
|
+
'placeholder' => '(555) 123-4567',
|
|
184
|
+
'wrapper_class' => 'mb-3',
|
|
185
|
+
'error' => $errors['phone'] ?? '',
|
|
186
|
+
]);
|
|
187
|
+
|
|
188
|
+
// Validates: 10-15 digits, allows common formatting
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## URL Field
|
|
192
|
+
|
|
193
|
+
```php
|
|
194
|
+
ima_forms_url_field([
|
|
195
|
+
'name' => 'website',
|
|
196
|
+
'label' => 'Website URL',
|
|
197
|
+
'value' => $data['website'] ?? '',
|
|
198
|
+
'required' => false,
|
|
199
|
+
'placeholder' => 'https://example.com',
|
|
200
|
+
'wrapper_class' => 'mb-3',
|
|
201
|
+
'error' => $errors['website'] ?? '',
|
|
202
|
+
]);
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Address Fieldset
|
|
206
|
+
|
|
207
|
+
Composite field that registers multiple validation specs:
|
|
208
|
+
|
|
209
|
+
```php
|
|
210
|
+
ima_forms_address_fieldset([
|
|
211
|
+
'name_prefix' => 'billing', // Creates: billing_street, billing_city, etc.
|
|
212
|
+
'label' => 'Billing Address',
|
|
213
|
+
'values' => [
|
|
214
|
+
'street' => $data['billing_street'] ?? '',
|
|
215
|
+
'line2' => $data['billing_line2'] ?? '',
|
|
216
|
+
'city' => $data['billing_city'] ?? '',
|
|
217
|
+
'state' => $data['billing_state'] ?? '',
|
|
218
|
+
'zip' => $data['billing_zip'] ?? '',
|
|
219
|
+
'country' => $data['billing_country'] ?? 'United States',
|
|
220
|
+
],
|
|
221
|
+
'required' => true,
|
|
222
|
+
'wrapper_class' => 'mb-3',
|
|
223
|
+
'errors' => $errors, // Pass all errors, fieldset extracts relevant ones
|
|
224
|
+
]);
|
|
225
|
+
|
|
226
|
+
// Registers 4 specs: {prefix}_street, {prefix}_city, {prefix}_state, {prefix}_zip
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Consent Agreement Field
|
|
230
|
+
|
|
231
|
+
Complex component with scrollable content, checkbox, and signature:
|
|
232
|
+
|
|
233
|
+
```php
|
|
234
|
+
// Load agreement content
|
|
235
|
+
ob_start();
|
|
236
|
+
include get_template_directory() . '/templates/agreements/terms-of-service.php';
|
|
237
|
+
$agreement_html = ob_get_clean();
|
|
238
|
+
|
|
239
|
+
ima_forms_consent_agreement_field([
|
|
240
|
+
'content_html' => $agreement_html,
|
|
241
|
+
'checkbox_name' => 'agree_to_terms',
|
|
242
|
+
'checkbox_label' => 'I have read and agree to the terms',
|
|
243
|
+
'signature_name' => 'signature',
|
|
244
|
+
'signature_label' => 'Type your full name as signature',
|
|
245
|
+
'scroll_height' => '350px',
|
|
246
|
+
'required' => true,
|
|
247
|
+
'wrapper_class' => 'mb-4',
|
|
248
|
+
'errors' => [
|
|
249
|
+
'agree_to_terms' => $errors['agree_to_terms'] ?? '',
|
|
250
|
+
'signature' => $errors['signature'] ?? '',
|
|
251
|
+
],
|
|
252
|
+
'checkbox_value' => $_POST['agree_to_terms'] ?? null,
|
|
253
|
+
'signature_value' => $_POST['signature'] ?? '',
|
|
254
|
+
]);
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
**Features**:
|
|
258
|
+
- Checkbox disabled until user scrolls to bottom
|
|
259
|
+
- Signature field disabled until checkbox checked
|
|
260
|
+
- Auto-enqueues `ima-forms-consent.js`
|
|
261
|
+
- Registers TWO validation specs (checkbox + signature)
|
|
262
|
+
|
|
263
|
+
## Repeater (Dynamic Rows)
|
|
264
|
+
|
|
265
|
+
```php
|
|
266
|
+
ima_forms_repeater([
|
|
267
|
+
'name' => 'additional_locations',
|
|
268
|
+
'label' => 'Additional Locations',
|
|
269
|
+
'description' => 'Add multiple practice locations',
|
|
270
|
+
'min_rows' => 0,
|
|
271
|
+
'max_rows' => 10,
|
|
272
|
+
'add_button_text' => 'Add Location',
|
|
273
|
+
'values' => $data['additional_locations'] ?? [],
|
|
274
|
+
'errors' => $errors['additional_locations'] ?? [],
|
|
275
|
+
], function($index, $row_data, $row_errors) {
|
|
276
|
+
// Row template - use array bracket notation
|
|
277
|
+
ima_forms_text_field([
|
|
278
|
+
'name' => "additional_locations[{$index}][location_name]",
|
|
279
|
+
'label' => 'Location Name',
|
|
280
|
+
'value' => $row_data['location_name'] ?? '',
|
|
281
|
+
'error' => $row_errors['location_name'] ?? '',
|
|
282
|
+
'wrapper_class' => 'mb-3',
|
|
283
|
+
]);
|
|
284
|
+
|
|
285
|
+
ima_forms_text_field([
|
|
286
|
+
'name' => "additional_locations[{$index}][address]",
|
|
287
|
+
'label' => 'Address',
|
|
288
|
+
'value' => $row_data['address'] ?? '',
|
|
289
|
+
'error' => $row_errors['address'] ?? '',
|
|
290
|
+
'wrapper_class' => 'mb-3',
|
|
291
|
+
]);
|
|
292
|
+
});
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
**Features**:
|
|
296
|
+
- Auto-enqueues `ima-forms-repeater.js`
|
|
297
|
+
- Template placeholder uses `__INDEX__` (skipped during validation)
|
|
298
|
+
- Vanilla JavaScript (no jQuery dependency)
|