diff --git a/plugin-update-checker/.gitattributes b/plugin-update-checker/.gitattributes new file mode 100644 index 00000000..ba74e788 --- /dev/null +++ b/plugin-update-checker/.gitattributes @@ -0,0 +1 @@ +/build export-ignore diff --git a/plugin-update-checker/Puc/v5/PucFactory.php b/plugin-update-checker/Puc/v5/PucFactory.php index 9ea49a52..3cda059d 100644 --- a/plugin-update-checker/Puc/v5/PucFactory.php +++ b/plugin-update-checker/Puc/v5/PucFactory.php @@ -4,7 +4,7 @@ if ( !class_exists(PucFactory::class, false) ): - class PucFactory extends \YahnisElsts\PluginUpdateChecker\v5p0\PucFactory { + class PucFactory extends \YahnisElsts\PluginUpdateChecker\v5p1\PucFactory { } endif; diff --git a/plugin-update-checker/Puc/v5p0/Autoloader.php b/plugin-update-checker/Puc/v5p1/Autoloader.php similarity index 98% rename from plugin-update-checker/Puc/v5p0/Autoloader.php rename to plugin-update-checker/Puc/v5p1/Autoloader.php index dfd1ee6a..ecdede90 100644 --- a/plugin-update-checker/Puc/v5p0/Autoloader.php +++ b/plugin-update-checker/Puc/v5p1/Autoloader.php @@ -1,6 +1,6 @@ ' . htmlentities(print_r($value, true)) . ''; } else if ($value === null) { $value = 'null'; } - printf('%1$s %2$s', $name, $value); + printf( + '%1$s %2$s', + esc_html($name), + //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Escaped above. + $value + ); } } diff --git a/plugin-update-checker/Puc/v5p0/DebugBar/PluginExtension.php b/plugin-update-checker/Puc/v5p1/DebugBar/PluginExtension.php similarity index 90% rename from plugin-update-checker/Puc/v5p0/DebugBar/PluginExtension.php rename to plugin-update-checker/Puc/v5p1/DebugBar/PluginExtension.php index feaf2ff4..21fbe0b5 100644 --- a/plugin-update-checker/Puc/v5p0/DebugBar/PluginExtension.php +++ b/plugin-update-checker/Puc/v5p1/DebugBar/PluginExtension.php @@ -1,8 +1,8 @@ updateChecker->triggerError( sprintf( - "Can't to read the Version header for '%s'. The filename is incorrect or is not a plugin.", + "Cannot read the Version header for '%s'. The filename is incorrect or is not a plugin.", $this->updateChecker->pluginFile ), E_USER_WARNING diff --git a/plugin-update-checker/Puc/v5p0/Plugin/PluginInfo.php b/plugin-update-checker/Puc/v5p1/Plugin/PluginInfo.php similarity index 97% rename from plugin-update-checker/Puc/v5p0/Plugin/PluginInfo.php rename to plugin-update-checker/Puc/v5p1/Plugin/PluginInfo.php index 45700f90..db8e51ef 100644 --- a/plugin-update-checker/Puc/v5p0/Plugin/PluginInfo.php +++ b/plugin-update-checker/Puc/v5p1/Plugin/PluginInfo.php @@ -1,7 +1,7 @@ checkPeriod . 'hours'; + //phpcs:ignore WordPress.WP.CronInterval.ChangeDetected -- WPCS fails to parse the callback. add_filter('cron_schedules', array($this, '_addCustomSchedule')); } @@ -79,6 +80,7 @@ public function __construct($updateChecker, $checkPeriod, $hourlyHooks = array(' //Like WordPress itself, we check more often on certain pages. /** @see wp_update_plugins */ add_action('load-update-core.php', array($this, 'maybeCheckForUpdates')); + //phpcs:ignore Squiz.PHP.CommentedOutCode.Found -- Not actually code, just file names. //"load-update.php" and "load-plugins.php" or "load-themes.php". $this->hourlyCheckHooks = array_merge($this->hourlyCheckHooks, $hourlyHooks); foreach($this->hourlyCheckHooks as $hook) { diff --git a/plugin-update-checker/Puc/v5p0/StateStore.php b/plugin-update-checker/Puc/v5p1/StateStore.php similarity index 95% rename from plugin-update-checker/Puc/v5p0/StateStore.php rename to plugin-update-checker/Puc/v5p1/StateStore.php index ec26969f..b6f6cc10 100644 --- a/plugin-update-checker/Puc/v5p0/StateStore.php +++ b/plugin-update-checker/Puc/v5p1/StateStore.php @@ -1,5 +1,5 @@ updateClass; } - if ( ($updateClass !== null) && class_exists($updateClass) ) { - $this->update = call_user_func(array($updateClass, 'fromObject'), $state->update); + $factory = array($updateClass, 'fromObject'); + if ( ($updateClass !== null) && is_callable($factory) ) { + $this->update = call_user_func($factory, $state->update); } } } diff --git a/plugin-update-checker/Puc/v5p0/Theme/Package.php b/plugin-update-checker/Puc/v5p1/Theme/Package.php similarity index 93% rename from plugin-update-checker/Puc/v5p0/Theme/Package.php rename to plugin-update-checker/Puc/v5p1/Theme/Package.php index d6776144..030046ba 100644 --- a/plugin-update-checker/Puc/v5p0/Theme/Package.php +++ b/plugin-update-checker/Puc/v5p1/Theme/Package.php @@ -1,7 +1,7 @@ 10, //seconds + 'timeout' => wp_doing_cron() ? 10 : 3, 'headers' => array( 'Accept' => 'application/json', ), diff --git a/plugin-update-checker/Puc/v5p0/UpgraderStatus.php b/plugin-update-checker/Puc/v5p1/UpgraderStatus.php similarity index 98% rename from plugin-update-checker/Puc/v5p0/UpgraderStatus.php rename to plugin-update-checker/Puc/v5p1/UpgraderStatus.php index f95100dd..2dc67364 100644 --- a/plugin-update-checker/Puc/v5p0/UpgraderStatus.php +++ b/plugin-update-checker/Puc/v5p1/UpgraderStatus.php @@ -1,5 +1,5 @@ oauth->sign($url,'GET'); } - $options = array('timeout' => 10); + $options = array('timeout' => wp_doing_cron() ? 10 : 3); if ( !empty($this->httpFilterName) ) { $options = apply_filters($this->httpFilterName, $options); } diff --git a/plugin-update-checker/Puc/v5p0/Vcs/GitHubApi.php b/plugin-update-checker/Puc/v5p1/Vcs/GitHubApi.php similarity index 72% rename from plugin-update-checker/Puc/v5p0/Vcs/GitHubApi.php rename to plugin-update-checker/Puc/v5p1/Vcs/GitHubApi.php index a1ca4c58..364584b8 100644 --- a/plugin-update-checker/Puc/v5p0/Vcs/GitHubApi.php +++ b/plugin-update-checker/Puc/v5p1/Vcs/GitHubApi.php @@ -1,11 +1,15 @@ api('/repos/:user/:repo/releases/latest'); - if ( is_wp_error($release) || !is_object($release) || !isset($release->tag_name) ) { - return null; + //The "latest release" endpoint returns one release and always skips pre-releases, + //so we can only use it if that's compatible with the current filter settings. + if ( + $this->shouldSkipPreReleases() + && ( + ($this->releaseFilterMaxReleases === 1) || !$this->hasCustomReleaseFilter() + ) + ) { + //Just get the latest release. + $release = $this->api('/repos/:user/:repo/releases/latest'); + if ( is_wp_error($release) || !is_object($release) || !isset($release->tag_name) ) { + return null; + } + $foundReleases = array($release); + } else { + //Get a list of the most recent releases. + $foundReleases = $this->api( + '/repos/:user/:repo/releases', + array('per_page' => $this->releaseFilterMaxReleases) + ); + if ( is_wp_error($foundReleases) || !is_array($foundReleases) ) { + return null; + } } - $reference = new Reference(array( - 'name' => $release->tag_name, - 'version' => ltrim($release->tag_name, 'v'), //Remove the "v" prefix from "v1.2.3". - 'downloadUrl' => $release->zipball_url, - 'updated' => $release->created_at, - 'apiResponse' => $release, - )); + foreach ($foundReleases as $release) { + //Always skip drafts. + if ( isset($release->draft) && !empty($release->draft) ) { + continue; + } - if ( isset($release->assets[0]) ) { - $reference->downloadCount = $release->assets[0]->download_count; - } + //Skip pre-releases unless specifically included. + if ( + $this->shouldSkipPreReleases() + && isset($release->prerelease) + && !empty($release->prerelease) + ) { + continue; + } + + $versionNumber = ltrim($release->tag_name, 'v'); //Remove the "v" prefix from "v1.2.3". + + //Custom release filtering. + if ( !$this->matchesCustomReleaseFilter($versionNumber, $release) ) { + continue; + } + + $reference = new Reference(array( + 'name' => $release->tag_name, + 'version' => $versionNumber, + 'downloadUrl' => $release->zipball_url, + '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_values(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 setUpdateDownloadHeaders() - */ - $reference->downloadUrl = $matchingAssets[0]->url; + if ( $this->releaseAssetsEnabled ) { + //Use the first release asset that matches the specified regular expression. + if ( isset($release->assets, $release->assets[0]) ) { + $matchingAssets = array_values(array_filter($release->assets, array($this, 'matchesAssetFilter'))); } 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; + $matchingAssets = array(); } - $reference->downloadCount = $matchingAssets[0]->download_count; + if ( !empty($matchingAssets) ) { + if ( $this->isAuthenticationEnabled() ) { + /** + * Keep in mind that we'll need to add an "Accept" header to download this asset. + * + * @see setUpdateDownloadHeaders() + */ + $reference->downloadUrl = $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; + } else if ( $this->releaseAssetPreference === Api::REQUIRE_RELEASE_ASSETS ) { + //None of the assets match the filter, and we're not allowed + //to fall back to the auto-generated source ZIP. + return null; + } } - } - if ( !empty($release->body) ) { - $reference->changelog = Parsedown::instance()->text($release->body); + if ( !empty($release->body) ) { + $reference->changelog = Parsedown::instance()->text($release->body); + } + + return $reference; } - return $reference; + return null; } /** @@ -205,7 +248,7 @@ protected function api($url, $queryParams = array()) { $baseUrl = $url; $url = $this->buildApiUrl($url, $queryParams); - $options = array('timeout' => 10); + $options = array('timeout' => wp_doing_cron() ? 10 : 3); if ( $this->isAuthenticationEnabled() ) { $options['headers'] = array('Authorization' => $this->getAuthorizationHeader()); } @@ -315,7 +358,7 @@ public function setAuthentication($credentials) { protected function getUpdateDetectionStrategies($configBranch) { $strategies = array(); - if ( $configBranch === 'master' || $configBranch === 'main' ) { + if ( $configBranch === 'master' || $configBranch === 'main') { //Use the latest release. $strategies[self::STRATEGY_LATEST_RELEASE] = array($this, 'getLatestRelease'); //Failing that, use the tag with the highest version number. @@ -323,7 +366,7 @@ protected function getUpdateDetectionStrategies($configBranch) { } //Alternatively, just use the branch itself. - $strategies[self::STRATEGY_BRANCH] = function() use ($configBranch) { + $strategies[self::STRATEGY_BRANCH] = function () use ($configBranch) { return $this->getBranch($configBranch); }; @@ -331,43 +374,29 @@ protected function getUpdateDetectionStrategies($configBranch) { } /** - * 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. + * Get the unchanging part of a release asset URL. Used to identify download attempts. * - * @param string|null $fileNameRegex Optional. Use only those assets where the file name matches this regex. + * @return string */ - public function enableReleaseAssets($fileNameRegex = null) { - $this->releaseAssetsEnabled = true; - $this->assetFilterRegex = $fileNameRegex; - $this->assetApiBaseUrl = sprintf( + protected function getAssetApiBaseUrl() { + return sprintf( '//api.github.com/repos/%1$s/%2$s/releases/assets/', $this->userName, $this->repositoryName ); } - /** - * 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; + protected function getFilterableAssetName($releaseAsset) { + if ( isset($releaseAsset->name) ) { + return $releaseAsset->name; } - return isset($releaseAsset->name) && preg_match($this->assetFilterRegex, $releaseAsset->name); + return null; } /** - * @internal * @param bool $result * @return bool + * @internal */ public function addHttpRequestFilter($result) { if ( !$this->downloadFilterAdded && $this->isAuthenticationEnabled() ) { @@ -383,6 +412,7 @@ public function addHttpRequestFilter($result) { * Set the HTTP headers that are necessary to download updates from private repositories. * * See GitHub docs: + * * @link https://developer.github.com/v3/repos/releases/#get-a-single-release-asset * @link https://developer.github.com/v3/auth/#basic-authentication * @@ -393,7 +423,7 @@ public function addHttpRequestFilter($result) { */ public function setUpdateDownloadHeaders($requestArgs, $url = '') { //Is WordPress trying to download one of our release assets? - if ( $this->releaseAssetsEnabled && (strpos($url, $this->assetApiBaseUrl) !== false) ) { + if ( $this->releaseAssetsEnabled && (strpos($url, $this->getAssetApiBaseUrl()) !== false) ) { $requestArgs['headers']['Accept'] = 'application/octet-stream'; } //Use Basic authentication, but only if the download is from our repository. @@ -409,9 +439,9 @@ public function setUpdateDownloadHeaders($requestArgs, $url = '') { * the authorization header to other hosts. We don't want that because it breaks * AWS downloads and can leak authorization information. * - * @internal * @param string $location * @param array $headers + * @internal */ public function removeAuthHeaderFromRedirects(&$location, &$headers) { $repoApiBaseUrl = $this->buildApiUrl('/repos/:user/:repo/', array()); diff --git a/plugin-update-checker/Puc/v5p0/Vcs/GitLabApi.php b/plugin-update-checker/Puc/v5p1/Vcs/GitLabApi.php similarity index 76% rename from plugin-update-checker/Puc/v5p0/Vcs/GitLabApi.php rename to plugin-update-checker/Puc/v5p1/Vcs/GitLabApi.php index b43b4cef..663c2e7c 100644 --- a/plugin-update-checker/Puc/v5p0/Vcs/GitLabApi.php +++ b/plugin-update-checker/Puc/v5p1/Vcs/GitLabApi.php @@ -1,9 +1,13 @@ api('/:id/releases'); + $releases = $this->api('/:id/releases', array('per_page' => $this->releaseFilterMaxReleases)); if ( is_wp_error($releases) || empty($releases) || !is_array($releases) ) { return null; } foreach ($releases as $release) { - if ( true !== $release->upcoming_release ) { - break 1; //Break the loop on the first release we find that isn't an upcoming release + if ( + //Skip invalid/unsupported releases. + !is_object($release) + || !isset($release->tag_name) + //Skip upcoming releases. + || ( + !empty($release->upcoming_release) + && $this->shouldSkipPreReleases() + ) + ) { + continue; } - } - if ( is_wp_error($release) || !is_object($release) || !isset($release->tag_name) ) { - return null; - } - $reference = new Reference(array( - 'name' => $release->tag_name, - 'version' => ltrim($release->tag_name, 'v'), //Remove the "v" prefix from "v1.2.3". - 'downloadUrl' => '', - 'updated' => $release->released_at, - 'apiResponse' => $release, - )); - $download_url = false; - - if ( $this->releasePackageEnabled && isset($release->assets, $release->assets->links) ) { - /** - * Use the first asset LINK that is a zip format file generated by a Gitlab Release Pipeline - * - * @link https://gist.github.com/timwiel/9dfd3526c768efad4973254085e065ce - */ - foreach ($release->assets->links as $link) { - //TODO: Check the "format" property instead of the URL suffix. - if ( 'zip' === substr($link->url, -3) ) { - $download_url = $link->url; - break 1; - } + $versionNumber = ltrim($release->tag_name, 'v'); //Remove the "v" prefix from "v1.2.3". + + //Apply custom filters. + if ( !$this->matchesCustomReleaseFilter($versionNumber, $release) ) { + continue; } - if ( empty( $download_url ) ) { + + $downloadUrl = $this->findReleaseDownloadUrl($release); + if ( empty($downloadUrl) ) { + //The latest release doesn't have valid download URL. return null; } - if ( ! empty( $this->accessToken ) ) { - $download_url = add_query_arg('private_token', $this->accessToken, $download_url); + + if ( !empty($this->accessToken) ) { + $downloadUrl = add_query_arg('private_token', $this->accessToken, $downloadUrl); } - $reference->downloadUrl = $download_url; - return $reference; - - } elseif ( isset($release->assets) ) { - /** - * Use the first asset SOURCE file that is a zip format from a Gitlab Release which should be a zip file - */ - foreach ($release->assets->sources as $source) { - if ( 'zip' === $source->format ) { - $download_url = $source->url; - break 1; + + return new Reference(array( + 'name' => $release->tag_name, + 'version' => $versionNumber, + 'downloadUrl' => $downloadUrl, + 'updated' => $release->released_at, + 'apiResponse' => $release, + )); + } + + return null; + } + + /** + * @param object $release + * @return string|null + */ + protected function findReleaseDownloadUrl($release) { + if ( $this->releaseAssetsEnabled ) { + if ( isset($release->assets, $release->assets->links) ) { + //Use the first asset link where the URL matches the filter. + foreach ($release->assets->links as $link) { + if ( $this->matchesAssetFilter($link) ) { + return $link->url; + } } } - if ( empty( $download_url ) ) { + + if ( $this->releaseAssetPreference === Api::REQUIRE_RELEASE_ASSETS ) { + //Falling back to source archives is not allowed, so give up. return null; } - if ( ! empty( $this->accessToken ) ) { - $download_url = add_query_arg('private_token', $this->accessToken, $download_url); - } - $reference->downloadUrl = $download_url; - return $reference; + } + //Use the first source code archive that's in ZIP format. + foreach ($release->assets->sources as $source) { + if ( isset($source->format) && ($source->format === 'zip') ) { + return $source->url; + } } - //If we get this far without a return then obviosuly noi release download urls were found return null; - } + } /** * Get the tag that looks like the highest version number. @@ -251,7 +260,7 @@ protected function api($url, $queryParams = array()) { $baseUrl = $url; $url = $this->buildApiUrl($url, $queryParams); - $options = array('timeout' => 10); + $options = array('timeout' => wp_doing_cron() ? 10 : 3); if ( !empty($this->httpFilterName) ) { $options = apply_filters($this->httpFilterName, $options); } @@ -365,7 +374,7 @@ protected function getUpdateDetectionStrategies($configBranch) { $strategies[self::STRATEGY_LATEST_TAG] = array($this, 'getLatestTag'); } - $strategies[self::STRATEGY_BRANCH] = function() use ($configBranch) { + $strategies[self::STRATEGY_BRANCH] = function () use ($configBranch) { return $this->getBranch($configBranch); }; @@ -377,16 +386,29 @@ public function setAuthentication($credentials) { $this->accessToken = is_string($credentials) ? $credentials : null; } - public function enableReleaseAssets() { - $this->releaseAssetsEnabled = true; - $this->releasePackageEnabled = false; - } - + /** + * Use release assets that link to GitLab generic packages (e.g. .zip files) + * instead of automatically generated source archives. + * + * This is included for backwards compatibility with older versions of PUC. + * + * @return void + * @deprecated Use enableReleaseAssets() instead. + * @noinspection PhpUnused -- Public API + */ public function enableReleasePackages() { - $this->releaseAssetsEnabled = false; - $this->releasePackageEnabled = true; + $this->enableReleaseAssets( + /** @lang RegExp */ '/\.zip($|[?&#])/i', + Api::REQUIRE_RELEASE_ASSETS + ); } + protected function getFilterableAssetName($releaseAsset) { + if ( isset($releaseAsset->url) ) { + return $releaseAsset->url; + } + return null; + } } endif; diff --git a/plugin-update-checker/Puc/v5p0/Vcs/PluginUpdateChecker.php b/plugin-update-checker/Puc/v5p1/Vcs/PluginUpdateChecker.php similarity index 98% rename from plugin-update-checker/Puc/v5p0/Vcs/PluginUpdateChecker.php rename to plugin-update-checker/Puc/v5p1/Vcs/PluginUpdateChecker.php index aa1dfae9..082a847f 100644 --- a/plugin-update-checker/Puc/v5p0/Vcs/PluginUpdateChecker.php +++ b/plugin-update-checker/Puc/v5p1/Vcs/PluginUpdateChecker.php @@ -1,8 +1,8 @@ releaseAssetsEnabled = true; + $this->assetFilterRegex = $nameRegex; + $this->releaseAssetPreference = $preference; + } + + /** + * Disable release assets. + * + * @return void + * @noinspection PhpUnused -- Public API + */ + public function disableReleaseAssets() { + $this->releaseAssetsEnabled = false; + $this->assetFilterRegex = null; + } + + /** + * Does the specified asset match the name regex? + * + * @param mixed $releaseAsset Data type and structure depend on the host/API. + * @return bool + */ + protected function matchesAssetFilter($releaseAsset) { + if ( $this->assetFilterRegex === null ) { + //The default is to accept all assets. + return true; + } + + $name = $this->getFilterableAssetName($releaseAsset); + if ( !is_string($name) ) { + return false; + } + return (bool)preg_match($this->assetFilterRegex, $releaseAsset->name); + } + + /** + * Get the part of asset data that will be checked against the filter regex. + * + * @param mixed $releaseAsset + * @return string|null + */ + abstract protected function getFilterableAssetName($releaseAsset); + } + +endif; \ No newline at end of file diff --git a/plugin-update-checker/Puc/v5p1/Vcs/ReleaseFilteringFeature.php b/plugin-update-checker/Puc/v5p1/Vcs/ReleaseFilteringFeature.php new file mode 100644 index 00000000..12ef54ed --- /dev/null +++ b/plugin-update-checker/Puc/v5p1/Vcs/ReleaseFilteringFeature.php @@ -0,0 +1,108 @@ + 100 ) { + throw new \InvalidArgumentException(sprintf( + 'The max number of releases is too high (%d). It must be 100 or less.', + $maxReleases + )); + } else if ( $maxReleases < 1 ) { + throw new \InvalidArgumentException(sprintf( + 'The max number of releases is too low (%d). It must be at least 1.', + $maxReleases + )); + } + + $this->releaseFilterCallback = $callback; + $this->releaseFilterByType = $releaseTypes; + $this->releaseFilterMaxReleases = $maxReleases; + return $this; + } + + /** + * Filter releases by their version number. + * + * @param string $regex A regular expression. The release version number must match this regex. + * @param int $releaseTypes + * @param int $maxReleasesToExamine + * @return $this + * @noinspection PhpUnused -- Public API + */ + public function setReleaseVersionFilter( + $regex, + $releaseTypes = Api::RELEASE_FILTER_SKIP_PRERELEASE, + $maxReleasesToExamine = 20 + ) { + return $this->setReleaseFilter( + function ($versionNumber) use ($regex) { + return (preg_match($regex, $versionNumber) === 1); + }, + $releaseTypes, + $maxReleasesToExamine + ); + } + + /** + * @param string $versionNumber The detected release version number. + * @param object $releaseObject Varies depending on the host/API. + * @return bool + */ + protected function matchesCustomReleaseFilter($versionNumber, $releaseObject) { + if ( !is_callable($this->releaseFilterCallback) ) { + return true; //No custom filter. + } + return call_user_func($this->releaseFilterCallback, $versionNumber, $releaseObject); + } + + /** + * @return bool + */ + protected function shouldSkipPreReleases() { + //Maybe this could be a bitfield in the future, if we need to support + //more release types. + return ($this->releaseFilterByType !== Api::RELEASE_FILTER_ALL); + } + + /** + * @return bool + */ + protected function hasCustomReleaseFilter() { + return isset($this->releaseFilterCallback) && is_callable($this->releaseFilterCallback); + } + } + +endif; \ No newline at end of file diff --git a/plugin-update-checker/Puc/v5p0/Vcs/ThemeUpdateChecker.php b/plugin-update-checker/Puc/v5p1/Vcs/ThemeUpdateChecker.php similarity index 94% rename from plugin-update-checker/Puc/v5p0/Vcs/ThemeUpdateChecker.php rename to plugin-update-checker/Puc/v5p1/Vcs/ThemeUpdateChecker.php index b8624c6b..c871ce9f 100644 --- a/plugin-update-checker/Puc/v5p0/Vcs/ThemeUpdateChecker.php +++ b/plugin-update-checker/Puc/v5p1/Vcs/ThemeUpdateChecker.php @@ -1,9 +1,9 @@ setBranch('stable-branch-name'); - ``` - - Caveats: - - If you set the branch to `main` (the default) or `master` (the historical default), the update checker will look for recent releases and tags first. It'll only use the `main` or `master` branch if it doesn't find anything else suitable. - -2. **GitLab Releases using Generic Packages**: - - Use a Gitlab CI/CD Pipeline to automatically generate your update on release using a Generic Package. The benefit of using Generic Package assets over the Source Code assets is that the code can already be built and production ready. - - Add the following code: - ```php - //Add the following code to your main plugin file or `functions.php` file to check for a new update from releases using generic packages - $myUpdateChecker->getVcsApi()->enableReleasePackages(); - ``` - - PUC will periodically check the release version (i.e. the tag name of the release) and will display a notification if the release is a greater version than the installed version. - - The release tag name should loosely follow [SemVer](https://semver.org/) but these are all valid release names: `v1.2.3`, `v1.2-foo`, `1.2.3_rc1-ABC`, `1.2.3.4.5` However, be warned that it's not smart enough to filter out alpha/beta/RC versions. If that's a problem, you might want to use GitLab branches instead. - - For more information about *Gitlab Release Generic Packages* refer to the following links: + +A GitLab repository can be checked for updates in 3 different ways. + +- **GitLab releases** + + Create a new release using the "Releases" feature on GitLab. The tag name should match the version number. You can add a `v` prefix to the tag, like `v1.2.3`. Releases that are marked as ["Upcoming Release"](https://docs.gitlab.com/ee/user/project/releases/index.html#upcoming-releases) will be automatically ignored. + + If you want to use custom release assets, call the `enableReleaseAssets()` method after creating the update checker instance: + ```php + $myUpdateChecker->getVcsApi()->enableReleaseAssets(); + ``` + + By default, PUC will use the first available asset link, regardless of type. You can pass a regular expression to `enableReleaseAssets()` to make it pick the first link where the URL matches the regex. For example: + ```php + $myUpdateChecker->getVcsApi()->enableReleaseAssets('/\.zip($|[?&#])/i'); + ``` + + **Tip:** You can use a Gitlab CI/CD Pipeline to automatically generate your update on release using a Generic Package. For more information about generic packages, refer to the following links: - [Gitlab CI/CD Release Documentation](https://docs.gitlab.com/ee/user/project/releases/#create-release-from-gitlab-ci) - [Gitlab Release Assets as Generic Package Documentation](https://gitlab.com/gitlab-org/release-cli/-/tree/master/docs/examples/release-assets-as-generic-package/) - [Example .gitlab-ci.yml file using Release Generic Packages for generating a update package from the Sensei-LMS wordpress plugin](https://gist.github.com/timwiel/9dfd3526c768efad4973254085e065ce) +- **Tags** -3. **GitLab Releases using Source Code Assets**: - - Create a new release using the "Releases" feature on Gitlab. - - Add the following code: - ```php - //Add the following code to your main plugin file or `functions.php` file to check for a new update from releases using release assets - $myUpdateChecker->getVcsApi()->enableReleaseAssets(); - ``` - - PUC will periodically check the release version (based on release tag name) and display a notification if the release version is greater than the installed version. - - The release name should loosely follow [SemVer](https://semver.org/) but these are all valid release names: `v1.2.3`, `v1.2-foo`, `1.2.3_rc1-ABC`, `1.2.3.4.5` However, be warned that it's not smart enough to filter out alpha/beta/RC versions. If that's a problem, you might want to use GitLab branches instead. + To release version 1.2.3, create a new Git tag named `v1.2.3` or `1.2.3`. The update checker will look at recent tags and use the one that looks like the highest version number. + + PUC doesn't require strict adherence to [SemVer](https://semver.org/). However, be warned that it's not smart enough to filter out alpha/beta/RC versions. If that's a problem, you might want to use GitLab branches instead. +- **Stable branch** -4. **Tags** (this is the default option): - - To release version 1.2.3, create a new Git tag named `v1.2.3` or `1.2.3`. - - Optionally, add the following code: - ```php - //Add the following code to your main plugin file or `functions.php` file to check for updates from the default branch - $myUpdateChecker->setBranch('master'); //or 'main' - ``` - - PUC doesn't require strict adherence to [SemVer](https://semver.org/). These are all valid tag names: `v1.2.3`, `v1.2-foo`, `1.2.3_rc1-ABC`, `1.2.3.4.5`. However, be warned that it's not smart enough to filter out alpha/beta/RC versions. If that's a problem, you might want to use GitLab branches instead. + Point the update checker at any stable, production-ready branch: + ```php + $myUpdateChecker->setBranch('stable-branch-name'); + ``` + PUC will periodically check the `Version` header in the main plugin file or `style.css` and display a notification if it's greater than the installed version. Caveat: Even if you set the branch to `main` (the default) or `master` (the historical default), the update checker will still look for recent releases and tags first. Migrating from 4.x ------------------ @@ -358,14 +347,14 @@ Other classes have also been renamed, usually by simply removing the `Puc_vXpY_` | Old class name | New class name | |-------------------------------------|----------------------------------------------------------------| | `Puc_v4_Factory` | `YahnisElsts\PluginUpdateChecker\v5\PucFactory` | -| `Puc_v4p13_Factory` | `YahnisElsts\PluginUpdateChecker\v5p0\PucFactory` | -| `Puc_v4p13_Plugin_UpdateChecker` | `YahnisElsts\PluginUpdateChecker\v5p0\Plugin\UpdateChecker` | -| `Puc_v4p13_Theme_UpdateChecker` | `YahnisElsts\PluginUpdateChecker\v5p0\Theme\UpdateChecker` | -| `Puc_v4p13_Vcs_PluginUpdateChecker` | `YahnisElsts\PluginUpdateChecker\v5p0\Vcs\PluginUpdateChecker` | -| `Puc_v4p13_Vcs_ThemeUpdateChecker` | `YahnisElsts\PluginUpdateChecker\v5p0\Vcs\ThemeUpdateChecker` | -| `Puc_v4p13_Vcs_GitHubApi` | `YahnisElsts\PluginUpdateChecker\v5p0\Vcs\GitHubApi` | -| `Puc_v4p13_Vcs_GitLabApi` | `YahnisElsts\PluginUpdateChecker\v5p0\Vcs\GitLabApi` | -| `Puc_v4p13_Vcs_BitBucketApi` | `YahnisElsts\PluginUpdateChecker\v5p0\Vcs\BitBucketApi` | +| `Puc_v4p13_Factory` | `YahnisElsts\PluginUpdateChecker\v5p1\PucFactory` | +| `Puc_v4p13_Plugin_UpdateChecker` | `YahnisElsts\PluginUpdateChecker\v5p1\Plugin\UpdateChecker` | +| `Puc_v4p13_Theme_UpdateChecker` | `YahnisElsts\PluginUpdateChecker\v5p1\Theme\UpdateChecker` | +| `Puc_v4p13_Vcs_PluginUpdateChecker` | `YahnisElsts\PluginUpdateChecker\v5p1\Vcs\PluginUpdateChecker` | +| `Puc_v4p13_Vcs_ThemeUpdateChecker` | `YahnisElsts\PluginUpdateChecker\v5p1\Vcs\ThemeUpdateChecker` | +| `Puc_v4p13_Vcs_GitHubApi` | `YahnisElsts\PluginUpdateChecker\v5p1\Vcs\GitHubApi` | +| `Puc_v4p13_Vcs_GitLabApi` | `YahnisElsts\PluginUpdateChecker\v5p1\Vcs\GitLabApi` | +| `Puc_v4p13_Vcs_BitBucketApi` | `YahnisElsts\PluginUpdateChecker\v5p1\Vcs\BitBucketApi` | License Management ------------------ diff --git a/plugin-update-checker/composer.json b/plugin-update-checker/composer.json index 0589f014..1a71ce83 100644 --- a/plugin-update-checker/composer.json +++ b/plugin-update-checker/composer.json @@ -18,6 +18,6 @@ "ext-json": "*" }, "autoload": { - "files": ["load-v5p0.php"] + "files": ["load-v5p1.php"] } } diff --git a/plugin-update-checker/examples/plugin.json b/plugin-update-checker/examples/plugin.json index fea211a1..946b7307 100644 --- a/plugin-update-checker/examples/plugin.json +++ b/plugin-update-checker/examples/plugin.json @@ -1,52 +1,52 @@ -{ - "name": "My Example Plugin", - "version": "2.0", - "download_url": "http://example.com/updates/example-plugin.zip", - - "homepage": "http://example.com/", - "requires": "4.5", - "tested": "4.8", - "last_updated": "2017-01-01 16:17:00", - "upgrade_notice": "Here's why you should upgrade...", - - "author": "Janis Elsts", - "author_homepage": "http://example.com/", - - "sections": { - "description": "(Required) Plugin description. Basic HTML can be used in all sections.", - "installation": "(Recommended) Installation instructions.", - "changelog": "(Recommended) Changelog.

This section will be displayed by default when the user clicks 'View version x.y.z details'.

", - "custom_section": "This is a custom section labeled 'Custom Section'." - }, - - "icons" : { - "1x" : "http://w-shadow.com/files/external-update-example/assets/icon-128x128.png", - "2x" : "http://w-shadow.com/files/external-update-example/assets/icon-256x256.png" - }, - - "banners": { - "low": "http://w-shadow.com/files/external-update-example/assets/banner-772x250.png", - "high": "http://w-shadow.com/files/external-update-example/assets/banner-1544x500.png" - }, - - "translations": [ - { - "language": "fr_FR", - "version": "4.0", - "updated": "2016-04-22 23:22:42", - "package": "http://example.com/updates/translations/french-language-pack.zip" - }, - { - "language": "de_DE", - "version": "5.0", - "updated": "2016-04-22 23:22:42", - "package": "http://example.com/updates/translations/german-language-pack.zip" - } - ], - - "rating": 90, - "num_ratings": 123, - - "downloaded": 1234, - "active_installs": 12345 +{ + "name": "My Example Plugin", + "version": "2.0", + "download_url": "http://example.com/updates/example-plugin.zip", + + "homepage": "http://example.com/", + "requires": "4.5", + "tested": "4.8", + "last_updated": "2017-01-01 16:17:00", + "upgrade_notice": "Here's why you should upgrade...", + + "author": "Janis Elsts", + "author_homepage": "http://example.com/", + + "sections": { + "description": "(Required) Plugin description. Basic HTML can be used in all sections.", + "installation": "(Recommended) Installation instructions.", + "changelog": "(Recommended) Changelog.

This section will be displayed by default when the user clicks 'View version x.y.z details'.

", + "custom_section": "This is a custom section labeled 'Custom Section'." + }, + + "icons" : { + "1x" : "http://w-shadow.com/files/external-update-example/assets/icon-128x128.png", + "2x" : "http://w-shadow.com/files/external-update-example/assets/icon-256x256.png" + }, + + "banners": { + "low": "http://w-shadow.com/files/external-update-example/assets/banner-772x250.png", + "high": "http://w-shadow.com/files/external-update-example/assets/banner-1544x500.png" + }, + + "translations": [ + { + "language": "fr_FR", + "version": "4.0", + "updated": "2016-04-22 23:22:42", + "package": "http://example.com/updates/translations/french-language-pack.zip" + }, + { + "language": "de_DE", + "version": "5.0", + "updated": "2016-04-22 23:22:42", + "package": "http://example.com/updates/translations/german-language-pack.zip" + } + ], + + "rating": 90, + "num_ratings": 123, + + "downloaded": 1234, + "active_installs": 12345 } \ No newline at end of file diff --git a/plugin-update-checker/examples/theme.json b/plugin-update-checker/examples/theme.json index df6c8c79..0e080725 100644 --- a/plugin-update-checker/examples/theme.json +++ b/plugin-update-checker/examples/theme.json @@ -1,5 +1,5 @@ -{ - "version": "2.0", - "details_url": "http://example.com/version-2.0-details.html", - "download_url": "http://example.com/example-theme-2.0.zip" +{ + "version": "2.0", + "details_url": "http://example.com/version-2.0-details.html", + "download_url": "http://example.com/example-theme-2.0.zip" } \ No newline at end of file diff --git a/plugin-update-checker/js/debug-bar.js b/plugin-update-checker/js/debug-bar.js index 9cb65a01..80f53f11 100644 --- a/plugin-update-checker/js/debug-bar.js +++ b/plugin-update-checker/js/debug-bar.js @@ -14,6 +14,8 @@ jQuery(function($) { _wpnonce: panel.data('nonce') }, function(data) { + //The response contains HTML that should already be escaped in server-side code. + //phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html responseBox.html(data); }, 'html' diff --git a/plugin-update-checker/languages/plugin-update-checker.pot b/plugin-update-checker/languages/plugin-update-checker.pot index ec7bc19a..edc8de4f 100644 --- a/plugin-update-checker/languages/plugin-update-checker.pot +++ b/plugin-update-checker/languages/plugin-update-checker.pot @@ -17,33 +17,33 @@ msgstr "" "X-Poedit-KeywordsList: __;_e;_x:1,2c;_x\n" "X-Poedit-SearchPath-0: .\n" -#: Puc/v5p0/Plugin/Ui.php:128 +#: Puc/v5p1/Plugin/Ui.php:128 msgid "Check for updates" msgstr "" -#: Puc/v5p0/Plugin/Ui.php:214 +#: Puc/v5p1/Plugin/Ui.php:214 #, php-format msgctxt "the plugin title" msgid "The %s plugin is up to date." msgstr "" -#: Puc/v5p0/Plugin/Ui.php:216 +#: Puc/v5p1/Plugin/Ui.php:216 #, php-format msgctxt "the plugin title" msgid "A new version of the %s plugin is available." msgstr "" -#: Puc/v5p0/Plugin/Ui.php:218 +#: Puc/v5p1/Plugin/Ui.php:218 #, php-format msgctxt "the plugin title" msgid "Could not determine if updates are available for %s." msgstr "" -#: Puc/v5p0/Plugin/Ui.php:224 +#: Puc/v5p1/Plugin/Ui.php:224 #, php-format msgid "Unknown update checker status \"%s\"" msgstr "" -#: Puc/v5p0/Vcs/PluginUpdateChecker.php:100 +#: Puc/v5p1/Vcs/PluginUpdateChecker.php:100 msgid "There is no changelog available." msgstr "" diff --git a/plugin-update-checker/load-v5p0.php b/plugin-update-checker/load-v5p1.php similarity index 80% rename from plugin-update-checker/load-v5p0.php rename to plugin-update-checker/load-v5p1.php index 7998cbfe..83cebcb6 100644 --- a/plugin-update-checker/load-v5p0.php +++ b/plugin-update-checker/load-v5p1.php @@ -1,14 +1,14 @@ $pucVersionedClass ) { - MajorFactory::addVersion($pucGeneralClass, $pucVersionedClass, '5.0'); + MajorFactory::addVersion($pucGeneralClass, $pucVersionedClass, '5.1'); //Also add it to the minor-version factory in case the major-version factory //was already defined by another, older version of the update checker. - MinorFactory::addVersion($pucGeneralClass, $pucVersionedClass, '5.0'); + MinorFactory::addVersion($pucGeneralClass, $pucVersionedClass, '5.1'); } diff --git a/plugin-update-checker/phpcs.xml b/plugin-update-checker/phpcs.xml new file mode 100644 index 00000000..e8260b99 --- /dev/null +++ b/plugin-update-checker/phpcs.xml @@ -0,0 +1,21 @@ + + + PHPCS settings for Plugin Update Checker + + + + + + + + ./ + + + + + + + + + ^vendor/* + diff --git a/plugin-update-checker/plugin-update-checker.php b/plugin-update-checker/plugin-update-checker.php index 1d892f3f..ebf10bc7 100644 --- a/plugin-update-checker/plugin-update-checker.php +++ b/plugin-update-checker/plugin-update-checker.php @@ -1,10 +1,10 @@