Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5c8f1716a3 | |||
| eeaf8a0f87 |
@@ -9,6 +9,75 @@ Format: [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/) — versi
|
||||
|
||||
---
|
||||
|
||||
## [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/<owner>/<repo>/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/<tag>.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
|
||||
lists.
|
||||
- Row-level labels intentionally **unchanged** — buttons like *Add
|
||||
Note* / *Mark as Done* / empty-state *No active notes found* still
|
||||
say "note" because that's the unit-of-work term in the existing
|
||||
data model and UI. The headings are brand-y; the row-level strings
|
||||
are functional. Two different concerns, different rename scope.
|
||||
|
||||
---
|
||||
|
||||
## [3.2.0] — 2026-05-25
|
||||
|
||||
**Plugin rebrand: `A-WP-Notes` → `WP Logbook`.** Bundles the rename
|
||||
|
||||
@@ -125,7 +125,11 @@ function wp_notes_about_page() {
|
||||
<h2>Version history</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<span class="ver">v3.2.0</span> — 25 May 2026 <span class="latest">latest</span><br>
|
||||
<span class="ver">v3.3.0</span> — 25 May 2026 <span class="latest">latest</span><br>
|
||||
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.
|
||||
</li>
|
||||
<li>
|
||||
<span class="ver">v3.2.0</span> — 25 May 2026<br>
|
||||
Renamed to <strong>WP Logbook</strong> to match what the plugin is actually becoming — a work logbook for freelancers and students, not just a notes pad. Menu submenu renamed <em>My Notes</em> → <em>My Log</em>. Internal storage and slugs unchanged, no data migration.
|
||||
</li>
|
||||
<li>
|
||||
|
||||
+239
-39
@@ -1,51 +1,251 @@
|
||||
<?php
|
||||
// Add this to your main plugin file
|
||||
require plugin_dir_path(__FILE__) . 'vendor/plugin-update-checker/plugin-update-checker.php';
|
||||
/**
|
||||
* WP Logbook — self-hosted update checker against the Gitea repo.
|
||||
*
|
||||
* Polls the Gitea Releases API for the latest tagged release and
|
||||
* compares its tag (e.g. "v3.3.0") against WP_NOTES_VERSION.
|
||||
* Renders the result on the Settings page. NO auto-install — users
|
||||
* download manually from Gitea. Safer for a self-hosted plugin and
|
||||
* less surprising for admins.
|
||||
*
|
||||
* Result cached for 12h in a site transient so we don't hammer the
|
||||
* Gitea host on every settings load. A manual "Check now" button
|
||||
* force-refreshes the cache.
|
||||
*/
|
||||
|
||||
// Include update checker - Assuming the 'vendor' directory is at the plugin root
|
||||
// WP_NOTES_PATH is defined in your main plugin file (wp-notes.php)
|
||||
// and points to the plugin's root directory.
|
||||
require WP_NOTES_PATH . 'vendor/plugin-update-checker/plugin-update-checker.php';
|
||||
if ( ! defined( 'ABSPATH' ) ) { exit; }
|
||||
|
||||
function wp_notes_setup_updater() {
|
||||
if (!class_exists('YahnisElsts\PluginUpdateChecker\v5\PucFactory')) {
|
||||
return;
|
||||
// Gitea repo coordinates — change here if the repo ever moves.
|
||||
if ( ! defined( 'WP_NOTES_GITEA_HOST' ) ) { define( 'WP_NOTES_GITEA_HOST', 'https://git.davidtkeane.com' ); }
|
||||
if ( ! defined( 'WP_NOTES_GITEA_OWNER' ) ) { define( 'WP_NOTES_GITEA_OWNER', 'ranger' ); }
|
||||
if ( ! defined( 'WP_NOTES_GITEA_REPO' ) ) { define( 'WP_NOTES_GITEA_REPO', 'a-wp-notes-v3' ); }
|
||||
|
||||
/**
|
||||
* Convenience: full web URL of the repo / its releases page.
|
||||
*/
|
||||
function wp_notes_gitea_repo_url() {
|
||||
return WP_NOTES_GITEA_HOST . '/' . WP_NOTES_GITEA_OWNER . '/' . WP_NOTES_GITEA_REPO;
|
||||
}
|
||||
function wp_notes_gitea_releases_url() {
|
||||
return wp_notes_gitea_repo_url() . '/releases';
|
||||
}
|
||||
|
||||
/**
|
||||
* Hit Gitea's /api/v1/repos/<owner>/<repo>/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();
|
||||
?>
|
||||
<div class="wp-notes-updates" style="max-width:720px; margin-top:24px; padding:18px 20px; background:#fff; border:1px solid #ccd0d4; border-radius:4px;">
|
||||
<h2 style="margin-top:0;">Updates</h2>
|
||||
<p style="margin:0 0 12px;">
|
||||
WP Logbook is self-hosted on Gitea. Click <strong>Check now</strong> to ask the repo whether there's a newer release than the one you're running.
|
||||
</p>
|
||||
|
||||
<p id="wp-notes-update-status" style="margin:0 0 12px;">
|
||||
<strong>Status:</strong>
|
||||
<span id="wp-notes-update-status-text"><?php echo esc_html( $status['message'] ); ?></span>
|
||||
<?php if ( $status['status'] === 'available' && ! empty( $status['download_url'] ) ) : ?>
|
||||
<br>
|
||||
<a href="<?php echo esc_url( $status['download_url'] ); ?>" class="button button-primary" style="margin-top:8px;">
|
||||
Download v<?php echo esc_html( $status['latest'] ); ?> (.zip)
|
||||
</a>
|
||||
<?php if ( ! empty( $status['html_url'] ) ) : ?>
|
||||
<a href="<?php echo esc_url( $status['html_url'] ); ?>" target="_blank" rel="noopener" style="margin-left:8px;">View release notes →</a>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</p>
|
||||
|
||||
<p style="margin:0 0 4px;">
|
||||
<button type="button" id="wp-notes-check-updates-btn" class="button" data-nonce="<?php echo esc_attr( $nonce ); ?>">
|
||||
↻ Check now
|
||||
</button>
|
||||
<a href="<?php echo esc_url( $repo_url ); ?>" target="_blank" rel="noopener" class="button" style="margin-left:6px;">View on Gitea</a>
|
||||
<a href="<?php echo esc_url( $rel_url ); ?>" target="_blank" rel="noopener" class="button" style="margin-left:6px;">View all releases</a>
|
||||
</p>
|
||||
<p style="margin:10px 0 0; color:#646970; font-size:12px;">
|
||||
Manual update path: download the .zip, deactivate the plugin in WordPress, upload via Plugins → Add New → Upload, reactivate. Your notes are stored in <code>wp_options</code> and survive the upgrade.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var btn = document.getElementById('wp-notes-check-updates-btn');
|
||||
var statusText = document.getElementById('wp-notes-update-status-text');
|
||||
if (!btn || !statusText) { return; }
|
||||
|
||||
btn.addEventListener('click', function () {
|
||||
var nonce = btn.getAttribute('data-nonce');
|
||||
btn.disabled = true;
|
||||
var orig = btn.textContent;
|
||||
btn.textContent = '↻ Checking…';
|
||||
statusText.textContent = 'Asking Gitea…';
|
||||
|
||||
var fd = new FormData();
|
||||
fd.append('action', 'wp_notes_check_updates');
|
||||
fd.append('nonce', nonce);
|
||||
|
||||
fetch(ajaxurl, { method: 'POST', credentials: 'same-origin', body: fd })
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (res) {
|
||||
if (!res || !res.success) {
|
||||
statusText.textContent = (res && res.data) ? String(res.data) : 'Check failed.';
|
||||
} else {
|
||||
statusText.textContent = res.data.message || 'Check complete.';
|
||||
if (res.data.status === 'available' && res.data.download_url) {
|
||||
// Soft reload so the download / release-notes links render with the fresh data.
|
||||
setTimeout(function () { window.location.reload(); }, 400);
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(function () { statusText.textContent = 'Network error — try again in a moment.'; })
|
||||
.finally(function () {
|
||||
btn.disabled = false;
|
||||
btn.textContent = orig;
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
|
||||
+13
-4
@@ -5,7 +5,7 @@
|
||||
* Plugin Name: WP Logbook
|
||||
* Plugin URI: https://icanhelp.ie/wp-notes
|
||||
* Description: A lightweight task & logbook plugin for WordPress. Log your daily work, mark tasks done, and keep a tidy record inside the dashboard. Perfect for freelancers showing clients what's been delivered and students proving work to teachers.
|
||||
* Version: 3.2.0
|
||||
* Version: 3.3.0
|
||||
* Requires at least: 5.0
|
||||
* Requires PHP: 7.2
|
||||
* Author: IR240474
|
||||
@@ -33,7 +33,7 @@ if (!isset($wp_notes_init)) {
|
||||
$wp_notes_init = true;
|
||||
|
||||
// Plugin Constants
|
||||
if (!defined('WP_NOTES_VERSION')) define('WP_NOTES_VERSION', '3.2.0');
|
||||
if (!defined('WP_NOTES_VERSION')) define('WP_NOTES_VERSION', '3.3.0');
|
||||
if (!defined('WP_NOTES_FILE')) define('WP_NOTES_FILE', __FILE__);
|
||||
if (!defined('WP_NOTES_PATH')) define('WP_NOTES_PATH', plugin_dir_path(__FILE__));
|
||||
if (!defined('WP_NOTES_URL')) define('WP_NOTES_URL', plugin_dir_url(__FILE__));
|
||||
@@ -162,6 +162,7 @@ if (defined('WP_NOTES_PATH')) {
|
||||
require_once WP_NOTES_PATH . 'inc/wp-notes-feedback.php';
|
||||
require_once WP_NOTES_PATH . 'inc/wp-notes-display.php';
|
||||
require_once WP_NOTES_PATH . 'inc/wp-notes-styles.php';
|
||||
require_once WP_NOTES_PATH . 'inc/wp-notes-updater.php';
|
||||
}
|
||||
|
||||
|
||||
@@ -298,6 +299,14 @@ function wp_notes_settings_page() {
|
||||
submit_button();
|
||||
?>
|
||||
</form>
|
||||
|
||||
<?php
|
||||
// Updates panel — checks the Gitea repo for a newer release.
|
||||
// Defined in inc/wp-notes-updater.php.
|
||||
if ( function_exists( 'wp_notes_render_updates_panel' ) ) {
|
||||
wp_notes_render_updates_panel();
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
@@ -843,7 +852,7 @@ function wp_notes_page_callback() {
|
||||
<!-- Note Creation Form with WordPress Admin Styling -->
|
||||
<div class="postbox">
|
||||
<div class="postbox-header">
|
||||
<h2 class="hndle">Add New Note</h2>
|
||||
<h2 class="hndle">New Log Entry</h2>
|
||||
</div>
|
||||
<div class="inside">
|
||||
<form method="post" class="wp-notes-form">
|
||||
@@ -995,7 +1004,7 @@ function wp_notes_page_callback() {
|
||||
</style>
|
||||
|
||||
<!-- Notes List -->
|
||||
<h2>Notes Todo List:</h2>
|
||||
<h2>Log entries</h2>
|
||||
<!-- Active Notes List -->
|
||||
<div class="wp-notes-active" id="active-notes">
|
||||
<?php wp_notes_display_notes('active'); ?>
|
||||
|
||||
Reference in New Issue
Block a user