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
100 changes: 100 additions & 0 deletions docs/releases/v1.0.0-rc.6.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
## WPToolkit 1.0.0-rc.6 — release candidate

A rebuild of WPToolkit as a library that other plugins and themes can rely on in production: secure by default, translatable, and safe to bundle next to a plugin built on a different version of it.

> **This is a release candidate.** It is feature-complete and CI-green, and is published so it can be tried on a real site before `1.0.0`. Expect fixes, not features, between now and the final release. Details: [what an RC is](#what-a-release-candidate-means).

**Stored data is unchanged.** Meta keys, option keys and value formats are exactly 0.x's, pinned by tests against rows shaped like real 0.x plugins and themes wrote them. Upgrading needs **no data migration**, and rolling back to 0.x stays possible.

### Changes since rc.5

- **New: the packager can show command output as it runs.** Commands run by a build (`npm run build`, Composer, scoping) were silent unless they failed, so a slow asset build looked hung. `Build::verbose()` streams every command's output line by line; `->runVerbose('npm run build')` does it for chosen commands only; `wptoolkit-build package --verbose` and `"verbose": true` in `wptoolkit.json` do the same. The default is unchanged. A command that fails after being streamed repeats only its last 20 lines in the error.

### Changes in rc.5 (since rc.4)

- **New: the guard can autoload your plugin's own classes** ([ADR-0025](https://github.com/codad5/wptoolkit/blob/v1.0.0-rc.6/docs/adr/0025-the-guard-can-autoload-the-plugins-own-namespaces.md)). Without Composer, pass `'namespaces' => array('MyPlugin\\' => __DIR__ . '/src')` to the guard and your classes load by PSR-4, with no loader code of your own. This replaces 0.x's `Autoloader::init()`, which 1.0 had dropped without a replacement. A folder that does not exist pauses the plugin with a notice naming it, instead of a "class not found" later. With Composer nothing changes and you do not need it.

### Changes in rc.4 (since rc.3)

Two fixes to scoping, both reported by a plugin being migrated to 1.0 ([ADR-0024](https://github.com/codad5/wptoolkit/blob/v1.0.0-rc.6/docs/adr/0024-the-packager-scopes-the-consumers-code-too.md)). **If you package a Composer-based plugin with `Build::scope()`, upgrade: rc.1–rc.3 built a zip that could not load.**

- **Fixed: `Build::scope()` now scopes your plugin's code and Composer's autoloader, not only the library.** Before, a plugin written against `Codad5\WPToolkit` and packaged with `->composer()->scope()->zip()` kept the old namespace in its own code and in Composer's autoload map, so activating the packaged plugin was a fatal error. In the staged copy only, `scope()` now renames the library, your code's references to it, and the package prefix in Composer's metadata, then regenerates the autoloader.
- **New safety checks in the build.** After scoping a Composer install, the build loads the scoped `Application` in a separate process and fails if it does not load. Verification refuses any package whose bundled copy is scoped while its code still names the unscoped library. A class named in a string is reported (strings are not renamed; use `::class`). `composer()` after `scope()` is refused, because it would undo the scoping.
- **Fixed: `wp {your-slug} make:*` in a plugin with a scoped copy.** Generated files named the unscoped library and could not load. They now use the namespace of the copy that generated them.
- **Tests:** the end-to-end suite now packages the example plugin with `Build::scope()` and runs it on WordPress.

Known limit: a dependency in `vendor/` that itself requires WPToolkit is not scoped by this step; use Strauss for that. See [scoping and coexistence](https://github.com/codad5/wptoolkit/blob/v1.0.0-rc.6/docs/guides/scoping-and-coexistence.md).

### Changes in rc.3 (since rc.2)

- **New: `Settings` can keep an existing option prefix** ([ADR-0023](https://github.com/codad5/wptoolkit/blob/v1.0.0-rc.6/docs/adr/0023-settings-may-keep-a-legacy-option-prefix.md)). 0.x `Settings` took an `option_prefix`; rc.1 and rc.2 always stored settings as `{slug}_{name}`, so a plugin whose saved options use another prefix could only upgrade by renaming its users' options. Pass `optionPrefix: 'acmeforms_'` and those options are read, written and registered where they already are. Without it, nothing changes.

### Changes in rc.2 (since rc.1)

- **New: shortcodes** (`Frontend\Shortcodes`, [ADR-0022](https://github.com/codad5/wptoolkit/blob/v1.0.0-rc.6/docs/adr/0022-shortcodes-are-declared-validated-and-lazy.md)). A prefixed tag (or an explicit one a plugin already ships), attributes declared and validated like route args, a required access rule, handlers that return data for a view instead of echoing, and assets that load only on pages where the shortcode renders. A handler that throws is logged and prints nothing. `wp {your-slug} make:shortcode` scaffolds one.
- **New: on-demand assets.** `->onDemand()` on a script or style keeps it out of the normal enqueue hooks; `AssetManager::load()` enqueues it when needed.
- **Tests:** the example plugin gained a shortcode covered end to end as an anonymous visitor; end-to-end setup now retries the first login on a cold WordPress, which had failed CI intermittently.
- **Release process:** release notes are now written per tag (`docs/releases/<tag>.md`).

Nothing in rc.1's API changed; both additions are additive. The option prefix restores something 0.x had, so it belongs before `1.0.0`.

### Requirements

PHP 8.1+, WordPress 6.4+. Composer is optional.

### Install

Composer (from this repository):

```bash
composer config repositories.wptoolkit vcs https://github.com/codad5/wptoolkit
composer require codad5/wptoolkit:^1.0@RC
```

Without Composer: download `wptoolkit-1.0.0-rc.6.zip` below, verify it against the `.sha256` file, and scope the copy to your own namespace with `php bin/scope.php YourNamespace` — see [scoping and coexistence](https://github.com/codad5/wptoolkit/blob/v1.0.0-rc.6/docs/guides/scoping-and-coexistence.md).

### What's in it

| Area | |
| --- | --- |
| **Safe boot** | Your main file goes through a guard that parses on PHP 5.6. On an old PHP or WordPress, or if your plugin throws while starting, the site keeps working and administrators see why. |
| **Coexistence** | Everything put into WordPress's global namespace is prefixed by your slug, and a copy scoped to your namespace can't collide with another plugin's. An incompatible copy refuses to activate and names the plugin that won. |
| **HTTP** | One deny-by-default pipeline serves REST, admin-ajax and public pages. Every route must say who may call it; input is validated before your code runs; rate limits and real HTTP status codes are built in; a JS client calls routes by name. |
| **Data** | Fields with pluggable types and meta boxes; entities stored as post types, custom tables or options behind one repository contract; a query builder with capped pages; search across title, meta and terms; versioned migrations (batched, locked, reversible); admin list columns with quick edit. |
| **Admin and front end** | Settings on the Settings API (sensitive values readable only by name, optionally encrypted), admin pages, per-user notices, shortcodes, PHP views with theme overrides and an escaper, assets enqueued at the right moment (RTL included), pretty front-end URLs. |
| **Tooling** | `wp {your-slug}` commands (`toolkit:info`, `routes:list`, `hooks:list`, `migrate`, `make:entity` …) and `wptoolkit-build` for reproducible, verified release zips. |
| **Dependencies** | None at runtime. PSR-3, PSR-11 and PSR-16 bridges are there if you want them. |

### Security: findings from 0.x, closed by design

| 0.x | 1.0 |
| --- | --- |
| REST routes were open by default | Every route needs an access rule; one without it is refused |
| Search was open to anonymous users, with client-chosen meta and no limit | Capability-based visibility, allow-listed fields, capped pages |
| A meta box exposed any post's meta to anonymous users through Ajax | No such endpoint |
| Exception messages were returned to clients | Generic error with a request id; details only in logs |
| `getAll()` on settings could return API keys | Sensitive settings are left out of bulk reads, refused for JavaScript and redacted in logs |
| Assets could be enqueued too early | Declared any time, enqueued only in the enqueue hooks |

### Breaking changes

The PHP API changed; the data did not. PHP 8.1 and WordPress 6.4 are required, every route needs an access rule, `Registry::get()` becomes constructor injection, `Model` becomes an entity plus a repository, and `Settings::getAll()` no longer returns secrets. **[Migrating from 0.x](https://github.com/codad5/wptoolkit/blob/v1.0.0-rc.6/docs/guides/migrating-from-0x.md)** maps every 0.x class to its replacement with before/after code.

Projects that require `dev-main` get 1.0 on their next `composer update`. The last 0.x is **`v0.2.0`** — pin `"codad5/wptoolkit": "0.2.*"` to stay on it.

### Verified by

CI on every change — unit tests on PHP 8.1–8.4, static analysis (PHPStan level 8), WordPress coding and security standards — plus integration tests on real WordPress (PHP 8.1 and 8.3 × WordPress 6.4 and latest) and end-to-end tests in a browser: safe boot, two versions of WPToolkit on one site, and a complete example plugin in English and Arabic (right-to-left), by keyboard, with zero notices.

### Guides

[Getting started](https://github.com/codad5/wptoolkit/blob/v1.0.0-rc.6/docs/guides/getting-started.md) · [Routes and security](https://github.com/codad5/wptoolkit/blob/v1.0.0-rc.6/docs/guides/routes-and-security.md) · [Data](https://github.com/codad5/wptoolkit/blob/v1.0.0-rc.6/docs/guides/data.md) · [Admin and front end](https://github.com/codad5/wptoolkit/blob/v1.0.0-rc.6/docs/guides/admin-and-front-end.md) · [Todo example](https://github.com/codad5/wptoolkit/tree/v1.0.0-rc.6/examples/todo)

### Known limits

- Deferred to 1.1: a generated API reference, an agent-instructions template for new plugins, WordPress Abilities integration, Psalm taint analysis.
- Not ported from 0.x (nothing used them): appearance-menu groups and dashboard widgets.

### What a release candidate means

An RC is a version that is believed to be the final release, published so real use can find what tests can't. If it holds up on a real site with `WP_DEBUG_LOG` on, the same code is released as `1.0.0`; if something breaks, the fix ships as `rc.6`. Releases whose tag contains a hyphen are marked "pre-release" on GitHub, and Composer won't install them unless you ask (`@RC`).
2 changes: 1 addition & 1 deletion resources/js/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
(function (root) {
'use strict';

var VERSION = '1.0.0-rc.5';
var VERSION = '1.0.0-rc.6';

// The shared namespace is locked: no script can replace it; entries stay writable.
if (!Object.prototype.hasOwnProperty.call(root, 'wptoolkit')) {
Expand Down
2 changes: 1 addition & 1 deletion src/Foundation/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
*/
final class Application
{
public const VERSION = '1.0.0-rc.5';
public const VERSION = '1.0.0-rc.6';

private Container $container;

Expand Down
Loading