@lebtiga/sonic-agent 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/LICENSE.txt +223 -0
  2. package/README.md +61 -0
  3. package/bin/sonic.js +304 -0
  4. package/lib/index.js +20 -0
  5. package/lib/installer.js +156 -0
  6. package/lib/license.js +48 -0
  7. package/package.json +46 -0
  8. package/plugin/.claude-plugin/plugin.json +13 -0
  9. package/plugin/README.md +100 -0
  10. package/plugin/agents/sonic.md +80 -0
  11. package/plugin/commands/sonic-build.md +145 -0
  12. package/plugin/commands/sonic-help.md +71 -0
  13. package/plugin/skills/accessibility-qa/SKILL.md +160 -0
  14. package/plugin/skills/accessibility-qa/templates/accessibility-qa-report-template.md +123 -0
  15. package/plugin/skills/accessibility-qa/templates/wcag-compliance-statement.md +70 -0
  16. package/plugin/skills/aka-wireframe-wp/SKILL.md +149 -0
  17. package/plugin/skills/aka-wireframe-wp/assets/aka-framework-theme/README.md +190 -0
  18. package/plugin/skills/aka-wireframe-wp/assets/aka-framework-theme/footer.php +49 -0
  19. package/plugin/skills/aka-wireframe-wp/assets/aka-framework-theme/functions.php +395 -0
  20. package/plugin/skills/aka-wireframe-wp/assets/aka-framework-theme/header.php +58 -0
  21. package/plugin/skills/aka-wireframe-wp/assets/aka-framework-theme/index.php +39 -0
  22. package/plugin/skills/aka-wireframe-wp/assets/aka-framework-theme/page-answer.php +62 -0
  23. package/plugin/skills/aka-wireframe-wp/assets/aka-framework-theme/page-authority-hub.php +122 -0
  24. package/plugin/skills/aka-wireframe-wp/assets/aka-framework-theme/page-knowledge.php +58 -0
  25. package/plugin/skills/aka-wireframe-wp/assets/aka-framework-theme/style.css +633 -0
  26. package/plugin/skills/aka-wireframe-wp/references/content-generator.md +371 -0
  27. package/plugin/skills/aka-wireframe-wp/references/internal-linker.md +430 -0
  28. package/plugin/skills/aka-wireframe-wp/references/orchestrator.md +269 -0
  29. package/plugin/skills/aka-wireframe-wp/references/prompts-library.md +880 -0
  30. package/plugin/skills/aka-wireframe-wp/references/seo-optimizer.md +433 -0
  31. package/plugin/skills/aka-wireframe-wp/references/strategy-planner.md +317 -0
  32. package/plugin/skills/aka-wireframe-wp/references/wordpress-deployer.md +545 -0
  33. package/plugin/skills/authority-site-builder/SKILL.md +138 -0
  34. package/plugin/skills/brand-philosophy/SKILL.md +77 -0
  35. package/plugin/skills/freepik-spaces/SKILL.md +122 -0
  36. package/plugin/skills/freepik-spaces/docs/automation-guide.md +233 -0
  37. package/plugin/skills/freepik-spaces/docs/research-notes.md +264 -0
  38. package/plugin/skills/freepik-spaces/plans/naseberry-demo-plan.md +320 -0
  39. package/plugin/skills/freepik-spaces/templates/naseberry-demo.json +302 -0
  40. package/plugin/skills/freepik-spaces/templates/saas-demo.json +212 -0
  41. package/plugin/skills/frontend-design/LICENSE.txt +177 -0
  42. package/plugin/skills/frontend-design/SKILL.md +77 -0
  43. package/plugin/skills/programmatic-seo/SKILL.md +236 -0
  44. package/plugin/skills/programmatic-seo/references/playbooks.md +293 -0
  45. package/plugin/skills/seo-qa/SKILL.md +132 -0
  46. package/plugin/skills/seo-qa/templates/schema-localbusiness.json +49 -0
  47. package/plugin/skills/seo-qa/templates/schema-service.json +36 -0
  48. package/plugin/skills/seo-qa/templates/seo-qa-report-template.md +90 -0
  49. package/plugin/skills/visual-identity/SKILL.md +109 -0
  50. package/plugin/skills/visual-identity/templates/style-guide-template.md +108 -0
  51. package/plugin/skills/website-image-gen/SKILL.md +82 -0
  52. package/plugin/skills/website-image-gen/templates/blog-featured.md +56 -0
  53. package/plugin/skills/website-image-gen/templates/hero-service-photo.md +56 -0
  54. package/plugin/skills/wordpress-pro/SKILL.md +105 -0
  55. package/plugin/skills/wordpress-pro/references/gutenberg-blocks.md +870 -0
  56. package/plugin/skills/wordpress-pro/references/hooks-filters.md +845 -0
  57. package/plugin/skills/wordpress-pro/references/performance-security.md +1012 -0
  58. package/plugin/skills/wordpress-pro/references/plugin-architecture.md +1041 -0
  59. package/plugin/skills/wordpress-pro/references/theme-development.md +858 -0
  60. package/plugin/sops/SOP-Sonic 777/authority-site-sop.html +1100 -0
  61. package/plugin/sops/SOP-WORDPRESS-330-PAGE-SITES.md +926 -0
  62. package/scripts/postinstall.js +109 -0
