Update the public pages to 0.1.0
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
# The surface Nolimit Send exposes
|
||||
|
||||
<!-- nlsnd:version 0.1.0 -->
|
||||
|
||||
Everything another plugin, a theme or a site owner can bind to, store against,
|
||||
or break by changing. It describes what the installed plugin actually does, not
|
||||
what it might one day do: a table written from intent lists what a plugin means
|
||||
to provide rather than what it provides.
|
||||
|
||||
The contract covers the files inside the installed plugin. Nothing else is part
|
||||
of it.
|
||||
|
||||
## Hooks this plugin provides
|
||||
|
||||
**None.** This plugin fires no `do_action()` and applies no `apply_filters()`
|
||||
of its own, so there is nothing here for anyone to hook.
|
||||
|
||||
That is a deliberate state rather than an oversight, and it is worth one line
|
||||
so nobody adds one casually: every extension point is a promise that outlives
|
||||
the reason it was added, and nothing has yet asked to extend this. In
|
||||
particular there is no filter that lets another plugin change the SMTP
|
||||
settings — that is exactly the door a credential leaks through. The moment
|
||||
something does ask, the filter goes in this table in the same commit.
|
||||
|
||||
<!-- nlsnd:hooks:start -->
|
||||
|
||||
## Hooks this plugin registers on
|
||||
|
||||
🔴 Deliberately not headed "consumes". `add_filter()` is how you **answer** a
|
||||
filter — you supply the value somebody else asked for — and reading it as
|
||||
consumption gets the direction of the promise backwards. For an action the
|
||||
word is *listens*; for a filter it is *answers*. The Kind column below says
|
||||
which.
|
||||
|
||||
Priority and accepted-argument count are given where they are not WordPress'
|
||||
defaults of `10` and `1`, because those two are what break silently — see the
|
||||
`update_plugins_` row.
|
||||
|
||||
| Hook | Kind | Registered in | Why |
|
||||
| --- | --- | --- | --- |
|
||||
| `phpmailer_init` | action | `includes/class-mailer.php` | The whole job: points PHPMailer at the chosen server. Does nothing unless enabled, with a host, and no other mail plugin present. |
|
||||
| `wp_mail_failed` | action | `includes/class-mailer.php` | Keeps the reason a message failed — time and message only — when this plugin was the one sending. |
|
||||
| `wp_mail_from` | filter, priority 99 | `includes/class-mailer.php` | The sender address. 99 so WooCommerce's own callback at 10 has already had its say. |
|
||||
| `wp_mail_from_name` | filter, priority 99 | `includes/class-mailer.php` | The sender name, on the same rule. |
|
||||
| `plugins_loaded` | action | `includes/class-plugin.php` | Everything else is registered from here. |
|
||||
| `admin_menu` | action | `includes/class-admin.php` | Registers the screen under the shared Nolimit menu. |
|
||||
| `admin_init` | action | `includes/class-admin.php` | Handles the save, test and clear form posts, after the nonce and capability checks. |
|
||||
| `admin_init` | action | `includes/class-updater.php` | Drops the cached feed when the user presses "Check again". |
|
||||
| `admin_enqueue_scripts` | action | `includes/class-admin.php` | The one stylesheet, on this plugin's screen only. |
|
||||
| `admin_post_nlsnd_auto_update` | action | `includes/class-auto-update.php` | The auto-update toggle's form target. Name is this plugin's; the hook is WordPress'. |
|
||||
| `plugin_action_links_<basename>` | filter | `includes/class-admin.php` | The "Settings" link on the Plugins screen. Hook name is built from `NLSND_PLUGIN_BASENAME`. |
|
||||
| `plugins_api` | filter, 3 args | `includes/class-updater.php` | The "View details" panel. Guarded on `$args->slug`. |
|
||||
| `update_plugins_<hostname>` | filter, 3 args | `includes/class-updater.php` | Offers this plugin's own updates. |
|
||||
| `upgrader_process_complete` | action, 0 args | `includes/class-updater.php` | Forgets the cached feed after any update runs. |
|
||||
|
||||
`send_test()` also adds and removes a `wp_mail_failed` listener around one
|
||||
`wp_mail()` call, so the test button can say why a message did not go out. It
|
||||
is removed again on the way out.
|
||||
|
||||
**Nothing hooks `wp_mail` itself.** No filter here can alter a message's
|
||||
recipient, subject, body, headers or attachments, and the plugin's own checks
|
||||
assert that `wp_mail` has no callback from it. That is the property that lets
|
||||
the plugin be site-wide: it configures the transport and leaves the message
|
||||
alone.
|
||||
|
||||
### 🔴 `update_plugins_<hostname>` is shared, and the argument count is load-bearing
|
||||
|
||||
The hook name is built from the **host**, not the repository, so every plugin
|
||||
updating from `gitea.nolimit.dk` lands on the same filter and WordPress runs
|
||||
every callback on it for every one of them. Answering unconditionally offers
|
||||
this plugin's release to a sibling — which has happened on a live shop, and
|
||||
installs a stranger's zip over a plugin with auto-updates on.
|
||||
|
||||
The defence is two halves and neither works alone: `add_filter( …, 10, 3 )`
|
||||
**and** a guard returning `$update` untouched when `$plugin_file` is not this
|
||||
plugin's basename. Leave the count at `2` and the third argument never
|
||||
arrives, so the guard compares against an empty string and silently does
|
||||
nothing — the same bug it was written to prevent, wearing a fix's clothes.
|
||||
|
||||
A test on a site with one plugin from that host cannot see this, because the
|
||||
collision cannot arise there. The way to check it is to fire the real filter in
|
||||
both directions: the neighbour is left alone, **and** this plugin still answers
|
||||
for itself. The second half is not redundant. A guard that bails on everything
|
||||
also leaves the neighbour alone.
|
||||
|
||||
## The family's shared hooks
|
||||
|
||||
These are the Nolimit-family surface, not this plugin's.
|
||||
|
||||
| Hook | This plugin's relationship | Where |
|
||||
| --- | --- | --- |
|
||||
| `wpmgr_dashboard_cards` | **answers** — supplies this plugin's card | `includes/class-admin.php` |
|
||||
| `wpmgr_dashboard_cards` | applied by the shared file this plugin ships | `includes/shared-menu.php` |
|
||||
| `wpmgr_menu_title` | applied by the shared file this plugin ships | `includes/shared-menu.php` |
|
||||
| `wpmgr_menu_glyph` | applied by the shared file this plugin ships | `includes/shared-menu.php` |
|
||||
| `admin_head` | listened to by the shared file this plugin ships | `includes/shared-menu.php` |
|
||||
|
||||
<!-- nlsnd:hooks:end -->
|
||||
|
||||
### 🔴 Shipping a file is not owning its hooks
|
||||
|
||||
`includes/shared-menu.php` is byte-identical across the Nolimit plugins.
|
||||
Whichever copy loads **first** declares the functions; every later copy sees
|
||||
them already defined and does nothing. So the three `wpmgr_*`
|
||||
filters above are applied by *a* copy of that file, which on any real site is
|
||||
probably another plugin's. Anyone depending on those filters should depend on
|
||||
the family, not on this plugin being installed.
|
||||
|
||||
The card this plugin answers with carries `title`, `status`, `headline`,
|
||||
`lines`, `url`, `action` and `colour`. `colour` is `NLSND_COLOUR`, which the
|
||||
shared file paints as a card header with white text.
|
||||
|
||||
## The provider table — shared with Nolimit Forms
|
||||
|
||||
`NLSND_Mailer::providers()` is **hand-copied** from `NLFRM_Mailer::providers()`
|
||||
in Nolimit Forms, which has an SMTP mailer of its own. Nothing shares the code
|
||||
across the two plugins, so a difference between the copies is a defect that
|
||||
neither plugin can see. The values that must agree, by id:
|
||||
|
||||
| Id | Host | Port | Encryption |
|
||||
| --- | --- | --- | --- |
|
||||
| `brevo` | `smtp-relay.brevo.com` | 587 | tls |
|
||||
| `smtp2go` | `mail.smtp2go.com` | **2525** | tls |
|
||||
| `mailgun` | `smtp.mailgun.org` | 587 | tls |
|
||||
| `sendgrid` | `smtp.sendgrid.net` | 587 | tls |
|
||||
| `postmark` | `smtp.postmarkapp.com` | 587 | tls |
|
||||
| `ses` | *(none — region-specific, typed in)* | 587 | tls |
|
||||
| `google` | `smtp.gmail.com` | 587 | tls |
|
||||
| `microsoft` | `smtp.office365.com` | 587 | tls |
|
||||
| `custom` | *(none — typed in)* | 587 | tls |
|
||||
|
||||
Cautions such as the Microsoft one are kept out of the table on purpose
|
||||
(`NLSND_Mailer::warning()`), so one can change without touching the contract.
|
||||
|
||||
**Both plugins hook `phpmailer_init`.** With both switched on, the later
|
||||
callback wins. Nolimit Forms is told this plugin is present by the constant
|
||||
`NLSND_VERSION`, which is defined at include time for exactly that reason.
|
||||
Nolimit Send reports Forms' SMTP on its own screen as a notice rather than
|
||||
standing down for it.
|
||||
|
||||
## Stored data
|
||||
|
||||
Every row here is removed by `uninstall.php` on delete — not on deactivate,
|
||||
because switching a plugin off for ten seconds to test something must not
|
||||
destroy the shop's mail settings.
|
||||
|
||||
| Key | Kind | Holds |
|
||||
| --- | --- | --- |
|
||||
| `nlsnd_settings` | option, not autoloaded | Every setting, including the SMTP password as typed. |
|
||||
| `nlsnd_last_failure` | option | The last delivery failure: a time and a message capped at 400 characters. |
|
||||
| `nlsnd_release_feed` | transient, 15 min | The cached update feed. |
|
||||
| `nlsnd_auto_update_seeded` | option | A marker, so the auto-update default is offered once and never re-applied. |
|
||||
|
||||
The password is stored in clear because WordPress has nowhere to keep a secret;
|
||||
anyone who can read the options table can read it. It is never rendered back
|
||||
into a form. Mail content — recipient, subject, body — is **never** stored, and
|
||||
there is no send log.
|
||||
|
||||
## Capabilities
|
||||
|
||||
| Capability | Gates |
|
||||
| --- | --- |
|
||||
| `manage_options` | Seeing the screen, saving settings, sending a test, clearing the last failure. |
|
||||
| `update_plugins` | The auto-update toggle only. WordPress' own capability for the same decision. |
|
||||
|
||||
## Constants
|
||||
|
||||
`NLSND_VERSION`, `NLSND_PLUGIN_FILE`, `NLSND_PLUGIN_DIR`, `NLSND_PLUGIN_URL`,
|
||||
`NLSND_PLUGIN_BASENAME`, `NLSND_COLOUR`.
|
||||
|
||||
`NLSND_VERSION` has a reader outside this repository: Nolimit Forms asks whether
|
||||
it is defined. It must stay defined at include time, not from a hook.
|
||||
|
||||
`NLSND_COLOUR` (`#556b0f`) is the other one with a reader outside this
|
||||
plugin: the shared menu paints it as a dashboard card background with
|
||||
hardcoded white text, which it measures at 6.01:1 against white, over the 4.5:1
|
||||
the 14px label needs. It exists in three places that must stay the same string —
|
||||
the `define()`, the stylesheet's `--nlsnd-accent`, and `assets/icon.svg`.
|
||||
|
||||
## What is deliberately not here
|
||||
|
||||
Stated because an absent surface is invisible, and the next person to want one
|
||||
should find the reason rather than the silence.
|
||||
|
||||
* **No WP-CLI commands, no REST routes, no AJAX endpoints.** The screen is a
|
||||
form post handled on `admin_init`.
|
||||
* **No JavaScript.** One stylesheet, no script is enqueued.
|
||||
* **No cron, no scheduled events.** Nothing runs on a timer; the update feed is
|
||||
fetched lazily behind a transient.
|
||||
* **No queue, throttle or retry.** A failed message is recorded and not re-sent.
|
||||
* **No send log, and no bounce or complaint handling.** SMTP cannot report them.
|
||||
* **No OAuth.** Google needs an App Password; Microsoft's password sign-in is
|
||||
being retired and the screen says so.
|
||||
* **No hook on `wp_mail`, and no filter over the SMTP credentials.**
|
||||
* **No user meta, post meta or term meta.**
|
||||
* **No telemetry of any kind.** The only network traffic is the mail itself, to
|
||||
the server the owner named, and the update check against the public release
|
||||
repository, which sends no credential.
|
||||
Reference in New Issue
Block a user