# 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 `` 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.