Files
thatguygriffandClaude Opus 4.8 51dd032668
CI / Tests (PHP 8.1) (pull_request) Successful in 47s
CI / Tests (PHP 8.2) (pull_request) Successful in 47s
CI / No Debug Code (pull_request) Successful in 3s
CI / Coding Standards (pull_request) Successful in 3m7s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m38s
CI / PHPStan (pull_request) Successful in 2m50s
CI / Build Plugin Zip (pull_request) Skipped
Show the Enable auto-updates toggle for the self-updater
WordPress only renders the "Enable auto-updates" toggle for a plugin that
appears in the update_plugins transient's response or no_update list, which
is what sets core's update-supported flag. UpdateChecker only populated the
response side (when a newer release existed), so between releases the plugin
was absent from the transient and the toggle never showed.

provideUpdate() now returns a no_update payload (installed version, empty
package) whenever no newer release is offered — including when the release
lookup fails — so the plugin stays in the transient and the toggle appears.
The response path (one-click and unattended updates) is unchanged.

Bumps to 1.1.1 so the fix ships to installed sites via the self-updater.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-24 08:46:31 -03:00

81 lines
3.7 KiB
Markdown

# Feature: Plugin Self-Update from Gitea Releases
## Overview
WordPress sites running this plugin receive updates directly from the Gitea
repository's releases — no wordpress.org listing and no manual zip uploads.
Publishing a release is the whole deploy: bump the version, merge to `main`,
tag `vX.Y.Z` in Gitea. Every site sees the update on its next check and can
install it with one click, or unattended if the site admin enables
auto-updates for the plugin.
## How It Works
### Release side (`.gitea/workflows/release.yml`)
Pushing a `v*` tag (including tags created through Gitea's "New Release" UI)
triggers the release workflow, which:
1. Fails if the tag does not match the `Version:` plugin header — a mismatch
would make sites see a phantom update forever, or never see a real one.
2. Runs the test suite.
3. Builds the distributable zip via `composer build` (`bin/build-zip.sh`):
a single top-level `unsupervised-schedular/` folder with a production
(no-dev) Composer autoloader.
4. Creates the release for the tag (or reuses one created via the UI) and
attaches the zip as a release asset. Versions containing a hyphen
(e.g. `1.2.3-rc.1`) are flagged as pre-releases.
The attached asset — not Gitea's auto-generated source archive — is the
update package. Source archives have the wrong top-level folder name and no
`vendor/` directory, so WordPress could not install them.
### Site side (`src/Update/UpdateChecker.php`)
The plugin header declares:
```
Update URI: https://git.unsupervised.ca/Unsupervised/unsupervised-scheduler
```
Since WP 5.8 that header both blocks wordpress.org from ever serving an
update for a same-slug plugin and makes core fire the
`update_plugins_git.unsupervised.ca` filter during update checks.
`UpdateChecker` (registered in `Plugin::boot()`) answers that filter:
1. Fetches `GET /api/v1/repos/Unsupervised/unsupervised-scheduler/releases/latest`
(anonymous — the repo is public). The `/latest` endpoint excludes drafts
and pre-releases, so `-rc` builds are never offered to sites.
2. Caches the result (including failures) in the
`us_schedular_latest_release` transient for 6 hours.
3. Strips the leading `v` from the tag and compares against `USC_VERSION`
with `version_compare`; PHP orders `1.0.0-rc.2 < 1.0.0` correctly.
4. When newer, returns the release's first `.zip` asset as the update
package. Core takes over from there: Plugins-screen notice, one-click
update, and WP-Cron auto-updates if enabled.
5. When not newer — the site is current, or the lookup failed — returns a
`no_update` payload (installed version, empty package). This keeps the
plugin in core's `update_plugins` transient so core's `update-supported`
flag stays set and the **Enable auto-updates** toggle shows on the
Plugins screen. Without it, an off-directory plugin is absent from the
transient between releases and the toggle never appears.
Any API failure, malformed response, or asset-less release degrades to
"no update available" (the `no_update` payload) — never an error surfaced
to the site, and never a lost auto-update toggle during a Gitea blip.
## Cutting a Release
1. Bump the version in `unsupervised-schedular.php` (both the `Version:`
header and the `USC_VERSION` constant) and merge to `main`.
2. Tag the merge commit `vX.Y.Z` — via Gitea's New Release UI or
`git tag vX.Y.Z && git push origin vX.Y.Z`.
3. The release workflow attaches the zip; sites pick the update up on their
next check (twice daily via cron, or immediately from
Dashboard → Updates → Check again).
## Classes
| Class | Responsibility |
|---|---|
| `Update\UpdateChecker` | Answers core's `update_plugins_{hostname}` filter from the Gitea releases API |
## Tests
- `tests/Unit/Update/UpdateCheckerTest.php`