| 1 |
<?php |
| 2 |
|
| 3 |
/* |
| 4 |
* This file is part of the WindPress package. |
| 5 |
* |
| 6 |
* (c) Joshua Gugun Siagian <suabahasa@gmail.com> |
| 7 |
* |
| 8 |
* For the full copyright and license information, please view the LICENSE |
| 9 |
* file that was distributed with this source code. |
| 10 |
*/ |
| 11 |
declare (strict_types=1); |
| 12 |
namespace WindPress\WindPress\Abilities; |
| 13 |
|
| 14 |
use Exception; |
| 15 |
/** |
| 16 |
* Abilities API Loader |
| 17 |
* |
| 18 |
* Registers WindPress abilities with the WordPress Abilities API. |
| 19 |
* Abilities provide a standardized way to expose plugin functionality |
| 20 |
* to AI agents, automation tools, and other developers. |
| 21 |
* |
| 22 |
* @since 3.2.0 |
| 23 |
*/ |
| 24 |
class Loader |
| 25 |
{ |
| 26 |
/** |
| 27 |
* Stores the instance, implementing a Singleton pattern. |
| 28 |
*/ |
| 29 |
private static self $instance; |
| 30 |
/** |
| 31 |
* The Singleton's constructor should always be private to prevent direct |
| 32 |
* construction calls with the `new` operator. |
| 33 |
*/ |
| 34 |
private function __construct() |
| 35 |
{ |
| 36 |
} |
| 37 |
/** |
| 38 |
* Singletons should not be cloneable. |
| 39 |
*/ |
| 40 |
private function __clone() |
| 41 |
{ |
| 42 |
} |
| 43 |
/** |
| 44 |
* Singletons should not be restorable from strings. |
| 45 |
* |
| 46 |
* @throws Exception Cannot unserialize a singleton. |
| 47 |
*/ |
| 48 |
public function __wakeup() |
| 49 |
{ |
| 50 |
throw new Exception('Cannot unserialize a singleton.'); |
| 51 |
} |
| 52 |
/** |
| 53 |
* This is the static method that controls the access to the singleton |
| 54 |
* instance. On the first run, it creates a singleton object and places it |
| 55 |
* into the static property. On subsequent runs, it returns the client existing |
| 56 |
* object stored in the static property. |
| 57 |
*/ |
| 58 |
public static function get_instance(): self |
| 59 |
{ |
| 60 |
if (!isset(self::$instance)) { |
| 61 |
self::$instance = new self(); |
| 62 |
} |
| 63 |
return self::$instance; |
| 64 |
} |
| 65 |
/** |
| 66 |
* Initialize the Abilities API integration |
| 67 |
* |
| 68 |
* This method checks if the Abilities API is available (WordPress 6.9+ or via composer package) |
| 69 |
* and registers the appropriate hooks for categories and abilities. |
| 70 |
*/ |
| 71 |
public function init(): void |
| 72 |
{ |
| 73 |
// Check if Abilities API is available |
| 74 |
if (!function_exists('wp_register_ability_category') || !function_exists('wp_register_ability')) { |
| 75 |
return; |
| 76 |
} |
| 77 |
do_action('a!windpress/abilities/loader:init.start'); |
| 78 |
// Register ability categories on the categories init hook |
| 79 |
add_action('wp_abilities_api_categories_init', fn() => $this->register_categories()); |
| 80 |
// Register abilities on the abilities init hook |
| 81 |
add_action('wp_abilities_api_init', fn() => $this->register_abilities()); |
| 82 |
do_action('a!windpress/abilities/loader:init.end'); |
| 83 |
} |
| 84 |
/** |
| 85 |
* Register ability categories for WindPress |
| 86 |
*/ |
| 87 |
public function register_categories(): void |
| 88 |
{ |
| 89 |
do_action('a!windpress/abilities/loader:register_categories.start'); |
| 90 |
// Configuration category |
| 91 |
wp_register_ability_category('configuration', ['label' => __('Configuration', 'windpress'), 'description' => __('Abilities for retrieving and managing WindPress configuration.', 'windpress')]); |
| 92 |
// Volume Management category (Simple File System) |
| 93 |
wp_register_ability_category('volume-management', ['label' => __('Volume Management', 'windpress'), 'description' => __('Abilities for managing CSS/JS files in the WindPress Simple File System.', 'windpress')]); |
| 94 |
/** |
| 95 |
* Allow other plugins/themes to register additional ability categories. |
| 96 |
* |
| 97 |
* @since 3.2.0 |
| 98 |
*/ |
| 99 |
do_action('a!windpress/abilities/loader:register_categories.end'); |
| 100 |
} |
| 101 |
/** |
| 102 |
* Register all WindPress abilities |
| 103 |
*/ |
| 104 |
public function register_abilities(): void |
| 105 |
{ |
| 106 |
do_action('a!windpress/abilities/loader:register_abilities.start'); |
| 107 |
// Configuration |
| 108 |
$this->register_get_config(); |
| 109 |
// Volume - Read operations |
| 110 |
$this->register_get_volume_entries(); |
| 111 |
$this->register_get_volume_entry(); |
| 112 |
$this->register_get_volume_handlers(); |
| 113 |
// Volume - Write operations |
| 114 |
$this->register_save_volume_entries(); |
| 115 |
$this->register_save_volume_entry(); |
| 116 |
// Volume - Delete operations |
| 117 |
$this->register_delete_volume_entry(); |
| 118 |
// Volume - Utility operations |
| 119 |
$this->register_reset_volume_entry(); |
| 120 |
/** |
| 121 |
* Allow other plugins/themes to register additional abilities. |
| 122 |
* |
| 123 |
* @since 3.2.0 |
| 124 |
*/ |
| 125 |
do_action('a!windpress/abilities/loader:register_abilities.end'); |
| 126 |
} |
| 127 |
/** |
| 128 |
* Register the get-config ability |
| 129 |
*/ |
| 130 |
private function register_get_config(): void |
| 131 |
{ |
| 132 |
wp_register_ability('windpress/get-config', ['label' => __('Get Configuration', 'windpress'), 'description' => __('Retrieves the current WindPress configuration including Tailwind version, plugin settings, and enabled features.', 'windpress'), 'category' => 'configuration', 'input_schema' => [], 'output_schema' => ['type' => 'object', 'properties' => ['version' => ['type' => 'string', 'description' => __('WindPress plugin version.', 'windpress')], 'tailwind_version' => ['type' => 'integer', 'description' => __('Tailwind CSS version (3 or 4).', 'windpress')], 'settings' => ['type' => 'object', 'description' => __('Plugin settings and configuration options.', 'windpress')], 'data_dir' => ['type' => 'object', 'description' => __('WindPress data directory details.', 'windpress'), 'properties' => ['path' => ['type' => 'string', 'description' => __('Absolute path to the WindPress data directory.', 'windpress')], 'url' => ['type' => 'string', 'description' => __('URL to the WindPress data directory.', 'windpress')]]], 'cache_dir' => ['type' => 'object', 'description' => __('WindPress cache directory details.', 'windpress'), 'properties' => ['path' => ['type' => 'string', 'description' => __('Absolute path to the WindPress cache directory.', 'windpress')], 'url' => ['type' => 'string', 'description' => __('URL to the WindPress cache directory.', 'windpress')]]]]], 'execute_callback' => [\WindPress\WindPress\Abilities\Abilities\GetConfig::class, 'execute'], 'permission_callback' => fn() => current_user_can('manage_options'), 'meta' => ['annotations' => ['readonly' => \true, 'destructive' => \false, 'idempotent' => \true], 'show_in_rest' => \true]]); |
| 133 |
} |
| 134 |
/** |
| 135 |
* Register the get-volume-entries ability |
| 136 |
*/ |
| 137 |
private function register_get_volume_entries(): void |
| 138 |
{ |
| 139 |
wp_register_ability('windpress/get-volume-entries', ['label' => __('Get Volume Entries', 'windpress'), 'description' => __('Retrieves all files in the WindPress Simple File System including main.css, config files, and custom CSS/JS files.', 'windpress'), 'category' => 'volume-management', 'input_schema' => [], 'output_schema' => ['type' => 'array', 'items' => ['type' => 'object', 'properties' => ['name' => ['type' => 'string', 'description' => __('File name.', 'windpress')], 'relative_path' => ['type' => 'string', 'description' => __('Relative path from data directory.', 'windpress')], 'content' => ['type' => 'string', 'description' => __('File content.', 'windpress')], 'handler' => ['type' => 'string', 'description' => __('Handler type (internal or custom).', 'windpress')], 'signature' => ['type' => 'string', 'description' => __('Security signature for file operations.', 'windpress')], 'readonly' => ['type' => 'boolean', 'description' => __('Whether the file is read-only.', 'windpress')]]]], 'execute_callback' => [\WindPress\WindPress\Abilities\Abilities\GetVolumeEntries::class, 'execute'], 'permission_callback' => fn() => current_user_can('manage_options'), 'meta' => ['annotations' => ['readonly' => \true, 'destructive' => \false, 'idempotent' => \true], 'show_in_rest' => \true]]); |
| 140 |
} |
| 141 |
/** |
| 142 |
* Register the get-volume-entry ability |
| 143 |
*/ |
| 144 |
private function register_get_volume_entry(): void |
| 145 |
{ |
| 146 |
wp_register_ability('windpress/get-volume-entry', ['label' => __('Get Volume Entry', 'windpress'), 'description' => __('Retrieves a single file from the WindPress Simple File System by its relative path.', 'windpress'), 'category' => 'volume-management', 'input_schema' => ['type' => 'object', 'properties' => ['relative_path' => ['type' => 'string', 'description' => __('Relative path of the file to retrieve.', 'windpress')]], 'required' => ['relative_path']], 'output_schema' => ['type' => 'object', 'properties' => ['name' => ['type' => 'string', 'description' => __('File name.', 'windpress')], 'relative_path' => ['type' => 'string', 'description' => __('Relative path from data directory.', 'windpress')], 'content' => ['type' => 'string', 'description' => __('File content.', 'windpress')], 'handler' => ['type' => 'string', 'description' => __('Handler type (internal or custom).', 'windpress')], 'signature' => ['type' => 'string', 'description' => __('Security signature for file operations.', 'windpress')], 'readonly' => ['type' => 'boolean', 'description' => __('Whether the file is read-only.', 'windpress')]]], 'execute_callback' => [\WindPress\WindPress\Abilities\Abilities\GetVolumeEntry::class, 'execute'], 'permission_callback' => fn() => current_user_can('manage_options'), 'meta' => ['annotations' => ['readonly' => \true, 'destructive' => \false, 'idempotent' => \true], 'show_in_rest' => \true]]); |
| 147 |
} |
| 148 |
/** |
| 149 |
* Register the get-volume-handlers ability |
| 150 |
*/ |
| 151 |
private function register_get_volume_handlers(): void |
| 152 |
{ |
| 153 |
wp_register_ability('windpress/get-volume-handlers', ['label' => __('Get Volume Handlers', 'windpress'), 'description' => __('Retrieves available custom handlers for the WindPress Simple File System.', 'windpress'), 'category' => 'volume-management', 'input_schema' => [], 'output_schema' => ['type' => 'array', 'items' => ['type' => 'string'], 'description' => __('Array of available handler names.', 'windpress')], 'execute_callback' => [\WindPress\WindPress\Abilities\Abilities\GetVolumeHandlers::class, 'execute'], 'permission_callback' => fn() => current_user_can('manage_options'), 'meta' => ['annotations' => ['readonly' => \true, 'destructive' => \false, 'idempotent' => \true], 'show_in_rest' => \true]]); |
| 154 |
} |
| 155 |
/** |
| 156 |
* Register the save-volume-entries ability |
| 157 |
*/ |
| 158 |
private function register_save_volume_entries(): void |
| 159 |
{ |
| 160 |
wp_register_ability('windpress/save-volume-entries', ['label' => __('Save Volume Entries', 'windpress'), 'description' => __('Saves or updates multiple files in the WindPress Simple File System at once.', 'windpress'), 'category' => 'volume-management', 'input_schema' => ['type' => 'object', 'properties' => ['entries' => ['type' => 'array', 'items' => ['type' => 'object', 'properties' => ['name' => ['type' => 'string', 'description' => __('File name.', 'windpress')], 'relative_path' => ['type' => 'string', 'description' => __('Relative path from data directory.', 'windpress')], 'content' => ['type' => 'string', 'minLength' => 1, 'description' => __('File content.', 'windpress')], 'handler' => ['type' => 'string', 'description' => __('Handler type (internal or custom).', 'windpress')], 'signature' => ['type' => 'string', 'description' => __('Security signature for existing files.', 'windpress')]], 'required' => ['name', 'relative_path', 'content', 'handler']]]], 'required' => ['entries']], 'output_schema' => ['type' => 'object', 'properties' => ['success' => ['type' => 'boolean', 'description' => __('Whether the operation was successful.', 'windpress')], 'message' => ['type' => 'string', 'description' => __('Result message.', 'windpress')]]], 'execute_callback' => [\WindPress\WindPress\Abilities\Abilities\SaveVolumeEntries::class, 'execute'], 'permission_callback' => fn() => current_user_can('manage_options'), 'meta' => ['annotations' => ['readonly' => \false, 'destructive' => \true, 'idempotent' => \false], 'show_in_rest' => \true]]); |
| 161 |
} |
| 162 |
/** |
| 163 |
* Register the save-volume-entry ability |
| 164 |
*/ |
| 165 |
private function register_save_volume_entry(): void |
| 166 |
{ |
| 167 |
wp_register_ability('windpress/save-volume-entry', ['label' => __('Save Volume Entry', 'windpress'), 'description' => __('Saves or updates a single file in the WindPress Simple File System.', 'windpress'), 'category' => 'volume-management', 'input_schema' => ['type' => 'object', 'properties' => ['name' => ['type' => 'string', 'description' => __('File name.', 'windpress')], 'relative_path' => ['type' => 'string', 'description' => __('Relative path from data directory.', 'windpress')], 'content' => ['type' => 'string', 'minLength' => 1, 'description' => __('File content.', 'windpress')], 'handler' => ['type' => 'string', 'description' => __('Handler type (internal or custom).', 'windpress'), 'default' => 'internal'], 'signature' => ['type' => 'string', 'description' => __('Security signature for existing files.', 'windpress')]], 'required' => ['name', 'relative_path', 'content']], 'output_schema' => ['type' => 'object', 'properties' => ['success' => ['type' => 'boolean', 'description' => __('Whether the operation was successful.', 'windpress')], 'message' => ['type' => 'string', 'description' => __('Result message.', 'windpress')], 'entry' => ['type' => 'object', 'description' => __('The saved entry data.', 'windpress')]]], 'execute_callback' => [\WindPress\WindPress\Abilities\Abilities\SaveVolumeEntry::class, 'execute'], 'permission_callback' => fn() => current_user_can('manage_options'), 'meta' => ['annotations' => ['readonly' => \false, 'destructive' => \true, 'idempotent' => \false], 'show_in_rest' => \true]]); |
| 168 |
} |
| 169 |
/** |
| 170 |
* Register the delete-volume-entry ability |
| 171 |
*/ |
| 172 |
private function register_delete_volume_entry(): void |
| 173 |
{ |
| 174 |
wp_register_ability('windpress/delete-volume-entry', ['label' => __('Delete Volume Entry', 'windpress'), 'description' => __('Deletes a file from the WindPress Simple File System.', 'windpress'), 'category' => 'volume-management', 'input_schema' => ['type' => 'object', 'properties' => ['relative_path' => ['type' => 'string', 'description' => __('Relative path of the file to delete.', 'windpress')], 'signature' => ['type' => 'string', 'description' => __('Security signature for verification.', 'windpress')]], 'required' => ['relative_path', 'signature']], 'output_schema' => ['type' => 'object', 'properties' => ['success' => ['type' => 'boolean', 'description' => __('Whether the operation was successful.', 'windpress')], 'message' => ['type' => 'string', 'description' => __('Result message.', 'windpress')]]], 'execute_callback' => [\WindPress\WindPress\Abilities\Abilities\DeleteVolumeEntry::class, 'execute'], 'permission_callback' => fn() => current_user_can('manage_options'), 'meta' => ['annotations' => ['readonly' => \false, 'destructive' => \true, 'idempotent' => \true], 'show_in_rest' => \true]]); |
| 175 |
} |
| 176 |
/** |
| 177 |
* Register the reset-volume-entry ability |
| 178 |
*/ |
| 179 |
private function register_reset_volume_entry(): void |
| 180 |
{ |
| 181 |
wp_register_ability('windpress/reset-volume-entry', ['label' => __('Reset Volume Entry', 'windpress'), 'description' => __('Resets main.css or tailwind.config.js to their default content based on the current Tailwind version.', 'windpress'), 'category' => 'volume-management', 'input_schema' => ['type' => 'object', 'properties' => ['relative_path' => ['type' => 'string', 'enum' => ['main.css', 'tailwind.config.js', 'wizard.js', 'wizard.css'], 'description' => __('File to reset (main.css, tailwind.config.js, wizard.js, or wizard.css).', 'windpress')]], 'required' => ['relative_path']], 'output_schema' => ['type' => 'object', 'properties' => ['success' => ['type' => 'boolean', 'description' => __('Whether the operation was successful.', 'windpress')], 'message' => ['type' => 'string', 'description' => __('Result message.', 'windpress')], 'content' => ['type' => 'string', 'description' => __('The default file content.', 'windpress')]]], 'execute_callback' => [\WindPress\WindPress\Abilities\Abilities\ResetVolumeEntry::class, 'execute'], 'permission_callback' => fn() => current_user_can('manage_options'), 'meta' => ['annotations' => ['readonly' => \false, 'destructive' => \true, 'idempotent' => \true], 'show_in_rest' => \true]]); |
| 182 |
} |
| 183 |
} |
| 184 |
|