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
15 changes: 15 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = tab

[*.{yml,yaml,json,md}]
indent_style = space
indent_size = 4

[*.md]
trim_trailing_whitespace = false
15 changes: 15 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
* text=auto eol=lf

/.github export-ignore
/docs export-ignore
/tests export-ignore
/.editorconfig export-ignore
/.env.testing export-ignore
/.env.testing.slic export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore
/codeception.dist.yml export-ignore
/codeception.slic.yml export-ignore
/cspell.json export-ignore
/engineering-plan.md export-ignore
/phpstan.neon.dist export-ignore
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/vendor/
/composer.lock
/tests/_output/*
!/tests/_output/.gitkeep
/phpstan-cache/
/codeception.yml
/.env.testing.local
.DS_Store
.idea/
.vscode/
338 changes: 338 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Plugin Absorber

Safely load bundled WordPress plugins inside a host plugin — togglable or always-on — without
re-declaration fatal errors.

## Install

```bash
composer require stellarwp/plugin-absorber
```

**Use [Strauss](https://github.com/stellarwp/global-docs/blob/main/docs/strauss-setup.md).**
Two or more plugins shipping different versions of this library will collide otherwise.

> **Do not let `extra.strauss.constant_prefix` rewrite a sub-plugin's `plugin_loaded_constant`.**
> Those are real, shared runtime constants — the whole safety mechanism depends on the bundled
> copy and the standalone defining the *same* name. Add them to `exclude_from_copy`.

Requires PHP 7.4+ and WordPress 6.4+.

## Usage

_Added as each piece lands._

## License

This program is free software; you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version. See [`LICENSE`](LICENSE).
55 changes: 55 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{
"name": "stellarwp/plugin-absorber",
"description": "Safely load bundled WordPress plugins inside a host plugin, togglable or always-on, without fatal errors.",
"type": "library",
"license": "GPL-2.0-or-later",
"minimum-stability": "stable",
"authors": [
{
"name": "StellarWP",
"email": "dev@stellarwp.com"
}
],
Comment thread
d4mation marked this conversation as resolved.
"require": {
"php": ">=7.4",
"stellarwp/container-contract": "^1.0"
},
"require-dev": {
"codeception/module-asserts": "^1.0",
"codeception/util-universalframework": "^1.0",
"lucatume/di52": "^3.0",
"lucatume/wp-browser": "^3.6.5",
"php-stubs/wordpress-stubs": "~6.4.0",
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^9.5",
"szepeviktor/phpstan-wordpress": "^1.3"
},
"autoload": {
"psr-4": {
"Nexcess\\PluginAbsorber\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Nexcess\\PluginAbsorber\\Tests\\": "tests/",
"Nexcess\\PluginAbsorber\\Tests\\Support\\": "tests/_support",
"Nexcess\\PluginAbsorber\\Tests\\Unit\\": "tests/unit"
}
},
"scripts": {
"test:analysis": [
"phpstan analyse -c phpstan.neon.dist --memory-limit=512M"
],
"test:unit": [
"slic run unit"
]
},
"config": {
"optimize-autoloader": true,
"preferred-install": "dist",
"platform": {
"php": "7.4"
},
"allow-plugins": {}
}
}
27 changes: 27 additions & 0 deletions cspell.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"version": "0.2",
"language": "en",
"words": [
"absorber",
"codeception",
"fatals",
"kadence",
"learndash",
"multisite",
"nexcess",
"packagist",
"phpstan",
"referer",
"slic",
"stellarwp",
"strauss",
"unhookable",
"uopz",
"wpunit"
],
"ignorePaths": [
"vendor/**",
"tests/_output/**",
"composer.lock"
]
}
12 changes: 6 additions & 6 deletions docs/superpowers/plans/2026-07-31-plugin-absorber.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Ship `nexcess/plugin-absorber` 1.0.0 — a dependency-light PHP library that lets a WordPress host plugin safely load formerly-standalone plugins bundled inside it, without re-declaration fatals.
**Goal:** Ship `stellarwp/plugin-absorber` 1.0.0 — a dependency-light PHP library that lets a WordPress host plugin safely load formerly-standalone plugins bundled inside it, without re-declaration fatals.

**Architecture:** A static facade (`Config` + `Loader`) over four interface-backed collaborators (`Registrar`, `Notices`, `Conflict\Resolver`, `Activation`), each resolvable from an optional PSR-style container and otherwise instantiated directly. `Loader::boot()` wires two `plugins_loaded` hooks: conflict resolution at priority 1, then the load loop at priority 2. Safety rests on two independent config keys — a load-guard constant checked with `defined()` before `require_once`, and the standalone's plugin basename used for active-detection and deactivation.

Expand All @@ -12,7 +12,7 @@

Every task's requirements implicitly include this section.

- **Composer package:** `nexcess/plugin-absorber`. **Repository:** `github.com/stellarwp/plugin-absorber`.
- **Composer package:** `stellarwp/plugin-absorber`. **Repository:** `github.com/stellarwp/plugin-absorber`.
- **Root namespace:** `Nexcess\PluginAbsorber\`. Tests: `Nexcess\PluginAbsorber\Tests\`. Support: `Nexcess\PluginAbsorber\Tests\Support\`.
- **PHP floor:** `>=7.4`. **WordPress floor:** 6.4 (the `wp_admin_notice_markup` filter). Stated in the README only — WordPress is not a Composer dependency, so it is not enforceable in `require`.
- **Class naming:** `Snake_Case` (`Sub_Plugin`, `Conflict_Policy`, `Config_Exception`). Methods fully spelled out and readable. Config keys descriptive and WordPress-centric.
Expand Down Expand Up @@ -102,7 +102,7 @@ git checkout -b 01-repo-bootstrap

```json
{
"name": "nexcess/plugin-absorber",
"name": "stellarwp/plugin-absorber",
"description": "Safely load bundled WordPress plugins inside a host plugin, togglable or always-on, without fatal errors.",
"type": "library",
"license": "GPL-2.0-or-later",
Expand Down Expand Up @@ -271,7 +271,7 @@ re-declaration fatal errors.
## Install

```bash
composer require nexcess/plugin-absorber
composer require stellarwp/plugin-absorber
```

**Use [Strauss](https://github.com/stellarwp/global-docs/blob/main/docs/strauss-setup.md).**
Expand Down Expand Up @@ -308,9 +308,9 @@ gh pr create --base main --title "Repo bootstrap" --body 'What: composer manifes

Usage:

composer require nexcess/plugin-absorber
composer require stellarwp/plugin-absorber

Why this way: `nexcess/plugin-absorber` over `sub-plugin-loader` — Packagist returns 21 hits for
Why this way: `stellarwp/plugin-absorber` over `sub-plugin-loader` — Packagist returns 21 hits for
`plugin-loader` and the top five are WordPress mu-plugin autoloaders, so the term is taken and
misleading; `plugin-absorber` returns zero. Dropping `-loader` also kills the `PluginAbsorber\Loader`
stutter. `stellarwp/container-contract` is the only production dependency, matching uplink.
Expand Down
14 changes: 9 additions & 5 deletions docs/superpowers/specs/2026-07-31-plugin-absorber-design.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Design: `nexcess/plugin-absorber`
# Design: `stellarwp/plugin-absorber`

Delivery design for the library specified in [`engineering-plan.md`](../../../engineering-plan.md).
That document defines *what* to build; this one records the decisions taken since, the
Expand All @@ -12,7 +12,7 @@ Where the two disagree, this document wins.

| Decision | Value |
|---|---|
| Composer package | `nexcess/plugin-absorber` |
| Composer package | `stellarwp/plugin-absorber` |
| Repository | `github.com/stellarwp/plugin-absorber` |
| Root namespace | `Nexcess\PluginAbsorber\` |
| Filter segment | `{hook_prefix}/plugin_absorber/…` |
Expand Down Expand Up @@ -268,7 +268,7 @@ redirect tests call `uopz_allow_exit( false )` per test.
Roughly 120 lines. Minimal, dense, no padding. In order:

1. One-sentence statement of what the library does.
2. `composer require nexcess/plugin-absorber`, immediately followed by the Strauss
2. `composer require stellarwp/plugin-absorber`, immediately followed by the Strauss
recommendation, a link to `stellarwp/global-docs/docs/strauss-setup.md`, and the warning that
`extra.strauss.constant_prefix` must **not** rewrite a sub-plugin's `plugin_loaded_constant` —
those are real shared runtime constants.
Expand Down Expand Up @@ -314,8 +314,12 @@ green.

## 9. Open items

- Packagist name `nexcess/plugin-absorber` under repository `stellarwp/plugin-absorber`. Packagist
maps name to URL independently, so this works; noting the inconsistency once, not re-opening it.
- ~~Packagist name `nexcess/plugin-absorber` under repository `stellarwp/plugin-absorber`.~~
Resolved in PR 1: the package is `stellarwp/plugin-absorber`. Packagist protects a vendor prefix
once anyone publishes under it, and `nexcess` is already held by `nexcess/magento-turpentine`
(maintainer `miguelbalparda`), which is outside our control. The `stellarwp` vendor is already
ours across 38 packages, so this ships without chasing access. The PHP namespace stays
`Nexcess\PluginAbsorber\` — Composer does not require the two to match.
- The local working directory is still `sub-plugin-loader/` while the remote is
`plugin-absorber`. Rename before PR 1.
- `engineering-plan.md` is committed at the repository root alongside this spec. PR 1 decides
Expand Down