feature: Add github repo update functions

This commit is contained in:
Keith Solomon
2026-04-15 10:25:15 -05:00
parent f5256d6973
commit 3d6e3716b0
2 changed files with 246 additions and 0 deletions
+243
View File
@@ -0,0 +1,243 @@
<?php
/**
* GitHub-based plugin updater.
*
* Hooks into WordPress's native update system so the plugin receives update
* notifications sourced from GitHub releases rather than the wp.org directory.
*
* @package LogoSoup\Support
*/
namespace LogoSoup\Support;
/**
* Enables automatic plugin updates sourced from GitHub releases.
*/
class GitHub_Updater {
const GITHUB_USER = 'Vincent-Design-Inc';
const GITHUB_REPO = 'Logo-Soup';
const TRANSIENT = 'logo_soup_github_release';
const TRANSIENT_TTL = 43200; // 12 hours.
/**
* Plugin slug (relative path from plugins dir, e.g. logo-soup/logo-soup.php).
*
* @var string
*/
private $plugin_slug;
/**
* Absolute path to the main plugin file.
*
* @var string
*/
private $plugin_file;
/**
* Currently installed version, read from plugin headers.
*
* @var string
*/
private $current_version;
/**
* Constructor.
*
* @param string $plugin_file Absolute path to the main plugin file.
*/
public function __construct( $plugin_file ) {
$this->plugin_file = $plugin_file;
$this->plugin_slug = plugin_basename( $plugin_file );
$headers = get_file_data( $plugin_file, array( 'Version' => 'Version' ) );
$this->current_version = ! empty( $headers['Version'] ) ? $headers['Version'] : '0.0.0';
}
/**
* Register WordPress hooks.
*
* @return void
*/
public function register() {
add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'inject_update' ) );
add_filter( 'plugins_api', array( $this, 'plugin_info' ), 20, 3 );
add_filter( 'upgrader_process_complete', array( $this, 'purge_transient' ), 10, 2 );
}
/**
* Inject update data into WordPress's update_plugins transient.
*
* @param object $transient The update_plugins transient value.
* @return object
*/
public function inject_update( $transient ) {
if ( empty( $transient->checked ) ) {
return $transient;
}
$release = $this->fetch_latest_release();
if ( ! $release ) {
return $transient;
}
$remote_version = ltrim( $release['tag_name'], 'v' );
if ( version_compare( $this->current_version, $remote_version, '>=' ) ) {
return $transient;
}
$download_url = $this->get_download_url( $release );
if ( ! $download_url ) {
return $transient;
}
$transient->response[ $this->plugin_slug ] = (object) array(
'slug' => dirname( $this->plugin_slug ),
'plugin' => $this->plugin_slug,
'new_version' => $remote_version,
'url' => 'https://github.com/' . self::GITHUB_USER . '/' . self::GITHUB_REPO,
'package' => $download_url,
);
return $transient;
}
/**
* Supply plugin info for the "View version details" modal in the admin.
*
* @param false|object|array $result The result object or array, or false.
* @param string $action The type of info being requested.
* @param object $args Plugin API arguments.
* @return false|object
*/
public function plugin_info( $result, $action, $args ) {
if ( 'plugin_information' !== $action ) {
return $result;
}
if ( ! isset( $args->slug ) || dirname( $this->plugin_slug ) !== $args->slug ) {
return $result;
}
$release = $this->fetch_latest_release();
if ( ! $release ) {
return $result;
}
$remote_version = ltrim( $release['tag_name'], 'v' );
$download_url = $this->get_download_url( $release );
$changelog = ! empty( $release['body'] ) ? nl2br( esc_html( $release['body'] ) ) : '';
return (object) array(
'name' => 'Logo Soup',
'slug' => dirname( $this->plugin_slug ),
'version' => $remote_version,
'author' => 'Vincent Design Inc',
'homepage' => 'https://github.com/' . self::GITHUB_USER . '/' . self::GITHUB_REPO,
'download_link' => $download_url,
'trunk' => $download_url,
'requires' => '6.1',
'requires_php' => '7.4',
'sections' => array(
'description' => esc_html__( 'Build balanced logo grids and marquees directly inside the WordPress block editor.', 'logo-soup' ),
'changelog' => $changelog,
),
);
}
/**
* Delete the cached release transient after a successful plugin update.
*
* @param \WP_Upgrader $upgrader Upgrader instance.
* @param array $hook_extra Extra data about the upgrade.
* @return void
*/
public function purge_transient( $upgrader, $hook_extra ) {
if (
! empty( $hook_extra['action'] ) &&
'update' === $hook_extra['action'] &&
! empty( $hook_extra['type'] ) &&
'plugin' === $hook_extra['type'] &&
! empty( $hook_extra['plugins'] ) &&
in_array( $this->plugin_slug, (array) $hook_extra['plugins'], true )
) {
delete_transient( self::TRANSIENT );
}
}
/**
* Fetch the latest GitHub release, with a 12-hour transient cache.
*
* @return array|null Decoded release data or null on failure.
*/
private function fetch_latest_release() {
$cached = get_transient( self::TRANSIENT );
if ( is_array( $cached ) ) {
return $cached;
}
$url = sprintf(
'https://api.github.com/repos/%s/%s/releases/latest',
self::GITHUB_USER,
self::GITHUB_REPO
);
$response = wp_remote_get(
$url,
array(
'timeout' => 10,
'user-agent' => 'WordPress/' . get_bloginfo( 'version' ) . '; ' . get_bloginfo( 'url' ),
'headers' => array(
'Accept' => 'application/vnd.github+json',
),
)
);
if ( is_wp_error( $response ) ) {
return null;
}
if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
return null;
}
$data = json_decode( wp_remote_retrieve_body( $response ), true );
if ( ! is_array( $data ) || empty( $data['tag_name'] ) ) {
return null;
}
set_transient( self::TRANSIENT, $data, self::TRANSIENT_TTL );
return $data;
}
/**
* Extract the zip download URL from a release.
*
* Prefers an attached release asset (.zip); falls back to the auto-generated
* GitHub source zipball.
*
* @param array $release Decoded GitHub release data.
* @return string|null
*/
private function get_download_url( array $release ) {
if ( ! empty( $release['assets'] ) && is_array( $release['assets'] ) ) {
foreach ( $release['assets'] as $asset ) {
if (
isset( $asset['browser_download_url'] ) &&
'.zip' === substr( $asset['browser_download_url'], -4 )
) {
return $asset['browser_download_url'];
}
}
}
return isset( $release['zipball_url'] ) ? $release['zipball_url'] : null;
}
}
+3
View File
@@ -10,6 +10,7 @@ namespace LogoSoup;
use LogoSoup\Admin\Settings_Page; use LogoSoup\Admin\Settings_Page;
use LogoSoup\Blocks\Logo_Soup_Block; use LogoSoup\Blocks\Logo_Soup_Block;
use LogoSoup\REST\Settings_Controller; use LogoSoup\REST\Settings_Controller;
use LogoSoup\Support\GitHub_Updater;
/** /**
* Main plugin bootstrap class. * Main plugin bootstrap class.
@@ -34,6 +35,8 @@ class Plugin {
register_activation_hook( $plugin_file, array( Installer::class, 'activate' ) ); register_activation_hook( $plugin_file, array( Installer::class, 'activate' ) );
register_deactivation_hook( $plugin_file, array( Installer::class, 'deactivate' ) ); register_deactivation_hook( $plugin_file, array( Installer::class, 'deactivate' ) );
( new GitHub_Updater( $plugin_file ) )->register();
add_action( 'plugins_loaded', array( __CLASS__, 'load_textdomain' ) ); add_action( 'plugins_loaded', array( __CLASS__, 'load_textdomain' ) );
add_action( 'init', array( __CLASS__, 'register_settings' ) ); add_action( 'init', array( __CLASS__, 'register_settings' ) );
add_action( 'init', array( Logo_Soup_Block::class, 'register' ) ); add_action( 'init', array( Logo_Soup_Block::class, 'register' ) );