From 48e97862a690e50a6aa3ce135c1ef1004b6e110d Mon Sep 17 00:00:00 2001 From: David Keane Date: Mon, 25 May 2026 10:23:57 +0100 Subject: [PATCH] =?UTF-8?q?chore:=20initial=20commit=20=E2=80=94=20Buddy?= =?UTF-8?q?=20v0.1.0=20(Phase=20A=20complete)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Buddy is born. First commit of a new standalone WordPress plugin — the spiritual successor to the tamagotchi that once lived inside A-WP-Notes v1.1.5 (gracefully retired). Rebuilt from scratch with all the v3-discipline lessons baked in from day one. PHASE A — pet exists - Dashboard widget at WP Admin → Dashboard showing SVG character + name + mood label + four stats bars. - Dedicated admin page at WP Admin → Buddy → My Buddy (bigger view). - About page with side-by-side intro + plain-prose cards (Logbook About-page pattern carried forward). - Settings page with name-rename form + Updates panel. - Per-user state in user_meta key buddy_state (each WP admin gets their own pet, no shared state). - Inline SVG sprite renderer with three mood tones (happy/neutral/ sad) and three sizes (sm/md/lg). CSS keyframe animations: bobbing + periodic blinking. Zero image files. - Self-hosted update checker wired up from commit 1, ported from Logbook v3.3.5: /releases/latest with /tags?limit=1 fallback, 12h success cache / 1h negative cache. UI on Settings page. - dashicons-pets admin-menu icon — literal paw-print, brand match. ARCHITECTURE LOCKED FROM COMMIT 1 - Single-word brand name "Buddy" — no WP prefix, no future rebrand. - Public GPL v2+ Gitea repo (ranger/a-buddy). - Constants prefix BUDDY_*, function prefix buddy_*, text domain buddy. Clean naming throughout — none of Logbook's wp-notes-* historical-artifact baggage. - Single H1 per admin page, no nested toggle boxes, no duplicate sections — Tier-1 discipline carried forward from Logbook. - All assets local (inline SVG, plain CSS), no third-party CDN, no Gravatar-style external pings. NOT IN THIS RELEASE (planned) - Phase B — Feed/Play/Clean/Sleep interactions + cooldown timers. - Phase C — WP-cron decay + "Buddy is hungry" dismissible notices (port the persistent-dismissal pattern from Logbook). - Phase D — Multiple species (dog, dragon, sprite), per-species personality phrases. - Phase E — Site-health hook: pet stats react to wp_get_site_health() results. The killer feature. - Phase F — Pro tier (€2.99 lifetime) with custom skins + multi-pet. Co-Authored-By: Claude Sonnet 4.6 --- .gitignore | 14 +++ CHANGELOG.md | 38 +++++++ README.md | 47 ++++++++ assets/css/buddy.css | 153 +++++++++++++++++++++++++ buddy.php | 121 ++++++++++++++++++++ inc/about.php | 113 ++++++++++++++++++ inc/admin-page.php | 69 +++++++++++ inc/dashboard-widget.php | 81 +++++++++++++ inc/settings.php | 77 +++++++++++++ inc/sprite.php | 70 ++++++++++++ inc/state.php | 113 ++++++++++++++++++ inc/updater.php | 239 +++++++++++++++++++++++++++++++++++++++ 12 files changed, 1135 insertions(+) create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 README.md create mode 100644 assets/css/buddy.css create mode 100644 buddy.php create mode 100644 inc/about.php create mode 100644 inc/admin-page.php create mode 100644 inc/dashboard-widget.php create mode 100644 inc/settings.php create mode 100644 inc/sprite.php create mode 100644 inc/state.php create mode 100644 inc/updater.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ffcf6ad --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +# macOS +.DS_Store + +# Editor +.vscode/ +.idea/ +*.swp + +# Composer (not used today, but reserved if we ever add server-side libs) +vendor/ +composer.lock + +# Build artifacts +*.zip diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..90cc6e8 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,38 @@ +# Changelog + +All notable changes to **Buddy** are documented here. +Format: [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/) — versioning: [SemVer](https://semver.org/). + +--- + +## [Unreleased] + +--- + +## [0.1.0] — 2026-05-25 + +**Buddy is born.** First release of a new standalone WordPress plugin extracted-and-rebuilt from the tamagotchi feature that once lived inside A-WP-Notes v1.1.5 (now gracefully retired). Buddy stands on its own as a focused, charming companion plugin for the WordPress dashboard. + +### Added — Phase A complete (pet exists) +- **Dashboard widget** at WP Admin → Dashboard showing the SVG character, the pet's name, a mood label ("Thriving" / "Content" / "Okay" / "Hungry" / "Distressed"), and four stat bars (Hunger / Happiness / Health / Energy). +- **Dedicated admin page** at WP Admin → Buddy → My Buddy showing the same data in a larger format with the pet's age and a placeholder note describing what's coming next. +- **About page** at WP Admin → Buddy → About with the side-by-side intro pattern, "What Buddy does" / "Who Buddy is for" / "Version history" cards, and a link to this CHANGELOG.md. +- **Settings page** at WP Admin → Buddy → Settings with a name-rename form and the Updates panel. +- **Per-user state storage** via `user_meta` (key: `buddy_state`). Each WP admin gets their own Buddy with its own name, species, and stats. +- **SVG sprite renderer** with three mood tones (happy / neutral / sad) and three sizes (sm / md / lg). Pure inline SVG with CSS keyframe animations (bobbing motion + periodic blinking). No GIFs, no sprite sheets, no image files for the character. +- **Self-hosted update checker** wired up to the Gitea repo from commit 1. Polls `/api/v1/repos/ranger/a-buddy/releases/latest` with a `/tags?limit=1` fallback. 12h success cache, 1h negative cache. UI surface lives on the Settings page. +- **Custom admin-menu icon** (`dashicons-pets`, a paw print) reinforcing the pet identity. + +### Architecture (locked from day one) +- **Single-word brand name `Buddy`** — no "WP" prefix, no marketplace trademark hurdle. +- **Public GPL v2+ Gitea repo** at `ranger/a-buddy` on `git.davidtkeane.com`. +- **All persistent notice dismissals** to use `user_meta` + AJAX pattern (port from Logbook when first notice appears). +- **CSS-only animations, all assets local** — bundle stays small. +- **Single H1 per admin page**, no nested toggle boxes, no duplicate sections — Tier-1 discipline carried forward from Logbook. + +### Not in this release (planned) +- Phase B — Feed / Play / Clean / Sleep interactions with cooldown timers. +- Phase C — WP-cron stat decay, "Buddy is hungry" dismissible admin notices. +- Phase D — Multiple species (dog, dragon, sprite), per-species personality phrases. +- Phase E — Site-health integration: pet stats react to `wp_get_site_health()` results, outdated plugins drain health, published posts feed hunger, cleared spam boosts happiness. +- Phase F — Pro tier with custom skins, multi-pet farm, social visits. diff --git a/README.md b/README.md new file mode 100644 index 0000000..8b52ab3 --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# 🐾 Buddy + +> A friendly little companion that lives in your WordPress dashboard. + +Adopt a small virtual pet that lives in your WordPress admin. Right now Buddy just exists — they bob, they blink, they show their mood. As the plugin grows, their stats will reflect how well you take care of your WordPress site itself: published posts feed them, outdated plugins make them sick, clearing spam makes them happy. + +**Gamifies WordPress maintenance with a bit of charm.** + +--- + +## What this is + +- A standalone WordPress plugin, GPL v2+ licensed. +- The spiritual successor to the tamagotchi feature that once lived inside A-WP-Notes v1.1.5 (now gracefully retired). Rebuilt from scratch, with all the v3-discipline lessons baked in from day one. +- Sister plugin to [Logbook](https://git.davidtkeane.com/ranger/a-logbook) in the RangerHQ plugin family. Logbook is *work*. Buddy is *play*. + +## What it does + +- Dashboard widget shows your Buddy with name, mood, and four stats bars. +- Dedicated Buddy admin page for the bigger view. +- Settings page where you can rename your Buddy and check for updates. +- Each WP admin gets their own Buddy stored per-user in `user_meta` — nothing leaves your site's database. + +## What it doesn't do (yet) + +- No interactions — feed / play / clean / sleep coming in Phase B. +- No time-based stat decay yet — Phase C. +- Only one species available right now — Phase D will add dog, dragon, sprite, etc. +- The killer feature — having Buddy's stats reflect your actual WP site health — is Phase E. + +See [CHANGELOG.md](CHANGELOG.md) for the full version history and roadmap. + +## Install + +1. Download the latest [release zip](https://git.davidtkeane.com/ranger/a-buddy/releases) from Gitea. +2. WordPress admin → Plugins → Add New → Upload Plugin → choose the zip → Install Now → Activate. +3. Find Buddy in the admin sidebar (paw-print icon). + +The plugin self-checks for updates via the Gitea repo — see Settings → Buddy → Settings → Updates. + +## Project pace + +This is a **side project** built at a side-project pace. No commercial pressure, no release deadlines. Issues and feedback welcome via Gitea. + +## Licence + +GPL v2 or later — see [LICENSE](https://www.gnu.org/licenses/old-licenses/gpl-2.0.html). diff --git a/assets/css/buddy.css b/assets/css/buddy.css new file mode 100644 index 0000000..4e4c685 --- /dev/null +++ b/assets/css/buddy.css @@ -0,0 +1,153 @@ +/* + * Buddy plugin styles. + * Scoped via .buddy-* class names so we don't bleed into the WP admin. + * No external fonts, no @import, no images — just inline-SVG + plain CSS. + */ + +/* ── Sprite (the character) ──────────────────────────────────────── */ + +.buddy-sprite { + display: block; + user-select: none; + animation: buddyBob 4s ease-in-out infinite; +} +.buddy-sprite--sm { width: 64px; height: 64px; } +.buddy-sprite--md { width: 96px; height: 96px; } +.buddy-sprite--lg { width: 160px; height: 160px; } + +/* Eyes blink every ~5s. The fast-step keyframes simulate a quick blink. */ +.buddy-sprite__eye { + transform-origin: center; + animation: buddyBlink 5s infinite; +} +.buddy-sprite--sad .buddy-sprite__eye { + animation-duration: 7s; /* sadder Buddy blinks slower */ +} + +@keyframes buddyBob { + 0%, 100% { transform: translateY(0); } + 50% { transform: translateY(-4px); } +} +@keyframes buddyBlink { + 0%, 92%, 100% { transform: scaleY(1); } + 94%, 98% { transform: scaleY(0.1); } +} + +/* ── Dashboard widget ───────────────────────────────────────────── */ + +.buddy-widget { + display: flex; + gap: 16px; + align-items: center; +} +.buddy-widget__pet { flex: 0 0 auto; } +.buddy-widget__info { flex: 1 1 auto; min-width: 0; } +.buddy-widget__name { + font-size: 16px; + font-weight: 600; + margin-bottom: 8px; +} +.buddy-widget__mood { + display: inline-block; + margin-left: 6px; + padding: 2px 8px; + font-size: 11px; + font-weight: 500; + border-radius: 9px; + vertical-align: middle; +} +.buddy-widget__mood--happy { background:#e8f5ea; color:#1b6f2d; } +.buddy-widget__mood--neutral { background:#f0f0f1; color:#3c434a; } +.buddy-widget__mood--sad { background:#fcf0f1; color:#8a2424; } + +.buddy-widget__cta { margin: 10px 0 0; } + +/* ── Stats bars ─────────────────────────────────────────────────── */ + +.buddy-stats { + margin: 0; + padding: 0; + list-style: none; +} +.buddy-stat { + display: grid; + grid-template-columns: 22px 80px 1fr 36px; + align-items: center; + gap: 8px; + margin: 4px 0; +} +.buddy-stat__icon { text-align: center; font-size: 14px; } +.buddy-stat__label { font-size: 12px; color: #3c434a; } +.buddy-stat__bar { + height: 8px; + background: #f0f0f1; + border-radius: 4px; + overflow: hidden; + position: relative; +} +.buddy-stat__fill { + display: block; + height: 100%; + background: linear-gradient(90deg, #4ade80, #22c55e); + border-radius: 4px; + transition: width 0.4s ease; +} +.buddy-stat__fill--low { + background: linear-gradient(90deg, #f97316, #ef4444); +} +.buddy-stat__num { + text-align: right; + font-size: 12px; + color: #646970; + font-variant-numeric: tabular-nums; +} + +/* ── Main admin page layout ─────────────────────────────────────── */ + +.buddy-main { + display: grid; + grid-template-columns: 280px 1fr; + gap: 22px; + margin-top: 20px; +} +@media (max-width: 782px) { + .buddy-main { grid-template-columns: 1fr; } +} + +.buddy-main__card { + background: #fff; + border: 1px solid #ccd0d4; + border-radius: 4px; + padding: 22px; +} +.buddy-main__card--pet { + text-align: center; +} +.buddy-main__name { + font-size: 20px; + font-weight: 600; + margin: 14px 0 4px; +} +.buddy-main__mood { + display: inline-block; + margin-left: 8px; + padding: 2px 9px; + font-size: 12px; + font-weight: 500; + border-radius: 10px; + vertical-align: middle; +} +.buddy-main__mood--happy { background:#e8f5ea; color:#1b6f2d; } +.buddy-main__mood--neutral { background:#f0f0f1; color:#3c434a; } +.buddy-main__mood--sad { background:#fcf0f1; color:#8a2424; } + +.buddy-main__age { color: #646970; font-size: 13px; margin: 0; } +.buddy-main__note { + margin-top: 14px; + padding: 10px 12px; + background: #f0f6fc; + border-left: 3px solid #2271b1; + color: #135e96; + font-size: 13px; + border-radius: 0 4px 4px 0; +} diff --git a/buddy.php b/buddy.php new file mode 100644 index 0000000..6b004d0 --- /dev/null +++ b/buddy.php @@ -0,0 +1,121 @@ + + + +
+

