chore: wp.org submission prep — v0.1.3
Rebrand to RangerHQ Buddy and prepare for the WordPress.org Plugin Directory. Same workflow as rangerhq-radio v0.7.0 → v0.7.5. Changes: - Plugin Name: Buddy → RangerHQ Buddy (matches family naming) - Plugin URI: icanhelp.ie/buddy → davidtkeane.com/rangerhq-buddy - Author URI: rangersmyth.xyz → davidtkeane.com - Text Domain: buddy → rangerhq-buddy (62 occurrences across 8 PHP files) - Add LICENSE file (full GPL v2 text from gnu.org) - Add wp.org-format readme.txt with all 8 required headers - Remove inc/updater.php (self-hosted Gitea updater forbidden for wp.org-hosted plugins per the rangerhq-radio v0.7.3 walkback) - Replace mt_rand with wp_rand in inc/state.php for better RNG - Add 5 translator comments for printf-style i18n placeholders - Wrap 2 dashboard-widget.php printf placeholders in (int) casts - Add tests/ to .gitignore (PCP reports are local-only) PCP audit: 85 issues → ~1 (the .gitignore file itself, stripped from the submission zip). Plugin Check Namer Tool: "Generally Allowable" verdict on both name and slug. Plugin URI https://davidtkeane.com/rangerhq-buddy/ returns HTTP 200. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+16
-16
@@ -13,7 +13,7 @@ if ( ! defined( 'ABSPATH' ) ) { exit; }
|
||||
|
||||
function buddy_render_about_page() {
|
||||
if ( ! current_user_can( 'read' ) ) {
|
||||
wp_die( esc_html__( 'You do not have permission to view this page.', 'buddy' ) );
|
||||
wp_die( esc_html__( 'You do not have permission to view this page.', 'rangerhq-buddy' ) );
|
||||
}
|
||||
?>
|
||||
<style>
|
||||
@@ -59,7 +59,7 @@ function buddy_render_about_page() {
|
||||
</style>
|
||||
|
||||
<div class="wrap">
|
||||
<h1 class="wp-heading-inline"><?php esc_html_e( 'About', 'buddy' ); ?></h1>
|
||||
<h1 class="wp-heading-inline"><?php esc_html_e( 'About', 'rangerhq-buddy' ); ?></h1>
|
||||
<span class="page-title-action">v<?php echo esc_html( BUDDY_VERSION ); ?></span>
|
||||
<hr class="wp-header-end">
|
||||
</div>
|
||||
@@ -73,47 +73,47 @@ function buddy_render_about_page() {
|
||||
<h2>Buddy <span style="color:#646970; font-weight:400;">v<?php echo esc_html( BUDDY_VERSION ); ?></span></h2>
|
||||
<p>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.</p>
|
||||
<p style="margin-bottom:0;">
|
||||
<a href="<?php echo esc_url( admin_url( 'admin.php?page=buddy' ) ); ?>" class="button button-primary"><?php esc_html_e( 'Go to My Buddy →', 'buddy' ); ?></a>
|
||||
<a href="<?php echo esc_url( admin_url( 'admin.php?page=buddy' ) ); ?>" class="button button-primary"><?php esc_html_e( 'Go to My Buddy →', 'rangerhq-buddy' ); ?></a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="buddy-about-card">
|
||||
<h2><?php esc_html_e( 'What Buddy does', 'buddy' ); ?></h2>
|
||||
<p><?php esc_html_e( 'Buddy is a small virtual pet stored per-user in your WordPress site. Each WP admin gets their own Buddy with its own name, species, and stats. Everything lives in your site\'s own database — nothing leaves the server.', 'buddy' ); ?></p>
|
||||
<p><?php esc_html_e( 'Right now (v0.1.0): Buddy exists. You can see them on the WordPress Dashboard widget and on the dedicated Buddy admin page. Coming next: feed / play / clean / sleep interactions, time-based stat decay, multiple species, and the killer feature — stats that react to your actual WordPress site health.', 'buddy' ); ?></p>
|
||||
<h2><?php esc_html_e( 'What Buddy does', 'rangerhq-buddy' ); ?></h2>
|
||||
<p><?php esc_html_e( 'Buddy is a small virtual pet stored per-user in your WordPress site. Each WP admin gets their own Buddy with its own name, species, and stats. Everything lives in your site\'s own database — nothing leaves the server.', 'rangerhq-buddy' ); ?></p>
|
||||
<p><?php esc_html_e( 'Right now (v0.1.0): Buddy exists. You can see them on the WordPress Dashboard widget and on the dedicated Buddy admin page. Coming next: feed / play / clean / sleep interactions, time-based stat decay, multiple species, and the killer feature — stats that react to your actual WordPress site health.', 'rangerhq-buddy' ); ?></p>
|
||||
</div>
|
||||
|
||||
<div class="buddy-about-card">
|
||||
<h2><?php esc_html_e( 'Who Buddy is for', 'buddy' ); ?></h2>
|
||||
<h2><?php esc_html_e( 'Who Buddy is for', 'rangerhq-buddy' ); ?></h2>
|
||||
<ul>
|
||||
<li><strong><?php esc_html_e( 'WordPress admins', 'buddy' ); ?></strong> <?php esc_html_e( 'who want a small daily reason to keep their site tidy.', 'buddy' ); ?></li>
|
||||
<li><strong><?php esc_html_e( 'Freelancers', 'buddy' ); ?></strong> <?php esc_html_e( 'managing multiple client sites and wanting a visual gauge of each one\'s health.', 'buddy' ); ?></li>
|
||||
<li><strong><?php esc_html_e( '90s kids', 'buddy' ); ?></strong> <?php esc_html_e( 'who remember when computers had pets in them.', 'buddy' ); ?></li>
|
||||
<li><strong><?php esc_html_e( 'Anyone', 'buddy' ); ?></strong> <?php esc_html_e( 'who wants their WordPress admin to feel slightly less corporate.', 'buddy' ); ?></li>
|
||||
<li><strong><?php esc_html_e( 'WordPress admins', 'rangerhq-buddy' ); ?></strong> <?php esc_html_e( 'who want a small daily reason to keep their site tidy.', 'rangerhq-buddy' ); ?></li>
|
||||
<li><strong><?php esc_html_e( 'Freelancers', 'rangerhq-buddy' ); ?></strong> <?php esc_html_e( 'managing multiple client sites and wanting a visual gauge of each one\'s health.', 'rangerhq-buddy' ); ?></li>
|
||||
<li><strong><?php esc_html_e( '90s kids', 'rangerhq-buddy' ); ?></strong> <?php esc_html_e( 'who remember when computers had pets in them.', 'rangerhq-buddy' ); ?></li>
|
||||
<li><strong><?php esc_html_e( 'Anyone', 'rangerhq-buddy' ); ?></strong> <?php esc_html_e( 'who wants their WordPress admin to feel slightly less corporate.', 'rangerhq-buddy' ); ?></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="buddy-about-card buddy-about-card--versions">
|
||||
<h2><?php esc_html_e( 'Version history', 'buddy' ); ?></h2>
|
||||
<h2><?php esc_html_e( 'Version history', 'rangerhq-buddy' ); ?></h2>
|
||||
<ul>
|
||||
<li>
|
||||
<span class="ver">v0.1.2</span> — 26 May 2026 <span class="latest">latest</span><br>
|
||||
<?php esc_html_e( 'Wink, tuned. The Easter-egg wink from v0.1.1 was firing at 30% per render — felt closer to "stuck" than "playful". Dropped to 5%: same cheeky face when it lands, just rare enough to feel like a treat.', 'buddy' ); ?>
|
||||
<?php esc_html_e( 'Wink, tuned. The Easter-egg wink from v0.1.1 was firing at 30% per render — felt closer to "stuck" than "playful". Dropped to 5%: same cheeky face when it lands, just rare enough to feel like a treat.', 'rangerhq-buddy' ); ?>
|
||||
</li>
|
||||
<li>
|
||||
<span class="ver">v0.1.1</span> — 25 May 2026<br>
|
||||
<?php esc_html_e( 'Cheeky face! New wink expression that occasionally appears when Buddy is in a good mood — one eye closed, asymmetric smirk, rosier cheeks. Pure SVG, no image files. Proof that the expression engine is properly extensible.', 'buddy' ); ?>
|
||||
<?php esc_html_e( 'Cheeky face! New wink expression that occasionally appears when Buddy is in a good mood — one eye closed, asymmetric smirk, rosier cheeks. Pure SVG, no image files. Proof that the expression engine is properly extensible.', 'rangerhq-buddy' ); ?>
|
||||
</li>
|
||||
<li>
|
||||
<span class="ver">v0.1.0</span> — 25 May 2026<br>
|
||||
<?php esc_html_e( 'First release. Phase A complete: Buddy exists. Dashboard widget + dedicated admin page show the SVG character, name, mood label, four stats bars. Self-hosted update checker wired up to Gitea from commit 1. No interactions yet — that\'s next.', 'buddy' ); ?>
|
||||
<?php esc_html_e( 'First release. Phase A complete: Buddy exists. Dashboard widget + dedicated admin page show the SVG character, name, mood label, four stats bars. Self-hosted update checker wired up to Gitea from commit 1. No interactions yet — that\'s next.', 'rangerhq-buddy' ); ?>
|
||||
</li>
|
||||
</ul>
|
||||
<a class="buddy-about-changelog-link"
|
||||
href="https://git.davidtkeane.com/ranger/a-buddy/src/branch/main/CHANGELOG.md"
|
||||
target="_blank" rel="noopener">
|
||||
<?php esc_html_e( 'View the full CHANGELOG.md →', 'buddy' ); ?>
|
||||
<?php esc_html_e( 'View the full CHANGELOG.md →', 'rangerhq-buddy' ); ?>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+9
-8
@@ -17,7 +17,7 @@ if ( ! defined( 'ABSPATH' ) ) { exit; }
|
||||
*/
|
||||
function buddy_render_main_page() {
|
||||
if ( ! current_user_can( 'read' ) ) {
|
||||
wp_die( esc_html__( 'You do not have permission to view Buddy.', 'buddy' ) );
|
||||
wp_die( esc_html__( 'You do not have permission to view Buddy.', 'rangerhq-buddy' ) );
|
||||
}
|
||||
|
||||
$state = buddy_get_state();
|
||||
@@ -25,13 +25,13 @@ function buddy_render_main_page() {
|
||||
$tone = buddy_mood_label( $mood );
|
||||
?>
|
||||
<div class="wrap">
|
||||
<h1 class="wp-heading-inline">🐾 <?php esc_html_e( 'Buddy', 'buddy' ); ?></h1>
|
||||
<h1 class="wp-heading-inline">🐾 <?php esc_html_e( 'Buddy', 'rangerhq-buddy' ); ?></h1>
|
||||
<span class="page-title-action">v<?php echo esc_html( BUDDY_VERSION ); ?></span>
|
||||
<hr class="wp-header-end">
|
||||
|
||||
<p class="description" style="max-width: 720px;">
|
||||
<?php esc_html_e( 'Your dashboard pet. Keep its stats up — eventually they will reflect how well you take care of your WordPress site.', 'buddy' ); ?>
|
||||
<a href="<?php echo esc_url( admin_url( 'admin.php?page=buddy-about' ) ); ?>"><?php esc_html_e( 'Read more on the About page →', 'buddy' ); ?></a>
|
||||
<?php esc_html_e( 'Your dashboard pet. Keep its stats up — eventually they will reflect how well you take care of your WordPress site.', 'rangerhq-buddy' ); ?>
|
||||
<a href="<?php echo esc_url( admin_url( 'admin.php?page=buddy-about' ) ); ?>"><?php esc_html_e( 'Read more on the About page →', 'rangerhq-buddy' ); ?></a>
|
||||
</p>
|
||||
|
||||
<div class="buddy-main">
|
||||
@@ -49,18 +49,19 @@ function buddy_render_main_page() {
|
||||
$age_days = (int) floor( $age_seconds / DAY_IN_SECONDS );
|
||||
echo esc_html(
|
||||
$age_days < 1
|
||||
? __( 'Adopted today', 'buddy' )
|
||||
: sprintf( _n( '%d day old', '%d days old', $age_days, 'buddy' ), $age_days )
|
||||
? __( 'Adopted today', 'rangerhq-buddy' )
|
||||
/* translators: %d is the number of days since the Buddy was adopted */
|
||||
: sprintf( _n( '%d day old', '%d days old', $age_days, 'rangerhq-buddy' ), $age_days )
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="buddy-main__card buddy-main__card--stats">
|
||||
<h2><?php esc_html_e( 'How is Buddy doing?', 'buddy' ); ?></h2>
|
||||
<h2><?php esc_html_e( 'How is Buddy doing?', 'rangerhq-buddy' ); ?></h2>
|
||||
<?php buddy_render_stats_bars( $state ); ?>
|
||||
<p class="buddy-main__note">
|
||||
<?php esc_html_e( 'Coming soon: feed, play, clean, and sleep actions to keep Buddy thriving. Also: stats will react to your WordPress site\'s health.', 'buddy' ); ?>
|
||||
<?php esc_html_e( 'Coming soon: feed, play, clean, and sleep actions to keep Buddy thriving. Also: stats will react to your WordPress site\'s health.', 'rangerhq-buddy' ); ?>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -18,7 +18,7 @@ function buddy_register_dashboard_widget() {
|
||||
|
||||
wp_add_dashboard_widget(
|
||||
'buddy_dashboard_widget',
|
||||
'🐾 ' . __( 'Buddy', 'buddy' ),
|
||||
'🐾 ' . __( 'Buddy', 'rangerhq-buddy' ),
|
||||
'buddy_render_dashboard_widget'
|
||||
);
|
||||
}
|
||||
@@ -42,7 +42,7 @@ function buddy_render_dashboard_widget() {
|
||||
<?php buddy_render_stats_bars( $state ); ?>
|
||||
<p class="buddy-widget__cta">
|
||||
<a href="<?php echo esc_url( admin_url( 'admin.php?page=buddy' ) ); ?>" class="button button-small">
|
||||
<?php esc_html_e( 'Visit Buddy →', 'buddy' ); ?>
|
||||
<?php esc_html_e( 'Visit Buddy →', 'rangerhq-buddy' ); ?>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
@@ -56,10 +56,10 @@ function buddy_render_dashboard_widget() {
|
||||
*/
|
||||
function buddy_render_stats_bars( array $state ) {
|
||||
$stats = array(
|
||||
'hunger' => array( 'label' => __( 'Hunger', 'buddy' ), 'icon' => '🍎' ),
|
||||
'happiness' => array( 'label' => __( 'Happiness', 'buddy' ), 'icon' => '😊' ),
|
||||
'health' => array( 'label' => __( 'Health', 'buddy' ), 'icon' => '💚' ),
|
||||
'energy' => array( 'label' => __( 'Energy', 'buddy' ), 'icon' => '⚡' ),
|
||||
'hunger' => array( 'label' => __( 'Hunger', 'rangerhq-buddy' ), 'icon' => '🍎' ),
|
||||
'happiness' => array( 'label' => __( 'Happiness', 'rangerhq-buddy' ), 'icon' => '😊' ),
|
||||
'health' => array( 'label' => __( 'Health', 'rangerhq-buddy' ), 'icon' => '💚' ),
|
||||
'energy' => array( 'label' => __( 'Energy', 'rangerhq-buddy' ), 'icon' => '⚡' ),
|
||||
);
|
||||
echo '<ul class="buddy-stats">';
|
||||
foreach ( $stats as $key => $meta ) {
|
||||
@@ -73,8 +73,8 @@ function buddy_render_stats_bars( array $state ) {
|
||||
esc_html( $meta['icon'] ),
|
||||
esc_html( $meta['label'] ),
|
||||
$low ? ' buddy-stat__fill--low' : '',
|
||||
$val,
|
||||
$val
|
||||
(int) $val,
|
||||
(int) $val
|
||||
);
|
||||
}
|
||||
echo '</ul>';
|
||||
|
||||
+6
-6
@@ -33,25 +33,25 @@ function buddy_handle_rename_post() {
|
||||
add_action( 'admin_notices', function () {
|
||||
printf(
|
||||
'<div class="notice notice-success is-dismissible"><p>%s</p></div>',
|
||||
esc_html__( 'Buddy renamed.', 'buddy' )
|
||||
esc_html__( 'Buddy renamed.', 'rangerhq-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' ) );
|
||||
wp_die( esc_html__( 'You do not have permission to view this page.', 'rangerhq-buddy' ) );
|
||||
}
|
||||
$state = buddy_get_state();
|
||||
?>
|
||||
<div class="wrap">
|
||||
<h1><?php esc_html_e( 'Buddy Settings', 'buddy' ); ?></h1>
|
||||
<h1><?php esc_html_e( 'Buddy Settings', 'rangerhq-buddy' ); ?></h1>
|
||||
|
||||
<form method="post" style="max-width: 720px; background:#fff; padding:18px 22px; border:1px solid #ccd0d4; border-radius:4px; margin-top: 16px;">
|
||||
<?php wp_nonce_field( 'buddy_rename', 'buddy_rename_nonce' ); ?>
|
||||
<h2 style="margin-top:0;"><?php esc_html_e( 'Name', 'buddy' ); ?></h2>
|
||||
<h2 style="margin-top:0;"><?php esc_html_e( 'Name', 'rangerhq-buddy' ); ?></h2>
|
||||
<p>
|
||||
<label for="buddy_name"><?php esc_html_e( 'What is your Buddy called?', 'buddy' ); ?></label><br>
|
||||
<label for="buddy_name"><?php esc_html_e( 'What is your Buddy called?', 'rangerhq-buddy' ); ?></label><br>
|
||||
<input type="text"
|
||||
name="buddy_name"
|
||||
id="buddy_name"
|
||||
@@ -61,7 +61,7 @@ function buddy_render_settings_page() {
|
||||
</p>
|
||||
<p>
|
||||
<button type="submit" name="buddy_rename_submit" class="button button-primary">
|
||||
<?php esc_html_e( 'Save name', 'buddy' ); ?>
|
||||
<?php esc_html_e( 'Save name', 'rangerhq-buddy' ); ?>
|
||||
</button>
|
||||
</p>
|
||||
</form>
|
||||
|
||||
+4
-1
@@ -44,7 +44,10 @@ function buddy_render_sprite( $species = 'default', $tone = 'happy', $size = 'md
|
||||
viewBox="0 0 100 100"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
role="img"
|
||||
aria-label="<?php echo esc_attr( sprintf( __( 'Buddy is %s', 'buddy' ), $tone ) ); ?>">
|
||||
aria-label="<?php
|
||||
/* translators: %s is the Buddy's mood tone (happy / neutral / sad / wink) */
|
||||
echo esc_attr( sprintf( __( 'Buddy is %s', 'rangerhq-buddy' ), $tone ) );
|
||||
?>">
|
||||
<!-- body -->
|
||||
<circle cx="50" cy="55" r="32" fill="<?php echo esc_attr( $body_fill ); ?>" stroke="#c9941d" stroke-width="2" />
|
||||
<!-- left eye — always an open circle in the SVG. When the
|
||||
|
||||
+8
-8
@@ -21,7 +21,7 @@ const BUDDY_META_KEY = 'buddy_state';
|
||||
*/
|
||||
function buddy_default_state() {
|
||||
return array(
|
||||
'name' => __( 'Buddy', 'buddy' ),
|
||||
'name' => __( 'Buddy', 'rangerhq-buddy' ),
|
||||
'species' => 'default',
|
||||
'hunger' => 80,
|
||||
'happiness' => 80,
|
||||
@@ -112,12 +112,12 @@ function buddy_overall_mood( array $state ) {
|
||||
* you'll catch it.
|
||||
*/
|
||||
function buddy_mood_label( $mood_score ) {
|
||||
if ( $mood_score >= 75 && mt_rand( 1, 100 ) <= 5 ) {
|
||||
return array( 'label' => __( 'Cheeky 😉', 'buddy' ), 'tone' => 'wink' );
|
||||
if ( $mood_score >= 75 && wp_rand( 1, 100 ) <= 5 ) {
|
||||
return array( 'label' => __( 'Cheeky 😉', 'rangerhq-buddy' ), 'tone' => 'wink' );
|
||||
}
|
||||
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' );
|
||||
if ( $mood_score >= 80 ) { return array( 'label' => __( 'Thriving', 'rangerhq-buddy' ), 'tone' => 'happy' ); }
|
||||
if ( $mood_score >= 60 ) { return array( 'label' => __( 'Content', 'rangerhq-buddy' ), 'tone' => 'happy' ); }
|
||||
if ( $mood_score >= 40 ) { return array( 'label' => __( 'Okay', 'rangerhq-buddy' ), 'tone' => 'neutral' ); }
|
||||
if ( $mood_score >= 20 ) { return array( 'label' => __( 'Hungry', 'rangerhq-buddy' ), 'tone' => 'sad' ); }
|
||||
return array( 'label' => __( 'Distressed', 'rangerhq-buddy' ), 'tone' => 'sad' );
|
||||
}
|
||||
|
||||
-239
@@ -1,239 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Buddy — self-hosted update checker against the Gitea repo.
|
||||
*
|
||||
* Direct port of the Logbook v3.3.5 updater (proven in production).
|
||||
* Polls Gitea's /releases/latest, falls back to /tags?limit=1 when no
|
||||
* formal Release object exists, compares against BUDDY_VERSION,
|
||||
* renders an Updates panel on the Settings page. Cached 12h on
|
||||
* success, 1h on negative responses.
|
||||
*
|
||||
* Repo coordinates are constants you can override via define() in
|
||||
* wp-config.php if the repo ever moves.
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) { exit; }
|
||||
|
||||
if ( ! defined( 'BUDDY_GITEA_HOST' ) ) { define( 'BUDDY_GITEA_HOST', 'https://git.davidtkeane.com' ); }
|
||||
if ( ! defined( 'BUDDY_GITEA_OWNER' ) ) { define( 'BUDDY_GITEA_OWNER', 'ranger' ); }
|
||||
if ( ! defined( 'BUDDY_GITEA_REPO' ) ) { define( 'BUDDY_GITEA_REPO', 'a-buddy' ); }
|
||||
|
||||
function buddy_gitea_repo_url() {
|
||||
return BUDDY_GITEA_HOST . '/' . BUDDY_GITEA_OWNER . '/' . BUDDY_GITEA_REPO;
|
||||
}
|
||||
function buddy_gitea_releases_url() {
|
||||
return buddy_gitea_repo_url() . '/releases';
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the latest release/tag, normalised. Returns null on hard
|
||||
* error, or an array including `version`. See Logbook updater for the
|
||||
* full shape.
|
||||
*/
|
||||
function buddy_fetch_latest_release( $force_refresh = false ) {
|
||||
$cache_key = 'buddy_gitea_latest';
|
||||
|
||||
if ( ! $force_refresh ) {
|
||||
$cached = get_site_transient( $cache_key );
|
||||
if ( is_array( $cached ) ) { return $cached; }
|
||||
}
|
||||
|
||||
$base_api = BUDDY_GITEA_HOST . '/api/v1/repos/' . BUDDY_GITEA_OWNER . '/' . BUDDY_GITEA_REPO;
|
||||
|
||||
// Try formal Release first.
|
||||
$response = wp_remote_get( $base_api . '/releases/latest', array( 'timeout' => 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();
|
||||
?>
|
||||
<div class="buddy-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;"><?php esc_html_e( 'Updates', 'buddy' ); ?></h2>
|
||||
<p style="margin:0 0 12px;">
|
||||
<?php esc_html_e( 'Buddy is self-hosted on Gitea. Click Check now to ask the repo whether there is a newer release than the one you are running.', 'buddy' ); ?>
|
||||
</p>
|
||||
|
||||
<p id="buddy-update-status" style="margin:0 0 12px;">
|
||||
<strong><?php esc_html_e( 'Status:', 'buddy' ); ?></strong>
|
||||
<span id="buddy-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;">
|
||||
<?php
|
||||
/* translators: %s is the latest version number, e.g. "0.2.0" */
|
||||
echo esc_html( sprintf( __( 'Download v%s (.zip)', 'buddy' ), $status['latest'] ) );
|
||||
?>
|
||||
</a>
|
||||
<?php if ( ! empty( $status['html_url'] ) ) : ?>
|
||||
<a href="<?php echo esc_url( $status['html_url'] ); ?>" target="_blank" rel="noopener" style="margin-left:8px;"><?php esc_html_e( 'View release notes →', 'buddy' ); ?></a>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</p>
|
||||
|
||||
<p style="margin:0 0 4px;">
|
||||
<button type="button" id="buddy-check-updates-btn" class="button" data-nonce="<?php echo esc_attr( $nonce ); ?>">
|
||||
↻ <?php esc_html_e( 'Check now', 'buddy' ); ?>
|
||||
</button>
|
||||
<a href="<?php echo esc_url( $repo_url ); ?>" target="_blank" rel="noopener" class="button" style="margin-left:6px;"><?php esc_html_e( 'View on Gitea', 'buddy' ); ?></a>
|
||||
<a href="<?php echo esc_url( $rel_url ); ?>" target="_blank" rel="noopener" class="button" style="margin-left:6px;"><?php esc_html_e( 'View all releases', 'buddy' ); ?></a>
|
||||
</p>
|
||||
<p style="margin:10px 0 0; color:#646970; font-size:12px;">
|
||||
<?php esc_html_e( 'Manual update path: download the .zip, deactivate the plugin in WordPress, upload via Plugins → Add New → Upload, reactivate. Your Buddy survives the upgrade (state is stored in user_meta).', 'buddy' ); ?>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var btn = document.getElementById('buddy-check-updates-btn');
|
||||
var statusText = document.getElementById('buddy-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', 'buddy_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) {
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user