BitBucket support is now semi-usable.
More testing required. Could probably refactor to reduce duplication; lots of overlap with GitHub integration.
This commit is contained in:
parent
3692f5201e
commit
f64c3170cc
|
|
@ -2,16 +2,50 @@
|
|||
if ( !class_exists('Puc_v4_BitBucket_Api', false) ):
|
||||
|
||||
class Puc_v4_BitBucket_Api {
|
||||
public function __construct($repositoryUrl, $credentials = array()) {
|
||||
/**
|
||||
* @var Puc_v4_OAuthSignature
|
||||
*/
|
||||
private $oauth = null;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $username;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $repository;
|
||||
|
||||
public function __construct($repositoryUrl, $credentials = array()) {
|
||||
$path = @parse_url($repositoryUrl, PHP_URL_PATH);
|
||||
if ( preg_match('@^/?(?P<username>[^/]+?)/(?P<repository>[^/#?&]+?)/?$@', $path, $matches) ) {
|
||||
$this->username = $matches['username'];
|
||||
$this->repository = $matches['repository'];
|
||||
} else {
|
||||
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']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $ref
|
||||
* @return array
|
||||
*/
|
||||
public function getRemoteReadme($ref) {
|
||||
return array();
|
||||
public function getRemoteReadme($ref = 'master') {
|
||||
$fileContents = $this->getRemoteFile('readme.txt', $ref);
|
||||
if ( empty($fileContents) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$parser = new PucReadmeParser();
|
||||
return $parser->parse_readme_contents($fileContents);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -21,7 +55,11 @@ if ( !class_exists('Puc_v4_BitBucket_Api', false) ):
|
|||
* @return stdClass|null
|
||||
*/
|
||||
public function getTag($tagName) {
|
||||
return null;
|
||||
$tag = $this->api('/refs/tags/' . $tagName);
|
||||
if ( is_wp_error($tag) || empty($tag) ) {
|
||||
return null;
|
||||
}
|
||||
return $tag;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -30,9 +68,63 @@ if ( !class_exists('Puc_v4_BitBucket_Api', false) ):
|
|||
* @return stdClass|null
|
||||
*/
|
||||
public function getLatestTag() {
|
||||
$tags = $this->api('/refs/tags');
|
||||
if ( !isset($tags, $tags->values) || !is_array($tags->values) ) {
|
||||
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'));
|
||||
|
||||
//Return the first result.
|
||||
if ( !empty($versionTags) ) {
|
||||
return $versionTags[0];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function isVersionTag($tag) {
|
||||
return isset($tag->name) && $this->looksLikeVersion($tag->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a tag name string looks like a version number.
|
||||
*
|
||||
* @param string $name
|
||||
* @return bool
|
||||
*/
|
||||
protected function looksLikeVersion($name) {
|
||||
//Tag names may be prefixed with "v", e.g. "v1.2.3".
|
||||
$name = ltrim($name, 'v');
|
||||
|
||||
//The version string must start with a number.
|
||||
if ( !is_numeric($name) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//The goal is to accept any SemVer-compatible or "PHP-standardized" version number.
|
||||
return (preg_match('@^(\d{1,5}?)(\.\d{1,10}?){0,4}?($|[abrdp+_\-]|\s)@i', $name) === 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two BitBucket tags as if they were version number.
|
||||
*
|
||||
* @param string $tag1
|
||||
* @param string $tag2
|
||||
* @return int
|
||||
*/
|
||||
protected function compareTagNames($tag1, $tag2) {
|
||||
if ( !isset($tag1->name) ) {
|
||||
return 1;
|
||||
}
|
||||
if ( !isset($tag2->name) ) {
|
||||
return -1;
|
||||
}
|
||||
return -version_compare(ltrim($tag1->name, 'v'), ltrim($tag2->name, 'v'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the contents of a file from a specific branch or tag.
|
||||
*
|
||||
|
|
@ -41,7 +133,11 @@ if ( !class_exists('Puc_v4_BitBucket_Api', false) ):
|
|||
* @return null|string Either the contents of the file, or null if the file doesn't exist or there's an error.
|
||||
*/
|
||||
public function getRemoteFile($path, $ref = 'master') {
|
||||
return null;
|
||||
$response = $this->api('src/' . $ref . '/' . ltrim($path), '1.0');
|
||||
if ( is_wp_error($response) || !isset($response, $response->data) ) {
|
||||
return null;
|
||||
}
|
||||
return $response->data;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -51,6 +147,10 @@ if ( !class_exists('Puc_v4_BitBucket_Api', false) ):
|
|||
* @return string|null
|
||||
*/
|
||||
public function getLatestCommitTime($ref) {
|
||||
$response = $this->api('commits/' . $ref);
|
||||
if ( isset($response->values, $response->values[0], $response->values[0]->date) ) {
|
||||
return $response->values[0]->date;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -84,6 +184,48 @@ if ( !class_exists('Puc_v4_BitBucket_Api', false) ):
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a BitBucket API 2.0 request.
|
||||
*
|
||||
* @param string $url
|
||||
* @param string $version
|
||||
* @return mixed|WP_Error
|
||||
*/
|
||||
public function api($url, $version = '2.0') {
|
||||
//printf('Requesting %s<br>' . "\n", $url);
|
||||
|
||||
$url = implode('/', array(
|
||||
'https://api.bitbucket.org',
|
||||
$version,
|
||||
'repositories',
|
||||
$this->username,
|
||||
$this->repository,
|
||||
ltrim($url, '/')
|
||||
));
|
||||
|
||||
if ( $this->oauth ) {
|
||||
$url = $this->oauth->sign($url,'GET');
|
||||
}
|
||||
|
||||
$response = wp_remote_get($url, array('timeout' => 10));
|
||||
//var_dump($response);
|
||||
if ( is_wp_error($response) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$code = wp_remote_retrieve_response_code($response);
|
||||
$body = wp_remote_retrieve_body($response);
|
||||
if ( $code === 200 ) {
|
||||
$document = json_decode($body);
|
||||
return $document;
|
||||
}
|
||||
|
||||
return new WP_Error(
|
||||
'puc-bitbucket-http-error',
|
||||
'BitBucket API error. HTTP status: ' . $code
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
endif;
|
||||
|
|
@ -5,24 +5,17 @@ if ( !class_exists('Puc_v4_BitBucket_PluginUpdateChecker') ):
|
|||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $repositoryUrl;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $branch;
|
||||
protected $branch = 'master';
|
||||
|
||||
/**
|
||||
* @var Puc_v4_BitBucket_Api
|
||||
*/
|
||||
protected $api;
|
||||
|
||||
protected $credentials = array();
|
||||
|
||||
public function requestInfo($queryArgs = array()) {
|
||||
//TODO: BitBucket support
|
||||
$api = $this->api = new Puc_v4_BitBucket_Api(
|
||||
$this->repositoryUrl,
|
||||
array()
|
||||
);
|
||||
$api = $this->api = new Puc_v4_BitBucket_Api($this->metadataUrl, $this->credentials);
|
||||
|
||||
$info = new Puc_v4_Plugin_Info();
|
||||
$info->filename = $this->pluginFile;
|
||||
|
|
@ -42,7 +35,6 @@ if ( !class_exists('Puc_v4_BitBucket_PluginUpdateChecker') ):
|
|||
$ref = $tag->name;
|
||||
$info->version = ltrim($tag->name, 'v');
|
||||
$info->last_updated = $tag->target->date;
|
||||
//TODO: Download url
|
||||
$foundVersion = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -54,7 +46,6 @@ if ( !class_exists('Puc_v4_BitBucket_PluginUpdateChecker') ):
|
|||
$ref = $tag->name;
|
||||
$info->version = ltrim($tag->name, 'v');
|
||||
$info->last_updated = $tag->target->date;
|
||||
//TODO: Download url
|
||||
$foundVersion = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -62,9 +53,10 @@ if ( !class_exists('Puc_v4_BitBucket_PluginUpdateChecker') ):
|
|||
//If all else fails, use the specified branch itself.
|
||||
if ( !$foundVersion ) {
|
||||
$ref = $this->branch;
|
||||
//TODO: Download url for this branch.
|
||||
}
|
||||
|
||||
$info->download_url = trailingslashit($this->metadataUrl) . 'get/' . $ref . '.zip';
|
||||
|
||||
//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);
|
||||
|
|
@ -76,7 +68,7 @@ if ( !class_exists('Puc_v4_BitBucket_PluginUpdateChecker') ):
|
|||
|
||||
//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() ) {
|
||||
if ( $this->readmeTxtExistsLocally() || !empty($remoteReadme) ) {
|
||||
$this->setInfoFromRemoteReadme($ref, $info);
|
||||
}
|
||||
|
||||
|
|
@ -100,6 +92,16 @@ if ( !class_exists('Puc_v4_BitBucket_PluginUpdateChecker') ):
|
|||
return $info;
|
||||
}
|
||||
|
||||
public function setAuthentication($credentials) {
|
||||
$this->credentials = array_merge(
|
||||
array(
|
||||
'consumer_key' => '',
|
||||
'consumer_secret' => '',
|
||||
),
|
||||
$credentials
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the currently installed version has a readme.txt file.
|
||||
*
|
||||
|
|
@ -170,6 +172,26 @@ if ( !class_exists('Puc_v4_BitBucket_PluginUpdateChecker') ):
|
|||
$pluginInfo->upgrade_notice = $readme['upgrade_notice'][$pluginInfo->version];
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
return $update;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
endif;
|
||||
|
|
@ -31,7 +31,7 @@ if ( !class_exists('Puc_v4_Factory', false) ):
|
|||
* @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_UpdateChecker
|
||||
* @return Puc_v4_Plugin_UpdateChecker|Puc_v4_Theme_UpdateChecker
|
||||
*/
|
||||
public static function buildUpdateChecker($metadataUrl, $fullPath, $slug = '', $checkPeriod = 12, $optionName = '', $muPluginFile = '') {
|
||||
$fullPath = wp_normalize_path($fullPath);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
|
||||
if ( !class_exists('Puc_v4_OAuthSignature', false) ):
|
||||
|
||||
/**
|
||||
* A basic signature generator for zero-legged OAuth 1.0.
|
||||
*/
|
||||
class Puc_v4_OAuthSignature {
|
||||
private $consumerKey = '';
|
||||
private $consumerSecret = '';
|
||||
|
||||
public function __construct($consumerKey, $consumerSecret) {
|
||||
$this->consumerKey = $consumerKey;
|
||||
$this->consumerSecret = $consumerSecret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign a URL using OAuth 1.0.
|
||||
*
|
||||
* @param string $url The URL to be signed. It may contain query parameters.
|
||||
* @param string $method HTTP method such as "GET", "POST" and so on.
|
||||
* @return string The signed URL.
|
||||
*/
|
||||
public function sign($url, $method = 'GET') {
|
||||
$parameters = array();
|
||||
|
||||
//Parse query parameters.
|
||||
$query = @parse_url($url, PHP_URL_QUERY);
|
||||
if ( !empty($query) ) {
|
||||
parse_str($query, $parsedParams);
|
||||
if ( is_array($parameters) ) {
|
||||
$parameters = $parsedParams;
|
||||
}
|
||||
//Remove the query string from the URL. We'll replace it later.
|
||||
$url = substr($url, 0, strpos($url, '?'));
|
||||
}
|
||||
|
||||
$parameters = array_merge(
|
||||
$parameters,
|
||||
array(
|
||||
'oauth_consumer_key' => $this->consumerKey,
|
||||
'oauth_nonce' => $this->nonce(),
|
||||
'oauth_signature_method' => 'HMAC-SHA1',
|
||||
'oauth_timestamp' => time(),
|
||||
'oauth_version' => '1.0',
|
||||
)
|
||||
);
|
||||
|
||||
//Parameters must be sorted alphabetically before signing.
|
||||
ksort($parameters);
|
||||
|
||||
//The most complicated part of the request - generating the signature.
|
||||
//The string to sign contains the HTTP method, the URL path, and all of
|
||||
//our query parameters. Everything is URL encoded. Then we concatenate
|
||||
//them with ampersands into a single string to hash.
|
||||
$encodedVerb = urlencode($method);
|
||||
$encodedUrl = urlencode($url);
|
||||
$encodedParams = urlencode(http_build_query($parameters, '', '&'));
|
||||
|
||||
$stringToSign = $encodedVerb . '&' . $encodedUrl . '&' . $encodedParams;
|
||||
|
||||
//Since we only have one OAuth token (the consumer secret) we only have
|
||||
//to use it as our HMAC key. However, we still have to append an & to it
|
||||
//as if we were using it with additional tokens.
|
||||
$secret = urlencode($this->consumerSecret) . '&';
|
||||
|
||||
//The signature is a hash of the consumer key and the base string. Note
|
||||
//that we have to get the raw output from hash_hmac and base64 encode
|
||||
//the binary data result.
|
||||
$parameters['oauth_signature'] = base64_encode(hash_hmac('sha1', $stringToSign, $secret, true));
|
||||
|
||||
return ($url . '?' . http_build_query($parameters));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a random nonce.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function nonce() {
|
||||
$mt = microtime();
|
||||
$rand = mt_rand();
|
||||
return md5($mt . '_' . $rand);
|
||||
}
|
||||
}
|
||||
|
||||
endif;
|
||||
|
|
@ -1,11 +1,13 @@
|
|||
<?php
|
||||
|
||||
if ( !class_exists('PucReadmeParser', false) ):
|
||||
|
||||
/**
|
||||
* This is a slightly modified version of github.com/markjaquith/WordPress-Plugin-Readme-Parser
|
||||
* It uses Parsedown instead of the "Markdown Extra" parser.
|
||||
*/
|
||||
|
||||
Class PucReadmeParser {
|
||||
class PucReadmeParser {
|
||||
|
||||
function __construct() {
|
||||
// This space intentionally blank
|
||||
|
|
@ -328,4 +330,4 @@ Class PucReadmeParser {
|
|||
|
||||
} // end class
|
||||
|
||||
Class Automattic_Readme extends PucReadmeParser {}
|
||||
endif;
|
||||
Loading…
Reference in New Issue