diff --git a/Puc/v4/Factory.php b/Puc/v4/Factory.php index 7293299..147150e 100644 --- a/Puc/v4/Factory.php +++ b/Puc/v4/Factory.php @@ -23,13 +23,13 @@ if ( !class_exists('Puc_v4_Factory', false) ): * * @see PluginUpdateChecker::__construct() * - * @param string $metadataUrl The URL of the metadata file, or a GitHub repository, etc. + * @param string $metadataUrl The URL of the metadata file, a GitHub repository, or another supported update source. * @param string $fullPath Full path to the main plugin file or to the theme directory. * @param string $slug Custom slug. Defaults to the name of the main plugin file or the theme directory. * @param int $checkPeriod How often to check for updates (in hours). * @param string $optionName Where to store book-keeping info about update checks. * @param string $muPluginFile The plugin filename relative to the mu-plugins directory. - * @return Puc_v4_Plugin_UpdateChecker|Puc_v4_Theme_UpdateChecker + * @return Puc_v4_Plugin_UpdateChecker|Puc_v4_Theme_UpdateChecker|Puc_v4_Vcs_BaseChecker */ public static function buildUpdateChecker($metadataUrl, $fullPath, $slug = '', $checkPeriod = 12, $optionName = '', $muPluginFile = '') { $fullPath = wp_normalize_path($fullPath); @@ -68,31 +68,63 @@ if ( !class_exists('Puc_v4_Factory', false) ): } } - $class = null; + $checkerClass = null; + $apiClass = null; if ( empty($service) ) { //The default is to get update information from a remote JSON file. - $class = $type . '_UpdateChecker'; + $checkerClass = $type . '_UpdateChecker'; } else { - $class = $service . '_' . $type . 'UpdateChecker'; + //You can also use a VCS repository like GitHub. + $checkerClass = 'Vcs_' . $type . 'UpdateChecker'; + $apiClass = $service . 'Api'; } - if ( !isset(self::$classVersions[$class][self::$greatestCompatVersion]) ) { + $checkerClass = self::getCompatibleClass($checkerClass); + if ( !$checkerClass ) { trigger_error( sprintf( - 'PUC %s does not support updates for %ss hosted on %s', + 'PUC %s does not support updates for %ss %s', htmlentities(self::$greatestCompatVersion), strtolower($type), - $service + $service ? ('hosted on ' . htmlentities($service)) : 'using JSON metadata' ), E_USER_ERROR ); return null; } - $class = self::$classVersions[$class][self::$greatestCompatVersion]; - return new $class($metadataUrl, $id, $slug, $checkPeriod, $optionName, $muPluginFile); + if ( !isset($apiClass) ) { + //Plain old update checker. + return new $checkerClass($metadataUrl, $id, $slug, $checkPeriod, $optionName, $muPluginFile); + } else { + //VCS checker + an API client. + $apiClass = self::getCompatibleClass($apiClass); + if ( !$apiClass ) { + trigger_error(sprintf( + 'PUC %s does not support %s', + htmlentities(self::$greatestCompatVersion), + htmlentities($service) + ), E_USER_ERROR); + return null; + } + + return new $checkerClass( + new $apiClass($metadataUrl), + $id, + $slug, + $checkPeriod, + $optionName, + $muPluginFile + ); + } } + /** + * Check if the path points to something inside the "plugins" or "mu-plugins" directories. + * + * @param string $absolutePath + * @return bool + */ protected static function isPluginFile($absolutePath) { $pluginDir = wp_normalize_path(WP_PLUGIN_DIR); $muPluginDir = wp_normalize_path(WPMU_PLUGIN_DIR); @@ -101,6 +133,20 @@ if ( !class_exists('Puc_v4_Factory', false) ): return (strpos($absolutePath, $pluginDir) === 0) || (strpos($absolutePath, $muPluginDir) === 0); } + /** + * Get the latest version of the specified class that has the same major version number + * as this factory class. + * + * @param string $class Partial class name. + * @return string|null Full class name. + */ + protected static function getCompatibleClass($class) { + if ( isset(self::$classVersions[$class][self::$greatestCompatVersion]) ) { + return self::$classVersions[$class][self::$greatestCompatVersion]; + } + return null; + } + /** * Get the specific class name for the latest available version of a class. * diff --git a/Puc/v4/GitHub/PluginUpdateChecker.php b/Puc/v4/GitHub/PluginUpdateChecker.php deleted file mode 100644 index 31c0c9c..0000000 --- a/Puc/v4/GitHub/PluginUpdateChecker.php +++ /dev/null @@ -1,224 +0,0 @@ -repositoryUrl = $repositoryUrl; - parent::__construct($repositoryUrl, $pluginFile, $slug, $checkPeriod, $optionName, $muPluginFile); - } - - /** - * Set the GitHub branch to use for updates. Defaults to 'master'. - * - * @param string $branch - * @return $this - */ - public function setBranch($branch) { - $this->branch = empty($branch) ? 'master' : $branch; - return $this; - } - - /** - * Retrieve details about the latest plugin version from GitHub. - * - * @param array $unusedQueryArgs Unused. - * @return Puc_v4_Plugin_Info - */ - public function requestInfo($unusedQueryArgs = array()) { - $api = $this->api = new Puc_v4_GitHub_Api($this->repositoryUrl, $this->accessToken); - - $info = new Puc_v4_Plugin_Info(); - $info->filename = $this->pluginFile; - $info->slug = $this->slug; - - $this->setInfoFromHeader($this->getPluginHeader(), $info); - - //Figure out which reference (tag or branch) we'll use to get the latest version of the plugin. - $updateSource = $this->chooseReference(); - if ( $updateSource ) { - $ref = $updateSource->name; - $info->version = $updateSource->version; - $info->last_updated = $updateSource->updated; - $info->download_url = $updateSource->downloadUrl; - - if ( !empty($updateSource->changelog) ) { - $info->sections['changelog'] = $updateSource->changelog; - } - if ( isset($updateSource->downloadCount) ) { - $info->downloaded = $updateSource->downloadCount; - } - } else { - return null; - } - - if ( !empty($info->download_url) && !empty($this->accessToken) ) { - $info->download_url = add_query_arg('access_token', $this->accessToken, $info->download_url); - } - - //Get headers from the main plugin file in this branch/tag. Its "Version" header and other metadata - //are what the WordPress install will actually see after upgrading, so they take precedence over releases/tags. - $mainPluginFile = basename($this->pluginFile); - $remotePlugin = $api->getRemoteFile($mainPluginFile, $ref); - if ( !empty($remotePlugin) ) { - $remoteHeader = $this->getFileHeader($remotePlugin); - $this->setInfoFromHeader($remoteHeader, $info); - } - - //Try parsing readme.txt. If it's formatted according to WordPress.org standards, it will contain - //a lot of useful information like the required/tested WP version, changelog, and so on. - if ( $this->readmeTxtExistsLocally() ) { - $this->setInfoFromRemoteReadme($ref, $info); - } - - //The changelog might be in a separate file. - if ( empty($info->sections['changelog']) ) { - $info->sections['changelog'] = $api->getRemoteChangelog($ref, dirname($this->getAbsolutePath())); - if ( empty($info->sections['changelog']) ) { - $info->sections['changelog'] = __('There is no changelog available.', 'plugin-update-checker'); - } - } - - if ( empty($info->last_updated) ) { - //Fetch the latest commit that changed the tag/branch and use it as the "last_updated" date. - $info->last_updated = $api->getLatestCommitTime($ref); - } - - $info = apply_filters($this->getUniqueName('request_info_result'), $info, null); - return $info; - } - - /** - * @return Puc_v4_VcsReference|null - */ - protected function chooseReference() { - $api = $this->api; - $updateSource = null; - - if ( $this->branch === 'master' ) { - //Use the latest release. - $updateSource = $api->getLatestRelease(); - if ( $updateSource === null ) { - //Failing that, use the tag with the highest version number. - $updateSource = $api->getLatestTag(); - } - } - //Alternatively, just use the branch itself. - if ( empty($ref) ) { - $updateSource = $api->getBranch($this->branch); - } - - return $updateSource; - } - - /** - * Set the access token that will be used to make authenticated GitHub API requests. - * - * @param string $accessToken - * @return $this - */ - public function setAccessToken($accessToken) { - $this->accessToken = $accessToken; - return $this; - } - - /** - * Copy plugin metadata from a file header to a PluginInfo object. - * - * @param array $fileHeader - * @param Puc_v4_Plugin_Info $pluginInfo - */ - protected function setInfoFromHeader($fileHeader, $pluginInfo) { - $headerToPropertyMap = array( - 'Version' => 'version', - 'Name' => 'name', - 'PluginURI' => 'homepage', - 'Author' => 'author', - 'AuthorName' => 'author', - 'AuthorURI' => 'author_homepage', - - 'Requires WP' => 'requires', - 'Tested WP' => 'tested', - 'Requires at least' => 'requires', - 'Tested up to' => 'tested', - ); - foreach ($headerToPropertyMap as $headerName => $property) { - if ( isset($fileHeader[$headerName]) && !empty($fileHeader[$headerName]) ) { - $pluginInfo->$property = $fileHeader[$headerName]; - } - } - - if ( !empty($fileHeader['Description']) ) { - $pluginInfo->sections['description'] = $fileHeader['Description']; - } - } - - /** - * Copy plugin metadata from the remote readme.txt file. - * - * @param string $ref GitHub tag or branch where to look for the readme. - * @param Puc_v4_Plugin_Info $pluginInfo - */ - protected function setInfoFromRemoteReadme($ref, $pluginInfo) { - $readme = $this->api->getRemoteReadme($ref); - if ( empty($readmeTxt) ) { - return; - } - - if ( isset($readme['sections']) ) { - $pluginInfo->sections = array_merge($pluginInfo->sections, $readme['sections']); - } - if ( !empty($readme['tested_up_to']) ) { - $pluginInfo->tested = $readme['tested_up_to']; - } - if ( !empty($readme['requires_at_least']) ) { - $pluginInfo->requires = $readme['requires_at_least']; - } - - if ( isset($readme['upgrade_notice'], $readme['upgrade_notice'][$pluginInfo->version]) ) { - $pluginInfo->upgrade_notice = $readme['upgrade_notice'][$pluginInfo->version]; - } - } - - /** - * Check if the currently installed version has a readme.txt file. - * - * @return bool - */ - protected function readmeTxtExistsLocally() { - $pluginDirectory = dirname($this->pluginAbsolutePath); - if ( empty($this->pluginAbsolutePath) || !is_dir($pluginDirectory) || ($pluginDirectory === '.') ) { - return false; - } - return is_file($pluginDirectory . '/readme.txt'); - } -} - -endif; \ No newline at end of file diff --git a/Puc/v4/GitHub/ThemeUpdateChecker.php b/Puc/v4/GitHub/ThemeUpdateChecker.php deleted file mode 100644 index 4721759..0000000 --- a/Puc/v4/GitHub/ThemeUpdateChecker.php +++ /dev/null @@ -1,99 +0,0 @@ -repositoryUrl = $repositoryUrl; - parent::__construct($repositoryUrl, $stylesheet, $customSlug, $checkPeriod, $optionName); - } - - public function requestUpdate() { - $api = new Puc_v4_GitHub_Api($this->repositoryUrl, $this->accessToken); - - $update = new Puc_v4_Theme_Update(); - $update->slug = $this->slug; - - //Figure out which reference (tag or branch) we'll use to get the latest version of the theme. - $ref = $this->branch; - if ( $this->branch === 'master' ) { - //Use the latest release. - $release = $api->getLatestRelease(); - if ( $release !== null ) { - $ref = $release->name; - $update->version = ltrim($release->name, 'v'); //Remove the "v" prefix from "v1.2.3". - $update->download_url = $release->downloadUrl; - } else { - //Failing that, use the tag with the highest version number. - $tag = $api->getLatestTag(); - if ( $tag !== null ) { - $ref = $tag->name; - $update->version = $tag->version; - $update->download_url = $tag->downloadUrl; - } - } - } - - if ( empty($update->download_url) ) { - $update->download_url = $api->buildArchiveDownloadUrl($ref); - } else if ( !empty($this->accessToken) ) { - $update->download_url = add_query_arg('access_token', $this->accessToken, $update->download_url); - } - - //Get headers from the main stylesheet in this branch/tag. Its "Version" header and other metadata - //are what the WordPress install will actually see after upgrading, so they take precedence over releases/tags. - $remoteStylesheet = $api->getRemoteFile('style.css', $ref); - if ( !empty($remoteStylesheet) ) { - $remoteHeader = $this->getFileHeader($remoteStylesheet); - if ( !empty($remoteHeader['Version']) ) { - $update->version = $remoteHeader['Version']; - } - if ( !empty($remoteHeader['ThemeURI']) ) { - $update->details_url = $remoteHeader['ThemeURI']; - } - } - - //The details URL defaults to the Theme URI header or the repository URL. - if ( empty($update->details_url) ) { - $update->details_url = $this->theme->get('ThemeURI'); - } - if ( empty($update->details_url) ) { - $update->details_url = $this->repositoryUrl; - } - - if ( empty($update->version) ) { - //It looks like we didn't find a valid update after all. - $update = null; - } - - $update = $this->filterUpdateResult($update); - return $update; - } - - /** - * Set the GitHub branch to use for updates. Defaults to 'master'. - * - * @param string $branch - * @return $this - */ - public function setBranch($branch) { - $this->branch = empty($branch) ? 'master' : $branch; - return $this; - } - - /** - * Set the access token that will be used to make authenticated GitHub API requests. - * - * @param string $accessToken - * @return $this - */ - public function setAccessToken($accessToken) { - $this->accessToken = $accessToken; - return $this; - } - } - -endif; \ No newline at end of file diff --git a/Puc/v4/VcsApi.php b/Puc/v4/Vcs/Api.php similarity index 62% rename from Puc/v4/VcsApi.php rename to Puc/v4/Vcs/Api.php index ecfafff..7d5dd32 100644 --- a/Puc/v4/VcsApi.php +++ b/Puc/v4/Vcs/Api.php @@ -1,9 +1,46 @@ repositoryUrl = $repositoryUrl; + $this->setAuthentication($credentials); + } + + /** + * @return string + */ + public function getRepositoryUrl() { + return $this->repositoryUrl; + } + + /** + * Figure out which reference (i.e tag or branch) contains the latest version. + * + * @param string $configBranch Start looking in this branch. + * @param bool $useStableTag + * @return null|Puc_v4_Vcs_Reference + */ + abstract public function chooseReference($configBranch, $useStableTag = true); + /** * Get the readme.txt file from the remote repository and parse it * according to the plugin readme standard. @@ -25,7 +62,7 @@ if ( !class_exists('Puc_v4_VcsApi') ): * Get a branch. * * @param string $branchName - * @return Puc_v4_VcsReference|null + * @return Puc_v4_Vcs_Reference|null */ abstract public function getBranch($branchName); @@ -33,7 +70,7 @@ if ( !class_exists('Puc_v4_VcsApi') ): * Get a specific tag. * * @param string $tagName - * @return Puc_v4_VcsReference|null + * @return Puc_v4_Vcs_Reference|null */ abstract public function getTag($tagName); @@ -41,7 +78,7 @@ if ( !class_exists('Puc_v4_VcsApi') ): * Get the tag that looks like the highest version number. * (Implementations should skip pre-release versions if possible.) * - * @return Puc_v4_VcsReference|null + * @return Puc_v4_Vcs_Reference|null */ abstract public function getLatestTag(); @@ -64,6 +101,33 @@ if ( !class_exists('Puc_v4_VcsApi') ): return (preg_match('@^(\d{1,5}?)(\.\d{1,10}?){0,4}?($|[abrdp+_\-]|\s)@i', $name) === 1); } + /** + * Check if a tag appears to be named like a version number. + * + * @param stdClass $tag + * @return bool + */ + protected function isVersionTag($tag) { + $property = $this->tagNameProperty; + return isset($tag->$property) && $this->looksLikeVersion($tag->$property); + } + + /** + * Sort a list of tags as if they were version numbers. + * Tags that don't look like version number will be removed. + * + * @param stdClass[] $tags Array of tag objects. + * @return stdClass[] Filtered array of tags sorted in descending order. + */ + protected function sortTagsByVersion($tags) { + //Keep only those tags that look like version numbers. + $versionTags = array_filter($tags, array($this, 'isVersionTag')); + //Sort them in descending order. + usort($versionTags, array($this, 'compareTagNames')); + + return $versionTags; + } + /** * Compare two tags as if they were version number. * @@ -141,6 +205,23 @@ if ( !class_exists('Puc_v4_VcsApi') ): } return null; } + + /** + * Set authentication credentials. + * + * @param $credentials + */ + public function setAuthentication($credentials) { + $this->credentials = $credentials; + } + + /** + * @param string $url + * @return string + */ + public function signDownloadUrl($url) { + return $url; + } } endif; diff --git a/Puc/v4/Vcs/BaseChecker.php b/Puc/v4/Vcs/BaseChecker.php new file mode 100644 index 0000000..77a8919 --- /dev/null +++ b/Puc/v4/Vcs/BaseChecker.php @@ -0,0 +1,22 @@ +repositoryUrl = $repositoryUrl; - $path = @parse_url($repositoryUrl, PHP_URL_PATH); if ( preg_match('@^/?(?P[^/]+?)/(?P[^/#?&]+?)/?$@', $path, $matches) ) { $this->username = $matches['username']; @@ -33,12 +26,45 @@ if ( !class_exists('Puc_v4_BitBucket_Api', false) ): throw new InvalidArgumentException('Invalid BitBucket repository URL: "' . $repositoryUrl . '"'); } - if ( !empty($credentials) && !empty($credentials['consumer_key']) ) { - $this->oauth = new Puc_v4_OAuthSignature( - $credentials['consumer_key'], - $credentials['consumer_secret'] - ); + parent::__construct($repositoryUrl, $credentials); + } + + /** + * Figure out which reference (i.e tag or branch) contains the latest version. + * + * @param string $configBranch Start looking in this branch. + * @param bool $useStableTag + * @return null|Puc_v4_Vcs_Reference + */ + public function chooseReference($configBranch, $useStableTag = true) { + $updateSource = null; + + //Check if there's a "Stable tag: 1.2.3" header that points to a valid tag. + if ( $useStableTag ) { + $remoteReadme = $this->getRemoteReadme($configBranch); + if ( !empty($remoteReadme['stable_tag']) ) { + $tag = $remoteReadme['stable_tag']; + + //You can explicitly opt out of using tags by setting "Stable tag" to + //"trunk" or the name of the current branch. + if ( ($tag === $configBranch) || ($tag === 'trunk') ) { + return $this->getBranch($configBranch); + } + + $updateSource = $this->getTag($tag); + } } + + //Look for version-like tags. + if ( !$updateSource && ($configBranch === 'master') ) { + $updateSource = $this->getLatestTag(); + } + //If all else fails, use the specified branch itself. + if ( !$updateSource ) { + $updateSource = $this->getBranch($configBranch); + } + + return $updateSource; } public function getBranch($branchName) { @@ -47,7 +73,7 @@ if ( !class_exists('Puc_v4_BitBucket_Api', false) ): return null; } - return new Puc_v4_VcsReference(array( + return new Puc_v4_Vcs_Reference(array( 'name' => $branch->name, 'updated' => $branch->target->date, 'downloadUrl' => $this->getDownloadUrl($branch->name), @@ -58,7 +84,7 @@ if ( !class_exists('Puc_v4_BitBucket_Api', false) ): * Get a specific tag. * * @param string $tagName - * @return Puc_v4_VcsReference|null + * @return Puc_v4_Vcs_Reference|null */ public function getTag($tagName) { $tag = $this->api('/refs/tags/' . $tagName); @@ -66,7 +92,7 @@ if ( !class_exists('Puc_v4_BitBucket_Api', false) ): return null; } - return new Puc_v4_VcsReference(array( + return new Puc_v4_Vcs_Reference(array( 'name' => $tag->name, 'version' => ltrim($tag->name, 'v'), 'updated' => $tag->target->date, @@ -77,7 +103,7 @@ if ( !class_exists('Puc_v4_BitBucket_Api', false) ): /** * Get the tag that looks like the highest version number. * - * @return Puc_v4_VcsReference|null + * @return Puc_v4_Vcs_Reference|null */ public function getLatestTag() { $tags = $this->api('/refs/tags'); @@ -85,15 +111,13 @@ if ( !class_exists('Puc_v4_BitBucket_Api', false) ): return null; } - //Keep only those tags that look like version numbers. - $versionTags = array_filter($tags->values, array($this, 'isVersionTag')); - //Sort them in descending order. - usort($versionTags, array($this, 'compareTagNames')); + //Filter and sort the list of tags. + $versionTags = $this->sortTagsByVersion($tags->values); //Return the first result. if ( !empty($versionTags) ) { $tag = $versionTags[0]; - return new Puc_v4_VcsReference(array( + return new Puc_v4_Vcs_Reference(array( 'name' => $tag->name, 'version' => ltrim($tag->name, 'v'), 'updated' => $tag->target->date, @@ -111,10 +135,6 @@ if ( !class_exists('Puc_v4_BitBucket_Api', false) ): return trailingslashit($this->repositoryUrl) . 'get/' . $ref . '.zip'; } - protected function isVersionTag($tag) { - return isset($tag->name) && $this->looksLikeVersion($tag->name); - } - /** * Get the contents of a file from a specific branch or tag. * @@ -185,6 +205,32 @@ if ( !class_exists('Puc_v4_BitBucket_Api', false) ): 'BitBucket API error. HTTP status: ' . $code ); } + + /** + * @param array $credentials + */ + public function setAuthentication($credentials) { + parent::setAuthentication($credentials); + + if ( !empty($credentials) && !empty($credentials['consumer_key']) ) { + $this->oauth = new Puc_v4_OAuthSignature( + $credentials['consumer_key'], + $credentials['consumer_secret'] + ); + } else { + $this->oauth = null; + } + } + + public function signDownloadUrl($url) { + //Add authentication data to download URLs. Since OAuth signatures incorporate + //timestamps, we have to do this immediately before inserting the update. Otherwise + //authentication could fail due to a stale timestamp. + if ( $this->oauth ) { + $url = $this->oauth->sign($url); + } + return $url; + } } endif; \ No newline at end of file diff --git a/Puc/v4/GitHub/Api.php b/Puc/v4/Vcs/GitHubApi.php similarity index 75% rename from Puc/v4/GitHub/Api.php rename to Puc/v4/Vcs/GitHubApi.php index 2dc3558..3e3e74d 100644 --- a/Puc/v4/GitHub/Api.php +++ b/Puc/v4/Vcs/GitHubApi.php @@ -1,8 +1,8 @@ repositoryUrl = $repositoryUrl; - $this->accessToken = $accessToken; - $path = @parse_url($repositoryUrl, PHP_URL_PATH); if ( preg_match('@^/?(?P[^/]+?)/(?P[^/#?&]+?)/?$@', $path, $matches) ) { $this->userName = $matches['username']; @@ -33,12 +30,14 @@ if ( !class_exists('Puc_v4_GitHub_Api', false) ): } else { throw new InvalidArgumentException('Invalid GitHub repository URL: "' . $repositoryUrl . '"'); } + + parent::__construct($repositoryUrl, $accessToken); } /** * Get the latest release from GitHub. * - * @return Puc_v4_VcsReference|null + * @return Puc_v4_Vcs_Reference|null */ public function getLatestRelease() { $release = $this->api('/repos/:user/:repo/releases/latest'); @@ -46,10 +45,10 @@ if ( !class_exists('Puc_v4_GitHub_Api', false) ): return null; } - $reference = new Puc_v4_VcsReference(array( + $reference = new Puc_v4_Vcs_Reference(array( 'name' => $release->tag_name, 'version' => ltrim($release->tag_name, 'v'), //Remove the "v" prefix from "v1.2.3". - 'downloadUrl' => $release->zipball_url, + 'downloadUrl' => $this->signDownloadUrl($release->zipball_url), 'updated' => $release->created_at, )); @@ -67,7 +66,7 @@ if ( !class_exists('Puc_v4_GitHub_Api', false) ): /** * Get the tag that looks like the highest version number. * - * @return Puc_v4_VcsReference|null + * @return Puc_v4_Vcs_Reference|null */ public function getLatestTag() { $tags = $this->api('/repos/:user/:repo/tags'); @@ -76,13 +75,16 @@ if ( !class_exists('Puc_v4_GitHub_Api', false) ): return null; } - usort($tags, array($this, 'compareTagNames')); //Sort from highest to lowest. + $versionTags = $this->sortTagsByVersion($tags); + if ( empty($versionTags) ) { + return null; + } - $tag = $tags[0]; - return new Puc_v4_VcsReference(array( + $tag = $versionTags[0]; + return new Puc_v4_Vcs_Reference(array( 'name' => $tag->name, 'version' => ltrim($tag->name, 'v'), - 'downloadUrl' => $tag->zipball_url, + 'downloadUrl' => $this->signDownloadUrl($tag->zipball_url), )); } @@ -90,7 +92,7 @@ if ( !class_exists('Puc_v4_GitHub_Api', false) ): * Get a branch by name. * * @param string $branchName - * @return null|Puc_v4_VcsReference + * @return null|Puc_v4_Vcs_Reference */ public function getBranch($branchName) { $branch = $this->api('/repos/:user/:repo/branches/' . $branchName); @@ -98,7 +100,7 @@ if ( !class_exists('Puc_v4_GitHub_Api', false) ): return null; } - $reference = new Puc_v4_VcsReference(array( + $reference = new Puc_v4_Vcs_Reference(array( 'name' => $branch->name, 'downloadUrl' => $this->buildArchiveDownloadUrl($branch->name), )); @@ -218,7 +220,7 @@ if ( !class_exists('Puc_v4_GitHub_Api', false) ): urlencode($ref) ); if ( !empty($this->accessToken) ) { - $url = add_query_arg('access_token', $this->accessToken, $url); + $url = $this->signDownloadUrl($url); } return $url; } @@ -227,12 +229,43 @@ if ( !class_exists('Puc_v4_GitHub_Api', false) ): * Get a specific tag. * * @param string $tagName - * @return Puc_v4_VcsReference|null + * @return Puc_v4_Vcs_Reference|null */ public function getTag($tagName) { - //The current GitHub update checker doesn't use getTag, so didn't bother to implement it. + //The current GitHub update checker doesn't use getTag, so I didn't bother to implement it. throw new LogicException('The ' . __METHOD__ . ' method is not implemented and should not be used.'); } + + public function setAuthentication($credentials) { + parent::setAuthentication($credentials); + $this->accessToken = is_string($credentials) ? $credentials : null; + } + + /** + * Figure out which reference (i.e tag or branch) contains the latest version. + * + * @param string $configBranch Start looking in this branch. + * @param bool $useStableTag Ignored. The GitHub client doesn't use the "Stable tag" header. + * @return null|Puc_v4_Vcs_Reference + */ + public function chooseReference($configBranch, $useStableTag = false) { + $updateSource = null; + + if ( $configBranch === 'master' ) { + //Use the latest release. + $updateSource = $this->getLatestRelease(); + if ( $updateSource === null ) { + //Failing that, use the tag with the highest version number. + $updateSource = $this->getLatestTag(); + } + } + //Alternatively, just use the branch itself. + if ( empty($updateSource) ) { + $updateSource = $this->getBranch($configBranch); + } + + return $updateSource; + } } endif; \ No newline at end of file diff --git a/Puc/v4/BitBucket/PluginUpdateChecker.php b/Puc/v4/Vcs/PluginUpdateChecker.php similarity index 68% rename from Puc/v4/BitBucket/PluginUpdateChecker.php rename to Puc/v4/Vcs/PluginUpdateChecker.php index a3dae8c..da2ba5f 100644 --- a/Puc/v4/BitBucket/PluginUpdateChecker.php +++ b/Puc/v4/Vcs/PluginUpdateChecker.php @@ -1,25 +1,38 @@ api = $api; + parent::__construct($api->getRepositoryUrl(), $pluginFile, $slug, $checkPeriod, $optionName, $muPluginFile); + } public function requestInfo($queryArgs = array()) { //We have to make several remote API requests to gather all the necessary info //which can take a while on slow networks. set_time_limit(60); - $api = $this->api = new Puc_v4_BitBucket_Api($this->metadataUrl, $this->credentials); + $api = $this->api; $info = new Puc_v4_Plugin_Info(); $info->filename = $this->pluginFile; @@ -28,12 +41,19 @@ if ( !class_exists('Puc_v4_BitBucket_PluginUpdateChecker') ): $this->setInfoFromHeader($this->getPluginHeader(), $info); //Pick a branch or tag. - $updateSource = $this->chooseReference(); + $updateSource = $api->chooseReference($this->branch); if ( $updateSource ) { $ref = $updateSource->name; $info->version = $updateSource->version; $info->last_updated = $updateSource->updated; $info->download_url = $updateSource->downloadUrl; + + if ( !empty($updateSource->changelog) ) { + $info->sections['changelog'] = $updateSource->changelog; + } + if ( isset($updateSource->downloadCount) ) { + $info->downloaded = $updateSource->downloadCount; + } } else { //There's probably a network problem or an authentication error. return null; @@ -74,56 +94,6 @@ if ( !class_exists('Puc_v4_BitBucket_PluginUpdateChecker') ): return $info; } - /** - * Figure out which reference (tag or branch) we'll use to get the latest version of the plugin. - * - * @return Puc_v4_VcsReference|null - */ - protected function chooseReference() { - $api = $this->api; - $updateSource = null; - - //Check if there's a "Stable tag: v1.2.3" header that points to a valid tag. - $remoteReadme = $api->getRemoteReadme($this->branch); - if ( !empty($remoteReadme['stable_tag']) ) { - $tag = $remoteReadme['stable_tag']; - - //You can explicitly opt out of using tags by setting "Stable tag" to - //"trunk" or the name of the current branch. - if ( ($tag === $this->branch) || ($tag === 'trunk') ) { - return $api->getBranch($this->branch); - } - - $updateSource = $api->getTag($tag); - } - //Look for version-like tags. - if ( !$updateSource && ($this->branch === 'master') ) { - $updateSource = $api->getLatestTag(); - } - //If all else fails, use the specified branch itself. - if ( !$updateSource ) { - $updateSource = $api->getBranch($this->branch); - } - - return $updateSource; - } - - public function setAuthentication($credentials) { - $this->credentials = array_merge( - array( - 'consumer_key' => '', - 'consumer_secret' => '', - ), - $credentials - ); - return $this; - } - - public function setBranch($branchName = 'master') { - $this->branch = $branchName; - return $this; - } - /** * Check if the currently installed version has a readme.txt file. * @@ -195,20 +165,21 @@ if ( !class_exists('Puc_v4_BitBucket_PluginUpdateChecker') ): } } + public function setBranch($branch) { + $this->branch = $branch; + return $this; + } + + public function setAuthentication($credentials) { + $this->api->setAuthentication($credentials); + return $this; + } + public function getUpdate() { $update = parent::getUpdate(); - //Add authentication data to download URLs. Since OAuth signatures incorporate - //timestamps, we have to do this immediately before inserting the update. Otherwise - //authentication could fail due to a stale timestamp. - if ( isset($update, $update->download_url) && !empty($update->download_url) && !empty($this->credentials) ) { - if ( !empty($this->credentials['consumer_key']) ) { - $oauth = new Puc_v4_OAuthSignature( - $this->credentials['consumer_key'], - $this->credentials['consumer_secret'] - ); - $update->download_url = $oauth->sign($update->download_url); - } + if ( isset($update) && !empty($update->download_url) ) { + $update->download_url = $this->api->signDownloadUrl($update->download_url); } return $update; diff --git a/Puc/v4/VcsReference.php b/Puc/v4/Vcs/Reference.php similarity index 91% rename from Puc/v4/VcsReference.php rename to Puc/v4/Vcs/Reference.php index e7047fa..501955f 100644 --- a/Puc/v4/VcsReference.php +++ b/Puc/v4/Vcs/Reference.php @@ -1,5 +1,5 @@ api = $api; + parent::__construct($api->getRepositoryUrl(), $stylesheet, $customSlug, $checkPeriod, $optionName); + } + + public function requestUpdate() { + $api = $this->api; + + $update = new Puc_v4_Theme_Update(); + $update->slug = $this->slug; + + //Figure out which reference (tag or branch) we'll use to get the latest version of the theme. + $updateSource = $api->chooseReference($this->branch, false); + if ( $updateSource ) { + $ref = $updateSource->name; + $update->version = $updateSource->version; + $update->download_url = $updateSource->downloadUrl; + } else { + $ref = $this->branch; + } + + //Get headers from the main stylesheet in this branch/tag. Its "Version" header and other metadata + //are what the WordPress install will actually see after upgrading, so they take precedence over releases/tags. + $remoteStylesheet = $api->getRemoteFile('style.css', $ref); + if ( !empty($remoteStylesheet) ) { + $remoteHeader = $this->getFileHeader($remoteStylesheet); + if ( !empty($remoteHeader['Version']) ) { + $update->version = $remoteHeader['Version']; + } + if ( !empty($remoteHeader['ThemeURI']) ) { + $update->details_url = $remoteHeader['ThemeURI']; + } + } + + //The details URL defaults to the Theme URI header or the repository URL. + if ( empty($update->details_url) ) { + $update->details_url = $this->theme->get('ThemeURI'); + } + if ( empty($update->details_url) ) { + $update->details_url = $this->metadataUrl; + } + + if ( empty($update->version) ) { + //It looks like we didn't find a valid update after all. + $update = null; + } + + $update = $this->filterUpdateResult($update); + return $update; + } + + public function setBranch($branch) { + $this->branch = $branch; + return $this; + } + + public function setAuthentication($credentials) { + $this->api->setAuthentication($credentials); + return $this; + } + + public function getUpdate() { + $update = parent::getUpdate(); + + if ( isset($update) && !empty($update->download_url) ) { + $update->download_url = $this->api->signDownloadUrl($update->download_url); + } + + return $update; + } + + + } + +endif; \ No newline at end of file diff --git a/plugin-update-checker.php b/plugin-update-checker.php index 81dbd87..642809a 100644 --- a/plugin-update-checker.php +++ b/plugin-update-checker.php @@ -13,7 +13,9 @@ new Puc_v4_Autoloader(); //Register classes defined in this file with the factory. Puc_v4_Factory::addVersion('Plugin_UpdateChecker', 'Puc_v4_Plugin_UpdateChecker', '4.0'); Puc_v4_Factory::addVersion('Theme_UpdateChecker', 'Puc_v4_Theme_UpdateChecker', '4.0'); -Puc_v4_Factory::addVersion('GitHub_PluginUpdateChecker', 'Puc_v4_GitHub_PluginUpdateChecker', '4.0'); -Puc_v4_Factory::addVersion('GitHub_ThemeUpdateChecker', 'Puc_v4_GitHub_ThemeUpdateChecker', '4.0'); -Puc_v4_Factory::addVersion('BitBucket_PluginUpdateChecker', 'Puc_v4_BitBucket_PluginUpdateChecker', '4.0'); +Puc_v4_Factory::addVersion('Vcs_PluginUpdateChecker', 'Puc_v4_Vcs_PluginUpdateChecker', '4.0'); +Puc_v4_Factory::addVersion('Vcs_ThemeUpdateChecker', 'Puc_v4_Vcs_ThemeUpdateChecker', '4.0'); + +Puc_v4_Factory::addVersion('GitHubApi', 'Puc_v4_Vcs_GitHubApi', '4.0'); +Puc_v4_Factory::addVersion('BitBucketApi', 'Puc_v4_Vcs_BitBucketApi', '4.0');