From 60add5f915d5f581a737d0e3d931ac2f48c264fd Mon Sep 17 00:00:00 2001 From: Yahnis Elsts Date: Mon, 27 Nov 2017 17:32:49 +0200 Subject: [PATCH] Add support for GitHub release assets. See #93 Usage: Set up the update checker instance as usual, then call the new enableReleaseAssets() method of the GitHub API class. `$gitHubUpdateChecker->getVcsApi()->enableReleaseAssets();` Notes: - You can make PUC look for a specific asset by passing a regular expression to enableReleaseAssets(). For example, `$api->enableReleaseAssets('/custom-asset/')` will make PUC use the first asset where the file name contains the string "custom-asset". - PUC always chooses the first matching asset. - If the latest release has no assets or none of them match the regex, PUC will fall back to the old behaviour of using the automatically generated ZIP file. - Private repositories only work with WP 3.7 or later. Older WordPress versions will throw an error when trying to download the update. --- Puc/v4p3/Vcs/BaseChecker.php | 5 ++ Puc/v4p3/Vcs/GitHubApi.php | 126 ++++++++++++++++++++++++--- Puc/v4p3/Vcs/PluginUpdateChecker.php | 4 + Puc/v4p3/Vcs/ThemeUpdateChecker.php | 4 + 4 files changed, 129 insertions(+), 10 deletions(-) diff --git a/Puc/v4p3/Vcs/BaseChecker.php b/Puc/v4p3/Vcs/BaseChecker.php index ac10783..de04274 100644 --- a/Puc/v4p3/Vcs/BaseChecker.php +++ b/Puc/v4p3/Vcs/BaseChecker.php @@ -17,6 +17,11 @@ if ( !interface_exists('Puc_v4p3_Vcs_BaseChecker', false) ): * @return $this */ public function setAuthentication($credentials); + + /** + * @return Puc_v4p3_Vcs_Api + */ + public function getVcsApi(); } endif; \ No newline at end of file diff --git a/Puc/v4p3/Vcs/GitHubApi.php b/Puc/v4p3/Vcs/GitHubApi.php index 58c8d05..81883a7 100644 --- a/Puc/v4p3/Vcs/GitHubApi.php +++ b/Puc/v4p3/Vcs/GitHubApi.php @@ -22,6 +22,21 @@ if ( !class_exists('Puc_v4p3_Vcs_GitHubApi', false) ): */ protected $accessToken; + /** + * @var bool Whether to download release assets instead of the auto-generated source code archives. + */ + protected $releaseAssetsEnabled = false; + + /** + * @var string|null Regular expression that's used to filter release assets by name. Optional. + */ + protected $assetFilterRegex = null; + + /** + * @var string|null The unchanging part of a release asset URL. Used to identify download attempts. + */ + protected $assetApiBaseUrl = null; + public function __construct($repositoryUrl, $accessToken = null) { $path = @parse_url($repositoryUrl, PHP_URL_PATH); if ( preg_match('@^/?(?P[^/]+?)/(?P[^/#?&]+?)/?$@', $path, $matches) ) { @@ -46,20 +61,41 @@ if ( !class_exists('Puc_v4p3_Vcs_GitHubApi', false) ): } $reference = new Puc_v4p3_Vcs_Reference(array( - 'name' => $release->tag_name, - 'version' => ltrim($release->tag_name, 'v'), //Remove the "v" prefix from "v1.2.3". + 'name' => $release->tag_name, + 'version' => ltrim($release->tag_name, 'v'), //Remove the "v" prefix from "v1.2.3". 'downloadUrl' => $this->signDownloadUrl($release->zipball_url), - 'updated' => $release->created_at, + 'updated' => $release->created_at, 'apiResponse' => $release, )); + if ( isset($release->assets[0]) ) { + $reference->downloadCount = $release->assets[0]->download_count; + } + + if ( $this->releaseAssetsEnabled && isset($release->assets, $release->assets[0]) ) { + //Use the first release asset that matches the specified regular expression. + $matchingAssets = array_filter($release->assets, array($this, 'matchesAssetFilter')); + if ( !empty($matchingAssets) ) { + if ( $this->isAuthenticationEnabled() ) { + /** + * Keep in mind that we'll need to add an "Accept" header to download this asset. + * @see setReleaseDownloadHeader() + */ + $reference->downloadUrl = $this->signDownloadUrl($matchingAssets[0]->url); + } else { + //It seems that browser_download_url only works for public repositories. + //Using an access_token doesn't help. Maybe OAuth would work? + $reference->downloadUrl = $matchingAssets[0]->browser_download_url; + } + + $reference->downloadCount = $matchingAssets[0]->download_count; + } + } + if ( !empty($release->body) ) { /** @noinspection PhpUndefinedClassInspection */ $reference->changelog = Parsedown::instance()->text($release->body); } - if ( isset($release->assets[0]) ) { - $reference->downloadCount = $release->assets[0]->download_count; - } return $reference; } @@ -83,8 +119,8 @@ if ( !class_exists('Puc_v4p3_Vcs_GitHubApi', false) ): $tag = $versionTags[0]; return new Puc_v4p3_Vcs_Reference(array( - 'name' => $tag->name, - 'version' => ltrim($tag->name, 'v'), + 'name' => $tag->name, + 'version' => ltrim($tag->name, 'v'), 'downloadUrl' => $this->signDownloadUrl($tag->zipball_url), 'apiResponse' => $tag, )); @@ -103,7 +139,7 @@ if ( !class_exists('Puc_v4p3_Vcs_GitHubApi', false) ): } $reference = new Puc_v4p3_Vcs_Reference(array( - 'name' => $branch->name, + 'name' => $branch->name, 'downloadUrl' => $this->buildArchiveDownloadUrl($branch->name), 'apiResponse' => $branch, )); @@ -127,7 +163,7 @@ if ( !class_exists('Puc_v4p3_Vcs_GitHubApi', false) ): '/repos/:user/:repo/commits', array( 'path' => $filename, - 'sha' => $ref, + 'sha' => $ref, ) ); if ( !is_wp_error($commits) && is_array($commits) && isset($commits[0]) ) { @@ -302,6 +338,76 @@ if ( !class_exists('Puc_v4p3_Vcs_GitHubApi', false) ): return add_query_arg('access_token', $this->credentials, $url); } + /** + * Enable updating via release assets. + * + * If the latest release contains no usable assets, the update checker + * will fall back to using the automatically generated ZIP archive. + * + * Private repositories will only work with WordPress 3.7 or later. + * + * @param string|null $fileNameRegex Optional. Use only those assets where the file name matches this regex. + */ + public function enableReleaseAssets($fileNameRegex = null) { + $this->releaseAssetsEnabled = true; + $this->assetFilterRegex = $fileNameRegex; + $this->assetApiBaseUrl = sprintf( + '//api.github.com/repos/%1$s/%2$s/releases/assets/', + $this->userName, + $this->repositoryName + ); + + //Optimization: Instead of filtering all HTTP requests, let's do it only when + //WordPress is about to download an update. + add_filter('upgrader_pre_download', array($this, 'addHttpRequestFilter'), 10, 1); //WP 3.7+ + } + + /** + * Does this asset match the file name regex? + * + * @param stdClass $releaseAsset + * @return bool + */ + protected function matchesAssetFilter($releaseAsset) { + if ( $this->assetFilterRegex === null ) { + //The default is to accept all assets. + return true; + } + return isset($releaseAsset->name) && preg_match($this->assetFilterRegex, $releaseAsset->name); + } + + /** + * @internal + * @param bool $result + * @return bool + */ + public function addHttpRequestFilter($result) { + static $filterAdded = false; + if ( $this->releaseAssetsEnabled && !$filterAdded && $this->isAuthenticationEnabled() ) { + add_filter('http_request_args', array($this, 'setReleaseDownloadHeader'), 10, 2); + $filterAdded = true; + } + return $result; + } + + /** + * Set the HTTP header that's necessary to download private release assets. + * + * See GitHub docs: + * @link https://developer.github.com/v3/repos/releases/#get-a-single-release-asset + * + * @internal + * @param array $requestArgs + * @param string $url + * @return array + */ + public function setReleaseDownloadHeader($requestArgs, $url = '') { + //Is WordPress trying to download one of our assets? + if ( strpos($url, $this->assetApiBaseUrl) !== false ) { + $requestArgs['headers']['accept'] = 'application/octet-stream'; + } + return $requestArgs; + } } endif; \ No newline at end of file diff --git a/Puc/v4p3/Vcs/PluginUpdateChecker.php b/Puc/v4p3/Vcs/PluginUpdateChecker.php index ec83a40..7aa7601 100644 --- a/Puc/v4p3/Vcs/PluginUpdateChecker.php +++ b/Puc/v4p3/Vcs/PluginUpdateChecker.php @@ -192,6 +192,10 @@ if ( !class_exists('Puc_v4p3_Vcs_PluginUpdateChecker') ): return $this; } + public function getVcsApi() { + return $this->api; + } + public function getUpdate() { $update = parent::getUpdate(); diff --git a/Puc/v4p3/Vcs/ThemeUpdateChecker.php b/Puc/v4p3/Vcs/ThemeUpdateChecker.php index ea5149a..078822e 100644 --- a/Puc/v4p3/Vcs/ThemeUpdateChecker.php +++ b/Puc/v4p3/Vcs/ThemeUpdateChecker.php @@ -93,6 +93,10 @@ if ( !class_exists('Puc_v4p3_Vcs_ThemeUpdateChecker', false) ): return $this; } + public function getVcsApi() { + return $this->api; + } + public function getUpdate() { $update = parent::getUpdate();