@@ -0,0 +1,845 @@
1
+ # Hooks & Filters
2
+
3
+ ---
4
+
5
+ ## WordPress Hook System
6
+
7
+ WordPress uses an event-driven architecture with two types of hooks:
8
+
9
+ | Hook Type | Purpose | Function |
10
+ |-----------|---------|----------|
11
+ | **Actions** | Execute code at specific points | `add_action()` / `do_action()` |
12
+ | **Filters** | Modify data before it's used | `add_filter()` / `apply_filters()` |
13
+
14
+ ---
15
+
16
+ ## Actions
17
+
18
+ Actions allow you to execute custom code at specific points in WordPress execution.
19
+
20
+ ### Action Basics
21
+
22
+ ```php
23
+ <?php
24
+ declare(strict_types=1);
25
+
26
+ /**
27
+ * Register an action hook
28
+ *
29
+ * @param string $hook_name The name of the action
30
+ * @param callable $callback Function to execute
31
+ * @param int $priority Order of execution (default: 10)
32
+ * @param int $accepted_args Number of arguments passed (default: 1)
33
+ */
34
+ add_action('init', 'my_plugin_init', 10, 1);
35
+
36
+ /**
37
+ * Execute custom code on init
38
+ */
39
+ function my_plugin_init(): void {
40
+ // Your code here
41
+ register_post_type('product', [...]);
42
+ }
43
+
44
+ // Using anonymous functions (PHP 8.1+)
45
+ add_action('wp_footer', function(): void {
46
+ echo '<!-- Custom footer content -->';
47
+ }, 99);
48
+
49
+ // Using class methods
50
+ class My_Plugin {
51
+ public function __construct() {
52
+ add_action('init', [$this, 'init']);
53
+ add_action('admin_init', [$this, 'admin_init']);
54
+ }
55
+
56
+ public function init(): void {
57
+ // Initialization code
58
+ }
59
+
60
+ public function admin_init(): void {
61
+ // Admin initialization
62
+ }
63
+ }
64
+
65
+ // Using static methods
66
+ add_action('init', [My_Plugin::class, 'static_init']);
67
+ ```
68
+
69
+ ### Essential Action Hooks
70
+
71
+ ```php
72
+ <?php
73
+ /**
74
+ * Execution order and common action hooks
75
+ */
76
+
77
+ // === EARLY LOADING ===
78
+
79
+ // After WordPress loads but before headers sent
80
+ add_action('muplugins_loaded', function(): void {
81
+ // Must-use plugins loaded
82
+ });
83
+
84
+ // After active plugins loaded
85
+ add_action('plugins_loaded', function(): void {
86
+ // Safe to check for other plugins
87
+ if (class_exists('WooCommerce')) {
88
+ // WooCommerce is active
89
+ }
90
+ });
91
+
92
+ // After theme functions.php loaded
93
+ add_action('after_setup_theme', function(): void {
94
+ // Theme setup: add_theme_support, register_nav_menus, etc.
95
+ }, 10);
96
+
97
+ // === MAIN INITIALIZATION ===
98
+
99
+ // WordPress fully loaded, safe for most operations
100
+ add_action('init', function(): void {
101
+ // Register post types, taxonomies, shortcodes
102
+ // Load text domains
103
+ // Start session if needed
104
+ }, 10);
105
+
106
+ // All widgets registered
107
+ add_action('widgets_init', function(): void {
108
+ // Register widget areas
109
+ register_sidebar([...]);
110
+ });
111
+
112
+ // === ADMIN HOOKS ===
113
+
114
+ // Admin area initializing
115
+ add_action('admin_init', function(): void {
116
+ // Register settings, add capabilities
117
+ });
118
+
119
+ // Build admin menu
120
+ add_action('admin_menu', function(): void {
121
+ // Add menu pages
122
+ add_menu_page(...);
123
+ });
124
+
125
+ // Enqueue admin assets
126
+ add_action('admin_enqueue_scripts', function(string $hook_suffix): void {
127
+ // $hook_suffix: e.g., 'post.php', 'settings_page_my-settings'
128
+ if ($hook_suffix !== 'settings_page_my-settings') {
129
+ return;
130
+ }
131
+ wp_enqueue_script('my-admin-script', ...);
132
+ }, 10, 1);
133
+
134
+ // === FRONTEND HOOKS ===
135
+
136
+ // Main query parsed, before template loaded
137
+ add_action('template_redirect', function(): void {
138
+ // Check conditions, redirect if needed
139
+ if (is_page('restricted') && !is_user_logged_in()) {
140
+ wp_redirect(wp_login_url());
141
+ exit;
142
+ }
143
+ });
144
+
145
+ // Enqueue frontend assets
146
+ add_action('wp_enqueue_scripts', function(): void {
147
+ wp_enqueue_style('my-style', ...);
148
+ wp_enqueue_script('my-script', ...);
149
+ });
150
+
151
+ // Inside <head> tag
152
+ add_action('wp_head', function(): void {
153
+ // Meta tags, inline styles
154
+ echo '<meta name="custom" content="value" />';
155
+ }, 1); // Priority 1 = early in head
156
+
157
+ // Before </body> tag
158
+ add_action('wp_footer', function(): void {
159
+ // Tracking scripts, modals
160
+ }, 99); // Priority 99 = late in footer
161
+
162
+ // === POST/PAGE HOOKS ===
163
+
164
+ // Before post is saved
165
+ add_action('pre_post_update', function(int $post_id, array $data): void {
166
+ // Validate or modify before save
167
+ }, 10, 2);
168
+
169
+ // After post is saved (any status)
170
+ add_action('save_post', function(int $post_id, WP_Post $post, bool $update): void {
171
+ // Skip autosaves
172
+ if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
173
+ return;
174
+ }
175
+
176
+ // Skip revisions
177
+ if (wp_is_post_revision($post_id)) {
178
+ return;
179
+ }
180
+
181
+ // Skip specific post types
182
+ if ($post->post_type !== 'my_post_type') {
183
+ return;
184
+ }
185
+
186
+ // Update meta, trigger notifications, etc.
187
+ update_post_meta($post_id, '_custom_meta', sanitize_text_field($_POST['custom_field'] ?? ''));
188
+ }, 10, 3);
189
+
190
+ // Post status transitions
191
+ add_action('transition_post_status', function(string $new_status, string $old_status, WP_Post $post): void {
192
+ if ($new_status === 'publish' && $old_status !== 'publish') {
193
+ // Post just published
194
+ my_notify_subscribers($post);
195
+ }
196
+ }, 10, 3);
197
+
198
+ // Post deleted (moved to trash)
199
+ add_action('wp_trash_post', function(int $post_id): void {
200
+ // Clean up related data
201
+ });
202
+
203
+ // Post permanently deleted
204
+ add_action('before_delete_post', function(int $post_id, WP_Post $post): void {
205
+ // Clean up custom tables, files, etc.
206
+ global $wpdb;
207
+ $wpdb->delete(
208
+ $wpdb->prefix . 'my_table',
209
+ ['post_id' => $post_id],
210
+ ['%d']
211
+ );
212
+ }, 10, 2);
213
+
214
+ // === USER HOOKS ===
215
+
216
+ // User registered
217
+ add_action('user_register', function(int $user_id, array $userdata): void {
218
+ // Set default meta, send welcome email
219
+ update_user_meta($user_id, 'welcome_dismissed', false);
220
+ }, 10, 2);
221
+
222
+ // User logged in
223
+ add_action('wp_login', function(string $user_login, WP_User $user): void {
224
+ // Log login, update last login time
225
+ update_user_meta($user->ID, 'last_login', current_time('mysql'));
226
+ }, 10, 2);
227
+
228
+ // User logged out
229
+ add_action('wp_logout', function(int $user_id): void {
230
+ // Cleanup session data
231
+ }, 10, 1);
232
+
233
+ // === REST API HOOKS ===
234
+
235
+ // Register REST routes
236
+ add_action('rest_api_init', function(): void {
237
+ register_rest_route('my-plugin/v1', '/items', [
238
+ 'methods' => 'GET',
239
+ 'callback' => 'my_plugin_get_items',
240
+ 'permission_callback' => '__return_true',
241
+ ]);
242
+ });
243
+
244
+ // === CRON HOOKS ===
245
+
246
+ // Schedule custom cron event
247
+ add_action('init', function(): void {
248
+ if (!wp_next_scheduled('my_plugin_daily_task')) {
249
+ wp_schedule_event(time(), 'daily', 'my_plugin_daily_task');
250
+ }
251
+ });
252
+
253
+ // Handle cron event
254
+ add_action('my_plugin_daily_task', function(): void {
255
+ // Cleanup, sync, report generation, etc.
256
+ });
257
+ ```
258
+
259
+ ### Removing Actions
260
+
261
+ ```php
262
+ <?php
263
+ /**
264
+ * Remove actions added by WordPress or other plugins
265
+ */
266
+
267
+ // Remove default WordPress actions
268
+ remove_action('wp_head', 'wp_generator');
269
+ remove_action('wp_head', 'wlwmanifest_link');
270
+ remove_action('wp_head', 'rsd_link');
271
+ remove_action('wp_head', 'wp_shortlink_wp_head');
272
+ remove_action('wp_head', 'print_emoji_detection_script', 7);
273
+ remove_action('wp_print_styles', 'print_emoji_styles');
274
+
275
+ // Remove action from a class (must match exact instance)
276
+ // If original: add_action('init', [$instance, 'method'], 10);
277
+ // Need to access same $instance to remove
278
+
279
+ // Remove using class name for static methods
280
+ remove_action('init', [Some_Class::class, 'static_method'], 10);
281
+
282
+ // Remove all callbacks from a hook
283
+ remove_all_actions('some_hook');
284
+
285
+ // Check if action is hooked
286
+ if (has_action('init', 'some_callback')) {
287
+ // Callback is registered
288
+ }
289
+ ```
290
+
291
+ ---
292
+
293
+ ## Filters
294
+
295
+ Filters modify data before it's used or displayed.
296
+
297
+ ### Filter Basics
298
+
299
+ ```php
300
+ <?php
301
+ declare(strict_types=1);
302
+
303
+ /**
304
+ * Register a filter hook
305
+ *
306
+ * @param string $hook_name The name of the filter
307
+ * @param callable $callback Function to filter data
308
+ * @param int $priority Order of execution (default: 10)
309
+ * @param int $accepted_args Number of arguments passed (default: 1)
310
+ */
311
+ add_filter('the_content', 'my_plugin_modify_content', 10, 1);
312
+
313
+ /**
314
+ * Modify post content
315
+ *
316
+ * @param string $content The post content
317
+ * @return string Modified content
318
+ */
319
+ function my_plugin_modify_content(string $content): string {
320
+ // Always return the filtered value
321
+ if (is_single() && in_the_loop()) {
322
+ $content .= '<div class="post-cta">Subscribe for more!</div>';
323
+ }
324
+ return $content;
325
+ }
326
+
327
+ // Filter with multiple arguments
328
+ add_filter('post_thumbnail_html', function(
329
+ string $html,
330
+ int $post_id,
331
+ int $thumbnail_id,
332
+ string $size,
333
+ string $attr
334
+ ): string {
335
+ // Add lazy loading
336
+ return str_replace('<img', '<img loading="lazy"', $html);
337
+ }, 10, 5);
338
+ ```
339
+
340
+ ### Essential Filter Hooks
341
+
342
+ ```php
343
+ <?php
344
+ /**
345
+ * Common filter hooks
346
+ */
347
+
348
+ // === CONTENT FILTERS ===
349
+
350
+ // Modify post title
351
+ add_filter('the_title', function(string $title, int $post_id): string {
352
+ if (is_admin()) {
353
+ return $title;
354
+ }
355
+ // Add icon for featured posts
356
+ if (get_post_meta($post_id, '_is_featured', true)) {
357
+ $title = '&#9733; ' . $title;
358
+ }
359
+ return $title;
360
+ }, 10, 2);
361
+
362
+ // Modify post content
363
+ add_filter('the_content', function(string $content): string {
364
+ // Add social sharing after content
365
+ if (is_singular('post') && in_the_loop() && is_main_query()) {
366
+ $content .= my_plugin_get_share_buttons();
367
+ }
368
+ return $content;
369
+ });
370
+
371
+ // Modify excerpt length
372
+ add_filter('excerpt_length', function(int $length): int {
373
+ return 30; // words
374
+ });
375
+
376
+ // Modify excerpt "more" text
377
+ add_filter('excerpt_more', function(string $more): string {
378
+ return '&hellip; <a href="' . esc_url(get_permalink()) . '">Read more</a>';
379
+ });
380
+
381
+ // === QUERY FILTERS ===
382
+
383
+ // Modify main query
384
+ add_filter('pre_get_posts', function(WP_Query $query): void {
385
+ if (is_admin() || !$query->is_main_query()) {
386
+ return;
387
+ }
388
+
389
+ // Exclude category from blog
390
+ if ($query->is_home()) {
391
+ $query->set('cat', '-5'); // Exclude category ID 5
392
+ }
393
+
394
+ // Custom archive ordering
395
+ if ($query->is_post_type_archive('product')) {
396
+ $query->set('orderby', 'menu_order');
397
+ $query->set('order', 'ASC');
398
+ }
399
+ });
400
+
401
+ // Modify search results
402
+ add_filter('posts_search', function(string $search, WP_Query $query): string {
403
+ if (!$query->is_search() || !$query->is_main_query()) {
404
+ return $search;
405
+ }
406
+ // Customize search SQL
407
+ return $search;
408
+ }, 10, 2);
409
+
410
+ // === TEMPLATE FILTERS ===
411
+
412
+ // Override template file
413
+ add_filter('template_include', function(string $template): string {
414
+ if (is_singular('product')) {
415
+ $custom = locate_template('templates/single-product.php');
416
+ if ($custom) {
417
+ return $custom;
418
+ }
419
+ }
420
+ return $template;
421
+ });
422
+
423
+ // Add body classes
424
+ add_filter('body_class', function(array $classes): array {
425
+ if (is_user_logged_in()) {
426
+ $classes[] = 'logged-in-user';
427
+ }
428
+ if (wp_is_mobile()) {
429
+ $classes[] = 'is-mobile';
430
+ }
431
+ return $classes;
432
+ });
433
+
434
+ // Add post classes
435
+ add_filter('post_class', function(array $classes, array $class, int $post_id): array {
436
+ if (has_post_thumbnail($post_id)) {
437
+ $classes[] = 'has-thumbnail';
438
+ }
439
+ return $classes;
440
+ }, 10, 3);
441
+
442
+ // === ADMIN FILTERS ===
443
+
444
+ // Modify admin columns
445
+ add_filter('manage_product_posts_columns', function(array $columns): array {
446
+ $new_columns = [];
447
+ foreach ($columns as $key => $value) {
448
+ $new_columns[$key] = $value;
449
+ if ($key === 'title') {
450
+ $new_columns['price'] = __('Price', 'my-plugin');
451
+ $new_columns['sku'] = __('SKU', 'my-plugin');
452
+ }
453
+ }
454
+ return $new_columns;
455
+ });
456
+
457
+ // Populate custom columns
458
+ add_action('manage_product_posts_custom_column', function(string $column, int $post_id): void {
459
+ switch ($column) {
460
+ case 'price':
461
+ echo esc_html(get_post_meta($post_id, '_price', true));
462
+ break;
463
+ case 'sku':
464
+ echo esc_html(get_post_meta($post_id, '_sku', true));
465
+ break;
466
+ }
467
+ }, 10, 2);
468
+
469
+ // Make columns sortable
470
+ add_filter('manage_edit-product_sortable_columns', function(array $columns): array {
471
+ $columns['price'] = 'price';
472
+ $columns['sku'] = 'sku';
473
+ return $columns;
474
+ });
475
+
476
+ // === URL/LINK FILTERS ===
477
+
478
+ // Modify permalink structure
479
+ add_filter('post_type_link', function(string $permalink, WP_Post $post): string {
480
+ if ($post->post_type !== 'product') {
481
+ return $permalink;
482
+ }
483
+ // Add category to permalink
484
+ $terms = get_the_terms($post->ID, 'product_category');
485
+ if ($terms && !is_wp_error($terms)) {
486
+ $permalink = str_replace('%product_category%', $terms[0]->slug, $permalink);
487
+ }
488
+ return $permalink;
489
+ }, 10, 2);
490
+
491
+ // Modify upload directory
492
+ add_filter('upload_dir', function(array $uploads): array {
493
+ // Custom upload path for specific post types
494
+ if (isset($_POST['post_id'])) {
495
+ $post_type = get_post_type((int) $_POST['post_id']);
496
+ if ($post_type === 'product') {
497
+ $uploads['subdir'] = '/products' . $uploads['subdir'];
498
+ $uploads['path'] = $uploads['basedir'] . $uploads['subdir'];
499
+ $uploads['url'] = $uploads['baseurl'] . $uploads['subdir'];
500
+ }
501
+ }
502
+ return $uploads;
503
+ });
504
+
505
+ // === SECURITY FILTERS ===
506
+
507
+ // Modify allowed HTML in wp_kses
508
+ add_filter('wp_kses_allowed_html', function(array $allowed, string $context): array {
509
+ if ($context === 'post') {
510
+ $allowed['iframe'] = [
511
+ 'src' => true,
512
+ 'width' => true,
513
+ 'height' => true,
514
+ 'frameborder' => true,
515
+ 'allowfullscreen' => true,
516
+ ];
517
+ }
518
+ return $allowed;
519
+ }, 10, 2);
520
+
521
+ // Modify authentication
522
+ add_filter('authenticate', function(?WP_User $user, string $username, string $password): WP_User|WP_Error|null {
523
+ // Block login for specific conditions
524
+ if ($username === 'admin') {
525
+ return new WP_Error('invalid_username', __('Direct admin login is disabled.', 'my-plugin'));
526
+ }
527
+ return $user;
528
+ }, 30, 3);
529
+
530
+ // === REST API FILTERS ===
531
+
532
+ // Modify REST response
533
+ add_filter('rest_prepare_post', function(WP_REST_Response $response, WP_Post $post, WP_REST_Request $request): WP_REST_Response {
534
+ // Add custom field to response
535
+ $response->data['reading_time'] = my_plugin_calculate_reading_time($post->post_content);
536
+ return $response;
537
+ }, 10, 3);
538
+ ```
539
+
540
+ ### Removing Filters
541
+
542
+ ```php
543
+ <?php
544
+ /**
545
+ * Remove filters
546
+ */
547
+
548
+ // Remove wpautop (auto paragraphs)
549
+ remove_filter('the_content', 'wpautop');
550
+ remove_filter('the_excerpt', 'wpautop');
551
+
552
+ // Remove wptexturize (smart quotes)
553
+ remove_filter('the_content', 'wptexturize');
554
+ remove_filter('the_title', 'wptexturize');
555
+ remove_filter('comment_text', 'wptexturize');
556
+
557
+ // Remove specific filter (must match priority)
558
+ remove_filter('the_content', 'some_callback', 10);
559
+
560
+ // Remove all filters from a hook
561
+ remove_all_filters('the_content');
562
+
563
+ // Check if filter is applied
564
+ if (has_filter('the_content', 'some_callback')) {
565
+ // Filter is registered
566
+ }
567
+ ```
568
+
569
+ ---
570
+
571
+ ## Creating Custom Hooks
572
+
573
+ Allow other developers to extend your plugin/theme.
574
+
575
+ ### Custom Actions
576
+
577
+ ```php
578
+ <?php
579
+ declare(strict_types=1);
580
+
581
+ namespace MyPlugin;
582
+
583
+ /**
584
+ * Example: Custom hooks in a plugin
585
+ */
586
+ class OrderProcessor {
587
+
588
+ /**
589
+ * Process an order with custom hooks
590
+ */
591
+ public function process_order(array $order_data): int {
592
+ // Allow modification of order data before processing
593
+ $order_data = apply_filters('my_plugin_pre_process_order', $order_data);
594
+
595
+ // Action before order creation
596
+ do_action('my_plugin_before_create_order', $order_data);
597
+
598
+ // Create the order
599
+ $order_id = $this->create_order($order_data);
600
+
601
+ if ($order_id) {
602
+ // Action after successful order creation
603
+ do_action('my_plugin_order_created', $order_id, $order_data);
604
+
605
+ // Process payment
606
+ $payment_result = $this->process_payment($order_id);
607
+
608
+ if ($payment_result) {
609
+ // Action after successful payment
610
+ do_action('my_plugin_payment_complete', $order_id, $payment_result);
611
+ } else {
612
+ // Action on payment failure
613
+ do_action('my_plugin_payment_failed', $order_id);
614
+ }
615
+ }
616
+
617
+ // Action after all processing complete
618
+ do_action('my_plugin_after_process_order', $order_id, $order_data);
619
+
620
+ return $order_id;
621
+ }
622
+
623
+ /**
624
+ * Get order total with filter
625
+ */
626
+ public function get_order_total(int $order_id): float {
627
+ $subtotal = $this->calculate_subtotal($order_id);
628
+ $shipping = $this->calculate_shipping($order_id);
629
+ $tax = $this->calculate_tax($order_id);
630
+
631
+ $total = $subtotal + $shipping + $tax;
632
+
633
+ // Allow modification of total (for discounts, fees, etc.)
634
+ return (float) apply_filters('my_plugin_order_total', $total, $order_id, [
635
+ 'subtotal' => $subtotal,
636
+ 'shipping' => $shipping,
637
+ 'tax' => $tax,
638
+ ]);
639
+ }
640
+
641
+ /**
642
+ * Generate email content with filter
643
+ */
644
+ public function get_order_email_content(int $order_id): string {
645
+ $order = $this->get_order($order_id);
646
+
647
+ $content = sprintf(
648
+ __('Order #%d has been placed.', 'my-plugin'),
649
+ $order_id
650
+ );
651
+
652
+ // Allow complete override or modification
653
+ return apply_filters('my_plugin_order_email_content', $content, $order_id, $order);
654
+ }
655
+ }
656
+
657
+ /**
658
+ * Example usage by another developer
659
+ */
660
+
661
+ // Add discount to order total
662
+ add_filter('my_plugin_order_total', function(float $total, int $order_id, array $components): float {
663
+ // Apply 10% discount for orders over $100
664
+ if ($total > 100) {
665
+ $total *= 0.9;
666
+ }
667
+ return $total;
668
+ }, 10, 3);
669
+
670
+ // Send notification on order creation
671
+ add_action('my_plugin_order_created', function(int $order_id, array $order_data): void {
672
+ // Send Slack notification
673
+ my_send_slack_notification("New order #{$order_id} created!");
674
+ }, 10, 2);
675
+
676
+ // Custom email content
677
+ add_filter('my_plugin_order_email_content', function(string $content, int $order_id, object $order): string {
678
+ // Add custom footer
679
+ $content .= "\n\nThank you for your business!";
680
+ return $content;
681
+ }, 10, 3);
682
+ ```
683
+
684
+ ### Custom Filter with Default Value
685
+
686
+ ```php
687
+ <?php
688
+ /**
689
+ * Create filter with sensible defaults
690
+ */
691
+
692
+ /**
693
+ * Get items per page with filter
694
+ */
695
+ function my_plugin_get_items_per_page(): int {
696
+ $default = 10;
697
+
698
+ /**
699
+ * Filter the number of items per page
700
+ *
701
+ * @since 1.0.0
702
+ *
703
+ * @param int $items_per_page Number of items. Default 10.
704
+ */
705
+ return (int) apply_filters('my_plugin_items_per_page', $default);
706
+ }
707
+
708
+ /**
709
+ * Get allowed file types with filter
710
+ */
711
+ function my_plugin_get_allowed_file_types(): array {
712
+ $defaults = ['jpg', 'jpeg', 'png', 'gif', 'pdf'];
713
+
714
+ /**
715
+ * Filter allowed file types for upload
716
+ *
717
+ * @since 1.0.0
718
+ *
719
+ * @param array $types Array of allowed file extensions.
720
+ */
721
+ return (array) apply_filters('my_plugin_allowed_file_types', $defaults);
722
+ }
723
+
724
+ /**
725
+ * Check if feature is enabled with filter
726
+ */
727
+ function my_plugin_is_feature_enabled(string $feature): bool {
728
+ $enabled_features = [
729
+ 'dark_mode' => true,
730
+ 'analytics' => true,
731
+ 'beta_feature' => false,
732
+ ];
733
+
734
+ $is_enabled = $enabled_features[$feature] ?? false;
735
+
736
+ /**
737
+ * Filter whether a feature is enabled
738
+ *
739
+ * @since 1.0.0
740
+ *
741
+ * @param bool $is_enabled Whether the feature is enabled.
742
+ * @param string $feature The feature slug.
743
+ */
744
+ return (bool) apply_filters('my_plugin_feature_enabled', $is_enabled, $feature);
745
+ }
746
+ ```
747
+
748
+ ---
749
+
750
+ ## Hook Priority & Order
751
+
752
+ ```php
753
+ <?php
754
+ /**
755
+ * Priority determines execution order
756
+ * Lower number = runs earlier
757
+ * Default priority: 10
758
+ */
759
+
760
+ // Runs first (priority 1)
761
+ add_action('init', 'my_first_function', 1);
762
+
763
+ // Runs with default priority (10)
764
+ add_action('init', 'my_default_function');
765
+ add_action('init', 'my_default_function_2'); // Runs after, same priority
766
+
767
+ // Runs last (priority 999)
768
+ add_action('init', 'my_last_function', 999);
769
+
770
+ /**
771
+ * Filter priority example: Modify content
772
+ */
773
+
774
+ // First: Add wrapper
775
+ add_filter('the_content', function(string $content): string {
776
+ return '<div class="content-wrapper">' . $content . '</div>';
777
+ }, 5);
778
+
779
+ // Default: Add sharing buttons
780
+ add_filter('the_content', function(string $content): string {
781
+ return $content . '<div class="share-buttons">...</div>';
782
+ }, 10);
783
+
784
+ // Late: Final output processing
785
+ add_filter('the_content', function(string $content): string {
786
+ // Do final cleanup
787
+ return $content;
788
+ }, 99);
789
+ ```
790
+
791
+ ---
792
+
793
+ ## Best Practices
794
+
795
+ ### Do
796
+
797
+ - Document custom hooks with PHPDoc comments
798
+ - Use prefixed hook names (`my_plugin_*`)
799
+ - Provide sensible default values for filters
800
+ - Pass relevant context to hooks (post ID, data arrays)
801
+ - Check `has_filter()`/`has_action()` before calling expensive operations
802
+ - Use appropriate priorities (don't default to 10 when order matters)
803
+ - Type hint callback parameters and return values (PHP 8.1+)
804
+ - Use namespaced functions or class methods as callbacks
805
+
806
+ ### Do Not
807
+
808
+ - Remove core WordPress hooks without understanding consequences
809
+ - Create hooks that pass sensitive data (passwords, tokens)
810
+ - Rely on global variables in callbacks
811
+ - Forget to return filtered values
812
+ - Use anonymous functions when removal might be needed
813
+ - Create circular hook dependencies
814
+ - Add too many hooks at low priorities (performance impact)
815
+ - Modify data passed by reference unexpectedly
816
+
817
+ ### Security Considerations
818
+
819
+ ```php
820
+ <?php
821
+ /**
822
+ * Security in hooks
823
+ */
824
+
825
+ // Always validate/sanitize data from hooks
826
+ add_filter('my_plugin_user_input', function(mixed $input): string {
827
+ return sanitize_text_field((string) $input);
828
+ });
829
+
830
+ // Check capabilities in action callbacks
831
+ add_action('my_plugin_admin_action', function(): void {
832
+ if (!current_user_can('manage_options')) {
833
+ wp_die(__('Unauthorized', 'my-plugin'));
834
+ }
835
+ // Proceed with admin action
836
+ });
837
+
838
+ // Verify nonces for form submissions
839
+ add_action('admin_post_my_plugin_save', function(): void {
840
+ if (!wp_verify_nonce($_POST['_wpnonce'] ?? '', 'my_plugin_save')) {
841
+ wp_die(__('Security check failed', 'my-plugin'));
842
+ }
843
+ // Process form
844
+ });
845
+ ```