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..3773d06 --- /dev/null +++ b/includes/updater.php @@ -0,0 +1,293 @@ +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 object $transient Update transient object. + * + * @return object 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' => esc_html__( 'Latest version from GitHub', 'viget-blocks-toolkit' ), + '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 object $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 esc_html__( 'No changelog available.', 'viget-blocks-toolkit' ); + } + + // 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..3d210ad 100644 --- a/viget-blocks-toolkit.php +++ b/viget-blocks-toolkit.php @@ -19,15 +19,21 @@ // 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';