@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,866 @@
|
|
|
1
|
+
# Security Best Practices
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
This guide covers critical security rules for WordPress plugin development including nonce verification, input sanitization, output escaping, capability checks, and prepared statements.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Nonce Verification
|
|
10
|
+
|
|
11
|
+
### What are Nonces?
|
|
12
|
+
|
|
13
|
+
Nonces (Number Used Once) are security tokens that protect against CSRF (Cross-Site Request Forgery) attacks.
|
|
14
|
+
|
|
15
|
+
### Creating Nonces
|
|
16
|
+
|
|
17
|
+
```php
|
|
18
|
+
<?php
|
|
19
|
+
/**
|
|
20
|
+
* Create nonce field in form
|
|
21
|
+
*/
|
|
22
|
+
function my_plugin_settings_form() {
|
|
23
|
+
?>
|
|
24
|
+
<form method="post" action="">
|
|
25
|
+
<?php wp_nonce_field( 'my_plugin_save_settings', 'my_plugin_nonce' ); ?>
|
|
26
|
+
|
|
27
|
+
<input type="text" name="setting_value" />
|
|
28
|
+
<input type="submit" value="Save" />
|
|
29
|
+
</form>
|
|
30
|
+
<?php
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Create nonce URL
|
|
35
|
+
*/
|
|
36
|
+
$delete_url = wp_nonce_url(
|
|
37
|
+
admin_url( 'admin.php?page=my-plugin&action=delete&id=123' ),
|
|
38
|
+
'delete_item_123'
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Create nonce value
|
|
43
|
+
*/
|
|
44
|
+
$nonce = wp_create_nonce( 'my_plugin_action' );
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Verifying Nonces
|
|
48
|
+
|
|
49
|
+
```php
|
|
50
|
+
<?php
|
|
51
|
+
/**
|
|
52
|
+
* Verify nonce in form submission
|
|
53
|
+
*/
|
|
54
|
+
function my_plugin_save_settings() {
|
|
55
|
+
// Check if nonce field exists and is valid
|
|
56
|
+
if ( ! isset( $_POST['my_plugin_nonce'] ) ||
|
|
57
|
+
! wp_verify_nonce( $_POST['my_plugin_nonce'], 'my_plugin_save_settings' ) ) {
|
|
58
|
+
wp_die( __( 'Security check failed', 'my-plugin' ) );
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Process form data
|
|
62
|
+
$value = sanitize_text_field( $_POST['setting_value'] );
|
|
63
|
+
update_option( 'my_plugin_setting', $value );
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Verify nonce in URL
|
|
68
|
+
*/
|
|
69
|
+
function my_plugin_delete_item() {
|
|
70
|
+
if ( ! isset( $_GET['_wpnonce'] ) ||
|
|
71
|
+
! wp_verify_nonce( $_GET['_wpnonce'], 'delete_item_' . $_GET['id'] ) ) {
|
|
72
|
+
wp_die( __( 'Security check failed', 'my-plugin' ) );
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Delete item
|
|
76
|
+
$item_id = absint( $_GET['id'] );
|
|
77
|
+
my_plugin_delete( $item_id );
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Verify nonce in AJAX
|
|
82
|
+
*/
|
|
83
|
+
function my_plugin_ajax_handler() {
|
|
84
|
+
check_ajax_referer( 'my_plugin_ajax_nonce', 'security' );
|
|
85
|
+
|
|
86
|
+
// Process AJAX request
|
|
87
|
+
$data = sanitize_text_field( $_POST['data'] );
|
|
88
|
+
wp_send_json_success( array( 'message' => 'Success' ) );
|
|
89
|
+
}
|
|
90
|
+
add_action( 'wp_ajax_my_plugin_action', 'my_plugin_ajax_handler' );
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Input Sanitization
|
|
96
|
+
|
|
97
|
+
### Text Fields
|
|
98
|
+
|
|
99
|
+
```php
|
|
100
|
+
<?php
|
|
101
|
+
// Sanitize text field (removes tags, encodes special chars)
|
|
102
|
+
$clean_text = sanitize_text_field( $_POST['field'] );
|
|
103
|
+
|
|
104
|
+
// Sanitize textarea (preserves line breaks)
|
|
105
|
+
$clean_textarea = sanitize_textarea_field( $_POST['textarea'] );
|
|
106
|
+
|
|
107
|
+
// Sanitize title (lowercase alphanumeric with dashes)
|
|
108
|
+
$clean_title = sanitize_title( $_POST['title'] );
|
|
109
|
+
|
|
110
|
+
// Sanitize key (lowercase alphanumeric with dashes and underscores)
|
|
111
|
+
$clean_key = sanitize_key( $_POST['key'] );
|
|
112
|
+
|
|
113
|
+
// Sanitize file name
|
|
114
|
+
$clean_filename = sanitize_file_name( $_FILES['file']['name'] );
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Email and URL
|
|
118
|
+
|
|
119
|
+
```php
|
|
120
|
+
<?php
|
|
121
|
+
// Sanitize email
|
|
122
|
+
$clean_email = sanitize_email( $_POST['email'] );
|
|
123
|
+
|
|
124
|
+
// Validate email
|
|
125
|
+
if ( ! is_email( $clean_email ) ) {
|
|
126
|
+
wp_die( __( 'Invalid email address', 'my-plugin' ) );
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Sanitize URL
|
|
130
|
+
$clean_url = esc_url_raw( $_POST['url'] );
|
|
131
|
+
|
|
132
|
+
// Validate URL
|
|
133
|
+
if ( ! filter_var( $clean_url, FILTER_VALIDATE_URL ) ) {
|
|
134
|
+
wp_die( __( 'Invalid URL', 'my-plugin' ) );
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Numbers
|
|
139
|
+
|
|
140
|
+
```php
|
|
141
|
+
<?php
|
|
142
|
+
// Sanitize integer (absolute integer)
|
|
143
|
+
$clean_int = absint( $_POST['number'] );
|
|
144
|
+
|
|
145
|
+
// Sanitize integer (can be negative)
|
|
146
|
+
$clean_int = intval( $_POST['number'] );
|
|
147
|
+
|
|
148
|
+
// Sanitize float
|
|
149
|
+
$clean_float = floatval( $_POST['price'] );
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### HTML Content
|
|
153
|
+
|
|
154
|
+
```php
|
|
155
|
+
<?php
|
|
156
|
+
// Sanitize HTML (allows safe HTML tags)
|
|
157
|
+
$clean_html = wp_kses_post( $_POST['content'] );
|
|
158
|
+
|
|
159
|
+
// Sanitize with custom allowed tags
|
|
160
|
+
$allowed_tags = array(
|
|
161
|
+
'a' => array(
|
|
162
|
+
'href' => array(),
|
|
163
|
+
'title' => array(),
|
|
164
|
+
),
|
|
165
|
+
'br' => array(),
|
|
166
|
+
'em' => array(),
|
|
167
|
+
'strong' => array(),
|
|
168
|
+
);
|
|
169
|
+
$clean_html = wp_kses( $_POST['content'], $allowed_tags );
|
|
170
|
+
|
|
171
|
+
// Strip all tags
|
|
172
|
+
$clean_text = wp_strip_all_tags( $_POST['content'] );
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Arrays
|
|
176
|
+
|
|
177
|
+
```php
|
|
178
|
+
<?php
|
|
179
|
+
// Sanitize array of text fields
|
|
180
|
+
$clean_array = array_map( 'sanitize_text_field', $_POST['items'] );
|
|
181
|
+
|
|
182
|
+
// Sanitize array of integers
|
|
183
|
+
$clean_ids = array_map( 'absint', $_POST['ids'] );
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## Output Escaping
|
|
189
|
+
|
|
190
|
+
### HTML Context
|
|
191
|
+
|
|
192
|
+
```php
|
|
193
|
+
<?php
|
|
194
|
+
// Escape HTML
|
|
195
|
+
echo esc_html( $text );
|
|
196
|
+
|
|
197
|
+
// Escape HTML with translation
|
|
198
|
+
echo esc_html__( 'Hello World', 'my-plugin' );
|
|
199
|
+
echo esc_html_e( 'Hello World', 'my-plugin' ); // Echoes directly
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Attribute Context
|
|
203
|
+
|
|
204
|
+
```php
|
|
205
|
+
<?php
|
|
206
|
+
// Escape attribute
|
|
207
|
+
echo '<input type="text" value="' . esc_attr( $value ) . '" />';
|
|
208
|
+
|
|
209
|
+
// Escape attribute with translation
|
|
210
|
+
echo '<input type="text" placeholder="' . esc_attr__( 'Enter name', 'my-plugin' ) . '" />';
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### URL Context
|
|
214
|
+
|
|
215
|
+
```php
|
|
216
|
+
<?php
|
|
217
|
+
// Escape URL
|
|
218
|
+
echo '<a href="' . esc_url( $url ) . '">Link</a>';
|
|
219
|
+
|
|
220
|
+
// Escape URL for use in HTML attribute
|
|
221
|
+
echo '<a href="' . esc_url( $url ) . '">Link</a>';
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### JavaScript Context
|
|
225
|
+
|
|
226
|
+
```php
|
|
227
|
+
<?php
|
|
228
|
+
// Escape JavaScript
|
|
229
|
+
echo '<script>var message = "' . esc_js( $message ) . '";</script>';
|
|
230
|
+
|
|
231
|
+
// Better: Use wp_json_encode for complex data
|
|
232
|
+
echo '<script>var data = ' . wp_json_encode( $data ) . ';</script>';
|
|
233
|
+
|
|
234
|
+
// Localize script data (recommended)
|
|
235
|
+
wp_localize_script( 'my-script', 'myData', array(
|
|
236
|
+
'message' => $message,
|
|
237
|
+
'url' => admin_url( 'admin-ajax.php' ),
|
|
238
|
+
) );
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Textarea Context
|
|
242
|
+
|
|
243
|
+
```php
|
|
244
|
+
<?php
|
|
245
|
+
// Escape textarea
|
|
246
|
+
echo '<textarea>' . esc_textarea( $content ) . '</textarea>';
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### SQL Context
|
|
250
|
+
|
|
251
|
+
```php
|
|
252
|
+
<?php
|
|
253
|
+
// Use $wpdb->prepare() - covered in Prepared Statements section
|
|
254
|
+
global $wpdb;
|
|
255
|
+
$results = $wpdb->get_results(
|
|
256
|
+
$wpdb->prepare(
|
|
257
|
+
"SELECT * FROM {$wpdb->prefix}table WHERE id = %d",
|
|
258
|
+
$id
|
|
259
|
+
)
|
|
260
|
+
);
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## Capability Checks
|
|
266
|
+
|
|
267
|
+
### Check User Capabilities
|
|
268
|
+
|
|
269
|
+
```php
|
|
270
|
+
<?php
|
|
271
|
+
/**
|
|
272
|
+
* Check if user can manage options
|
|
273
|
+
*/
|
|
274
|
+
function my_plugin_admin_page() {
|
|
275
|
+
if ( ! current_user_can( 'manage_options' ) ) {
|
|
276
|
+
wp_die( __( 'You do not have sufficient permissions to access this page.', 'my-plugin' ) );
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Display admin page
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Check if user can edit posts
|
|
284
|
+
*/
|
|
285
|
+
function my_plugin_save_post_meta( $post_id ) {
|
|
286
|
+
if ( ! current_user_can( 'edit_post', $post_id ) ) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Save post meta
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Check if user can delete users
|
|
295
|
+
*/
|
|
296
|
+
function my_plugin_delete_user( $user_id ) {
|
|
297
|
+
if ( ! current_user_can( 'delete_users' ) ) {
|
|
298
|
+
wp_die( __( 'You do not have permission to delete users.', 'my-plugin' ) );
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Delete user
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### Common Capabilities
|
|
306
|
+
|
|
307
|
+
- `manage_options` - Administrator
|
|
308
|
+
- `edit_posts` - Editor, Author, Contributor
|
|
309
|
+
- `publish_posts` - Editor, Author
|
|
310
|
+
- `edit_published_posts` - Editor, Author
|
|
311
|
+
- `delete_posts` - Editor, Author, Contributor
|
|
312
|
+
- `upload_files` - Editor, Author
|
|
313
|
+
- `edit_pages` - Editor
|
|
314
|
+
- `edit_users` - Administrator
|
|
315
|
+
- `delete_users` - Administrator
|
|
316
|
+
- `install_plugins` - Administrator
|
|
317
|
+
- `activate_plugins` - Administrator
|
|
318
|
+
|
|
319
|
+
### Custom Capabilities
|
|
320
|
+
|
|
321
|
+
```php
|
|
322
|
+
<?php
|
|
323
|
+
/**
|
|
324
|
+
* Add custom capability on activation
|
|
325
|
+
*/
|
|
326
|
+
function my_plugin_add_capabilities() {
|
|
327
|
+
$role = get_role( 'administrator' );
|
|
328
|
+
|
|
329
|
+
if ( $role ) {
|
|
330
|
+
$role->add_cap( 'manage_my_plugin' );
|
|
331
|
+
$role->add_cap( 'edit_my_plugin_items' );
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
register_activation_hook( __FILE__, 'my_plugin_add_capabilities' );
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Check custom capability
|
|
338
|
+
*/
|
|
339
|
+
function my_plugin_admin_page() {
|
|
340
|
+
if ( ! current_user_can( 'manage_my_plugin' ) ) {
|
|
341
|
+
wp_die( __( 'Insufficient permissions', 'my-plugin' ) );
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Display admin page
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
---
|
|
349
|
+
|
|
350
|
+
## Prepared Statements
|
|
351
|
+
|
|
352
|
+
### Using $wpdb->prepare()
|
|
353
|
+
|
|
354
|
+
**Always use prepared statements to prevent SQL injection.**
|
|
355
|
+
|
|
356
|
+
```php
|
|
357
|
+
<?php
|
|
358
|
+
global $wpdb;
|
|
359
|
+
|
|
360
|
+
// Single placeholder
|
|
361
|
+
$results = $wpdb->get_results(
|
|
362
|
+
$wpdb->prepare(
|
|
363
|
+
"SELECT * FROM {$wpdb->prefix}my_table WHERE user_id = %d",
|
|
364
|
+
$user_id
|
|
365
|
+
)
|
|
366
|
+
);
|
|
367
|
+
|
|
368
|
+
// Multiple placeholders
|
|
369
|
+
$results = $wpdb->get_results(
|
|
370
|
+
$wpdb->prepare(
|
|
371
|
+
"SELECT * FROM {$wpdb->prefix}my_table WHERE user_id = %d AND status = %s",
|
|
372
|
+
$user_id,
|
|
373
|
+
$status
|
|
374
|
+
)
|
|
375
|
+
);
|
|
376
|
+
|
|
377
|
+
// LIKE query
|
|
378
|
+
$results = $wpdb->get_results(
|
|
379
|
+
$wpdb->prepare(
|
|
380
|
+
"SELECT * FROM {$wpdb->prefix}my_table WHERE title LIKE %s",
|
|
381
|
+
'%' . $wpdb->esc_like( $search_term ) . '%'
|
|
382
|
+
)
|
|
383
|
+
);
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
### Placeholder Types
|
|
387
|
+
|
|
388
|
+
- `%s` - String
|
|
389
|
+
- `%d` - Integer (signed)
|
|
390
|
+
- `%f` - Float
|
|
391
|
+
|
|
392
|
+
```php
|
|
393
|
+
<?php
|
|
394
|
+
// String placeholder
|
|
395
|
+
$wpdb->prepare( "SELECT * FROM table WHERE name = %s", $name );
|
|
396
|
+
|
|
397
|
+
// Integer placeholder
|
|
398
|
+
$wpdb->prepare( "SELECT * FROM table WHERE id = %d", $id );
|
|
399
|
+
|
|
400
|
+
// Float placeholder
|
|
401
|
+
$wpdb->prepare( "SELECT * FROM table WHERE price = %f", $price );
|
|
402
|
+
|
|
403
|
+
// Multiple types
|
|
404
|
+
$wpdb->prepare(
|
|
405
|
+
"INSERT INTO table (name, age, score) VALUES (%s, %d, %f)",
|
|
406
|
+
$name,
|
|
407
|
+
$age,
|
|
408
|
+
$score
|
|
409
|
+
);
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### ❌ DON'T - SQL Injection Vulnerability
|
|
413
|
+
|
|
414
|
+
```php
|
|
415
|
+
<?php
|
|
416
|
+
// WRONG - Direct variable insertion (SQL injection risk)
|
|
417
|
+
$results = $wpdb->get_results(
|
|
418
|
+
"SELECT * FROM {$wpdb->prefix}table WHERE user_id = $user_id"
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
// WRONG - String concatenation
|
|
422
|
+
$results = $wpdb->get_results(
|
|
423
|
+
"SELECT * FROM {$wpdb->prefix}table WHERE name = '" . $name . "'"
|
|
424
|
+
);
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
### ✅ DO - Use Prepared Statements
|
|
428
|
+
|
|
429
|
+
```php
|
|
430
|
+
<?php
|
|
431
|
+
// CORRECT - Use $wpdb->prepare()
|
|
432
|
+
$results = $wpdb->get_results(
|
|
433
|
+
$wpdb->prepare(
|
|
434
|
+
"SELECT * FROM {$wpdb->prefix}table WHERE user_id = %d",
|
|
435
|
+
$user_id
|
|
436
|
+
)
|
|
437
|
+
);
|
|
438
|
+
|
|
439
|
+
// CORRECT - Multiple parameters
|
|
440
|
+
$results = $wpdb->get_results(
|
|
441
|
+
$wpdb->prepare(
|
|
442
|
+
"SELECT * FROM {$wpdb->prefix}table WHERE name = %s AND age = %d",
|
|
443
|
+
$name,
|
|
444
|
+
$age
|
|
445
|
+
)
|
|
446
|
+
);
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
---
|
|
450
|
+
|
|
451
|
+
## File Upload Security
|
|
452
|
+
|
|
453
|
+
### Validate File Uploads
|
|
454
|
+
|
|
455
|
+
```php
|
|
456
|
+
<?php
|
|
457
|
+
/**
|
|
458
|
+
* Validate file upload
|
|
459
|
+
*/
|
|
460
|
+
function my_plugin_validate_file_upload( $file ) {
|
|
461
|
+
// Check if file was uploaded
|
|
462
|
+
if ( ! isset( $file['error'] ) || is_array( $file['error'] ) ) {
|
|
463
|
+
wp_die( __( 'Invalid file upload', 'my-plugin' ) );
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Check for upload errors
|
|
467
|
+
if ( $file['error'] !== UPLOAD_ERR_OK ) {
|
|
468
|
+
wp_die( __( 'Upload error', 'my-plugin' ) );
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Check file size (5MB max)
|
|
472
|
+
if ( $file['size'] > 5242880 ) {
|
|
473
|
+
wp_die( __( 'File too large (max 5MB)', 'my-plugin' ) );
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Check file type
|
|
477
|
+
$allowed_types = array( 'image/jpeg', 'image/png', 'image/gif' );
|
|
478
|
+
$finfo = finfo_open( FILEINFO_MIME_TYPE );
|
|
479
|
+
$mime_type = finfo_file( $finfo, $file['tmp_name'] );
|
|
480
|
+
finfo_close( $finfo );
|
|
481
|
+
|
|
482
|
+
if ( ! in_array( $mime_type, $allowed_types, true ) ) {
|
|
483
|
+
wp_die( __( 'Invalid file type', 'my-plugin' ) );
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Sanitize filename
|
|
487
|
+
$filename = sanitize_file_name( $file['name'] );
|
|
488
|
+
|
|
489
|
+
return true;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Handle file upload
|
|
494
|
+
*/
|
|
495
|
+
function my_plugin_handle_upload() {
|
|
496
|
+
// Check nonce
|
|
497
|
+
check_ajax_referer( 'my_plugin_upload_nonce', 'security' );
|
|
498
|
+
|
|
499
|
+
// Check capability
|
|
500
|
+
if ( ! current_user_can( 'upload_files' ) ) {
|
|
501
|
+
wp_send_json_error( array( 'message' => 'Insufficient permissions' ) );
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Validate file
|
|
505
|
+
if ( empty( $_FILES['file'] ) ) {
|
|
506
|
+
wp_send_json_error( array( 'message' => 'No file uploaded' ) );
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
my_plugin_validate_file_upload( $_FILES['file'] );
|
|
510
|
+
|
|
511
|
+
// Use WordPress upload handler
|
|
512
|
+
require_once( ABSPATH . 'wp-admin/includes/file.php' );
|
|
513
|
+
|
|
514
|
+
$uploaded_file = wp_handle_upload( $_FILES['file'], array( 'test_form' => false ) );
|
|
515
|
+
|
|
516
|
+
if ( isset( $uploaded_file['error'] ) ) {
|
|
517
|
+
wp_send_json_error( array( 'message' => $uploaded_file['error'] ) );
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
wp_send_json_success( array(
|
|
521
|
+
'url' => $uploaded_file['url'],
|
|
522
|
+
'path' => $uploaded_file['file'],
|
|
523
|
+
) );
|
|
524
|
+
}
|
|
525
|
+
add_action( 'wp_ajax_my_plugin_upload', 'my_plugin_handle_upload' );
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
---
|
|
529
|
+
|
|
530
|
+
## Authentication and Authorization
|
|
531
|
+
|
|
532
|
+
### Check if User is Logged In
|
|
533
|
+
|
|
534
|
+
```php
|
|
535
|
+
<?php
|
|
536
|
+
/**
|
|
537
|
+
* Require user to be logged in
|
|
538
|
+
*/
|
|
539
|
+
function my_plugin_members_only_page() {
|
|
540
|
+
if ( ! is_user_logged_in() ) {
|
|
541
|
+
wp_redirect( wp_login_url( get_permalink() ) );
|
|
542
|
+
exit;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// Display members-only content
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Get current user ID
|
|
550
|
+
*/
|
|
551
|
+
$user_id = get_current_user_id();
|
|
552
|
+
|
|
553
|
+
if ( $user_id ) {
|
|
554
|
+
// User is logged in
|
|
555
|
+
} else {
|
|
556
|
+
// User is not logged in
|
|
557
|
+
}
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
### Verify User Owns Resource
|
|
561
|
+
|
|
562
|
+
```php
|
|
563
|
+
<?php
|
|
564
|
+
/**
|
|
565
|
+
* Verify user owns the item before editing
|
|
566
|
+
*/
|
|
567
|
+
function my_plugin_edit_item( $item_id ) {
|
|
568
|
+
global $wpdb;
|
|
569
|
+
|
|
570
|
+
$item = $wpdb->get_row(
|
|
571
|
+
$wpdb->prepare(
|
|
572
|
+
"SELECT * FROM {$wpdb->prefix}my_plugin_items WHERE id = %d",
|
|
573
|
+
$item_id
|
|
574
|
+
)
|
|
575
|
+
);
|
|
576
|
+
|
|
577
|
+
if ( ! $item ) {
|
|
578
|
+
wp_die( __( 'Item not found', 'my-plugin' ) );
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// Check if current user owns the item
|
|
582
|
+
if ( $item->user_id !== get_current_user_id() ) {
|
|
583
|
+
wp_die( __( 'You do not have permission to edit this item', 'my-plugin' ) );
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// Edit item
|
|
587
|
+
}
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
---
|
|
591
|
+
|
|
592
|
+
## Common Vulnerabilities
|
|
593
|
+
|
|
594
|
+
### SQL Injection Prevention
|
|
595
|
+
|
|
596
|
+
```php
|
|
597
|
+
<?php
|
|
598
|
+
// ✅ CORRECT
|
|
599
|
+
$results = $wpdb->get_results(
|
|
600
|
+
$wpdb->prepare(
|
|
601
|
+
"SELECT * FROM {$wpdb->prefix}posts WHERE ID = %d",
|
|
602
|
+
$id
|
|
603
|
+
)
|
|
604
|
+
);
|
|
605
|
+
|
|
606
|
+
// ❌ WRONG
|
|
607
|
+
$results = $wpdb->query( "SELECT * FROM {$wpdb->prefix}posts WHERE ID = $id" );
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
### Cross-Site Scripting (XSS) Prevention
|
|
611
|
+
|
|
612
|
+
```php
|
|
613
|
+
<?php
|
|
614
|
+
// ✅ CORRECT
|
|
615
|
+
echo esc_html( $user_input );
|
|
616
|
+
|
|
617
|
+
// ❌ WRONG
|
|
618
|
+
echo $user_input;
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
### Cross-Site Request Forgery (CSRF) Prevention
|
|
622
|
+
|
|
623
|
+
```php
|
|
624
|
+
<?php
|
|
625
|
+
// ✅ CORRECT
|
|
626
|
+
wp_nonce_field( 'my_action', 'my_nonce' );
|
|
627
|
+
wp_verify_nonce( $_POST['my_nonce'], 'my_action' );
|
|
628
|
+
|
|
629
|
+
// ❌ WRONG
|
|
630
|
+
// No nonce verification
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
### Directory Traversal Prevention
|
|
634
|
+
|
|
635
|
+
```php
|
|
636
|
+
<?php
|
|
637
|
+
// ✅ CORRECT
|
|
638
|
+
$file = basename( $_GET['file'] );
|
|
639
|
+
$path = WP_CONTENT_DIR . '/uploads/' . $file;
|
|
640
|
+
|
|
641
|
+
if ( ! file_exists( $path ) ) {
|
|
642
|
+
wp_die( __( 'File not found', 'my-plugin' ) );
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// ❌ WRONG
|
|
646
|
+
$file = $_GET['file'];
|
|
647
|
+
$path = WP_CONTENT_DIR . '/uploads/' . $file; // Can access ../../../etc/passwd
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
### Remote Code Execution Prevention
|
|
651
|
+
|
|
652
|
+
```php
|
|
653
|
+
<?php
|
|
654
|
+
// ❌ WRONG - Never use eval() with user input
|
|
655
|
+
eval( $_POST['code'] );
|
|
656
|
+
|
|
657
|
+
// ❌ WRONG - Never execute user input
|
|
658
|
+
system( $_POST['command'] );
|
|
659
|
+
|
|
660
|
+
// ✅ CORRECT - Validate and whitelist allowed actions
|
|
661
|
+
$allowed_actions = array( 'action1', 'action2', 'action3' );
|
|
662
|
+
$action = sanitize_text_field( $_POST['action'] );
|
|
663
|
+
|
|
664
|
+
if ( in_array( $action, $allowed_actions, true ) ) {
|
|
665
|
+
call_user_func( 'my_plugin_' . $action );
|
|
666
|
+
}
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
---
|
|
670
|
+
|
|
671
|
+
## Best Practices Summary
|
|
672
|
+
|
|
673
|
+
### Input Handling
|
|
674
|
+
|
|
675
|
+
✅ **DO**:
|
|
676
|
+
- Sanitize all input using appropriate functions
|
|
677
|
+
- Validate data types and formats
|
|
678
|
+
- Use whitelist validation when possible
|
|
679
|
+
- Never trust user input
|
|
680
|
+
|
|
681
|
+
❌ **DON'T**:
|
|
682
|
+
- Use unsanitized input directly
|
|
683
|
+
- Assume input is safe
|
|
684
|
+
- Skip validation
|
|
685
|
+
|
|
686
|
+
### Output Handling
|
|
687
|
+
|
|
688
|
+
✅ **DO**:
|
|
689
|
+
- Escape all output based on context
|
|
690
|
+
- Use `esc_html()`, `esc_attr()`, `esc_url()`, `esc_js()`
|
|
691
|
+
- Escape late (just before output)
|
|
692
|
+
|
|
693
|
+
❌ **DON'T**:
|
|
694
|
+
- Output raw user input
|
|
695
|
+
- Forget to escape
|
|
696
|
+
- Use wrong escaping function for context
|
|
697
|
+
|
|
698
|
+
### Database
|
|
699
|
+
|
|
700
|
+
✅ **DO**:
|
|
701
|
+
- Always use prepared statements
|
|
702
|
+
- Use `$wpdb->prepare()` for custom queries
|
|
703
|
+
- Use WordPress database functions when possible
|
|
704
|
+
|
|
705
|
+
❌ **DON'T**:
|
|
706
|
+
- Concatenate user input into SQL
|
|
707
|
+
- Skip prepared statements
|
|
708
|
+
- Trust user input in queries
|
|
709
|
+
|
|
710
|
+
### Authentication
|
|
711
|
+
|
|
712
|
+
✅ **DO**:
|
|
713
|
+
- Use nonces for all form submissions
|
|
714
|
+
- Check user capabilities
|
|
715
|
+
- Verify user identity for sensitive operations
|
|
716
|
+
- Use HTTPS for login and admin areas
|
|
717
|
+
|
|
718
|
+
❌ **DON'T**:
|
|
719
|
+
- Skip nonce verification
|
|
720
|
+
- Forget capability checks
|
|
721
|
+
- Allow unauthenticated access to sensitive data
|
|
722
|
+
|
|
723
|
+
### Files
|
|
724
|
+
|
|
725
|
+
✅ **DO**:
|
|
726
|
+
- Validate file types and sizes
|
|
727
|
+
- Use WordPress upload functions
|
|
728
|
+
- Sanitize filenames
|
|
729
|
+
- Check MIME types
|
|
730
|
+
|
|
731
|
+
❌ **DON'T**:
|
|
732
|
+
- Trust file extensions
|
|
733
|
+
- Allow unrestricted uploads
|
|
734
|
+
- Skip file validation
|
|
735
|
+
|
|
736
|
+
---
|
|
737
|
+
|
|
738
|
+
## Security Checklist
|
|
739
|
+
|
|
740
|
+
### For Every Form
|
|
741
|
+
|
|
742
|
+
- [ ] Nonce field added with `wp_nonce_field()`
|
|
743
|
+
- [ ] Nonce verified with `wp_verify_nonce()`
|
|
744
|
+
- [ ] Capability check with `current_user_can()`
|
|
745
|
+
- [ ] All input sanitized
|
|
746
|
+
- [ ] All output escaped
|
|
747
|
+
|
|
748
|
+
### For Every AJAX Handler
|
|
749
|
+
|
|
750
|
+
- [ ] Nonce verified with `check_ajax_referer()`
|
|
751
|
+
- [ ] Capability check performed
|
|
752
|
+
- [ ] Input sanitized
|
|
753
|
+
- [ ] Output escaped in response
|
|
754
|
+
|
|
755
|
+
### For Every Database Query
|
|
756
|
+
|
|
757
|
+
- [ ] Using `$wpdb->prepare()` for custom queries
|
|
758
|
+
- [ ] Correct placeholder types (%s, %d, %f)
|
|
759
|
+
- [ ] No direct variable insertion
|
|
760
|
+
|
|
761
|
+
### For Every File Upload
|
|
762
|
+
|
|
763
|
+
- [ ] File type validated
|
|
764
|
+
- [ ] File size checked
|
|
765
|
+
- [ ] MIME type verified
|
|
766
|
+
- [ ] Filename sanitized
|
|
767
|
+
- [ ] Using `wp_handle_upload()`
|
|
768
|
+
|
|
769
|
+
---
|
|
770
|
+
|
|
771
|
+
## Complete Example
|
|
772
|
+
|
|
773
|
+
### Secure Form Processing
|
|
774
|
+
|
|
775
|
+
```php
|
|
776
|
+
<?php
|
|
777
|
+
/**
|
|
778
|
+
* Display form
|
|
779
|
+
*/
|
|
780
|
+
function my_plugin_display_form() {
|
|
781
|
+
?>
|
|
782
|
+
<form method="post" action="">
|
|
783
|
+
<?php wp_nonce_field( 'my_plugin_save_data', 'my_plugin_nonce' ); ?>
|
|
784
|
+
|
|
785
|
+
<label for="title">Title:</label>
|
|
786
|
+
<input type="text" id="title" name="title" value="<?php echo esc_attr( get_option( 'my_plugin_title' ) ); ?>" />
|
|
787
|
+
|
|
788
|
+
<label for="content">Content:</label>
|
|
789
|
+
<textarea id="content" name="content"><?php echo esc_textarea( get_option( 'my_plugin_content' ) ); ?></textarea>
|
|
790
|
+
|
|
791
|
+
<input type="submit" name="submit" value="<?php esc_attr_e( 'Save', 'my-plugin' ); ?>" />
|
|
792
|
+
</form>
|
|
793
|
+
<?php
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
/**
|
|
797
|
+
* Process form submission
|
|
798
|
+
*/
|
|
799
|
+
function my_plugin_process_form() {
|
|
800
|
+
// Check if form was submitted
|
|
801
|
+
if ( ! isset( $_POST['submit'] ) ) {
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// Verify nonce
|
|
806
|
+
if ( ! isset( $_POST['my_plugin_nonce'] ) ||
|
|
807
|
+
! wp_verify_nonce( $_POST['my_plugin_nonce'], 'my_plugin_save_data' ) ) {
|
|
808
|
+
wp_die( __( 'Security check failed', 'my-plugin' ) );
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
// Check capability
|
|
812
|
+
if ( ! current_user_can( 'manage_options' ) ) {
|
|
813
|
+
wp_die( __( 'Insufficient permissions', 'my-plugin' ) );
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// Sanitize input
|
|
817
|
+
$title = sanitize_text_field( $_POST['title'] );
|
|
818
|
+
$content = wp_kses_post( $_POST['content'] );
|
|
819
|
+
|
|
820
|
+
// Validate input
|
|
821
|
+
if ( empty( $title ) ) {
|
|
822
|
+
add_settings_error( 'my_plugin', 'empty_title', __( 'Title is required', 'my-plugin' ) );
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// Save data
|
|
827
|
+
update_option( 'my_plugin_title', $title );
|
|
828
|
+
update_option( 'my_plugin_content', $content );
|
|
829
|
+
|
|
830
|
+
// Success message
|
|
831
|
+
add_settings_error( 'my_plugin', 'settings_saved', __( 'Settings saved', 'my-plugin' ), 'success' );
|
|
832
|
+
}
|
|
833
|
+
add_action( 'admin_init', 'my_plugin_process_form' );
|
|
834
|
+
```
|
|
835
|
+
|
|
836
|
+
---
|
|
837
|
+
|
|
838
|
+
## Summary
|
|
839
|
+
|
|
840
|
+
**Key Takeaways:**
|
|
841
|
+
|
|
842
|
+
1. **Nonces**: Always use nonces for form submissions and AJAX requests
|
|
843
|
+
2. **Sanitization**: Sanitize all input with appropriate functions
|
|
844
|
+
3. **Escaping**: Escape all output based on context
|
|
845
|
+
4. **Capabilities**: Check user capabilities for all sensitive operations
|
|
846
|
+
5. **Prepared Statements**: Always use `$wpdb->prepare()` for database queries
|
|
847
|
+
6. **File Uploads**: Validate file types, sizes, and MIME types
|
|
848
|
+
7. **Authentication**: Verify user identity for sensitive operations
|
|
849
|
+
|
|
850
|
+
**Common Mistakes to Avoid:**
|
|
851
|
+
|
|
852
|
+
- Skipping nonce verification
|
|
853
|
+
- Not sanitizing user input
|
|
854
|
+
- Forgetting to escape output
|
|
855
|
+
- Using direct SQL queries without preparation
|
|
856
|
+
- Trusting file extensions
|
|
857
|
+
- Not checking user capabilities
|
|
858
|
+
|
|
859
|
+
**Resources:**
|
|
860
|
+
|
|
861
|
+
- [WordPress Security](https://developer.wordpress.org/plugins/security/)
|
|
862
|
+
- [Data Validation](https://developer.wordpress.org/plugins/security/data-validation/)
|
|
863
|
+
- [Securing Input](https://developer.wordpress.org/plugins/security/securing-input/)
|
|
864
|
+
- [Securing Output](https://developer.wordpress.org/plugins/security/securing-output/)
|
|
865
|
+
- [Nonces](https://developer.wordpress.org/plugins/security/nonces/)
|
|
866
|
+
|