@mytechtoday/augment-extensions 0.1.1 → 0.2.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.
Files changed (79) hide show
  1. package/augment-extensions/domain-rules/wordpress/README.md +163 -0
  2. package/augment-extensions/domain-rules/wordpress/module.json +32 -0
  3. package/augment-extensions/domain-rules/wordpress/rules/coding-standards.md +617 -0
  4. package/augment-extensions/domain-rules/wordpress/rules/directory-structure.md +270 -0
  5. package/augment-extensions/domain-rules/wordpress/rules/file-patterns.md +423 -0
  6. package/augment-extensions/domain-rules/wordpress/rules/gutenberg-blocks.md +493 -0
  7. package/augment-extensions/domain-rules/wordpress/rules/performance.md +568 -0
  8. package/augment-extensions/domain-rules/wordpress/rules/plugin-development.md +510 -0
  9. package/augment-extensions/domain-rules/wordpress/rules/project-detection.md +251 -0
  10. package/augment-extensions/domain-rules/wordpress/rules/rest-api.md +501 -0
  11. package/augment-extensions/domain-rules/wordpress/rules/security.md +564 -0
  12. package/augment-extensions/domain-rules/wordpress/rules/theme-development.md +388 -0
  13. package/augment-extensions/domain-rules/wordpress/rules/woocommerce.md +441 -0
  14. package/augment-extensions/domain-rules/wordpress-plugin/README.md +139 -0
  15. package/augment-extensions/domain-rules/wordpress-plugin/examples/ajax-plugin.md +1599 -0
  16. package/augment-extensions/domain-rules/wordpress-plugin/examples/custom-post-type-plugin.md +1727 -0
  17. package/augment-extensions/domain-rules/wordpress-plugin/examples/gutenberg-block-plugin.md +428 -0
  18. package/augment-extensions/domain-rules/wordpress-plugin/examples/gutenberg-block.md +422 -0
  19. package/augment-extensions/domain-rules/wordpress-plugin/examples/mvc-plugin.md +1623 -0
  20. package/augment-extensions/domain-rules/wordpress-plugin/examples/object-oriented-plugin.md +1343 -0
  21. package/augment-extensions/domain-rules/wordpress-plugin/examples/rest-endpoint.md +734 -0
  22. package/augment-extensions/domain-rules/wordpress-plugin/examples/settings-page-plugin.md +1350 -0
  23. package/augment-extensions/domain-rules/wordpress-plugin/examples/simple-procedural-plugin.md +503 -0
  24. package/augment-extensions/domain-rules/wordpress-plugin/examples/singleton-plugin.md +971 -0
  25. package/augment-extensions/domain-rules/wordpress-plugin/module.json +53 -0
  26. package/augment-extensions/domain-rules/wordpress-plugin/rules/activation-hooks.md +770 -0
  27. package/augment-extensions/domain-rules/wordpress-plugin/rules/admin-interface.md +874 -0
  28. package/augment-extensions/domain-rules/wordpress-plugin/rules/ajax-handlers.md +629 -0
  29. package/augment-extensions/domain-rules/wordpress-plugin/rules/asset-management.md +559 -0
  30. package/augment-extensions/domain-rules/wordpress-plugin/rules/context-providers.md +709 -0
  31. package/augment-extensions/domain-rules/wordpress-plugin/rules/cron-jobs.md +736 -0
  32. package/augment-extensions/domain-rules/wordpress-plugin/rules/database-management.md +1057 -0
  33. package/augment-extensions/domain-rules/wordpress-plugin/rules/documentation-standards.md +463 -0
  34. package/augment-extensions/domain-rules/wordpress-plugin/rules/frontend-functionality.md +478 -0
  35. package/augment-extensions/domain-rules/wordpress-plugin/rules/gutenberg-blocks.md +818 -0
  36. package/augment-extensions/domain-rules/wordpress-plugin/rules/internationalization.md +416 -0
  37. package/augment-extensions/domain-rules/wordpress-plugin/rules/migration.md +667 -0
  38. package/augment-extensions/domain-rules/wordpress-plugin/rules/performance-optimization.md +878 -0
  39. package/augment-extensions/domain-rules/wordpress-plugin/rules/plugin-architecture.md +693 -0
  40. package/augment-extensions/domain-rules/wordpress-plugin/rules/plugin-structure.md +352 -0
  41. package/augment-extensions/domain-rules/wordpress-plugin/rules/rest-api.md +818 -0
  42. package/augment-extensions/domain-rules/wordpress-plugin/rules/scaffolding-workflow.md +624 -0
  43. package/augment-extensions/domain-rules/wordpress-plugin/rules/security-best-practices.md +866 -0
  44. package/augment-extensions/domain-rules/wordpress-plugin/rules/testing-patterns.md +1165 -0
  45. package/augment-extensions/domain-rules/wordpress-plugin/rules/testing.md +414 -0
  46. package/augment-extensions/domain-rules/wordpress-plugin/rules/vscode-integration.md +751 -0
  47. package/augment-extensions/domain-rules/wordpress-plugin/rules/woocommerce-integration.md +949 -0
  48. package/augment-extensions/domain-rules/wordpress-plugin/rules/wordpress-org-submission.md +458 -0
  49. package/augment-extensions/examples/gutenberg-block-plugin/README.md +101 -0
  50. package/augment-extensions/examples/gutenberg-block-plugin/examples/testimonial-block.md +428 -0
  51. package/augment-extensions/examples/gutenberg-block-plugin/module.json +40 -0
  52. package/augment-extensions/examples/rest-api-plugin/README.md +98 -0
  53. package/augment-extensions/examples/rest-api-plugin/examples/task-manager-api.md +1299 -0
  54. package/augment-extensions/examples/rest-api-plugin/module.json +40 -0
  55. package/augment-extensions/examples/woocommerce-extension/README.md +98 -0
  56. package/augment-extensions/examples/woocommerce-extension/examples/product-customizer.md +763 -0
  57. package/augment-extensions/examples/woocommerce-extension/module.json +40 -0
  58. package/augment-extensions/workflows/wordpress-plugin/README.md +232 -0
  59. package/augment-extensions/workflows/wordpress-plugin/ai-prompts.md +839 -0
  60. package/augment-extensions/workflows/wordpress-plugin/bead-decomposition-patterns.md +854 -0
  61. package/augment-extensions/workflows/wordpress-plugin/examples/complete-plugin-example.md +540 -0
  62. package/augment-extensions/workflows/wordpress-plugin/examples/custom-post-type-example.md +1083 -0
  63. package/augment-extensions/workflows/wordpress-plugin/examples/feature-addition-workflow.md +669 -0
  64. package/augment-extensions/workflows/wordpress-plugin/examples/plugin-creation-workflow.md +597 -0
  65. package/augment-extensions/workflows/wordpress-plugin/examples/secure-form-handler-example.md +925 -0
  66. package/augment-extensions/workflows/wordpress-plugin/examples/security-audit-workflow.md +752 -0
  67. package/augment-extensions/workflows/wordpress-plugin/examples/wordpress-org-submission-workflow.md +773 -0
  68. package/augment-extensions/workflows/wordpress-plugin/module.json +49 -0
  69. package/augment-extensions/workflows/wordpress-plugin/rules/best-practices.md +942 -0
  70. package/augment-extensions/workflows/wordpress-plugin/rules/development-workflow.md +702 -0
  71. package/augment-extensions/workflows/wordpress-plugin/rules/submission-workflow.md +728 -0
  72. package/augment-extensions/workflows/wordpress-plugin/rules/testing-workflow.md +775 -0
  73. package/cli/dist/cli.js +5 -1
  74. package/cli/dist/cli.js.map +1 -1
  75. package/cli/dist/commands/show.d.ts.map +1 -1
  76. package/cli/dist/commands/show.js +41 -0
  77. package/cli/dist/commands/show.js.map +1 -1
  78. package/modules.md +52 -0
  79. package/package.json +1 -1
