From 5c8f1716a31e337a7b1cc571d037e19e38898e36 Mon Sep 17 00:00:00 2001 From: David Keane Date: Mon, 25 May 2026 09:09:40 +0100 Subject: [PATCH] =?UTF-8?q?release:=203.2.0=20=E2=86=92=203.3.0=20?= =?UTF-8?q?=E2=80=94=20self-hosted=20update=20checker=20(Gitea=20API)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WP Logbook is hosted on the author's own Gitea instance (git.davidtkeane.com), not on WordPress.org, so WP's built-in update flow doesn't see new releases. This release adds a Settings → Updates panel that polls Gitea's Releases API and reports whether a newer version is available. CHANGES - inc/wp-notes-updater.php: full rewrite of the previous broken stub (it had a hard require on a non-existent vendor path and was never included from the main plugin file anyway). - wp_notes_fetch_latest_release() hits /api/v1/repos///releases/latest, parses, normalises. Prefers a .zip asset attached to the release; falls back to Gitea's source-archive zip URL. - wp_notes_update_status() compares against WP_NOTES_VERSION and returns 'available' | 'up-to-date' | 'unknown' (the last when no release has been tagged yet — graceful first-run UX since the repo currently has zero tags). - New AJAX endpoint wp_notes_check_updates, capability-gated (manage_options) + nonce-protected. Force-refreshes the cache. - Settings page now renders the Updates panel via wp_notes_render_updates_panel() — current status text, "Check now" button, View on Gitea / View all releases quick links, manual-install instructions, and a Download .zip button + View release notes link when an update is detected. - wp-notes.php require_once chain now includes the updater file. CACHING - Successful fetches: 12h site transient. - Negative responses (404 = no releases yet): 1h so a freshly- tagged release shows up quickly. INSTALLATION FLOW (intentionally manual) The panel does NOT auto-install. Manual path printed in the panel: download .zip → deactivate → upload via Plugins → Add New → Upload → reactivate. Notes live in wp_options so they survive the upgrade. ALSO IN 3.3.0 - Section heading rename carried from the previous unreleased block: "Add New Note" → "New Log Entry"; "Notes Todo List:" → "Log entries". Row-level "note" labels intentionally unchanged. VERSION BUMP - wp-notes.php header 3.2.0 → 3.3.0 - WP_NOTES_VERSION constant 3.2.0 → 3.3.0 - About page version history leads with v3.3.0 as latest, demotes v3.2.0 to previous entry NOTES FOR FUTURE-CLAUDE - Gitea repo currently has ZERO release tags. First run will show "No releases tagged on the Gitea repo yet." Tag v3.2.0 / v3.3.0 on Gitea and the checker will start reporting versions. - Repo coordinates live in three constants at the top of inc/wp-notes-updater.php (WP_NOTES_GITEA_HOST, _OWNER, _REPO). Override via define() in wp-config.php if the repo ever moves. Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 59 +++++++++ inc/wp-notes-about.php | 6 +- inc/wp-notes-updater.php | 278 +++++++++++++++++++++++++++++++++------ wp-notes.php | 13 +- 4 files changed, 314 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bcc0436..4fabd32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,65 @@ Format: [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/) — versi ## [Unreleased] +--- + +## [3.3.0] — 2026-05-25 + +**New feature: self-hosted update checker.** WP Logbook is hosted on +the author's own Gitea instance (`git.davidtkeane.com`), not on +WordPress.org, so WordPress's built-in update flow doesn't see new +releases. This release adds a small **Updates** panel to the +Settings page that polls the Gitea Releases API and tells you when +there's a newer version waiting. + +### Added +- **`inc/wp-notes-updater.php`** — full rewrite of the previous + broken stub (which had a hard `require` on a non-existent vendor + path and was never included from the main plugin file anyway). +- **Settings → Updates panel** at the bottom of `Settings → WP + Logbook → Settings`. Shows the current status on load (cached), + with a **Check now** button that force-refreshes against the + Gitea API. +- **`wp_notes_fetch_latest_release()`** — hits + `/api/v1/repos///releases/latest` on Gitea, parses + the response, normalises into `{version, html_url, + download_url, body, published_at}`. Prefers a `.zip` asset + attached to the release; falls back to Gitea's source-archive + URL (`/archive/.zip`). +- **`wp_notes_update_status()`** — version-compares against + `WP_NOTES_VERSION` and returns one of `available`, + `up-to-date`, or `unknown` (the last when no release has been + tagged yet — graceful first-time UX). +- **AJAX endpoint `wp_notes_check_updates`** — capability gated + (`manage_options`) + nonce protected. Deletes the cache and + re-fetches. +- **Quick links** in the panel: *View on Gitea* and *View all + releases* — both open the Gitea web UI in a new tab. + +### Cached +- Successful release fetches: **12 hours** in a site transient. +- Negative responses (e.g. HTTP 404 = no releases tagged yet): + **1 hour** so a freshly-tagged release shows up quickly. + +### Installation flow (manual on purpose) +The panel does NOT auto-install. Manual path (printed in the +panel itself): *download the .zip → deactivate plugin → upload via +Plugins → Add New → Upload → reactivate*. Notes live in +`wp_options` so they survive the upgrade. + +### Notes for future-Claude / future-David +- The Gitea repo currently has **zero release tags** — so the very + first run of this checker will show *"No releases tagged on the + Gitea repo yet."* That's by design. Tag the v3.2.0 / v3.3.0 / v4 + releases on Gitea as we ship them and the checker will start + reporting versions on its own. +- The Gitea repo coordinates live in three constants at the top of + `inc/wp-notes-updater.php` (`WP_NOTES_GITEA_HOST`, `_OWNER`, + `_REPO`). Override-able via `define()` in `wp-config.php` if the + repo ever moves. + +### Also in this release — heading rename carried over from the unreleased block + ### Changed — Section headings on My Log page renamed to match the logbook framing - `Add New Note` → **`New Log Entry`** on the create form postbox. - `Notes Todo List:` → **`Log entries`** above the active/completed diff --git a/inc/wp-notes-about.php b/inc/wp-notes-about.php index b521636..b13bd17 100644 --- a/inc/wp-notes-about.php +++ b/inc/wp-notes-about.php @@ -125,7 +125,11 @@ function wp_notes_about_page() {

