@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.
- package/augment-extensions/domain-rules/wordpress/README.md +163 -0
- package/augment-extensions/domain-rules/wordpress/module.json +32 -0
- package/augment-extensions/domain-rules/wordpress/rules/coding-standards.md +617 -0
- package/augment-extensions/domain-rules/wordpress/rules/directory-structure.md +270 -0
- package/augment-extensions/domain-rules/wordpress/rules/file-patterns.md +423 -0
- package/augment-extensions/domain-rules/wordpress/rules/gutenberg-blocks.md +493 -0
- package/augment-extensions/domain-rules/wordpress/rules/performance.md +568 -0
- package/augment-extensions/domain-rules/wordpress/rules/plugin-development.md +510 -0
- package/augment-extensions/domain-rules/wordpress/rules/project-detection.md +251 -0
- package/augment-extensions/domain-rules/wordpress/rules/rest-api.md +501 -0
- package/augment-extensions/domain-rules/wordpress/rules/security.md +564 -0
- package/augment-extensions/domain-rules/wordpress/rules/theme-development.md +388 -0
- package/augment-extensions/domain-rules/wordpress/rules/woocommerce.md +441 -0
- package/augment-extensions/domain-rules/wordpress-plugin/README.md +139 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/ajax-plugin.md +1599 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/custom-post-type-plugin.md +1727 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/gutenberg-block-plugin.md +428 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/gutenberg-block.md +422 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/mvc-plugin.md +1623 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/object-oriented-plugin.md +1343 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/rest-endpoint.md +734 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/settings-page-plugin.md +1350 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/simple-procedural-plugin.md +503 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/singleton-plugin.md +971 -0
- package/augment-extensions/domain-rules/wordpress-plugin/module.json +53 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/activation-hooks.md +770 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/admin-interface.md +874 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/ajax-handlers.md +629 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/asset-management.md +559 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/context-providers.md +709 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/cron-jobs.md +736 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/database-management.md +1057 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/documentation-standards.md +463 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/frontend-functionality.md +478 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/gutenberg-blocks.md +818 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/internationalization.md +416 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/migration.md +667 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/performance-optimization.md +878 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/plugin-architecture.md +693 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/plugin-structure.md +352 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/rest-api.md +818 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/scaffolding-workflow.md +624 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/security-best-practices.md +866 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/testing-patterns.md +1165 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/testing.md +414 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/vscode-integration.md +751 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/woocommerce-integration.md +949 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/wordpress-org-submission.md +458 -0
- package/augment-extensions/examples/gutenberg-block-plugin/README.md +101 -0
- package/augment-extensions/examples/gutenberg-block-plugin/examples/testimonial-block.md +428 -0
- package/augment-extensions/examples/gutenberg-block-plugin/module.json +40 -0
- package/augment-extensions/examples/rest-api-plugin/README.md +98 -0
- package/augment-extensions/examples/rest-api-plugin/examples/task-manager-api.md +1299 -0
- package/augment-extensions/examples/rest-api-plugin/module.json +40 -0
- package/augment-extensions/examples/woocommerce-extension/README.md +98 -0
- package/augment-extensions/examples/woocommerce-extension/examples/product-customizer.md +763 -0
- package/augment-extensions/examples/woocommerce-extension/module.json +40 -0
- package/augment-extensions/workflows/wordpress-plugin/README.md +232 -0
- package/augment-extensions/workflows/wordpress-plugin/ai-prompts.md +839 -0
- package/augment-extensions/workflows/wordpress-plugin/bead-decomposition-patterns.md +854 -0
- package/augment-extensions/workflows/wordpress-plugin/examples/complete-plugin-example.md +540 -0
- package/augment-extensions/workflows/wordpress-plugin/examples/custom-post-type-example.md +1083 -0
- package/augment-extensions/workflows/wordpress-plugin/examples/feature-addition-workflow.md +669 -0
- package/augment-extensions/workflows/wordpress-plugin/examples/plugin-creation-workflow.md +597 -0
- package/augment-extensions/workflows/wordpress-plugin/examples/secure-form-handler-example.md +925 -0
- package/augment-extensions/workflows/wordpress-plugin/examples/security-audit-workflow.md +752 -0
- package/augment-extensions/workflows/wordpress-plugin/examples/wordpress-org-submission-workflow.md +773 -0
- package/augment-extensions/workflows/wordpress-plugin/module.json +49 -0
- package/augment-extensions/workflows/wordpress-plugin/rules/best-practices.md +942 -0
- package/augment-extensions/workflows/wordpress-plugin/rules/development-workflow.md +702 -0
- package/augment-extensions/workflows/wordpress-plugin/rules/submission-workflow.md +728 -0
- package/augment-extensions/workflows/wordpress-plugin/rules/testing-workflow.md +775 -0
- package/cli/dist/cli.js +5 -1
- package/cli/dist/cli.js.map +1 -1
- package/cli/dist/commands/show.d.ts.map +1 -1
- package/cli/dist/commands/show.js +41 -0
- package/cli/dist/commands/show.js.map +1 -1
- package/modules.md +52 -0
- package/package.json +1 -1
|
@@ -0,0 +1,1599 @@
|
|
|
1
|
+
# AJAX Plugin Example
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
This example demonstrates a complete **AJAX Plugin** with JavaScript AJAX calls, PHP handlers, nonce verification, JSON responses, error handling, and both frontend and admin AJAX functionality.
|
|
6
|
+
|
|
7
|
+
**Complexity**: Medium
|
|
8
|
+
**File Count**: 5-10 files
|
|
9
|
+
**Team Size**: 1-2 developers
|
|
10
|
+
**Use Case**: Dynamic content loading, form submissions, real-time updates, user interactions
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Complete Plugin: "AJAX Content Manager"
|
|
15
|
+
|
|
16
|
+
A comprehensive AJAX plugin demonstrating WordPress AJAX API, nonce security, JSON responses, error handling, and best practices for both frontend and admin AJAX requests.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Directory Structure
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
ajax-content-manager/
|
|
24
|
+
├── ajax-content-manager.php # Main plugin file
|
|
25
|
+
├── uninstall.php # Uninstall cleanup
|
|
26
|
+
├── readme.txt # WordPress.org readme
|
|
27
|
+
├── includes/
|
|
28
|
+
│ ├── class-ajax-handler.php # AJAX handler class
|
|
29
|
+
│ ├── class-content-manager.php # Content management
|
|
30
|
+
│ └── class-ajax-validator.php # Validation logic
|
|
31
|
+
├── admin/
|
|
32
|
+
│ ├── css/
|
|
33
|
+
│ │ └── admin.css # Admin styles
|
|
34
|
+
│ ├── js/
|
|
35
|
+
│ │ └── admin-ajax.js # Admin AJAX scripts
|
|
36
|
+
│ └── views/
|
|
37
|
+
│ └── admin-page.php # Admin page view
|
|
38
|
+
└── public/
|
|
39
|
+
├── css/
|
|
40
|
+
│ └── public.css # Public styles
|
|
41
|
+
└── js/
|
|
42
|
+
└── public-ajax.js # Public AJAX scripts
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Main Plugin File
|
|
48
|
+
|
|
49
|
+
### File: `ajax-content-manager.php`
|
|
50
|
+
|
|
51
|
+
```php
|
|
52
|
+
<?php
|
|
53
|
+
/**
|
|
54
|
+
* Plugin Name: AJAX Content Manager
|
|
55
|
+
* Plugin URI: https://example.com/ajax-content-manager
|
|
56
|
+
* Description: Complete AJAX functionality with nonce security, JSON responses, and error handling
|
|
57
|
+
* Version: 1.0.0
|
|
58
|
+
* Author: Your Name
|
|
59
|
+
* Author URI: https://example.com
|
|
60
|
+
* License: GPL-2.0+
|
|
61
|
+
* License URI: http://www.gnu.org/licenses/gpl-2.0.txt
|
|
62
|
+
* Text Domain: ajax-content-manager
|
|
63
|
+
* Domain Path: /languages
|
|
64
|
+
*
|
|
65
|
+
* @package AJAX_Content_Manager
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
// Exit if accessed directly
|
|
69
|
+
if (!defined('ABSPATH')) {
|
|
70
|
+
exit;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Define plugin constants
|
|
74
|
+
define('ACM_VERSION', '1.0.0');
|
|
75
|
+
define('ACM_PLUGIN_DIR', plugin_dir_path(__FILE__));
|
|
76
|
+
define('ACM_PLUGIN_URL', plugin_dir_url(__FILE__));
|
|
77
|
+
define('ACM_PLUGIN_FILE', __FILE__);
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Autoloader
|
|
81
|
+
*/
|
|
82
|
+
spl_autoload_register(function ($class) {
|
|
83
|
+
$prefix = 'ACM_';
|
|
84
|
+
|
|
85
|
+
if (strpos($class, $prefix) !== 0) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
$class_name = str_replace($prefix, '', $class);
|
|
90
|
+
$class_file = 'class-' . str_replace('_', '-', strtolower($class_name)) . '.php';
|
|
91
|
+
|
|
92
|
+
$file = ACM_PLUGIN_DIR . 'includes/' . $class_file;
|
|
93
|
+
if (file_exists($file)) {
|
|
94
|
+
require $file;
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Activation hook
|
|
100
|
+
*/
|
|
101
|
+
register_activation_hook(__FILE__, function() {
|
|
102
|
+
// Create custom table for demo
|
|
103
|
+
global $wpdb;
|
|
104
|
+
$table_name = $wpdb->prefix . 'acm_items';
|
|
105
|
+
|
|
106
|
+
$charset_collate = $wpdb->get_charset_collate();
|
|
107
|
+
|
|
108
|
+
$sql = "CREATE TABLE $table_name (
|
|
109
|
+
id mediumint(9) NOT NULL AUTO_INCREMENT,
|
|
110
|
+
title varchar(255) NOT NULL,
|
|
111
|
+
content text NOT NULL,
|
|
112
|
+
status varchar(20) DEFAULT 'active',
|
|
113
|
+
created_at datetime DEFAULT CURRENT_TIMESTAMP,
|
|
114
|
+
PRIMARY KEY (id)
|
|
115
|
+
) $charset_collate;";
|
|
116
|
+
|
|
117
|
+
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
|
|
118
|
+
dbDelta($sql);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Deactivation hook
|
|
123
|
+
*/
|
|
124
|
+
register_deactivation_hook(__FILE__, function() {
|
|
125
|
+
// Cleanup if needed
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Initialize the plugin
|
|
130
|
+
*/
|
|
131
|
+
function run_ajax_content_manager() {
|
|
132
|
+
// Initialize AJAX handlers
|
|
133
|
+
ACM_Ajax_Handler::init();
|
|
134
|
+
|
|
135
|
+
// Load text domain
|
|
136
|
+
add_action('plugins_loaded', 'acm_load_textdomain');
|
|
137
|
+
|
|
138
|
+
// Enqueue scripts
|
|
139
|
+
add_action('wp_enqueue_scripts', 'acm_enqueue_public_assets');
|
|
140
|
+
add_action('admin_enqueue_scripts', 'acm_enqueue_admin_assets');
|
|
141
|
+
|
|
142
|
+
// Add admin menu
|
|
143
|
+
add_action('admin_menu', 'acm_add_admin_menu');
|
|
144
|
+
}
|
|
145
|
+
add_action('plugins_loaded', 'run_ajax_content_manager');
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Load plugin text domain
|
|
149
|
+
*/
|
|
150
|
+
function acm_load_textdomain() {
|
|
151
|
+
load_plugin_textdomain(
|
|
152
|
+
'ajax-content-manager',
|
|
153
|
+
false,
|
|
154
|
+
dirname(plugin_basename(ACM_PLUGIN_FILE)) . '/languages/'
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Enqueue public assets
|
|
160
|
+
*/
|
|
161
|
+
function acm_enqueue_public_assets() {
|
|
162
|
+
wp_enqueue_style(
|
|
163
|
+
'acm-public',
|
|
164
|
+
ACM_PLUGIN_URL . 'public/css/public.css',
|
|
165
|
+
array(),
|
|
166
|
+
ACM_VERSION
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
wp_enqueue_script(
|
|
170
|
+
'acm-public',
|
|
171
|
+
ACM_PLUGIN_URL . 'public/js/public-ajax.js',
|
|
172
|
+
array('jquery'),
|
|
173
|
+
ACM_VERSION,
|
|
174
|
+
true
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
// Localize script with AJAX URL and nonce
|
|
178
|
+
wp_localize_script('acm-public', 'acmAjax', array(
|
|
179
|
+
'ajaxUrl' => admin_url('admin-ajax.php'),
|
|
180
|
+
'nonce' => wp_create_nonce('acm_public_nonce'),
|
|
181
|
+
));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Enqueue admin assets
|
|
186
|
+
*/
|
|
187
|
+
function acm_enqueue_admin_assets($hook) {
|
|
188
|
+
// Only load on our admin page
|
|
189
|
+
if ('toplevel_page_ajax-content-manager' !== $hook) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
wp_enqueue_style(
|
|
194
|
+
'acm-admin',
|
|
195
|
+
ACM_PLUGIN_URL . 'admin/css/admin.css',
|
|
196
|
+
array(),
|
|
197
|
+
ACM_VERSION
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
wp_enqueue_script(
|
|
201
|
+
'acm-admin',
|
|
202
|
+
ACM_PLUGIN_URL . 'admin/js/admin-ajax.js',
|
|
203
|
+
array('jquery'),
|
|
204
|
+
ACM_VERSION,
|
|
205
|
+
true
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
// Localize script with AJAX URL and nonce
|
|
209
|
+
wp_localize_script('acm-admin', 'acmAdminAjax', array(
|
|
210
|
+
'ajaxUrl' => admin_url('admin-ajax.php'),
|
|
211
|
+
'nonce' => wp_create_nonce('acm_admin_nonce'),
|
|
212
|
+
));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Add admin menu
|
|
217
|
+
*/
|
|
218
|
+
function acm_add_admin_menu() {
|
|
219
|
+
add_menu_page(
|
|
220
|
+
__('AJAX Content Manager', 'ajax-content-manager'),
|
|
221
|
+
__('AJAX Manager', 'ajax-content-manager'),
|
|
222
|
+
'manage_options',
|
|
223
|
+
'ajax-content-manager',
|
|
224
|
+
'acm_render_admin_page',
|
|
225
|
+
'dashicons-update',
|
|
226
|
+
30
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Render admin page
|
|
232
|
+
*/
|
|
233
|
+
function acm_render_admin_page() {
|
|
234
|
+
if (!current_user_can('manage_options')) {
|
|
235
|
+
wp_die(__('You do not have sufficient permissions to access this page.', 'ajax-content-manager'));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
include ACM_PLUGIN_DIR . 'admin/views/admin-page.php';
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## AJAX Handler Class
|
|
245
|
+
|
|
246
|
+
### File: `includes/class-ajax-handler.php`
|
|
247
|
+
|
|
248
|
+
```php
|
|
249
|
+
<?php
|
|
250
|
+
/**
|
|
251
|
+
* AJAX handler class
|
|
252
|
+
*
|
|
253
|
+
* @package AJAX_Content_Manager
|
|
254
|
+
*/
|
|
255
|
+
|
|
256
|
+
class ACM_Ajax_Handler {
|
|
257
|
+
/**
|
|
258
|
+
* Initialize AJAX handlers
|
|
259
|
+
*/
|
|
260
|
+
public static function init() {
|
|
261
|
+
// Public AJAX actions (for logged-in and non-logged-in users)
|
|
262
|
+
add_action('wp_ajax_acm_load_content', array(__CLASS__, 'load_content'));
|
|
263
|
+
add_action('wp_ajax_nopriv_acm_load_content', array(__CLASS__, 'load_content'));
|
|
264
|
+
|
|
265
|
+
add_action('wp_ajax_acm_submit_form', array(__CLASS__, 'submit_form'));
|
|
266
|
+
add_action('wp_ajax_nopriv_acm_submit_form', array(__CLASS__, 'submit_form'));
|
|
267
|
+
|
|
268
|
+
// Admin AJAX actions (for logged-in users only)
|
|
269
|
+
add_action('wp_ajax_acm_create_item', array(__CLASS__, 'create_item'));
|
|
270
|
+
add_action('wp_ajax_acm_update_item', array(__CLASS__, 'update_item'));
|
|
271
|
+
add_action('wp_ajax_acm_delete_item', array(__CLASS__, 'delete_item'));
|
|
272
|
+
add_action('wp_ajax_acm_get_items', array(__CLASS__, 'get_items'));
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Load content (public AJAX)
|
|
277
|
+
*/
|
|
278
|
+
public static function load_content() {
|
|
279
|
+
// Verify nonce
|
|
280
|
+
if (!check_ajax_referer('acm_public_nonce', 'nonce', false)) {
|
|
281
|
+
wp_send_json_error(array(
|
|
282
|
+
'message' => __('Security check failed.', 'ajax-content-manager'),
|
|
283
|
+
), 403);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Get and sanitize input
|
|
287
|
+
$post_id = isset($_POST['post_id']) ? intval($_POST['post_id']) : 0;
|
|
288
|
+
|
|
289
|
+
if (!$post_id) {
|
|
290
|
+
wp_send_json_error(array(
|
|
291
|
+
'message' => __('Invalid post ID.', 'ajax-content-manager'),
|
|
292
|
+
), 400);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Get post
|
|
296
|
+
$post = get_post($post_id);
|
|
297
|
+
|
|
298
|
+
if (!$post) {
|
|
299
|
+
wp_send_json_error(array(
|
|
300
|
+
'message' => __('Post not found.', 'ajax-content-manager'),
|
|
301
|
+
), 404);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Prepare response
|
|
305
|
+
$response = array(
|
|
306
|
+
'id' => $post->ID,
|
|
307
|
+
'title' => get_the_title($post),
|
|
308
|
+
'content' => apply_filters('the_content', $post->post_content),
|
|
309
|
+
'excerpt' => get_the_excerpt($post),
|
|
310
|
+
'date' => get_the_date('', $post),
|
|
311
|
+
'author' => get_the_author_meta('display_name', $post->post_author),
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
wp_send_json_success($response);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Submit form (public AJAX)
|
|
319
|
+
*/
|
|
320
|
+
public static function submit_form() {
|
|
321
|
+
// Verify nonce
|
|
322
|
+
if (!check_ajax_referer('acm_public_nonce', 'nonce', false)) {
|
|
323
|
+
wp_send_json_error(array(
|
|
324
|
+
'message' => __('Security check failed.', 'ajax-content-manager'),
|
|
325
|
+
), 403);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Get and sanitize input
|
|
329
|
+
$name = isset($_POST['name']) ? sanitize_text_field($_POST['name']) : '';
|
|
330
|
+
$email = isset($_POST['email']) ? sanitize_email($_POST['email']) : '';
|
|
331
|
+
$message = isset($_POST['message']) ? sanitize_textarea_field($_POST['message']) : '';
|
|
332
|
+
|
|
333
|
+
// Validate input
|
|
334
|
+
$errors = array();
|
|
335
|
+
|
|
336
|
+
if (empty($name)) {
|
|
337
|
+
$errors[] = __('Name is required.', 'ajax-content-manager');
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (empty($email) || !is_email($email)) {
|
|
341
|
+
$errors[] = __('Valid email is required.', 'ajax-content-manager');
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (empty($message)) {
|
|
345
|
+
$errors[] = __('Message is required.', 'ajax-content-manager');
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (!empty($errors)) {
|
|
349
|
+
wp_send_json_error(array(
|
|
350
|
+
'message' => implode(' ', $errors),
|
|
351
|
+
'errors' => $errors,
|
|
352
|
+
), 400);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Process form (example: send email)
|
|
356
|
+
$to = get_option('admin_email');
|
|
357
|
+
$subject = sprintf(__('New message from %s', 'ajax-content-manager'), $name);
|
|
358
|
+
$body = sprintf(
|
|
359
|
+
"Name: %s\nEmail: %s\n\nMessage:\n%s",
|
|
360
|
+
$name,
|
|
361
|
+
$email,
|
|
362
|
+
$message
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
$sent = wp_mail($to, $subject, $body);
|
|
366
|
+
|
|
367
|
+
if ($sent) {
|
|
368
|
+
wp_send_json_success(array(
|
|
369
|
+
'message' => __('Form submitted successfully!', 'ajax-content-manager'),
|
|
370
|
+
));
|
|
371
|
+
} else {
|
|
372
|
+
wp_send_json_error(array(
|
|
373
|
+
'message' => __('Failed to send email.', 'ajax-content-manager'),
|
|
374
|
+
), 500);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Create item (admin AJAX)
|
|
380
|
+
*/
|
|
381
|
+
public static function create_item() {
|
|
382
|
+
// Verify nonce
|
|
383
|
+
if (!check_ajax_referer('acm_admin_nonce', 'nonce', false)) {
|
|
384
|
+
wp_send_json_error(array(
|
|
385
|
+
'message' => __('Security check failed.', 'ajax-content-manager'),
|
|
386
|
+
), 403);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Check capabilities
|
|
390
|
+
if (!current_user_can('manage_options')) {
|
|
391
|
+
wp_send_json_error(array(
|
|
392
|
+
'message' => __('Permission denied.', 'ajax-content-manager'),
|
|
393
|
+
), 403);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Get and sanitize input
|
|
397
|
+
$title = isset($_POST['title']) ? sanitize_text_field($_POST['title']) : '';
|
|
398
|
+
$content = isset($_POST['content']) ? sanitize_textarea_field($_POST['content']) : '';
|
|
399
|
+
|
|
400
|
+
// Validate input
|
|
401
|
+
if (empty($title)) {
|
|
402
|
+
wp_send_json_error(array(
|
|
403
|
+
'message' => __('Title is required.', 'ajax-content-manager'),
|
|
404
|
+
), 400);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Insert into database
|
|
408
|
+
global $wpdb;
|
|
409
|
+
$table_name = $wpdb->prefix . 'acm_items';
|
|
410
|
+
|
|
411
|
+
$result = $wpdb->insert(
|
|
412
|
+
$table_name,
|
|
413
|
+
array(
|
|
414
|
+
'title' => $title,
|
|
415
|
+
'content' => $content,
|
|
416
|
+
'status' => 'active',
|
|
417
|
+
),
|
|
418
|
+
array('%s', '%s', '%s')
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
if ($result === false) {
|
|
422
|
+
wp_send_json_error(array(
|
|
423
|
+
'message' => __('Failed to create item.', 'ajax-content-manager'),
|
|
424
|
+
), 500);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
$item_id = $wpdb->insert_id;
|
|
428
|
+
|
|
429
|
+
wp_send_json_success(array(
|
|
430
|
+
'message' => __('Item created successfully!', 'ajax-content-manager'),
|
|
431
|
+
'item_id' => $item_id,
|
|
432
|
+
));
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Update item (admin AJAX)
|
|
437
|
+
*/
|
|
438
|
+
public static function update_item() {
|
|
439
|
+
// Verify nonce
|
|
440
|
+
if (!check_ajax_referer('acm_admin_nonce', 'nonce', false)) {
|
|
441
|
+
wp_send_json_error(array(
|
|
442
|
+
'message' => __('Security check failed.', 'ajax-content-manager'),
|
|
443
|
+
), 403);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Check capabilities
|
|
447
|
+
if (!current_user_can('manage_options')) {
|
|
448
|
+
wp_send_json_error(array(
|
|
449
|
+
'message' => __('Permission denied.', 'ajax-content-manager'),
|
|
450
|
+
), 403);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Get and sanitize input
|
|
454
|
+
$item_id = isset($_POST['item_id']) ? intval($_POST['item_id']) : 0;
|
|
455
|
+
$title = isset($_POST['title']) ? sanitize_text_field($_POST['title']) : '';
|
|
456
|
+
$content = isset($_POST['content']) ? sanitize_textarea_field($_POST['content']) : '';
|
|
457
|
+
$status = isset($_POST['status']) ? sanitize_text_field($_POST['status']) : 'active';
|
|
458
|
+
|
|
459
|
+
// Validate input
|
|
460
|
+
if (!$item_id) {
|
|
461
|
+
wp_send_json_error(array(
|
|
462
|
+
'message' => __('Invalid item ID.', 'ajax-content-manager'),
|
|
463
|
+
), 400);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
if (empty($title)) {
|
|
467
|
+
wp_send_json_error(array(
|
|
468
|
+
'message' => __('Title is required.', 'ajax-content-manager'),
|
|
469
|
+
), 400);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Update in database
|
|
473
|
+
global $wpdb;
|
|
474
|
+
$table_name = $wpdb->prefix . 'acm_items';
|
|
475
|
+
|
|
476
|
+
$result = $wpdb->update(
|
|
477
|
+
$table_name,
|
|
478
|
+
array(
|
|
479
|
+
'title' => $title,
|
|
480
|
+
'content' => $content,
|
|
481
|
+
'status' => $status,
|
|
482
|
+
),
|
|
483
|
+
array('id' => $item_id),
|
|
484
|
+
array('%s', '%s', '%s'),
|
|
485
|
+
array('%d')
|
|
486
|
+
);
|
|
487
|
+
|
|
488
|
+
if ($result === false) {
|
|
489
|
+
wp_send_json_error(array(
|
|
490
|
+
'message' => __('Failed to update item.', 'ajax-content-manager'),
|
|
491
|
+
), 500);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
wp_send_json_success(array(
|
|
495
|
+
'message' => __('Item updated successfully!', 'ajax-content-manager'),
|
|
496
|
+
));
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Delete item (admin AJAX)
|
|
501
|
+
*/
|
|
502
|
+
public static function delete_item() {
|
|
503
|
+
// Verify nonce
|
|
504
|
+
if (!check_ajax_referer('acm_admin_nonce', 'nonce', false)) {
|
|
505
|
+
wp_send_json_error(array(
|
|
506
|
+
'message' => __('Security check failed.', 'ajax-content-manager'),
|
|
507
|
+
), 403);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Check capabilities
|
|
511
|
+
if (!current_user_can('manage_options')) {
|
|
512
|
+
wp_send_json_error(array(
|
|
513
|
+
'message' => __('Permission denied.', 'ajax-content-manager'),
|
|
514
|
+
), 403);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Get and sanitize input
|
|
518
|
+
$item_id = isset($_POST['item_id']) ? intval($_POST['item_id']) : 0;
|
|
519
|
+
|
|
520
|
+
// Validate input
|
|
521
|
+
if (!$item_id) {
|
|
522
|
+
wp_send_json_error(array(
|
|
523
|
+
'message' => __('Invalid item ID.', 'ajax-content-manager'),
|
|
524
|
+
), 400);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Delete from database
|
|
528
|
+
global $wpdb;
|
|
529
|
+
$table_name = $wpdb->prefix . 'acm_items';
|
|
530
|
+
|
|
531
|
+
$result = $wpdb->delete(
|
|
532
|
+
$table_name,
|
|
533
|
+
array('id' => $item_id),
|
|
534
|
+
array('%d')
|
|
535
|
+
);
|
|
536
|
+
|
|
537
|
+
if ($result === false) {
|
|
538
|
+
wp_send_json_error(array(
|
|
539
|
+
'message' => __('Failed to delete item.', 'ajax-content-manager'),
|
|
540
|
+
), 500);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
wp_send_json_success(array(
|
|
544
|
+
'message' => __('Item deleted successfully!', 'ajax-content-manager'),
|
|
545
|
+
));
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Get items (admin AJAX)
|
|
550
|
+
*/
|
|
551
|
+
public static function get_items() {
|
|
552
|
+
// Verify nonce
|
|
553
|
+
if (!check_ajax_referer('acm_admin_nonce', 'nonce', false)) {
|
|
554
|
+
wp_send_json_error(array(
|
|
555
|
+
'message' => __('Security check failed.', 'ajax-content-manager'),
|
|
556
|
+
), 403);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Check capabilities
|
|
560
|
+
if (!current_user_can('manage_options')) {
|
|
561
|
+
wp_send_json_error(array(
|
|
562
|
+
'message' => __('Permission denied.', 'ajax-content-manager'),
|
|
563
|
+
), 403);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Get pagination parameters
|
|
567
|
+
$page = isset($_POST['page']) ? intval($_POST['page']) : 1;
|
|
568
|
+
$per_page = isset($_POST['per_page']) ? intval($_POST['per_page']) : 10;
|
|
569
|
+
$offset = ($page - 1) * $per_page;
|
|
570
|
+
|
|
571
|
+
// Get items from database
|
|
572
|
+
global $wpdb;
|
|
573
|
+
$table_name = $wpdb->prefix . 'acm_items';
|
|
574
|
+
|
|
575
|
+
$items = $wpdb->get_results(
|
|
576
|
+
$wpdb->prepare(
|
|
577
|
+
"SELECT * FROM $table_name ORDER BY created_at DESC LIMIT %d OFFSET %d",
|
|
578
|
+
$per_page,
|
|
579
|
+
$offset
|
|
580
|
+
),
|
|
581
|
+
ARRAY_A
|
|
582
|
+
);
|
|
583
|
+
|
|
584
|
+
// Get total count
|
|
585
|
+
$total = $wpdb->get_var("SELECT COUNT(*) FROM $table_name");
|
|
586
|
+
|
|
587
|
+
wp_send_json_success(array(
|
|
588
|
+
'items' => $items,
|
|
589
|
+
'total' => intval($total),
|
|
590
|
+
'page' => $page,
|
|
591
|
+
'per_page' => $per_page,
|
|
592
|
+
'total_pages' => ceil($total / $per_page),
|
|
593
|
+
));
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
---
|
|
599
|
+
|
|
600
|
+
## JavaScript Files
|
|
601
|
+
|
|
602
|
+
### File: `public/js/public-ajax.js`
|
|
603
|
+
|
|
604
|
+
```javascript
|
|
605
|
+
(function($) {
|
|
606
|
+
'use strict';
|
|
607
|
+
|
|
608
|
+
$(document).ready(function() {
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* Load content via AJAX
|
|
612
|
+
*/
|
|
613
|
+
$('.acm-load-content').on('click', function(e) {
|
|
614
|
+
e.preventDefault();
|
|
615
|
+
|
|
616
|
+
var $button = $(this);
|
|
617
|
+
var postId = $button.data('post-id');
|
|
618
|
+
var $container = $('#acm-content-container');
|
|
619
|
+
|
|
620
|
+
// Disable button
|
|
621
|
+
$button.prop('disabled', true).text('Loading...');
|
|
622
|
+
|
|
623
|
+
// Show loading state
|
|
624
|
+
$container.html('<p>Loading content...</p>');
|
|
625
|
+
|
|
626
|
+
$.ajax({
|
|
627
|
+
url: acmAjax.ajaxUrl,
|
|
628
|
+
type: 'POST',
|
|
629
|
+
data: {
|
|
630
|
+
action: 'acm_load_content',
|
|
631
|
+
nonce: acmAjax.nonce,
|
|
632
|
+
post_id: postId
|
|
633
|
+
},
|
|
634
|
+
success: function(response) {
|
|
635
|
+
if (response.success) {
|
|
636
|
+
var data = response.data;
|
|
637
|
+
var html = '<article class="acm-post">';
|
|
638
|
+
html += '<h2>' + data.title + '</h2>';
|
|
639
|
+
html += '<div class="acm-meta">';
|
|
640
|
+
html += '<span class="acm-date">' + data.date + '</span>';
|
|
641
|
+
html += '<span class="acm-author">By ' + data.author + '</span>';
|
|
642
|
+
html += '</div>';
|
|
643
|
+
html += '<div class="acm-content">' + data.content + '</div>';
|
|
644
|
+
html += '</article>';
|
|
645
|
+
|
|
646
|
+
$container.html(html);
|
|
647
|
+
} else {
|
|
648
|
+
$container.html('<p class="error">' + response.data.message + '</p>');
|
|
649
|
+
}
|
|
650
|
+
},
|
|
651
|
+
error: function(xhr, status, error) {
|
|
652
|
+
$container.html('<p class="error">An error occurred: ' + error + '</p>');
|
|
653
|
+
},
|
|
654
|
+
complete: function() {
|
|
655
|
+
$button.prop('disabled', false).text('Load Content');
|
|
656
|
+
}
|
|
657
|
+
});
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* Submit form via AJAX
|
|
662
|
+
*/
|
|
663
|
+
$('#acm-contact-form').on('submit', function(e) {
|
|
664
|
+
e.preventDefault();
|
|
665
|
+
|
|
666
|
+
var $form = $(this);
|
|
667
|
+
var $submit = $form.find('button[type=\"submit\"]');
|
|
668
|
+
var $message = $('#acm-form-message');
|
|
669
|
+
|
|
670
|
+
// Get form data
|
|
671
|
+
var formData = {
|
|
672
|
+
action: 'acm_submit_form',
|
|
673
|
+
nonce: acmAjax.nonce,
|
|
674
|
+
name: $form.find('#acm-name').val(),
|
|
675
|
+
email: $form.find('#acm-email').val(),
|
|
676
|
+
message: $form.find('#acm-message').val()
|
|
677
|
+
};
|
|
678
|
+
|
|
679
|
+
// Disable submit button
|
|
680
|
+
$submit.prop('disabled', true).text('Submitting...');
|
|
681
|
+
$message.html('');
|
|
682
|
+
|
|
683
|
+
$.ajax({
|
|
684
|
+
url: acmAjax.ajaxUrl,
|
|
685
|
+
type: 'POST',
|
|
686
|
+
data: formData,
|
|
687
|
+
success: function(response) {
|
|
688
|
+
if (response.success) {
|
|
689
|
+
$message.html('<p class=\"success\">' + response.data.message + '</p>');
|
|
690
|
+
$form[0].reset();
|
|
691
|
+
} else {
|
|
692
|
+
$message.html('<p class=\"error\">' + response.data.message + '</p>');
|
|
693
|
+
}
|
|
694
|
+
},
|
|
695
|
+
error: function(xhr, status, error) {
|
|
696
|
+
$message.html('<p class=\"error\">An error occurred: ' + error + '</p>');
|
|
697
|
+
},
|
|
698
|
+
complete: function() {
|
|
699
|
+
$submit.prop('disabled', false).text('Submit');
|
|
700
|
+
}
|
|
701
|
+
});
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* Real-time search example
|
|
706
|
+
*/
|
|
707
|
+
var searchTimeout;
|
|
708
|
+
$('#acm-search').on('keyup', function() {
|
|
709
|
+
var $input = $(this);
|
|
710
|
+
var query = $input.val();
|
|
711
|
+
var $results = $('#acm-search-results');
|
|
712
|
+
|
|
713
|
+
// Clear previous timeout
|
|
714
|
+
clearTimeout(searchTimeout);
|
|
715
|
+
|
|
716
|
+
// Don't search if query is too short
|
|
717
|
+
if (query.length < 3) {
|
|
718
|
+
$results.html('');
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// Show loading
|
|
723
|
+
$results.html('<p>Searching...</p>');
|
|
724
|
+
|
|
725
|
+
// Debounce search
|
|
726
|
+
searchTimeout = setTimeout(function() {
|
|
727
|
+
$.ajax({
|
|
728
|
+
url: acmAjax.ajaxUrl,
|
|
729
|
+
type: 'POST',
|
|
730
|
+
data: {
|
|
731
|
+
action: 'acm_search',
|
|
732
|
+
nonce: acmAjax.nonce,
|
|
733
|
+
query: query
|
|
734
|
+
},
|
|
735
|
+
success: function(response) {
|
|
736
|
+
if (response.success && response.data.results.length > 0) {
|
|
737
|
+
var html = '<ul>';
|
|
738
|
+
$.each(response.data.results, function(index, item) {
|
|
739
|
+
html += '<li><a href=\"' + item.url + '\">' + item.title + '</a></li>';
|
|
740
|
+
});
|
|
741
|
+
html += '</ul>';
|
|
742
|
+
$results.html(html);
|
|
743
|
+
} else {
|
|
744
|
+
$results.html('<p>No results found.</p>');
|
|
745
|
+
}
|
|
746
|
+
},
|
|
747
|
+
error: function() {
|
|
748
|
+
$results.html('<p class=\"error\">Search failed.</p>');
|
|
749
|
+
}
|
|
750
|
+
});
|
|
751
|
+
}, 500); // 500ms debounce
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
})(jQuery);
|
|
757
|
+
```
|
|
758
|
+
|
|
759
|
+
### File: `admin/js/admin-ajax.js`
|
|
760
|
+
|
|
761
|
+
```javascript
|
|
762
|
+
(function($) {
|
|
763
|
+
'use strict';
|
|
764
|
+
|
|
765
|
+
$(document).ready(function() {
|
|
766
|
+
|
|
767
|
+
/**
|
|
768
|
+
* Load items on page load
|
|
769
|
+
*/
|
|
770
|
+
loadItems();
|
|
771
|
+
|
|
772
|
+
/**
|
|
773
|
+
* Create item
|
|
774
|
+
*/
|
|
775
|
+
$('#acm-create-form').on('submit', function(e) {
|
|
776
|
+
e.preventDefault();
|
|
777
|
+
|
|
778
|
+
var $form = $(this);
|
|
779
|
+
var $submit = $form.find('button[type=\"submit\"]');
|
|
780
|
+
|
|
781
|
+
var formData = {
|
|
782
|
+
action: 'acm_create_item',
|
|
783
|
+
nonce: acmAdminAjax.nonce,
|
|
784
|
+
title: $form.find('#acm-title').val(),
|
|
785
|
+
content: $form.find('#acm-content').val()
|
|
786
|
+
};
|
|
787
|
+
|
|
788
|
+
$submit.prop('disabled', true).text('Creating...');
|
|
789
|
+
|
|
790
|
+
$.ajax({
|
|
791
|
+
url: acmAdminAjax.ajaxUrl,
|
|
792
|
+
type: 'POST',
|
|
793
|
+
data: formData,
|
|
794
|
+
success: function(response) {
|
|
795
|
+
if (response.success) {
|
|
796
|
+
alert(response.data.message);
|
|
797
|
+
$form[0].reset();
|
|
798
|
+
loadItems(); // Reload items list
|
|
799
|
+
} else {
|
|
800
|
+
alert('Error: ' + response.data.message);
|
|
801
|
+
}
|
|
802
|
+
},
|
|
803
|
+
error: function(xhr, status, error) {
|
|
804
|
+
alert('An error occurred: ' + error);
|
|
805
|
+
},
|
|
806
|
+
complete: function() {
|
|
807
|
+
$submit.prop('disabled', false).text('Create Item');
|
|
808
|
+
}
|
|
809
|
+
});
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
/**
|
|
813
|
+
* Delete item
|
|
814
|
+
*/
|
|
815
|
+
$(document).on('click', '.acm-delete-item', function(e) {
|
|
816
|
+
e.preventDefault();
|
|
817
|
+
|
|
818
|
+
if (!confirm('Are you sure you want to delete this item?')) {
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
var $button = $(this);
|
|
823
|
+
var itemId = $button.data('item-id');
|
|
824
|
+
|
|
825
|
+
$.ajax({
|
|
826
|
+
url: acmAdminAjax.ajaxUrl,
|
|
827
|
+
type: 'POST',
|
|
828
|
+
data: {
|
|
829
|
+
action: 'acm_delete_item',
|
|
830
|
+
nonce: acmAdminAjax.nonce,
|
|
831
|
+
item_id: itemId
|
|
832
|
+
},
|
|
833
|
+
success: function(response) {
|
|
834
|
+
if (response.success) {
|
|
835
|
+
alert(response.data.message);
|
|
836
|
+
loadItems(); // Reload items list
|
|
837
|
+
} else {
|
|
838
|
+
alert('Error: ' + response.data.message);
|
|
839
|
+
}
|
|
840
|
+
},
|
|
841
|
+
error: function(xhr, status, error) {
|
|
842
|
+
alert('An error occurred: ' + error);
|
|
843
|
+
}
|
|
844
|
+
});
|
|
845
|
+
});
|
|
846
|
+
|
|
847
|
+
/**
|
|
848
|
+
* Update item (inline edit example)
|
|
849
|
+
*/
|
|
850
|
+
$(document).on('click', '.acm-update-item', function(e) {
|
|
851
|
+
e.preventDefault();
|
|
852
|
+
|
|
853
|
+
var $button = $(this);
|
|
854
|
+
var $row = $button.closest('tr');
|
|
855
|
+
var itemId = $button.data('item-id');
|
|
856
|
+
|
|
857
|
+
var newTitle = prompt('Enter new title:', $row.find('.acm-item-title').text());
|
|
858
|
+
if (!newTitle) {
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
$.ajax({
|
|
863
|
+
url: acmAdminAjax.ajaxUrl,
|
|
864
|
+
type: 'POST',
|
|
865
|
+
data: {
|
|
866
|
+
action: 'acm_update_item',
|
|
867
|
+
nonce: acmAdminAjax.nonce,
|
|
868
|
+
item_id: itemId,
|
|
869
|
+
title: newTitle,
|
|
870
|
+
content: '', // Could get from a modal or inline editor
|
|
871
|
+
status: 'active'
|
|
872
|
+
},
|
|
873
|
+
success: function(response) {
|
|
874
|
+
if (response.success) {
|
|
875
|
+
alert(response.data.message);
|
|
876
|
+
loadItems(); // Reload items list
|
|
877
|
+
} else {
|
|
878
|
+
alert('Error: ' + response.data.message);
|
|
879
|
+
}
|
|
880
|
+
},
|
|
881
|
+
error: function(xhr, status, error) {
|
|
882
|
+
alert('An error occurred: ' + error);
|
|
883
|
+
}
|
|
884
|
+
});
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
/**
|
|
888
|
+
* Load items with pagination
|
|
889
|
+
*/
|
|
890
|
+
function loadItems(page) {
|
|
891
|
+
page = page || 1;
|
|
892
|
+
|
|
893
|
+
var $container = $('#acm-items-list');
|
|
894
|
+
$container.html('<p>Loading items...</p>');
|
|
895
|
+
|
|
896
|
+
$.ajax({
|
|
897
|
+
url: acmAdminAjax.ajaxUrl,
|
|
898
|
+
type: 'POST',
|
|
899
|
+
data: {
|
|
900
|
+
action: 'acm_get_items',
|
|
901
|
+
nonce: acmAdminAjax.nonce,
|
|
902
|
+
page: page,
|
|
903
|
+
per_page: 10
|
|
904
|
+
},
|
|
905
|
+
success: function(response) {
|
|
906
|
+
if (response.success) {
|
|
907
|
+
renderItems(response.data);
|
|
908
|
+
} else {
|
|
909
|
+
$container.html('<p class=\"error\">' + response.data.message + '</p>');
|
|
910
|
+
}
|
|
911
|
+
},
|
|
912
|
+
error: function(xhr, status, error) {
|
|
913
|
+
$container.html('<p class=\"error\">Failed to load items: ' + error + '</p>');
|
|
914
|
+
}
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
/**
|
|
919
|
+
* Render items table
|
|
920
|
+
*/
|
|
921
|
+
function renderItems(data) {
|
|
922
|
+
var $container = $('#acm-items-list');
|
|
923
|
+
|
|
924
|
+
if (data.items.length === 0) {
|
|
925
|
+
$container.html('<p>No items found.</p>');
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
var html = '<table class=\"wp-list-table widefat fixed striped\">';
|
|
930
|
+
html += '<thead><tr>';
|
|
931
|
+
html += '<th>ID</th>';
|
|
932
|
+
html += '<th>Title</th>';
|
|
933
|
+
html += '<th>Status</th>';
|
|
934
|
+
html += '<th>Created</th>';
|
|
935
|
+
html += '<th>Actions</th>';
|
|
936
|
+
html += '</tr></thead>';
|
|
937
|
+
html += '<tbody>';
|
|
938
|
+
|
|
939
|
+
$.each(data.items, function(index, item) {
|
|
940
|
+
html += '<tr>';
|
|
941
|
+
html += '<td>' + item.id + '</td>';
|
|
942
|
+
html += '<td class=\"acm-item-title\">' + item.title + '</td>';
|
|
943
|
+
html += '<td>' + item.status + '</td>';
|
|
944
|
+
html += '<td>' + item.created_at + '</td>';
|
|
945
|
+
html += '<td>';
|
|
946
|
+
html += '<button class=\"button acm-update-item\" data-item-id=\"' + item.id + '\">Edit</button> ';
|
|
947
|
+
html += '<button class=\"button acm-delete-item\" data-item-id=\"' + item.id + '\">Delete</button>';
|
|
948
|
+
html += '</td>';
|
|
949
|
+
html += '</tr>';
|
|
950
|
+
});
|
|
951
|
+
|
|
952
|
+
html += '</tbody></table>';
|
|
953
|
+
|
|
954
|
+
// Add pagination
|
|
955
|
+
if (data.total_pages > 1) {
|
|
956
|
+
html += '<div class=\"acm-pagination\">';
|
|
957
|
+
for (var i = 1; i <= data.total_pages; i++) {
|
|
958
|
+
var activeClass = (i === data.page) ? ' button-primary' : '';
|
|
959
|
+
html += '<button class=\"button acm-page-btn' + activeClass + '\" data-page=\"' + i + '\">' + i + '</button> ';
|
|
960
|
+
}
|
|
961
|
+
html += '</div>';
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
$container.html(html);
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
/**
|
|
968
|
+
* Pagination click
|
|
969
|
+
*/
|
|
970
|
+
$(document).on('click', '.acm-page-btn', function() {
|
|
971
|
+
var page = $(this).data('page');
|
|
972
|
+
loadItems(page);
|
|
973
|
+
});
|
|
974
|
+
|
|
975
|
+
});
|
|
976
|
+
|
|
977
|
+
})(jQuery);
|
|
978
|
+
```
|
|
979
|
+
|
|
980
|
+
---
|
|
981
|
+
|
|
982
|
+
## Admin View
|
|
983
|
+
|
|
984
|
+
### File: `admin/views/admin-page.php`
|
|
985
|
+
|
|
986
|
+
```php
|
|
987
|
+
<?php
|
|
988
|
+
/**
|
|
989
|
+
* Admin page view
|
|
990
|
+
*
|
|
991
|
+
* @package AJAX_Content_Manager
|
|
992
|
+
*/
|
|
993
|
+
|
|
994
|
+
// Exit if accessed directly
|
|
995
|
+
if (!defined('ABSPATH')) {
|
|
996
|
+
exit;
|
|
997
|
+
}
|
|
998
|
+
?>
|
|
999
|
+
|
|
1000
|
+
<div class="wrap acm-admin-wrap">
|
|
1001
|
+
<h1><?php echo esc_html(get_admin_page_title()); ?></h1>
|
|
1002
|
+
|
|
1003
|
+
<div class="acm-admin-container">
|
|
1004
|
+
<div class="acm-admin-main">
|
|
1005
|
+
<div class="acm-section">
|
|
1006
|
+
<h2><?php _e('Create New Item', 'ajax-content-manager'); ?></h2>
|
|
1007
|
+
<form id="acm-create-form">
|
|
1008
|
+
<table class="form-table">
|
|
1009
|
+
<tr>
|
|
1010
|
+
<th><label for="acm-title"><?php _e('Title', 'ajax-content-manager'); ?></label></th>
|
|
1011
|
+
<td>
|
|
1012
|
+
<input type="text" id="acm-title" class="regular-text" required>
|
|
1013
|
+
</td>
|
|
1014
|
+
</tr>
|
|
1015
|
+
<tr>
|
|
1016
|
+
<th><label for="acm-content"><?php _e('Content', 'ajax-content-manager'); ?></label></th>
|
|
1017
|
+
<td>
|
|
1018
|
+
<textarea id="acm-content" rows="5" class="large-text"></textarea>
|
|
1019
|
+
</td>
|
|
1020
|
+
</tr>
|
|
1021
|
+
</table>
|
|
1022
|
+
<p class="submit">
|
|
1023
|
+
<button type="submit" class="button button-primary"><?php _e('Create Item', 'ajax-content-manager'); ?></button>
|
|
1024
|
+
</p>
|
|
1025
|
+
</form>
|
|
1026
|
+
</div>
|
|
1027
|
+
|
|
1028
|
+
<div class="acm-section">
|
|
1029
|
+
<h2><?php _e('Items List', 'ajax-content-manager'); ?></h2>
|
|
1030
|
+
<div id="acm-items-list">
|
|
1031
|
+
<p><?php _e('Loading items...', 'ajax-content-manager'); ?></p>
|
|
1032
|
+
</div>
|
|
1033
|
+
</div>
|
|
1034
|
+
</div>
|
|
1035
|
+
|
|
1036
|
+
<div class="acm-admin-sidebar">
|
|
1037
|
+
<div class="acm-sidebar-box">
|
|
1038
|
+
<h3><?php _e('About AJAX', 'ajax-content-manager'); ?></h3>
|
|
1039
|
+
<p><?php _e('This plugin demonstrates WordPress AJAX functionality with proper security and error handling.', 'ajax-content-manager'); ?></p>
|
|
1040
|
+
</div>
|
|
1041
|
+
|
|
1042
|
+
<div class="acm-sidebar-box">
|
|
1043
|
+
<h3><?php _e('Features', 'ajax-content-manager'); ?></h3>
|
|
1044
|
+
<ul>
|
|
1045
|
+
<li><?php _e('Nonce verification', 'ajax-content-manager'); ?></li>
|
|
1046
|
+
<li><?php _e('JSON responses', 'ajax-content-manager'); ?></li>
|
|
1047
|
+
<li><?php _e('Error handling', 'ajax-content-manager'); ?></li>
|
|
1048
|
+
<li><?php _e('Capability checks', 'ajax-content-manager'); ?></li>
|
|
1049
|
+
<li><?php _e('Input sanitization', 'ajax-content-manager'); ?></li>
|
|
1050
|
+
</ul>
|
|
1051
|
+
</div>
|
|
1052
|
+
</div>
|
|
1053
|
+
</div>
|
|
1054
|
+
</div>
|
|
1055
|
+
```
|
|
1056
|
+
|
|
1057
|
+
---
|
|
1058
|
+
|
|
1059
|
+
## CSS Files
|
|
1060
|
+
|
|
1061
|
+
### File: `public/css/public.css`
|
|
1062
|
+
|
|
1063
|
+
```css
|
|
1064
|
+
/* AJAX Content Container */
|
|
1065
|
+
#acm-content-container {
|
|
1066
|
+
padding: 20px;
|
|
1067
|
+
background: #f9f9f9;
|
|
1068
|
+
border: 1px solid #ddd;
|
|
1069
|
+
border-radius: 4px;
|
|
1070
|
+
margin: 20px 0;
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
.acm-post {
|
|
1074
|
+
background: #fff;
|
|
1075
|
+
padding: 20px;
|
|
1076
|
+
border-radius: 4px;
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
.acm-post h2 {
|
|
1080
|
+
margin-top: 0;
|
|
1081
|
+
color: #333;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
.acm-meta {
|
|
1085
|
+
color: #666;
|
|
1086
|
+
font-size: 14px;
|
|
1087
|
+
margin-bottom: 15px;
|
|
1088
|
+
padding-bottom: 10px;
|
|
1089
|
+
border-bottom: 1px solid #eee;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
.acm-meta span {
|
|
1093
|
+
margin-right: 15px;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
.acm-content {
|
|
1097
|
+
line-height: 1.6;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
/* Contact Form */
|
|
1101
|
+
#acm-contact-form {
|
|
1102
|
+
max-width: 600px;
|
|
1103
|
+
margin: 20px 0;
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
#acm-contact-form label {
|
|
1107
|
+
display: block;
|
|
1108
|
+
margin-bottom: 5px;
|
|
1109
|
+
font-weight: 600;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
#acm-contact-form input[type="text"],
|
|
1113
|
+
#acm-contact-form input[type="email"],
|
|
1114
|
+
#acm-contact-form textarea {
|
|
1115
|
+
width: 100%;
|
|
1116
|
+
padding: 10px;
|
|
1117
|
+
border: 1px solid #ddd;
|
|
1118
|
+
border-radius: 4px;
|
|
1119
|
+
margin-bottom: 15px;
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
#acm-contact-form button {
|
|
1123
|
+
background: #0073aa;
|
|
1124
|
+
color: #fff;
|
|
1125
|
+
padding: 10px 20px;
|
|
1126
|
+
border: none;
|
|
1127
|
+
border-radius: 4px;
|
|
1128
|
+
cursor: pointer;
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
#acm-contact-form button:hover {
|
|
1132
|
+
background: #005177;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
#acm-contact-form button:disabled {
|
|
1136
|
+
background: #ccc;
|
|
1137
|
+
cursor: not-allowed;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
/* Messages */
|
|
1141
|
+
#acm-form-message .success {
|
|
1142
|
+
color: #46b450;
|
|
1143
|
+
background: #ecf7ed;
|
|
1144
|
+
padding: 10px;
|
|
1145
|
+
border-left: 4px solid #46b450;
|
|
1146
|
+
margin: 15px 0;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
#acm-form-message .error,
|
|
1150
|
+
.error {
|
|
1151
|
+
color: #dc3232;
|
|
1152
|
+
background: #fbeaea;
|
|
1153
|
+
padding: 10px;
|
|
1154
|
+
border-left: 4px solid #dc3232;
|
|
1155
|
+
margin: 15px 0;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
/* Search */
|
|
1159
|
+
#acm-search {
|
|
1160
|
+
width: 100%;
|
|
1161
|
+
max-width: 400px;
|
|
1162
|
+
padding: 10px;
|
|
1163
|
+
border: 1px solid #ddd;
|
|
1164
|
+
border-radius: 4px;
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
#acm-search-results {
|
|
1168
|
+
margin-top: 15px;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
#acm-search-results ul {
|
|
1172
|
+
list-style: none;
|
|
1173
|
+
padding: 0;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
#acm-search-results li {
|
|
1177
|
+
padding: 10px;
|
|
1178
|
+
border-bottom: 1px solid #eee;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
#acm-search-results li:last-child {
|
|
1182
|
+
border-bottom: none;
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
#acm-search-results a {
|
|
1186
|
+
text-decoration: none;
|
|
1187
|
+
color: #0073aa;
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
#acm-search-results a:hover {
|
|
1191
|
+
text-decoration: underline;
|
|
1192
|
+
}
|
|
1193
|
+
```
|
|
1194
|
+
|
|
1195
|
+
### File: `admin/css/admin.css`
|
|
1196
|
+
|
|
1197
|
+
```css
|
|
1198
|
+
/* Admin Layout */
|
|
1199
|
+
.acm-admin-wrap {
|
|
1200
|
+
margin: 20px 20px 0 0;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
.acm-admin-container {
|
|
1204
|
+
display: flex;
|
|
1205
|
+
gap: 30px;
|
|
1206
|
+
margin-top: 20px;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
.acm-admin-main {
|
|
1210
|
+
flex: 1;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
.acm-admin-sidebar {
|
|
1214
|
+
width: 300px;
|
|
1215
|
+
flex-shrink: 0;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
@media (max-width: 1024px) {
|
|
1219
|
+
.acm-admin-container {
|
|
1220
|
+
flex-direction: column;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
.acm-admin-sidebar {
|
|
1224
|
+
width: 100%;
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
/* Sections */
|
|
1229
|
+
.acm-section {
|
|
1230
|
+
background: #fff;
|
|
1231
|
+
padding: 20px;
|
|
1232
|
+
margin-bottom: 20px;
|
|
1233
|
+
border: 1px solid #ccd0d4;
|
|
1234
|
+
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04);
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
.acm-section h2 {
|
|
1238
|
+
margin-top: 0;
|
|
1239
|
+
padding-bottom: 10px;
|
|
1240
|
+
border-bottom: 1px solid #e0e0e0;
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
/* Items List */
|
|
1244
|
+
#acm-items-list {
|
|
1245
|
+
margin-top: 15px;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
#acm-items-list table {
|
|
1249
|
+
margin-top: 0;
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
/* Pagination */
|
|
1253
|
+
.acm-pagination {
|
|
1254
|
+
margin-top: 15px;
|
|
1255
|
+
text-align: center;
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
.acm-pagination .button {
|
|
1259
|
+
margin: 0 2px;
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
/* Sidebar */
|
|
1263
|
+
.acm-sidebar-box {
|
|
1264
|
+
background: #fff;
|
|
1265
|
+
padding: 20px;
|
|
1266
|
+
margin-bottom: 20px;
|
|
1267
|
+
border: 1px solid #ccd0d4;
|
|
1268
|
+
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04);
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
.acm-sidebar-box h3 {
|
|
1272
|
+
margin-top: 0;
|
|
1273
|
+
margin-bottom: 15px;
|
|
1274
|
+
font-size: 14px;
|
|
1275
|
+
font-weight: 600;
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
.acm-sidebar-box ul {
|
|
1279
|
+
margin: 0;
|
|
1280
|
+
padding-left: 20px;
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
.acm-sidebar-box li {
|
|
1284
|
+
margin-bottom: 8px;
|
|
1285
|
+
font-size: 13px;
|
|
1286
|
+
}
|
|
1287
|
+
```
|
|
1288
|
+
|
|
1289
|
+
---
|
|
1290
|
+
|
|
1291
|
+
## Uninstall
|
|
1292
|
+
|
|
1293
|
+
### File: `uninstall.php`
|
|
1294
|
+
|
|
1295
|
+
```php
|
|
1296
|
+
<?php
|
|
1297
|
+
/**
|
|
1298
|
+
* Uninstall script
|
|
1299
|
+
*
|
|
1300
|
+
* @package AJAX_Content_Manager
|
|
1301
|
+
*/
|
|
1302
|
+
|
|
1303
|
+
// Exit if accessed directly or not uninstalling
|
|
1304
|
+
if (!defined('WP_UNINSTALL_PLUGIN')) {
|
|
1305
|
+
exit;
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
// Delete custom table
|
|
1309
|
+
global $wpdb;
|
|
1310
|
+
$table_name = $wpdb->prefix . 'acm_items';
|
|
1311
|
+
$wpdb->query("DROP TABLE IF EXISTS $table_name");
|
|
1312
|
+
|
|
1313
|
+
// Delete options (if any)
|
|
1314
|
+
delete_option('acm_settings');
|
|
1315
|
+
|
|
1316
|
+
// Clear any cached data
|
|
1317
|
+
wp_cache_flush();
|
|
1318
|
+
```
|
|
1319
|
+
|
|
1320
|
+
---
|
|
1321
|
+
|
|
1322
|
+
## Usage Examples
|
|
1323
|
+
|
|
1324
|
+
### Frontend AJAX Example
|
|
1325
|
+
|
|
1326
|
+
**HTML:**
|
|
1327
|
+
```html
|
|
1328
|
+
<button class="acm-load-content" data-post-id="123">Load Post</button>
|
|
1329
|
+
<div id="acm-content-container"></div>
|
|
1330
|
+
|
|
1331
|
+
<form id="acm-contact-form">
|
|
1332
|
+
<label for="acm-name">Name</label>
|
|
1333
|
+
<input type="text" id="acm-name" required>
|
|
1334
|
+
|
|
1335
|
+
<label for="acm-email">Email</label>
|
|
1336
|
+
<input type="email" id="acm-email" required>
|
|
1337
|
+
|
|
1338
|
+
<label for="acm-message">Message</label>
|
|
1339
|
+
<textarea id="acm-message" required></textarea>
|
|
1340
|
+
|
|
1341
|
+
<button type="submit">Submit</button>
|
|
1342
|
+
</form>
|
|
1343
|
+
<div id="acm-form-message"></div>
|
|
1344
|
+
```
|
|
1345
|
+
|
|
1346
|
+
### Admin AJAX Example
|
|
1347
|
+
|
|
1348
|
+
The admin page automatically loads items and provides create/update/delete functionality through AJAX.
|
|
1349
|
+
|
|
1350
|
+
### Custom AJAX Handler
|
|
1351
|
+
|
|
1352
|
+
```php
|
|
1353
|
+
<?php
|
|
1354
|
+
// Add to includes/class-ajax-handler.php
|
|
1355
|
+
|
|
1356
|
+
public static function custom_action() {
|
|
1357
|
+
// Verify nonce
|
|
1358
|
+
if (!check_ajax_referer('acm_public_nonce', 'nonce', false)) {
|
|
1359
|
+
wp_send_json_error(array(
|
|
1360
|
+
'message' => __('Security check failed.', 'ajax-content-manager'),
|
|
1361
|
+
), 403);
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
// Get and sanitize input
|
|
1365
|
+
$data = isset($_POST['data']) ? sanitize_text_field($_POST['data']) : '';
|
|
1366
|
+
|
|
1367
|
+
// Process data
|
|
1368
|
+
$result = do_something_with_data($data);
|
|
1369
|
+
|
|
1370
|
+
// Send response
|
|
1371
|
+
if ($result) {
|
|
1372
|
+
wp_send_json_success(array(
|
|
1373
|
+
'message' => __('Success!', 'ajax-content-manager'),
|
|
1374
|
+
'data' => $result,
|
|
1375
|
+
));
|
|
1376
|
+
} else {
|
|
1377
|
+
wp_send_json_error(array(
|
|
1378
|
+
'message' => __('Failed to process data.', 'ajax-content-manager'),
|
|
1379
|
+
), 500);
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
// Register the action
|
|
1384
|
+
add_action('wp_ajax_acm_custom_action', array(__CLASS__, 'custom_action'));
|
|
1385
|
+
add_action('wp_ajax_nopriv_acm_custom_action', array(__CLASS__, 'custom_action'));
|
|
1386
|
+
?>
|
|
1387
|
+
```
|
|
1388
|
+
|
|
1389
|
+
### JavaScript AJAX Call
|
|
1390
|
+
|
|
1391
|
+
```javascript
|
|
1392
|
+
$.ajax({
|
|
1393
|
+
url: acmAjax.ajaxUrl,
|
|
1394
|
+
type: 'POST',
|
|
1395
|
+
data: {
|
|
1396
|
+
action: 'acm_custom_action',
|
|
1397
|
+
nonce: acmAjax.nonce,
|
|
1398
|
+
data: 'your data here'
|
|
1399
|
+
},
|
|
1400
|
+
success: function(response) {
|
|
1401
|
+
if (response.success) {
|
|
1402
|
+
console.log('Success:', response.data);
|
|
1403
|
+
} else {
|
|
1404
|
+
console.error('Error:', response.data.message);
|
|
1405
|
+
}
|
|
1406
|
+
},
|
|
1407
|
+
error: function(xhr, status, error) {
|
|
1408
|
+
console.error('AJAX error:', error);
|
|
1409
|
+
}
|
|
1410
|
+
});
|
|
1411
|
+
```
|
|
1412
|
+
|
|
1413
|
+
---
|
|
1414
|
+
|
|
1415
|
+
## Key Features
|
|
1416
|
+
|
|
1417
|
+
### 1. Security
|
|
1418
|
+
- **Nonce verification**: All AJAX requests verified with `check_ajax_referer()`
|
|
1419
|
+
- **Capability checks**: Admin actions require `manage_options` capability
|
|
1420
|
+
- **Input sanitization**: All inputs sanitized with appropriate functions
|
|
1421
|
+
- **Output escaping**: All outputs escaped for security
|
|
1422
|
+
|
|
1423
|
+
### 2. AJAX Actions
|
|
1424
|
+
- **Public actions**: Available to all users (logged-in and non-logged-in)
|
|
1425
|
+
- **Admin actions**: Available only to logged-in users with proper capabilities
|
|
1426
|
+
- **Proper registration**: Using `wp_ajax_` and `wp_ajax_nopriv_` hooks
|
|
1427
|
+
|
|
1428
|
+
### 3. JSON Responses
|
|
1429
|
+
- **Success responses**: `wp_send_json_success($data)`
|
|
1430
|
+
- **Error responses**: `wp_send_json_error($data, $status_code)`
|
|
1431
|
+
- **Consistent format**: All responses follow WordPress standards
|
|
1432
|
+
|
|
1433
|
+
### 4. Error Handling
|
|
1434
|
+
- **Validation errors**: Clear error messages for invalid input
|
|
1435
|
+
- **Database errors**: Proper error handling for database operations
|
|
1436
|
+
- **AJAX errors**: JavaScript error handling with user feedback
|
|
1437
|
+
|
|
1438
|
+
### 5. User Experience
|
|
1439
|
+
- **Loading states**: Buttons disabled during AJAX requests
|
|
1440
|
+
- **Progress indicators**: "Loading..." messages
|
|
1441
|
+
- **Success/error messages**: Clear feedback to users
|
|
1442
|
+
- **Debouncing**: Search input debounced to reduce server load
|
|
1443
|
+
|
|
1444
|
+
---
|
|
1445
|
+
|
|
1446
|
+
## Best Practices Demonstrated
|
|
1447
|
+
|
|
1448
|
+
### Security
|
|
1449
|
+
✅ Nonce verification (`check_ajax_referer`)
|
|
1450
|
+
✅ Capability checks (`current_user_can`)
|
|
1451
|
+
✅ Input sanitization (`sanitize_text_field`, `sanitize_email`)
|
|
1452
|
+
✅ Output escaping (`esc_html`, `esc_attr`, `esc_url`)
|
|
1453
|
+
✅ Prepared statements (`$wpdb->prepare`)
|
|
1454
|
+
|
|
1455
|
+
### WordPress Standards
|
|
1456
|
+
✅ Proper AJAX action registration
|
|
1457
|
+
✅ `wp_send_json_success` and `wp_send_json_error`
|
|
1458
|
+
✅ `wp_localize_script` for passing data to JavaScript
|
|
1459
|
+
✅ Internationalization (`__()`, `_e()`)
|
|
1460
|
+
✅ WordPress coding standards
|
|
1461
|
+
|
|
1462
|
+
### JavaScript
|
|
1463
|
+
✅ jQuery document ready
|
|
1464
|
+
✅ Event delegation for dynamic elements
|
|
1465
|
+
✅ Debouncing for search inputs
|
|
1466
|
+
✅ Loading states and user feedback
|
|
1467
|
+
✅ Error handling
|
|
1468
|
+
|
|
1469
|
+
### Code Quality
|
|
1470
|
+
✅ Separation of concerns (handler, validator, manager)
|
|
1471
|
+
✅ Reusable AJAX handler methods
|
|
1472
|
+
✅ Consistent naming conventions
|
|
1473
|
+
✅ Proper documentation
|
|
1474
|
+
✅ Clean uninstall process
|
|
1475
|
+
|
|
1476
|
+
---
|
|
1477
|
+
|
|
1478
|
+
## Common AJAX Patterns
|
|
1479
|
+
|
|
1480
|
+
### 1. Load More Posts
|
|
1481
|
+
|
|
1482
|
+
```javascript
|
|
1483
|
+
$('.load-more').on('click', function() {
|
|
1484
|
+
var page = $(this).data('page');
|
|
1485
|
+
|
|
1486
|
+
$.ajax({
|
|
1487
|
+
url: acmAjax.ajaxUrl,
|
|
1488
|
+
type: 'POST',
|
|
1489
|
+
data: {
|
|
1490
|
+
action: 'acm_load_more_posts',
|
|
1491
|
+
nonce: acmAjax.nonce,
|
|
1492
|
+
page: page
|
|
1493
|
+
},
|
|
1494
|
+
success: function(response) {
|
|
1495
|
+
if (response.success) {
|
|
1496
|
+
$('.posts-container').append(response.data.html);
|
|
1497
|
+
$('.load-more').data('page', page + 1);
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
});
|
|
1501
|
+
});
|
|
1502
|
+
```
|
|
1503
|
+
|
|
1504
|
+
### 2. Like/Unlike Button
|
|
1505
|
+
|
|
1506
|
+
```javascript
|
|
1507
|
+
$('.like-button').on('click', function() {
|
|
1508
|
+
var postId = $(this).data('post-id');
|
|
1509
|
+
var $button = $(this);
|
|
1510
|
+
|
|
1511
|
+
$.ajax({
|
|
1512
|
+
url: acmAjax.ajaxUrl,
|
|
1513
|
+
type: 'POST',
|
|
1514
|
+
data: {
|
|
1515
|
+
action: 'acm_toggle_like',
|
|
1516
|
+
nonce: acmAjax.nonce,
|
|
1517
|
+
post_id: postId
|
|
1518
|
+
},
|
|
1519
|
+
success: function(response) {
|
|
1520
|
+
if (response.success) {
|
|
1521
|
+
$button.text(response.data.liked ? 'Unlike' : 'Like');
|
|
1522
|
+
$button.siblings('.like-count').text(response.data.count);
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
});
|
|
1526
|
+
});
|
|
1527
|
+
```
|
|
1528
|
+
|
|
1529
|
+
### 3. Auto-save Draft
|
|
1530
|
+
|
|
1531
|
+
```javascript
|
|
1532
|
+
var autoSaveTimeout;
|
|
1533
|
+
$('#content').on('keyup', function() {
|
|
1534
|
+
clearTimeout(autoSaveTimeout);
|
|
1535
|
+
|
|
1536
|
+
autoSaveTimeout = setTimeout(function() {
|
|
1537
|
+
$.ajax({
|
|
1538
|
+
url: acmAjax.ajaxUrl,
|
|
1539
|
+
type: 'POST',
|
|
1540
|
+
data: {
|
|
1541
|
+
action: 'acm_auto_save',
|
|
1542
|
+
nonce: acmAjax.nonce,
|
|
1543
|
+
content: $('#content').val()
|
|
1544
|
+
},
|
|
1545
|
+
success: function(response) {
|
|
1546
|
+
if (response.success) {
|
|
1547
|
+
$('.save-status').text('Saved at ' + new Date().toLocaleTimeString());
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
});
|
|
1551
|
+
}, 2000); // Auto-save after 2 seconds of inactivity
|
|
1552
|
+
});
|
|
1553
|
+
```
|
|
1554
|
+
|
|
1555
|
+
---
|
|
1556
|
+
|
|
1557
|
+
## Testing Checklist
|
|
1558
|
+
|
|
1559
|
+
- [ ] Test nonce verification (try with invalid nonce)
|
|
1560
|
+
- [ ] Test capability checks (try as non-admin user)
|
|
1561
|
+
- [ ] Test input validation (submit invalid data)
|
|
1562
|
+
- [ ] Test success responses
|
|
1563
|
+
- [ ] Test error responses
|
|
1564
|
+
- [ ] Test loading states
|
|
1565
|
+
- [ ] Test pagination
|
|
1566
|
+
- [ ] Test search debouncing
|
|
1567
|
+
- [ ] Test form submission
|
|
1568
|
+
- [ ] Test CRUD operations
|
|
1569
|
+
- [ ] Check for JavaScript errors in console
|
|
1570
|
+
- [ ] Test on different browsers
|
|
1571
|
+
- [ ] Test with slow network (throttling)
|
|
1572
|
+
|
|
1573
|
+
---
|
|
1574
|
+
|
|
1575
|
+
## Comparison with Other Patterns
|
|
1576
|
+
|
|
1577
|
+
| Feature | This Plugin | Basic AJAX | Advanced AJAX | REST API |\n|---------|-------------|------------|---------------|----------|\n| **Complexity** | Medium | Low | High | High |\n| **Security** | ✅ Full | ⚠️ Basic | ✅ Full | ✅ Full |\n| **Nonce Verification** | ✅ | ❌ | ✅ | ✅ |\n| **JSON Responses** | ✅ | ⚠️ | ✅ | ✅ |\n| **Error Handling** | ✅ Advanced | ❌ | ✅ Advanced | ✅ Advanced |\n| **Capability Checks** | ✅ | ❌ | ✅ | ✅ |\n| **Loading States** | ✅ | ❌ | ✅ | ✅ |\n| **Best For** | Most plugins | Simple tasks | Complex apps | APIs |\n\n---
|
|
1578
|
+
|
|
1579
|
+
## Summary
|
|
1580
|
+
|
|
1581
|
+
This example demonstrates a **complete AJAX plugin** with:
|
|
1582
|
+
|
|
1583
|
+
- ✅ WordPress AJAX API integration
|
|
1584
|
+
- ✅ Nonce verification for security
|
|
1585
|
+
- ✅ JSON responses with `wp_send_json_success` and `wp_send_json_error`
|
|
1586
|
+
- ✅ Both frontend and admin AJAX functionality
|
|
1587
|
+
- ✅ Proper error handling and validation
|
|
1588
|
+
- ✅ Loading states and user feedback
|
|
1589
|
+
- ✅ Capability checks for admin actions
|
|
1590
|
+
- ✅ Input sanitization and output escaping
|
|
1591
|
+
- ✅ Debouncing for search inputs
|
|
1592
|
+
- ✅ Pagination support
|
|
1593
|
+
- ✅ CRUD operations via AJAX
|
|
1594
|
+
- ✅ Clean uninstall process
|
|
1595
|
+
|
|
1596
|
+
**Perfect for**: Dynamic content loading, form submissions, real-time updates, user interactions, admin interfaces, or any plugin requiring asynchronous communication with the server.
|
|
1597
|
+
|
|
1598
|
+
**Character Count**: ~28,000 characters
|
|
1599
|
+
|