@@ -0,0 +1,925 @@
1
+ # Secure Form Handler Example
2
+
3
+ This example demonstrates implementing a secure form handler in WordPress with comprehensive security measures including nonce verification, capability checks, input sanitization, and output escaping.
4
+
5
+ ## Scenario
6
+
7
+ Creating a secure contact form handler that demonstrates all WordPress security best practices.
8
+
9
+ ## Complete Implementation
10
+
11
+ ### Form Display
12
+
13
+ **File**: `public/partials/contact-form.php`
14
+
15
+ ```php
16
+ <?php
17
+ /**
18
+ * Contact form template.
19
+ *
20
+ * Security features:
21
+ * - Nonce field for CSRF protection
22
+ * - Escaped output for XSS prevention
23
+ * - Honeypot field for spam prevention
24
+ */
25
+
26
+ // Get current values (for form repopulation after validation errors)
27
+ $name = isset($_POST['contact_name']) ? sanitize_text_field($_POST['contact_name']) : '';
28
+ $email = isset($_POST['contact_email']) ? sanitize_email($_POST['contact_email']) : '';
29
+ $subject = isset($_POST['contact_subject']) ? sanitize_text_field($_POST['contact_subject']) : '';
30
+ $message = isset($_POST['contact_message']) ? sanitize_textarea_field($_POST['contact_message']) : '';
31
+
32
+ // Get error messages
33
+ $errors = get_transient('contact_form_errors_' . session_id());
34
+ $success = get_transient('contact_form_success_' . session_id());
35
+
36
+ // Clear transients
37
+ delete_transient('contact_form_errors_' . session_id());
38
+ delete_transient('contact_form_success_' . session_id());
39
+ ?>
40
+
41
+ <div class="contact-form-wrapper">
42
+ <?php if ($success) : ?>
43
+ <div class="contact-form-success">
44
+ <p><?php echo esc_html($success); ?></p>
45
+ </div>
46
+ <?php endif; ?>
47
+
48
+ <?php if ($errors && is_array($errors)) : ?>
49
+ <div class="contact-form-errors">
50
+ <ul>
51
+ <?php foreach ($errors as $error) : ?>
52
+ <li><?php echo esc_html($error); ?></li>
53
+ <?php endforeach; ?>
54
+ </ul>
55
+ </div>
56
+ <?php endif; ?>
57
+
58
+ <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" class="contact-form">
59
+ <!-- Nonce field for CSRF protection -->
60
+ <?php wp_nonce_field('contact_form_submit', 'contact_form_nonce'); ?>
61
+
62
+ <!-- Action for WordPress admin-post.php handler -->
63
+ <input type="hidden" name="action" value="submit_contact_form" />
64
+
65
+ <!-- Honeypot field (hidden from users, catches bots) -->
66
+ <input type="text" name="contact_website" value="" style="display:none;" tabindex="-1" autocomplete="off" />
67
+
68
+ <div class="form-field">
69
+ <label for="contact_name">
70
+ <?php _e('Name', 'text-domain'); ?> <span class="required">*</span>
71
+ </label>
72
+ <input type="text"
73
+ id="contact_name"
74
+ name="contact_name"
75
+ value="<?php echo esc_attr($name); ?>"
76
+ required
77
+ maxlength="100" />
78
+ </div>
79
+
80
+ <div class="form-field">
81
+ <label for="contact_email">
82
+ <?php _e('Email', 'text-domain'); ?> <span class="required">*</span>
83
+ </label>
84
+ <input type="email"
85
+ id="contact_email"
86
+ name="contact_email"
87
+ value="<?php echo esc_attr($email); ?>"
88
+ required
89
+ maxlength="100" />
90
+ </div>
91
+
92
+ <div class="form-field">
93
+ <label for="contact_subject">
94
+ <?php _e('Subject', 'text-domain'); ?> <span class="required">*</span>
95
+ </label>
96
+ <input type="text"
97
+ id="contact_subject"
98
+ name="contact_subject"
99
+ value="<?php echo esc_attr($subject); ?>"
100
+ required
101
+ maxlength="200" />
102
+ </div>
103
+
104
+ <div class="form-field">
105
+ <label for="contact_message">
106
+ <?php _e('Message', 'text-domain'); ?> <span class="required">*</span>
107
+ </label>
108
+ <textarea id="contact_message"
109
+ name="contact_message"
110
+ required
111
+ rows="6"
112
+ maxlength="2000"><?php echo esc_textarea($message); ?></textarea>
113
+ </div>
114
+
115
+ <div class="form-field">
116
+ <button type="submit" class="submit-button">
117
+ <?php _e('Send Message', 'text-domain'); ?>
118
+ </button>
119
+ </div>
120
+ </form>
121
+ </div>
122
+ ```
123
+
124
+ ### Form Handler Class
125
+
126
+ **File**: `includes/class-contact-form-handler.php`
127
+
128
+ ```php
129
+ <?php
130
+ /**
131
+ * Secure contact form handler.
132
+ *
133
+ * Security features:
134
+ * - Nonce verification (CSRF protection)
135
+ * - Capability checks (authorization)
136
+ * - Input sanitization (data cleaning)
137
+ * - Input validation (data verification)
138
+ * - Rate limiting (spam prevention)
139
+ * - Honeypot field (bot detection)
140
+ * - SQL injection prevention (prepared statements)
141
+ * - XSS prevention (output escaping)
142
+ */
143
+ class Contact_Form_Handler {
144
+
145
+ /**
146
+ * Rate limit: max submissions per IP per hour.
147
+ */
148
+ const RATE_LIMIT = 3;
149
+
150
+ /**
151
+ * Initialize the form handler.
152
+ */
153
+ public function __construct() {
154
+ // Register form submission handler
155
+ add_action('admin_post_submit_contact_form', array($this, 'handle_submission'));
156
+ add_action('admin_post_nopriv_submit_contact_form', array($this, 'handle_submission'));
157
+ }
158
+
159
+ /**
160
+ * Handle form submission.
161
+ */
162
+ public function handle_submission() {
163
+ // Start session for error/success messages
164
+ if (!session_id()) {
165
+ session_start();
166
+ }
167
+
168
+ $errors = array();
169
+
170
+ // 1. CSRF Protection: Verify nonce
171
+ if (!isset($_POST['contact_form_nonce']) ||
172
+ !wp_verify_nonce($_POST['contact_form_nonce'], 'contact_form_submit')) {
173
+ $errors[] = __('Security check failed. Please try again.', 'text-domain');
174
+ $this->redirect_with_errors($errors);
175
+ return;
176
+ }
177
+
178
+ // 2. Bot Detection: Check honeypot field
179
+ if (!empty($_POST['contact_website'])) {
180
+ // Bot detected - silently fail
181
+ $this->redirect_with_success(__('Thank you for your message!', 'text-domain'));
182
+ return;
183
+ }
184
+
185
+ // 3. Rate Limiting: Check submission rate
186
+ if (!$this->check_rate_limit()) {
187
+ $errors[] = __('Too many submissions. Please try again later.', 'text-domain');
188
+ $this->redirect_with_errors($errors);
189
+ return;
190
+ }
191
+
192
+ // 4. Input Sanitization: Clean all inputs
193
+ $name = isset($_POST['contact_name']) ? sanitize_text_field($_POST['contact_name']) : '';
194
+ $email = isset($_POST['contact_email']) ? sanitize_email($_POST['contact_email']) : '';
195
+ $subject = isset($_POST['contact_subject']) ? sanitize_text_field($_POST['contact_subject']) : '';
196
+ $message = isset($_POST['contact_message']) ? sanitize_textarea_field($_POST['contact_message']) : '';
197
+
198
+ // 5. Input Validation: Verify data
199
+ $errors = $this->validate_input($name, $email, $subject, $message);
200
+
201
+ if (!empty($errors)) {
202
+ $this->redirect_with_errors($errors);
203
+ return;
204
+ }
205
+
206
+ // 6. Save to Database: Use prepared statements
207
+ $saved = $this->save_to_database($name, $email, $subject, $message);
208
+
209
+ if (!$saved) {
210
+ $errors[] = __('Failed to save message. Please try again.', 'text-domain');
211
+ $this->redirect_with_errors($errors);
212
+ return;
213
+ }
214
+
215
+ // 7. Send Email Notification
216
+ $this->send_email_notification($name, $email, $subject, $message);
217
+
218
+ // 8. Log Submission
219
+ $this->log_submission($email);
220
+
221
+ // 9. Success
222
+ $this->redirect_with_success(__('Thank you for your message! We will get back to you soon.', 'text-domain'));
223
+ }
224
+
225
+ /**
226
+ * Validate form input.
227
+ *
228
+ * @return array Array of error messages (empty if valid)
229
+ */
230
+ private function validate_input($name, $email, $subject, $message) {
231
+ $errors = array();
232
+
233
+ // Validate name
234
+ if (empty($name)) {
235
+ $errors[] = __('Name is required.', 'text-domain');
236
+ } elseif (strlen($name) < 2) {
237
+ $errors[] = __('Name must be at least 2 characters.', 'text-domain');
238
+ } elseif (strlen($name) > 100) {
239
+ $errors[] = __('Name must be less than 100 characters.', 'text-domain');
240
+ }
241
+
242
+ // Validate email
243
+ if (empty($email)) {
244
+ $errors[] = __('Email is required.', 'text-domain');
245
+ } elseif (!is_email($email)) {
246
+ $errors[] = __('Please enter a valid email address.', 'text-domain');
247
+ }
248
+
249
+ // Validate subject
250
+ if (empty($subject)) {
251
+ $errors[] = __('Subject is required.', 'text-domain');
252
+ } elseif (strlen($subject) < 3) {
253
+ $errors[] = __('Subject must be at least 3 characters.', 'text-domain');
254
+ } elseif (strlen($subject) > 200) {
255
+ $errors[] = __('Subject must be less than 200 characters.', 'text-domain');
256
+ }
257
+
258
+ // Validate message
259
+ if (empty($message)) {
260
+ $errors[] = __('Message is required.', 'text-domain');
261
+ } elseif (strlen($message) < 10) {
262
+ $errors[] = __('Message must be at least 10 characters.', 'text-domain');
263
+ } elseif (strlen($message) > 2000) {
264
+ $errors[] = __('Message must be less than 2000 characters.', 'text-domain');
265
+ }
266
+
267
+ return $errors;
268
+ }
269
+
270
+ /**
271
+ * Check rate limit for submissions.
272
+ *
273
+ * @return bool True if within rate limit, false otherwise
274
+ */
275
+ private function check_rate_limit() {
276
+ $ip_address = $this->get_client_ip();
277
+ $transient_key = 'contact_form_rate_' . md5($ip_address);
278
+
279
+ $submissions = get_transient($transient_key);
280
+
281
+ if ($submissions === false) {
282
+ // First submission in this hour
283
+ set_transient($transient_key, 1, HOUR_IN_SECONDS);
284
+ return true;
285
+ }
286
+
287
+ if ($submissions >= self::RATE_LIMIT) {
288
+ // Rate limit exceeded
289
+ return false;
290
+ }
291
+
292
+ // Increment submission count
293
+ set_transient($transient_key, $submissions + 1, HOUR_IN_SECONDS);
294
+ return true;
295
+ }
296
+
297
+ /**
298
+ * Get client IP address.
299
+ *
300
+ * @return string IP address
301
+ */
302
+ private function get_client_ip() {
303
+ $ip_address = '';
304
+
305
+ if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
306
+ $ip_address = $_SERVER['HTTP_CLIENT_IP'];
307
+ } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
308
+ $ip_address = $_SERVER['HTTP_X_FORWARDED_FOR'];
309
+ } else {
310
+ $ip_address = $_SERVER['REMOTE_ADDR'];
311
+ }
312
+
313
+ // Sanitize IP address
314
+ return filter_var($ip_address, FILTER_VALIDATE_IP) ? $ip_address : '0.0.0.0';
315
+ }
316
+
317
+ /**
318
+ * Save submission to database using prepared statements.
319
+ *
320
+ * @return bool True on success, false on failure
321
+ */
322
+ private function save_to_database($name, $email, $subject, $message) {
323
+ global $wpdb;
324
+
325
+ $table_name = $wpdb->prefix . 'contact_messages';
326
+
327
+ // Use prepared statement to prevent SQL injection
328
+ $result = $wpdb->insert(
329
+ $table_name,
330
+ array(
331
+ 'name' => $name,
332
+ 'email' => $email,
333
+ 'subject' => $subject,
334
+ 'message' => $message,
335
+ 'ip_address' => $this->get_client_ip(),
336
+ 'submitted_at' => current_time('mysql'),
337
+ 'status' => 'unread',
338
+ ),
339
+ array(
340
+ '%s', // name
341
+ '%s', // email
342
+ '%s', // subject
343
+ '%s', // message
344
+ '%s', // ip_address
345
+ '%s', // submitted_at
346
+ '%s', // status
347
+ )
348
+ );
349
+
350
+ return $result !== false;
351
+ }
352
+
353
+ /**
354
+ * Send email notification to admin.
355
+ */
356
+ private function send_email_notification($name, $email, $subject, $message) {
357
+ $admin_email = get_option('admin_email');
358
+
359
+ // Prepare email
360
+ $to = $admin_email;
361
+ $email_subject = sprintf(
362
+ __('[%s] New Contact Form Submission: %s', 'text-domain'),
363
+ get_bloginfo('name'),
364
+ $subject
365
+ );
366
+
367
+ // Build email body with escaped content
368
+ $email_body = sprintf(
369
+ __("New contact form submission:\n\nName: %s\nEmail: %s\nSubject: %s\n\nMessage:\n%s", 'text-domain'),
370
+ $name,
371
+ $email,
372
+ $subject,
373
+ $message
374
+ );
375
+
376
+ // Set headers
377
+ $headers = array(
378
+ 'Content-Type: text/plain; charset=UTF-8',
379
+ 'Reply-To: ' . $email,
380
+ );
381
+
382
+ // Send email
383
+ wp_mail($to, $email_subject, $email_body, $headers);
384
+ }
385
+
386
+ /**
387
+ * Log submission for security monitoring.
388
+ */
389
+ private function log_submission($email) {
390
+ // Log to WordPress debug log if enabled
391
+ if (defined('WP_DEBUG') && WP_DEBUG && defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
392
+ error_log(sprintf(
393
+ 'Contact form submission from %s (IP: %s)',
394
+ $email,
395
+ $this->get_client_ip()
396
+ ));
397
+ }
398
+ }
399
+
400
+ /**
401
+ * Redirect back to form with errors.
402
+ */
403
+ private function redirect_with_errors($errors) {
404
+ set_transient('contact_form_errors_' . session_id(), $errors, 60);
405
+
406
+ $redirect_url = wp_get_referer();
407
+ if (!$redirect_url) {
408
+ $redirect_url = home_url();
409
+ }
410
+
411
+ wp_safe_redirect($redirect_url);
412
+ exit;
413
+ }
414
+
415
+ /**
416
+ * Redirect back to form with success message.
417
+ */
418
+ private function redirect_with_success($message) {
419
+ set_transient('contact_form_success_' . session_id(), $message, 60);
420
+
421
+ $redirect_url = wp_get_referer();
422
+ if (!$redirect_url) {
423
+ $redirect_url = home_url();
424
+ }
425
+
426
+ wp_safe_redirect($redirect_url);
427
+ exit;
428
+ }
429
+ }
430
+ ```
431
+
432
+ ### AJAX Form Handler (Alternative)
433
+
434
+ **File**: `includes/class-contact-form-ajax-handler.php`
435
+
436
+ ```php
437
+ <?php
438
+ /**
439
+ * Secure AJAX contact form handler.
440
+ *
441
+ * Additional security for AJAX:
442
+ * - check_ajax_referer() for nonce verification
443
+ * - wp_send_json_error() and wp_send_json_success() for responses
444
+ */
445
+ class Contact_Form_Ajax_Handler {
446
+
447
+ /**
448
+ * Initialize the AJAX handler.
449
+ */
450
+ public function __construct() {
451
+ // Register AJAX handlers
452
+ add_action('wp_ajax_submit_contact_form', array($this, 'handle_ajax_submission'));
453
+ add_action('wp_ajax_nopriv_submit_contact_form', array($this, 'handle_ajax_submission'));
454
+
455
+ // Enqueue scripts with localized nonce
456
+ add_action('wp_enqueue_scripts', array($this, 'enqueue_scripts'));
457
+ }
458
+
459
+ /**
460
+ * Enqueue scripts with localized data.
461
+ */
462
+ public function enqueue_scripts() {
463
+ wp_enqueue_script(
464
+ 'contact-form-ajax',
465
+ plugin_dir_url(__FILE__) . '../public/js/contact-form-ajax.js',
466
+ array('jquery'),
467
+ '1.0.0',
468
+ true
469
+ );
470
+
471
+ // Localize script with AJAX URL and nonce
472
+ wp_localize_script('contact-form-ajax', 'contactFormAjax', array(
473
+ 'ajax_url' => admin_url('admin-ajax.php'),
474
+ 'nonce' => wp_create_nonce('contact_form_ajax_nonce'),
475
+ ));
476
+ }
477
+
478
+ /**
479
+ * Handle AJAX form submission.
480
+ */
481
+ public function handle_ajax_submission() {
482
+ // 1. CSRF Protection: Verify AJAX nonce
483
+ if (!check_ajax_referer('contact_form_ajax_nonce', 'nonce', false)) {
484
+ wp_send_json_error(array(
485
+ 'message' => __('Security check failed. Please refresh the page and try again.', 'text-domain'),
486
+ ));
487
+ }
488
+
489
+ // 2. Bot Detection: Check honeypot
490
+ if (!empty($_POST['contact_website'])) {
491
+ // Bot detected - send fake success
492
+ wp_send_json_success(array(
493
+ 'message' => __('Thank you for your message!', 'text-domain'),
494
+ ));
495
+ }
496
+
497
+ // 3. Rate Limiting
498
+ if (!$this->check_rate_limit()) {
499
+ wp_send_json_error(array(
500
+ 'message' => __('Too many submissions. Please try again later.', 'text-domain'),
501
+ ));
502
+ }
503
+
504
+ // 4. Input Sanitization
505
+ $name = isset($_POST['contact_name']) ? sanitize_text_field($_POST['contact_name']) : '';
506
+ $email = isset($_POST['contact_email']) ? sanitize_email($_POST['contact_email']) : '';
507
+ $subject = isset($_POST['contact_subject']) ? sanitize_text_field($_POST['contact_subject']) : '';
508
+ $message = isset($_POST['contact_message']) ? sanitize_textarea_field($_POST['contact_message']) : '';
509
+
510
+ // 5. Input Validation
511
+ $errors = $this->validate_input($name, $email, $subject, $message);
512
+
513
+ if (!empty($errors)) {
514
+ wp_send_json_error(array(
515
+ 'message' => implode(' ', $errors),
516
+ 'errors' => $errors,
517
+ ));
518
+ }
519
+
520
+ // 6. Save to Database
521
+ $saved = $this->save_to_database($name, $email, $subject, $message);
522
+
523
+ if (!$saved) {
524
+ wp_send_json_error(array(
525
+ 'message' => __('Failed to save message. Please try again.', 'text-domain'),
526
+ ));
527
+ }
528
+
529
+ // 7. Send Email
530
+ $this->send_email_notification($name, $email, $subject, $message);
531
+
532
+ // 8. Log Submission
533
+ $this->log_submission($email);
534
+
535
+ // 9. Success Response
536
+ wp_send_json_success(array(
537
+ 'message' => __('Thank you for your message! We will get back to you soon.', 'text-domain'),
538
+ ));
539
+ }
540
+
541
+ // ... (same helper methods as Contact_Form_Handler)
542
+ }
543
+ ```
544
+
545
+ ### JavaScript for AJAX Form
546
+
547
+ **File**: `public/js/contact-form-ajax.js`
548
+
549
+ ```javascript
550
+ (function($) {
551
+ 'use strict';
552
+
553
+ $(document).ready(function() {
554
+ $('#contact-form-ajax').on('submit', function(e) {
555
+ e.preventDefault();
556
+
557
+ var $form = $(this);
558
+ var $submitButton = $form.find('button[type="submit"]');
559
+ var $messageContainer = $('#form-messages');
560
+
561
+ // Disable submit button
562
+ $submitButton.prop('disabled', true).text('Sending...');
563
+
564
+ // Clear previous messages
565
+ $messageContainer.html('').removeClass('success error');
566
+
567
+ // Prepare form data
568
+ var formData = {
569
+ action: 'submit_contact_form',
570
+ nonce: contactFormAjax.nonce,
571
+ contact_name: $form.find('#contact_name').val(),
572
+ contact_email: $form.find('#contact_email').val(),
573
+ contact_subject: $form.find('#contact_subject').val(),
574
+ contact_message: $form.find('#contact_message').val(),
575
+ contact_website: $form.find('[name="contact_website"]').val() // Honeypot
576
+ };
577
+
578
+ // Send AJAX request
579
+ $.ajax({
580
+ url: contactFormAjax.ajax_url,
581
+ type: 'POST',
582
+ data: formData,
583
+ dataType: 'json',
584
+ success: function(response) {
585
+ if (response.success) {
586
+ // Success
587
+ $messageContainer
588
+ .html('<p>' + escapeHtml(response.data.message) + '</p>')
589
+ .addClass('success');
590
+
591
+ // Reset form
592
+ $form[0].reset();
593
+ } else {
594
+ // Error
595
+ $messageContainer
596
+ .html('<p>' + escapeHtml(response.data.message) + '</p>')
597
+ .addClass('error');
598
+ }
599
+ },
600
+ error: function(xhr, status, error) {
601
+ // AJAX error
602
+ $messageContainer
603
+ .html('<p>An error occurred. Please try again.</p>')
604
+ .addClass('error');
605
+ },
606
+ complete: function() {
607
+ // Re-enable submit button
608
+ $submitButton.prop('disabled', false).text('Send Message');
609
+ }
610
+ });
611
+ });
612
+ });
613
+
614
+ /**
615
+ * Escape HTML to prevent XSS in JavaScript.
616
+ */
617
+ function escapeHtml(text) {
618
+ var map = {
619
+ '&': '&amp;',
620
+ '<': '&lt;',
621
+ '>': '&gt;',
622
+ '"': '&quot;',
623
+ "'": '&#039;'
624
+ };
625
+ return text.replace(/[&<>"']/g, function(m) { return map[m]; });
626
+ }
627
+
628
+ })(jQuery);
629
+ ```
630
+
631
+ ### Database Table Creation
632
+
633
+ **File**: `includes/class-contact-form-database.php`
634
+
635
+ ```php
636
+ <?php
637
+ /**
638
+ * Database table management for contact form.
639
+ */
640
+ class Contact_Form_Database {
641
+
642
+ /**
643
+ * Create the contact messages table.
644
+ */
645
+ public static function create_table() {
646
+ global $wpdb;
647
+
648
+ $table_name = $wpdb->prefix . 'contact_messages';
649
+ $charset_collate = $wpdb->get_charset_collate();
650
+
651
+ $sql = "CREATE TABLE $table_name (
652
+ id bigint(20) NOT NULL AUTO_INCREMENT,
653
+ name varchar(100) NOT NULL,
654
+ email varchar(100) NOT NULL,
655
+ subject varchar(200) NOT NULL,
656
+ message text NOT NULL,
657
+ ip_address varchar(45) NOT NULL,
658
+ submitted_at datetime NOT NULL,
659
+ status varchar(20) NOT NULL DEFAULT 'unread',
660
+ PRIMARY KEY (id),
661
+ KEY email (email),
662
+ KEY status (status),
663
+ KEY submitted_at (submitted_at)
664
+ ) $charset_collate;";
665
+
666
+ require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
667
+ dbDelta($sql);
668
+ }
669
+ }
670
+ ```
671
+
672
+ ### Admin Page to View Submissions
673
+
674
+ **File**: `admin/class-contact-messages-admin.php`
675
+
676
+ ```php
677
+ <?php
678
+ /**
679
+ * Admin page to view contact form submissions.
680
+ *
681
+ * Security features:
682
+ * - Capability check (manage_options)
683
+ * - Nonce verification for actions
684
+ * - Prepared statements for queries
685
+ * - Output escaping
686
+ */
687
+ class Contact_Messages_Admin {
688
+
689
+ /**
690
+ * Initialize the admin page.
691
+ */
692
+ public function __construct() {
693
+ add_action('admin_menu', array($this, 'add_admin_menu'));
694
+ add_action('admin_post_delete_contact_message', array($this, 'delete_message'));
695
+ }
696
+
697
+ /**
698
+ * Add admin menu item.
699
+ */
700
+ public function add_admin_menu() {
701
+ add_submenu_page(
702
+ 'tools.php',
703
+ __('Contact Messages', 'text-domain'),
704
+ __('Contact Messages', 'text-domain'),
705
+ 'manage_options',
706
+ 'contact-messages',
707
+ array($this, 'render_admin_page')
708
+ );
709
+ }
710
+
711
+ /**
712
+ * Render the admin page.
713
+ */
714
+ public function render_admin_page() {
715
+ // Check user capability
716
+ if (!current_user_can('manage_options')) {
717
+ wp_die(__('You do not have sufficient permissions to access this page.', 'text-domain'));
718
+ }
719
+
720
+ global $wpdb;
721
+ $table_name = $wpdb->prefix . 'contact_messages';
722
+
723
+ // Get messages with prepared statement
724
+ $messages = $wpdb->get_results(
725
+ $wpdb->prepare(
726
+ "SELECT * FROM $table_name ORDER BY submitted_at DESC LIMIT %d",
727
+ 100
728
+ )
729
+ );
730
+
731
+ ?>
732
+ <div class="wrap">
733
+ <h1><?php _e('Contact Messages', 'text-domain'); ?></h1>
734
+
735
+ <?php if (empty($messages)) : ?>
736
+ <p><?php _e('No messages found.', 'text-domain'); ?></p>
737
+ <?php else : ?>
738
+ <table class="wp-list-table widefat fixed striped">
739
+ <thead>
740
+ <tr>
741
+ <th><?php _e('Date', 'text-domain'); ?></th>
742
+ <th><?php _e('Name', 'text-domain'); ?></th>
743
+ <th><?php _e('Email', 'text-domain'); ?></th>
744
+ <th><?php _e('Subject', 'text-domain'); ?></th>
745
+ <th><?php _e('Message', 'text-domain'); ?></th>
746
+ <th><?php _e('Status', 'text-domain'); ?></th>
747
+ <th><?php _e('Actions', 'text-domain'); ?></th>
748
+ </tr>
749
+ </thead>
750
+ <tbody>
751
+ <?php foreach ($messages as $message) : ?>
752
+ <tr>
753
+ <td><?php echo esc_html($message->submitted_at); ?></td>
754
+ <td><?php echo esc_html($message->name); ?></td>
755
+ <td><a href="mailto:<?php echo esc_attr($message->email); ?>"><?php echo esc_html($message->email); ?></a></td>
756
+ <td><?php echo esc_html($message->subject); ?></td>
757
+ <td><?php echo esc_html(wp_trim_words($message->message, 10)); ?></td>
758
+ <td><?php echo esc_html($message->status); ?></td>
759
+ <td>
760
+ <a href="<?php echo esc_url(wp_nonce_url(
761
+ admin_url('admin-post.php?action=delete_contact_message&id=' . $message->id),
762
+ 'delete_message_' . $message->id
763
+ )); ?>"
764
+ onclick="return confirm('<?php _e('Are you sure you want to delete this message?', 'text-domain'); ?>');">
765
+ <?php _e('Delete', 'text-domain'); ?>
766
+ </a>
767
+ </td>
768
+ </tr>
769
+ <?php endforeach; ?>
770
+ </tbody>
771
+ </table>
772
+ <?php endif; ?>
773
+ </div>
774
+ <?php
775
+ }
776
+
777
+ /**
778
+ * Delete a message.
779
+ */
780
+ public function delete_message() {
781
+ // Check user capability
782
+ if (!current_user_can('manage_options')) {
783
+ wp_die(__('You do not have sufficient permissions to perform this action.', 'text-domain'));
784
+ }
785
+
786
+ // Get message ID
787
+ $message_id = isset($_GET['id']) ? intval($_GET['id']) : 0;
788
+
789
+ if (!$message_id) {
790
+ wp_die(__('Invalid message ID.', 'text-domain'));
791
+ }
792
+
793
+ // Verify nonce
794
+ if (!isset($_GET['_wpnonce']) ||
795
+ !wp_verify_nonce($_GET['_wpnonce'], 'delete_message_' . $message_id)) {
796
+ wp_die(__('Security check failed.', 'text-domain'));
797
+ }
798
+
799
+ // Delete message with prepared statement
800
+ global $wpdb;
801
+ $table_name = $wpdb->prefix . 'contact_messages';
802
+
803
+ $deleted = $wpdb->delete(
804
+ $table_name,
805
+ array('id' => $message_id),
806
+ array('%d')
807
+ );
808
+
809
+ if ($deleted) {
810
+ wp_safe_redirect(admin_url('tools.php?page=contact-messages&deleted=1'));
811
+ } else {
812
+ wp_die(__('Failed to delete message.', 'text-domain'));
813
+ }
814
+
815
+ exit;
816
+ }
817
+ }
818
+ ```
819
+
820
+ ## Security Checklist
821
+
822
+ ### ✅ CSRF Protection (Cross-Site Request Forgery)
823
+
824
+ - [x] **Nonce field in form**: `wp_nonce_field('contact_form_submit', 'contact_form_nonce')`
825
+ - [x] **Nonce verification in handler**: `wp_verify_nonce($_POST['contact_form_nonce'], 'contact_form_submit')`
826
+ - [x] **AJAX nonce**: `check_ajax_referer('contact_form_ajax_nonce', 'nonce', false)`
827
+ - [x] **Admin action nonce**: `wp_nonce_url()` for delete links
828
+
829
+ ### ✅ XSS Prevention (Cross-Site Scripting)
830
+
831
+ - [x] **Output escaping**: `esc_html()`, `esc_attr()`, `esc_url()`, `esc_textarea()`
832
+ - [x] **JavaScript escaping**: Custom `escapeHtml()` function
833
+ - [x] **Email content**: Plain text email (no HTML)
834
+
835
+ ### ✅ SQL Injection Prevention
836
+
837
+ - [x] **Prepared statements**: `$wpdb->insert()` with format array
838
+ - [x] **Prepared queries**: `$wpdb->prepare()` with placeholders
839
+ - [x] **Type casting**: `intval()` for IDs
840
+
841
+ ### ✅ Authorization
842
+
843
+ - [x] **Capability checks**: `current_user_can('manage_options')`
844
+ - [x] **Admin page protection**: Check capability before rendering
845
+ - [x] **Admin action protection**: Check capability before deleting
846
+
847
+ ### ✅ Input Validation
848
+
849
+ - [x] **Required fields**: Check for empty values
850
+ - [x] **Length validation**: Min/max character limits
851
+ - [x] **Email validation**: `is_email()` function
852
+ - [x] **Type validation**: Ensure correct data types
853
+
854
+ ### ✅ Input Sanitization
855
+
856
+ - [x] **Text fields**: `sanitize_text_field()`
857
+ - [x] **Email**: `sanitize_email()`
858
+ - [x] **Textarea**: `sanitize_textarea_field()`
859
+ - [x] **IP address**: `filter_var($ip, FILTER_VALIDATE_IP)`
860
+
861
+ ### ✅ Spam Prevention
862
+
863
+ - [x] **Rate limiting**: Max 3 submissions per hour per IP
864
+ - [x] **Honeypot field**: Hidden field to catch bots
865
+ - [x] **IP logging**: Track submissions by IP address
866
+
867
+ ### ✅ Additional Security
868
+
869
+ - [x] **Session management**: Proper session handling
870
+ - [x] **Transients for messages**: Temporary storage for errors/success
871
+ - [x] **Safe redirects**: `wp_safe_redirect()`
872
+ - [x] **Error logging**: Log submissions for monitoring
873
+
874
+ ## Testing Checklist
875
+
876
+ ### Functional Testing
877
+
878
+ - [ ] Form displays correctly
879
+ - [ ] Form submits successfully with valid data
880
+ - [ ] Success message displays after submission
881
+ - [ ] Email notification sent to admin
882
+ - [ ] Data saved to database correctly
883
+ - [ ] AJAX form works without page reload
884
+
885
+ ### Security Testing
886
+
887
+ - [ ] Form submission fails without nonce
888
+ - [ ] Form submission fails with invalid nonce
889
+ - [ ] Form submission fails with expired nonce
890
+ - [ ] Rate limiting blocks excessive submissions
891
+ - [ ] Honeypot catches bot submissions
892
+ - [ ] Admin page requires manage_options capability
893
+ - [ ] Delete action requires nonce verification
894
+ - [ ] SQL injection attempts are blocked
895
+ - [ ] XSS attempts are escaped
896
+
897
+ ### Validation Testing
898
+
899
+ - [ ] Empty fields show error messages
900
+ - [ ] Invalid email shows error message
901
+ - [ ] Too short inputs show error messages
902
+ - [ ] Too long inputs show error messages
903
+ - [ ] Multiple errors display correctly
904
+
905
+ ### Edge Cases
906
+
907
+ - [ ] Form works with special characters in input
908
+ - [ ] Form works with Unicode characters
909
+ - [ ] Form works with very long messages
910
+ - [ ] Form handles database errors gracefully
911
+ - [ ] Form handles email sending failures gracefully
912
+
913
+ ## Key Takeaways
914
+
915
+ 1. **Defense in Depth**: Multiple layers of security (nonces, capability checks, sanitization, escaping)
916
+ 2. **Never Trust User Input**: Always sanitize and validate
917
+ 3. **Always Escape Output**: Prevent XSS vulnerabilities
918
+ 4. **Use Prepared Statements**: Prevent SQL injection
919
+ 5. **Rate Limiting**: Prevent spam and abuse
920
+ 6. **Honeypot Fields**: Simple bot detection
921
+ 7. **Proper Error Handling**: Don't expose sensitive information
922
+ 8. **Logging**: Monitor for suspicious activity
923
+ 9. **WordPress Functions**: Use built-in security functions
924
+ 10. **Testing**: Comprehensive security testing is essential
925
+