Files
nolimit-cache-dist/docs/CONTRACT.md
T

92 lines
5.9 KiB
Markdown
Raw Normal View History

2026-10-07 08:10:26 +00:00
# Hooks and integration
What Nolimit Cache offers to other code, and what it listens to. Written for
developers who want to extend the plugin or sit next to it; nothing here is
needed to use it.
Hooks prefixed `nlcac_` are this plugin's own and are stable: a rename is a
breaking change and will be called out in the release notes. Hooks prefixed
`wpmgr_` are shared by name with the other Nolimit plugins (see section 2).
A note on the words: for a filter, `add_filter()` is how you **answer** it, by
supplying a value. The headings below describe the mechanism, who calls
`apply_filters()` and who calls `add_filter()`, because that cannot be misread.
---
## 1. Extension points this plugin OFFERS — `nlcac_*`
`apply_filters()` / `do_action()` in our code: we run everyone's callbacks.
| Hook | Type | Signature | Fired from |
|---|---|---|---|
| `nlcac_reason_not_to_cache` | filter | `( string $reason, … )` — return a non-empty string to refuse to cache this request | page cache |
| `nlcac_page_cached` | action | `( string $path )` — a page was just written to disk | page cache |
| `nlcac_purge_urls_for_post` | filter | `( array $urls, int $post_id )` — the URLs about to be purged for a post | purging |
| `nlcac_preload_urls` | filter | `( array $urls )` — the queue the preloader will walk | preloader |
| `nlcac_exclude_script` | filter | `( bool $exclude, string $handle )` — keep a script out of defer | optimiser |
| `nlcac_ai_payload` | filter | `( array $payload )` — what the assistant is about to send | assistant |
| `nlcac_images_skip` | filter | `( bool $skip, string $img_tag )` — return true to keep one `<img>` out of the WebP/AVIF rewrite | images |
**If this plugin is absent**, none of these fire. A consumer must not assume
its callback ever runs; `has_filter()` is not a substitute for the plugin
being active.
**A consumer must not** do slow or networked work in
`nlcac_reason_not_to_cache`. It runs on every request, before WordPress, in
the drop-in — which is the whole reason the cache is fast.
## 2. The family's shared hooks — `wpmgr_*`
Shared by name across the Nolimit plugins, so **load order decides who wins**
for anything that declares itself once.
### Offered here — we call `apply_filters()` / `do_action()`
| Hook | Type | Notes |
|---|---|---|
| `wpmgr_cache_cleared` | action | `( int $removed )`. Fired when the page cache is emptied, so a sibling holding derived state can drop it in the same click. Our own object cache listens to it and only calls `wp_cache_flush()`, so there is no path back into purge. |
| `wpmgr_dashboard_cards` | filter | Provided by the shared menu file, which every Nolimit plugin carries identically. |
| `wpmgr_menu_title` | filter | Same. |
| `wpmgr_menu_glyph` | filter | Same. It exists so the glyph can be overridden. |
### Answered here — we register a callback with `add_filter()` / `add_action()`
| Hook | Type | What we do | If nobody offers it |
|---|---|---|---|
| `wpmgr_dashboard_cards` | filter | Add this plugin's card | The card is not drawn; nothing breaks |
| `wpmgr_menu_title` | filter | Answer the shared menu's title | Shared file's default |
| `wpmgr_cache_cleared` | action | Flush our object cache | — (we also fire it) |
| `wpmgr_signals` | filter | Supply this plugin's health signals to a family-wide collection. We answer it; we never read it | Only Nolimit Assistant applies this filter. With it absent or inactive, our signals are contributed to nothing and no error is raised. |
| `wpmgr_setup_recommendations` | filter | Offer three setup items to the Assistant's Setup screen: turn the page cache on, warm it in the background, keep the basket rule on. Every `done` is read from the stored setting, never measured. | Nothing is offered and nothing breaks. Only Nolimit Assistant applies this filter, so without it these items are shown nowhere |
## 3. WordPress and WooCommerce core
The plugin also attaches to WordPress and WooCommerce core hooks. They are
listed by area rather than one row each, because none of them is a contract
anyone else needs to honour: they are how the plugin attaches to WordPress.
| Area | Hooks |
|---|---|
| Boot | `init`, `plugins_loaded` |
| Admin | `admin_init`, `admin_menu`, `admin_notices`, `admin_head`, `admin_footer`, `admin_bar_menu`, `admin_enqueue_scripts`, `plugin_action_links_{file}` |
| Purging | `transition_post_status`, `post_updated`, `deleted_post`, `edit_term`, `delete_term`, `comment_post`, `transition_comment_status`, `update_option_sidebars_widgets`, `shutdown` |
| Front end | `template_redirect`, `wp_head`, `wp_enqueue_scripts`, `script_loader_tag`, `script_loader_src`, `style_loader_src`, `the_content`, `wp_get_attachment_url`, `wp_calculate_image_srcset`, `embed_oembed_discover`, `tiny_mce_plugins` |
| Updates | `site_transient_update_plugins`, `plugins_api`, `upgrader_process_complete` |
| Media | `wp_update_attachment_metadata`, `wp_delete_file` |
| Settings | `update_option_nlcac_settings` |
| Scheduling | `cron_schedules` |
| REST | `rest_api_init` |
| WooCommerce | `woocommerce_new_product`, `woocommerce_update_product`, `woocommerce_order_status_changed`, `woocommerce_product_set_stock`, `woocommerce_product_set_stock_status`, `woocommerce_variation_set_stock`, `woocommerce_variation_set_stock_status`, `wc_after_products_starting_sales`, `wc_after_products_ending_sales` |
🔴 **`site_transient_update_plugins` is the dangerous one.** The filter name
is built from the update host, so every plugin updating from
gitea.nolimit.dk lands on the same one and WordPress runs every callback for
every plugin. Answering unconditionally offers our zip to a sibling. The
guard is two halves and both are required — register with **3** arguments,
and return `$update` untouched unless `$plugin_file` is ours. Registering
with 2 leaves the argument unset and the guard silently does nothing, which
is the same shape of bug it exists to fix.
If you write a plugin that updates from the same host, apply the same guard.