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
12 changes: 6 additions & 6 deletions .github/scripts/fill-in-scaffold.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,18 @@ done
# A namespace that is a reserved word, such as List, parses from PHP 8.0 only, which the fill-in job's PHP 7.4 syntax
# check refuses.
[[ $NS =~ ^[A-Za-z][A-Za-z0-9]*$ ]] || fail 'php-namespace must match ^[A-Za-z][A-Za-z0-9]*$.'
# One segment after dws_ keeps every prefix in one plugin's family from starting another plugin's; past 23 characters
# One segment after dws_ keeps every prefix in one plugin's family from starting another plugin's; past 22 characters
# the template's longest option constants fail PHPCS's array alignment, which Quality's rehearsal at the cap checks,
# and the framework's admin notices refuse a network or hidden segment.
[[ $PREFIX =~ ^dws_[a-z0-9]{1,23}$ && $PREFIX != dws_network && $PREFIX != dws_hidden ]] || fail 'prefix must match ^dws_[a-z0-9]{1,23}$ and must not be dws_network or dws_hidden.'
[[ $PREFIX =~ ^dws_[a-z0-9]{1,22}$ && $PREFIX != dws_network && $PREFIX != dws_hidden ]] || fail 'prefix must match ^dws_[a-z0-9]{1,22}$ and must not be dws_network or dws_hidden.'
# The ports land in JSON numbers, where a leading zero is invalid, and the tests and multisite environments take the
# next two; none may be the template's own 8811, 8812 or 8813, which the leftover check below refuses.
[[ $PORT =~ ^[1-9][0-9]{0,4}$ && $PORT -le 65533 && ( $PORT -lt 8809 || $PORT -gt 8813 ) ]] ||
fail 'wp-env-port must be 1-65533 with no leading zero, and neither it nor the next two ports may be 8811, 8812 or 8813.'
# WordPress skips a plugin whose name is blank, and the name lands in single-quoted PHP and JavaScript strings, and in
# JSX text.
name_breakers="['\\\$/<>{}]"
[[ $PLUGIN_NAME =~ [^[:space:]] && ! $PLUGIN_NAME =~ $name_breakers ]] || fail "plugin-name must not be blank or contain \\, \$, /, ', <, >, { or }."
# WordPress skips a plugin whose name is blank and strips tags from the name it shows, and the name lands in
# single-quoted PHP and JavaScript strings.
name_breakers="['\\\$/<>]"
[[ $PLUGIN_NAME =~ [^[:space:]] && ! $PLUGIN_NAME =~ $name_breakers ]] || fail "plugin-name must not be blank or contain \\, \$, /, ', < or >."
# The description lands in the entry's header docblock, which */ would close.
[[ $DESCRIPTION =~ [^[:space:]] && $DESCRIPTION != *'*/'* ]] || fail 'description must not be blank or contain */.'

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ jobs:
SLUG: acme-orders
NS: AcmeOrders
# The rehearsal fills in the longest prefix the script accepts, so its PHPCS run fails once the cap stops passing.
PREFIX: dws_acmeextraordinaryorders
PREFIX: dws_acmeextraordinarysales
PORT: '8821'
REPO: acme/acme-orders
run: .github/scripts/fill-in-scaffold.sh
Expand All @@ -98,7 +98,7 @@ 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.
# The display name lands in the block's editor script, 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
Expand Down
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ src/ the composition root and its components only
├── Plugin.php SLUG, PREFIX (dws_tpl), COMPONENTS, the logger, the scheduler, the migration runner and the kernel
├── Settings.php the WooCommerce settings tab dws_tpl: the greeting's fields and the uninstall consent
├── Blocks.php the blocks from blocks/build/blocks-manifest.php and the block-editor script
└── Feature/ the greeting: a composite gated on DWS_TPL_OPTION_FEATURE and its account-dashboard child
└── Greeting/ the greeting: GreetingFeature, gated on DWS_TPL_OPTION_GREETING_ENABLED, and its AccountDashboardGreeting child
includes/ hook-free code as dws_tpl_ functions and DWS_TPL_ constants, loaded in name order, _-prefixed files skipped
├── safeguards.php restrictions that hold whenever the plugin cannot boot; loaded by the entry before the floor check
├── options.php the option keys, their defaults, the typed readers, the translated text and the validator
├── footprint.php dws_tpl_get_footprint(), every kind of data the plugin stores
├── migrations.php dws_tpl_get_migration_steps() and one function per step, append-only
├── assets.php the *.asset.php reader and the script and style registrars
└── metadata.php the slug, name and version getters, the plugin's surface for a peer plugin
blocks/src/, assets/src/ TypeScript and SCSS, built into blocks/build/ and assets/build/ (untracked)
blocks/src/, assets/src/ TypeScript, SCSS and each block's render.php, built into blocks/build/ and assets/build/ (untracked)
models/, templates/ empty extension points
.github/scripts/fill-in-scaffold.sh the fill-in's sweep, which fill-in-scaffold.yml runs once in a new plugin and Quality rehearses here
```
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ A release writes the fragments into a new version block of `CHANGELOG.md`, whose
composer changelog:write
```

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.
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. It sets each `blocks/src/*/block.json`'s `version` too, which WordPress puts in the block's stylesheet URL and the Unit suite holds to the header. 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 @@ -26,7 +26,7 @@ The workflow:
## Naming

