@mytechtoday/augment-extensions 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/augment-extensions/domain-rules/wordpress/README.md +163 -0
  2. package/augment-extensions/domain-rules/wordpress/module.json +32 -0
  3. package/augment-extensions/domain-rules/wordpress/rules/coding-standards.md +617 -0
  4. package/augment-extensions/domain-rules/wordpress/rules/directory-structure.md +270 -0
  5. package/augment-extensions/domain-rules/wordpress/rules/file-patterns.md +423 -0
  6. package/augment-extensions/domain-rules/wordpress/rules/gutenberg-blocks.md +493 -0
  7. package/augment-extensions/domain-rules/wordpress/rules/performance.md +568 -0
  8. package/augment-extensions/domain-rules/wordpress/rules/plugin-development.md +510 -0
  9. package/augment-extensions/domain-rules/wordpress/rules/project-detection.md +251 -0
  10. package/augment-extensions/domain-rules/wordpress/rules/rest-api.md +501 -0
  11. package/augment-extensions/domain-rules/wordpress/rules/security.md +564 -0
  12. package/augment-extensions/domain-rules/wordpress/rules/theme-development.md +388 -0
  13. package/augment-extensions/domain-rules/wordpress/rules/woocommerce.md +441 -0
  14. package/augment-extensions/domain-rules/wordpress-plugin/README.md +139 -0
  15. package/augment-extensions/domain-rules/wordpress-plugin/examples/ajax-plugin.md +1599 -0
  16. package/augment-extensions/domain-rules/wordpress-plugin/examples/custom-post-type-plugin.md +1727 -0
  17. package/augment-extensions/domain-rules/wordpress-plugin/examples/gutenberg-block-plugin.md +428 -0
  18. package/augment-extensions/domain-rules/wordpress-plugin/examples/gutenberg-block.md +422 -0
  19. package/augment-extensions/domain-rules/wordpress-plugin/examples/mvc-plugin.md +1623 -0
  20. package/augment-extensions/domain-rules/wordpress-plugin/examples/object-oriented-plugin.md +1343 -0
  21. package/augment-extensions/domain-rules/wordpress-plugin/examples/rest-endpoint.md +734 -0
  22. package/augment-extensions/domain-rules/wordpress-plugin/examples/settings-page-plugin.md +1350 -0
  23. package/augment-extensions/domain-rules/wordpress-plugin/examples/simple-procedural-plugin.md +503 -0
  24. package/augment-extensions/domain-rules/wordpress-plugin/examples/singleton-plugin.md +971 -0
  25. package/augment-extensions/domain-rules/wordpress-plugin/module.json +53 -0
  26. package/augment-extensions/domain-rules/wordpress-plugin/rules/activation-hooks.md +770 -0
  27. package/augment-extensions/domain-rules/wordpress-plugin/rules/admin-interface.md +874 -0
  28. package/augment-extensions/domain-rules/wordpress-plugin/rules/ajax-handlers.md +629 -0
  29. package/augment-extensions/domain-rules/wordpress-plugin/rules/asset-management.md +559 -0
  30. package/augment-extensions/domain-rules/wordpress-plugin/rules/context-providers.md +709 -0
  31. package/augment-extensions/domain-rules/wordpress-plugin/rules/cron-jobs.md +736 -0
  32. package/augment-extensions/domain-rules/wordpress-plugin/rules/database-management.md +1057 -0
  33. package/augment-extensions/domain-rules/wordpress-plugin/rules/documentation-standards.md +463 -0
  34. package/augment-extensions/domain-rules/wordpress-plugin/rules/frontend-functionality.md +478 -0
  35. package/augment-extensions/domain-rules/wordpress-plugin/rules/gutenberg-blocks.md +818 -0
  36. package/augment-extensions/domain-rules/wordpress-plugin/rules/internationalization.md +416 -0
  37. package/augment-extensions/domain-rules/wordpress-plugin/rules/migration.md +667 -0
  38. package/augment-extensions/domain-rules/wordpress-plugin/rules/performance-optimization.md +878 -0
  39. package/augment-extensions/domain-rules/wordpress-plugin/rules/plugin-architecture.md +693 -0
  40. package/augment-extensions/domain-rules/wordpress-plugin/rules/plugin-structure.md +352 -0
  41. package/augment-extensions/domain-rules/wordpress-plugin/rules/rest-api.md +818 -0
  42. package/augment-extensions/domain-rules/wordpress-plugin/rules/scaffolding-workflow.md +624 -0
  43. package/augment-extensions/domain-rules/wordpress-plugin/rules/security-best-practices.md +866 -0
  44. package/augment-extensions/domain-rules/wordpress-plugin/rules/testing-patterns.md +1165 -0
  45. package/augment-extensions/domain-rules/wordpress-plugin/rules/testing.md +414 -0
  46. package/augment-extensions/domain-rules/wordpress-plugin/rules/vscode-integration.md +751 -0
  47. package/augment-extensions/domain-rules/wordpress-plugin/rules/woocommerce-integration.md +949 -0
  48. package/augment-extensions/domain-rules/wordpress-plugin/rules/wordpress-org-submission.md +458 -0
  49. package/augment-extensions/examples/gutenberg-block-plugin/README.md +101 -0
  50. package/augment-extensions/examples/gutenberg-block-plugin/examples/testimonial-block.md +428 -0
  51. package/augment-extensions/examples/gutenberg-block-plugin/module.json +40 -0
  52. package/augment-extensions/examples/rest-api-plugin/README.md +98 -0
  53. package/augment-extensions/examples/rest-api-plugin/examples/task-manager-api.md +1299 -0
  54. package/augment-extensions/examples/rest-api-plugin/module.json +40 -0
  55. package/augment-extensions/examples/woocommerce-extension/README.md +98 -0
  56. package/augment-extensions/examples/woocommerce-extension/examples/product-customizer.md +763 -0
  57. package/augment-extensions/examples/woocommerce-extension/module.json +40 -0
  58. package/augment-extensions/workflows/wordpress-plugin/README.md +232 -0
  59. package/augment-extensions/workflows/wordpress-plugin/ai-prompts.md +839 -0
  60. package/augment-extensions/workflows/wordpress-plugin/bead-decomposition-patterns.md +854 -0
  61. package/augment-extensions/workflows/wordpress-plugin/examples/complete-plugin-example.md +540 -0
  62. package/augment-extensions/workflows/wordpress-plugin/examples/custom-post-type-example.md +1083 -0
  63. package/augment-extensions/workflows/wordpress-plugin/examples/feature-addition-workflow.md +669 -0
  64. package/augment-extensions/workflows/wordpress-plugin/examples/plugin-creation-workflow.md +597 -0
  65. package/augment-extensions/workflows/wordpress-plugin/examples/secure-form-handler-example.md +925 -0
  66. package/augment-extensions/workflows/wordpress-plugin/examples/security-audit-workflow.md +752 -0
  67. package/augment-extensions/workflows/wordpress-plugin/examples/wordpress-org-submission-workflow.md +773 -0
  68. package/augment-extensions/workflows/wordpress-plugin/module.json +49 -0
  69. package/augment-extensions/workflows/wordpress-plugin/rules/best-practices.md +942 -0
  70. package/augment-extensions/workflows/wordpress-plugin/rules/development-workflow.md +702 -0
  71. package/augment-extensions/workflows/wordpress-plugin/rules/submission-workflow.md +728 -0
  72. package/augment-extensions/workflows/wordpress-plugin/rules/testing-workflow.md +775 -0
  73. package/cli/dist/cli.js +5 -1
  74. package/cli/dist/cli.js.map +1 -1
  75. package/cli/dist/commands/show.d.ts.map +1 -1
  76. package/cli/dist/commands/show.js +41 -0
  77. package/cli/dist/commands/show.js.map +1 -1
  78. package/modules.md +52 -0
  79. package/package.json +1 -1
