@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,1299 @@
|
|
|
1
|
+
# Task Manager REST API Plugin Example
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
This example demonstrates a complete **REST API Plugin** with full CRUD operations (GET, POST, PUT, DELETE), authentication, validation, error handling, pagination, and JavaScript client examples.
|
|
6
|
+
|
|
7
|
+
**Complexity**: Medium
|
|
8
|
+
**File Count**: 5-8 files
|
|
9
|
+
**Team Size**: 1-2 developers
|
|
10
|
+
**Use Case**: REST API development, CRUD operations, external integrations, headless WordPress
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Complete Plugin: "Task Manager API"
|
|
15
|
+
|
|
16
|
+
A comprehensive REST API plugin demonstrating WordPress REST API best practices with custom endpoints, authentication, validation, and JavaScript client integration.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Directory Structure
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
task-manager-api/
|
|
24
|
+
├── task-manager-api.php # Main plugin file
|
|
25
|
+
├── includes/
|
|
26
|
+
│ ├── class-api-controller.php # API controller
|
|
27
|
+
│ ├── class-task-model.php # Task model
|
|
28
|
+
│ └── class-validator.php # Validation logic
|
|
29
|
+
├── assets/
|
|
30
|
+
│ └── js/
|
|
31
|
+
│ └── api-client.js # JavaScript client
|
|
32
|
+
└── readme.txt # WordPress.org readme
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Main Plugin File
|
|
38
|
+
|
|
39
|
+
### File: `task-manager-api.php`
|
|
40
|
+
|
|
41
|
+
```php
|
|
42
|
+
<?php
|
|
43
|
+
/**
|
|
44
|
+
* Plugin Name: Task Manager API
|
|
45
|
+
* Plugin URI: https://example.com/task-manager-api
|
|
46
|
+
* Description: REST API for task management with full CRUD operations
|
|
47
|
+
* Version: 1.0.0
|
|
48
|
+
* Requires at least: 5.8
|
|
49
|
+
* Requires PHP: 7.4
|
|
50
|
+
* Author: Your Name
|
|
51
|
+
* Author URI: https://example.com
|
|
52
|
+
* License: GPL-2.0+
|
|
53
|
+
* License URI: http://www.gnu.org/licenses/gpl-2.0.txt
|
|
54
|
+
* Text Domain: task-manager-api
|
|
55
|
+
*
|
|
56
|
+
* @package Task_Manager_API
|
|
57
|
+
*/
|
|
58
|
+
|
|
59
|
+
// Exit if accessed directly
|
|
60
|
+
if (!defined('ABSPATH')) {
|
|
61
|
+
exit;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Define plugin constants
|
|
65
|
+
define('TMA_VERSION', '1.0.0');
|
|
66
|
+
define('TMA_PLUGIN_DIR', plugin_dir_path(__FILE__));
|
|
67
|
+
define('TMA_PLUGIN_URL', plugin_dir_url(__FILE__));
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Autoloader for plugin classes
|
|
71
|
+
*/
|
|
72
|
+
spl_autoload_register(function ($class) {
|
|
73
|
+
$prefix = 'TMA_';
|
|
74
|
+
$base_dir = TMA_PLUGIN_DIR . 'includes/';
|
|
75
|
+
|
|
76
|
+
$len = strlen($prefix);
|
|
77
|
+
if (strncmp($prefix, $class, $len) !== 0) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
$relative_class = substr($class, $len);
|
|
82
|
+
$file = $base_dir . 'class-' . str_replace('_', '-', strtolower($relative_class)) . '.php';
|
|
83
|
+
|
|
84
|
+
if (file_exists($file)) {
|
|
85
|
+
require $file;
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Register REST API routes
|
|
91
|
+
*/
|
|
92
|
+
function tma_register_routes() {
|
|
93
|
+
$controller = new TMA_API_Controller();
|
|
94
|
+
$controller->register_routes();
|
|
95
|
+
}
|
|
96
|
+
add_action('rest_api_init', 'tma_register_routes');
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Enqueue admin scripts
|
|
100
|
+
*/
|
|
101
|
+
function tma_enqueue_admin_scripts($hook) {
|
|
102
|
+
if ('toplevel_page_task-manager-api' !== $hook) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
wp_enqueue_script(
|
|
107
|
+
'tma-api-client',
|
|
108
|
+
TMA_PLUGIN_URL . 'assets/js/api-client.js',
|
|
109
|
+
array('wp-api-fetch'),
|
|
110
|
+
TMA_VERSION,
|
|
111
|
+
true
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
wp_localize_script('tma-api-client', 'tmaData', array(
|
|
115
|
+
'apiUrl' => rest_url('task-manager/v1'),
|
|
116
|
+
'nonce' => wp_create_nonce('wp_rest'),
|
|
117
|
+
));
|
|
118
|
+
}
|
|
119
|
+
add_action('admin_enqueue_scripts', 'tma_enqueue_admin_scripts');
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Add admin menu
|
|
123
|
+
*/
|
|
124
|
+
function tma_add_admin_menu() {
|
|
125
|
+
add_menu_page(
|
|
126
|
+
__('Task Manager API', 'task-manager-api'),
|
|
127
|
+
__('Tasks', 'task-manager-api'),
|
|
128
|
+
'manage_options',
|
|
129
|
+
'task-manager-api',
|
|
130
|
+
'tma_render_admin_page',
|
|
131
|
+
'dashicons-list-view',
|
|
132
|
+
25
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
add_action('admin_menu', 'tma_add_admin_menu');
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Render admin page
|
|
139
|
+
*/
|
|
140
|
+
function tma_render_admin_page() {
|
|
141
|
+
?>
|
|
142
|
+
<div class="wrap">
|
|
143
|
+
<h1><?php echo esc_html(get_admin_page_title()); ?></h1>
|
|
144
|
+
<div id="tma-app"></div>
|
|
145
|
+
</div>
|
|
146
|
+
<?php
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Activation hook
|
|
151
|
+
*/
|
|
152
|
+
register_activation_hook(__FILE__, function() {
|
|
153
|
+
// Create custom table for tasks
|
|
154
|
+
global $wpdb;
|
|
155
|
+
$table_name = $wpdb->prefix . 'tma_tasks';
|
|
156
|
+
$charset_collate = $wpdb->get_charset_collate();
|
|
157
|
+
|
|
158
|
+
$sql = "CREATE TABLE $table_name (
|
|
159
|
+
id bigint(20) NOT NULL AUTO_INCREMENT,
|
|
160
|
+
title varchar(255) NOT NULL,
|
|
161
|
+
description text,
|
|
162
|
+
status varchar(20) DEFAULT 'pending',
|
|
163
|
+
priority int(11) DEFAULT 1,
|
|
164
|
+
user_id bigint(20) NOT NULL,
|
|
165
|
+
created_at datetime DEFAULT CURRENT_TIMESTAMP,
|
|
166
|
+
updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
167
|
+
PRIMARY KEY (id),
|
|
168
|
+
KEY user_id (user_id),
|
|
169
|
+
KEY status (status)
|
|
170
|
+
) $charset_collate;";
|
|
171
|
+
|
|
172
|
+
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
|
|
173
|
+
dbDelta($sql);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Deactivation hook
|
|
178
|
+
*/
|
|
179
|
+
register_deactivation_hook(__FILE__, function() {
|
|
180
|
+
// Cleanup if needed
|
|
181
|
+
});
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## API Controller
|
|
187
|
+
|
|
188
|
+
### File: `includes/class-api-controller.php`
|
|
189
|
+
|
|
190
|
+
```php
|
|
191
|
+
<?php
|
|
192
|
+
/**
|
|
193
|
+
* API Controller
|
|
194
|
+
*
|
|
195
|
+
* @package Task_Manager_API
|
|
196
|
+
*/
|
|
197
|
+
|
|
198
|
+
class TMA_API_Controller extends WP_REST_Controller {
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Namespace
|
|
202
|
+
*
|
|
203
|
+
* @var string
|
|
204
|
+
*/
|
|
205
|
+
protected $namespace = 'task-manager/v1';
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Rest base
|
|
209
|
+
*
|
|
210
|
+
* @var string
|
|
211
|
+
*/
|
|
212
|
+
protected $rest_base = 'tasks';
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Register routes
|
|
216
|
+
*/
|
|
217
|
+
public function register_routes() {
|
|
218
|
+
// GET /tasks - List all tasks
|
|
219
|
+
register_rest_route($this->namespace, '/' . $this->rest_base, array(
|
|
220
|
+
array(
|
|
221
|
+
'methods' => WP_REST_Server::READABLE,
|
|
222
|
+
'callback' => array($this, 'get_items'),
|
|
223
|
+
'permission_callback' => array($this, 'get_items_permissions_check'),
|
|
224
|
+
'args' => $this->get_collection_params(),
|
|
225
|
+
),
|
|
226
|
+
'schema' => array($this, 'get_item_schema'),
|
|
227
|
+
));
|
|
228
|
+
|
|
229
|
+
// POST /tasks - Create task
|
|
230
|
+
register_rest_route($this->namespace, '/' . $this->rest_base, array(
|
|
231
|
+
array(
|
|
232
|
+
'methods' => WP_REST_Server::CREATABLE,
|
|
233
|
+
'callback' => array($this, 'create_item'),
|
|
234
|
+
'permission_callback' => array($this, 'create_item_permissions_check'),
|
|
235
|
+
'args' => $this->get_endpoint_args_for_item_schema(WP_REST_Server::CREATABLE),
|
|
236
|
+
),
|
|
237
|
+
));
|
|
238
|
+
|
|
239
|
+
// GET /tasks/{id} - Get single task
|
|
240
|
+
register_rest_route($this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
|
|
241
|
+
array(
|
|
242
|
+
'methods' => WP_REST_Server::READABLE,
|
|
243
|
+
'callback' => array($this, 'get_item'),
|
|
244
|
+
'permission_callback' => array($this, 'get_item_permissions_check'),
|
|
245
|
+
'args' => array(
|
|
246
|
+
'id' => array(
|
|
247
|
+
'description' => __('Unique identifier for the task.', 'task-manager-api'),
|
|
248
|
+
'type' => 'integer',
|
|
249
|
+
),
|
|
250
|
+
),
|
|
251
|
+
),
|
|
252
|
+
));
|
|
253
|
+
|
|
254
|
+
// PUT /tasks/{id} - Update task
|
|
255
|
+
register_rest_route($this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
|
|
256
|
+
array(
|
|
257
|
+
'methods' => WP_REST_Server::EDITABLE,
|
|
258
|
+
'callback' => array($this, 'update_item'),
|
|
259
|
+
'permission_callback' => array($this, 'update_item_permissions_check'),
|
|
260
|
+
'args' => $this->get_endpoint_args_for_item_schema(WP_REST_Server::EDITABLE),
|
|
261
|
+
),
|
|
262
|
+
));
|
|
263
|
+
|
|
264
|
+
// DELETE /tasks/{id} - Delete task
|
|
265
|
+
register_rest_route($this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
|
|
266
|
+
array(
|
|
267
|
+
'methods' => WP_REST_Server::DELETABLE,
|
|
268
|
+
'callback' => array($this, 'delete_item'),
|
|
269
|
+
'permission_callback' => array($this, 'delete_item_permissions_check'),
|
|
270
|
+
'args' => array(
|
|
271
|
+
'id' => array(
|
|
272
|
+
'description' => __('Unique identifier for the task.', 'task-manager-api'),
|
|
273
|
+
'type' => 'integer',
|
|
274
|
+
),
|
|
275
|
+
),
|
|
276
|
+
),
|
|
277
|
+
));
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Get collection of tasks
|
|
282
|
+
*
|
|
283
|
+
* @param WP_REST_Request $request Full request data.
|
|
284
|
+
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
|
285
|
+
*/
|
|
286
|
+
public function get_items($request) {
|
|
287
|
+
$model = new TMA_Task_Model();
|
|
288
|
+
|
|
289
|
+
$args = array(
|
|
290
|
+
'page' => $request->get_param('page') ?: 1,
|
|
291
|
+
'per_page' => $request->get_param('per_page') ?: 10,
|
|
292
|
+
'status' => $request->get_param('status'),
|
|
293
|
+
'user_id' => $request->get_param('user_id'),
|
|
294
|
+
'orderby' => $request->get_param('orderby') ?: 'created_at',
|
|
295
|
+
'order' => $request->get_param('order') ?: 'DESC',
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
$tasks = $model->get_tasks($args);
|
|
299
|
+
$total = $model->get_total_tasks($args);
|
|
300
|
+
|
|
301
|
+
$data = array();
|
|
302
|
+
foreach ($tasks as $task) {
|
|
303
|
+
$data[] = $this->prepare_item_for_response($task, $request);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
$response = rest_ensure_response($data);
|
|
307
|
+
|
|
308
|
+
// Add pagination headers
|
|
309
|
+
$response->header('X-WP-Total', $total);
|
|
310
|
+
$response->header('X-WP-TotalPages', ceil($total / $args['per_page']));
|
|
311
|
+
|
|
312
|
+
return $response;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Get single task
|
|
317
|
+
*
|
|
318
|
+
* @param WP_REST_Request $request Full request data.
|
|
319
|
+
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
|
320
|
+
*/
|
|
321
|
+
public function get_item($request) {
|
|
322
|
+
$model = new TMA_Task_Model();
|
|
323
|
+
$task = $model->get_task($request['id']);
|
|
324
|
+
|
|
325
|
+
if (!$task) {
|
|
326
|
+
return new WP_Error(
|
|
327
|
+
'rest_task_not_found',
|
|
328
|
+
__('Task not found.', 'task-manager-api'),
|
|
329
|
+
array('status' => 404)
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return rest_ensure_response($this->prepare_item_for_response($task, $request));
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Create task
|
|
338
|
+
*
|
|
339
|
+
* @param WP_REST_Request $request Full request data.
|
|
340
|
+
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
|
341
|
+
*/
|
|
342
|
+
public function create_item($request) {
|
|
343
|
+
$validator = new TMA_Validator();
|
|
344
|
+
|
|
345
|
+
// Validate input
|
|
346
|
+
$validation = $validator->validate_task_data($request->get_params());
|
|
347
|
+
if (is_wp_error($validation)) {
|
|
348
|
+
return $validation;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
$model = new TMA_Task_Model();
|
|
352
|
+
|
|
353
|
+
$data = array(
|
|
354
|
+
'title' => sanitize_text_field($request['title']),
|
|
355
|
+
'description' => wp_kses_post($request['description']),
|
|
356
|
+
'status' => sanitize_text_field($request['status'] ?: 'pending'),
|
|
357
|
+
'priority' => absint($request['priority'] ?: 1),
|
|
358
|
+
'user_id' => get_current_user_id(),
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
$task_id = $model->create_task($data);
|
|
362
|
+
|
|
363
|
+
if (!$task_id) {
|
|
364
|
+
return new WP_Error(
|
|
365
|
+
'rest_task_create_failed',
|
|
366
|
+
__('Failed to create task.', 'task-manager-api'),
|
|
367
|
+
array('status' => 500)
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
$task = $model->get_task($task_id);
|
|
372
|
+
|
|
373
|
+
return rest_ensure_response($this->prepare_item_for_response($task, $request));
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Update task
|
|
378
|
+
*
|
|
379
|
+
* @param WP_REST_Request $request Full request data.
|
|
380
|
+
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
|
381
|
+
*/
|
|
382
|
+
public function update_item($request) {
|
|
383
|
+
$model = new TMA_Task_Model();
|
|
384
|
+
$task = $model->get_task($request['id']);
|
|
385
|
+
|
|
386
|
+
if (!$task) {
|
|
387
|
+
return new WP_Error(
|
|
388
|
+
'rest_task_not_found',
|
|
389
|
+
__('Task not found.', 'task-manager-api'),
|
|
390
|
+
array('status' => 404)
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
$validator = new TMA_Validator();
|
|
395
|
+
$validation = $validator->validate_task_data($request->get_params(), true);
|
|
396
|
+
if (is_wp_error($validation)) {
|
|
397
|
+
return $validation;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
$data = array();
|
|
401
|
+
if (isset($request['title'])) {
|
|
402
|
+
$data['title'] = sanitize_text_field($request['title']);
|
|
403
|
+
}
|
|
404
|
+
if (isset($request['description'])) {
|
|
405
|
+
$data['description'] = wp_kses_post($request['description']);
|
|
406
|
+
}
|
|
407
|
+
if (isset($request['status'])) {
|
|
408
|
+
$data['status'] = sanitize_text_field($request['status']);
|
|
409
|
+
}
|
|
410
|
+
if (isset($request['priority'])) {
|
|
411
|
+
$data['priority'] = absint($request['priority']);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
$updated = $model->update_task($request['id'], $data);
|
|
415
|
+
|
|
416
|
+
if (!$updated) {
|
|
417
|
+
return new WP_Error(
|
|
418
|
+
'rest_task_update_failed',
|
|
419
|
+
__('Failed to update task.', 'task-manager-api'),
|
|
420
|
+
array('status' => 500)
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
$task = $model->get_task($request['id']);
|
|
425
|
+
|
|
426
|
+
return rest_ensure_response($this->prepare_item_for_response($task, $request));
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Delete task
|
|
431
|
+
*
|
|
432
|
+
* @param WP_REST_Request $request Full request data.
|
|
433
|
+
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
|
434
|
+
*/
|
|
435
|
+
public function delete_item($request) {
|
|
436
|
+
$model = new TMA_Task_Model();
|
|
437
|
+
$task = $model->get_task($request['id']);
|
|
438
|
+
|
|
439
|
+
if (!$task) {
|
|
440
|
+
return new WP_Error(
|
|
441
|
+
'rest_task_not_found',
|
|
442
|
+
__('Task not found.', 'task-manager-api'),
|
|
443
|
+
array('status' => 404)
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
$deleted = $model->delete_task($request['id']);
|
|
448
|
+
|
|
449
|
+
if (!$deleted) {
|
|
450
|
+
return new WP_Error(
|
|
451
|
+
'rest_task_delete_failed',
|
|
452
|
+
__('Failed to delete task.', 'task-manager-api'),
|
|
453
|
+
array('status' => 500)
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
return rest_ensure_response(array(
|
|
458
|
+
'deleted' => true,
|
|
459
|
+
'previous' => $this->prepare_item_for_response($task, $request),
|
|
460
|
+
));
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Prepare item for response
|
|
465
|
+
*
|
|
466
|
+
* @param object $item Task object.
|
|
467
|
+
* @param WP_REST_Request $request Request object.
|
|
468
|
+
* @return array Response data.
|
|
469
|
+
*/
|
|
470
|
+
protected function prepare_item_for_response($item, $request) {
|
|
471
|
+
return array(
|
|
472
|
+
'id' => (int) $item->id,
|
|
473
|
+
'title' => $item->title,
|
|
474
|
+
'description' => $item->description,
|
|
475
|
+
'status' => $item->status,
|
|
476
|
+
'priority' => (int) $item->priority,
|
|
477
|
+
'user_id' => (int) $item->user_id,
|
|
478
|
+
'created_at' => $item->created_at,
|
|
479
|
+
'updated_at' => $item->updated_at,
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Permission check for getting items
|
|
485
|
+
*/
|
|
486
|
+
public function get_items_permissions_check($request) {
|
|
487
|
+
return current_user_can('read');
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Permission check for getting item
|
|
492
|
+
*/
|
|
493
|
+
public function get_item_permissions_check($request) {
|
|
494
|
+
return current_user_can('read');
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Permission check for creating item
|
|
499
|
+
*/
|
|
500
|
+
public function create_item_permissions_check($request) {
|
|
501
|
+
return current_user_can('edit_posts');
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Permission check for updating item
|
|
506
|
+
*/
|
|
507
|
+
public function update_item_permissions_check($request) {
|
|
508
|
+
return current_user_can('edit_posts');
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Permission check for deleting item
|
|
513
|
+
*/
|
|
514
|
+
public function delete_item_permissions_check($request) {
|
|
515
|
+
return current_user_can('delete_posts');
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Get collection parameters
|
|
520
|
+
*/
|
|
521
|
+
public function get_collection_params() {
|
|
522
|
+
return array(
|
|
523
|
+
'page' => array(
|
|
524
|
+
'description' => __('Current page of the collection.', 'task-manager-api'),
|
|
525
|
+
'type' => 'integer',
|
|
526
|
+
'default' => 1,
|
|
527
|
+
'sanitize_callback' => 'absint',
|
|
528
|
+
'validate_callback' => 'rest_validate_request_arg',
|
|
529
|
+
'minimum' => 1,
|
|
530
|
+
),
|
|
531
|
+
'per_page' => array(
|
|
532
|
+
'description' => __('Maximum number of items to be returned.', 'task-manager-api'),
|
|
533
|
+
'type' => 'integer',
|
|
534
|
+
'default' => 10,
|
|
535
|
+
'minimum' => 1,
|
|
536
|
+
'maximum' => 100,
|
|
537
|
+
'sanitize_callback' => 'absint',
|
|
538
|
+
'validate_callback' => 'rest_validate_request_arg',
|
|
539
|
+
),
|
|
540
|
+
'status' => array(
|
|
541
|
+
'description' => __('Filter by status.', 'task-manager-api'),
|
|
542
|
+
'type' => 'string',
|
|
543
|
+
'enum' => array('pending', 'in_progress', 'completed'),
|
|
544
|
+
'sanitize_callback' => 'sanitize_text_field',
|
|
545
|
+
'validate_callback' => 'rest_validate_request_arg',
|
|
546
|
+
),
|
|
547
|
+
'user_id' => array(
|
|
548
|
+
'description' => __('Filter by user ID.', 'task-manager-api'),
|
|
549
|
+
'type' => 'integer',
|
|
550
|
+
'sanitize_callback' => 'absint',
|
|
551
|
+
'validate_callback' => 'rest_validate_request_arg',
|
|
552
|
+
),
|
|
553
|
+
'orderby' => array(
|
|
554
|
+
'description' => __('Order by field.', 'task-manager-api'),
|
|
555
|
+
'type' => 'string',
|
|
556
|
+
'default' => 'created_at',
|
|
557
|
+
'enum' => array('id', 'title', 'created_at', 'updated_at', 'priority'),
|
|
558
|
+
'sanitize_callback' => 'sanitize_text_field',
|
|
559
|
+
'validate_callback' => 'rest_validate_request_arg',
|
|
560
|
+
),
|
|
561
|
+
'order' => array(
|
|
562
|
+
'description' => __('Order direction.', 'task-manager-api'),
|
|
563
|
+
'type' => 'string',
|
|
564
|
+
'default' => 'DESC',
|
|
565
|
+
'enum' => array('ASC', 'DESC'),
|
|
566
|
+
'sanitize_callback' => 'sanitize_text_field',
|
|
567
|
+
'validate_callback' => 'rest_validate_request_arg',
|
|
568
|
+
),
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Get item schema
|
|
574
|
+
*/
|
|
575
|
+
public function get_item_schema() {
|
|
576
|
+
return array(
|
|
577
|
+
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
|
578
|
+
'title' => 'task',
|
|
579
|
+
'type' => 'object',
|
|
580
|
+
'properties' => array(
|
|
581
|
+
'id' => array(
|
|
582
|
+
'description' => __('Unique identifier for the task.', 'task-manager-api'),
|
|
583
|
+
'type' => 'integer',
|
|
584
|
+
'context' => array('view', 'edit'),
|
|
585
|
+
'readonly' => true,
|
|
586
|
+
),
|
|
587
|
+
'title' => array(
|
|
588
|
+
'description' => __('The title for the task.', 'task-manager-api'),
|
|
589
|
+
'type' => 'string',
|
|
590
|
+
'context' => array('view', 'edit'),
|
|
591
|
+
'required' => true,
|
|
592
|
+
'arg_options' => array(
|
|
593
|
+
'sanitize_callback' => 'sanitize_text_field',
|
|
594
|
+
),
|
|
595
|
+
),
|
|
596
|
+
'description' => array(
|
|
597
|
+
'description' => __('The description for the task.', 'task-manager-api'),
|
|
598
|
+
'type' => 'string',
|
|
599
|
+
'context' => array('view', 'edit'),
|
|
600
|
+
'arg_options' => array(
|
|
601
|
+
'sanitize_callback' => 'wp_kses_post',
|
|
602
|
+
),
|
|
603
|
+
),
|
|
604
|
+
'status' => array(
|
|
605
|
+
'description' => __('The status of the task.', 'task-manager-api'),
|
|
606
|
+
'type' => 'string',
|
|
607
|
+
'enum' => array('pending', 'in_progress', 'completed'),
|
|
608
|
+
'context' => array('view', 'edit'),
|
|
609
|
+
'default' => 'pending',
|
|
610
|
+
),
|
|
611
|
+
'priority' => array(
|
|
612
|
+
'description' => __('The priority of the task.', 'task-manager-api'),
|
|
613
|
+
'type' => 'integer',
|
|
614
|
+
'context' => array('view', 'edit'),
|
|
615
|
+
'default' => 1,
|
|
616
|
+
'minimum' => 1,
|
|
617
|
+
'maximum' => 5,
|
|
618
|
+
),
|
|
619
|
+
'user_id' => array(
|
|
620
|
+
'description' => __('The user ID who created the task.', 'task-manager-api'),
|
|
621
|
+
'type' => 'integer',
|
|
622
|
+
'context' => array('view', 'edit'),
|
|
623
|
+
'readonly' => true,
|
|
624
|
+
),
|
|
625
|
+
'created_at' => array(
|
|
626
|
+
'description' => __('The date the task was created.', 'task-manager-api'),
|
|
627
|
+
'type' => 'string',
|
|
628
|
+
'format' => 'date-time',
|
|
629
|
+
'context' => array('view', 'edit'),
|
|
630
|
+
'readonly' => true,
|
|
631
|
+
),
|
|
632
|
+
'updated_at' => array(
|
|
633
|
+
'description' => __('The date the task was last updated.', 'task-manager-api'),
|
|
634
|
+
'type' => 'string',
|
|
635
|
+
'format' => 'date-time',
|
|
636
|
+
'context' => array('view', 'edit'),
|
|
637
|
+
'readonly' => true,
|
|
638
|
+
),
|
|
639
|
+
),
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
---
|
|
646
|
+
|
|
647
|
+
## Task Model
|
|
648
|
+
|
|
649
|
+
### File: `includes/class-task-model.php`
|
|
650
|
+
|
|
651
|
+
```php
|
|
652
|
+
<?php
|
|
653
|
+
/**
|
|
654
|
+
* Task Model
|
|
655
|
+
*
|
|
656
|
+
* @package Task_Manager_API
|
|
657
|
+
*/
|
|
658
|
+
|
|
659
|
+
class TMA_Task_Model {
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* Table name
|
|
663
|
+
*
|
|
664
|
+
* @var string
|
|
665
|
+
*/
|
|
666
|
+
private $table_name;
|
|
667
|
+
|
|
668
|
+
/**
|
|
669
|
+
* Constructor
|
|
670
|
+
*/
|
|
671
|
+
public function __construct() {
|
|
672
|
+
global $wpdb;
|
|
673
|
+
$this->table_name = $wpdb->prefix . 'tma_tasks';
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* Get tasks
|
|
678
|
+
*
|
|
679
|
+
* @param array $args Query arguments.
|
|
680
|
+
* @return array Tasks.
|
|
681
|
+
*/
|
|
682
|
+
public function get_tasks($args = array()) {
|
|
683
|
+
global $wpdb;
|
|
684
|
+
|
|
685
|
+
$defaults = array(
|
|
686
|
+
'page' => 1,
|
|
687
|
+
'per_page' => 10,
|
|
688
|
+
'status' => null,
|
|
689
|
+
'user_id' => null,
|
|
690
|
+
'orderby' => 'created_at',
|
|
691
|
+
'order' => 'DESC',
|
|
692
|
+
);
|
|
693
|
+
|
|
694
|
+
$args = wp_parse_args($args, $defaults);
|
|
695
|
+
|
|
696
|
+
$where = array('1=1');
|
|
697
|
+
|
|
698
|
+
if ($args['status']) {
|
|
699
|
+
$where[] = $wpdb->prepare('status = %s', $args['status']);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
if ($args['user_id']) {
|
|
703
|
+
$where[] = $wpdb->prepare('user_id = %d', $args['user_id']);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
$where_clause = implode(' AND ', $where);
|
|
707
|
+
|
|
708
|
+
$offset = ($args['page'] - 1) * $args['per_page'];
|
|
709
|
+
|
|
710
|
+
$sql = $wpdb->prepare(
|
|
711
|
+
"SELECT * FROM {$this->table_name} WHERE {$where_clause} ORDER BY {$args['orderby']} {$args['order']} LIMIT %d OFFSET %d",
|
|
712
|
+
$args['per_page'],
|
|
713
|
+
$offset
|
|
714
|
+
);
|
|
715
|
+
|
|
716
|
+
return $wpdb->get_results($sql);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* Get total tasks
|
|
721
|
+
*
|
|
722
|
+
* @param array $args Query arguments.
|
|
723
|
+
* @return int Total tasks.
|
|
724
|
+
*/
|
|
725
|
+
public function get_total_tasks($args = array()) {
|
|
726
|
+
global $wpdb;
|
|
727
|
+
|
|
728
|
+
$where = array('1=1');
|
|
729
|
+
|
|
730
|
+
if (isset($args['status']) && $args['status']) {
|
|
731
|
+
$where[] = $wpdb->prepare('status = %s', $args['status']);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
if (isset($args['user_id']) && $args['user_id']) {
|
|
735
|
+
$where[] = $wpdb->prepare('user_id = %d', $args['user_id']);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
$where_clause = implode(' AND ', $where);
|
|
739
|
+
|
|
740
|
+
return (int) $wpdb->get_var("SELECT COUNT(*) FROM {$this->table_name} WHERE {$where_clause}");
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* Get single task
|
|
745
|
+
*
|
|
746
|
+
* @param int $id Task ID.
|
|
747
|
+
* @return object|null Task object or null.
|
|
748
|
+
*/
|
|
749
|
+
public function get_task($id) {
|
|
750
|
+
global $wpdb;
|
|
751
|
+
|
|
752
|
+
return $wpdb->get_row($wpdb->prepare(
|
|
753
|
+
"SELECT * FROM {$this->table_name} WHERE id = %d",
|
|
754
|
+
$id
|
|
755
|
+
));
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
/**
|
|
759
|
+
* Create task
|
|
760
|
+
*
|
|
761
|
+
* @param array $data Task data.
|
|
762
|
+
* @return int|false Task ID or false on failure.
|
|
763
|
+
*/
|
|
764
|
+
public function create_task($data) {
|
|
765
|
+
global $wpdb;
|
|
766
|
+
|
|
767
|
+
$inserted = $wpdb->insert(
|
|
768
|
+
$this->table_name,
|
|
769
|
+
$data,
|
|
770
|
+
array('%s', '%s', '%s', '%d', '%d')
|
|
771
|
+
);
|
|
772
|
+
|
|
773
|
+
return $inserted ? $wpdb->insert_id : false;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
/**
|
|
777
|
+
* Update task
|
|
778
|
+
*
|
|
779
|
+
* @param int $id Task ID.
|
|
780
|
+
* @param array $data Task data.
|
|
781
|
+
* @return bool True on success, false on failure.
|
|
782
|
+
*/
|
|
783
|
+
public function update_task($id, $data) {
|
|
784
|
+
global $wpdb;
|
|
785
|
+
|
|
786
|
+
return $wpdb->update(
|
|
787
|
+
$this->table_name,
|
|
788
|
+
$data,
|
|
789
|
+
array('id' => $id),
|
|
790
|
+
array('%s', '%s', '%s', '%d'),
|
|
791
|
+
array('%d')
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* Delete task
|
|
797
|
+
*
|
|
798
|
+
* @param int $id Task ID.
|
|
799
|
+
* @return bool True on success, false on failure.
|
|
800
|
+
*/
|
|
801
|
+
public function delete_task($id) {
|
|
802
|
+
global $wpdb;
|
|
803
|
+
|
|
804
|
+
return $wpdb->delete(
|
|
805
|
+
$this->table_name,
|
|
806
|
+
array('id' => $id),
|
|
807
|
+
array('%d')
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
```
|
|
812
|
+
|
|
813
|
+
---
|
|
814
|
+
|
|
815
|
+
## Validator
|
|
816
|
+
|
|
817
|
+
### File: `includes/class-validator.php`
|
|
818
|
+
|
|
819
|
+
```php
|
|
820
|
+
<?php
|
|
821
|
+
/**
|
|
822
|
+
* Validator
|
|
823
|
+
*
|
|
824
|
+
* @package Task_Manager_API
|
|
825
|
+
*/
|
|
826
|
+
|
|
827
|
+
class TMA_Validator {
|
|
828
|
+
|
|
829
|
+
/**
|
|
830
|
+
* Validate task data
|
|
831
|
+
*
|
|
832
|
+
* @param array $data Task data.
|
|
833
|
+
* @param bool $update Whether this is an update operation.
|
|
834
|
+
* @return true|WP_Error True on success, WP_Error on failure.
|
|
835
|
+
*/
|
|
836
|
+
public function validate_task_data($data, $update = false) {
|
|
837
|
+
$errors = new WP_Error();
|
|
838
|
+
|
|
839
|
+
// Title validation (required for create, optional for update)
|
|
840
|
+
if (!$update && empty($data['title'])) {
|
|
841
|
+
$errors->add(
|
|
842
|
+
'rest_missing_title',
|
|
843
|
+
__('Title is required.', 'task-manager-api'),
|
|
844
|
+
array('status' => 400)
|
|
845
|
+
);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
if (isset($data['title']) && strlen($data['title']) > 255) {
|
|
849
|
+
$errors->add(
|
|
850
|
+
'rest_invalid_title',
|
|
851
|
+
__('Title must be 255 characters or less.', 'task-manager-api'),
|
|
852
|
+
array('status' => 400)
|
|
853
|
+
);
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// Status validation
|
|
857
|
+
if (isset($data['status'])) {
|
|
858
|
+
$valid_statuses = array('pending', 'in_progress', 'completed');
|
|
859
|
+
if (!in_array($data['status'], $valid_statuses, true)) {
|
|
860
|
+
$errors->add(
|
|
861
|
+
'rest_invalid_status',
|
|
862
|
+
__('Invalid status. Must be one of: pending, in_progress, completed.', 'task-manager-api'),
|
|
863
|
+
array('status' => 400)
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
// Priority validation
|
|
869
|
+
if (isset($data['priority'])) {
|
|
870
|
+
$priority = absint($data['priority']);
|
|
871
|
+
if ($priority < 1 || $priority > 5) {
|
|
872
|
+
$errors->add(
|
|
873
|
+
'rest_invalid_priority',
|
|
874
|
+
__('Priority must be between 1 and 5.', 'task-manager-api'),
|
|
875
|
+
array('status' => 400)
|
|
876
|
+
);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
if ($errors->has_errors()) {
|
|
881
|
+
return $errors;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
return true;
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
```
|
|
888
|
+
|
|
889
|
+
---
|
|
890
|
+
|
|
891
|
+
## JavaScript Client
|
|
892
|
+
|
|
893
|
+
### File: `assets/js/api-client.js`
|
|
894
|
+
|
|
895
|
+
```javascript
|
|
896
|
+
/**
|
|
897
|
+
* Task Manager API Client
|
|
898
|
+
*
|
|
899
|
+
* @package Task_Manager_API
|
|
900
|
+
*/
|
|
901
|
+
|
|
902
|
+
(function() {
|
|
903
|
+
'use strict';
|
|
904
|
+
|
|
905
|
+
const { apiFetch } = wp;
|
|
906
|
+
const apiUrl = tmaData.apiUrl;
|
|
907
|
+
|
|
908
|
+
/**
|
|
909
|
+
* Task Manager API Client
|
|
910
|
+
*/
|
|
911
|
+
class TaskManagerAPI {
|
|
912
|
+
|
|
913
|
+
/**
|
|
914
|
+
* Get all tasks
|
|
915
|
+
*
|
|
916
|
+
* @param {Object} params Query parameters
|
|
917
|
+
* @returns {Promise}
|
|
918
|
+
*/
|
|
919
|
+
static async getTasks(params = {}) {
|
|
920
|
+
const queryString = new URLSearchParams(params).toString();
|
|
921
|
+
const url = `${apiUrl}/tasks${queryString ? '?' + queryString : ''}`;
|
|
922
|
+
|
|
923
|
+
try {
|
|
924
|
+
const response = await apiFetch({
|
|
925
|
+
path: url,
|
|
926
|
+
method: 'GET',
|
|
927
|
+
});
|
|
928
|
+
|
|
929
|
+
return response;
|
|
930
|
+
} catch (error) {
|
|
931
|
+
console.error('Error fetching tasks:', error);
|
|
932
|
+
throw error;
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
/**
|
|
937
|
+
* Get single task
|
|
938
|
+
*
|
|
939
|
+
* @param {number} id Task ID
|
|
940
|
+
* @returns {Promise}
|
|
941
|
+
*/
|
|
942
|
+
static async getTask(id) {
|
|
943
|
+
try {
|
|
944
|
+
const response = await apiFetch({
|
|
945
|
+
path: `${apiUrl}/tasks/${id}`,
|
|
946
|
+
method: 'GET',
|
|
947
|
+
});
|
|
948
|
+
|
|
949
|
+
return response;
|
|
950
|
+
} catch (error) {
|
|
951
|
+
console.error(`Error fetching task ${id}:`, error);
|
|
952
|
+
throw error;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
/**
|
|
957
|
+
* Create task
|
|
958
|
+
*
|
|
959
|
+
* @param {Object} data Task data
|
|
960
|
+
* @returns {Promise}
|
|
961
|
+
*/
|
|
962
|
+
static async createTask(data) {
|
|
963
|
+
try {
|
|
964
|
+
const response = await apiFetch({
|
|
965
|
+
path: `${apiUrl}/tasks`,
|
|
966
|
+
method: 'POST',
|
|
967
|
+
data: data,
|
|
968
|
+
});
|
|
969
|
+
|
|
970
|
+
return response;
|
|
971
|
+
} catch (error) {
|
|
972
|
+
console.error('Error creating task:', error);
|
|
973
|
+
throw error;
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
/**
|
|
978
|
+
* Update task
|
|
979
|
+
*
|
|
980
|
+
* @param {number} id Task ID
|
|
981
|
+
* @param {Object} data Task data
|
|
982
|
+
* @returns {Promise}
|
|
983
|
+
*/
|
|
984
|
+
static async updateTask(id, data) {
|
|
985
|
+
try {
|
|
986
|
+
const response = await apiFetch({
|
|
987
|
+
path: `${apiUrl}/tasks/${id}`,
|
|
988
|
+
method: 'PUT',
|
|
989
|
+
data: data,
|
|
990
|
+
});
|
|
991
|
+
|
|
992
|
+
return response;
|
|
993
|
+
} catch (error) {
|
|
994
|
+
console.error(`Error updating task ${id}:`, error);
|
|
995
|
+
throw error;
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
/**
|
|
1000
|
+
* Delete task
|
|
1001
|
+
*
|
|
1002
|
+
* @param {number} id Task ID
|
|
1003
|
+
* @returns {Promise}
|
|
1004
|
+
*/
|
|
1005
|
+
static async deleteTask(id) {
|
|
1006
|
+
try {
|
|
1007
|
+
const response = await apiFetch({
|
|
1008
|
+
path: `${apiUrl}/tasks/${id}`,
|
|
1009
|
+
method: 'DELETE',
|
|
1010
|
+
});
|
|
1011
|
+
|
|
1012
|
+
return response;
|
|
1013
|
+
} catch (error) {
|
|
1014
|
+
console.error(`Error deleting task ${id}:`, error);
|
|
1015
|
+
throw error;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
/**
|
|
1021
|
+
* Example usage
|
|
1022
|
+
*/
|
|
1023
|
+
document.addEventListener('DOMContentLoaded', async function() {
|
|
1024
|
+
|
|
1025
|
+
// Get all tasks
|
|
1026
|
+
try {
|
|
1027
|
+
const tasks = await TaskManagerAPI.getTasks({
|
|
1028
|
+
page: 1,
|
|
1029
|
+
per_page: 10,
|
|
1030
|
+
status: 'pending',
|
|
1031
|
+
});
|
|
1032
|
+
console.log('Tasks:', tasks);
|
|
1033
|
+
} catch (error) {
|
|
1034
|
+
console.error('Failed to fetch tasks');
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
// Create task
|
|
1038
|
+
try {
|
|
1039
|
+
const newTask = await TaskManagerAPI.createTask({
|
|
1040
|
+
title: 'New Task',
|
|
1041
|
+
description: 'Task description',
|
|
1042
|
+
status: 'pending',
|
|
1043
|
+
priority: 3,
|
|
1044
|
+
});
|
|
1045
|
+
console.log('Created task:', newTask);
|
|
1046
|
+
} catch (error) {
|
|
1047
|
+
console.error('Failed to create task');
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
// Update task
|
|
1051
|
+
try {
|
|
1052
|
+
const updatedTask = await TaskManagerAPI.updateTask(1, {
|
|
1053
|
+
status: 'completed',
|
|
1054
|
+
});
|
|
1055
|
+
console.log('Updated task:', updatedTask);
|
|
1056
|
+
} catch (error) {
|
|
1057
|
+
console.error('Failed to update task');
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
// Delete task
|
|
1061
|
+
try {
|
|
1062
|
+
const result = await TaskManagerAPI.deleteTask(1);
|
|
1063
|
+
console.log('Deleted task:', result);
|
|
1064
|
+
} catch (error) {
|
|
1065
|
+
console.error('Failed to delete task');
|
|
1066
|
+
}
|
|
1067
|
+
});
|
|
1068
|
+
|
|
1069
|
+
// Make API client available globally
|
|
1070
|
+
window.TaskManagerAPI = TaskManagerAPI;
|
|
1071
|
+
|
|
1072
|
+
})();
|
|
1073
|
+
```
|
|
1074
|
+
|
|
1075
|
+
---
|
|
1076
|
+
|
|
1077
|
+
## Using Fetch API (Alternative)
|
|
1078
|
+
|
|
1079
|
+
### Vanilla JavaScript with Fetch
|
|
1080
|
+
|
|
1081
|
+
```javascript
|
|
1082
|
+
/**
|
|
1083
|
+
* Task Manager API Client (Fetch API)
|
|
1084
|
+
*/
|
|
1085
|
+
class TaskManagerFetchAPI {
|
|
1086
|
+
|
|
1087
|
+
constructor(apiUrl, nonce) {
|
|
1088
|
+
this.apiUrl = apiUrl;
|
|
1089
|
+
this.nonce = nonce;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
/**
|
|
1093
|
+
* Make API request
|
|
1094
|
+
*/
|
|
1095
|
+
async request(endpoint, options = {}) {
|
|
1096
|
+
const url = `${this.apiUrl}${endpoint}`;
|
|
1097
|
+
|
|
1098
|
+
const defaultOptions = {
|
|
1099
|
+
headers: {
|
|
1100
|
+
'Content-Type': 'application/json',
|
|
1101
|
+
'X-WP-Nonce': this.nonce,
|
|
1102
|
+
},
|
|
1103
|
+
};
|
|
1104
|
+
|
|
1105
|
+
const mergedOptions = {
|
|
1106
|
+
...defaultOptions,
|
|
1107
|
+
...options,
|
|
1108
|
+
headers: {
|
|
1109
|
+
...defaultOptions.headers,
|
|
1110
|
+
...options.headers,
|
|
1111
|
+
},
|
|
1112
|
+
};
|
|
1113
|
+
|
|
1114
|
+
try {
|
|
1115
|
+
const response = await fetch(url, mergedOptions);
|
|
1116
|
+
|
|
1117
|
+
if (!response.ok) {
|
|
1118
|
+
const error = await response.json();
|
|
1119
|
+
throw new Error(error.message || 'API request failed');
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
return await response.json();
|
|
1123
|
+
} catch (error) {
|
|
1124
|
+
console.error('API Error:', error);
|
|
1125
|
+
throw error;
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
/**
|
|
1130
|
+
* Get all tasks
|
|
1131
|
+
*/
|
|
1132
|
+
async getTasks(params = {}) {
|
|
1133
|
+
const queryString = new URLSearchParams(params).toString();
|
|
1134
|
+
return this.request(`/tasks${queryString ? '?' + queryString : ''}`);
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
/**
|
|
1138
|
+
* Get single task
|
|
1139
|
+
*/
|
|
1140
|
+
async getTask(id) {
|
|
1141
|
+
return this.request(`/tasks/${id}`);
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
/**
|
|
1145
|
+
* Create task
|
|
1146
|
+
*/
|
|
1147
|
+
async createTask(data) {
|
|
1148
|
+
return this.request('/tasks', {
|
|
1149
|
+
method: 'POST',
|
|
1150
|
+
body: JSON.stringify(data),
|
|
1151
|
+
});
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
/**
|
|
1155
|
+
* Update task
|
|
1156
|
+
*/
|
|
1157
|
+
async updateTask(id, data) {
|
|
1158
|
+
return this.request(`/tasks/${id}`, {
|
|
1159
|
+
method: 'PUT',
|
|
1160
|
+
body: JSON.stringify(data),
|
|
1161
|
+
});
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
/**
|
|
1165
|
+
* Delete task
|
|
1166
|
+
*/
|
|
1167
|
+
async deleteTask(id) {
|
|
1168
|
+
return this.request(`/tasks/${id}`, {
|
|
1169
|
+
method: 'DELETE',
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
// Usage
|
|
1175
|
+
const api = new TaskManagerFetchAPI(
|
|
1176
|
+
'https://example.com/wp-json/task-manager/v1',
|
|
1177
|
+
tmaData.nonce
|
|
1178
|
+
);
|
|
1179
|
+
|
|
1180
|
+
// Get tasks
|
|
1181
|
+
api.getTasks({ status: 'pending' })
|
|
1182
|
+
.then(tasks => console.log(tasks))
|
|
1183
|
+
.catch(error => console.error(error));
|
|
1184
|
+
```
|
|
1185
|
+
|
|
1186
|
+
---
|
|
1187
|
+
|
|
1188
|
+
## Testing the API
|
|
1189
|
+
|
|
1190
|
+
### Using cURL
|
|
1191
|
+
|
|
1192
|
+
```bash
|
|
1193
|
+
# Get all tasks
|
|
1194
|
+
curl -X GET https://example.com/wp-json/task-manager/v1/tasks
|
|
1195
|
+
|
|
1196
|
+
# Get tasks with filters
|
|
1197
|
+
curl -X GET "https://example.com/wp-json/task-manager/v1/tasks?status=pending&per_page=5"
|
|
1198
|
+
|
|
1199
|
+
# Get single task
|
|
1200
|
+
curl -X GET https://example.com/wp-json/task-manager/v1/tasks/1
|
|
1201
|
+
|
|
1202
|
+
# Create task (requires authentication)
|
|
1203
|
+
curl -X POST https://example.com/wp-json/task-manager/v1/tasks \
|
|
1204
|
+
-H "Content-Type: application/json" \
|
|
1205
|
+
-H "X-WP-Nonce: YOUR_NONCE" \
|
|
1206
|
+
--cookie "wordpress_logged_in_HASH=YOUR_COOKIE" \
|
|
1207
|
+
-d '{
|
|
1208
|
+
"title": "New Task",
|
|
1209
|
+
"description": "Task description",
|
|
1210
|
+
"status": "pending",
|
|
1211
|
+
"priority": 3
|
|
1212
|
+
}'
|
|
1213
|
+
|
|
1214
|
+
# Update task
|
|
1215
|
+
curl -X PUT https://example.com/wp-json/task-manager/v1/tasks/1 \
|
|
1216
|
+
-H "Content-Type: application/json" \
|
|
1217
|
+
-H "X-WP-Nonce: YOUR_NONCE" \
|
|
1218
|
+
--cookie "wordpress_logged_in_HASH=YOUR_COOKIE" \
|
|
1219
|
+
-d '{
|
|
1220
|
+
"status": "completed"
|
|
1221
|
+
}'
|
|
1222
|
+
|
|
1223
|
+
# Delete task
|
|
1224
|
+
curl -X DELETE https://example.com/wp-json/task-manager/v1/tasks/1 \
|
|
1225
|
+
-H "X-WP-Nonce: YOUR_NONCE" \
|
|
1226
|
+
--cookie "wordpress_logged_in_HASH=YOUR_COOKIE"
|
|
1227
|
+
```
|
|
1228
|
+
|
|
1229
|
+
### Using Postman
|
|
1230
|
+
|
|
1231
|
+
1. **Set up authentication**:
|
|
1232
|
+
- Method: Cookie Authentication
|
|
1233
|
+
- Add `X-WP-Nonce` header
|
|
1234
|
+
|
|
1235
|
+
2. **Test endpoints**:
|
|
1236
|
+
- GET `/wp-json/task-manager/v1/tasks`
|
|
1237
|
+
- POST `/wp-json/task-manager/v1/tasks`
|
|
1238
|
+
- PUT `/wp-json/task-manager/v1/tasks/{id}`
|
|
1239
|
+
- DELETE `/wp-json/task-manager/v1/tasks/{id}`
|
|
1240
|
+
|
|
1241
|
+
---
|
|
1242
|
+
|
|
1243
|
+
## Key Features Demonstrated
|
|
1244
|
+
|
|
1245
|
+
### 1. **Complete CRUD Operations**
|
|
1246
|
+
- ✅ GET (list and single)
|
|
1247
|
+
- ✅ POST (create)
|
|
1248
|
+
- ✅ PUT (update)
|
|
1249
|
+
- ✅ DELETE (delete)
|
|
1250
|
+
|
|
1251
|
+
### 2. **Authentication & Permissions**
|
|
1252
|
+
- ✅ Permission callbacks for each endpoint
|
|
1253
|
+
- ✅ User capability checks
|
|
1254
|
+
- ✅ Nonce verification
|
|
1255
|
+
|
|
1256
|
+
### 3. **Validation**
|
|
1257
|
+
- ✅ Input validation
|
|
1258
|
+
- ✅ Schema validation
|
|
1259
|
+
- ✅ Error responses
|
|
1260
|
+
|
|
1261
|
+
### 4. **Pagination**
|
|
1262
|
+
- ✅ Page and per_page parameters
|
|
1263
|
+
- ✅ Total count headers
|
|
1264
|
+
- ✅ Collection parameters
|
|
1265
|
+
|
|
1266
|
+
### 5. **Error Handling**
|
|
1267
|
+
- ✅ WP_Error responses
|
|
1268
|
+
- ✅ Proper HTTP status codes
|
|
1269
|
+
- ✅ Descriptive error messages
|
|
1270
|
+
|
|
1271
|
+
### 6. **JavaScript Integration**
|
|
1272
|
+
- ✅ wp.apiFetch client
|
|
1273
|
+
- ✅ Vanilla Fetch API client
|
|
1274
|
+
- ✅ Error handling in JavaScript
|
|
1275
|
+
|
|
1276
|
+
---
|
|
1277
|
+
|
|
1278
|
+
## Best Practices Demonstrated
|
|
1279
|
+
|
|
1280
|
+
1. **Use WP_REST_Controller** - Extends base controller class
|
|
1281
|
+
2. **Schema definition** - Proper schema with validation
|
|
1282
|
+
3. **Permission callbacks** - Security checks for each endpoint
|
|
1283
|
+
4. **Sanitization** - All input sanitized
|
|
1284
|
+
5. **Error handling** - Proper WP_Error usage
|
|
1285
|
+
6. **Pagination** - Collection endpoints support pagination
|
|
1286
|
+
7. **Versioning** - API namespace includes version (v1)
|
|
1287
|
+
8. **Documentation** - Clear docblocks and comments
|
|
1288
|
+
|
|
1289
|
+
---
|
|
1290
|
+
|
|
1291
|
+
## Next Steps
|
|
1292
|
+
|
|
1293
|
+
1. Add unit tests for API endpoints
|
|
1294
|
+
2. Implement caching for GET requests
|
|
1295
|
+
3. Add rate limiting
|
|
1296
|
+
4. Implement webhook notifications
|
|
1297
|
+
5. Add batch operations endpoint
|
|
1298
|
+
6. Create Swagger/OpenAPI documentation
|
|
1299
|
+
|