- A WooCommerce plugin's display name reads `X for WooCommerce`, as WooCommerce's trademark guidelines ask, and its slug ends in `-for-woocommerce`.
- The prefix is `dws_` and one segment of up to 23 lowercase letters and digits, such as `dws_lpmwc`, so no plugin's prefix starts with another's and the template's longest constants still pass PHPCS; the framework's admin notices refuse `dws_network` and `dws_hidden`; a WooCommerce plugin's segment ends in `wc`.
- The prefix is `dws_` and one segment of up to 22 lowercase letters and digits, such as `dws_lpmwc`, so no plugin's prefix starts with another's and the template's longest constants still pass PHPCS; the framework's admin notices refuse `dws_network` and `dws_hidden`; a WooCommerce plugin's segment ends in `wc`.
- The namespace is flat: `DeepWebSolutions\<Name>\`, with no segment in between.
- The repository is `wc-<name>` for a WooCommerce plugin and `wp-<name>` otherwise, as the plugin mirrors are named, and the fill-in names the Composer package `<owner>/<repository>` and the npm package `@<owner>/<repository>` after it.
- Each plugin takes its own three consecutive wp-env ports, for its development, tests and multisite sites, apart from 8888 and 8889, which wp-env uses by default, and the template's own 8811, 8812 and 8813.
Expand Down
6 changes: 3 additions & 3 deletions README.scaffold.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ The fill-in renamed the plugin and kept the template's demo, which boots and pas

| Part | Where it lives | Removing it |
| --- | --- | --- |
| The greeting | `src/Feature/`, the Greeting section of `src/Settings.php`, and the `DWS_TPL_OPTION_FEATURE`, `DWS_TPL_OPTION_GREETING` and `DWS_TPL_OPTION_GREETING_ORDERS` keys in `includes/options.php` | Delete `src/Feature/` and its row in `Plugin::COMPONENTS`, the Greeting section of `src/Settings.php`, `assets/src/greeting.scss` and its `greeting=` entry in `package.json`'s `build:assets` and `start:assets`, the three keys with their defaults, text and validator, their lines in `dws_tpl_add_settings()`, and `tests/Integration/Feature/`, then the rows naming them in `OptionsTest`, `MigrationsTest` and `SettingsTest`, and `PluginBootTest`'s expectation that the greeting is skipped. `tests/EndToEnd/settings.spec.ts` drives the greeting's validated field, so point it at a validated field of the plugin's own. |
| The example block | `blocks/src/example-notice/`, registered by `src/Blocks.php` from `blocks/build/blocks-manifest.php` | Delete `blocks/`, `build:blocks` and `start:blocks` with their calls in `build` and `start`, `register_blocks()` and its `init` hook in `src/Blocks.php`, and `BlocksTest`'s block rows. |
| The editor script | `assets/src/editor.ts`, enqueued in the block editor by `src/Blocks.php` | Delete it, its `editor=` entry in `build:assets` and `start:assets`, `enqueue_editor_script()` and its hook in `src/Blocks.php`, and `BlocksTest`'s editor row. Once `assets/src/` holds nothing, delete `build:assets` and `start:assets` with their calls in `build` and `start`. Once the plugin registers no blocks either, delete `src/Blocks.php`, its row in `Plugin::COMPONENTS` and `BlocksTest`. |
| The greeting | `src/Greeting/`, the Greeting section of `src/Settings.php`, and the `DWS_TPL_OPTION_GREETING_ENABLED`, `DWS_TPL_OPTION_GREETING` and `DWS_TPL_OPTION_GREETING_ORDERS` keys in `includes/options.php` | Delete `src/Greeting/` and its row in `Plugin::COMPONENTS`, the Greeting section of `src/Settings.php`, `assets/src/greeting.scss` and its `greeting=` entry in `package.json`'s `build:assets` and `start:assets`, the three keys with their defaults, text and validator, their lines in `dws_tpl_add_settings()`, and `tests/Integration/Greeting/`, then the rows naming them in `OptionsTest`, `MigrationsTest` and `SettingsTest`, and `PluginBootTest`'s expectation that the greeting is skipped. `tests/EndToEnd/settings.spec.ts` drives the greeting's validated field, so point it at a validated field of the plugin's own. |
| The example block | `blocks/src/example-notice/`, registered by `src/Blocks.php` from `blocks/build/blocks-manifest.php` and rendered by its `render.php` | Delete `blocks/`, `build:blocks` and `start:blocks` with their calls in `build` and `start`, `register_blocks()` and its `init` hook in `src/Blocks.php`, `BlocksTest`'s block rows, and the editor script's command, which inserts the block, with `tests/EndToEnd/editor.spec.ts`. |
| The editor script | `assets/src/editor.ts`, a command in the editor's command palette that inserts the example block, enqueued in the block editor by `src/Blocks.php` | Delete it, its `editor=` entry in `build:assets` and `start:assets`, `@wordpress/commands` and `@wordpress/data` from `package.json`'s `devDependencies`, `enqueue_editor_script()` and its hook in `src/Blocks.php`, `BlocksTest`'s editor row and `tests/EndToEnd/editor.spec.ts`. Once `assets/src/` holds nothing, delete `build:assets` and `start:assets` with their calls in `build` and `start`. Once the plugin registers no blocks either, delete `src/Blocks.php`, its row in `Plugin::COMPONENTS` and `BlocksTest`. |
| `models/` and `templates/` | Empty extension points, for classes on Composer's classmap and for template partials | Delete the folders the plugin never fills, and `models/` from `composer.json`'s `autoload.classmap` and `phpstan.dist.neon`'s `paths`, since Composer and PHPStan both fail on a missing folder. |
| The fill-in | `.github/workflows/fill-in-scaffold.yml`, `.github/scripts/fill-in-scaffold.sh` and Quality's `scaffold` job | Delete all three: GitHub refuses the fill-in's own token for workflow changes, so the fill-in cannot remove them, and the job skips itself outside the template. |

Expand Down
17 changes: 13 additions & 4 deletions assets/src/editor.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import { registerBlockStyle } from '@wordpress/blocks';
import { store as blockEditorStore } from '@wordpress/block-editor';
import { createBlock } from '@wordpress/blocks';
import { store as commandsStore } from '@wordpress/commands';
import { dispatch } from '@wordpress/data';
import { __ } from '@wordpress/i18n';

registerBlockStyle( 'dws-plugin-template/example-notice', {
name: 'outlined',
label: __( 'Outlined', 'dws-plugin-template' ),
dispatch( commandsStore ).registerCommand( {
name: 'dws-plugin-template/insert-example-notice',
label: __( 'Insert an example notice', 'dws-plugin-template' ),
callback: ( { close } ) => {
dispatch( blockEditorStore ).insertBlocks(
createBlock( 'dws-plugin-template/example-notice' )
);
close();
},
} );
23 changes: 16 additions & 7 deletions blocks/src/declarations.d.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
// wp-scripts bundles the styles a block imports, so TypeScript only needs to know the imports exist.
declare module '*.scss';

// @wordpress/block-editor ships no type declarations, so the hooks the blocks use are declared here.
// @wordpress/block-editor ships no type declarations, so the hooks the blocks use and the store the editor script
// dispatches to are declared here.
declare module '@wordpress/block-editor' {
import type { BlockInstance } from '@wordpress/blocks';
import type { ReduxStoreConfig, StoreDescriptor } from '@wordpress/data';
import type { HTMLAttributes } from 'react';

export const store: StoreDescriptor<
ReduxStoreConfig<
unknown,
{
insertBlocks: (
blocks: BlockInstance | BlockInstance[]
) => object;
},
unknown
>
>;

export function useBlockProps(
props?: HTMLAttributes< HTMLElement >
): HTMLAttributes< HTMLElement >;

export namespace useBlockProps {
function save(
props?: HTMLAttributes< HTMLElement >
): HTMLAttributes< HTMLElement >;
}
}
12 changes: 10 additions & 2 deletions blocks/src/example-notice/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,25 @@
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "dws-plugin-template/example-notice",
"version": "1.0.0",
"title": "Example Notice",
"category": "widgets",
"icon": "info",
"description": "Displays a static example notice.",
"description": "Displays an example notice.",
"attributes": {},
"example": {},
"supports": {
"html": false
},
"styles": [
{
"name": "outlined",
"label": "Outlined"
}
],
"textdomain": "dws-plugin-template",
"editorScript": "file:./index.js",
"editorStyle": "file:./index.css",
"style": "file:./style-index.css"
"style": "file:./style-index.css",
"render": "file:./render.php"
}
2 changes: 1 addition & 1 deletion blocks/src/example-notice/editor.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
.wp-block-dws-plugin-template-example-notice {
.wp-block-dws-plugin-template-example-notice:not(.is-style-outlined) {
outline: 1px dashed currentcolor;
}
2 changes: 0 additions & 2 deletions blocks/src/example-notice/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { registerBlockType } from '@wordpress/blocks';

import Edit from './edit';
import save from './save';
import metadata from './block.json';
import './style.scss';

registerBlockType( metadata, {
edit: Edit,
save,
} );
14 changes: 14 additions & 0 deletions blocks/src/example-notice/render.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php declare( strict_types=1 );
/**
* Prints the example notice, which renders on each request so that every visitor reads it in their own language.
*
* @since 1.0.0
* @version 1.0.0
*
* @package DeepWebSolutions\PluginTemplate
*/

defined( 'ABSPATH' ) || exit;

?>
<p <?php echo get_block_wrapper_attributes(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- WordPress escapes every attribute it returns. ?>><?php esc_html_e( 'An example notice from DWS Plugin Template.', 'dws-plugin-template' ); ?></p>
10 changes: 0 additions & 10 deletions blocks/src/example-notice/save.tsx

This file was deleted.

2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"name": "ahegyes/wordpress-plugin-template",
"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.",
Expand Down Expand Up @@ -56,7 +56,7 @@
"automattic/jetpack-changelogger": "^7",
"humbug/php-scoper": "^0.18",
"johnpbloch/wordpress-core": "^7.1",
"laravel/serializable-closure": "^2.1",
"laravel/serializable-closure": "^2.2",
"php-di/php-di": "^7.1.1",
"php-stubs/woocommerce-stubs": "^11.2",
"phpunit/phpunit": "^13",
Expand Down
14 changes: 7 additions & 7 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 @@ -29,7 +29,7 @@ The request that activates the plugin does not boot it, because WordPress includ

- `src/` holds the composition root and its components: final classes that implement the framework's `ComponentInterface` and add their hooks in `register_hooks()`. A component takes ungated services in its constructor, such as the logger, the scheduler or `AdminNotices`, which `Plugin::get_definitions()` provides.
- `includes/` holds code that registers no hooks and is not part of the tree, as prefixed functions and constants: the option keys, defaults and typed readers (`options.php`), the footprint (`footprint.php`), the migration steps (`migrations.php`), the asset registrars (`assets.php`) and the slug, name and version getters a peer plugin reads (`metadata.php`). Quality's syntax job parses the folder on PHP 7.4, because `safeguards.php` runs before the floor check, and `phpstan.safeguards.neon` analyzes it at PHP 7.4 and the oldest WordPress the bootstrap package runs on.
- `blocks/src/` and `assets/src/` hold the TypeScript and SCSS sources, which `npm run build` compiles into `blocks/build/` and `assets/build/`; `includes/assets.php` reads the `*.asset.php` file wp-scripts writes beside each build, and `src/Blocks.php` registers the blocks from `blocks/build/blocks-manifest.php`.
- `blocks/src/` and `assets/src/` hold the TypeScript and SCSS sources, which `npm run build` compiles into `blocks/build/` and `assets/build/`, and each block's `render.php`, which it copies beside the block's build and WordPress runs on each request, so the block's text follows the visitor's language; `includes/assets.php` reads the `*.asset.php` file wp-scripts writes beside each build, and `src/Blocks.php` registers the blocks from `blocks/build/blocks-manifest.php`.
- `models/` and `templates/` are empty extension points, for classes on Composer's classmap and for template partials.

## Data
Expand Down
Loading
Loading