@@ -0,0 +1,1623 @@
1
+ # MVC Plugin Example
2
+
3
+ ## Overview
4
+
5
+ This example demonstrates **Model-View-Controller (MVC) Pattern** - a clean separation of data, presentation, and business logic for WordPress plugins.
6
+
7
+ **Complexity**: Medium-High
8
+ **File Count**: 15-40 files
9
+ **Team Size**: 2-5 developers
10
+ **Use Case**: Complex business logic, multiple views, testable code, clear separation of concerns
11
+
12
+ ---
13
+
14
+ ## Complete Plugin: "Task Manager MVC"
15
+
16
+ A complete task management plugin demonstrating MVC architecture with models, views, controllers, and routing.
17
+
18
+ ---
19
+
20
+ ## Directory Structure
21
+
22
+ ```
23
+ task-manager-mvc/
24
+ ├── task-manager-mvc.php # Main plugin file (bootstrap)
25
+ ├── uninstall.php # Uninstall cleanup
26
+ ├── readme.txt # WordPress.org readme
27
+ ├── app/
28
+ │ ├── models/
29
+ │ │ ├── Task.php # Task model
30
+ │ │ ├── Category.php # Category model
31
+ │ │ └── BaseModel.php # Base model class
32
+ │ ├── controllers/
33
+ │ │ ├── TaskController.php # Task controller
34
+ │ │ ├── CategoryController.php # Category controller
35
+ │ │ └── BaseController.php # Base controller class
36
+ │ ├── views/
37
+ │ │ ├── tasks/
38
+ │ │ │ ├── index.php # Task list view
39
+ │ │ │ ├── create.php # Create task view
40
+ │ │ │ ├── edit.php # Edit task view
41
+ │ │ │ └── show.php # Single task view
42
+ │ │ ├── categories/
43
+ │ │ │ ├── index.php # Category list view
44
+ │ │ │ └── form.php # Category form view
45
+ │ │ └── layouts/
46
+ │ │ ├── admin.php # Admin layout
47
+ │ │ └── public.php # Public layout
48
+ │ └── core/
49
+ │ ├── Router.php # URL routing
50
+ │ ├── View.php # View renderer
51
+ │ ├── Database.php # Database abstraction
52
+ │ └── Validator.php # Input validation
53
+ ├── assets/
54
+ │ ├── css/
55
+ │ │ ├── admin.css
56
+ │ │ └── public.css
57
+ │ └── js/
58
+ │ ├── admin.js
59
+ │ └── public.js
60
+ └── languages/
61
+ └── task-manager-mvc.pot
62
+ ```
63
+
64
+ ---
65
+
66
+ ## Main Plugin File
67
+
68
+ ### File: `task-manager-mvc.php`
69
+
70
+ ```php
71
+ <?php
72
+ /**
73
+ * Plugin Name: Task Manager MVC
74
+ * Plugin URI: https://example.com/task-manager-mvc
75
+ * Description: Task management with MVC architecture
76
+ * Version: 1.0.0
77
+ * Author: Your Name
78
+ * Author URI: https://example.com
79
+ * License: GPL-2.0+
80
+ * License URI: http://www.gnu.org/licenses/gpl-2.0.txt
81
+ * Text Domain: task-manager-mvc
82
+ * Domain Path: /languages
83
+ *
84
+ * @package Task_Manager_MVC
85
+ */
86
+
87
+ // Exit if accessed directly
88
+ if (!defined('ABSPATH')) {
89
+ exit;
90
+ }
91
+
92
+ // Define plugin constants
93
+ define('TMM_VERSION', '1.0.0');
94
+ define('TMM_PLUGIN_DIR', plugin_dir_path(__FILE__));
95
+ define('TMM_PLUGIN_URL', plugin_dir_url(__FILE__));
96
+ define('TMM_APP_DIR', TMM_PLUGIN_DIR . 'app/');
97
+
98
+ /**
99
+ * PSR-4 Autoloader
100
+ */
101
+ spl_autoload_register(function ($class) {
102
+ // Only autoload our classes
103
+ if (strpos($class, 'TMM\\') !== 0) {
104
+ return;
105
+ }
106
+
107
+ // Remove namespace prefix
108
+ $class = str_replace('TMM\\', '', $class);
109
+
110
+ // Convert namespace separators to directory separators
111
+ $class = str_replace('\\', DIRECTORY_SEPARATOR, $class);
112
+
113
+ // Build file path
114
+ $file = TMM_APP_DIR . $class . '.php';
115
+
116
+ if (file_exists($file)) {
117
+ require $file;
118
+ }
119
+ });
120
+
121
+ /**
122
+ * Activation hook
123
+ */
124
+ register_activation_hook(__FILE__, function() {
125
+ require_once TMM_PLUGIN_DIR . 'install.php';
126
+ TMM_Install::activate();
127
+ });
128
+
129
+ /**
130
+ * Deactivation hook
131
+ */
132
+ register_deactivation_hook(__FILE__, function() {
133
+ require_once TMM_PLUGIN_DIR . 'install.php';
134
+ TMM_Install::deactivate();
135
+ });
136
+
137
+ /**
138
+ * Initialize the plugin
139
+ */
140
+ add_action('plugins_loaded', function() {
141
+ // Load text domain
142
+ load_plugin_textdomain('task-manager-mvc', false, dirname(plugin_basename(__FILE__)) . '/languages');
143
+
144
+ // Initialize router
145
+ $router = new TMM\Core\Router();
146
+ $router->init();
147
+ });
148
+
149
+ /**
150
+ * Admin menu
151
+ */
152
+ add_action('admin_menu', function() {
153
+ add_menu_page(
154
+ __('Task Manager', 'task-manager-mvc'),
155
+ __('Tasks', 'task-manager-mvc'),
156
+ 'manage_options',
157
+ 'tmm-tasks',
158
+ 'tmm_render_admin_page',
159
+ 'dashicons-list-view',
160
+ 25
161
+ );
162
+ });
163
+
164
+ /**
165
+ * Render admin page (entry point for router)
166
+ */
167
+ function tmm_render_admin_page() {
168
+ $router = new TMM\Core\Router();
169
+ $router->dispatch();
170
+ }
171
+
172
+ /**
173
+ * Enqueue assets
174
+ */
175
+ add_action('admin_enqueue_scripts', function($hook) {
176
+ if (strpos($hook, 'tmm-') !== 0) {
177
+ return;
178
+ }
179
+
180
+ wp_enqueue_style('tmm-admin', TMM_PLUGIN_URL . 'assets/css/admin.css', array(), TMM_VERSION);
181
+ wp_enqueue_script('tmm-admin', TMM_PLUGIN_URL . 'assets/js/admin.js', array('jquery'), TMM_VERSION, true);
182
+ });
183
+ ```
184
+
185
+ ---
186
+
187
+ ## MVC Core Components
188
+
189
+ ### File: `app/core/Router.php`
190
+
191
+ ```php
192
+ <?php
193
+ namespace TMM\Core;
194
+
195
+ /**
196
+ * Router class - handles URL routing and dispatching to controllers
197
+ *
198
+ * @package Task_Manager_MVC
199
+ */
200
+ class Router {
201
+ /**
202
+ * Routes registry
203
+ *
204
+ * @var array
205
+ */
206
+ private $routes = array();
207
+
208
+ /**
209
+ * Initialize router and register routes
210
+ */
211
+ public function init() {
212
+ $this->register_routes();
213
+ }
214
+
215
+ /**
216
+ * Register all routes
217
+ */
218
+ private function register_routes() {
219
+ // Task routes
220
+ $this->add_route('GET', 'tasks', 'TaskController@index');
221
+ $this->add_route('GET', 'tasks/create', 'TaskController@create');
222
+ $this->add_route('POST', 'tasks/store', 'TaskController@store');
223
+ $this->add_route('GET', 'tasks/edit', 'TaskController@edit');
224
+ $this->add_route('POST', 'tasks/update', 'TaskController@update');
225
+ $this->add_route('POST', 'tasks/delete', 'TaskController@delete');
226
+
227
+ // Category routes
228
+ $this->add_route('GET', 'categories', 'CategoryController@index');
229
+ $this->add_route('POST', 'categories/store', 'CategoryController@store');
230
+ }
231
+
232
+ /**
233
+ * Add a route
234
+ *
235
+ * @param string $method HTTP method
236
+ * @param string $path Route path
237
+ * @param string $action Controller@method
238
+ */
239
+ public function add_route($method, $path, $action) {
240
+ $this->routes[$method][$path] = $action;
241
+ }
242
+
243
+ /**
244
+ * Dispatch request to appropriate controller
245
+ */
246
+ public function dispatch() {
247
+ $method = $_SERVER['REQUEST_METHOD'];
248
+ $action = isset($_GET['action']) ? sanitize_text_field($_GET['action']) : 'tasks';
249
+
250
+ // Find matching route
251
+ if (isset($this->routes[$method][$action])) {
252
+ $route = $this->routes[$method][$action];
253
+ $this->call_controller($route);
254
+ } else {
255
+ // Default to task index
256
+ $this->call_controller('TaskController@index');
257
+ }
258
+ }
259
+
260
+ /**
261
+ * Call controller method
262
+ *
263
+ * @param string $route Controller@method string
264
+ */
265
+ private function call_controller($route) {
266
+ list($controller, $method) = explode('@', $route);
267
+
268
+ $controller_class = "TMM\\Controllers\\{$controller}";
269
+
270
+ if (class_exists($controller_class)) {
271
+ $controller_instance = new $controller_class();
272
+
273
+ if (method_exists($controller_instance, $method)) {
274
+ $controller_instance->$method();
275
+ }
276
+ }
277
+ }
278
+ }
279
+ ```
280
+
281
+ ### File: `app/core/View.php`
282
+
283
+ ```php
284
+ <?php
285
+ namespace TMM\Core;
286
+
287
+ /**
288
+ * View class - handles view rendering
289
+ *
290
+ * @package Task_Manager_MVC
291
+ */
292
+ class View {
293
+ /**
294
+ * Render a view
295
+ *
296
+ * @param string $view View path (e.g., 'tasks/index')
297
+ * @param array $data Data to pass to view
298
+ * @param string $layout Layout to use (default: 'admin')
299
+ */
300
+ public static function render($view, $data = array(), $layout = 'admin') {
301
+ // Extract data to variables
302
+ extract($data);
303
+
304
+ // Start output buffering
305
+ ob_start();
306
+
307
+ // Include view file
308
+ $view_file = TMM_APP_DIR . 'views/' . $view . '.php';
309
+ if (file_exists($view_file)) {
310
+ include $view_file;
311
+ } else {
312
+ echo '<p>View not found: ' . esc_html($view) . '</p>';
313
+ }
314
+
315
+ // Get view content
316
+ $content = ob_get_clean();
317
+
318
+ // Render with layout
319
+ if ($layout) {
320
+ self::render_layout($layout, $content, $data);
321
+ } else {
322
+ echo $content;
323
+ }
324
+ }
325
+
326
+ /**
327
+ * Render layout
328
+ *
329
+ * @param string $layout Layout name
330
+ * @param string $content View content
331
+ * @param array $data Additional data
332
+ */
333
+ private static function render_layout($layout, $content, $data = array()) {
334
+ extract($data);
335
+
336
+ $layout_file = TMM_APP_DIR . 'views/layouts/' . $layout . '.php';
337
+ if (file_exists($layout_file)) {
338
+ include $layout_file;
339
+ } else {
340
+ echo $content;
341
+ }
342
+ }
343
+
344
+ /**
345
+ * Render partial view
346
+ *
347
+ * @param string $partial Partial view path
348
+ * @param array $data Data to pass to partial
349
+ */
350
+ public static function partial($partial, $data = array()) {
351
+ extract($data);
352
+
353
+ $partial_file = TMM_APP_DIR . 'views/' . $partial . '.php';
354
+ if (file_exists($partial_file)) {
355
+ include $partial_file;
356
+ }
357
+ }
358
+ }
359
+ ```
360
+
361
+ ### File: `app/core/Validator.php`
362
+
363
+ ```php
364
+ <?php
365
+ namespace TMM\Core;
366
+
367
+ /**
368
+ * Validator class - handles input validation
369
+ *
370
+ * @package Task_Manager_MVC
371
+ */
372
+ class Validator {
373
+ /**
374
+ * Validation errors
375
+ *
376
+ * @var array
377
+ */
378
+ private $errors = array();
379
+
380
+ /**
381
+ * Validate data against rules
382
+ *
383
+ * @param array $data Data to validate
384
+ * @param array $rules Validation rules
385
+ * @return bool
386
+ */
387
+ public function validate($data, $rules) {
388
+ $this->errors = array();
389
+
390
+ foreach ($rules as $field => $rule_string) {
391
+ $rules_array = explode('|', $rule_string);
392
+
393
+ foreach ($rules_array as $rule) {
394
+ $this->apply_rule($field, $data[$field] ?? '', $rule);
395
+ }
396
+ }
397
+
398
+ return empty($this->errors);
399
+ }
400
+
401
+ /**
402
+ * Apply validation rule
403
+ *
404
+ * @param string $field Field name
405
+ * @param mixed $value Field value
406
+ * @param string $rule Rule to apply
407
+ */
408
+ private function apply_rule($field, $value, $rule) {
409
+ if ($rule === 'required' && empty($value)) {
410
+ $this->errors[$field][] = sprintf(__('%s is required', 'task-manager-mvc'), ucfirst($field));
411
+ }
412
+
413
+ if (strpos($rule, 'min:') === 0) {
414
+ $min = (int) substr($rule, 4);
415
+ if (strlen($value) < $min) {
416
+ $this->errors[$field][] = sprintf(__('%s must be at least %d characters', 'task-manager-mvc'), ucfirst($field), $min);
417
+ }
418
+ }
419
+
420
+ if (strpos($rule, 'max:') === 0) {
421
+ $max = (int) substr($rule, 4);
422
+ if (strlen($value) > $max) {
423
+ $this->errors[$field][] = sprintf(__('%s must not exceed %d characters', 'task-manager-mvc'), ucfirst($field), $max);
424
+ }
425
+ }
426
+ }
427
+
428
+ /**
429
+ * Get validation errors
430
+ *
431
+ * @return array
432
+ */
433
+ public function errors() {
434
+ return $this->errors;
435
+ }
436
+
437
+ /**
438
+ * Check if validation has errors
439
+ *
440
+ * @return bool
441
+ */
442
+ public function has_errors() {
443
+ return !empty($this->errors);
444
+ }
445
+ }
446
+ ```
447
+
448
+ ---
449
+
450
+ ## Model Layer
451
+
452
+ ### File: `app/models/BaseModel.php`
453
+
454
+ ```php
455
+ <?php
456
+ namespace TMM\Models;
457
+
458
+ /**
459
+ * Base Model class - provides common database operations
460
+ *
461
+ * @package Task_Manager_MVC
462
+ */
463
+ abstract class BaseModel {
464
+ /**
465
+ * Database table name
466
+ *
467
+ * @var string
468
+ */
469
+ protected $table;
470
+
471
+ /**
472
+ * Primary key column
473
+ *
474
+ * @var string
475
+ */
476
+ protected $primary_key = 'id';
477
+
478
+ /**
479
+ * Model attributes
480
+ *
481
+ * @var array
482
+ */
483
+ protected $attributes = array();
484
+
485
+ /**
486
+ * Get WordPress database object
487
+ *
488
+ * @return wpdb
489
+ */
490
+ protected function db() {
491
+ global $wpdb;
492
+ return $wpdb;
493
+ }
494
+
495
+ /**
496
+ * Get full table name with prefix
497
+ *
498
+ * @return string
499
+ */
500
+ protected function get_table_name() {
501
+ return $this->db()->prefix . $this->table;
502
+ }
503
+
504
+ /**
505
+ * Find record by ID
506
+ *
507
+ * @param int $id Record ID
508
+ * @return static|null
509
+ */
510
+ public static function find($id) {
511
+ $instance = new static();
512
+ $table = $instance->get_table_name();
513
+ $pk = $instance->primary_key;
514
+
515
+ $result = $instance->db()->get_row(
516
+ $instance->db()->prepare("SELECT * FROM {$table} WHERE {$pk} = %d", $id),
517
+ ARRAY_A
518
+ );
519
+
520
+ if ($result) {
521
+ $instance->attributes = $result;
522
+ return $instance;
523
+ }
524
+
525
+ return null;
526
+ }
527
+
528
+ /**
529
+ * Get all records
530
+ *
531
+ * @param array $where Where conditions
532
+ * @param array $order_by Order by clauses
533
+ * @return array
534
+ */
535
+ public static function all($where = array(), $order_by = array()) {
536
+ $instance = new static();
537
+ $table = $instance->get_table_name();
538
+
539
+ $sql = "SELECT * FROM {$table}";
540
+
541
+ if (!empty($where)) {
542
+ $conditions = array();
543
+ foreach ($where as $key => $value) {
544
+ $conditions[] = $instance->db()->prepare("{$key} = %s", $value);
545
+ }
546
+ $sql .= " WHERE " . implode(' AND ', $conditions);
547
+ }
548
+
549
+ if (!empty($order_by)) {
550
+ $sql .= " ORDER BY " . implode(', ', $order_by);
551
+ }
552
+
553
+ $results = $instance->db()->get_results($sql, ARRAY_A);
554
+
555
+ $models = array();
556
+ foreach ($results as $result) {
557
+ $model = new static();
558
+ $model->attributes = $result;
559
+ $models[] = $model;
560
+ }
561
+
562
+ return $models;
563
+ }
564
+
565
+ /**
566
+ * Save model (insert or update)
567
+ *
568
+ * @return bool
569
+ */
570
+ public function save() {
571
+ $table = $this->get_table_name();
572
+
573
+ if (isset($this->attributes[$this->primary_key]) && $this->attributes[$this->primary_key] > 0) {
574
+ // Update
575
+ $id = $this->attributes[$this->primary_key];
576
+ unset($this->attributes[$this->primary_key]);
577
+
578
+ $result = $this->db()->update(
579
+ $table,
580
+ $this->attributes,
581
+ array($this->primary_key => $id)
582
+ );
583
+
584
+ $this->attributes[$this->primary_key] = $id;
585
+ return $result !== false;
586
+ } else {
587
+ // Insert
588
+ $result = $this->db()->insert($table, $this->attributes);
589
+
590
+ if ($result) {
591
+ $this->attributes[$this->primary_key] = $this->db()->insert_id;
592
+ return true;
593
+ }
594
+
595
+ return false;
596
+ }
597
+ }
598
+
599
+ /**
600
+ * Delete model
601
+ *
602
+ * @return bool
603
+ */
604
+ public function delete() {
605
+ if (!isset($this->attributes[$this->primary_key])) {
606
+ return false;
607
+ }
608
+
609
+ $table = $this->get_table_name();
610
+
611
+ return $this->db()->delete(
612
+ $table,
613
+ array($this->primary_key => $this->attributes[$this->primary_key])
614
+ ) !== false;
615
+ }
616
+
617
+ /**
618
+ * Get attribute value
619
+ *
620
+ * @param string $key Attribute key
621
+ * @return mixed
622
+ */
623
+ public function __get($key) {
624
+ return $this->attributes[$key] ?? null;
625
+ }
626
+
627
+ /**
628
+ * Set attribute value
629
+ *
630
+ * @param string $key Attribute key
631
+ * @param mixed $value Attribute value
632
+ */
633
+ public function __set($key, $value) {
634
+ $this->attributes[$key] = $value;
635
+ }
636
+
637
+ /**
638
+ * Get all attributes
639
+ *
640
+ * @return array
641
+ */
642
+ public function to_array() {
643
+ return $this->attributes;
644
+ }
645
+ }
646
+ ```
647
+
648
+ ### File: `app/models/Task.php`
649
+
650
+ ```php
651
+ <?php
652
+ namespace TMM\Models;
653
+
654
+ /**
655
+ * Task Model
656
+ *
657
+ * @package Task_Manager_MVC
658
+ */
659
+ class Task extends BaseModel {
660
+ /**
661
+ * Table name
662
+ *
663
+ * @var string
664
+ */
665
+ protected $table = 'tmm_tasks';
666
+
667
+ /**
668
+ * Get tasks by status
669
+ *
670
+ * @param string $status Task status
671
+ * @return array
672
+ */
673
+ public static function by_status($status) {
674
+ return self::all(array('status' => $status), array('created_at DESC'));
675
+ }
676
+
677
+ /**
678
+ * Get tasks by category
679
+ *
680
+ * @param int $category_id Category ID
681
+ * @return array
682
+ */
683
+ public static function by_category($category_id) {
684
+ return self::all(array('category_id' => $category_id), array('created_at DESC'));
685
+ }
686
+
687
+ /**
688
+ * Mark task as complete
689
+ *
690
+ * @return bool
691
+ */
692
+ public function mark_complete() {
693
+ $this->status = 'completed';
694
+ $this->completed_at = current_time('mysql');
695
+ return $this->save();
696
+ }
697
+
698
+ /**
699
+ * Check if task is overdue
700
+ *
701
+ * @return bool
702
+ */
703
+ public function is_overdue() {
704
+ if (empty($this->due_date) || $this->status === 'completed') {
705
+ return false;
706
+ }
707
+
708
+ return strtotime($this->due_date) < time();
709
+ }
710
+
711
+ /**
712
+ * Get category
713
+ *
714
+ * @return Category|null
715
+ */
716
+ public function category() {
717
+ if (empty($this->category_id)) {
718
+ return null;
719
+ }
720
+
721
+ return Category::find($this->category_id);
722
+ }
723
+ }
724
+ ```
725
+
726
+ ### File: `app/models/Category.php`
727
+
728
+ ```php
729
+ <?php
730
+ namespace TMM\Models;
731
+
732
+ /**
733
+ * Category Model
734
+ *
735
+ * @package Task_Manager_MVC
736
+ */
737
+ class Category extends BaseModel {
738
+ /**
739
+ * Table name
740
+ *
741
+ * @var string
742
+ */
743
+ protected $table = 'tmm_categories';
744
+
745
+ /**
746
+ * Get tasks in this category
747
+ *
748
+ * @return array
749
+ */
750
+ public function tasks() {
751
+ return Task::by_category($this->id);
752
+ }
753
+
754
+ /**
755
+ * Get task count
756
+ *
757
+ * @return int
758
+ */
759
+ public function task_count() {
760
+ $table = $this->db()->prefix . 'tmm_tasks';
761
+
762
+ return (int) $this->db()->get_var(
763
+ $this->db()->prepare(
764
+ "SELECT COUNT(*) FROM {$table} WHERE category_id = %d",
765
+ $this->id
766
+ )
767
+ );
768
+ }
769
+ }
770
+ ```
771
+
772
+ ---
773
+
774
+ ## Controller Layer
775
+
776
+ ### File: `app/controllers/BaseController.php`
777
+
778
+ ```php
779
+ <?php
780
+ namespace TMM\Controllers;
781
+
782
+ use TMM\Core\View;
783
+ use TMM\Core\Validator;
784
+
785
+ /**
786
+ * Base Controller class
787
+ *
788
+ * @package Task_Manager_MVC
789
+ */
790
+ abstract class BaseController {
791
+ /**
792
+ * Validator instance
793
+ *
794
+ * @var Validator
795
+ */
796
+ protected $validator;
797
+
798
+ /**
799
+ * Constructor
800
+ */
801
+ public function __construct() {
802
+ $this->validator = new Validator();
803
+ }
804
+
805
+ /**
806
+ * Render view
807
+ *
808
+ * @param string $view View path
809
+ * @param array $data Data to pass to view
810
+ * @param string $layout Layout to use
811
+ */
812
+ protected function view($view, $data = array(), $layout = 'admin') {
813
+ View::render($view, $data, $layout);
814
+ }
815
+
816
+ /**
817
+ * Redirect to URL
818
+ *
819
+ * @param string $url URL to redirect to
820
+ */
821
+ protected function redirect($url) {
822
+ wp_safe_redirect($url);
823
+ exit;
824
+ }
825
+
826
+ /**
827
+ * Redirect back with message
828
+ *
829
+ * @param string $message Message to display
830
+ * @param string $type Message type (success, error, warning, info)
831
+ */
832
+ protected function redirect_back($message, $type = 'success') {
833
+ set_transient('tmm_flash_message', array('message' => $message, 'type' => $type), 30);
834
+ $this->redirect(wp_get_referer());
835
+ }
836
+
837
+ /**
838
+ * Verify nonce
839
+ *
840
+ * @param string $action Nonce action
841
+ * @param string $name Nonce name
842
+ * @return bool
843
+ */
844
+ protected function verify_nonce($action, $name = '_wpnonce') {
845
+ return isset($_POST[$name]) && wp_verify_nonce($_POST[$name], $action);
846
+ }
847
+
848
+ /**
849
+ * Check user capability
850
+ *
851
+ * @param string $capability Capability to check
852
+ * @return bool
853
+ */
854
+ protected function can($capability) {
855
+ return current_user_can($capability);
856
+ }
857
+ }
858
+ ```
859
+
860
+ ### File: `app/controllers/TaskController.php`
861
+
862
+ ```php
863
+ <?php
864
+ namespace TMM\Controllers;
865
+
866
+ use TMM\Models\Task;
867
+ use TMM\Models\Category;
868
+
869
+ /**
870
+ * Task Controller
871
+ *
872
+ * @package Task_Manager_MVC
873
+ */
874
+ class TaskController extends BaseController {
875
+ /**
876
+ * Display task list
877
+ */
878
+ public function index() {
879
+ $status = isset($_GET['status']) ? sanitize_text_field($_GET['status']) : 'all';
880
+
881
+ if ($status === 'all') {
882
+ $tasks = Task::all(array(), array('created_at DESC'));
883
+ } else {
884
+ $tasks = Task::by_status($status);
885
+ }
886
+
887
+ $this->view('tasks/index', array(
888
+ 'tasks' => $tasks,
889
+ 'current_status' => $status,
890
+ ));
891
+ }
892
+
893
+ /**
894
+ * Show create task form
895
+ */
896
+ public function create() {
897
+ $categories = Category::all();
898
+
899
+ $this->view('tasks/create', array(
900
+ 'categories' => $categories,
901
+ ));
902
+ }
903
+
904
+ /**
905
+ * Store new task
906
+ */
907
+ public function store() {
908
+ // Verify nonce
909
+ if (!$this->verify_nonce('tmm_create_task')) {
910
+ wp_die(__('Security check failed', 'task-manager-mvc'));
911
+ }
912
+
913
+ // Check capability
914
+ if (!$this->can('manage_options')) {
915
+ wp_die(__('You do not have permission to perform this action', 'task-manager-mvc'));
916
+ }
917
+
918
+ // Validate input
919
+ $valid = $this->validator->validate($_POST, array(
920
+ 'title' => 'required|min:3|max:200',
921
+ 'description' => 'max:1000',
922
+ ));
923
+
924
+ if (!$valid) {
925
+ set_transient('tmm_validation_errors', $this->validator->errors(), 30);
926
+ $this->redirect_back('', 'error');
927
+ return;
928
+ }
929
+
930
+ // Create task
931
+ $task = new Task();
932
+ $task->title = sanitize_text_field($_POST['title']);
933
+ $task->description = sanitize_textarea_field($_POST['description']);
934
+ $task->category_id = isset($_POST['category_id']) ? absint($_POST['category_id']) : 0;
935
+ $task->due_date = isset($_POST['due_date']) ? sanitize_text_field($_POST['due_date']) : '';
936
+ $task->status = 'pending';
937
+ $task->created_at = current_time('mysql');
938
+
939
+ if ($task->save()) {
940
+ $this->redirect_back(__('Task created successfully', 'task-manager-mvc'), 'success');
941
+ } else {
942
+ $this->redirect_back(__('Failed to create task', 'task-manager-mvc'), 'error');
943
+ }
944
+ }
945
+
946
+ /**
947
+ * Show edit task form
948
+ */
949
+ public function edit() {
950
+ $task_id = isset($_GET['id']) ? absint($_GET['id']) : 0;
951
+ $task = Task::find($task_id);
952
+
953
+ if (!$task) {
954
+ wp_die(__('Task not found', 'task-manager-mvc'));
955
+ }
956
+
957
+ $categories = Category::all();
958
+
959
+ $this->view('tasks/edit', array(
960
+ 'task' => $task,
961
+ 'categories' => $categories,
962
+ ));
963
+ }
964
+
965
+ /**
966
+ * Update task
967
+ */
968
+ public function update() {
969
+ // Verify nonce
970
+ if (!$this->verify_nonce('tmm_update_task')) {
971
+ wp_die(__('Security check failed', 'task-manager-mvc'));
972
+ }
973
+
974
+ // Check capability
975
+ if (!$this->can('manage_options')) {
976
+ wp_die(__('You do not have permission to perform this action', 'task-manager-mvc'));
977
+ }
978
+
979
+ $task_id = isset($_POST['task_id']) ? absint($_POST['task_id']) : 0;
980
+ $task = Task::find($task_id);
981
+
982
+ if (!$task) {
983
+ $this->redirect_back(__('Task not found', 'task-manager-mvc'), 'error');
984
+ return;
985
+ }
986
+
987
+ // Validate input
988
+ $valid = $this->validator->validate($_POST, array(
989
+ 'title' => 'required|min:3|max:200',
990
+ 'description' => 'max:1000',
991
+ ));
992
+
993
+ if (!$valid) {
994
+ set_transient('tmm_validation_errors', $this->validator->errors(), 30);
995
+ $this->redirect_back('', 'error');
996
+ return;
997
+ }
998
+
999
+ // Update task
1000
+ $task->title = sanitize_text_field($_POST['title']);
1001
+ $task->description = sanitize_textarea_field($_POST['description']);
1002
+ $task->category_id = isset($_POST['category_id']) ? absint($_POST['category_id']) : 0;
1003
+ $task->due_date = isset($_POST['due_date']) ? sanitize_text_field($_POST['due_date']) : '';
1004
+ $task->status = isset($_POST['status']) ? sanitize_text_field($_POST['status']) : 'pending';
1005
+ $task->updated_at = current_time('mysql');
1006
+
1007
+ if ($task->save()) {
1008
+ $this->redirect_back(__('Task updated successfully', 'task-manager-mvc'), 'success');
1009
+ } else {
1010
+ $this->redirect_back(__('Failed to update task', 'task-manager-mvc'), 'error');
1011
+ }
1012
+ }
1013
+
1014
+ /**
1015
+ * Delete task
1016
+ */
1017
+ public function delete() {
1018
+ // Verify nonce
1019
+ if (!$this->verify_nonce('tmm_delete_task')) {
1020
+ wp_die(__('Security check failed', 'task-manager-mvc'));
1021
+ }
1022
+
1023
+ // Check capability
1024
+ if (!$this->can('manage_options')) {
1025
+ wp_die(__('You do not have permission to perform this action', 'task-manager-mvc'));
1026
+ }
1027
+
1028
+ $task_id = isset($_POST['task_id']) ? absint($_POST['task_id']) : 0;
1029
+ $task = Task::find($task_id);
1030
+
1031
+ if (!$task) {
1032
+ $this->redirect_back(__('Task not found', 'task-manager-mvc'), 'error');
1033
+ return;
1034
+ }
1035
+
1036
+ if ($task->delete()) {
1037
+ $this->redirect_back(__('Task deleted successfully', 'task-manager-mvc'), 'success');
1038
+ } else {
1039
+ $this->redirect_back(__('Failed to delete task', 'task-manager-mvc'), 'error');
1040
+ }
1041
+ }
1042
+ }
1043
+ ```
1044
+
1045
+ ---
1046
+
1047
+ ## View Layer
1048
+
1049
+ ### File: `app/views/layouts/admin.php`
1050
+
1051
+ ```php
1052
+ <div class="wrap tmm-admin">
1053
+ <h1><?php echo esc_html(get_admin_page_title()); ?></h1>
1054
+
1055
+ <?php
1056
+ // Display flash messages
1057
+ $flash = get_transient('tmm_flash_message');
1058
+ if ($flash):
1059
+ delete_transient('tmm_flash_message');
1060
+ ?>
1061
+ <div class="notice notice-<?php echo esc_attr($flash['type']); ?> is-dismissible">
1062
+ <p><?php echo esc_html($flash['message']); ?></p>
1063
+ </div>
1064
+ <?php endif; ?>
1065
+
1066
+ <?php
1067
+ // Display validation errors
1068
+ $errors = get_transient('tmm_validation_errors');
1069
+ if ($errors):
1070
+ delete_transient('tmm_validation_errors');
1071
+ ?>
1072
+ <div class="notice notice-error is-dismissible">
1073
+ <p><strong><?php _e('Validation errors:', 'task-manager-mvc'); ?></strong></p>
1074
+ <ul>
1075
+ <?php foreach ($errors as $field => $field_errors): ?>
1076
+ <?php foreach ($field_errors as $error): ?>
1077
+ <li><?php echo esc_html($error); ?></li>
1078
+ <?php endforeach; ?>
1079
+ <?php endforeach; ?>
1080
+ </ul>
1081
+ </div>
1082
+ <?php endif; ?>
1083
+
1084
+ <div class="tmm-content">
1085
+ <?php echo $content; ?>
1086
+ </div>
1087
+ </div>
1088
+ ```
1089
+
1090
+ ### File: `app/views/tasks/index.php`
1091
+
1092
+ ```php
1093
+ <div class="tmm-tasks-index">
1094
+ <div class="tmm-header">
1095
+ <a href="<?php echo admin_url('admin.php?page=tmm-tasks&action=tasks/create'); ?>" class="button button-primary">
1096
+ <?php _e('Add New Task', 'task-manager-mvc'); ?>
1097
+ </a>
1098
+
1099
+ <div class="tmm-filters">
1100
+ <a href="<?php echo admin_url('admin.php?page=tmm-tasks&status=all'); ?>"
1101
+ class="<?php echo $current_status === 'all' ? 'current' : ''; ?>">
1102
+ <?php _e('All', 'task-manager-mvc'); ?>
1103
+ </a>
1104
+ <a href="<?php echo admin_url('admin.php?page=tmm-tasks&status=pending'); ?>"
1105
+ class="<?php echo $current_status === 'pending' ? 'current' : ''; ?>">
1106
+ <?php _e('Pending', 'task-manager-mvc'); ?>
1107
+ </a>
1108
+ <a href="<?php echo admin_url('admin.php?page=tmm-tasks&status=completed'); ?>"
1109
+ class="<?php echo $current_status === 'completed' ? 'current' : ''; ?>">
1110
+ <?php _e('Completed', 'task-manager-mvc'); ?>
1111
+ </a>
1112
+ </div>
1113
+ </div>
1114
+
1115
+ <table class="wp-list-table widefat fixed striped">
1116
+ <thead>
1117
+ <tr>
1118
+ <th><?php _e('Title', 'task-manager-mvc'); ?></th>
1119
+ <th><?php _e('Category', 'task-manager-mvc'); ?></th>
1120
+ <th><?php _e('Due Date', 'task-manager-mvc'); ?></th>
1121
+ <th><?php _e('Status', 'task-manager-mvc'); ?></th>
1122
+ <th><?php _e('Actions', 'task-manager-mvc'); ?></th>
1123
+ </tr>
1124
+ </thead>
1125
+ <tbody>
1126
+ <?php if (empty($tasks)): ?>
1127
+ <tr>
1128
+ <td colspan="5"><?php _e('No tasks found', 'task-manager-mvc'); ?></td>
1129
+ </tr>
1130
+ <?php else: ?>
1131
+ <?php foreach ($tasks as $task): ?>
1132
+ <tr class="<?php echo $task->is_overdue() ? 'tmm-overdue' : ''; ?>">
1133
+ <td>
1134
+ <strong><?php echo esc_html($task->title); ?></strong>
1135
+ <?php if ($task->is_overdue()): ?>
1136
+ <span class="tmm-badge tmm-badge-danger"><?php _e('Overdue', 'task-manager-mvc'); ?></span>
1137
+ <?php endif; ?>
1138
+ </td>
1139
+ <td>
1140
+ <?php
1141
+ $category = $task->category();
1142
+ echo $category ? esc_html($category->name) : '—';
1143
+ ?>
1144
+ </td>
1145
+ <td><?php echo $task->due_date ? esc_html(date('M j, Y', strtotime($task->due_date))) : '—'; ?></td>
1146
+ <td>
1147
+ <span class="tmm-status tmm-status-<?php echo esc_attr($task->status); ?>">
1148
+ <?php echo esc_html(ucfirst($task->status)); ?>
1149
+ </span>
1150
+ </td>
1151
+ <td>
1152
+ <a href="<?php echo admin_url('admin.php?page=tmm-tasks&action=tasks/edit&id=' . $task->id); ?>"
1153
+ class="button button-small">
1154
+ <?php _e('Edit', 'task-manager-mvc'); ?>
1155
+ </a>
1156
+
1157
+ <form method="post" action="<?php echo admin_url('admin.php?page=tmm-tasks&action=tasks/delete'); ?>"
1158
+ style="display:inline;"
1159
+ onsubmit="return confirm('<?php _e('Are you sure?', 'task-manager-mvc'); ?>');">
1160
+ <?php wp_nonce_field('tmm_delete_task'); ?>
1161
+ <input type="hidden" name="task_id" value="<?php echo esc_attr($task->id); ?>">
1162
+ <button type="submit" class="button button-small button-link-delete">
1163
+ <?php _e('Delete', 'task-manager-mvc'); ?>
1164
+ </button>
1165
+ </form>
1166
+ </td>
1167
+ </tr>
1168
+ <?php endforeach; ?>
1169
+ <?php endif; ?>
1170
+ </tbody>
1171
+ </table>
1172
+ </div>
1173
+ ```
1174
+
1175
+ ### File: `app/views/tasks/create.php`
1176
+
1177
+ ```php
1178
+ <div class="tmm-task-form">
1179
+ <form method="post" action="<?php echo admin_url('admin.php?page=tmm-tasks&action=tasks/store'); ?>">
1180
+ <?php wp_nonce_field('tmm_create_task'); ?>
1181
+
1182
+ <table class="form-table">
1183
+ <tr>
1184
+ <th scope="row">
1185
+ <label for="title"><?php _e('Title', 'task-manager-mvc'); ?> <span class="required">*</span></label>
1186
+ </th>
1187
+ <td>
1188
+ <input type="text"
1189
+ id="title"
1190
+ name="title"
1191
+ class="regular-text"
1192
+ required>
1193
+ </td>
1194
+ </tr>
1195
+
1196
+ <tr>
1197
+ <th scope="row">
1198
+ <label for="description"><?php _e('Description', 'task-manager-mvc'); ?></label>
1199
+ </th>
1200
+ <td>
1201
+ <textarea id="description"
1202
+ name="description"
1203
+ rows="5"
1204
+ class="large-text"></textarea>
1205
+ </td>
1206
+ </tr>
1207
+
1208
+ <tr>
1209
+ <th scope="row">
1210
+ <label for="category_id"><?php _e('Category', 'task-manager-mvc'); ?></label>
1211
+ </th>
1212
+ <td>
1213
+ <select id="category_id" name="category_id">
1214
+ <option value="0"><?php _e('None', 'task-manager-mvc'); ?></option>
1215
+ <?php foreach ($categories as $category): ?>
1216
+ <option value="<?php echo esc_attr($category->id); ?>">
1217
+ <?php echo esc_html($category->name); ?>
1218
+ </option>
1219
+ <?php endforeach; ?>
1220
+ </select>
1221
+ </td>
1222
+ </tr>
1223
+
1224
+ <tr>
1225
+ <th scope="row">
1226
+ <label for="due_date"><?php _e('Due Date', 'task-manager-mvc'); ?></label>
1227
+ </th>
1228
+ <td>
1229
+ <input type="date"
1230
+ id="due_date"
1231
+ name="due_date">
1232
+ </td>
1233
+ </tr>
1234
+ </table>
1235
+
1236
+ <p class="submit">
1237
+ <input type="submit"
1238
+ class="button button-primary"
1239
+ value="<?php _e('Create Task', 'task-manager-mvc'); ?>">
1240
+ <a href="<?php echo admin_url('admin.php?page=tmm-tasks'); ?>"
1241
+ class="button">
1242
+ <?php _e('Cancel', 'task-manager-mvc'); ?>
1243
+ </a>
1244
+ </p>
1245
+ </form>
1246
+ </div>
1247
+ ```
1248
+
1249
+ ---
1250
+
1251
+ ## MVC Principles Demonstrated
1252
+
1253
+ ### 1. **Model** - Data and Business Logic
1254
+
1255
+ ✅ **Encapsulates data access**: All database operations in model classes
1256
+ ✅ **Business logic**: Methods like `is_overdue()`, `mark_complete()`
1257
+ ✅ **Relationships**: `task->category()`, `category->tasks()`
1258
+ ✅ **Reusable**: Models used by multiple controllers
1259
+
1260
+ ### 2. **View** - Presentation Layer
1261
+
1262
+ ✅ **No business logic**: Views only display data
1263
+ ✅ **Reusable layouts**: Admin layout wraps all views
1264
+ ✅ **Partials**: Reusable view components
1265
+ ✅ **Proper escaping**: All output escaped for security
1266
+
1267
+ ### 3. **Controller** - Request Handling
1268
+
1269
+ ✅ **Handles requests**: Processes form submissions
1270
+ ✅ **Coordinates**: Calls models, passes data to views
1271
+ ✅ **Validation**: Validates input before processing
1272
+ ✅ **Security**: Nonce verification, capability checks
1273
+
1274
+ ### 4. **Router** - URL Mapping
1275
+
1276
+ ✅ **Clean URLs**: Maps actions to controller methods
1277
+ ✅ **RESTful**: GET for display, POST for modifications
1278
+ ✅ **Centralized**: All routes in one place
1279
+
1280
+ ---
1281
+
1282
+ ## Advantages of MVC Pattern
1283
+
1284
+ ### ✅ Separation of Concerns
1285
+
1286
+ - **Models**: Handle data and business logic
1287
+ - **Views**: Handle presentation
1288
+ - **Controllers**: Handle request/response flow
1289
+ - **Easy to modify**: Change one layer without affecting others
1290
+
1291
+ ### ✅ Testability
1292
+
1293
+ - **Unit test models**: Test business logic independently
1294
+ - **Test controllers**: Mock models and test request handling
1295
+ - **Test views**: Verify output without database
1296
+
1297
+ ### ✅ Reusability
1298
+
1299
+ - **Models**: Reused across admin and public
1300
+ - **Views**: Partials and layouts reduce duplication
1301
+ - **Controllers**: Base controller provides common functionality
1302
+
1303
+ ### ✅ Maintainability
1304
+
1305
+ - **Clear structure**: Easy to find code
1306
+ - **Predictable**: Consistent patterns throughout
1307
+ - **Scalable**: Easy to add new features
1308
+
1309
+ ### ✅ Team Collaboration
1310
+
1311
+ - **Parallel development**: Frontend and backend developers work independently
1312
+ - **Clear responsibilities**: Each layer has specific role
1313
+ - **Code review**: Smaller, focused files
1314
+
1315
+ ---
1316
+
1317
+ ## When to Use MVC Pattern
1318
+
1319
+ ### ✅ Good Use Cases
1320
+
1321
+ - **Complex business logic**: Multiple models with relationships
1322
+ - **Multiple views**: Different ways to display same data
1323
+ - **Team development**: Frontend and backend developers
1324
+ - **Testability required**: Unit and integration testing
1325
+ - **Long-term maintenance**: Plugin will evolve over time
1326
+ - **API development**: Models can serve both UI and API
1327
+
1328
+ ### ❌ Not Suitable For
1329
+
1330
+ - **Simple plugins**: Single feature, minimal logic (use Pattern 1)
1331
+ - **Quick prototypes**: Temporary or experimental code
1332
+ - **Beginner projects**: Learning WordPress basics
1333
+ - **Micro-plugins**: Very specific, focused functionality
1334
+
1335
+ ---
1336
+
1337
+ ## Best Practices
1338
+
1339
+ ### 1. **Model Best Practices**
1340
+
1341
+ ```php
1342
+ // Good - Business logic in model
1343
+ class Task extends BaseModel {
1344
+ public function is_overdue() {
1345
+ return strtotime($this->due_date) < time();
1346
+ }
1347
+ }
1348
+
1349
+ // Bad - Business logic in controller
1350
+ if (strtotime($task->due_date) < time()) {
1351
+ // ...
1352
+ }
1353
+ ```
1354
+
1355
+ ### 2. **View Best Practices**
1356
+
1357
+ ```php
1358
+ // Good - No logic in view
1359
+ <?php echo esc_html($task->title); ?>
1360
+
1361
+ // Bad - Business logic in view
1362
+ <?php
1363
+ $category = Category::find($task->category_id);
1364
+ echo $category->name;
1365
+ ?>
1366
+ ```
1367
+
1368
+ ### 3. **Controller Best Practices**
1369
+
1370
+ ```php
1371
+ // Good - Thin controller
1372
+ public function store() {
1373
+ $this->validate_request();
1374
+ $task = $this->create_task_from_request();
1375
+ $task->save();
1376
+ $this->redirect_with_message('Success');
1377
+ }
1378
+
1379
+ // Bad - Fat controller with business logic
1380
+ public function store() {
1381
+ // 100 lines of business logic
1382
+ }
1383
+ ```
1384
+
1385
+ ### 4. **Router Best Practices**
1386
+
1387
+ ```php
1388
+ // Good - RESTful routes
1389
+ $this->add_route('GET', 'tasks', 'TaskController@index');
1390
+ $this->add_route('POST', 'tasks/store', 'TaskController@store');
1391
+
1392
+ // Bad - Inconsistent routes
1393
+ $this->add_route('GET', 'show_tasks', 'TaskController@show');
1394
+ $this->add_route('GET', 'save_task', 'TaskController@save');
1395
+ ```
1396
+
1397
+ ### 5. **Security Best Practices**
1398
+
1399
+ ```php
1400
+ // Always in controllers
1401
+ public function store() {
1402
+ // 1. Verify nonce
1403
+ if (!$this->verify_nonce('action_name')) {
1404
+ wp_die('Security check failed');
1405
+ }
1406
+
1407
+ // 2. Check capability
1408
+ if (!$this->can('manage_options')) {
1409
+ wp_die('Permission denied');
1410
+ }
1411
+
1412
+ // 3. Validate input
1413
+ $this->validator->validate($_POST, $rules);
1414
+
1415
+ // 4. Sanitize input
1416
+ $value = sanitize_text_field($_POST['value']);
1417
+ }
1418
+ ```
1419
+
1420
+ ---
1421
+
1422
+ ## Installation Example
1423
+
1424
+ ### File: `install.php`
1425
+
1426
+ ```php
1427
+ <?php
1428
+ class TMM_Install {
1429
+ public static function activate() {
1430
+ global $wpdb;
1431
+
1432
+ $charset_collate = $wpdb->get_charset_collate();
1433
+
1434
+ // Tasks table
1435
+ $tasks_table = $wpdb->prefix . 'tmm_tasks';
1436
+ $sql_tasks = "CREATE TABLE $tasks_table (
1437
+ id bigint(20) NOT NULL AUTO_INCREMENT,
1438
+ title varchar(200) NOT NULL,
1439
+ description text,
1440
+ category_id bigint(20) DEFAULT 0,
1441
+ due_date date,
1442
+ status varchar(20) DEFAULT 'pending',
1443
+ created_at datetime DEFAULT CURRENT_TIMESTAMP,
1444
+ updated_at datetime,
1445
+ completed_at datetime,
1446
+ PRIMARY KEY (id),
1447
+ KEY category_id (category_id),
1448
+ KEY status (status)
1449
+ ) $charset_collate;";
1450
+
1451
+ // Categories table
1452
+ $categories_table = $wpdb->prefix . 'tmm_categories';
1453
+ $sql_categories = "CREATE TABLE $categories_table (
1454
+ id bigint(20) NOT NULL AUTO_INCREMENT,
1455
+ name varchar(100) NOT NULL,
1456
+ slug varchar(100) NOT NULL,
1457
+ description text,
1458
+ created_at datetime DEFAULT CURRENT_TIMESTAMP,
1459
+ PRIMARY KEY (id),
1460
+ UNIQUE KEY slug (slug)
1461
+ ) $charset_collate;";
1462
+
1463
+ require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
1464
+ dbDelta($sql_tasks);
1465
+ dbDelta($sql_categories);
1466
+
1467
+ add_option('tmm_db_version', '1.0.0');
1468
+ }
1469
+
1470
+ public static function deactivate() {
1471
+ // Cleanup if needed
1472
+ }
1473
+ }
1474
+ ```
1475
+
1476
+ ---
1477
+
1478
+ ## Comparison with Other Patterns
1479
+
1480
+ | Feature | Simple Procedural | OOP | MVC | WPPB |
1481
+ |---------|------------------|-----|-----|------|
1482
+ | **Complexity** | Low | Medium-High | Medium-High | High |
1483
+ | **File Count** | 1-3 | 10-30 | 15-40 | 20-100 |
1484
+ | **Learning Curve** | Easy | Moderate | Moderate-Steep | Steep |
1485
+ | **Separation of Concerns** | None | Good | Excellent | Excellent |
1486
+ | **Testability** | Low | High | Very High | Very High |
1487
+ | **Business Logic** | Mixed | In classes | In models | In models |
1488
+ | **View Reusability** | None | Low | High | High |
1489
+ | **Team Size** | 1 | 2-5 | 2-5 | 3-10 |
1490
+ | **Best For** | Simple plugins | Complex plugins | Complex business logic | Professional plugins |
1491
+
1492
+ ---
1493
+
1494
+ ## Migration Path
1495
+
1496
+ ### From OOP to MVC
1497
+
1498
+ 1. **Extract models** from existing classes
1499
+ 2. **Create controllers** for request handling
1500
+ 3. **Move views** to separate files
1501
+ 4. **Add router** for URL mapping
1502
+ 5. **Refactor** to follow MVC principles
1503
+
1504
+ ### Example Migration
1505
+
1506
+ **Before (OOP):**
1507
+ ```php
1508
+ class Task_Manager {
1509
+ public function display_tasks() {
1510
+ $tasks = $this->get_tasks();
1511
+ include 'views/tasks.php';
1512
+ }
1513
+
1514
+ private function get_tasks() {
1515
+ global $wpdb;
1516
+ return $wpdb->get_results("SELECT * FROM tasks");
1517
+ }
1518
+ }
1519
+ ```
1520
+
1521
+ **After (MVC):**
1522
+ ```php
1523
+ // Model
1524
+ class Task extends BaseModel {
1525
+ protected $table = 'tasks';
1526
+ }
1527
+
1528
+ // Controller
1529
+ class TaskController extends BaseController {
1530
+ public function index() {
1531
+ $tasks = Task::all();
1532
+ $this->view('tasks/index', compact('tasks'));
1533
+ }
1534
+ }
1535
+
1536
+ // View (tasks/index.php)
1537
+ <?php foreach ($tasks as $task): ?>
1538
+ <div><?php echo esc_html($task->title); ?></div>
1539
+ <?php endforeach; ?>
1540
+ ```
1541
+
1542
+ ---
1543
+
1544
+ ## Testing Example
1545
+
1546
+ ### Unit Test for Task Model
1547
+
1548
+ ```php
1549
+ <?php
1550
+ class Task_Model_Test extends WP_UnitTestCase {
1551
+ public function test_task_creation() {
1552
+ $task = new TMM\Models\Task();
1553
+ $task->title = 'Test Task';
1554
+ $task->status = 'pending';
1555
+ $task->save();
1556
+
1557
+ $this->assertGreaterThan(0, $task->id);
1558
+ $this->assertEquals('Test Task', $task->title);
1559
+ }
1560
+
1561
+ public function test_is_overdue() {
1562
+ $task = new TMM\Models\Task();
1563
+ $task->due_date = date('Y-m-d', strtotime('-1 day'));
1564
+ $task->status = 'pending';
1565
+
1566
+ $this->assertTrue($task->is_overdue());
1567
+ }
1568
+
1569
+ public function test_category_relationship() {
1570
+ $category = new TMM\Models\Category();
1571
+ $category->name = 'Work';
1572
+ $category->save();
1573
+
1574
+ $task = new TMM\Models\Task();
1575
+ $task->category_id = $category->id;
1576
+ $task->save();
1577
+
1578
+ $this->assertEquals('Work', $task->category()->name);
1579
+ }
1580
+ }
1581
+ ```
1582
+
1583
+ ---
1584
+
1585
+ ## Related Patterns
1586
+
1587
+ - **Pattern 1**: [Simple Procedural Plugin](simple-procedural-plugin.md) - For simple plugins
1588
+ - **Pattern 3**: [Object-Oriented Plugin](object-oriented-plugin.md) - OOP without MVC
1589
+ - **Pattern 4**: [WPPB Plugin](wppb-plugin.md) - Professional standard
1590
+ - **Pattern 6**: [Service Container Plugin](service-container-plugin.md) - Dependency injection
1591
+
1592
+ ---
1593
+
1594
+ ## Additional Resources
1595
+
1596
+ - [MVC Pattern Explained](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller)
1597
+ - [WordPress MVC Frameworks](https://wordpress.org/plugins/tags/mvc/)
1598
+ - [Testing WordPress Plugins](https://make.wordpress.org/core/handbook/testing/automated-testing/phpunit/)
1599
+ - [RESTful Routing](https://restfulapi.net/)
1600
+
1601
+ ---
1602
+
1603
+ ## Summary
1604
+
1605
+ This MVC plugin example demonstrates:
1606
+
1607
+ ✅ **Complete MVC architecture** with models, views, controllers
1608
+ ✅ **Router** for clean URL mapping
1609
+ ✅ **Base classes** for code reusability
1610
+ ✅ **Model layer** with business logic and relationships
1611
+ ✅ **View layer** with layouts and partials
1612
+ ✅ **Controller layer** with request handling and validation
1613
+ ✅ **Security best practices** (nonces, capability checks, sanitization)
1614
+ ✅ **Validation** with custom validator class
1615
+ ✅ **Flash messages** for user feedback
1616
+ ✅ **Database abstraction** in base model
1617
+ ✅ **RESTful routing** for clean URLs
1618
+ ✅ **Testable code** structure
1619
+
1620
+ **Perfect for**: Complex business logic, multiple views, team development, testable code, long-term maintenance.
1621
+
1622
+ **Next steps**: For even more structure with dependency injection, consider Pattern 6 (Service Container) or modern frameworks like Laravel-style WordPress development.
1623
+