2 Commits
Author SHA1 Message Date
Nolimit release c98ae99d95 Update the public pages to 0.3.2 2026-10-08 16:57:33 +00:00
Nolimit release 7aa7eb117b Update the public pages to 0.3.1 2026-10-07 08:10:26 +00:00
9 changed files with 272 additions and 8 deletions
+36 -8
View File
@@ -1,11 +1,39 @@
# Nolimit Cache — release feed
# Nolimit Cache
Built zips only. The source lives in `kw/nolimit-cache`, which is private.
![Nolimit Cache — the Overview screen](assets/hero.png)
This repository is **public on purpose**: every WordPress site running the
plugin asks it for updates, and it never sends credentials. A private feed
answers 404 and the update simply never appears — no error on the site, no
entry in the plugins list.
A page cache and front-end optimiser for WordPress and WooCommerce. It answers
a visitor's request from a file on disk before WordPress boots, and it knows
enough about WooCommerce not to do that to a customer holding a basket. WebP
and AVIF copies of your images, Redis object cache, preloading and a set of
health checks are in the same plugin — with **nothing leaving the site**. No
account, no licence check, no telemetry.
Nothing here is edited by hand. Each release is published by the tag job in
the source repository.
## Install
1. Download `nolimit-cache.zip` from the [latest release](https://gitea.nolimit.dk/kw/nolimit-cache-dist/releases/latest).
2. **Plugins → Add New → Upload Plugin**, then activate.
3. Open **Nolimit → Cache** and let the setup walk you through it — [the setup
guide](docs/SETUP.md) explains what it changes and how to tell it is working.
Later versions arrive in **Dashboard → Updates** like any other plugin.
## Documentation
- [Overview](docs/README.md) — what it does, and what it deliberately does not
- [Installing it, and telling whether it works](docs/SETUP.md) — the settings
that need a decision, images in WebP and AVIF, and how to check the cache is
really serving
- [Hooks and integration](docs/CONTRACT.md) — for developers: what the plugin
offers to other plugins and to your own code
## More of it
| | |
|---|---|
| ![Page cache settings](assets/screenshots/page-cache.png) | **Page cache.** How long a page lives, what is never cached whatever you set, and separate copies for phones or signed-in visitors only if you ask for them. |
| ![Images](assets/screenshots/images.png) | **Images.** WebP and AVIF copies written beside your JPEG and PNG files and offered to the browsers that understand them. Originals are never changed. |
| ![WooCommerce settings](assets/screenshots/woocommerce.png) | **WooCommerce.** The basket, checkout and account pages are never cached, and a product page is cleared when its stock or an order changes. |
| ![Tools](assets/screenshots/tools.png) | **Tools.** What it has installed in `wp-content`, written out plainly, and a button to write it again. |
Requires WordPress 6.0+ and PHP 7.4+. WooCommerce is optional.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

+91
View File
@@ -0,0 +1,91 @@
# 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.
+54
View File
@@ -0,0 +1,54 @@
# Nolimit Cache
A page cache and front-end optimiser for WordPress and WooCommerce. It answers
a visitor's request from a file on disk before WordPress boots, and it knows
enough about WooCommerce not to do that to a customer holding a basket.
It is one of a family of Nolimit plugins that share an admin menu and a
dashboard. [Hooks and integration](CONTRACT.md) lists what it offers to the
others, and to your own code.
## What it does
- **Page cache.** An `advanced-cache.php` drop-in serves a stored copy of a
page. This runs before the theme, the plugins and most of WordPress, which
is where the speed comes from and also why it has to be careful.
- **Object cache.** An optional `object-cache.php` drop-in backed by Redis.
- **Front-end optimisation.** Defers scripts, drops WordPress's emoji and
oEmbed machinery, and can rewrite asset URLs to a CDN.
- **Images.** Writes WebP (and optionally AVIF) copies beside JPEG and PNG
files and offers them with `<picture>`, so the page cache stays correct for
every browser. Originals are never touched.
- **Preloading.** Walks a queue of URLs on a five-minute cron so the first
real visitor does not pay for the first render.
- **Purging.** Clears the right pages when a post, a term, a comment or a
WooCommerce product or order changes.
- **Health checks and an assistant.** A set of signals about whether the cache
is actually working, and an ask-box on the admin screens.
## What it deliberately does not do
- **No minification or concatenation.** Those break themes in ways that are
expensive to debug and are better handled by a build step.
- **No CSS "critical path" extraction.** Same reason.
- **No telemetry of any kind.** Nothing is reported anywhere. The only
outbound requests are the update check against gitea.nolimit.dk and an AI
provider if, and only if, one has been configured with a key.
- **No account, no licence check, no phone-home.**
## Where things are
| | |
|---|---|
| Admin screen | **Nolimit → Cache** |
| Settings | one option, `nlcac_settings` |
| Engine config | `wp-content/cache/nolimit/config.php`, written from the settings |
| Drop-ins | `wp-content/advanced-cache.php`, `wp-content/object-cache.php` |
| REST namespace | `nlcac/v1` |
| Requires | WordPress 6.0, PHP 7.4. WooCommerce optional |
| Version | 0.3.2 |
## Reading order
1. [Installing it, and telling whether it works](SETUP.md)
2. [Hooks and integration](CONTRACT.md), for developers
+91
View File
@@ -0,0 +1,91 @@
# Installing it, and telling whether it works
## Install
The plugin updates itself from its release repository, but the **first**
install is manual, and on a copy older than 0.2.3 it has to be.
1. Download `nolimit-cache.zip` from the latest release at
`https://gitea.nolimit.dk/kw/nolimit-cache-dist/releases`.
2. Plugins → Add New → Upload Plugin → Activate.
3. After that, updates appear in the normal update row.
> **Below 0.2.3, do not trust the update row.** Versions before 0.2.3 could
> offer another Nolimit plugin's zip as their own update. Upgrading *to*
> 0.2.3 or later has to be done by hand for that reason; after that the update
> row is safe.
## Activation writes two things outside the plugin folder
- `wp-content/advanced-cache.php` — the drop-in. Without it nothing is served
from cache, however the settings look.
- `WP_CACHE` in `wp-config.php` — WordPress only loads the drop-in when this
constant is true.
If `wp-config.php` is not writable, the plugin says so on its own screen
rather than failing quietly. That is the single most common reason for "it is
switched on and nothing is cached".
## The settings that are a decision
Everything else has a defensible default. These four do not:
| Setting | Why it needs a decision |
|---|---|
| **Cache lifetime** | How stale a page may be. A shop with frequent price changes wants a short one; a mostly-static site can leave it long, because a change purges the affected pages anyway. |
| **Excluded URIs / cookies** | Anything that must never be served from a shared copy. The WooCommerce basket, checkout and account pages are already excluded and cannot be un-excluded. |
| **Object cache (Redis)** | Off unless there is a Redis to point it at. It replaces `object-cache.php`, so it interacts with anything else that wants that file. |
| **CDN rewriting** | Only if assets are genuinely served from the other host. Wrong here means missing images rather than slow ones. |
## Images: WebP and AVIF
**Nolimit → Cache → Images** writes WebP (and, if the server can, AVIF) copies
next to your JPEG and PNG files and offers them to browsers that understand
them. The originals are never touched, so switching it off, or deleting the
copies, leaves the site exactly as it was.
- Conversion runs in the background; the screen shows how many of the library
are done and how much space was saved.
- If an image changes size or position after switching on, pick the second
delivery option on the same screen. A few themes style images with selectors
that the extra `<picture>` element breaks.
## How to tell it is working
Do not read the settings page for this. Read these, in order.
### 1. The hit ratio, on **Nolimit → Cache**
The dashboard shows a hit ratio over the last 7 days and a count of stored
pages. **Two numbers produced by different routes**: stored pages are counted
by walking the disk, the ratio comes from a counter the engine writes before
WordPress boots. If pages are stored and the ratio says nothing has been
measured, those two disagree and the dashboard will say so explicitly rather
than showing a bare zero.
That distinction exists because a zero can mean two things, a quiet site or a
broken counter, and the dashboard should never leave you guessing which.
### 2. The response headers
```
curl -sI https://example.test/ | grep -i x-nolimit
```
A served-from-cache response carries the plugin's header. A logged-in request
or one with a basket cookie should **not**.
### 3. The health report
**Nolimit → Cache → Health** runs each signal and says which failed and why —
the drop-in missing, the cache directory not writable, `WP_CACHE` unset, the
drop-in pointing at a different copy of the plugin. Each is measured by doing
the thing (writing a file, reading the config the engine reads), not by
calling `is_writable()` and believing it.
## Turning it off
Deactivating removes the drop-in. If a drop-in is ever left behind — a
half-finished delete, a file permission problem — a site can end up serving
from a cache no plugin is managing. `wp-content/advanced-cache.php` is safe to
delete by hand; WordPress treats its absence as "no page cache".