Version history

  • - v3.2.0 — 25 May 2026 latest
    + v3.3.0 — 25 May 2026 latest
    + Self-hosted update checker. Settings → Updates panel polls the Gitea repo via its JSON API, compares against the running version, and shows a download link when a new release is tagged. Includes “View on Gitea” and “View all releases” quick links. No auto-install — manual download keeps things safe. +
  • +
  • + v3.2.0 — 25 May 2026
    Renamed to WP Logbook to match what the plugin is actually becoming — a work logbook for freelancers and students, not just a notes pad. Menu submenu renamed My NotesMy Log. Internal storage and slugs unchanged, no data migration.
  • diff --git a/inc/wp-notes-updater.php b/inc/wp-notes-updater.php index 8d0d3bf..c20fcc3 100644 --- a/inc/wp-notes-updater.php +++ b/inc/wp-notes-updater.php @@ -1,51 +1,251 @@ //releases/latest endpoint + * and return a normalised array, or null on irrecoverable error. + * + * Cached for 12h. Negative responses (404 = no releases yet) cached + * for 1h so a freshly-tagged release shows up quickly. + * + * @param bool $force_refresh skip the transient cache. + * @return array|null { version, html_url, download_url, body, published_at, error_code? } + */ +function wp_notes_fetch_latest_release( $force_refresh = false ) { + $cache_key = 'wp_notes_gitea_latest'; + + if ( ! $force_refresh ) { + $cached = get_site_transient( $cache_key ); + if ( is_array( $cached ) ) { return $cached; } } - $myUpdateChecker = YahnisElsts\PluginUpdateChecker\v5\PucFactory::buildUpdateChecker( - 'https://github.com/your-username/wp-notes/', // Change this to your GitHub repository - __FILE__, - 'wp-notes' + + $api_url = WP_NOTES_GITEA_HOST . '/api/v1/repos/' . WP_NOTES_GITEA_OWNER . '/' . WP_NOTES_GITEA_REPO . '/releases/latest'; + $response = wp_remote_get( $api_url, array( 'timeout' => 8 ) ); + + if ( is_wp_error( $response ) ) { + return null; + } + + $code = (int) wp_remote_retrieve_response_code( $response ); + + // 404 = repo has no releases yet, OR private. Cache briefly and surface + // a friendly status to the UI. + if ( $code !== 200 ) { + $info = array( + 'version' => null, + 'html_url' => wp_notes_gitea_releases_url(), + 'download_url' => null, + 'body' => '', + 'published_at' => null, + 'error_code' => $code, + ); + set_site_transient( $cache_key, $info, HOUR_IN_SECONDS ); + return $info; + } + + $body = json_decode( wp_remote_retrieve_body( $response ), true ); + if ( ! is_array( $body ) || empty( $body['tag_name'] ) ) { + return null; + } + + // "v3.3.0" → "3.3.0" so version_compare() against WP_NOTES_VERSION works cleanly. + $version = ltrim( (string) $body['tag_name'], 'vV' ); + + // Prefer an explicit .zip asset attached to the release; fall back to + // Gitea's source-archive zip URL pattern. + $download_url = null; + if ( ! empty( $body['assets'] ) && is_array( $body['assets'] ) ) { + foreach ( $body['assets'] as $asset ) { + if ( isset( $asset['name'], $asset['browser_download_url'] ) + && substr( strtolower( $asset['name'] ), -4 ) === '.zip' ) { + $download_url = $asset['browser_download_url']; + break; + } + } + } + if ( ! $download_url ) { + $download_url = wp_notes_gitea_repo_url() . '/archive/' . rawurlencode( $body['tag_name'] ) . '.zip'; + } + + $info = array( + 'version' => $version, + 'html_url' => isset( $body['html_url'] ) ? esc_url_raw( $body['html_url'] ) : '', + 'download_url' => esc_url_raw( $download_url ), + 'body' => isset( $body['body'] ) ? wp_strip_all_tags( $body['body'] ) : '', + 'published_at' => isset( $body['published_at'] ) ? $body['published_at'] : null, ); - // Optional: Set the branch that contains the stable release - $myUpdateChecker->setBranch('main'); + set_site_transient( $cache_key, $info, 12 * HOUR_IN_SECONDS ); + return $info; } -add_action('init', 'wp_notes_setup_updater'); -// Add update checking functionality -function wp_notes_check_for_updates() { - // Check if the 'check_update' GET parameter is set and the user has the capability to manage plugin options. - if (isset($_GET['check_update']) && $_GET['check_update'] === '1' && current_user_can('manage_a_wp_notes_options')) { - // Verify the nonce. The nonce name 'a_wp_notes_check_update_nonce' and action 'a_wp_notes_check_update_action' - // should match what's generated in admin-bar.php. - if (!isset($_GET['a_wp_notes_check_update_nonce']) || !wp_verify_nonce(sanitize_key($_GET['a_wp_notes_check_update_nonce']), 'a_wp_notes_check_update_action')) { - wp_die('Nonce verification failed!', 'Error', array('response' => 403)); +/** + * Compare a fetched release against WP_NOTES_VERSION and return a + * status payload suitable for the Settings UI. + * + * @return array { status: 'available'|'up-to-date'|'unknown', current, latest?, message, ... } + */ +function wp_notes_update_status( $force_refresh = false ) { + $current = defined( 'WP_NOTES_VERSION' ) ? WP_NOTES_VERSION : '0.0.0'; + $latest = wp_notes_fetch_latest_release( $force_refresh ); + + if ( ! $latest || empty( $latest['version'] ) ) { + $msg = 'No releases tagged on the Gitea repo yet. Once a release is published, this check will start returning a version.'; + if ( $latest && ! empty( $latest['error_code'] ) && (int) $latest['error_code'] !== 404 ) { + $msg = 'Could not reach Gitea (HTTP ' . (int) $latest['error_code'] . '). Try again in a few minutes.'; } - // Trigger a manual update check - wp_clean_plugins_cache(); - wp_update_plugins(); - - add_settings_error( - 'wp_notes', - 'update_check', - 'Update check completed.', - 'updated' + return array( + 'status' => 'unknown', + 'current' => $current, + 'message' => $msg, + 'repo_url' => wp_notes_gitea_repo_url(), ); } -} -add_action('admin_init', 'wp_notes_check_for_updates'); -// Note: For the PucFactory::buildUpdateChecker, the second argument `__FILE__` currently refers to -// `wp-notes-updater.php`. For the library to work correctly, this should typically be the path -// to your main plugin file (e.g., `WP_PLUGIN_DIR . '/a-wp-notes/a-wp-notes.php'`). -// This is a setup detail for the update checker rather than a direct security fix from this review. + if ( version_compare( $latest['version'], $current, '>' ) ) { + return array( + 'status' => 'available', + 'current' => $current, + 'latest' => $latest['version'], + 'html_url' => $latest['html_url'], + 'download_url' => $latest['download_url'], + 'published_at' => $latest['published_at'], + 'body' => $latest['body'], + 'message' => sprintf( 'A new version (v%s) is available — you are on v%s.', $latest['version'], $current ), + ); + } + + return array( + 'status' => 'up-to-date', + 'current' => $current, + 'latest' => $latest['version'], + 'message' => sprintf( 'You are up to date (v%s).', $current ), + 'repo_url' => wp_notes_gitea_repo_url(), + ); +} + +/** + * AJAX: force a fresh check, return the status payload. + */ +add_action( 'wp_ajax_wp_notes_check_updates', 'wp_notes_ajax_check_updates' ); +function wp_notes_ajax_check_updates() { + if ( ! current_user_can( 'manage_options' ) ) { + wp_send_json_error( 'Insufficient permissions.', 403 ); + } + check_ajax_referer( 'wp_notes_check_updates', 'nonce' ); + + delete_site_transient( 'wp_notes_gitea_latest' ); + wp_send_json_success( wp_notes_update_status( true ) ); +} + +/** + * Render the "Updates" panel on the Settings page. Called from the + * settings render function via wp_notes_settings_page(). + */ +function wp_notes_render_updates_panel() { + $status = wp_notes_update_status( false ); // use the cached value on initial load + $nonce = wp_create_nonce( 'wp_notes_check_updates' ); + $repo_url = wp_notes_gitea_repo_url(); + $rel_url = wp_notes_gitea_releases_url(); + ?> +
    +

    Updates

    +

    + WP Logbook is self-hosted on Gitea. Click Check now to ask the repo whether there's a newer release than the one you're running. +

    + +

    + Status: + + +
    + + Download v (.zip) + + + View release notes → + + +

    + +

    + + View on Gitea + View all releases +

    +

    + Manual update path: download the .zip, deactivate the plugin in WordPress, upload via Plugins → Add New → Upload, reactivate. Your notes are stored in wp_options and survive the upgrade. +

    +
    + + + + +