7 evolutionary tasks implemented: 1. PHP web development: php-developer agent + 6 skills (Laravel, Symfony, WordPress, security, testing, modular architecture) + 2 pipeline commands (/laravel, /wordpress) 2. Atomic task decomposition: 1 action = 1 task rule, task sizing guide, decomposition protocol for orchestrator, token budgets per complexity 3. Modular code rules: max 100 lines/file, max 30 lines/function, service/repository patterns, cross-module communication via events only 4. Gitea-centric workflow: mandatory issue creation before work, research with links, progress checkboxes, screenshots on test, git history as knowledge base 5. Fix: target project auto-detection — removed all hardcoded UniqueSoft/APAW from API calls, added get_target_repo() via git remote, GITEA_TARGET_REPO env override 6. Agent execution monitoring: agent-executions.jsonl logging, agent-stats.ts statistics script, required fields per invocation, Gitea comment includes duration/tokens 7. Token optimization: 1 action = 1 task principle, token budgets by task type, routing matrix, no scope creep, skip unnecessary pipeline steps
7.7 KiB
7.7 KiB
name, description
| name | description |
|---|---|
| php-wordpress-patterns | WordPress development patterns - plugins, themes, REST API, hooks, Custom Post Types, Gutenberg blocks |
PHP WordPress Patterns
Plugin Structure
my-plugin/
├── my-plugin.php # Main plugin file
├── composer.json # Dependencies
├── package.json # Build tools
├── includes/
│ ├── Admin/ # Admin settings pages
│ ├── Frontend/ # Frontend rendering
│ ├── REST/ # REST API controllers
│ ├── PostTypes/ # Custom Post Types
│ ├── Taxonomies/ # Custom Taxonomies
│ ├── Shortcodes/ # Shortcode handlers
│ ├── Widgets/ # Widget classes
│ └── Utils/ # Helper functions
├── src/
│ ├── blocks/ # Gutenberg blocks (JSX)
│ └── store/ # Block data stores
├── assets/
│ ├── css/
│ ├── js/
│ └── images/
├── templates/ # Template files
├── languages/ # i18n
└── tests/
├── Unit/
└── Integration/
Main Plugin File
<?php
/**
* Plugin Name: My Plugin
* Description: Custom functionality
* Version: 1.0.0
* Author: Developer
* Text Domain: my-plugin
* Requires at least: 6.0
* Requires PHP: 8.1
*/
declare(strict_types=1);
namespace MyPlugin;
if (!defined('ABSPATH')) exit;
require_once __DIR__ . '/vendor/autoload.php';
final class Plugin
{
private static ?self $instance = null;
public static function init(): self
{
return self::$instance ??= new self();
}
private function __construct()
{
add_action('init', [$this, 'registerPostTypes']);
add_action('rest_api_init', [$this, 'registerRestRoutes']);
add_action('admin_menu', [$this, 'addAdminMenu']);
add_action('wp_enqueue_scripts', [$this, 'enqueueAssets']);
register_activation_hook(__FILE__, [$this, 'activate']);
register_deactivation_hook(__FILE__, [$this, 'deactivate']);
}
public function activate(): void
{
$db = new Database\Seeder();
$db->createTables();
flush_rewrite_rules();
}
public function deactivate(): void
{
flush_rewrite_rules();
}
}
Plugin::init();
Custom Post Type
// includes/PostTypes/Product.php
namespace MyPlugin\PostTypes;
class Product
{
public static function register(): void
{
register_post_type('product', [
'labels' => [
'name' => __('Products', 'my-plugin'),
'singular_name' => __('Product', 'my-plugin'),
],
'public' => true,
'has_archive' => true,
'rewrite' => ['slug' => 'products'],
'supports' => ['title', 'editor', 'thumbnail', 'excerpt', 'custom-fields'],
'show_in_rest' => true, // Enable REST API
'menu_icon' => 'dashicons-cart',
]);
// Custom fields via meta
register_post_meta('product', 'price', [
'show_in_rest' => true,
'single' => true,
'type' => 'number',
'sanitize_callback' => 'absint',
]);
}
}
add_action('init', [Product::class, 'register']);
REST API Controller
// includes/REST/ProductController.php
namespace MyPlugin\REST;
use WP_REST_Controller;
use WP_REST_Request;
use WP_REST_Response;
use WP_Error;
class ProductController extends WP_REST_Controller
{
public function __construct()
{
$this->namespace = 'my-plugin/v1';
$this->rest_base = 'products';
}
public function registerRoutes(): void
{
register_rest_route($this->namespace, '/' . $this->rest_base, [
[
'methods' => 'GET',
'callback' => [$this, 'getItems'],
'permission_callback' => '__return_true',
'args' => [
'page' => ['default' => 1, 'sanitize_callback' => 'absint'],
'per_page' => ['default' => 20, 'sanitize_callback' => 'absint'],
'category' => ['sanitize_callback' => 'absint'],
],
],
[
'methods' => 'POST',
'callback' => [$this, 'createItem'],
'permission_callback' => function() {
return current_user_can('edit_posts');
},
],
]);
register_rest_route($this->namespace, '/' . $this->rest_base . '/(?P<id>\d+)', [
[
'methods' => 'GET',
'callback' => [$this, 'getItem'],
'permission_callback' => '__return_true',
],
[
'methods' => 'PUT',
'callback' => [$this, 'updateItem'],
'permission_callback' => function() {
return current_user_can('edit_posts');
},
],
[
'methods' => 'DELETE',
'callback' => [$this, 'deleteItem'],
'permission_callback' => function() {
return current_user_can('delete_posts');
},
],
]);
}
public function getItems(WP_REST_Request $request): WP_REST_Response
{
$args = [
'post_type' => 'product',
'posts_per_page' => $request['per_page'],
'paged' => $request['page'],
'post_status' => 'publish',
];
if ($request['category']) {
$args['tax_query'] = [[
'taxonomy' => 'product_category',
'field' => 'term_id',
'terms' => $request['category'],
]];
}
$query = new \WP_Query($args);
$products = array_map(fn($p) => $this->prepareItem($p), $query->posts);
return new WP_REST_Response([
'data' => $products,
'total' => (int) $query->found_posts,
'pages' => (int) $query->max_num_pages,
], 200);
}
private function prepareItem(\WP_Post $post): array
{
return [
'id' => $post->ID,
'title' => $post->post_title,
'content' => $post->post_content,
'price' => get_post_meta($post->ID, 'price', true),
'thumbnail' => get_the_post_thumbnail_url($post->ID, 'medium'),
'date' => $post->post_date,
];
}
}
Security Essentials
// Nonce verification for forms
if (!wp_verify_nonce($_POST['_wpnonce'], 'my_plugin_action')) {
wp_die('Security check failed');
}
// Data sanitization
$title = sanitize_text_field($_POST['title']);
$content = wp_kses_post($_POST['content']);
$price = floatval($_POST['price']);
$id = absint($_POST['id']);
// SQL queries - always use $wpdb->prepare()
$results = $wpdb->get_results($wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}products WHERE category_id = %d AND status = %s",
$category_id,
'active'
));
// Escape output
echo esc_html($title);
echo esc_url($url);
echo esc_attr($attr);
Checklist
- Namespace all PHP code (no global functions)
- Use
declare(strict_types=1)in all files - Nonce verification on all forms and AJAX
- Sanitize input, escape output, prepare SQL
- Custom Post Types with
show_in_rest - REST API controllers extend
WP_REST_Controller - Capability checks (
current_user_can) - Use
wp_enqueue_script/wp_enqueue_style - Internationalization (
__()and_e()) - Activation/deactivation hooks
- Options API for settings (never custom tables for settings)