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

5.9 KiB

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.