From 6f3b9f2822265f8e9371b36e28ce6ab3c9b363ec Mon Sep 17 00:00:00 2001 From: Brian DiChiara Date: Tue, 21 Oct 2025 11:58:58 -0500 Subject: [PATCH 1/3] [N/A] First pass at Github Updater --- README.md | 10 +- includes/updater.php | 296 +++++++++++++++++++++++++++++++++++++++ src/classes/Core.php | 7 + viget-blocks-toolkit.php | 11 +- 4 files changed, 319 insertions(+), 5 deletions(-) create mode 100644 includes/updater.php diff --git a/README.md b/README.md index ce9cd35..22b3555 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Adding the `mediaPosition` attribute will enable the Media Position toggle butto * Root level transformations will apply to all child blocks. * `reverse` will reverse the order of the child blocks. * `attributes` will modify the block's attributes. - * Each value will be determined based on the value of `mediaPosition`. See example below. + * Each value will be determined based on the value of `mediaPosition`. See example below. * Nested `innerBlocks` will apply transformations only to child blocks when present within the parent block. In the following example scenario: @@ -171,7 +171,7 @@ vgtbt()->block_icons->get_icon( 'my-custom-icon' ); Outputs the block attributes as a string. Supported arguments: * `$block` array - The block data. -* `$custom_class` string - Additional classes to add to the block. +* `$custom_class` string - Additional classes to add to the block. * `$attrs` array - Additional attributes to add to the block. ### `inner_blocks()` @@ -214,7 +214,7 @@ add_filter( 'icon' => '', 'defaultLeft' => false, // Optional, defaults icon to align left. ]; - + return $icons; } ); @@ -282,3 +282,7 @@ add_filter( } ); ``` + +## Auto-Updates + +This plugin automatically checks for updates from GitHub releases every 12 hours and can be updated directly from the WordPress dashboard. diff --git a/includes/updater.php b/includes/updater.php new file mode 100644 index 0000000..c5d2042 --- /dev/null +++ b/includes/updater.php @@ -0,0 +1,296 @@ +plugin_file = $plugin_file; + $this->github_owner = $github_owner; + $this->github_repo = $github_repo; + $this->plugin_basename = plugin_basename( $plugin_file ); + + // Get plugin data. + $plugin_data = get_plugin_data( $plugin_file ); + $this->plugin_slug = dirname( $this->plugin_basename ); + $this->plugin_version = $plugin_data['Version']; + + // Hook into WordPress. + add_filter( 'pre_set_site_transient_update_plugins', [ $this, 'check_for_update' ] ); + add_filter( 'plugins_api', [ $this, 'plugin_info' ], 10, 3 ); + add_action( 'upgrader_process_complete', [ $this, 'purge_cache' ], 10, 2 ); + } + + /** + * Check for plugin updates. + * + * @param stdClass $transient Update transient object. + * + * @return stdClass Modified transient object. + */ + public function check_for_update( $transient ) { + if ( empty( $transient->checked ) ) { + return $transient; + } + + $release_info = $this->get_release_info(); + + if ( ! $release_info || ! isset( $release_info->tag_name ) ) { + return $transient; + } + + // Remove 'v' prefix from tag name for version comparison. + $latest_version = ltrim( $release_info->tag_name, 'v' ); + + // Check if there's a newer version. + if ( version_compare( $this->plugin_version, $latest_version, '<' ) ) { + $package_url = $this->get_package_url( $release_info ); + + if ( $package_url ) { + $transient->response[ $this->plugin_basename ] = (object) [ + 'slug' => $this->plugin_slug, + 'plugin' => $this->plugin_basename, + 'new_version' => $latest_version, + 'url' => $release_info->html_url, + 'package' => $package_url, + 'icons' => [], + 'banners' => [], + 'banners_rtl' => [], + 'tested' => '', + 'requires_php' => '', + ]; + } + } + + return $transient; + } + + /** + * Provide plugin information for the update details modal + * + * @param false|object|array $result The result object or array. + * @param string $action The type of information being requested. + * @param object $args Plugin API arguments. + * + * @return false|object Plugin information or false. + */ + public function plugin_info( $result, $action, $args ) { + if ( 'plugin_information' !== $action ) { + return $result; + } + + if ( ! isset( $args->slug ) || $args->slug !== $this->plugin_slug ) { + return $result; + } + + $release_info = $this->get_release_info(); + + if ( ! $release_info ) { + return $result; + } + + $latest_version = ltrim( $release_info->tag_name, 'v' ); + + return (object) [ + 'name' => $this->plugin_slug, + 'slug' => $this->plugin_slug, + 'version' => $latest_version, + 'author' => $release_info->author->login, + 'author_profile' => $release_info->author->html_url, + 'last_updated' => $release_info->published_at, + 'homepage' => $release_info->html_url, + 'short_description' => 'Latest version from GitHub', + 'sections' => [ + 'changelog' => $this->format_changelog( $release_info->body ), + ], + 'download_link' => $this->get_package_url( $release_info ), + 'requires' => '', + 'tested' => '', + 'requires_php' => '', + ]; + } + + /** + * Clear cache after successful update + * + * @param WP_Upgrader $upgrader Upgrader instance. + * @param array $options Update options. + */ + public function purge_cache( $upgrader, $options ) { + if ( 'update' !== $options['action'] || 'plugin' !== $options['type'] ) { + return; + } + + if ( empty( $options['plugins'] ) || ! in_array( $this->plugin_basename, $options['plugins'], true ) ) { + return; + } + + delete_site_transient( $this->get_transient_key() ); + } + + /** + * Get release information from GitHub API. + * + * @return object|false Release information or false on failure. + */ + private function get_release_info() { + $transient_key = $this->get_transient_key(); + $release_info = get_site_transient( $transient_key ); + + if ( false === $release_info ) { + $api_url = sprintf( + 'https://api.github.com/repos/%s/%s/releases/latest', + $this->github_owner, + $this->github_repo + ); + + $response = wp_remote_get( + $api_url, + [ + 'timeout' => 10, + 'headers' => [ + 'Accept' => 'application/vnd.github.v3+json', + 'User-Agent' => 'WordPress-Plugin-Updater', + ], + ] + ); + + if ( is_wp_error( $response ) ) { + return false; + } + + $response_code = wp_remote_retrieve_response_code( $response ); + if ( 200 !== $response_code ) { + return false; + } + + $release_info = json_decode( wp_remote_retrieve_body( $response ) ); + + if ( ! $release_info ) { + return false; + } + + // Cache for 12 hours. + set_site_transient( $transient_key, $release_info, 12 * HOUR_IN_SECONDS ); + } + + return $release_info; + } + + /** + * Get download URL for the release package. + * + * @param stdClass $release_info Release information from GitHub API. + * + * @return string|false Download URL or false if not found. + */ + private function get_package_url( $release_info ): string|false { + if ( ! isset( $release_info->assets ) || ! is_array( $release_info->assets ) ) { + return false; + } + + // Look for a ZIP file asset. + foreach ( $release_info->assets as $asset ) { + if ( ! empty( $asset->content_type ) && in_array( $asset->content_type, [ 'application/zip', 'application/octet-stream' ], true ) ) { + return $asset->browser_download_url; + } + } + + return false; + } + + /** + * Format changelog text. + * + * @param string $changelog Raw changelog text. + * @return string Formatted changelog. + */ + private function format_changelog( $changelog ) { + if ( empty( $changelog ) ) { + return 'No changelog available.'; + } + + // Convert markdown links to HTML + $changelog = preg_replace( '/\[([^\]]+)\]\(([^)]+)\)/', '$1', $changelog ); + + // Convert line breaks to HTML + $changelog = nl2br( esc_html( $changelog ) ); + + return $changelog; + } + + /** + * Get transient key for caching + * + * @return string Transient key. + */ + private function get_transient_key() { + return 'github_updater_' . md5( $this->github_owner . '/' . $this->github_repo ); + } +} diff --git a/src/classes/Core.php b/src/classes/Core.php index 9a00e11..e984323 100644 --- a/src/classes/Core.php +++ b/src/classes/Core.php @@ -39,6 +39,13 @@ class Core { public function __construct() { $this->block_icons = new BlockIcons(); $this->bp_visibility = new BreakpointVisibility(); + + // Initialize GitHub updater + new \GitHub_Plugin_Updater( + VGTBT_PLUGIN_FILE, + 'vigetlabs', + 'viget-blocks-toolkit' + ); } /** diff --git a/viget-blocks-toolkit.php b/viget-blocks-toolkit.php index 4a59123..21a316f 100644 --- a/viget-blocks-toolkit.php +++ b/viget-blocks-toolkit.php @@ -19,15 +19,22 @@ // Plugin version. const VGTBT_VERSION = '1.1.1'; +// Plugin file. +define( 'VGTBT_PLUGIN_FILE', __FILE__ ); + + // Plugin path. -define( 'VGTBT_PLUGIN_PATH', plugin_dir_path( __FILE__ ) ); +define( 'VGTBT_PLUGIN_PATH', plugin_dir_path( VGTBT_PLUGIN_FILE ) ); // Plugin URL. -define( 'VGTBT_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); +define( 'VGTBT_PLUGIN_URL', plugin_dir_url( VGTBT_PLUGIN_FILE ) ); // Helper functions. require_once 'includes/helpers.php'; +// Plugin updater. +require_once 'includes/updater.php'; + // Timber functions. require_once 'includes/timber.php'; From 0e2c516ebf8791f810d6ed9c7d3aff67da9ae456 Mon Sep 17 00:00:00 2001 From: Brian DiChiara Date: Tue, 21 Oct 2025 12:04:06 -0500 Subject: [PATCH 2/3] [N/A] Remove extra line --- viget-blocks-toolkit.php | 1 - 1 file changed, 1 deletion(-) diff --git a/viget-blocks-toolkit.php b/viget-blocks-toolkit.php index 21a316f..3d210ad 100644 --- a/viget-blocks-toolkit.php +++ b/viget-blocks-toolkit.php @@ -22,7 +22,6 @@ // Plugin file. define( 'VGTBT_PLUGIN_FILE', __FILE__ ); - // Plugin path. define( 'VGTBT_PLUGIN_PATH', plugin_dir_path( VGTBT_PLUGIN_FILE ) ); From d383e14aa5f5c17775029765b58cadbdfba4872c Mon Sep 17 00:00:00 2001 From: Brian DiChiara Date: Tue, 21 Oct 2025 12:26:42 -0500 Subject: [PATCH 3/3] Fix some PHP Notices --- includes/updater.php | 55 +++++++++++++++++++++----------------------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/includes/updater.php b/includes/updater.php index c5d2042..3773d06 100644 --- a/includes/updater.php +++ b/includes/updater.php @@ -8,9 +8,6 @@ * @package Viget\BlocksToolkit */ -use stdClass; -use WP_Upgrader; - /** * GitHub Plugin Updater Class */ @@ -66,33 +63,33 @@ class GitHub_Plugin_Updater { * @param string $github_repo GitHub repository name. */ public function __construct( $plugin_file, $github_owner, $github_repo ) { - // Only run in the admin area. - if ( ! is_admin() ) { - return; - } - - $this->plugin_file = $plugin_file; - $this->github_owner = $github_owner; - $this->github_repo = $github_repo; - $this->plugin_basename = plugin_basename( $plugin_file ); - - // Get plugin data. - $plugin_data = get_plugin_data( $plugin_file ); - $this->plugin_slug = dirname( $this->plugin_basename ); - $this->plugin_version = $plugin_data['Version']; - - // Hook into WordPress. - add_filter( 'pre_set_site_transient_update_plugins', [ $this, 'check_for_update' ] ); - add_filter( 'plugins_api', [ $this, 'plugin_info' ], 10, 3 ); - add_action( 'upgrader_process_complete', [ $this, 'purge_cache' ], 10, 2 ); + add_action( + 'admin_init', + function() use ( $plugin_file, $github_owner, $github_repo ) { + $this->plugin_file = $plugin_file; + $this->github_owner = $github_owner; + $this->github_repo = $github_repo; + $this->plugin_basename = plugin_basename( $plugin_file ); + + // Get plugin data. + $plugin_data = get_plugin_data( $plugin_file ); + $this->plugin_slug = dirname( $this->plugin_basename ); + $this->plugin_version = $plugin_data['Version']; + + // Hook into WordPress. + add_filter( 'pre_set_site_transient_update_plugins', [ $this, 'check_for_update' ] ); + add_filter( 'plugins_api', [ $this, 'plugin_info' ], 10, 3 ); + add_action( 'upgrader_process_complete', [ $this, 'purge_cache' ], 10, 2 ); + } + ); } /** * Check for plugin updates. * - * @param stdClass $transient Update transient object. + * @param object $transient Update transient object. * - * @return stdClass Modified transient object. + * @return object Modified transient object. */ public function check_for_update( $transient ) { if ( empty( $transient->checked ) ) { @@ -165,7 +162,7 @@ public function plugin_info( $result, $action, $args ) { 'author_profile' => $release_info->author->html_url, 'last_updated' => $release_info->published_at, 'homepage' => $release_info->html_url, - 'short_description' => 'Latest version from GitHub', + 'short_description' => esc_html__( 'Latest version from GitHub', 'viget-blocks-toolkit' ), 'sections' => [ 'changelog' => $this->format_changelog( $release_info->body ), ], @@ -179,8 +176,8 @@ public function plugin_info( $result, $action, $args ) { /** * Clear cache after successful update * - * @param WP_Upgrader $upgrader Upgrader instance. - * @param array $options Update options. + * @param \WP_Upgrader $upgrader Upgrader instance. + * @param array $options Update options. */ public function purge_cache( $upgrader, $options ) { if ( 'update' !== $options['action'] || 'plugin' !== $options['type'] ) { @@ -246,7 +243,7 @@ private function get_release_info() { /** * Get download URL for the release package. * - * @param stdClass $release_info Release information from GitHub API. + * @param object $release_info Release information from GitHub API. * * @return string|false Download URL or false if not found. */ @@ -273,7 +270,7 @@ private function get_package_url( $release_info ): string|false { */ private function format_changelog( $changelog ) { if ( empty( $changelog ) ) { - return 'No changelog available.'; + return esc_html__( 'No changelog available.', 'viget-blocks-toolkit' ); } // Convert markdown links to HTML