+ v +
+
+ +
+
+
+ +
+
+

Buddy v

+

A friendly little companion that lives in your WordPress dashboard. The idea: keep Buddy's stats up, eventually Buddy's mood will reflect how well you're taking care of your site itself. Gamifies WordPress maintenance with a bit of charm.

+

+ +

+
+
+ +
+

+

+

+
+ +
+

+
    +
  • +
  • +
  • +
  • +
+
+ +
+

+
    +
  • + v0.1.0 — 25 May 2026 latest
    + +
  • +
+ + + +
+
+ +
+

🐾

+ v +
+ +

+ + +

+ +
+
+ +

+ + + + +

+

+ +

+
+ +
+

+ +

+ +

+
+
+
+ +
+
+ +
+
+
+ + + + +
+ +

+ + + +

+
+
+ array( 'label' => __( 'Hunger', 'buddy' ), 'icon' => '🍎' ), + 'happiness' => array( 'label' => __( 'Happiness', 'buddy' ), 'icon' => '😊' ), + 'health' => array( 'label' => __( 'Health', 'buddy' ), 'icon' => '💚' ), + 'energy' => array( 'label' => __( 'Energy', 'buddy' ), 'icon' => '⚡' ), + ); + echo ''; +} diff --git a/inc/settings.php b/inc/settings.php new file mode 100644 index 0000000..291624a --- /dev/null +++ b/inc/settings.php @@ -0,0 +1,77 @@ + 32 ) { return; } + buddy_update_state( array( 'name' => $new_name ) ); + + add_action( 'admin_notices', function () { + printf( + '

%s

', + esc_html__( 'Buddy renamed.', 'buddy' ) + ); + } ); +} + +function buddy_render_settings_page() { + if ( ! current_user_can( 'read' ) ) { + wp_die( esc_html__( 'You do not have permission to view this page.', 'buddy' ) ); + } + $state = buddy_get_state(); + ?> +
+

+ +
+ +

+

+
+ +

+

+ +

+
+ + +
+ 'M 42 60 Q 50 70 58 60', // smile + 'neutral' => 'M 42 64 L 58 64', // flat + 'sad' => 'M 42 66 Q 50 58 58 66', // frown + ); + $mouth_d = $mouth[ $tone ]; + + // Body fill: subtle shift by tone. + $body_fill = ( $tone === 'sad' ) ? '#d8b04a' : '#f4c64e'; + ?> + + + + + + + + + + + + + + + + + + + + + + + + + __( 'Buddy', 'buddy' ), + 'species' => 'default', + 'hunger' => 80, + 'happiness' => 80, + 'health' => 90, + 'energy' => 70, + 'born_at' => time(), + 'last_tick' => time(), + ); +} + +/** + * Fetch the current user's Buddy state, falling back to defaults and + * persisting them on first read so the pet has a "birthday" timestamp. + * + * @param int $user_id Optional. Defaults to current user. + * @return array { name, species, hunger, happiness, health, energy, born_at, last_tick } + */ +function buddy_get_state( $user_id = 0 ) { + $user_id = $user_id ? (int) $user_id : get_current_user_id(); + if ( ! $user_id ) { return buddy_default_state(); } + + $state = get_user_meta( $user_id, BUDDY_META_KEY, true ); + if ( ! is_array( $state ) || empty( $state ) ) { + $state = buddy_default_state(); + update_user_meta( $user_id, BUDDY_META_KEY, $state ); + } + + // Merge against defaults so newly-added keys appear with sensible + // values for users adopted under earlier versions. + return array_merge( buddy_default_state(), $state ); +} + +/** + * Persist a partial state update for the current user. Unknown keys + * are silently dropped; values are clamped to valid ranges. + * + * @param array $patch Keys: name (str), species (str), hunger/happiness/health/energy (int 0–100). + * @return array The full updated state. + */ +function buddy_update_state( array $patch, $user_id = 0 ) { + $user_id = $user_id ? (int) $user_id : get_current_user_id(); + if ( ! $user_id ) { return buddy_default_state(); } + + $state = buddy_get_state( $user_id ); + + $stat_keys = array( 'hunger', 'happiness', 'health', 'energy' ); + foreach ( $stat_keys as $k ) { + if ( array_key_exists( $k, $patch ) ) { + $state[ $k ] = max( 0, min( 100, (int) $patch[ $k ] ) ); + } + } + if ( array_key_exists( 'name', $patch ) ) { + $name = sanitize_text_field( (string) $patch['name'] ); + if ( $name !== '' && mb_strlen( $name ) <= 32 ) { + $state['name'] = $name; + } + } + if ( array_key_exists( 'species', $patch ) ) { + $allowed_species = array( 'default' ); // Phase D will widen this. + $sp = sanitize_key( (string) $patch['species'] ); + if ( in_array( $sp, $allowed_species, true ) ) { + $state['species'] = $sp; + } + } + + update_user_meta( $user_id, BUDDY_META_KEY, $state ); + return $state; +} + +/** + * Average of the four stats — used as a single "mood" indicator for + * the dashboard widget. Returns 0–100. + */ +function buddy_overall_mood( array $state ) { + $sum = (int) $state['hunger'] + (int) $state['happiness'] + (int) $state['health'] + (int) $state['energy']; + return (int) round( $sum / 4 ); +} + +/** + * Pick a one-word emoji / status label based on overall mood. Pure + * cosmetic, used in the dashboard widget header. + */ +function buddy_mood_label( $mood_score ) { + if ( $mood_score >= 80 ) { return array( 'label' => __( 'Thriving', 'buddy' ), 'tone' => 'happy' ); } + if ( $mood_score >= 60 ) { return array( 'label' => __( 'Content', 'buddy' ), 'tone' => 'happy' ); } + if ( $mood_score >= 40 ) { return array( 'label' => __( 'Okay', 'buddy' ), 'tone' => 'neutral' ); } + if ( $mood_score >= 20 ) { return array( 'label' => __( 'Hungry', 'buddy' ), 'tone' => 'sad' ); } + return array( 'label' => __( 'Distressed', 'buddy' ), 'tone' => 'sad' ); +} diff --git a/inc/updater.php b/inc/updater.php new file mode 100644 index 0000000..5a6d2da --- /dev/null +++ b/inc/updater.php @@ -0,0 +1,239 @@ + 8 ) ); + if ( is_wp_error( $response ) ) { return null; } + + $code = (int) wp_remote_retrieve_response_code( $response ); + $body = ( $code === 200 ) ? json_decode( wp_remote_retrieve_body( $response ), true ) : null; + + // Fallback to /tags if no Release object exists yet. + if ( $code !== 200 || ! is_array( $body ) || empty( $body['tag_name'] ) ) { + $tags_response = wp_remote_get( $base_api . '/tags?limit=1', array( 'timeout' => 8 ) ); + if ( ! is_wp_error( $tags_response ) + && (int) wp_remote_retrieve_response_code( $tags_response ) === 200 ) { + $tags = json_decode( wp_remote_retrieve_body( $tags_response ), true ); + if ( is_array( $tags ) && ! empty( $tags[0]['name'] ) ) { + $body = array( + 'tag_name' => $tags[0]['name'], + 'html_url' => buddy_gitea_repo_url() . '/src/tag/' . rawurlencode( $tags[0]['name'] ), + 'body' => isset( $tags[0]['message'] ) ? $tags[0]['message'] : '', + 'published_at' => isset( $tags[0]['commit']['created'] ) ? $tags[0]['commit']['created'] : null, + 'assets' => array(), + ); + $code = 200; + } + } + } + + if ( $code !== 200 || ! is_array( $body ) || empty( $body['tag_name'] ) ) { + $info = array( + 'version' => null, + 'html_url' => buddy_gitea_releases_url(), + 'download_url' => null, + 'body' => '', + 'published_at' => null, + 'error_code' => $code, + ); + set_site_transient( $cache_key, $info, HOUR_IN_SECONDS ); + return $info; + } + + $version = ltrim( (string) $body['tag_name'], 'vV' ); + + // Prefer a .zip asset; fall back to Gitea source-archive URL. + $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 = buddy_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, + ); + + set_site_transient( $cache_key, $info, 12 * HOUR_IN_SECONDS ); + return $info; +} + +function buddy_update_status( $force_refresh = false ) { + $current = defined( 'BUDDY_VERSION' ) ? BUDDY_VERSION : '0.0.0'; + $latest = buddy_fetch_latest_release( $force_refresh ); + + if ( ! $latest || empty( $latest['version'] ) ) { + $msg = __( 'No releases tagged on the Gitea repo yet.', 'buddy' ); + if ( $latest && ! empty( $latest['error_code'] ) && (int) $latest['error_code'] !== 404 ) { + $msg = sprintf( __( 'Could not reach Gitea (HTTP %d). Try again in a few minutes.', 'buddy' ), (int) $latest['error_code'] ); + } + return array( + 'status' => 'unknown', + 'current' => $current, + 'message' => $msg, + 'repo_url' => buddy_gitea_repo_url(), + ); + } + + 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%1$s) is available — you are on v%2$s.', 'buddy' ), $latest['version'], $current ), + ); + } + + return array( + 'status' => 'up-to-date', + 'current' => $current, + 'latest' => $latest['version'], + 'message' => sprintf( __( 'You are up to date (v%s).', 'buddy' ), $current ), + 'repo_url' => buddy_gitea_repo_url(), + ); +} + +add_action( 'wp_ajax_buddy_check_updates', 'buddy_ajax_check_updates' ); +function buddy_ajax_check_updates() { + if ( ! current_user_can( 'manage_options' ) ) { + wp_send_json_error( 'Insufficient permissions.', 403 ); + } + check_ajax_referer( 'buddy_check_updates', 'nonce' ); + delete_site_transient( 'buddy_gitea_latest' ); + wp_send_json_success( buddy_update_status( true ) ); +} + +function buddy_render_updates_panel() { + $status = buddy_update_status( false ); + $nonce = wp_create_nonce( 'buddy_check_updates' ); + $repo_url = buddy_gitea_repo_url(); + $rel_url = buddy_gitea_releases_url(); + ?> +
+

+

+ +

+ +

+ + + +
+ + + + + + + +

+ +

+ + + +

+

+ +

+
+ + +