Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .distignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ CHANGELOG.md
CLAUDE.md
CONTRIBUTING.md
README.md
README.scaffold.md
SECURITY.md

changelog/
Expand Down
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ body:
attributes:
label: Affected area
options:
- Plugin runtime (Plugin singleton, WelcomeNotice, framework wiring)
- Plugin runtime (composition root, components, framework wiring)
- Composer scoping pipeline (scoper.inc.php, post-autoload-dump)
- Changelog tooling (composer changelog:* scripts)
- CI workflows (quality.yml, tests.yml)
Expand Down
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/feature_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ body:
attributes:
label: Target area
options:
- Plugin runtime scaffold (Plugin, WelcomeNotice, framework wiring)
- Plugin runtime scaffold (composition root, components, framework wiring)
- Composer scoping pipeline
- Changelog tooling
- CI workflows
Expand Down
11 changes: 9 additions & 2 deletions .github/scripts/fill-in-scaffold.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,16 @@ grep -Eq '^[A-Za-z][A-Za-z0-9]*$' <<< "$NS" || { echo "::error::php-namespace mu
# Both ports land in JSON numbers, where a leading zero is invalid, and the tests environment takes the next one;
# neither may be the template's own 8811 or 8812, which the leftover check below refuses.
{ grep -Eq '^[1-9][0-9]{0,4}$' <<< "$PORT" && [ "$PORT" -le 65534 ] && { [ "$PORT" -lt 8810 ] || [ "$PORT" -gt 8812 ]; }; } || { echo "::error::wp-env-port must be 1-65534 with no leading zero, and neither it nor the next port may be 8811 or 8812."; exit 1; }
# The display name lands in single-quoted PHP and JavaScript strings.
if printf '%s' "$PLUGIN_NAME" | grep -q "['\\\$/]"; then echo "::error::plugin-name must not contain \\, \$, / or '."; exit 1; fi
# The display name lands in single-quoted PHP and JavaScript strings, and in JSX text.
if printf '%s' "$PLUGIN_NAME" | grep -q "['\\\$/<>{}]"; then echo "::error::plugin-name must not contain \\, \$, /, ', <, >, { or }."; exit 1; fi

export SCREAMING_PREFIX="${PREFIX^^}"
export TESTS_PORT=$(( PORT + 1 ))
export REPO_NAME="${REPO#*/}"

git mv --force README.scaffold.md README.md
# The scaffold README becomes the plugin's README.md, which .distignore already excludes.
perl -ni -e 'print unless /^README\.scaffold\.md$/' .distignore
git rm --quiet includes/_disabled-example.php
# The template's recipe test loads the recipe's safeguard helper, which a plugin that applies the recipe declares
# itself, so only the recipe's own test stays.
Expand Down Expand Up @@ -65,6 +67,11 @@ for swept_json in .wp-env.json .wp-env.tests.json composer.json package.json pac
php -r 'json_decode( (string) file_get_contents( $argv[1] ), false, 512, JSON_THROW_ON_ERROR );' "$swept_json"
done

# A name or slug shorter than the template's leaves wrapped lines that Prettier joins, so the swept scripts are formatted
# again. Install scripts stay off, because the fill-in job holds a token that can push.
npm ci --ignore-scripts --no-audit --no-fund
node_modules/.bin/wp-scripts format '{blocks/src,assets/src,tests/e2e}/**/*.{js,jsx,ts,tsx}'

status=0
if git grep -n -I -E 'dws-plugin-template|dws_tpl|DWS_TPL|PluginTemplate|DWS Plugin Template|wordpress-plugin-template' -- . ':!.github/scripts/fill-in-scaffold.sh'; then
echo "::error::The fill-in left the template's names above."
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/audit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ jobs:
audit:
name: Audit
# npm gate audits production deps only — the dev-tooling graph (wp-scripts/playwright trees) is advisory via dependabot alerts.
uses: ahegyes/wordpress-configs/.github/workflows/reusable-supply-chain-audit.yml@9f64dc77b25727130884eaf427d2bd75493557ce
uses: ahegyes/wordpress-configs/.github/workflows/reusable-supply-chain-audit.yml@3cbd985255e76f8026644ccdee5be0b2b2da312b
with:
npm-audit-flags: '--omit=dev --audit-level=high'
2 changes: 1 addition & 1 deletion .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ jobs:
actions: read
contents: read
security-events: write
uses: ahegyes/wordpress-configs/.github/workflows/reusable-codeql.yml@9f64dc77b25727130884eaf427d2bd75493557ce
uses: ahegyes/wordpress-configs/.github/workflows/reusable-codeql.yml@3cbd985255e76f8026644ccdee5be0b2b2da312b
4 changes: 4 additions & 0 deletions .github/workflows/fill-in-scaffold.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ jobs:
coverage: none
tools: composer:v2

- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: package.json

- name: Fill in the placeholders
env:
PLUGIN_NAME: ${{ inputs.plugin-name }}
Expand Down
18 changes: 14 additions & 4 deletions .github/workflows/quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,20 @@ permissions:
jobs:
lint-php:
name: PHP Lint
uses: ahegyes/wordpress-configs/.github/workflows/reusable-php-lint.yml@9f64dc77b25727130884eaf427d2bd75493557ce
uses: ahegyes/wordpress-configs/.github/workflows/reusable-php-lint.yml@3cbd985255e76f8026644ccdee5be0b2b2da312b
with:
php-version: '8.5'
scripts: '["lint:php:phpcs", "lint:php:phpcs:tests", "lint:php:phpstan"]'

lint-scripts-styles:
name: Scripts and styles lint
uses: ahegyes/wordpress-configs/.github/workflows/reusable-scripts-styles-lint.yml@9f64dc77b25727130884eaf427d2bd75493557ce
uses: ahegyes/wordpress-configs/.github/workflows/reusable-scripts-styles-lint.yml@3cbd985255e76f8026644ccdee5be0b2b2da312b
with:
scripts: '["lint:scripts", "lint:styles", "lint:types"]'

block-json:
name: block.json schema
uses: ahegyes/wordpress-configs/.github/workflows/reusable-block-json-check.yml@9f64dc77b25727130884eaf427d2bd75493557ce
uses: ahegyes/wordpress-configs/.github/workflows/reusable-block-json-check.yml@3cbd985255e76f8026644ccdee5be0b2b2da312b

slug:
name: Plugin slug
Expand All @@ -50,7 +50,7 @@ jobs:
php-syntax:
name: PHP syntax
needs: slug
uses: ahegyes/wordpress-configs/.github/workflows/reusable-php-syntax-check.yml@9f64dc77b25727130884eaf427d2bd75493557ce
uses: ahegyes/wordpress-configs/.github/workflows/reusable-php-syntax-check.yml@3cbd985255e76f8026644ccdee5be0b2b2da312b
with:
php-versions: '["7.4"]'
paths: ${{ format('["{0}.php", "uninstall.php", "includes"]', needs.slug.outputs.slug) }}
Expand Down Expand Up @@ -85,6 +85,10 @@ jobs:
php-version: '8.5'
coverage: none
tools: composer:v2
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: package.json
cache: 'npm'
- name: Fill in the scaffold
env:
PLUGIN_NAME: Acme Orders
Expand All @@ -100,3 +104,9 @@ jobs:
run: |
composer lint:php
composer test:unit
# The display name lands in the block's JSX, which only a build and the script and type lints parse.
- name: Build and lint the filled-in plugin's scripts
run: |
npm run build
npm run lint:scripts
npm run lint:types
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ jobs:
permissions:
actions: read
contents: write
uses: ahegyes/wordpress-configs/.github/workflows/reusable-release.yml@9f64dc77b25727130884eaf427d2bd75493557ce
uses: ahegyes/wordpress-configs/.github/workflows/reusable-release.yml@3cbd985255e76f8026644ccdee5be0b2b2da312b
with:
project-path: '.'
plugin-slug: ${{ needs.slug.outputs.slug }}
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/tests-nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ permissions:
jobs:
integration:
name: Integration
uses: ahegyes/wordpress-configs/.github/workflows/reusable-phpunit.yml@9f64dc77b25727130884eaf427d2bd75493557ce
uses: ahegyes/wordpress-configs/.github/workflows/reusable-phpunit.yml@3cbd985255e76f8026644ccdee5be0b2b2da312b
with:
php-version: '8.5'
wp-env-core: 'https://wordpress.org/nightly-builds/wordpress-latest.zip'
Expand All @@ -21,7 +21,7 @@ jobs:

e2e:
name: E2E
uses: ahegyes/wordpress-configs/.github/workflows/reusable-playwright-e2e.yml@9f64dc77b25727130884eaf427d2bd75493557ce
uses: ahegyes/wordpress-configs/.github/workflows/reusable-playwright-e2e.yml@3cbd985255e76f8026644ccdee5be0b2b2da312b
with:
artifact-slug: nightly
php-version: '8.5'
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ permissions:
jobs:
unit:
name: Unit
uses: ahegyes/wordpress-configs/.github/workflows/reusable-phpunit.yml@9f64dc77b25727130884eaf427d2bd75493557ce
uses: ahegyes/wordpress-configs/.github/workflows/reusable-phpunit.yml@3cbd985255e76f8026644ccdee5be0b2b2da312b
with:
php-version: '8.5'
composer-script: 'test:unit'
Expand Down Expand Up @@ -52,7 +52,7 @@ jobs:
fail-fast: false
matrix:
wp: ${{ fromJSON(needs.wordpress.outputs.versions) }}
uses: ahegyes/wordpress-configs/.github/workflows/reusable-phpunit.yml@9f64dc77b25727130884eaf427d2bd75493557ce
uses: ahegyes/wordpress-configs/.github/workflows/reusable-phpunit.yml@3cbd985255e76f8026644ccdee5be0b2b2da312b
with:
php-version: '8.5'
wp-version: ${{ matrix.wp }}
Expand All @@ -66,7 +66,7 @@ jobs:
fail-fast: false
matrix:
wp: ${{ fromJSON(needs.wordpress.outputs.versions) }}
uses: ahegyes/wordpress-configs/.github/workflows/reusable-playwright-e2e.yml@9f64dc77b25727130884eaf427d2bd75493557ce
uses: ahegyes/wordpress-configs/.github/workflows/reusable-playwright-e2e.yml@3cbd985255e76f8026644ccdee5be0b2b2da312b
with:
artifact-slug: wp-${{ matrix.wp }}
php-version: '8.5'
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/workflow-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ jobs:
contents: read
security-events: write
actions: read
uses: ahegyes/wordpress-configs/.github/workflows/reusable-workflow-checks.yml@9f64dc77b25727130884eaf427d2bd75493557ce
uses: ahegyes/wordpress-configs/.github/workflows/reusable-workflow-checks.yml@3cbd985255e76f8026644ccdee5be0b2b2da312b
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Both run PHP 8.5 with WooCommerce from `vendor/` and its order tables on; CI run
2. **Variants.** A component that exists only in one build variant is a child in a branch of its parent's `get_child_component_classes()`, never a second entry in `COMPONENTS`.
3. **Request context.** `is_admin()` is false on REST requests and true in admin-ajax ones, so an admin-only feature decides the request context in its callbacks, never in `should_load()`.
4. **Add-ons.** An add-on extends a base plugin only through the base plugin's documented hooks.
5. **Request reads.** An integer or boolean is read as `filter_var( wp_unslash( $_POST['key'] ?? '' ), FILTER_VALIDATE_INT, FILTER_NULL_ON_FAILURE ) ?? $default` (`FILTER_VALIDATE_BOOL` for a boolean), and a string through an `is_string()` guard and the field's sanitizer; never `filter_input()`, the `default` filter option or a cast on request input.
5. **Request reads.** An integer or boolean is read as `filter_var( wp_unslash( $_POST['key'] ?? '' ), FILTER_VALIDATE_INT, FILTER_NULL_ON_FAILURE ) ?? $default` (`FILTER_VALIDATE_BOOL` for a boolean, where an absent key reads false, as an unchecked checkbox posts nothing), and a string through an `is_string()` guard and the field's sanitizer; never `filter_input()`, the `default` filter option or a cast on request input.
6. **Options and data.** Options are read only through `includes/options.php`'s typed readers, safeguards excepted. Everything stored lives under `dws_tpl_`, or `_dws_tpl_` for hidden meta. The footprint covers the prefix's options, transients and user meta; object meta needs a `meta_prefixes` entry per type, and anything else its own entry.
7. **Safeguards** live in `includes/safeguards.php`, call only WordPress and that file, and relax only through their own filter, through `dws_tpl_is_relaxed()` (`docs/recipes.md`).
8. **Migrations** are append-only: a step converges from any partial state, verifies its writes and throws when one fails.
Expand Down
8 changes: 5 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,20 @@

## Changelog entries

One fragment per user-visible change so PRs don't conflict on `CHANGELOG.md`:
Each user-visible change adds one fragment, so pull requests never conflict on `CHANGELOG.md`:

```bash
composer changelog:add
```

Interactive prompt (Significance + Type + entry); fragment lands in `changelog/<branch-name>`, commit it with the PR. CI validates via `composer changelog:validate`.
The command asks for the change's significance, its type and its entry, and writes the fragment to `changelog/<branch-name>`, which the pull request commits. CI validates the fragments with `composer changelog:validate`.

## Releases

A release writes the fragments into a new version block of `CHANGELOG.md`, whose version the fragments' significance sets:

```bash
composer changelog:write
```

Aggregates fragments → new `CHANGELOG.md` version block (semver computed from fragment significance). Commit + tag.
The release commit also sets the entry file's `Version:` header to that version, and `readme.txt`'s `Stable tag:` where the plugin has one, because the release refuses a tag either differs from. Pushing the tag `v<version>` on that commit starts the release.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

WordPress plugins on the wp-framework packages start here, from one tested anatomy, so all are built alike and a fix found in one applies to all.

The template is itself a working WooCommerce plugin: it boots the framework's composition, holds a settings tab, a feature gated on one of its settings, a block, migrations, safeguards and an uninstall footprint, and every suite and release check passes on it. `docs/ARCHITECTURE.md` describes that anatomy and `docs/recipes.md` the changes most plugins make to it.
The template is itself a working WooCommerce plugin: it boots the framework's composition, holds a settings tab, a feature gated on one of its settings, a block, migrations, an empty safeguards slot and an uninstall footprint, and every suite and release check passes on it. `docs/ARCHITECTURE.md` describes that anatomy and `docs/recipes.md` the changes most plugins make to it.

## Starting a plugin

Expand Down
2 changes: 1 addition & 1 deletion README.scaffold.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# DWS Plugin Template

TODO: one sentence saying why this plugin exists, its philosophy rather than a list of what it holds. The same sentence is `composer.json`'s `description`, the entry file's `Description` header and the repository's GitHub description.
TODO: one sentence saying why this plugin exists, its philosophy rather than a list of what it holds. The same sentence is `composer.json`'s and `package.json`'s `description`, the entry file's `Description` header and the repository's GitHub description.

## Requirements

Expand Down
2 changes: 1 addition & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ In scope: the plugin's PHP, JavaScript and CSS, its prefixing configuration (`sc
Out of scope, reported upstream instead:
- plugins generated from this repository, to their maintainers;
- the wp-framework packages, to `ahegyes/wordpress-framework`;
- WordPress, WooCommerce and the other Composer and npm dependencies, to their projects. Composer refuses to resolve a dependency version with a known advisory through `roave/security-advisories`, and the Supply-Chain Audit workflow fails on one.
- WordPress, WooCommerce and the other Composer and npm dependencies, to their projects. Composer refuses to resolve a dependency version with a known advisory through `roave/security-advisories`. The Supply-Chain Audit workflow fails on an advisory anywhere in the Composer lock, and on a high-severity one among the npm runtime dependencies, of which the template has none; advisories in the npm development tools reach Dependabot alerts instead.
8 changes: 4 additions & 4 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ The request that activates the plugin does not boot it, because WordPress includ

## Data

- **Options** are prefixed constants in `includes/options.php`, read only through its typed readers, which guard each stored value and fall back to the key's default. Translated defaults come from `dws_tpl_get_option_text()` after `init`.
- **Options** are prefixed constants in `includes/options.php`, read only through its typed readers, which guard each stored value and fall back to the key's default. Translated defaults come from `dws_tpl_get_option_text()` after `init`; a field shows one as its placeholder and stores an empty value, which its reader reads as the default, so no save stores the admin's language.
- **Migrations** are the append-only steps `dws_tpl_get_migration_steps()` returns. The framework's runner runs each pending step once per site on `wp_loaded`, behind a lock, and records the revision reached in `dws_tpl_revision`. A step converges from any partial state and throws when a write fails, so a failed step runs again on a later request.
- **The footprint**, `dws_tpl_get_footprint()`, names every kind of data the plugin stores: option and user meta prefixes, network options, scheduled hooks and Action Scheduler groups. Options, transients and user meta under the `dws_tpl_` prefix are covered; meta on posts, terms, comments or orders needs a `meta_prefixes` entry per object type, and data under another name its own entry, which no check enforces.
- **Deactivation** unschedules every hook in the footprint's `cron_hooks` and `as_hooks` on the current site.
Expand Down
4 changes: 3 additions & 1 deletion docs/recipes.md
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ final class SettingsPage implements ComponentInterface, OptionsPageDefinitionInt
'type' => 'number',
'id' => DWS_TPL_SETTING_LIMIT,
'title' => __( 'Limit', 'dws-plugin-template' ),
'default' => (string) DWS_TPL_SETTING_DEFAULTS[ DWS_TPL_SETTING_LIMIT ],
'custom_attributes' => array(
'min' => '1',
'step' => '1',
Expand Down Expand Up @@ -708,7 +709,7 @@ final class Pruning implements ComponentInterface {
* @return void
*/
public function repair_schedule(): void {
$ensured_at = filter_var( get_option( DWS_TPL_OPTION_RECURRING_ENSURED_AT, 0 ), FILTER_VALIDATE_INT, FILTER_NULL_ON_FAILURE ) ?? 0;
$ensured_at = dws_tpl_get_int_option( DWS_TPL_OPTION_RECURRING_ENSURED_AT );
if ( time() - $ensured_at < HOUR_IN_SECONDS ) {
return;
}
Expand Down Expand Up @@ -1041,6 +1042,7 @@ final class Settings implements ComponentInterface, OptionsPageDefinitionInterfa
'type' => 'number',
'id' => DWS_TPL_SETTING_LIMIT,
'title' => __( 'Limit', 'dws-plugin-template' ),
'default' => (string) DWS_TPL_SETTING_DEFAULTS[ DWS_TPL_SETTING_LIMIT ],
'custom_attributes' => array(
'min' => '1',
'step' => '1',
Expand Down
2 changes: 1 addition & 1 deletion dws-plugin-template.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?php declare( strict_types=1 );
/**
* Plugin Name: DWS Plugin Template
* Plugin URI: https://github.com/ahegyes/wordpress-plugin-template
* Description: Starting point for WordPress plugins built on the DWS framework.
* Description: WordPress plugins on the wp-framework packages start here, from one tested anatomy, so all are built alike and a fix found in one applies to all.
* Version: 2.0.0
* Requires at least: 7.1
* Requires PHP: 8.5
Expand Down
Loading
Loading