@silverassist/agents-toolkit 2.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.
- package/LICENSE +135 -0
- package/README.md +388 -0
- package/bin/cli.js +940 -0
- package/package.json +52 -0
- package/src/index.js +58 -0
- package/templates/agents/AGENTS.codex.md +195 -0
- package/templates/agents/AGENTS.md +195 -0
- package/templates/agents/CLAUDE.md +213 -0
- package/templates/agents/copilot-instructions.md +83 -0
- package/templates/shared/instructions/css-styling.instructions.md +142 -0
- package/templates/shared/instructions/documentation-language.instructions.md +216 -0
- package/templates/shared/instructions/github-workflow.instructions.md +245 -0
- package/templates/shared/instructions/php-standards.instructions.md +293 -0
- package/templates/shared/instructions/react-components.instructions.md +196 -0
- package/templates/shared/instructions/server-actions.instructions.md +429 -0
- package/templates/shared/instructions/testing-standards.instructions.md +264 -0
- package/templates/shared/instructions/tests.instructions.md +103 -0
- package/templates/shared/instructions/typescript.instructions.md +106 -0
- package/templates/shared/instructions/wordpress-plugin-architecture.instructions.md +238 -0
- package/templates/shared/prompts/README.md +129 -0
- package/templates/shared/prompts/_partials/README.md +57 -0
- package/templates/shared/prompts/_partials/documentation.md +203 -0
- package/templates/shared/prompts/_partials/git-operations.md +171 -0
- package/templates/shared/prompts/_partials/github-integration.md +100 -0
- package/templates/shared/prompts/_partials/jira-integration.md +155 -0
- package/templates/shared/prompts/_partials/pr-template.md +216 -0
- package/templates/shared/prompts/_partials/validations.md +87 -0
- package/templates/shared/prompts/add-tests.prompt.md +151 -0
- package/templates/shared/prompts/analyze-github-issue.prompt.md +73 -0
- package/templates/shared/prompts/analyze-ticket.prompt.md +63 -0
- package/templates/shared/prompts/create-plan.prompt.md +118 -0
- package/templates/shared/prompts/create-pr.prompt.md +139 -0
- package/templates/shared/prompts/finalize-pr.prompt.md +150 -0
- package/templates/shared/prompts/fix-issues.prompt.md +92 -0
- package/templates/shared/prompts/new-wp-component.prompt.md +51 -0
- package/templates/shared/prompts/new-wp-plugin.prompt.md +46 -0
- package/templates/shared/prompts/prepare-pr.prompt.md +123 -0
- package/templates/shared/prompts/prepare-release.prompt.md +74 -0
- package/templates/shared/prompts/quality-check.prompt.md +45 -0
- package/templates/shared/prompts/review-code.prompt.md +81 -0
- package/templates/shared/prompts/work-github-issue.prompt.md +96 -0
- package/templates/shared/prompts/work-ticket.prompt.md +98 -0
- package/templates/shared/skills/README.md +53 -0
- package/templates/shared/skills/component-architecture/SKILL.md +321 -0
- package/templates/shared/skills/create-component/SKILL.md +714 -0
- package/templates/shared/skills/domain-driven-design/SKILL.md +277 -0
- package/templates/shared/skills/plugin-creation/SKILL.md +1094 -0
- package/templates/shared/skills/quality-checks/SKILL.md +493 -0
- package/templates/shared/skills/release-management/SKILL.md +649 -0
- package/templates/shared/skills/testing/SKILL.md +682 -0
- package/templates/shared/skills/testing-patterns/SKILL.md +243 -0
|
@@ -0,0 +1,1094 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: plugin-creation
|
|
3
|
+
description: Scaffold a new Silver Assist WordPress plugin from scratch. Covers file structure, PSR-4 autoloading, main plugin file, composer.json, LoadableInterface architecture, configuration files (phpcs.xml, phpstan.neon, phpunit.xml.dist), test bootstrap, CI/CD workflows, and AI agent customization. Use when creating a new plugin or understanding the standard architecture.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Silver Assist — Plugin Creation
|
|
7
|
+
|
|
8
|
+
This skill covers scaffolding a brand-new Silver Assist WordPress plugin from zero, following the unified standards documented in `SILVERASSIST_STANDARDS.md v2.0.0`.
|
|
9
|
+
|
|
10
|
+
## When to Use
|
|
11
|
+
|
|
12
|
+
- Creating a new Silver Assist WordPress plugin from scratch
|
|
13
|
+
- Setting up the full directory structure for a plugin
|
|
14
|
+
- Understanding the standard plugin architecture
|
|
15
|
+
- Bootstrapping all configuration files (PHPCS, PHPStan, PHPUnit, CI/CD)
|
|
16
|
+
- Adding AI agent customization (copilot-instructions, file instructions, skills)
|
|
17
|
+
|
|
18
|
+
## Prerequisites
|
|
19
|
+
|
|
20
|
+
- PHP 8.2+
|
|
21
|
+
- Composer installed globally
|
|
22
|
+
- WordPress 6.5+ development environment
|
|
23
|
+
- GitHub repository under the `SilverAssist` organization
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Architecture Overview
|
|
28
|
+
|
|
29
|
+
### File Structure
|
|
30
|
+
|
|
31
|
+
Every Silver Assist plugin follows this structure:
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
plugin-name/
|
|
35
|
+
├── plugin-name.php # Main plugin file
|
|
36
|
+
├── composer.json # Composer dependencies & autoloading
|
|
37
|
+
├── README.md # User documentation
|
|
38
|
+
├── CONTRIBUTING.md # Developer documentation
|
|
39
|
+
├── CHANGELOG.md # Version history
|
|
40
|
+
├── LICENSE # License file
|
|
41
|
+
├── phpcs.xml # PHPCS configuration
|
|
42
|
+
├── phpstan.neon # PHPStan configuration
|
|
43
|
+
├── phpunit.xml.dist # PHPUnit configuration
|
|
44
|
+
├── .github/
|
|
45
|
+
│ ├── copilot-instructions.md # Global AI agent context
|
|
46
|
+
│ ├── instructions/ # File-specific AI instructions
|
|
47
|
+
│ │ ├── php.instructions.md
|
|
48
|
+
│ │ ├── testing.instructions.md
|
|
49
|
+
│ │ └── ...
|
|
50
|
+
│ ├── skills/ # Specialized AI task skills
|
|
51
|
+
│ │ ├── release-management/
|
|
52
|
+
│ │ ├── quality-checks/
|
|
53
|
+
│ │ └── ...
|
|
54
|
+
│ └── workflows/ # CI/CD workflows
|
|
55
|
+
│ ├── ci.yml
|
|
56
|
+
│ ├── release.yml
|
|
57
|
+
│ ├── dependency-updates.yml
|
|
58
|
+
│ └── quality-checks.yml
|
|
59
|
+
├── includes/ # PSR-4 classes
|
|
60
|
+
│ ├── Core/
|
|
61
|
+
│ │ ├── Plugin.php # Main plugin bootstrap (singleton)
|
|
62
|
+
│ │ ├── Activator.php # Activation/deactivation logic
|
|
63
|
+
│ │ └── Interfaces/
|
|
64
|
+
│ │ └── LoadableInterface.php
|
|
65
|
+
│ ├── Service/ # Business logic services
|
|
66
|
+
│ │ └── Loader.php
|
|
67
|
+
│ ├── Admin/ # WordPress admin integration
|
|
68
|
+
│ │ └── Loader.php
|
|
69
|
+
│ ├── Controller/ # HTTP/Admin request handlers
|
|
70
|
+
│ ├── View/ # HTML rendering (static classes)
|
|
71
|
+
│ ├── Model/ # Domain models
|
|
72
|
+
│ ├── Repository/ # Data access layer
|
|
73
|
+
│ └── Utils/ # Utility classes
|
|
74
|
+
│ ├── Helpers.php
|
|
75
|
+
│ └── Logger.php
|
|
76
|
+
├── assets/
|
|
77
|
+
│ ├── css/
|
|
78
|
+
│ └── js/
|
|
79
|
+
├── languages/
|
|
80
|
+
│ └── plugin-name.pot
|
|
81
|
+
├── scripts/
|
|
82
|
+
│ ├── build-release.sh
|
|
83
|
+
│ ├── run-quality-checks.sh
|
|
84
|
+
│ ├── install-wp-tests.sh
|
|
85
|
+
│ ├── update-version-simple.sh
|
|
86
|
+
│ └── check-versions.sh
|
|
87
|
+
├── tests/
|
|
88
|
+
│ ├── bootstrap.php
|
|
89
|
+
│ ├── README.md
|
|
90
|
+
│ ├── Unit/
|
|
91
|
+
│ ├── Integration/
|
|
92
|
+
│ └── Helpers/
|
|
93
|
+
│ ├── TestCase.php
|
|
94
|
+
│ └── Helpers.php
|
|
95
|
+
└── docs/
|
|
96
|
+
└── API_REFERENCE.md
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### PSR-4 Autoloading
|
|
100
|
+
|
|
101
|
+
Namespace: `SilverAssist\PluginName\`
|
|
102
|
+
|
|
103
|
+
- PHP classes: `PascalCase.php` matching class names exactly
|
|
104
|
+
- Directories: `PascalCase/` (e.g., `Core/`, `Admin/`, `Service/`)
|
|
105
|
+
- Assets: `kebab-case.css`, `kebab-case.js`
|
|
106
|
+
|
|
107
|
+
### LoadableInterface Priority System
|
|
108
|
+
|
|
109
|
+
All components implement `LoadableInterface` with three methods:
|
|
110
|
+
- `init()` — Initialize the component
|
|
111
|
+
- `get_priority()` — Loading order (lower = first)
|
|
112
|
+
- `should_load()` — Conditional loading
|
|
113
|
+
|
|
114
|
+
Priority values:
|
|
115
|
+
- **10**: Core components (Plugin, Activator, critical services)
|
|
116
|
+
- **20**: Services (business logic, API clients)
|
|
117
|
+
- **30**: Admin components (controllers, settings pages)
|
|
118
|
+
- **40**: Utils & Assets (helpers, loggers)
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Step-by-Step Plugin Creation
|
|
123
|
+
|
|
124
|
+
### Step 1: Create Directory and Initialize Composer
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
mkdir plugin-name && cd plugin-name
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Create `composer.json`:
|
|
131
|
+
|
|
132
|
+
```json
|
|
133
|
+
{
|
|
134
|
+
"name": "silverassist/plugin-slug",
|
|
135
|
+
"description": "WordPress plugin description",
|
|
136
|
+
"type": "wordpress-plugin",
|
|
137
|
+
"license": "PolyForm-Noncommercial-1.0.0",
|
|
138
|
+
"authors": [
|
|
139
|
+
{
|
|
140
|
+
"name": "Silver Assist",
|
|
141
|
+
"homepage": "https://silverassist.com/"
|
|
142
|
+
}
|
|
143
|
+
],
|
|
144
|
+
"minimum-stability": "stable",
|
|
145
|
+
"require": {
|
|
146
|
+
"php": ">=8.2",
|
|
147
|
+
"composer/installers": "^2.0",
|
|
148
|
+
"silverassist/wp-github-updater": "^1.1",
|
|
149
|
+
"silverassist/wp-settings-hub": "^1.0"
|
|
150
|
+
},
|
|
151
|
+
"require-dev": {
|
|
152
|
+
"dealerdirect/phpcodesniffer-composer-installer": "^1.0",
|
|
153
|
+
"php-stubs/wordpress-stubs": "^6.6",
|
|
154
|
+
"php-stubs/wordpress-tests-stubs": "^6.6",
|
|
155
|
+
"phpcompatibility/phpcompatibility-wp": "^2.1",
|
|
156
|
+
"phpstan/phpstan": "*",
|
|
157
|
+
"phpunit/phpunit": "^9.6",
|
|
158
|
+
"squizlabs/php_codesniffer": "^3.7 || ^4.0",
|
|
159
|
+
"szepeviktor/phpstan-wordpress": "^1.3 || ^2.0",
|
|
160
|
+
"wp-coding-standards/wpcs": "^3.0",
|
|
161
|
+
"yoast/phpunit-polyfills": "^4.0"
|
|
162
|
+
},
|
|
163
|
+
"autoload": {
|
|
164
|
+
"psr-4": {
|
|
165
|
+
"SilverAssist\\PluginName\\": "includes/"
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
"autoload-dev": {
|
|
169
|
+
"psr-4": {
|
|
170
|
+
"SilverAssist\\PluginName\\Tests\\": "tests/"
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
"scripts": {
|
|
174
|
+
"phpcs": "phpcs",
|
|
175
|
+
"phpcbf": "phpcbf",
|
|
176
|
+
"phpstan": "phpstan analyse --memory-limit=1G",
|
|
177
|
+
"phpunit": "phpunit",
|
|
178
|
+
"test": [
|
|
179
|
+
"@phpcs",
|
|
180
|
+
"@phpstan",
|
|
181
|
+
"@phpunit"
|
|
182
|
+
]
|
|
183
|
+
},
|
|
184
|
+
"config": {
|
|
185
|
+
"allow-plugins": {
|
|
186
|
+
"composer/installers": true,
|
|
187
|
+
"dealerdirect/phpcodesniffer-composer-installer": true
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Then run:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
composer install
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Step 2: Create Main Plugin File
|
|
200
|
+
|
|
201
|
+
File: `plugin-name.php`
|
|
202
|
+
|
|
203
|
+
**CRITICAL**: Must include `Update URI` header to prevent WordPress.org update conflicts.
|
|
204
|
+
|
|
205
|
+
```php
|
|
206
|
+
<?php
|
|
207
|
+
/**
|
|
208
|
+
* Plugin Name: Plugin Display Name
|
|
209
|
+
* Plugin URI: https://github.com/SilverAssist/plugin-slug
|
|
210
|
+
* Description: Brief plugin description.
|
|
211
|
+
* Version: 1.0.0
|
|
212
|
+
* Author: Silver Assist
|
|
213
|
+
* Author URI: https://silverassist.com
|
|
214
|
+
* License: PolyForm-Noncommercial-1.0.0
|
|
215
|
+
* License URI: https://polyformproject.org/licenses/noncommercial/1.0.0/
|
|
216
|
+
* Text Domain: plugin-text-domain
|
|
217
|
+
* Domain Path: /languages
|
|
218
|
+
* Requires at least: 6.5
|
|
219
|
+
* Tested up to: 6.8
|
|
220
|
+
* Requires PHP: 8.2
|
|
221
|
+
* Network: false
|
|
222
|
+
* Update URI: https://github.com/SilverAssist/plugin-slug
|
|
223
|
+
*
|
|
224
|
+
* @package SilverAssist\PluginName
|
|
225
|
+
* @author Silver Assist
|
|
226
|
+
* @license PolyForm-Noncommercial-1.0.0
|
|
227
|
+
* @since 1.0.0
|
|
228
|
+
*/
|
|
229
|
+
|
|
230
|
+
// Prevent direct access.
|
|
231
|
+
\defined( 'ABSPATH' ) || exit;
|
|
232
|
+
|
|
233
|
+
// Define plugin constants.
|
|
234
|
+
define( 'PLUGIN_PREFIX_VERSION', '1.0.0' );
|
|
235
|
+
define( 'PLUGIN_PREFIX_FILE', __FILE__ );
|
|
236
|
+
define( 'PLUGIN_PREFIX_PATH', plugin_dir_path( __FILE__ ) );
|
|
237
|
+
define( 'PLUGIN_PREFIX_BASENAME', plugin_basename( __FILE__ ) );
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Composer autoloader with security validation.
|
|
241
|
+
*/
|
|
242
|
+
$plugin_prefix_autoload_path = PLUGIN_PREFIX_PATH . 'vendor/autoload.php';
|
|
243
|
+
$plugin_prefix_real_autoload_path = realpath( $plugin_prefix_autoload_path );
|
|
244
|
+
$plugin_prefix_plugin_real_path = realpath( PLUGIN_PREFIX_PATH );
|
|
245
|
+
|
|
246
|
+
// Validate: both paths resolve, autoloader is inside plugin directory.
|
|
247
|
+
if (
|
|
248
|
+
$plugin_prefix_real_autoload_path &&
|
|
249
|
+
$plugin_prefix_plugin_real_path &&
|
|
250
|
+
0 === strpos( $plugin_prefix_real_autoload_path, $plugin_prefix_plugin_real_path )
|
|
251
|
+
) {
|
|
252
|
+
require_once $plugin_prefix_real_autoload_path;
|
|
253
|
+
} else {
|
|
254
|
+
add_action(
|
|
255
|
+
'admin_notices',
|
|
256
|
+
function () {
|
|
257
|
+
printf(
|
|
258
|
+
'<div class="notice notice-error"><p>%s</p></div>',
|
|
259
|
+
esc_html__( 'Plugin Name: Missing or invalid Composer dependencies. Run "composer install".', 'plugin-text-domain' )
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Initialize plugin.
|
|
267
|
+
add_action(
|
|
268
|
+
'plugins_loaded',
|
|
269
|
+
function () {
|
|
270
|
+
\SilverAssist\PluginName\Core\Plugin::instance()->init();
|
|
271
|
+
}
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
// Register activation hook.
|
|
275
|
+
register_activation_hook(
|
|
276
|
+
__FILE__,
|
|
277
|
+
function () {
|
|
278
|
+
\SilverAssist\PluginName\Core\Activator::activate();
|
|
279
|
+
}
|
|
280
|
+
);
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
**Key Elements:**
|
|
284
|
+
- Prefixed global variables (`$plugin_prefix_*`) for WPCS compliance
|
|
285
|
+
- Security validation for Composer autoloader path (prevents path traversal)
|
|
286
|
+
- Graceful degradation with admin notice if autoloader missing
|
|
287
|
+
- Hook registration via closures
|
|
288
|
+
|
|
289
|
+
### Step 3: Create Core Classes
|
|
290
|
+
|
|
291
|
+
#### LoadableInterface
|
|
292
|
+
|
|
293
|
+
File: `includes/Core/Interfaces/LoadableInterface.php`
|
|
294
|
+
|
|
295
|
+
```php
|
|
296
|
+
<?php
|
|
297
|
+
namespace SilverAssist\PluginName\Core\Interfaces;
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* LoadableInterface defines the contract for loadable components.
|
|
301
|
+
*
|
|
302
|
+
* @package SilverAssist\PluginName\Core\Interfaces
|
|
303
|
+
*/
|
|
304
|
+
interface LoadableInterface {
|
|
305
|
+
/**
|
|
306
|
+
* Initialize the component.
|
|
307
|
+
*
|
|
308
|
+
* @return void
|
|
309
|
+
*/
|
|
310
|
+
public function init(): void;
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Get the loading priority.
|
|
314
|
+
*
|
|
315
|
+
* Lower numbers = higher priority.
|
|
316
|
+
* - 10: Core components
|
|
317
|
+
* - 20: Services
|
|
318
|
+
* - 30: Admin components
|
|
319
|
+
* - 40: Utils & Assets
|
|
320
|
+
*
|
|
321
|
+
* @return int
|
|
322
|
+
*/
|
|
323
|
+
public function get_priority(): int;
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Check if component should load.
|
|
327
|
+
*
|
|
328
|
+
* @return bool
|
|
329
|
+
*/
|
|
330
|
+
public function should_load(): bool;
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
#### Plugin.php (Main Bootstrap)
|
|
335
|
+
|
|
336
|
+
File: `includes/Core/Plugin.php`
|
|
337
|
+
|
|
338
|
+
```php
|
|
339
|
+
<?php
|
|
340
|
+
namespace SilverAssist\PluginName\Core;
|
|
341
|
+
|
|
342
|
+
use SilverAssist\PluginName\Core\Interfaces\LoadableInterface;
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Main Plugin Class.
|
|
346
|
+
*
|
|
347
|
+
* @package SilverAssist\PluginName\Core
|
|
348
|
+
* @since 1.0.0
|
|
349
|
+
*/
|
|
350
|
+
class Plugin implements LoadableInterface {
|
|
351
|
+
/**
|
|
352
|
+
* Plugin instance.
|
|
353
|
+
*
|
|
354
|
+
* @var Plugin|null
|
|
355
|
+
*/
|
|
356
|
+
private static ?Plugin $instance = null;
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Initialization flag.
|
|
360
|
+
*
|
|
361
|
+
* @var bool
|
|
362
|
+
*/
|
|
363
|
+
private bool $initialized = false;
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Get plugin instance.
|
|
367
|
+
*
|
|
368
|
+
* @return Plugin
|
|
369
|
+
*/
|
|
370
|
+
public static function instance(): Plugin {
|
|
371
|
+
if ( self::$instance === null ) {
|
|
372
|
+
self::$instance = new self();
|
|
373
|
+
}
|
|
374
|
+
return self::$instance;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Private constructor to prevent direct instantiation.
|
|
379
|
+
*/
|
|
380
|
+
private function __construct() {
|
|
381
|
+
// Initialization logic.
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Initialize plugin.
|
|
386
|
+
*
|
|
387
|
+
* @return void
|
|
388
|
+
*/
|
|
389
|
+
public function init(): void {
|
|
390
|
+
if ( $this->initialized ) {
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
$this->load_components();
|
|
395
|
+
$this->init_hooks();
|
|
396
|
+
|
|
397
|
+
$this->initialized = true;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Get loading priority.
|
|
402
|
+
*
|
|
403
|
+
* @return int
|
|
404
|
+
*/
|
|
405
|
+
public function get_priority(): int {
|
|
406
|
+
return 10;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Should this component load?
|
|
411
|
+
*
|
|
412
|
+
* @return bool
|
|
413
|
+
*/
|
|
414
|
+
public function should_load(): bool {
|
|
415
|
+
return true;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Get components to load.
|
|
420
|
+
*
|
|
421
|
+
* @return array<class-string<LoadableInterface>>
|
|
422
|
+
*/
|
|
423
|
+
private function get_components(): array {
|
|
424
|
+
return [
|
|
425
|
+
// Add component classes here.
|
|
426
|
+
// \SilverAssist\PluginName\Service\ServiceName::class,
|
|
427
|
+
// \SilverAssist\PluginName\Admin\Settings::class,
|
|
428
|
+
];
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Load all components by priority.
|
|
433
|
+
*
|
|
434
|
+
* @return void
|
|
435
|
+
*/
|
|
436
|
+
private function load_components(): void {
|
|
437
|
+
$components = [];
|
|
438
|
+
|
|
439
|
+
foreach ( $this->get_components() as $class ) {
|
|
440
|
+
if ( method_exists( $class, 'instance' ) ) {
|
|
441
|
+
$instance = $class::instance();
|
|
442
|
+
if ( $instance->should_load() ) {
|
|
443
|
+
$components[] = $instance;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Sort by priority (lower first).
|
|
449
|
+
usort( $components, fn( $a, $b ) => $a->get_priority() <=> $b->get_priority() );
|
|
450
|
+
|
|
451
|
+
// Initialize all.
|
|
452
|
+
foreach ( $components as $component ) {
|
|
453
|
+
$component->init();
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Initialize plugin hooks.
|
|
459
|
+
*
|
|
460
|
+
* @return void
|
|
461
|
+
*/
|
|
462
|
+
private function init_hooks(): void {
|
|
463
|
+
$this->init_updater();
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Initialize GitHub updater.
|
|
468
|
+
*
|
|
469
|
+
* @return void
|
|
470
|
+
*/
|
|
471
|
+
private function init_updater(): void {
|
|
472
|
+
if ( ! class_exists( \SilverAssist\GitHubUpdater\UpdaterConfig::class ) ) {
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
$config = new \SilverAssist\GitHubUpdater\UpdaterConfig(
|
|
477
|
+
plugin_basename: PLUGIN_PREFIX_BASENAME,
|
|
478
|
+
version: PLUGIN_PREFIX_VERSION,
|
|
479
|
+
github_repo: 'SilverAssist/plugin-slug',
|
|
480
|
+
plugin_file: PLUGIN_PREFIX_FILE
|
|
481
|
+
);
|
|
482
|
+
|
|
483
|
+
$config->init();
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
#### Activator.php
|
|
489
|
+
|
|
490
|
+
File: `includes/Core/Activator.php`
|
|
491
|
+
|
|
492
|
+
```php
|
|
493
|
+
<?php
|
|
494
|
+
namespace SilverAssist\PluginName\Core;
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Plugin Activator.
|
|
498
|
+
*
|
|
499
|
+
* @package SilverAssist\PluginName\Core
|
|
500
|
+
* @since 1.0.0
|
|
501
|
+
*/
|
|
502
|
+
class Activator {
|
|
503
|
+
/**
|
|
504
|
+
* Plugin activation logic.
|
|
505
|
+
*
|
|
506
|
+
* @return void
|
|
507
|
+
*/
|
|
508
|
+
public static function activate(): void {
|
|
509
|
+
// Create database tables.
|
|
510
|
+
// self::create_tables();
|
|
511
|
+
|
|
512
|
+
// Set default options.
|
|
513
|
+
self::set_default_options();
|
|
514
|
+
|
|
515
|
+
// Flush rewrite rules.
|
|
516
|
+
\flush_rewrite_rules();
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Plugin deactivation logic.
|
|
521
|
+
*
|
|
522
|
+
* @return void
|
|
523
|
+
*/
|
|
524
|
+
public static function deactivate(): void {
|
|
525
|
+
// Clean up temporary data.
|
|
526
|
+
\flush_rewrite_rules();
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Set default options.
|
|
531
|
+
*
|
|
532
|
+
* @return void
|
|
533
|
+
*/
|
|
534
|
+
private static function set_default_options(): void {
|
|
535
|
+
if ( ! \get_option( 'plugin_prefix_settings' ) ) {
|
|
536
|
+
\update_option( 'plugin_prefix_settings', [
|
|
537
|
+
'enabled' => true,
|
|
538
|
+
] );
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
### Step 4: Create Configuration Files
|
|
545
|
+
|
|
546
|
+
#### phpcs.xml
|
|
547
|
+
|
|
548
|
+
```xml
|
|
549
|
+
<?xml version="1.0"?>
|
|
550
|
+
<ruleset name="SilverAssist Plugin Standards">
|
|
551
|
+
<description>WordPress Coding Standards for SilverAssist Plugins</description>
|
|
552
|
+
|
|
553
|
+
<file>.</file>
|
|
554
|
+
|
|
555
|
+
<exclude-pattern>*/vendor/*</exclude-pattern>
|
|
556
|
+
<exclude-pattern>*/node_modules/*</exclude-pattern>
|
|
557
|
+
<exclude-pattern>*/.git/*</exclude-pattern>
|
|
558
|
+
<exclude-pattern>*/tests/*</exclude-pattern>
|
|
559
|
+
<exclude-pattern>*/build/*</exclude-pattern>
|
|
560
|
+
<exclude-pattern>*/assets/js/*</exclude-pattern>
|
|
561
|
+
<exclude-pattern>*/assets/css/*</exclude-pattern>
|
|
562
|
+
|
|
563
|
+
<arg value="ps"/>
|
|
564
|
+
<arg name="colors"/>
|
|
565
|
+
|
|
566
|
+
<config name="testVersion" value="8.2-"/>
|
|
567
|
+
|
|
568
|
+
<rule ref="WordPress-Extra">
|
|
569
|
+
<exclude name="Generic.Arrays.DisallowShortArraySyntax"/>
|
|
570
|
+
<exclude name="WordPress.Files.FileName.NotHyphenatedLowercase"/>
|
|
571
|
+
<exclude name="WordPress.Files.FileName.InvalidClassFileName"/>
|
|
572
|
+
<exclude name="Generic.Functions.OpeningFunctionBraceKernighanRitchie"/>
|
|
573
|
+
<exclude name="Generic.Classes.OpeningBraceSameLine"/>
|
|
574
|
+
</rule>
|
|
575
|
+
|
|
576
|
+
<rule ref="Universal.Arrays.DisallowShortArraySyntax">
|
|
577
|
+
<exclude name="Universal.Arrays.DisallowShortArraySyntax.Found"/>
|
|
578
|
+
</rule>
|
|
579
|
+
|
|
580
|
+
<rule ref="WordPress.NamingConventions.PrefixAllGlobals">
|
|
581
|
+
<properties>
|
|
582
|
+
<property name="prefixes" type="array">
|
|
583
|
+
<element value="plugin_prefix"/>
|
|
584
|
+
<element value="SilverAssist\PluginName"/>
|
|
585
|
+
</property>
|
|
586
|
+
</properties>
|
|
587
|
+
</rule>
|
|
588
|
+
|
|
589
|
+
<rule ref="WordPress-Docs"/>
|
|
590
|
+
<rule ref="Generic.CodeAnalysis.UnusedFunctionParameter"/>
|
|
591
|
+
<rule ref="Generic.Commenting.Todo"/>
|
|
592
|
+
|
|
593
|
+
<config name="minimum_supported_wp_version" value="6.5"/>
|
|
594
|
+
</ruleset>
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
**IMPORTANT**: Replace `plugin_prefix` and `SilverAssist\PluginName` with the actual plugin prefix and namespace.
|
|
598
|
+
|
|
599
|
+
#### phpstan.neon
|
|
600
|
+
|
|
601
|
+
```yaml
|
|
602
|
+
includes:
|
|
603
|
+
- vendor/szepeviktor/phpstan-wordpress/extension.neon
|
|
604
|
+
|
|
605
|
+
parameters:
|
|
606
|
+
level: 8
|
|
607
|
+
paths:
|
|
608
|
+
- includes
|
|
609
|
+
bootstrapFiles:
|
|
610
|
+
- plugin-main-file.php
|
|
611
|
+
scanDirectories:
|
|
612
|
+
- vendor
|
|
613
|
+
excludePaths:
|
|
614
|
+
- tests/*
|
|
615
|
+
- build/*
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
**IMPORTANT**: Replace `plugin-main-file.php` with the actual main plugin filename.
|
|
619
|
+
|
|
620
|
+
#### phpunit.xml.dist
|
|
621
|
+
|
|
622
|
+
```xml
|
|
623
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
624
|
+
<phpunit
|
|
625
|
+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
626
|
+
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.6/phpunit.xsd"
|
|
627
|
+
bootstrap="tests/bootstrap.php"
|
|
628
|
+
colors="true"
|
|
629
|
+
verbose="true"
|
|
630
|
+
stopOnFailure="false"
|
|
631
|
+
stopOnError="false"
|
|
632
|
+
beStrictAboutOutputDuringTests="true"
|
|
633
|
+
beStrictAboutTodoAnnotatedTests="true">
|
|
634
|
+
|
|
635
|
+
<testsuites>
|
|
636
|
+
<testsuite name="unit">
|
|
637
|
+
<directory suffix="Test.php">./tests/Unit</directory>
|
|
638
|
+
</testsuite>
|
|
639
|
+
<testsuite name="integration">
|
|
640
|
+
<directory suffix="Test.php">./tests/Integration</directory>
|
|
641
|
+
</testsuite>
|
|
642
|
+
<testsuite name="all">
|
|
643
|
+
<directory suffix="Test.php">./tests/Unit</directory>
|
|
644
|
+
<directory suffix="Test.php">./tests/Integration</directory>
|
|
645
|
+
</testsuite>
|
|
646
|
+
</testsuites>
|
|
647
|
+
|
|
648
|
+
<groups>
|
|
649
|
+
<exclude>
|
|
650
|
+
<group>ajax</group>
|
|
651
|
+
<group>ms-files</group>
|
|
652
|
+
<group>external-http</group>
|
|
653
|
+
</exclude>
|
|
654
|
+
</groups>
|
|
655
|
+
|
|
656
|
+
<coverage processUncoveredFiles="true">
|
|
657
|
+
<include>
|
|
658
|
+
<directory suffix=".php">./includes</directory>
|
|
659
|
+
</include>
|
|
660
|
+
<exclude>
|
|
661
|
+
<directory>./vendor</directory>
|
|
662
|
+
<directory>./tests</directory>
|
|
663
|
+
</exclude>
|
|
664
|
+
</coverage>
|
|
665
|
+
|
|
666
|
+
<php>
|
|
667
|
+
<env name="WP_ENVIRONMENT_TYPE" value="test"/>
|
|
668
|
+
<env name="WP_DEBUG" value="true"/>
|
|
669
|
+
<env name="WP_DEBUG_LOG" value="false"/>
|
|
670
|
+
<env name="WP_DEBUG_DISPLAY" value="false"/>
|
|
671
|
+
<env name="SCRIPT_DEBUG" value="false"/>
|
|
672
|
+
<const name="WP_TESTS_PHPUNIT_POLYFILLS_PATH" value="vendor/yoast/phpunit-polyfills"/>
|
|
673
|
+
<const name="PLUGIN_PREFIX_TESTING" value="true"/>
|
|
674
|
+
</php>
|
|
675
|
+
</phpunit>
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
### Step 5: Create Test Infrastructure
|
|
679
|
+
|
|
680
|
+
See the **testing** skill for full details on `tests/bootstrap.php`, `tests/Helpers/TestCase.php`, and test patterns.
|
|
681
|
+
|
|
682
|
+
### Step 6: Create CI/CD Workflows
|
|
683
|
+
|
|
684
|
+
See the **release-management** skill for `release.yml` and the CI workflow templates.
|
|
685
|
+
|
|
686
|
+
#### ci.yml
|
|
687
|
+
|
|
688
|
+
File: `.github/workflows/ci.yml`
|
|
689
|
+
|
|
690
|
+
```yaml
|
|
691
|
+
name: CI
|
|
692
|
+
permissions:
|
|
693
|
+
contents: read
|
|
694
|
+
|
|
695
|
+
on:
|
|
696
|
+
push:
|
|
697
|
+
branches: [ main, develop ]
|
|
698
|
+
pull_request:
|
|
699
|
+
branches: [ main ]
|
|
700
|
+
workflow_dispatch:
|
|
701
|
+
|
|
702
|
+
jobs:
|
|
703
|
+
quality-checks-82:
|
|
704
|
+
name: Quality Checks (PHP 8.2)
|
|
705
|
+
uses: ./.github/workflows/quality-checks.yml
|
|
706
|
+
with:
|
|
707
|
+
php-version: '8.2'
|
|
708
|
+
skip-wp-setup: false
|
|
709
|
+
upload-coverage: true
|
|
710
|
+
|
|
711
|
+
quality-checks-83:
|
|
712
|
+
name: Quality Checks (PHP 8.3)
|
|
713
|
+
uses: ./.github/workflows/quality-checks.yml
|
|
714
|
+
with:
|
|
715
|
+
php-version: '8.3'
|
|
716
|
+
skip-wp-setup: false
|
|
717
|
+
upload-coverage: false
|
|
718
|
+
|
|
719
|
+
quality-checks-84:
|
|
720
|
+
name: Quality Checks (PHP 8.4)
|
|
721
|
+
uses: ./.github/workflows/quality-checks.yml
|
|
722
|
+
with:
|
|
723
|
+
php-version: '8.4'
|
|
724
|
+
skip-wp-setup: false
|
|
725
|
+
upload-coverage: false
|
|
726
|
+
|
|
727
|
+
security-scan:
|
|
728
|
+
name: Security Scan
|
|
729
|
+
runs-on: ubuntu-latest
|
|
730
|
+
steps:
|
|
731
|
+
- uses: actions/checkout@v5
|
|
732
|
+
- uses: shivammathur/setup-php@v2
|
|
733
|
+
with:
|
|
734
|
+
php-version: '8.2'
|
|
735
|
+
extensions: mbstring, intl
|
|
736
|
+
coverage: none
|
|
737
|
+
- run: composer install --no-interaction --no-progress --optimize-autoloader
|
|
738
|
+
- run: composer audit
|
|
739
|
+
|
|
740
|
+
compatibility:
|
|
741
|
+
name: WordPress Compatibility
|
|
742
|
+
runs-on: ubuntu-latest
|
|
743
|
+
strategy:
|
|
744
|
+
matrix:
|
|
745
|
+
wordpress-version: ['6.5', '6.6', '6.7', 'latest']
|
|
746
|
+
services:
|
|
747
|
+
mysql:
|
|
748
|
+
image: mysql:8.0
|
|
749
|
+
env:
|
|
750
|
+
MYSQL_ROOT_PASSWORD: root
|
|
751
|
+
MYSQL_DATABASE: wordpress_test
|
|
752
|
+
ports:
|
|
753
|
+
- 3306:3306
|
|
754
|
+
options: >-
|
|
755
|
+
--health-cmd="mysqladmin ping"
|
|
756
|
+
--health-interval=10s
|
|
757
|
+
--health-timeout=5s
|
|
758
|
+
--health-retries=5
|
|
759
|
+
steps:
|
|
760
|
+
- uses: actions/checkout@v5
|
|
761
|
+
- uses: shivammathur/setup-php@v2
|
|
762
|
+
with:
|
|
763
|
+
php-version: '8.2'
|
|
764
|
+
extensions: mbstring, intl, mysql, pdo_mysql
|
|
765
|
+
coverage: none
|
|
766
|
+
- run: composer install --no-interaction --no-progress --optimize-autoloader
|
|
767
|
+
- run: bash scripts/install-wp-tests.sh wordpress_test root 'root' 127.0.0.1 ${{ matrix.wordpress-version }} false
|
|
768
|
+
- run: vendor/bin/phpunit
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
### Step 7: Create .gitignore
|
|
772
|
+
|
|
773
|
+
```gitignore
|
|
774
|
+
# Dependencies
|
|
775
|
+
vendor/
|
|
776
|
+
node_modules/
|
|
777
|
+
composer.lock
|
|
778
|
+
|
|
779
|
+
# Build
|
|
780
|
+
build/
|
|
781
|
+
dist/
|
|
782
|
+
*.zip
|
|
783
|
+
*.tar.gz
|
|
784
|
+
|
|
785
|
+
# IDE
|
|
786
|
+
.idea/
|
|
787
|
+
.vscode/
|
|
788
|
+
*.swp
|
|
789
|
+
*.swo
|
|
790
|
+
|
|
791
|
+
# OS
|
|
792
|
+
.DS_Store
|
|
793
|
+
Thumbs.db
|
|
794
|
+
|
|
795
|
+
# Coverage
|
|
796
|
+
coverage/
|
|
797
|
+
.phpunit.result.cache
|
|
798
|
+
```
|
|
799
|
+
|
|
800
|
+
### Step 8: Create CHANGELOG.md
|
|
801
|
+
|
|
802
|
+
```markdown
|
|
803
|
+
# Changelog
|
|
804
|
+
|
|
805
|
+
All notable changes to Plugin Name will be documented in this file.
|
|
806
|
+
|
|
807
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
808
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
809
|
+
|
|
810
|
+
## [Unreleased]
|
|
811
|
+
|
|
812
|
+
## [1.0.0] - YYYY-MM-DD
|
|
813
|
+
|
|
814
|
+
### Added
|
|
815
|
+
- Initial release.
|
|
816
|
+
```
|
|
817
|
+
|
|
818
|
+
---
|
|
819
|
+
|
|
820
|
+
## PHP Requirements & Coding Standards
|
|
821
|
+
|
|
822
|
+
### PHP Version
|
|
823
|
+
- **Minimum**: PHP 8.2
|
|
824
|
+
- **Recommended**: PHP 8.3+
|
|
825
|
+
- Use modern PHP: enums, readonly properties, union types, named arguments
|
|
826
|
+
|
|
827
|
+
### WordPress Naming Conventions (CRITICAL for WPCS)
|
|
828
|
+
|
|
829
|
+
1. **Global variables**: Prefix with `plugin_prefix_`
|
|
830
|
+
2. **Functions**: Prefix with `plugin_prefix_`
|
|
831
|
+
3. **Constants**: Prefix with `PLUGIN_PREFIX_`
|
|
832
|
+
4. **Classes**: Namespaced (no prefix needed)
|
|
833
|
+
5. **Inline comments**: MUST end with `.`, `!`, or `?`
|
|
834
|
+
6. **WordPress functions in namespaced code**: MUST use backslash prefix (`\add_action()`, `\get_option()`)
|
|
835
|
+
7. **String quotation**: Single quotes for literals, double quotes only for interpolation
|
|
836
|
+
|
|
837
|
+
### Security Standards
|
|
838
|
+
|
|
839
|
+
**Input Sanitization:**
|
|
840
|
+
```php
|
|
841
|
+
$text = \sanitize_text_field( \wp_unslash( $_POST['field_name'] ) );
|
|
842
|
+
$email = \sanitize_email( $_POST['email'] );
|
|
843
|
+
$url = \esc_url_raw( $_POST['url'] );
|
|
844
|
+
$int = \absint( $_POST['number'] );
|
|
845
|
+
```
|
|
846
|
+
|
|
847
|
+
**Output Escaping:**
|
|
848
|
+
```php
|
|
849
|
+
echo \esc_html( $text );
|
|
850
|
+
echo '<div class="' . \esc_attr( $class ) . '">';
|
|
851
|
+
echo '<a href="' . \esc_url( $url ) . '">';
|
|
852
|
+
```
|
|
853
|
+
|
|
854
|
+
**Nonce Verification:**
|
|
855
|
+
```php
|
|
856
|
+
\wp_nonce_field( 'plugin_action', 'plugin_nonce' );
|
|
857
|
+
if ( ! isset( $_POST['plugin_nonce'] ) || ! \wp_verify_nonce( $_POST['plugin_nonce'], 'plugin_action' ) ) {
|
|
858
|
+
\wp_die( \esc_html__( 'Security check failed.', 'plugin-text-domain' ) );
|
|
859
|
+
}
|
|
860
|
+
```
|
|
861
|
+
|
|
862
|
+
**Capability Checks:**
|
|
863
|
+
```php
|
|
864
|
+
if ( ! \current_user_can( 'manage_options' ) ) {
|
|
865
|
+
\wp_die( \esc_html__( 'Insufficient permissions.', 'plugin-text-domain' ) );
|
|
866
|
+
}
|
|
867
|
+
```
|
|
868
|
+
|
|
869
|
+
### Database Operations
|
|
870
|
+
|
|
871
|
+
Use `%i` placeholder for table/column names (WordPress 6.2+):
|
|
872
|
+
|
|
873
|
+
```php
|
|
874
|
+
$results = $wpdb->get_results(
|
|
875
|
+
$wpdb->prepare(
|
|
876
|
+
'SELECT * FROM %i WHERE status = %s AND form_id = %d',
|
|
877
|
+
$table_name,
|
|
878
|
+
$status,
|
|
879
|
+
$form_id
|
|
880
|
+
),
|
|
881
|
+
ARRAY_A
|
|
882
|
+
);
|
|
883
|
+
```
|
|
884
|
+
|
|
885
|
+
| Placeholder | Use Case |
|
|
886
|
+
|-------------|----------|
|
|
887
|
+
| `%i` | Identifiers (table/column names) |
|
|
888
|
+
| `%s` | Strings |
|
|
889
|
+
| `%d` | Integers |
|
|
890
|
+
| `%f` | Floats |
|
|
891
|
+
|
|
892
|
+
### Documentation (PHPDoc)
|
|
893
|
+
|
|
894
|
+
All classes, methods, and properties MUST have PHPDoc:
|
|
895
|
+
|
|
896
|
+
```php
|
|
897
|
+
/**
|
|
898
|
+
* Class description.
|
|
899
|
+
*
|
|
900
|
+
* @package SilverAssist\PluginName
|
|
901
|
+
* @since 1.0.0
|
|
902
|
+
*/
|
|
903
|
+
class ClassName {
|
|
904
|
+
/**
|
|
905
|
+
* Property description.
|
|
906
|
+
*
|
|
907
|
+
* @var string
|
|
908
|
+
*/
|
|
909
|
+
private string $property;
|
|
910
|
+
|
|
911
|
+
/**
|
|
912
|
+
* Method description.
|
|
913
|
+
*
|
|
914
|
+
* @param string $param Parameter description.
|
|
915
|
+
* @return bool Return value description.
|
|
916
|
+
* @since 1.0.0
|
|
917
|
+
*/
|
|
918
|
+
public function method_name( string $param ): bool {
|
|
919
|
+
// Implementation.
|
|
920
|
+
return true;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
```
|
|
924
|
+
|
|
925
|
+
### Internationalization (i18n)
|
|
926
|
+
|
|
927
|
+
**Pre-PR**: Always regenerate `.pot`:
|
|
928
|
+
|
|
929
|
+
```bash
|
|
930
|
+
wp i18n make-pot . languages/plugin-text-domain.pot --domain=plugin-text-domain
|
|
931
|
+
```
|
|
932
|
+
|
|
933
|
+
**Rules:**
|
|
934
|
+
- ALWAYS use literal text domain strings (never variables/constants)
|
|
935
|
+
- ALWAYS use ordered placeholders for multiple args with translator comments
|
|
936
|
+
|
|
937
|
+
```php
|
|
938
|
+
sprintf(
|
|
939
|
+
/* translators: %1$s: form name, %2$d: submission count */
|
|
940
|
+
__( 'Form "%1$s" has %2$d submissions', 'plugin-text-domain' ),
|
|
941
|
+
$form_name,
|
|
942
|
+
$count
|
|
943
|
+
);
|
|
944
|
+
```
|
|
945
|
+
|
|
946
|
+
---
|
|
947
|
+
|
|
948
|
+
## SilverAssist Required Packages
|
|
949
|
+
|
|
950
|
+
### wp-github-updater
|
|
951
|
+
|
|
952
|
+
Enables automatic updates from GitHub releases.
|
|
953
|
+
|
|
954
|
+
```bash
|
|
955
|
+
composer require silverassist/wp-github-updater:^1.1
|
|
956
|
+
```
|
|
957
|
+
|
|
958
|
+
Integration via `UpdaterConfig` in `Plugin::init_updater()` (see Plugin.php template above).
|
|
959
|
+
|
|
960
|
+
**IMPORTANT**: Do NOT add update-related headers to plugin file (handled programmatically by the package).
|
|
961
|
+
|
|
962
|
+
### wp-settings-hub
|
|
963
|
+
|
|
964
|
+
Provides unified settings interface for all SilverAssist plugins.
|
|
965
|
+
|
|
966
|
+
```bash
|
|
967
|
+
composer require silverassist/wp-settings-hub:^1.0
|
|
968
|
+
```
|
|
969
|
+
|
|
970
|
+
Integration in Settings class:
|
|
971
|
+
|
|
972
|
+
```php
|
|
973
|
+
use SilverAssist\SettingsHub\SettingsHub;
|
|
974
|
+
|
|
975
|
+
public function register_settings_page(): void {
|
|
976
|
+
if ( ! class_exists( SettingsHub::class ) ) {
|
|
977
|
+
// Fallback: standalone settings page.
|
|
978
|
+
\add_options_page( ... );
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
SettingsHub::get_instance()->register_plugin_page(
|
|
983
|
+
slug: 'plugin-settings',
|
|
984
|
+
title: __( 'Plugin Name', 'plugin-text-domain' ),
|
|
985
|
+
callback: [ $this, 'render_settings_page' ],
|
|
986
|
+
icon: 'dashicons-admin-generic',
|
|
987
|
+
position: 10
|
|
988
|
+
);
|
|
989
|
+
}
|
|
990
|
+
```
|
|
991
|
+
|
|
992
|
+
**Note**: Settings Hub uses `get_instance()` (different from plugin singletons which use `instance()`).
|
|
993
|
+
|
|
994
|
+
---
|
|
995
|
+
|
|
996
|
+
## View Classes (No LoadableInterface)
|
|
997
|
+
|
|
998
|
+
Views are static classes for HTML rendering — they do NOT implement LoadableInterface:
|
|
999
|
+
|
|
1000
|
+
```php
|
|
1001
|
+
<?php
|
|
1002
|
+
namespace SilverAssist\PluginName\View\Admin;
|
|
1003
|
+
|
|
1004
|
+
\defined( 'ABSPATH' ) || exit;
|
|
1005
|
+
|
|
1006
|
+
/**
|
|
1007
|
+
* Settings page view.
|
|
1008
|
+
*
|
|
1009
|
+
* @package SilverAssist\PluginName\View\Admin
|
|
1010
|
+
*/
|
|
1011
|
+
class SettingsView {
|
|
1012
|
+
/**
|
|
1013
|
+
* Render the settings page.
|
|
1014
|
+
*
|
|
1015
|
+
* @param array<string, mixed> $data Data to render.
|
|
1016
|
+
* @return void
|
|
1017
|
+
*/
|
|
1018
|
+
public static function render( array $data ): void {
|
|
1019
|
+
?>
|
|
1020
|
+
<div class="wrap">
|
|
1021
|
+
<h1><?php \esc_html_e( 'Settings', 'plugin-text-domain' ); ?></h1>
|
|
1022
|
+
<!-- HTML content -->
|
|
1023
|
+
</div>
|
|
1024
|
+
<?php
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
```
|
|
1028
|
+
|
|
1029
|
+
---
|
|
1030
|
+
|
|
1031
|
+
## AI Agent Customization (VS Code)
|
|
1032
|
+
|
|
1033
|
+
### 3-Tier System
|
|
1034
|
+
|
|
1035
|
+
| Level | Location | Scope |
|
|
1036
|
+
|-------|----------|-------|
|
|
1037
|
+
| Global Context | `.github/copilot-instructions.md` | Always active |
|
|
1038
|
+
| File-Specific | `.github/instructions/*.instructions.md` | Conditional via `applyTo` |
|
|
1039
|
+
| Task Skills | `.github/skills/*/SKILL.md` | On-demand |
|
|
1040
|
+
|
|
1041
|
+
### copilot-instructions.md Template
|
|
1042
|
+
|
|
1043
|
+
Keep concise (~100-150 lines max). Move details to Instructions or Skills.
|
|
1044
|
+
|
|
1045
|
+
Should contain:
|
|
1046
|
+
- Project overview (name, namespace, PHP version, WP version, standards)
|
|
1047
|
+
- Architecture summary (LoadableInterface priorities, key directories)
|
|
1048
|
+
- Critical rules (pre-PR checklist commands)
|
|
1049
|
+
- Quick references (common commands)
|
|
1050
|
+
|
|
1051
|
+
### Recommended Instructions Files
|
|
1052
|
+
|
|
1053
|
+
| File | `applyTo` | Content |
|
|
1054
|
+
|------|-----------|---------|
|
|
1055
|
+
| `php.instructions.md` | `**/*.php` | PHPCS rules, security, architecture |
|
|
1056
|
+
| `testing.instructions.md` | `tests/**/*.php` | Test patterns, WP factories |
|
|
1057
|
+
| `github-workflow.instructions.md` | `.github/workflows/**` | CI/CD patterns |
|
|
1058
|
+
| `documentation-language.instructions.md` | `**/*.md` | Language rules for docs |
|
|
1059
|
+
|
|
1060
|
+
### Recommended Skills
|
|
1061
|
+
|
|
1062
|
+
| Skill | Description |
|
|
1063
|
+
|-------|-------------|
|
|
1064
|
+
| `release-management` | Version bumps, immutable tags, release workflow |
|
|
1065
|
+
| `quality-checks` | PHPCS, PHPStan, PHPUnit troubleshooting |
|
|
1066
|
+
| `create-component` | New services, controllers, views with LoadableInterface |
|
|
1067
|
+
|
|
1068
|
+
---
|
|
1069
|
+
|
|
1070
|
+
## Checklist for New Plugin
|
|
1071
|
+
|
|
1072
|
+
- [ ] Directory created with correct slug
|
|
1073
|
+
- [ ] `composer.json` with correct namespace, prefixes, and packages
|
|
1074
|
+
- [ ] Main plugin file with all required headers (including `Update URI`)
|
|
1075
|
+
- [ ] `includes/Core/Interfaces/LoadableInterface.php` created
|
|
1076
|
+
- [ ] `includes/Core/Plugin.php` with singleton, components, and updater
|
|
1077
|
+
- [ ] `includes/Core/Activator.php` with activate/deactivate methods
|
|
1078
|
+
- [ ] `phpcs.xml` with correct prefixes for the plugin
|
|
1079
|
+
- [ ] `phpstan.neon` with correct bootstrap file
|
|
1080
|
+
- [ ] `phpunit.xml.dist` with correct constant prefix
|
|
1081
|
+
- [ ] `tests/bootstrap.php` with correct constants and plugin file
|
|
1082
|
+
- [ ] `tests/Helpers/TestCase.php` extending `WP_UnitTestCase`
|
|
1083
|
+
- [ ] `.github/workflows/ci.yml` created
|
|
1084
|
+
- [ ] `.github/workflows/release.yml` created (see release-management skill)
|
|
1085
|
+
- [ ] `scripts/build-release.sh` copied from existing plugin
|
|
1086
|
+
- [ ] `scripts/update-version-simple.sh` created
|
|
1087
|
+
- [ ] `.gitignore` configured
|
|
1088
|
+
- [ ] `CHANGELOG.md` initialized
|
|
1089
|
+
- [ ] `README.md` created
|
|
1090
|
+
- [ ] `LICENSE` file added (PolyForm-Noncommercial-1.0.0)
|
|
1091
|
+
- [ ] `composer install` runs successfully
|
|
1092
|
+
- [ ] `vendor/bin/phpcs` passes with 0 errors
|
|
1093
|
+
- [ ] `vendor/bin/phpstan analyse` passes at level 8
|
|
1094
|
+
- [ ] GitHub repository created under SilverAssist organization
|