Move into wp-content path
Signed-off-by: Adrian Nöthlich <git@promasu.tech>
This commit is contained in:
735
wp-content/plugins/github-updater/src/GitHub_Updater/API.php
Normal file
735
wp-content/plugins/github-updater/src/GitHub_Updater/API.php
Normal file
@@ -0,0 +1,735 @@
|
||||
<?php
|
||||
/**
|
||||
* GitHub Updater
|
||||
*
|
||||
* @author Andy Fragen
|
||||
* @license GPL-2.0+
|
||||
* @link https://github.com/afragen/github-updater
|
||||
* @package github-updater
|
||||
*/
|
||||
|
||||
namespace Fragen\GitHub_Updater;
|
||||
|
||||
use Fragen\Singleton;
|
||||
use Fragen\GitHub_Updater\Traits\API_Common;
|
||||
use Fragen\GitHub_Updater\Traits\GHU_Trait;
|
||||
use Fragen\GitHub_Updater\Traits\Basic_Auth_Loader;
|
||||
use Fragen\GitHub_Updater\API\GitHub_API;
|
||||
use Fragen\GitHub_Updater\API\Bitbucket_API;
|
||||
use Fragen\GitHub_Updater\API\Bitbucket_Server_API;
|
||||
use Fragen\GitHub_Updater\API\GitLab_API;
|
||||
use Fragen\GitHub_Updater\API\Gitea_API;
|
||||
|
||||
/*
|
||||
* Exit if called directly.
|
||||
*/
|
||||
if ( ! defined( 'WPINC' ) ) {
|
||||
die;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class API
|
||||
*/
|
||||
class API {
|
||||
use API_Common, GHU_Trait, Basic_Auth_Loader;
|
||||
|
||||
/**
|
||||
* Holds HTTP error code from API call.
|
||||
*
|
||||
* @var array ( $this->type->slug => $code )
|
||||
*/
|
||||
protected static $error_code = [];
|
||||
|
||||
/**
|
||||
* Holds site options.
|
||||
*
|
||||
* @var array $options
|
||||
*/
|
||||
protected static $options;
|
||||
|
||||
/**
|
||||
* Holds extra headers.
|
||||
*
|
||||
* @var array $extra_headers
|
||||
*/
|
||||
protected static $extra_headers;
|
||||
|
||||
/**
|
||||
* Variable for setting update transient hours.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $hours = 12;
|
||||
|
||||
/**
|
||||
* Variable to hold all repository remote info.
|
||||
*
|
||||
* @access protected
|
||||
* @var array
|
||||
*/
|
||||
protected $response = [];
|
||||
|
||||
/**
|
||||
* Variable to hold AWS redirect URL.
|
||||
*
|
||||
* @var string|\WP_Error $redirect
|
||||
*/
|
||||
protected $redirect;
|
||||
|
||||
/**
|
||||
* API constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
static::$options = $this->get_class_vars( 'Base', 'options' );
|
||||
static::$extra_headers = Singleton::get_instance( 'Base', $this )->add_headers( [] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds custom user agent for GitHub Updater.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $args Existing HTTP Request arguments.
|
||||
* @param string $url URL being passed.
|
||||
*
|
||||
* @return array Amended HTTP Request arguments.
|
||||
*/
|
||||
public static function http_request_args( $args, $url ) {
|
||||
$args['sslverify'] = true;
|
||||
if ( false === stripos( $args['user-agent'], 'GitHub Updater' ) ) {
|
||||
$args['user-agent'] .= '; GitHub Updater - https://github.com/afragen/github-updater';
|
||||
$args['wp-rest-cache'] = [ 'tag' => 'github-updater' ];
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add data in Settings page.
|
||||
*
|
||||
* @param object $git Git API object.
|
||||
*/
|
||||
public function settings_hook( $git ) {
|
||||
add_action(
|
||||
'github_updater_add_settings',
|
||||
function ( $auth_required ) use ( $git ) {
|
||||
$git->add_settings( $auth_required );
|
||||
}
|
||||
);
|
||||
add_filter( 'github_updater_add_repo_setting_field', [ $this, 'add_setting_field' ], 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add data to the setting_field in Settings.
|
||||
*
|
||||
* @param array $fields
|
||||
* @param array $repo
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_setting_field( $fields, $repo ) {
|
||||
if ( ! empty( $fields ) ) {
|
||||
return $fields;
|
||||
}
|
||||
|
||||
return $this->get_repo_api( $repo->git, $repo )->add_repo_setting_field();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get repo's API.
|
||||
*
|
||||
* @param string $git (github|bitbucket|gitlab|gitea)
|
||||
* @param bool|\stdClass $repo
|
||||
*
|
||||
* @return \Fragen\GitHub_Updater\API\Bitbucket_API|
|
||||
* \Fragen\GitHub_Updater\API\Bitbucket_Server_API|
|
||||
* \Fragen\GitHub_Updater\API\Gitea_API|
|
||||
* \Fragen\GitHub_Updater\API\GitHub_API|
|
||||
* \Fragen\GitHub_Updater\API\GitLab_API $repo_api
|
||||
*/
|
||||
public function get_repo_api( $git, $repo = false ) {
|
||||
$repo_api = null;
|
||||
$repo = $repo ?: new \stdClass();
|
||||
switch ( $git ) {
|
||||
case 'github':
|
||||
$repo_api = new GitHub_API( $repo );
|
||||
break;
|
||||
case 'bitbucket':
|
||||
if ( ! empty( $repo->enterprise ) ) {
|
||||
$repo_api = new Bitbucket_Server_API( $repo );
|
||||
} else {
|
||||
$repo_api = new Bitbucket_API( $repo );
|
||||
}
|
||||
break;
|
||||
case 'gitlab':
|
||||
$repo_api = new GitLab_API( $repo );
|
||||
break;
|
||||
case 'gitea':
|
||||
$repo_api = new Gitea_API( $repo );
|
||||
break;
|
||||
}
|
||||
|
||||
return $repo_api;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Install settings fields.
|
||||
*
|
||||
* @param object $git Git API from caller.
|
||||
*/
|
||||
public function add_install_fields( $git ) {
|
||||
add_action(
|
||||
'github_updater_add_install_settings_fields',
|
||||
function ( $type ) use ( $git ) {
|
||||
$git->add_install_settings_fields( $type );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the API and return a json decoded body.
|
||||
* Create error messages.
|
||||
*
|
||||
* @link http://developer.github.com/v3/
|
||||
*
|
||||
* @param string $url The URL to send the request to.
|
||||
*
|
||||
* @return boolean|\stdClass
|
||||
*/
|
||||
protected function api( $url ) {
|
||||
add_filter( 'http_request_args', [ $this, 'http_request_args' ], 10, 2 );
|
||||
|
||||
$type = $this->return_repo_type();
|
||||
$response = wp_remote_get( $this->get_api_url( $url ) );
|
||||
$code = (int) wp_remote_retrieve_response_code( $response );
|
||||
$allowed_codes = [ 200, 404 ];
|
||||
remove_filter( 'http_request_args', [ $this, 'http_request_args' ] );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
Singleton::get_instance( 'Messages', $this )->create_error_message( $response );
|
||||
|
||||
return $response;
|
||||
}
|
||||
if ( ! in_array( $code, $allowed_codes, true ) ) {
|
||||
static::$error_code = array_merge(
|
||||
static::$error_code,
|
||||
[
|
||||
$this->type->slug => [
|
||||
'repo' => $this->type->slug,
|
||||
'code' => $code,
|
||||
'name' => $this->type->name,
|
||||
'git' => $this->type->git,
|
||||
],
|
||||
]
|
||||
);
|
||||
if ( 'github' === $type['git'] ) {
|
||||
GitHub_API::ratelimit_reset( $response, $this->type->slug );
|
||||
}
|
||||
Singleton::get_instance( 'Messages', $this )->create_error_message( $type['git'] );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Gitea doesn't return json encoded raw file.
|
||||
$response = $this->convert_body_string_to_json( $response );
|
||||
|
||||
return json_decode( wp_remote_retrieve_body( $response ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert response body to JSON.
|
||||
*
|
||||
* @param mixed $response (JSON|string)
|
||||
* @return mixed $response JSON encoded.
|
||||
*/
|
||||
private function convert_body_string_to_json( $response ) {
|
||||
if ( $this instanceof Gitea_API || $this instanceof Bitbucket_API || $this instanceof Bitbucket_Server_API ) {
|
||||
$body = wp_remote_retrieve_body( $response );
|
||||
if ( null === json_decode( $body ) ) {
|
||||
$response['body'] = json_encode( $body );
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return repo data for API calls.
|
||||
*
|
||||
* @access protected
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function return_repo_type() {
|
||||
$arr = [];
|
||||
$arr['type'] = $this->type->type;
|
||||
|
||||
switch ( $this->type->git ) {
|
||||
case 'github':
|
||||
$arr['git'] = 'github';
|
||||
$arr['base_uri'] = 'https://api.github.com';
|
||||
$arr['base_download'] = 'https://github.com';
|
||||
break;
|
||||
case 'bitbucket':
|
||||
$arr['git'] = 'bitbucket';
|
||||
if ( empty( $this->type->enterprise ) ) {
|
||||
$arr['base_uri'] = 'https://api.bitbucket.org';
|
||||
$arr['base_download'] = 'https://bitbucket.org';
|
||||
} else {
|
||||
$arr['base_uri'] = $this->type->enterprise_api;
|
||||
$arr['base_download'] = $this->type->enterprise;
|
||||
}
|
||||
break;
|
||||
case 'gitlab':
|
||||
$arr['git'] = 'gitlab';
|
||||
$arr['base_uri'] = 'https://gitlab.com/api/v4';
|
||||
$arr['base_download'] = 'https://gitlab.com';
|
||||
break;
|
||||
case 'gitea':
|
||||
$arr['git'] = 'gitea';
|
||||
$arr['base_uri'] = $this->type->enterprise . '/api/v1';
|
||||
$arr['base_download'] = $this->type->enterprise;
|
||||
break;
|
||||
}
|
||||
|
||||
return $arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return API url.
|
||||
*
|
||||
* @access protected
|
||||
*
|
||||
* @param string $endpoint The endpoint to access.
|
||||
* @param bool|string $download_link The plugin or theme download link. Defaults to false.
|
||||
*
|
||||
* @return string $endpoint
|
||||
*/
|
||||
protected function get_api_url( $endpoint, $download_link = false ) {
|
||||
$type = $this->return_repo_type();
|
||||
$segments = [
|
||||
'owner' => $this->type->owner,
|
||||
'repo' => $this->type->slug,
|
||||
'branch' => empty( $this->type->branch ) ? 'master' : $this->type->branch,
|
||||
];
|
||||
|
||||
foreach ( $segments as $segment => $value ) {
|
||||
$endpoint = str_replace( ':' . $segment, sanitize_text_field( $value ), $endpoint );
|
||||
}
|
||||
|
||||
$repo_api = $this->get_repo_api( $type['git'], $this->type );
|
||||
switch ( $type['git'] ) {
|
||||
case 'github':
|
||||
if ( ! $this->type->enterprise && $download_link ) {
|
||||
$type['base_download'] = $type['base_uri'];
|
||||
break;
|
||||
}
|
||||
if ( $this->type->enterprise_api ) {
|
||||
$type['base_download'] = $this->type->enterprise_api;
|
||||
$type['base_uri'] = null;
|
||||
if ( $download_link ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$endpoint = $repo_api->add_endpoints( $this, $endpoint );
|
||||
break;
|
||||
case 'gitlab':
|
||||
if ( ! $this->type->enterprise && $download_link ) {
|
||||
break;
|
||||
}
|
||||
if ( $this->type->enterprise ) {
|
||||
$type['base_download'] = $this->type->enterprise;
|
||||
$type['base_uri'] = null;
|
||||
if ( $download_link ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$endpoint = $repo_api->add_endpoints( $this, $endpoint );
|
||||
break;
|
||||
case 'bitbucket':
|
||||
$this->load_authentication_hooks();
|
||||
if ( $this->type->enterprise_api ) {
|
||||
if ( $download_link ) {
|
||||
$type['base_download'] = $type['base_uri'];
|
||||
break;
|
||||
}
|
||||
$endpoint = $repo_api->add_endpoints( $this, $endpoint );
|
||||
|
||||
return $this->type->enterprise_api . $endpoint;
|
||||
}
|
||||
if ( $download_link && 'release_asset' === self::$method ) {
|
||||
$type['base_download'] = $type['base_uri'];
|
||||
}
|
||||
$endpoint = $repo_api->add_endpoints( $this, $endpoint );
|
||||
break;
|
||||
case 'gitea':
|
||||
if ( $download_link ) {
|
||||
$type['base_download'] = $type['base_uri'];
|
||||
break;
|
||||
}
|
||||
$endpoint = $repo_api->add_endpoints( $this, $endpoint );
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
$base = $download_link ? $type['base_download'] : $type['base_uri'];
|
||||
|
||||
return $base . $endpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query wp.org for plugin/theme information.
|
||||
*
|
||||
* @access protected
|
||||
*
|
||||
* @return bool|int|mixed|string|\WP_Error
|
||||
*/
|
||||
protected function get_dot_org_data() {
|
||||
$response = isset( $this->response['dot_org'] ) ? $this->response['dot_org'] : false;
|
||||
|
||||
if ( ! $response ) {
|
||||
$url = "https://api.wordpress.org/{$this->type->type}s/info/1.1/";
|
||||
$url = add_query_arg(
|
||||
[
|
||||
'action' => "{$this->type->type}_information",
|
||||
rawurlencode( 'request[slug]' ) => $this->type->slug,
|
||||
],
|
||||
$url
|
||||
);
|
||||
$response = wp_remote_get( $url );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
Singleton::get_instance( 'Messages', $this )->create_error_message( $response );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$response = json_decode( $response['body'] );
|
||||
$response = ! empty( $response ) && ! isset( $response->error ) ? 'in dot org' : 'not in dot org';
|
||||
|
||||
$this->set_repo_cache( 'dot_org', $response );
|
||||
}
|
||||
|
||||
return 'in dot org' === $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add appropriate access token to endpoint.
|
||||
*
|
||||
* @access protected
|
||||
*
|
||||
* @param GitHub_API|GitLab_API $git Class containing the GitAPI used.
|
||||
* @param string $endpoint The endpoint being accessed.
|
||||
*
|
||||
* @return string $endpoint
|
||||
*/
|
||||
protected function add_access_token_endpoint( $git, $endpoint ) {
|
||||
// This will return if checking during shiny updates.
|
||||
if ( null === static::$options ) {
|
||||
return $endpoint;
|
||||
}
|
||||
$key = null;
|
||||
$token = null;
|
||||
$token_enterprise = null;
|
||||
|
||||
switch ( $git->type->git ) {
|
||||
case 'github':
|
||||
$key = 'access_token';
|
||||
$token = 'github_access_token';
|
||||
$token_enterprise = 'github_enterprise_token';
|
||||
break;
|
||||
case 'gitlab':
|
||||
$key = 'private_token';
|
||||
$token = 'gitlab_access_token';
|
||||
$token_enterprise = 'gitlab_enterprise_token';
|
||||
break;
|
||||
case 'gitea':
|
||||
$key = 'access_token';
|
||||
$token = 'gitea_access_token';
|
||||
$token_enterprise = 'gitea_access_token';
|
||||
break;
|
||||
case 'bitbucket':
|
||||
return $endpoint;
|
||||
}
|
||||
|
||||
// Add hosted access token.
|
||||
if ( ! empty( static::$options[ $token ] ) ) {
|
||||
$endpoint = add_query_arg( $key, static::$options[ $token ], $endpoint );
|
||||
}
|
||||
|
||||
// Add Enterprise access token.
|
||||
if ( ! empty( $git->type->enterprise ) &&
|
||||
! empty( static::$options[ $token_enterprise ] )
|
||||
) {
|
||||
$endpoint = remove_query_arg( $key, $endpoint );
|
||||
$endpoint = add_query_arg( $key, static::$options[ $token_enterprise ], $endpoint );
|
||||
}
|
||||
|
||||
// Add repo access token.
|
||||
if ( ! empty( static::$options[ $git->type->slug ] ) ) {
|
||||
$endpoint = remove_query_arg( $key, $endpoint );
|
||||
$endpoint = add_query_arg( $key, static::$options[ $git->type->slug ], $endpoint );
|
||||
}
|
||||
|
||||
return $endpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to exit early if no update available, saves API calls.
|
||||
*
|
||||
* @param array|bool $response
|
||||
* @param bool $branch
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function exit_no_update( $response, $branch = false ) {
|
||||
/**
|
||||
* Filters the return value of exit_no_update.
|
||||
*
|
||||
* @since 6.0.0
|
||||
* @return bool `true` will exit this function early, default will not.
|
||||
*/
|
||||
if ( apply_filters( 'ghu_always_fetch_update', false ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( $branch ) {
|
||||
return empty( static::$options['branch_switch'] );
|
||||
}
|
||||
|
||||
return ! isset( $_POST['ghu_refresh_cache'] ) && ! $response && ! $this->can_update_repo( $this->type );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate wp_remote_get response.
|
||||
*
|
||||
* @access protected
|
||||
*
|
||||
* @param \stdClass $response The response.
|
||||
*
|
||||
* @return bool true if invalid
|
||||
*/
|
||||
protected function validate_response( $response ) {
|
||||
return empty( $response ) || isset( $response->message ) || is_wp_error( $response );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a local file for the repository exists.
|
||||
* Only checks the root directory of the repository.
|
||||
*
|
||||
* @access protected
|
||||
*
|
||||
* @param string $filename The filename to check for.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function local_file_exists( $filename ) {
|
||||
return file_exists( $this->type->local_path . $filename );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort tags and set object data.
|
||||
*
|
||||
* @param array $parsed_tags
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function sort_tags( $parsed_tags ) {
|
||||
if ( empty( $parsed_tags ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
list($tags, $rollback) = $parsed_tags;
|
||||
usort( $tags, 'version_compare' );
|
||||
krsort( $rollback );
|
||||
|
||||
$newest_tag = array_slice( $tags, -1, 1, true );
|
||||
$newest_tag_key = key( $newest_tag );
|
||||
$newest_tag = $tags[ $newest_tag_key ];
|
||||
|
||||
$this->type->newest_tag = $newest_tag;
|
||||
$this->type->tags = $tags;
|
||||
$this->type->rollback = $rollback;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get local file info if no update available. Save API calls.
|
||||
*
|
||||
* @param \stdClass $repo Repo data.
|
||||
* @param string $file
|
||||
*
|
||||
* @return null|string
|
||||
*/
|
||||
protected function get_local_info( $repo, $file ) {
|
||||
$response = false;
|
||||
|
||||
if ( isset( $_POST['ghu_refresh_cache'] ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
if ( is_dir( $repo->local_path ) &&
|
||||
file_exists( $repo->local_path . $file )
|
||||
) {
|
||||
$response = file_get_contents( $repo->local_path . $file );
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set repo object file info.
|
||||
*
|
||||
* @param array $response Repo data.
|
||||
*/
|
||||
protected function set_file_info( $response ) {
|
||||
$this->type->transient = $response;
|
||||
$this->type->remote_version = strtolower( $response['Version'] );
|
||||
$this->type->requires_php = ! empty( $response['Requires PHP'] ) ? $response['Requires PHP'] : false;
|
||||
$this->type->requires = ! empty( $response['Requires WP'] ) ? $response['Requires WP'] : null;
|
||||
$this->type->dot_org = $response['dot_org'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add remote data to type object.
|
||||
*
|
||||
* @access protected
|
||||
*/
|
||||
protected function add_meta_repo_object() {
|
||||
$this->type->rating = $this->make_rating( $this->type->repo_meta );
|
||||
$this->type->last_updated = $this->type->repo_meta['last_updated'];
|
||||
$this->type->num_ratings = $this->type->repo_meta['watchers'];
|
||||
$this->type->is_private = $this->type->repo_meta['private'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create some sort of rating from 0 to 100 for use in star ratings.
|
||||
* I'm really just making this up, more based upon popularity.
|
||||
*
|
||||
* @param array $repo_meta
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
protected function make_rating( $repo_meta ) {
|
||||
$watchers = ! empty( $repo_meta['watchers'] ) ? $repo_meta['watchers'] : 0;
|
||||
$forks = ! empty( $repo_meta['forks'] ) ? $repo_meta['forks'] : 0;
|
||||
$open_issues = ! empty( $repo_meta['open_issues'] ) ? $repo_meta['open_issues'] : 0;
|
||||
|
||||
$rating = abs( (int) round( $watchers + ( $forks * 1.5 ) - ( $open_issues * 0.1 ) ) );
|
||||
|
||||
if ( 100 < $rating ) {
|
||||
return 100;
|
||||
}
|
||||
|
||||
return $rating;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set data from readme.txt.
|
||||
* Prefer changelog from CHANGES.md.
|
||||
*
|
||||
* @param array $readme Array of parsed readme.txt data.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function set_readme_info( $readme ) {
|
||||
foreach ( (array) $this->type->sections as $section => $value ) {
|
||||
if ( 'description' === $section ) {
|
||||
continue;
|
||||
}
|
||||
$readme['sections'][ $section ] = $value;
|
||||
}
|
||||
|
||||
$readme['remaining_content'] = ! empty( $readme['remaining_content'] ) ? $readme['remaining_content'] : null;
|
||||
if ( empty( $readme['sections']['other_notes'] ) ) {
|
||||
unset( $readme['sections']['other_notes'] );
|
||||
} else {
|
||||
$readme['sections']['other_notes'] .= $readme['remaining_content'];
|
||||
}
|
||||
unset( $readme['sections']['screenshots'], $readme['sections']['installation'] );
|
||||
$readme['sections'] = ! empty( $readme['sections'] ) ? $readme['sections'] : [];
|
||||
$this->type->sections = array_merge( (array) $this->type->sections, (array) $readme['sections'] );
|
||||
$this->type->tested = isset( $readme['tested'] ) ? $readme['tested'] : null;
|
||||
$this->type->requires = isset( $readme['requires'] ) ? $readme['requires'] : null;
|
||||
$this->type->requires_php = isset( $readme['requires_php'] ) ? $readme['requires_php'] : null;
|
||||
$this->type->donate_link = isset( $readme['donate_link'] ) ? $readme['donate_link'] : null;
|
||||
$this->type->contributors = isset( $readme['contributors'] ) ? $readme['contributors'] : null;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the redirect download link for a release asset.
|
||||
* AWS download link sets a link expiration of ONLY 5 minutes.
|
||||
*
|
||||
* @since 6.1.0
|
||||
* @uses Requests, requires WP 4.6
|
||||
*
|
||||
* @param string $asset Release asset URI from git host.
|
||||
*
|
||||
* @return string|bool|\stdClass Release asset URI from AWS.
|
||||
*/
|
||||
protected function get_release_asset_redirect( $asset, $aws = false ) {
|
||||
if ( ! $asset ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Unset release asset url if older than 5 min to account for AWS expiration.
|
||||
if ( $aws && ( time() - strtotime( '-12 hours', $this->response['timeout'] ) ) >= 300 ) {
|
||||
unset( $this->response['release_asset_redirect'] );
|
||||
}
|
||||
|
||||
$response = isset( $this->response['release_asset_redirect'] ) ? $this->response['release_asset_redirect'] : false;
|
||||
|
||||
if ( $this->exit_no_update( $response ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! $response ) {
|
||||
add_action( 'requests-requests.before_redirect', [ $this, 'set_redirect' ], 10, 1 );
|
||||
add_filter( 'http_request_args', [ $this, 'set_aws_release_asset_header' ] );
|
||||
$url = $this->add_access_token_endpoint( $this, $asset );
|
||||
wp_remote_get( $url );
|
||||
remove_filter( 'http_request_args', [ $this, 'set_aws_release_asset_header' ] );
|
||||
}
|
||||
|
||||
if ( ! empty( $this->redirect ) ) {
|
||||
$this->set_repo_cache( 'release_asset_redirect', $this->redirect );
|
||||
|
||||
return $this->redirect;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set HTTP header for following AWS release assets.
|
||||
*
|
||||
* @since 6.1.0
|
||||
*
|
||||
* @param array $args
|
||||
* @param string $url
|
||||
*
|
||||
* @return mixed $args
|
||||
*/
|
||||
public function set_aws_release_asset_header( $args, $url = '' ) {
|
||||
$args['headers']['accept'] = 'application/octet-stream';
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set AWS redirect URL from action hook.
|
||||
*
|
||||
* @uses `requests-requests.before_redirect` Action hook.
|
||||
*
|
||||
* @param string $location
|
||||
* @return void
|
||||
*/
|
||||
public function set_redirect( $location ) {
|
||||
$this->redirect = $location;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
<?php
|
||||
/**
|
||||
* GitHub Updater
|
||||
*
|
||||
* @author Andy Fragen
|
||||
* @license GPL-2.0+
|
||||
* @link https://github.com/afragen/github-updater
|
||||
* @package github-updater
|
||||
*/
|
||||
|
||||
namespace Fragen\GitHub_Updater\API;
|
||||
|
||||
/**
|
||||
* Interface API_Interface
|
||||
*/
|
||||
interface API_Interface {
|
||||
/**
|
||||
* Read the remote file and parse headers.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $file Filename.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get_remote_info( $file);
|
||||
|
||||
/**
|
||||
* Get remote info for tags.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get_remote_tag();
|
||||
|
||||
/**
|
||||
* Read the remote CHANGES.md file.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $changes Changelog filename.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get_remote_changes( $changes);
|
||||
|
||||
/**
|
||||
* Read and parse remote readme.txt.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get_remote_readme();
|
||||
|
||||
/**
|
||||
* Read the repository meta from API.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get_repo_meta();
|
||||
|
||||
/**
|
||||
* Create array of branches and download links as array.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_remote_branches();
|
||||
|
||||
/**
|
||||
* Get release asset URL.
|
||||
*
|
||||
* @return string|bool
|
||||
*/
|
||||
public function get_release_asset();
|
||||
|
||||
/**
|
||||
* Construct $this->type->download_link using Repository Contents API.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param bool $branch_switch For direct branch switching. Defaults to false.
|
||||
*
|
||||
* @return string URL for download link.
|
||||
*/
|
||||
public function construct_download_link( $branch_switch = false);
|
||||
|
||||
/**
|
||||
* Create endpoints.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param GitHub_API|Bitbucket_API|Bitbucket_Server_API|GitLab_API $git
|
||||
* @param string $endpoint
|
||||
*
|
||||
* @return string $endpoint
|
||||
*/
|
||||
public function add_endpoints( $git, $endpoint);
|
||||
|
||||
/**
|
||||
* Parse API response call and return only array of tag numbers.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param \stdClass|array $response API response.
|
||||
*
|
||||
* @return array|\stdClass Array of tag numbers, object is error.
|
||||
*/
|
||||
public function parse_tag_response( $response);
|
||||
|
||||
/**
|
||||
* Parse API response and return array of meta variables.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param \stdClass|array $response API response.
|
||||
*
|
||||
* @return array|\stdClass Array of meta variables.
|
||||
*/
|
||||
public function parse_meta_response( $response);
|
||||
|
||||
/**
|
||||
* Parse API response and return array with changelog.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param \stdClass|array $response API response.
|
||||
*
|
||||
* @return array|\stdClass $arr Array of changes in base64, object if error.
|
||||
*/
|
||||
public function parse_changelog_response( $response);
|
||||
|
||||
/**
|
||||
* Parse API response and return array of branch data.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param \stdClass $response API response.
|
||||
*
|
||||
* @return array Array of branch data.
|
||||
*/
|
||||
public function parse_branch_response( $response );
|
||||
|
||||
/**
|
||||
* Add values for individual repo add_setting_field().
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function add_repo_setting_field();
|
||||
|
||||
/**
|
||||
* Add settings for each API.
|
||||
*
|
||||
* @param array $auth_required
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function add_settings( $auth_required);
|
||||
|
||||
/**
|
||||
* Add remote install settings fields.
|
||||
*
|
||||
* @param string $type plugin|theme.
|
||||
*/
|
||||
public function add_install_settings_fields( $type);
|
||||
|
||||
/**
|
||||
* Add remote install feature, create endpoint.
|
||||
*
|
||||
* @param array $headers
|
||||
* @param array $install
|
||||
*
|
||||
* @return mixed $install
|
||||
*/
|
||||
public function remote_install( $headers, $install);
|
||||
}
|
||||
@@ -0,0 +1,584 @@
|
||||
<?php
|
||||
/**
|
||||
* GitHub Updater
|
||||
*
|
||||
* @author Andy Fragen
|
||||
* @license GPL-2.0+
|
||||
* @link https://github.com/afragen/github-updater
|
||||
* @package github-updater
|
||||
*/
|
||||
|
||||
namespace Fragen\GitHub_Updater\API;
|
||||
|
||||
use Fragen\Singleton;
|
||||
use Fragen\GitHub_Updater\API;
|
||||
use Fragen\GitHub_Updater\Branch;
|
||||
|
||||
/*
|
||||
* Exit if called directly.
|
||||
*/
|
||||
if ( ! defined( 'WPINC' ) ) {
|
||||
die;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class Bitbucket_API
|
||||
*
|
||||
* Get remote data from a Bitbucket repo.
|
||||
*
|
||||
* @author Andy Fragen
|
||||
*/
|
||||
class Bitbucket_API extends API implements API_Interface {
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param \stdClass $type The repo type.
|
||||
*/
|
||||
public function __construct( $type ) {
|
||||
parent::__construct();
|
||||
$this->type = $type;
|
||||
$this->response = $this->get_repo_cache();
|
||||
$branch = new Branch( $this->response );
|
||||
if ( ! empty( $type->branch ) ) {
|
||||
$this->type->branch = ! empty( $branch->cache['current_branch'] )
|
||||
? $branch->cache['current_branch']
|
||||
: $type->branch;
|
||||
}
|
||||
$this->set_default_credentials();
|
||||
$this->settings_hook( $this );
|
||||
$this->add_settings_subtab();
|
||||
$this->add_install_fields( $this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default credentials if option not set.
|
||||
*/
|
||||
protected function set_default_credentials() {
|
||||
$running_servers = Singleton::get_instance( 'Base', $this )->get_running_git_servers();
|
||||
$set_credentials = false;
|
||||
if ( $this instanceof Bitbucket_API ) {
|
||||
$username = 'bitbucket_username';
|
||||
$password = 'bitbucket_password';
|
||||
}
|
||||
if ( $this instanceof Bitbucket_Server_API ) {
|
||||
$username = 'bitbucket_server_username';
|
||||
$password = 'bitbucket_server_password';
|
||||
}
|
||||
if ( ! isset( static::$options[ $username ] ) ) {
|
||||
static::$options[ $username ] = null;
|
||||
$set_credentials = true;
|
||||
}
|
||||
if ( ! isset( static::$options[ $password ] ) ) {
|
||||
static::$options[ $password ] = null;
|
||||
$set_credentials = true;
|
||||
}
|
||||
if ( ( empty( static::$options[ $username ] ) || empty( static::$options[ $password ] ) ) &&
|
||||
( ( 'bitbucket_username' === $username &&
|
||||
in_array( 'bitbucket', $running_servers, true ) ) ||
|
||||
( 'bitbucket_server_username' === $username &&
|
||||
in_array( 'bbserver', $running_servers, true ) ) )
|
||||
) {
|
||||
Singleton::get_instance( 'Messages', $this )->create_error_message( 'bitbucket' );
|
||||
static::$error_code['bitbucket'] = [ 'code' => 401 ];
|
||||
}
|
||||
if ( $set_credentials ) {
|
||||
add_site_option( 'github_updater', static::$options );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the remote file and parse headers.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $file The file.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_remote_info( $file ) {
|
||||
return $this->get_remote_api_info( 'bitbucket', $file, "/2.0/repositories/:owner/:repo/src/:branch/{$file}" );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the remote info for tags.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_remote_tag() {
|
||||
return $this->get_remote_api_tag( 'bitbucket', '/2.0/repositories/:owner/:repo/refs/tags' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the remote CHANGES.md file.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $changes The changelog filename.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_remote_changes( $changes ) {
|
||||
return $this->get_remote_api_changes( 'bitbucket', $changes, "/2.0/repositories/:owner/:repo/src/:branch/{$changes}" );
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and parse remote readme.txt.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_remote_readme() {
|
||||
return $this->get_remote_api_readme( 'bitbucket', '/2.0/repositories/:owner/:repo/src/:branch/readme.txt' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the repository meta from API
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_repo_meta() {
|
||||
return $this->get_remote_api_repo_meta( 'bitbucket', '/2.0/repositories/:owner/:repo' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create array of branches and download links as array.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_remote_branches() {
|
||||
return $this->get_remote_api_branches( 'bitbucket', '/2.0/repositories/:owner/:repo/refs/branches' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Bitbucket release asset URL.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_release_asset() {
|
||||
return $this->get_api_release_asset( 'bitbucket', '/2.0/repositories/:owner/:repo/downloads' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct $this->type->download_link using Bitbucket API
|
||||
*
|
||||
* @param boolean $branch_switch For direct branch changing. Defaults to false.
|
||||
*
|
||||
* @return string $endpoint
|
||||
*/
|
||||
public function construct_download_link( $branch_switch = false ) {
|
||||
self::$method = 'download_link';
|
||||
$download_link_base = $this->get_api_url( '/:owner/:repo/get/', true );
|
||||
$endpoint = '';
|
||||
|
||||
// Release asset.
|
||||
if ( $this->type->release_asset && '0.0.0' !== $this->type->newest_tag ) {
|
||||
$release_asset = $this->get_release_asset();
|
||||
return $this->get_release_asset_redirect( $release_asset, true );
|
||||
}
|
||||
|
||||
/*
|
||||
* If a branch has been given, use branch.
|
||||
* If branch is master (default) and tags are used, use newest tag.
|
||||
*/
|
||||
if ( 'master' !== $this->type->branch || empty( $this->type->tags ) ) {
|
||||
if ( ! empty( $this->type->enterprise_api ) ) {
|
||||
$endpoint = add_query_arg( 'at', $this->type->branch, $endpoint );
|
||||
} else {
|
||||
$endpoint .= $this->type->branch . '.zip';
|
||||
}
|
||||
} else {
|
||||
if ( ! empty( $this->type->enterprise_api ) ) {
|
||||
$endpoint = add_query_arg( 'at', $this->type->newest_tag, $endpoint );
|
||||
} else {
|
||||
$endpoint .= $this->type->newest_tag . '.zip';
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Create endpoint for branch switching.
|
||||
*/
|
||||
if ( $branch_switch ) {
|
||||
if ( ! empty( $this->type->enterprise_api ) ) {
|
||||
$endpoint = add_query_arg( 'at', $branch_switch, $endpoint );
|
||||
} else {
|
||||
$endpoint = $branch_switch . '.zip';
|
||||
}
|
||||
}
|
||||
|
||||
$download_link = $download_link_base . $endpoint;
|
||||
|
||||
/**
|
||||
* Filter download link so developers can point to specific ZipFile
|
||||
* to use as a download link during a branch switch.
|
||||
*
|
||||
* @since 8.8.0
|
||||
*
|
||||
* @param string $download_link Download URL.
|
||||
* @param /stdClass $this->type Repository object.
|
||||
* @param string $branch_switch Branch or tag for rollback or branch switching.
|
||||
*/
|
||||
return apply_filters( 'github_updater_post_construct_download_link', $download_link, $this->type, $branch_switch );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Bitbucket API endpoints.
|
||||
*
|
||||
* @param Bitbucket_API|API $git
|
||||
* @param string $endpoint
|
||||
*
|
||||
* @return string|void $endpoint
|
||||
*/
|
||||
public function add_endpoints( $git, $endpoint ) {
|
||||
switch ( $git::$method ) {
|
||||
case 'file':
|
||||
case 'readme':
|
||||
case 'meta':
|
||||
case 'changes':
|
||||
case 'translation':
|
||||
case 'release_asset':
|
||||
case 'download_link':
|
||||
break;
|
||||
case 'tags':
|
||||
case 'branches':
|
||||
$endpoint = add_query_arg(
|
||||
[
|
||||
'pagelen' => '100',
|
||||
'sort' => '-name',
|
||||
],
|
||||
$endpoint
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
$endpoint = $this->add_access_token_endpoint( $git, $endpoint );
|
||||
|
||||
return $endpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse API response call and return only array of tag numbers.
|
||||
*
|
||||
* @param \stdClass $response Response from API call.
|
||||
*
|
||||
* @return array|\stdClass Array of tag numbers, object is error.
|
||||
*/
|
||||
public function parse_tag_response( $response ) {
|
||||
if ( ! isset( $response->values ) || $this->validate_response( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$arr = [];
|
||||
array_map(
|
||||
function ( $e ) use ( &$arr ) {
|
||||
$arr[] = $e->name;
|
||||
|
||||
return $arr;
|
||||
},
|
||||
(array) $response->values
|
||||
);
|
||||
|
||||
return $arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse API response and return array of meta variables.
|
||||
*
|
||||
* @param \stdClass|array $response Response from API call.
|
||||
*
|
||||
* @return array $arr Array of meta variables.
|
||||
*/
|
||||
public function parse_meta_response( $response ) {
|
||||
if ( $this->validate_response( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
$arr = [];
|
||||
$response = [ $response ];
|
||||
|
||||
array_filter(
|
||||
$response,
|
||||
function ( $e ) use ( &$arr ) {
|
||||
$arr['private'] = $e->is_private;
|
||||
$arr['last_updated'] = $e->updated_on;
|
||||
$arr['watchers'] = 0;
|
||||
$arr['forks'] = 0;
|
||||
$arr['open_issues'] = 0;
|
||||
}
|
||||
);
|
||||
|
||||
return $arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse API response and return array with changelog in base64.
|
||||
*
|
||||
* @param \stdClass|array $response Response from API call.
|
||||
*
|
||||
* @return array|\stdClass $arr Array of changes in base64, object if error.
|
||||
*/
|
||||
public function parse_changelog_response( $response ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse API response and return array of branch data.
|
||||
*
|
||||
* @param \stdClass $response API response.
|
||||
*
|
||||
* @return array Array of branch data.
|
||||
*/
|
||||
public function parse_branch_response( $response ) {
|
||||
if ( $this->validate_response( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
$branches = [];
|
||||
foreach ( $response as $branch ) {
|
||||
$branches[ $branch->name ]['download'] = $this->construct_download_link( $branch->name );
|
||||
$branches[ $branch->name ]['commit_hash'] = $branch->target->hash;
|
||||
$branches[ $branch->name ]['commit_timestamp'] = $branch->target->date;
|
||||
}
|
||||
return $branches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse tags and create download links.
|
||||
*
|
||||
* @param \stdClass|array $response Response from API call.
|
||||
* @param string $repo_type
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function parse_tags( $response, $repo_type ) {
|
||||
$tags = [];
|
||||
$rollback = [];
|
||||
|
||||
foreach ( (array) $response as $tag ) {
|
||||
// $download_base = implode(
|
||||
// '/',
|
||||
// [
|
||||
// $repo_type['base_download'],
|
||||
// $this->type->owner,
|
||||
// $this->type->owner,
|
||||
// 'get/',
|
||||
// ]
|
||||
// );
|
||||
$download_base = "{$repo_type['base_download']}/{$this->type->owner}/{$this->type->owner}/get/";
|
||||
$tags[] = $tag;
|
||||
$rollback[ $tag ] = $download_base . $tag . '.zip';
|
||||
}
|
||||
|
||||
return [ $tags, $rollback ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add settings for Bitbucket Username and Password.
|
||||
*
|
||||
* @param array $auth_required
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_settings( $auth_required ) {
|
||||
add_settings_section(
|
||||
'bitbucket_user',
|
||||
esc_html__( 'Bitbucket Private Settings', 'github-updater' ),
|
||||
[ $this, 'print_section_bitbucket_username' ],
|
||||
'github_updater_bitbucket_install_settings'
|
||||
);
|
||||
|
||||
add_settings_field(
|
||||
'bitbucket_username',
|
||||
esc_html__( 'Bitbucket Username', 'github-updater' ),
|
||||
[ Singleton::get_instance( 'Settings', $this ), 'token_callback_text' ],
|
||||
'github_updater_bitbucket_install_settings',
|
||||
'bitbucket_user',
|
||||
[ 'id' => 'bitbucket_username' ]
|
||||
);
|
||||
|
||||
add_settings_field(
|
||||
'bitbucket_password',
|
||||
esc_html__( 'Bitbucket Password', 'github-updater' ),
|
||||
[ Singleton::get_instance( 'Settings', $this ), 'token_callback_text' ],
|
||||
'github_updater_bitbucket_install_settings',
|
||||
'bitbucket_user',
|
||||
[
|
||||
'id' => 'bitbucket_password',
|
||||
'token' => true,
|
||||
]
|
||||
);
|
||||
|
||||
/*
|
||||
* Show section for private Bitbucket repositories.
|
||||
*/
|
||||
if ( $auth_required['bitbucket_private'] ) {
|
||||
add_settings_section(
|
||||
'bitbucket_id',
|
||||
esc_html__( 'Bitbucket Private Repositories', 'github-updater' ),
|
||||
[ $this, 'print_section_bitbucket_info' ],
|
||||
'github_updater_bitbucket_install_settings'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add values for individual repo add_setting_field().
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function add_repo_setting_field() {
|
||||
$setting_field['page'] = 'github_updater_bitbucket_install_settings';
|
||||
$setting_field['section'] = 'bitbucket_id';
|
||||
$setting_field['callback_method'] = [
|
||||
Singleton::get_instance( 'Settings', $this ),
|
||||
'token_callback_checkbox',
|
||||
];
|
||||
|
||||
return $setting_field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add subtab to Settings page.
|
||||
*/
|
||||
private function add_settings_subtab() {
|
||||
add_filter(
|
||||
'github_updater_add_settings_subtabs',
|
||||
function ( $subtabs ) {
|
||||
return array_merge( $subtabs, [ 'bitbucket' => esc_html__( 'Bitbucket', 'github-updater' ) ] );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the Bitbucket repo Settings text.
|
||||
*/
|
||||
public function print_section_bitbucket_info() {
|
||||
esc_html_e( 'Check box if private repository. Leave unchecked for public repositories.', 'github-updater' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the Bitbucket user/pass Settings text.
|
||||
*/
|
||||
public function print_section_bitbucket_username() {
|
||||
esc_html_e( 'Enter your personal Bitbucket username and password.', 'github-updater' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add remote install settings fields.
|
||||
*
|
||||
* @param string $type
|
||||
*/
|
||||
public function add_install_settings_fields( $type ) {
|
||||
if ( ( empty( static::$options['bitbucket_username'] ) ||
|
||||
empty( static::$options['bitbucket_password'] ) ) ||
|
||||
|
||||
( empty( static::$options['bitbucket_server_username'] ) ||
|
||||
empty( static::$options['bitbucket_server_password'] ) )
|
||||
) {
|
||||
add_settings_field(
|
||||
'bitbucket_username',
|
||||
esc_html__( 'Bitbucket Username', 'github-updater' ),
|
||||
[ $this, 'bitbucket_username' ],
|
||||
'github_updater_install_' . $type,
|
||||
$type
|
||||
);
|
||||
|
||||
add_settings_field(
|
||||
'bitbucket_password',
|
||||
esc_html__( 'Bitbucket Password', 'github-updater' ),
|
||||
[ $this, 'bitbucket_password' ],
|
||||
'github_updater_install_' . $type,
|
||||
$type
|
||||
);
|
||||
}
|
||||
|
||||
add_settings_field(
|
||||
'is_private',
|
||||
esc_html__( 'Private Bitbucket Repository', 'github-updater' ),
|
||||
[ $this, 'is_private_repo' ],
|
||||
'github_updater_install_' . $type,
|
||||
$type
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setting for private repo for remote install.
|
||||
*/
|
||||
public function is_private_repo() {
|
||||
?>
|
||||
<label for="is_private">
|
||||
<input class="bitbucket_setting" type="checkbox" id="is_private" name="is_private" <?php checked( '1', false ); ?> >
|
||||
<br>
|
||||
<span class="description">
|
||||
<?php esc_html_e( 'Check for private Bitbucket repositories.', 'github-updater' ); ?>
|
||||
</span>
|
||||
</label>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Bitbucket username for remote install.
|
||||
*/
|
||||
public function bitbucket_username() {
|
||||
?>
|
||||
<label for="bitbucket_username">
|
||||
<input class="bitbucket_setting" type="text" style="width:50%;" id="bitbucket_username" name="bitbucket_username" value="">
|
||||
<br>
|
||||
<span class="description">
|
||||
<?php esc_html_e( 'Enter Bitbucket username.', 'github-updater' ); ?>
|
||||
</span>
|
||||
</label>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Bitbucket password for remote install.
|
||||
*/
|
||||
public function bitbucket_password() {
|
||||
?>
|
||||
<label for="bitbucket_password">
|
||||
<input class="bitbucket_setting" type="password" style="width:50%;" id="bitbucket_password" name="bitbucket_password" value="" autocomplete="new-password">
|
||||
<br>
|
||||
<span class="description">
|
||||
<?php esc_html_e( 'Enter Bitbucket password.', 'github-updater' ); ?>
|
||||
</span>
|
||||
</label>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Add remote install feature, create endpoint.
|
||||
*
|
||||
* @param array $headers
|
||||
* @param array $install
|
||||
*
|
||||
* @return mixed $install
|
||||
*/
|
||||
public function remote_install( $headers, $install ) {
|
||||
$bitbucket_org = true;
|
||||
|
||||
if ( 'bitbucket.org' === $headers['host'] || empty( $headers['host'] ) ) {
|
||||
$base = 'https://bitbucket.org';
|
||||
$headers['host'] = 'bitbucket.org';
|
||||
} else {
|
||||
$base = $headers['base_uri'];
|
||||
$bitbucket_org = false;
|
||||
}
|
||||
|
||||
if ( $bitbucket_org ) {
|
||||
$install['download_link'] = "{$base}/{$install['github_updater_repo']}/get/{$install['github_updater_branch']}.zip";
|
||||
if ( isset( $install['is_private'] ) ) {
|
||||
$install['options'][ $install['repo'] ] = 1;
|
||||
}
|
||||
if ( ! empty( $install['bitbucket_username'] ) ) {
|
||||
$install['options']['bitbucket_username'] = $install['bitbucket_username'];
|
||||
}
|
||||
if ( ! empty( $install['bitbucket_password'] ) ) {
|
||||
$install['options']['bitbucket_password'] = $install['bitbucket_password'];
|
||||
}
|
||||
}
|
||||
|
||||
return $install;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,451 @@
|
||||
<?php
|
||||
/**
|
||||
* GitHub Updater
|
||||
*
|
||||
* @author Andy Fragen
|
||||
* @license GPL-2.0+
|
||||
* @link https://github.com/afragen/github-updater
|
||||
* @package github-updater
|
||||
*/
|
||||
|
||||
namespace Fragen\GitHub_Updater\API;
|
||||
|
||||
use Fragen\Singleton;
|
||||
use Fragen\GitHub_Updater\API;
|
||||
use Fragen\GitHub_Updater\Readme_Parser;
|
||||
|
||||
/*
|
||||
* Exit if called directly.
|
||||
*/
|
||||
if ( ! defined( 'WPINC' ) ) {
|
||||
die;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class Bitbucket_Server_API
|
||||
*
|
||||
* Get remote data from a self-hosted Bitbucket Server repo.
|
||||
* Assumes an owner == project_key
|
||||
* Group URI: https://bitbucket.example.com/projects/<owner>/<repo>
|
||||
* User URI: https://bitbucket.example.com/users/<owner>/<repo>
|
||||
*
|
||||
* @link https://docs.atlassian.com/bitbucket-server/rest/5.3.1/bitbucket-rest.html
|
||||
*
|
||||
* @author Andy Fragen
|
||||
* @author Bjorn Wijers
|
||||
*/
|
||||
class Bitbucket_Server_API extends Bitbucket_API {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param \stdClass $type
|
||||
*/
|
||||
public function __construct( $type ) {
|
||||
parent::__construct( $type );
|
||||
$this->add_settings_subtab();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the remote file and parse headers.
|
||||
*
|
||||
* @param string $file Filename.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_remote_info( $file ) {
|
||||
return $this->get_remote_api_info( 'bbserver', $file, "/1.0/:owner/repos/:repo/browse/{$file}" );
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the repository meta from API
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_repo_meta() {
|
||||
return $this->get_remote_api_repo_meta( 'bbserver', '/1.0/:owner/repos/:repo' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the remote info for tags.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_remote_tag() {
|
||||
return $this->get_remote_api_tag( 'bbserver', '/1.0/:owner/repos/:repo/tags' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and parse remote readme.txt.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_remote_readme() {
|
||||
return $this->get_remote_api_readme( 'bbserver', '/1.0/:owner/repos/:repo/raw/readme.txt' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the remote CHANGES.md file
|
||||
*
|
||||
* @param string $changes Changelog filename.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_remote_changes( $changes ) {
|
||||
return $this->get_remote_api_changes( 'bbserver', $changes, "/1.0/:owner/repos/:repo/raw/{$changes}" );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create array of branches and download links as array.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_remote_branches() {
|
||||
return $this->get_remote_api_branches( 'bbserver', '/1.0/:owner/repos/:repo/branches' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Bitbucket Sever release asset URL.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_release_asset() {
|
||||
// TODO: make this work.
|
||||
// return $this->get_api_release_asset( 'bbserver', '/1.0/:owner/:repo/downloads' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct $this->type->download_link using Bitbucket Server API.
|
||||
*
|
||||
* Downloads requires the official stash-archive plugin which enables
|
||||
* subdirectory support using the prefix query argument.
|
||||
*
|
||||
* @link https://bitbucket.org/atlassian/stash-archive
|
||||
*
|
||||
* @param boolean $branch_switch for direct branch changing.
|
||||
*
|
||||
* @return string $endpoint
|
||||
*/
|
||||
public function construct_download_link( $branch_switch = false ) {
|
||||
self::$method = 'download_link';
|
||||
$download_link_base = $this->get_api_url( '/latest/:owner/repos/:repo/archive', true );
|
||||
$endpoint = $this->add_endpoints( $this, '' );
|
||||
|
||||
if ( $branch_switch ) {
|
||||
$endpoint = urldecode( add_query_arg( 'at', $branch_switch, $endpoint ) );
|
||||
}
|
||||
|
||||
return $download_link_base . $endpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Bitbucket Server API endpoints.
|
||||
*
|
||||
* @param Bitbucket_Server_API|API $git
|
||||
* @param string $endpoint
|
||||
*
|
||||
* @return string $endpoint
|
||||
*/
|
||||
public function add_endpoints( $git, $endpoint ) {
|
||||
switch ( self::$method ) {
|
||||
case 'meta':
|
||||
case 'translation':
|
||||
case 'branches':
|
||||
break;
|
||||
case 'file':
|
||||
case 'readme':
|
||||
$endpoint = add_query_arg( 'at', $git->type->branch, $endpoint );
|
||||
break;
|
||||
case 'changes':
|
||||
$endpoint = add_query_arg(
|
||||
[
|
||||
'at' => $git->type->branch,
|
||||
'raw' => '',
|
||||
],
|
||||
$endpoint
|
||||
);
|
||||
break;
|
||||
case 'tags':
|
||||
case 'download_link':
|
||||
/*
|
||||
* Add a prefix query argument to create a subdirectory with the same name
|
||||
* as the repo, e.g. 'my-repo' becomes 'my-repo/'
|
||||
* Required for using stash-archive.
|
||||
*/
|
||||
$defaults = [
|
||||
'prefix' => $git->type->slug . '/',
|
||||
'at' => $git->type->branch,
|
||||
'format' => 'zip',
|
||||
];
|
||||
$endpoint = add_query_arg( $defaults, $endpoint );
|
||||
if ( ! empty( $git->type->tags ) ) {
|
||||
$endpoint = urldecode( add_query_arg( 'at', $git->type->newest_tag, $endpoint ) );
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return $endpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines separate text lines from API response into one string with \n line endings.
|
||||
* Code relying on raw text can now parse it.
|
||||
*
|
||||
* @param string|\stdClass|mixed $response
|
||||
*
|
||||
* @return string Combined lines of text returned by API
|
||||
*/
|
||||
protected function bbserver_recombine_response( $response ) {
|
||||
if ( $this->validate_response( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
$remote_info_file = '';
|
||||
if ( isset( $response->lines ) ) {
|
||||
foreach ( (array) $response->lines as $line ) {
|
||||
$remote_info_file .= $line->text . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return $remote_info_file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse API response and return array of meta variables.
|
||||
*
|
||||
* @param \stdClass|array $response Response from API call.
|
||||
*
|
||||
* @return array $arr Array of meta variables.
|
||||
*/
|
||||
public function parse_meta_response( $response ) {
|
||||
if ( $this->validate_response( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
$arr = [];
|
||||
$response = [ $response ];
|
||||
|
||||
array_filter(
|
||||
$response,
|
||||
function ( $e ) use ( &$arr ) {
|
||||
$arr['private'] = ! $e->public;
|
||||
$arr['last_updated'] = null;
|
||||
$arr['watchers'] = 0;
|
||||
$arr['forks'] = 0;
|
||||
$arr['open_issues'] = 0;
|
||||
}
|
||||
);
|
||||
|
||||
return $arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse API response and return array with changelog.
|
||||
*
|
||||
* @param string $response Response from API call.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function parse_changelog_response( $response ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse API response and return object with readme body.
|
||||
*
|
||||
* @param string|\stdClass $response
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function parse_readme_response( $response ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse API response and return array of branch data.
|
||||
*
|
||||
* @param \stdClass $response API response.
|
||||
*
|
||||
* @return array Array of branch data.
|
||||
*/
|
||||
public function parse_branch_response( $response ) {
|
||||
if ( $this->validate_response( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
$branches = [];
|
||||
foreach ( $response as $branch ) {
|
||||
$branches[ $branch->displayId ]['download'] = $this->construct_download_link( $branch->displayId );
|
||||
$branches[ $branch->displayId ]['commit_hash'] = $branch->latestCommit;
|
||||
}
|
||||
return $branches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse API response call and return only array of tag numbers.
|
||||
*
|
||||
* @param \stdClass $response Response from API call.
|
||||
*
|
||||
* @return array|\stdClass Array of tag numbers, object is error.
|
||||
*/
|
||||
public function parse_tag_response( $response ) {
|
||||
if ( ! isset( $response->values ) || $this->validate_response( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$arr = [];
|
||||
array_map(
|
||||
function ( $e ) use ( &$arr ) {
|
||||
$arr[] = $e->displayId;
|
||||
|
||||
return $arr;
|
||||
},
|
||||
(array) $response->values
|
||||
);
|
||||
|
||||
return $arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse tags and create download links.
|
||||
*
|
||||
* @param \stdClass|array $response Response from API call.
|
||||
* @param string $repo_type
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function parse_tags( $response, $repo_type ) {
|
||||
$tags = [];
|
||||
$rollback = [];
|
||||
|
||||
foreach ( (array) $response as $tag ) {
|
||||
$download_base = "{$repo_type['base_uri']}/latest/{$this->type->owner}/repos/{$this->type->slug}/archive";
|
||||
$download_base = $this->add_endpoints( $this, $download_base );
|
||||
$tags[] = $tag;
|
||||
$rollback[ $tag ] = add_query_arg( 'at', $tag, $download_base );
|
||||
}
|
||||
|
||||
return [ $tags, $rollback ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add settings for Bitbucket Server Username and Password.
|
||||
*
|
||||
* @param array $auth_required
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_settings( $auth_required ) {
|
||||
add_settings_section(
|
||||
'bitbucket_server_user',
|
||||
esc_html__( 'Bitbucket Server Private Settings', 'github-updater' ),
|
||||
[ $this, 'print_section_bitbucket_username' ],
|
||||
'github_updater_bbserver_install_settings'
|
||||
);
|
||||
|
||||
add_settings_field(
|
||||
'bitbucket_server_username',
|
||||
esc_html__( 'Bitbucket Server Username', 'github-updater' ),
|
||||
[ Singleton::get_instance( 'Settings', $this ), 'token_callback_text' ],
|
||||
'github_updater_bbserver_install_settings',
|
||||
'bitbucket_server_user',
|
||||
[ 'id' => 'bitbucket_server_username' ]
|
||||
);
|
||||
|
||||
add_settings_field(
|
||||
'bitbucket_server_password',
|
||||
esc_html__( 'Bitbucket Server Password', 'github-updater' ),
|
||||
[ Singleton::get_instance( 'Settings', $this ), 'token_callback_text' ],
|
||||
'github_updater_bbserver_install_settings',
|
||||
'bitbucket_server_user',
|
||||
[
|
||||
'id' => 'bitbucket_server_password',
|
||||
'token' => true,
|
||||
]
|
||||
);
|
||||
|
||||
/*
|
||||
* Show section for private Bitbucket Server repositories.
|
||||
*/
|
||||
if ( $auth_required['bitbucket_server'] ) {
|
||||
add_settings_section(
|
||||
'bitbucket_server_id',
|
||||
esc_html__( 'Bitbucket Server Private Repositories', 'github-updater' ),
|
||||
[ $this, 'print_section_bitbucket_info' ],
|
||||
'github_updater_bbserver_install_settings'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add values for individual repo add_setting_field().
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function add_repo_setting_field() {
|
||||
$setting_field['page'] = 'github_updater_bbserver_install_settings';
|
||||
$setting_field['section'] = 'bitbucket_server_id';
|
||||
$setting_field['callback_method'] = [
|
||||
Singleton::get_instance( 'Settings', $this ),
|
||||
'token_callback_checkbox',
|
||||
];
|
||||
|
||||
return $setting_field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add subtab to Settings page.
|
||||
*/
|
||||
private function add_settings_subtab() {
|
||||
add_filter(
|
||||
'github_updater_add_settings_subtabs',
|
||||
function ( $subtabs ) {
|
||||
return array_merge( $subtabs, [ 'bbserver' => esc_html__( 'Bitbucket Server', 'github-updater' ) ] );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add remote install feature, create endpoint.
|
||||
*
|
||||
* @param array $headers
|
||||
* @param array $install
|
||||
*
|
||||
* @return array $install
|
||||
*/
|
||||
public function remote_install( $headers, $install ) {
|
||||
$bitbucket_org = true;
|
||||
|
||||
if ( 'bitbucket.org' === $headers['host'] || empty( $headers['host'] ) ) {
|
||||
$base = 'https://bitbucket.org';
|
||||
$headers['host'] = 'bitbucket.org';
|
||||
} else {
|
||||
$base = $headers['base_uri'];
|
||||
$bitbucket_org = false;
|
||||
}
|
||||
|
||||
if ( ! $bitbucket_org ) {
|
||||
$install['download_link'] = "{$base}/rest/api/latest/{$headers['owner']}/repos/{$headers['repo']}/archive";
|
||||
|
||||
$install['download_link'] = add_query_arg(
|
||||
[
|
||||
'prefix' => $headers['repo'] . '/',
|
||||
'at' => $install['github_updater_branch'],
|
||||
'format' => 'zip',
|
||||
],
|
||||
$install['download_link']
|
||||
);
|
||||
|
||||
if ( isset( $install['is_private'] ) ) {
|
||||
$install['options'][ $install['repo'] ] = 1;
|
||||
}
|
||||
if ( ! empty( $install['bitbucket_username'] ) ) {
|
||||
$install['options']['bitbucket_server_username'] = $install['bitbucket_username'];
|
||||
}
|
||||
if ( ! empty( $install['bitbucket_password'] ) ) {
|
||||
$install['options']['bitbucket_server_password'] = $install['bitbucket_password'];
|
||||
}
|
||||
}
|
||||
|
||||
return $install;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,549 @@
|
||||
<?php
|
||||
/**
|
||||
* GitHub Updater
|
||||
*
|
||||
* @author Andy Fragen
|
||||
* @license GPL-2.0+
|
||||
* @link https://github.com/afragen/github-updater
|
||||
* @package github-updater
|
||||
*/
|
||||
|
||||
namespace Fragen\GitHub_Updater\API;
|
||||
|
||||
use Fragen\Singleton;
|
||||
use Fragen\GitHub_Updater\API;
|
||||
use Fragen\GitHub_Updater\Branch;
|
||||
|
||||
/*
|
||||
* Exit if called directly.
|
||||
*/
|
||||
if ( ! defined( 'WPINC' ) ) {
|
||||
die;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class GitHub_API
|
||||
*
|
||||
* Get remote data from a GitHub repo.
|
||||
*
|
||||
* @author Andy Fragen
|
||||
*/
|
||||
class GitHub_API extends API implements API_Interface {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param \stdClass $type
|
||||
*/
|
||||
public function __construct( $type ) {
|
||||
parent::__construct();
|
||||
$this->type = $type;
|
||||
$this->response = $this->get_repo_cache();
|
||||
$branch = new Branch( $this->response );
|
||||
if ( ! empty( $type->branch ) ) {
|
||||
$this->type->branch = ! empty( $branch->cache['current_branch'] )
|
||||
? $branch->cache['current_branch']
|
||||
: $type->branch;
|
||||
}
|
||||
$this->settings_hook( $this );
|
||||
$this->add_settings_subtab();
|
||||
$this->add_install_fields( $this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the remote file and parse headers.
|
||||
*
|
||||
* @param string $file Filename.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_remote_info( $file ) {
|
||||
return $this->get_remote_api_info( 'github', $file, "/repos/:owner/:repo/contents/{$file}" );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get remote info for tags.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_remote_tag() {
|
||||
return $this->get_remote_api_tag( 'github', '/repos/:owner/:repo/tags' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the remote CHANGES.md file.
|
||||
*
|
||||
* @param string $changes Changelog filename.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_remote_changes( $changes ) {
|
||||
return $this->get_remote_api_changes( 'github', $changes, "/repos/:owner/:repo/contents/{$changes}" );
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and parse remote readme.txt.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_remote_readme() {
|
||||
$this->get_remote_api_readme( 'github', '/repos/:owner/:repo/contents/readme.txt' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the repository meta from API.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_repo_meta() {
|
||||
return $this->get_remote_api_repo_meta( 'github', '/repos/:owner/:repo' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create array of branches and download links as array.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_remote_branches() {
|
||||
return $this->get_remote_api_branches( 'github', '/repos/:owner/:repo/branches' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the GitHub release asset URL.
|
||||
*
|
||||
* @return string|bool
|
||||
*/
|
||||
public function get_release_asset() {
|
||||
return $this->get_api_release_asset( 'github', '/repos/:owner/:repo/releases/latest' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct $this->type->download_link using Repository Contents API.
|
||||
*
|
||||
* @url http://developer.github.com/v3/repos/contents/#get-archive-link
|
||||
*
|
||||
* @param boolean $branch_switch for direct branch changing.
|
||||
*
|
||||
* @return string $endpoint
|
||||
*/
|
||||
public function construct_download_link( $branch_switch = false ) {
|
||||
self::$method = 'download_link';
|
||||
$download_link_base = $this->get_api_url( '/repos/:owner/:repo/zipball/', true );
|
||||
$endpoint = '';
|
||||
|
||||
// Release asset.
|
||||
if ( $this->type->release_asset && '0.0.0' !== $this->type->newest_tag ) {
|
||||
$release_asset = $this->get_release_asset();
|
||||
if ( property_exists( $this->type, 'is_private' ) && $this->type->is_private ) {
|
||||
return $this->get_release_asset_redirect( $release_asset, true );
|
||||
}
|
||||
return $release_asset;
|
||||
}
|
||||
|
||||
/*
|
||||
* If a branch has been given, use branch.
|
||||
* If branch is master (default) and tags are used, use newest tag.
|
||||
*/
|
||||
if ( 'master' !== $this->type->branch || empty( $this->type->tags ) ) {
|
||||
$endpoint .= $this->type->branch;
|
||||
} else {
|
||||
$endpoint .= $this->type->newest_tag;
|
||||
}
|
||||
|
||||
// Create endpoint for branch switching.
|
||||
if ( $branch_switch ) {
|
||||
$endpoint = $branch_switch;
|
||||
}
|
||||
|
||||
$endpoint = $this->add_access_token_endpoint( $this, $endpoint );
|
||||
$download_link = $download_link_base . $endpoint;
|
||||
|
||||
/**
|
||||
* Filter download link so developers can point to specific ZipFile
|
||||
* to use as a download link during a branch switch.
|
||||
*
|
||||
* @since 8.8.0
|
||||
*
|
||||
* @param string $download_link Download URL.
|
||||
* @param /stdClass $this->type Repository object.
|
||||
* @param string $branch_switch Branch or tag for rollback or branch switching.
|
||||
*/
|
||||
return apply_filters( 'github_updater_post_construct_download_link', $download_link, $this->type, $branch_switch );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create GitHub API endpoints.
|
||||
*
|
||||
* @param GitHub_API|API $git
|
||||
* @param string $endpoint
|
||||
*
|
||||
* @return string $endpoint
|
||||
*/
|
||||
public function add_endpoints( $git, $endpoint ) {
|
||||
switch ( $git::$method ) {
|
||||
case 'file':
|
||||
case 'readme':
|
||||
case 'changes':
|
||||
$endpoint = add_query_arg( 'ref', $git->type->branch, $endpoint );
|
||||
break;
|
||||
case 'meta':
|
||||
case 'tags':
|
||||
case 'download_link':
|
||||
case 'release_asset':
|
||||
case 'translation':
|
||||
break;
|
||||
case 'branches':
|
||||
$endpoint = add_query_arg( 'per_page', '100', $endpoint );
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
$endpoint = $this->add_access_token_endpoint( $git, $endpoint );
|
||||
|
||||
/*
|
||||
* If GitHub Enterprise return this endpoint.
|
||||
*/
|
||||
if ( ! empty( $git->type->enterprise_api ) ) {
|
||||
return $git->type->enterprise_api . $endpoint;
|
||||
}
|
||||
|
||||
return $endpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate and store time until rate limit reset.
|
||||
*
|
||||
* @param array $response HTTP headers.
|
||||
* @param string $repo Repo name.
|
||||
*/
|
||||
public static function ratelimit_reset( $response, $repo ) {
|
||||
if ( isset( $response['headers']['x-ratelimit-reset'] ) ) {
|
||||
$reset = (int) $response['headers']['x-ratelimit-reset'];
|
||||
$wait = date( 'i', $reset - time() );
|
||||
static::$error_code[ $repo ] = array_merge(
|
||||
static::$error_code[ $repo ],
|
||||
[
|
||||
'git' => 'github',
|
||||
'wait' => $wait,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse API response call and return only array of tag numbers.
|
||||
*
|
||||
* @param \stdClass|array $response Response from API call.
|
||||
*
|
||||
* @return \stdClass|array $arr Array of tag numbers, object is error.
|
||||
*/
|
||||
public function parse_tag_response( $response ) {
|
||||
if ( $this->validate_response( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$arr = [];
|
||||
array_map(
|
||||
function ( $e ) use ( &$arr ) {
|
||||
$arr[] = $e->name;
|
||||
|
||||
return $arr;
|
||||
},
|
||||
(array) $response
|
||||
);
|
||||
|
||||
return $arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse API response and return array of meta variables.
|
||||
*
|
||||
* @param \stdClass|array $response Response from API call.
|
||||
*
|
||||
* @return array $arr Array of meta variables.
|
||||
*/
|
||||
public function parse_meta_response( $response ) {
|
||||
if ( $this->validate_response( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
$arr = [];
|
||||
$response = [ $response ];
|
||||
|
||||
array_filter(
|
||||
$response,
|
||||
function ( $e ) use ( &$arr ) {
|
||||
$arr['private'] = $e->private;
|
||||
$arr['last_updated'] = $e->pushed_at;
|
||||
$arr['watchers'] = $e->watchers;
|
||||
$arr['forks'] = $e->forks;
|
||||
$arr['open_issues'] = $e->open_issues;
|
||||
}
|
||||
);
|
||||
|
||||
return $arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse API response and return array with changelog in base64.
|
||||
*
|
||||
* @param \stdClass|array $response Response from API call.
|
||||
*
|
||||
* @return array $arr Array of changes in base64.
|
||||
*/
|
||||
public function parse_changelog_response( $response ) {
|
||||
if ( $this->validate_response( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
$arr = [];
|
||||
$response = [ $response ];
|
||||
|
||||
array_filter(
|
||||
$response,
|
||||
function ( $e ) use ( &$arr ) {
|
||||
$arr['changes'] = $e->content;
|
||||
}
|
||||
);
|
||||
|
||||
return $arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse API response and return array of branch data.
|
||||
*
|
||||
* @param \stdClass $response API response.
|
||||
*
|
||||
* @return array Array of branch data.
|
||||
*/
|
||||
public function parse_branch_response( $response ) {
|
||||
if ( $this->validate_response( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
$branches = [];
|
||||
foreach ( $response as $branch ) {
|
||||
$branches[ $branch->name ]['download'] = $this->construct_download_link( $branch->name );
|
||||
$branches[ $branch->name ]['commit_hash'] = $branch->commit->sha;
|
||||
$branches[ $branch->name ]['commit_api'] = $branch->commit->url;
|
||||
}
|
||||
return $branches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse tags and create download links.
|
||||
*
|
||||
* @param \stdClass|array $response Response from API call.
|
||||
* @param array $repo_type
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function parse_tags( $response, $repo_type ) {
|
||||
$tags = [];
|
||||
$rollback = [];
|
||||
|
||||
foreach ( (array) $response as $tag ) {
|
||||
$download_base = implode(
|
||||
'/',
|
||||
[
|
||||
$repo_type['base_uri'],
|
||||
'repos',
|
||||
$this->type->owner,
|
||||
$this->type->slug,
|
||||
'zipball/',
|
||||
]
|
||||
);
|
||||
$tags[] = $tag;
|
||||
$rollback[ $tag ] = $download_base . $tag;
|
||||
}
|
||||
|
||||
return [ $tags, $rollback ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add settings for GitHub Personal Access Token.
|
||||
*
|
||||
* @param array $auth_required
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_settings( $auth_required ) {
|
||||
add_settings_section(
|
||||
'github_access_token',
|
||||
esc_html__( 'GitHub Personal Access Token', 'github-updater' ),
|
||||
[ $this, 'print_section_github_access_token' ],
|
||||
'github_updater_github_install_settings'
|
||||
);
|
||||
|
||||
add_settings_field(
|
||||
'github_access_token',
|
||||
esc_html__( 'GitHub.com Access Token', 'github-updater' ),
|
||||
[ Singleton::get_instance( 'Settings', $this ), 'token_callback_text' ],
|
||||
'github_updater_github_install_settings',
|
||||
'github_access_token',
|
||||
[
|
||||
'id' => 'github_access_token',
|
||||
'token' => true,
|
||||
]
|
||||
);
|
||||
|
||||
if ( $auth_required['github_enterprise'] ) {
|
||||
add_settings_field(
|
||||
'github_enterprise_token',
|
||||
esc_html__( 'GitHub Enterprise Access Token', 'github-updater' ),
|
||||
[ Singleton::get_instance( 'Settings', $this ), 'token_callback_text' ],
|
||||
'github_updater_github_install_settings',
|
||||
'github_access_token',
|
||||
[
|
||||
'id' => 'github_enterprise_token',
|
||||
'token' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* Show section for private GitHub repositories.
|
||||
*/
|
||||
if ( $auth_required['github_private'] || $auth_required['github_enterprise'] ) {
|
||||
add_settings_section(
|
||||
'github_id',
|
||||
esc_html__( 'GitHub Private Settings', 'github-updater' ),
|
||||
[ $this, 'print_section_github_info' ],
|
||||
'github_updater_github_install_settings'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add values for individual repo add_setting_field().
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function add_repo_setting_field() {
|
||||
$setting_field['page'] = 'github_updater_github_install_settings';
|
||||
$setting_field['section'] = 'github_id';
|
||||
$setting_field['callback_method'] = [
|
||||
Singleton::get_instance( 'Settings', $this ),
|
||||
'token_callback_text',
|
||||
];
|
||||
|
||||
return $setting_field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the GitHub text.
|
||||
*/
|
||||
public function print_section_github_info() {
|
||||
esc_html_e( 'Enter your GitHub Access Token. Leave empty for public repositories.', 'github-updater' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the GitHub Personal Access Token text.
|
||||
*/
|
||||
public function print_section_github_access_token() {
|
||||
esc_html_e( 'Enter your personal GitHub.com or GitHub Enterprise Access Token to avoid API access limits.', 'github-updater' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add remote install settings fields.
|
||||
*
|
||||
* @param string $type plugin|theme.
|
||||
*/
|
||||
public function add_install_settings_fields( $type ) {
|
||||
add_settings_field(
|
||||
'github_access_token',
|
||||
esc_html__( 'GitHub Access Token', 'github-updater' ),
|
||||
[ $this, 'github_access_token' ],
|
||||
'github_updater_install_' . $type,
|
||||
$type
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add subtab to Settings page.
|
||||
*/
|
||||
private function add_settings_subtab() {
|
||||
add_filter(
|
||||
'github_updater_add_settings_subtabs',
|
||||
function ( $subtabs ) {
|
||||
return array_merge( $subtabs, [ 'github' => esc_html__( 'GitHub', 'github-updater' ) ] );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* GitHub Access Token for remote install.
|
||||
*/
|
||||
public function github_access_token() {
|
||||
?>
|
||||
<label for="github_access_token">
|
||||
<input class="github_setting" type="password" style="width:50%;" id="github_access_token" name="github_access_token" value="" autocomplete="new-password">
|
||||
<br>
|
||||
<span class="description">
|
||||
<?php esc_html_e( 'Enter GitHub Access Token for private GitHub repositories.', 'github-updater' ); ?>
|
||||
</span>
|
||||
</label>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Add remote install feature, create endpoint.
|
||||
*
|
||||
* @param array $headers
|
||||
* @param array $install
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function remote_install( $headers, $install ) {
|
||||
$github_com = true;
|
||||
$options['github_access_token'] = isset( static::$options['github_access_token'] ) ? static::$options['github_access_token'] : null;
|
||||
$options['github_enterprise_token'] = isset( static::$options['github_enterprise_token'] ) ? static::$options['github_enterprise_token'] : null;
|
||||
|
||||
if ( 'github.com' === $headers['host'] || empty( $headers['host'] ) ) {
|
||||
$base = 'https://api.github.com';
|
||||
$headers['host'] = 'github.com';
|
||||
} else {
|
||||
$base = $headers['base_uri'] . '/api/v3';
|
||||
$github_com = false;
|
||||
}
|
||||
|
||||
$install['download_link'] = "{$base}/repos/{$install['github_updater_repo']}/zipball/{$install['github_updater_branch']}";
|
||||
|
||||
// If asset is entered install it.
|
||||
if ( false !== stripos( $headers['uri'], 'releases/download' ) ) {
|
||||
$install['download_link'] = $headers['uri'];
|
||||
}
|
||||
|
||||
/*
|
||||
* Add/Save access token if present.
|
||||
*/
|
||||
if ( ! empty( $install['github_access_token'] ) ) {
|
||||
$install['options'][ $install['repo'] ] = $install['github_access_token'];
|
||||
if ( $github_com ) {
|
||||
$install['options']['github_access_token'] = $install['github_access_token'];
|
||||
} else {
|
||||
$install['options']['github_enterprise_token'] = $install['github_access_token'];
|
||||
}
|
||||
}
|
||||
if ( $github_com ) {
|
||||
$token = ! empty( $install['options']['github_access_token'] )
|
||||
? $install['options']['github_access_token']
|
||||
: $options['github_access_token'];
|
||||
} else {
|
||||
$token = ! empty( $install['options']['github_enterprise_token'] )
|
||||
? $install['options']['github_enterprise_token']
|
||||
: $options['github_enterprise_token'];
|
||||
}
|
||||
|
||||
if ( ! empty( $token ) ) {
|
||||
$install['download_link'] = add_query_arg( 'access_token', $token, $install['download_link'] );
|
||||
}
|
||||
|
||||
if ( ! empty( static::$options['github_access_token'] ) ) {
|
||||
unset( $install['options']['github_access_token'] );
|
||||
}
|
||||
if ( ! empty( static::$options['github_enterprise_token'] ) ) {
|
||||
unset( $install['options']['github_enterprise_token'] );
|
||||
}
|
||||
|
||||
return $install;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,645 @@
|
||||
<?php
|
||||
/**
|
||||
* GitHub Updater
|
||||
*
|
||||
* @author Andy Fragen
|
||||
* @license GPL-2.0+
|
||||
* @link https://github.com/afragen/github-updater
|
||||
* @package github-updater
|
||||
*/
|
||||
|
||||
namespace Fragen\GitHub_Updater\API;
|
||||
|
||||
use Fragen\Singleton;
|
||||
use Fragen\GitHub_Updater\API;
|
||||
use Fragen\GitHub_Updater\Branch;
|
||||
|
||||
/*
|
||||
* Exit if called directly.
|
||||
*/
|
||||
if ( ! defined( 'WPINC' ) ) {
|
||||
die;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class GitLab_API
|
||||
*
|
||||
* Get remote data from a GitLab repo.
|
||||
*
|
||||
* @author Andy Fragen
|
||||
*/
|
||||
class GitLab_API extends API implements API_Interface {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param \stdClass $type
|
||||
*/
|
||||
public function __construct( $type ) {
|
||||
parent::__construct();
|
||||
$this->type = $type;
|
||||
$this->response = $this->get_repo_cache();
|
||||
$branch = new Branch( $this->response );
|
||||
if ( ! empty( $type->branch ) ) {
|
||||
$this->type->branch = ! empty( $branch->cache['current_branch'] )
|
||||
? $branch->cache['current_branch']
|
||||
: $type->branch;
|
||||
}
|
||||
$this->set_default_credentials();
|
||||
$this->settings_hook( $this );
|
||||
$this->add_settings_subtab();
|
||||
$this->add_install_fields( $this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default credentials if option not set.
|
||||
*/
|
||||
protected function set_default_credentials() {
|
||||
$running_servers = Singleton::get_instance( 'Base', $this )->get_running_git_servers();
|
||||
$set_credentials = false;
|
||||
if ( ! isset( static::$options['gitlab_access_token'] ) ) {
|
||||
static::$options['gitlab_access_token'] = null;
|
||||
$set_credentials = true;
|
||||
}
|
||||
if ( ! isset( static::$options['gitlab_enterprise_token'] ) ) {
|
||||
static::$options['gitlab_enterprise_token'] = null;
|
||||
$set_credentials = true;
|
||||
}
|
||||
if ( ( empty( static::$options['gitlab_enterprise_token'] ) &&
|
||||
in_array( 'gitlabce', $running_servers, true ) ) ||
|
||||
( empty( static::$options['gitlab_access_token'] ) &&
|
||||
in_array( 'gitlab', $running_servers, true ) )
|
||||
) {
|
||||
$this->gitlab_error_notices();
|
||||
}
|
||||
if ( $set_credentials ) {
|
||||
add_site_option( 'github_updater', static::$options );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the remote file and parse headers.
|
||||
*
|
||||
* @param string $file Filename.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_remote_info( $file ) {
|
||||
$id = $this->get_gitlab_id();
|
||||
return $this->get_remote_api_info( 'gitlab', $file, "/projects/{$id}/repository/files/{$file}" );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get remote info for tags.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_remote_tag() {
|
||||
$id = $this->get_gitlab_id();
|
||||
return $this->get_remote_api_tag( 'gitlab', "/projects/{$id}/repository/tags" );
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the remote CHANGES.md file.
|
||||
*
|
||||
* @param string $changes Changelog filename.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_remote_changes( $changes ) {
|
||||
$id = $this->get_gitlab_id();
|
||||
return $this->get_remote_api_changes( 'gitlab', $changes, "/projects/{$id}/repository/files/{$changes}" );
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and parse remote readme.txt.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_remote_readme() {
|
||||
$id = $this->get_gitlab_id();
|
||||
return $this->get_remote_api_readme( 'gitlab', "/projects/{$id}/repository/files/readme.txt" );
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the repository meta from API.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_repo_meta() {
|
||||
$response = isset( $this->response['meta'] ) ? $this->response['meta'] : false;
|
||||
|
||||
if ( ! $response ) {
|
||||
self::$method = 'meta';
|
||||
$project = isset( $this->response['project'] ) ? $this->response['project'] : false;
|
||||
|
||||
// exit if transient is empty.
|
||||
if ( ! $project ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$response = ( $this->type->slug === $project->path ) ? $project : false;
|
||||
|
||||
if ( $response ) {
|
||||
$response = $this->parse_meta_response( $response );
|
||||
$this->set_repo_cache( 'meta', $response );
|
||||
$this->set_repo_cache( 'project', null );
|
||||
}
|
||||
}
|
||||
|
||||
if ( $this->validate_response( $response ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->type->repo_meta = $response;
|
||||
$this->add_meta_repo_object();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create array of branches and download links as array.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_remote_branches() {
|
||||
$id = $this->get_gitlab_id();
|
||||
return $this->get_remote_api_branches( 'gitlab', "/projects/{$id}/repository/branches" );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get GitLab release asset download link.
|
||||
*
|
||||
* @return string|bool
|
||||
*/
|
||||
public function get_release_asset() {
|
||||
return $this->get_api_release_asset( 'gitlab', "/projects/{$this->response['project_id']}/jobs/artifacts/{$this->type->newest_tag}/download" );
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct $this->type->download_link using GitLab API v4.
|
||||
*
|
||||
* @param boolean $branch_switch for direct branch changing.
|
||||
*
|
||||
* @return string $endpoint
|
||||
*/
|
||||
public function construct_download_link( $branch_switch = false ) {
|
||||
self::$method = 'download_link';
|
||||
$download_link_base = $this->get_api_url( "/projects/{$this->get_gitlab_id()}/repository/archive.zip" );
|
||||
$download_link_base = remove_query_arg( 'private_token', $download_link_base );
|
||||
|
||||
$endpoint = '';
|
||||
$endpoint = add_query_arg( 'sha', $this->type->branch, $endpoint );
|
||||
|
||||
// Release asset.
|
||||
if ( $this->type->ci_job && '0.0.0' !== $this->type->newest_tag ) {
|
||||
$release_asset = $this->get_release_asset();
|
||||
return $release_asset;
|
||||
}
|
||||
|
||||
// If branch is master (default) and tags are used, use newest tag.
|
||||
if ( 'master' === $this->type->branch && ! empty( $this->type->tags ) ) {
|
||||
$endpoint = add_query_arg( 'sha', $this->type->newest_tag, $endpoint );
|
||||
}
|
||||
|
||||
// Create endpoint for branch switching.
|
||||
if ( $branch_switch ) {
|
||||
$endpoint = add_query_arg( 'sha', $branch_switch, $endpoint );
|
||||
}
|
||||
|
||||
$endpoint = $this->add_access_token_endpoint( $this, $endpoint );
|
||||
$download_link = $download_link_base . $endpoint;
|
||||
|
||||
/**
|
||||
* Filter download link so developers can point to specific ZipFile
|
||||
* to use as a download link during a branch switch.
|
||||
*
|
||||
* @since 8.8.0
|
||||
*
|
||||
* @param string $download_link Download URL.
|
||||
* @param /stdClass $this->type Repository object.
|
||||
* @param string $branch_switch Branch or tag for rollback or branch switching.
|
||||
*/
|
||||
return apply_filters( 'github_updater_post_construct_download_link', $download_link, $this->type, $branch_switch );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create GitLab API endpoints.
|
||||
*
|
||||
* @param GitLab_API|API $git
|
||||
* @param string $endpoint
|
||||
*
|
||||
* @return string $endpoint
|
||||
*/
|
||||
public function add_endpoints( $git, $endpoint ) {
|
||||
switch ( $git::$method ) {
|
||||
case 'projects':
|
||||
$endpoint = add_query_arg( 'per_page', '100', $endpoint );
|
||||
break;
|
||||
case 'meta':
|
||||
case 'tags':
|
||||
case 'branches':
|
||||
case 'download_link':
|
||||
break;
|
||||
case 'file':
|
||||
case 'changes':
|
||||
case 'readme':
|
||||
$endpoint = add_query_arg( 'ref', $git->type->branch, $endpoint );
|
||||
break;
|
||||
case 'translation':
|
||||
$endpoint = add_query_arg( 'ref', 'master', $endpoint );
|
||||
break;
|
||||
case 'release_asset':
|
||||
$endpoint = add_query_arg( 'job', $git->type->ci_job, $endpoint );
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
$endpoint = $this->add_access_token_endpoint( $git, $endpoint );
|
||||
|
||||
/*
|
||||
* If GitLab CE/Enterprise return this endpoint.
|
||||
*/
|
||||
if ( ! empty( $git->type->enterprise_api ) ) {
|
||||
return $git->type->enterprise_api . $endpoint;
|
||||
}
|
||||
|
||||
return $endpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get GitLab project ID and project meta.
|
||||
*
|
||||
* @return string|int
|
||||
*/
|
||||
public function get_gitlab_id() {
|
||||
$id = null;
|
||||
$response = isset( $this->response['project_id'] ) ? $this->response['project_id'] : false;
|
||||
|
||||
if ( ! $response ) {
|
||||
self::$method = 'projects';
|
||||
$id = implode( '/', [ $this->type->owner, $this->type->slug ] );
|
||||
$id = rawurlencode( $id );
|
||||
$response = $this->api( '/projects/' . $id );
|
||||
|
||||
if ( $this->validate_response( $response ) ) {
|
||||
return $id;
|
||||
}
|
||||
|
||||
if ( $response && $this->type->slug === $response->path ) {
|
||||
$id = $response->id;
|
||||
$this->set_repo_cache( 'project_id', $id );
|
||||
$this->set_repo_cache( 'project', $response );
|
||||
}
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse API response call and return only array of tag numbers.
|
||||
*
|
||||
* @param \stdClass|array $response Response from API call for tags.
|
||||
*
|
||||
* @return \stdClass|array Array of tag numbers, object is error.
|
||||
*/
|
||||
public function parse_tag_response( $response ) {
|
||||
if ( $this->validate_response( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$arr = [];
|
||||
array_map(
|
||||
function ( $e ) use ( &$arr ) {
|
||||
$arr[] = $e->name;
|
||||
|
||||
return $arr;
|
||||
},
|
||||
(array) $response
|
||||
);
|
||||
|
||||
return $arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse API response and return array of meta variables.
|
||||
*
|
||||
* @param \stdClass|array $response Response from API call.
|
||||
*
|
||||
* @return array $arr Array of meta variables.
|
||||
*/
|
||||
public function parse_meta_response( $response ) {
|
||||
if ( $this->validate_response( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
$arr = [];
|
||||
$response = [ $response ];
|
||||
|
||||
array_filter(
|
||||
$response,
|
||||
function ( $e ) use ( &$arr ) {
|
||||
$arr['private'] = isset( $e->visibility ) && 'private' === $e->visibility ? true : false;
|
||||
$arr['private'] = isset( $e->public ) ? ! $e->public : $arr['private'];
|
||||
$arr['last_updated'] = $e->last_activity_at;
|
||||
$arr['watchers'] = 0;
|
||||
$arr['forks'] = $e->forks_count;
|
||||
$arr['open_issues'] = isset( $e->open_issues_count ) ? $e->open_issues_count : 0;
|
||||
}
|
||||
);
|
||||
|
||||
return $arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse API response and return array with changelog in base64.
|
||||
*
|
||||
* @param \stdClass|array $response Response from API call.
|
||||
*
|
||||
* @return array|\stdClass $arr Array of changes in base64, object if error.
|
||||
*/
|
||||
public function parse_changelog_response( $response ) {
|
||||
if ( $this->validate_response( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$arr = [];
|
||||
$response = [ $response ];
|
||||
|
||||
array_filter(
|
||||
$response,
|
||||
function ( $e ) use ( &$arr ) {
|
||||
$arr['changes'] = $e->content;
|
||||
}
|
||||
);
|
||||
|
||||
return $arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse API response and return array of branch data.
|
||||
*
|
||||
* @param \stdClass $response API response.
|
||||
*
|
||||
* @return array Array of branch data.
|
||||
*/
|
||||
public function parse_branch_response( $response ) {
|
||||
if ( $this->validate_response( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
$branches = [];
|
||||
foreach ( $response as $branch ) {
|
||||
$branches[ $branch->name ]['download'] = $this->construct_download_link( $branch->name );
|
||||
$branches[ $branch->name ]['commit_hash'] = $branch->commit->id;
|
||||
$branches[ $branch->name ]['commit_timestamp'] = $branch->commit->committed_date;
|
||||
}
|
||||
return $branches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse tags and create download links.
|
||||
*
|
||||
* @param \stdClass|array $response Response from API call.
|
||||
* @param array $repo_type
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function parse_tags( $response, $repo_type ) {
|
||||
$tags = [];
|
||||
$rollback = [];
|
||||
|
||||
foreach ( (array) $response as $tag ) {
|
||||
$download_link = "/projects/{$this->get_gitlab_id()}/repository/archive.zip";
|
||||
$download_link = $this->get_api_url( $download_link );
|
||||
$download_link = add_query_arg( 'sha', $tag, $download_link );
|
||||
$tags[] = $tag;
|
||||
$rollback[ $tag ] = $download_link;
|
||||
}
|
||||
|
||||
return [ $tags, $rollback ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add settings for GitLab.com, GitLab Community Edition.
|
||||
* or GitLab Enterprise Access Token.
|
||||
*
|
||||
* @param array $auth_required
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_settings( $auth_required ) {
|
||||
if ( $auth_required['gitlab'] || $auth_required['gitlab_enterprise'] ) {
|
||||
add_settings_section(
|
||||
'gitlab_settings',
|
||||
esc_html__( 'GitLab Personal Access Token', 'github-updater' ),
|
||||
[ $this, 'print_section_gitlab_token' ],
|
||||
'github_updater_gitlab_install_settings'
|
||||
);
|
||||
}
|
||||
|
||||
if ( $auth_required['gitlab_private'] ) {
|
||||
add_settings_section(
|
||||
'gitlab_id',
|
||||
esc_html__( 'GitLab Private Settings', 'github-updater' ),
|
||||
[ $this, 'print_section_gitlab_info' ],
|
||||
'github_updater_gitlab_install_settings'
|
||||
);
|
||||
}
|
||||
|
||||
if ( $auth_required['gitlab'] ) {
|
||||
add_settings_field(
|
||||
'gitlab_access_token',
|
||||
esc_html__( 'GitLab.com Access Token', 'github-updater' ),
|
||||
[ Singleton::get_instance( 'Settings', $this ), 'token_callback_text' ],
|
||||
'github_updater_gitlab_install_settings',
|
||||
'gitlab_settings',
|
||||
[
|
||||
'id' => 'gitlab_access_token',
|
||||
'token' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
if ( $auth_required['gitlab_enterprise'] ) {
|
||||
add_settings_field(
|
||||
'gitlab_enterprise_token',
|
||||
esc_html__( 'GitLab CE or GitLab Enterprise Personal Access Token', 'github-updater' ),
|
||||
[ Singleton::get_instance( 'Settings', $this ), 'token_callback_text' ],
|
||||
'github_updater_gitlab_install_settings',
|
||||
'gitlab_settings',
|
||||
[
|
||||
'id' => 'gitlab_enterprise_token',
|
||||
'token' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add values for individual repo add_setting_field().
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function add_repo_setting_field() {
|
||||
$setting_field['page'] = 'github_updater_gitlab_install_settings';
|
||||
$setting_field['section'] = 'gitlab_id';
|
||||
$setting_field['callback_method'] = [
|
||||
Singleton::get_instance( 'Settings', $this ),
|
||||
'token_callback_text',
|
||||
];
|
||||
|
||||
return $setting_field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add subtab to Settings page.
|
||||
*/
|
||||
private function add_settings_subtab() {
|
||||
add_filter(
|
||||
'github_updater_add_settings_subtabs',
|
||||
function ( $subtabs ) {
|
||||
return array_merge( $subtabs, [ 'gitlab' => esc_html__( 'GitLab', 'github-updater' ) ] );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the GitLab Settings text.
|
||||
*/
|
||||
public function print_section_gitlab_info() {
|
||||
esc_html_e( 'Enter your repository specific GitLab Access Token.', 'github-updater' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the GitLab Access Token Settings text.
|
||||
*/
|
||||
public function print_section_gitlab_token() {
|
||||
esc_html_e( 'Enter your GitLab.com, GitLab CE, or GitLab Enterprise Access Token.', 'github-updater' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add remote install settings fields.
|
||||
*
|
||||
* @param string $type
|
||||
*/
|
||||
public function add_install_settings_fields( $type ) {
|
||||
add_settings_field(
|
||||
'gitlab_access_token',
|
||||
esc_html__( 'GitLab Access Token', 'github-updater' ),
|
||||
[ $this, 'gitlab_access_token' ],
|
||||
'github_updater_install_' . $type,
|
||||
$type
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* GitLab Access Token for remote install.
|
||||
*/
|
||||
public function gitlab_access_token() {
|
||||
?>
|
||||
<label for="gitlab_access_token">
|
||||
<input class="gitlab_setting" type="password" style="width:50%;" id="gitlab_access_token" name="gitlab_access_token" value="" autocomplete="new-password">
|
||||
<br>
|
||||
<span class="description">
|
||||
<?php esc_html_e( 'Enter GitLab Access Token for private GitLab repositories.', 'github-updater' ); ?>
|
||||
</span>
|
||||
</label>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Display GitLab error admin notices.
|
||||
*/
|
||||
public function gitlab_error_notices() {
|
||||
add_action( is_multisite() ? 'network_admin_notices' : 'admin_notices', [ $this, 'gitlab_error' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate error message for missing GitLab Private Token.
|
||||
*/
|
||||
public function gitlab_error() {
|
||||
$auth_required = $this->get_class_vars( 'Settings', 'auth_required' );
|
||||
$error_code = $this->get_error_codes();
|
||||
|
||||
if ( ! isset( $error_code['gitlab'] ) &&
|
||||
( ( empty( static::$options['gitlab_enterprise_token'] ) &&
|
||||
$auth_required['gitlab_enterprise'] ) ||
|
||||
( empty( static::$options['gitlab_access_token'] ) &&
|
||||
$auth_required['gitlab'] ) )
|
||||
) {
|
||||
self::$error_code['gitlab'] = [ 'error' => true ];
|
||||
if ( ! \PAnD::is_admin_notice_active( 'gitlab-error-1' ) ) {
|
||||
return;
|
||||
}
|
||||
?>
|
||||
<div data-dismissible="gitlab-error-1" class="error notice is-dismissible">
|
||||
<p>
|
||||
<?php esc_html_e( 'You must set a GitLab.com, GitLab CE, or GitLab Enterprise Access Token.', 'github-updater' ); ?>
|
||||
</p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add remote install feature, create endpoint.
|
||||
*
|
||||
* @param array $headers
|
||||
* @param array $install
|
||||
*
|
||||
* @return mixed $install
|
||||
*/
|
||||
public function remote_install( $headers, $install ) {
|
||||
$gitlab_com = true;
|
||||
$options['gitlab_access_token'] = isset( static::$options['gitlab_access_token'] ) ? static::$options['gitlab_access_token'] : null;
|
||||
$options['gitlab_enterprise_token'] = isset( static::$options['gitlab_enterprise_token'] ) ? static::$options['gitlab_enterprise_token'] : null;
|
||||
|
||||
if ( 'gitlab.com' === $headers['host'] || empty( $headers['host'] ) ) {
|
||||
$base = 'https://gitlab.com';
|
||||
$headers['host'] = 'gitlab.com';
|
||||
} else {
|
||||
$base = $headers['base_uri'];
|
||||
$gitlab_com = false;
|
||||
}
|
||||
|
||||
$id = rawurlencode( $install['github_updater_repo'] );
|
||||
$install['download_link'] = "{$base}/api/v4/projects/{$id}/repository/archive.zip";
|
||||
$install['download_link'] = add_query_arg( 'sha', $install['github_updater_branch'], $install['download_link'] );
|
||||
|
||||
/*
|
||||
* Add/Save access token if present.
|
||||
*/
|
||||
if ( ! empty( $install['gitlab_access_token'] ) ) {
|
||||
$install['options'][ $install['repo'] ] = $install['gitlab_access_token'];
|
||||
if ( $gitlab_com ) {
|
||||
$install['options']['gitlab_access_token'] = $install['gitlab_access_token'];
|
||||
} else {
|
||||
$install['options']['gitlab_enterprise_token'] = $install['gitlab_access_token'];
|
||||
}
|
||||
}
|
||||
if ( $gitlab_com ) {
|
||||
$token = ! empty( $install['options']['gitlab_access_token'] )
|
||||
? $install['options']['gitlab_access_token']
|
||||
: $options['gitlab_access_token'];
|
||||
} else {
|
||||
$token = ! empty( $install['options']['gitlab_enterprise_token'] )
|
||||
? $install['options']['gitlab_enterprise_token']
|
||||
: $options['gitlab_enterprise_token'];
|
||||
}
|
||||
|
||||
if ( ! empty( $token ) ) {
|
||||
$install['download_link'] = add_query_arg( 'private_token', $token, $install['download_link'] );
|
||||
}
|
||||
|
||||
if ( ! empty( static::$options['gitlab_access_token'] ) ) {
|
||||
unset( $install['options']['gitlab_access_token'] );
|
||||
}
|
||||
if ( ! empty( static::$options['gitlab_enterprise_token'] ) ) {
|
||||
unset( $install['options']['gitlab_enterprise_token'] );
|
||||
}
|
||||
|
||||
return $install;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,514 @@
|
||||
<?php
|
||||
/**
|
||||
* GitHub Updater
|
||||
*
|
||||
* @author Andy Fragen
|
||||
* @license GPL-2.0+
|
||||
* @link https://github.com/afragen/github-updater
|
||||
* @package github-updater
|
||||
*/
|
||||
|
||||
namespace Fragen\GitHub_Updater\API;
|
||||
|
||||
use Fragen\Singleton;
|
||||
use Fragen\GitHub_Updater\API;
|
||||
use Fragen\GitHub_Updater\Branch;
|
||||
use Fragen\GitHub_Updater\Traits\GHU_Trait;
|
||||
|
||||
/*
|
||||
* Exit if called directly.
|
||||
*/
|
||||
if ( ! defined( 'WPINC' ) ) {
|
||||
die;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class Gitea_API
|
||||
*
|
||||
* Get remote data from a Gitea repo.
|
||||
*
|
||||
* @author Andy Fragen
|
||||
* @author Marco Betschart
|
||||
*/
|
||||
class Gitea_API extends API implements API_Interface {
|
||||
use GHU_Trait;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param \stdClass $type
|
||||
*/
|
||||
public function __construct( $type ) {
|
||||
parent::__construct();
|
||||
$this->type = $type;
|
||||
$this->response = $this->get_repo_cache();
|
||||
$branch = new Branch( $this->response );
|
||||
if ( ! empty( $type->branch ) ) {
|
||||
$this->type->branch = ! empty( $branch->cache['current_branch'] )
|
||||
? $branch->cache['current_branch']
|
||||
: $type->branch;
|
||||
}
|
||||
$this->set_default_credentials();
|
||||
$this->settings_hook( $this );
|
||||
$this->add_settings_subtab();
|
||||
$this->add_install_fields( $this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default credentials if option not set.
|
||||
*/
|
||||
protected function set_default_credentials() {
|
||||
$running_servers = Singleton::get_instance( 'Base', $this )->get_running_git_servers();
|
||||
$set_credentials = false;
|
||||
if ( ! isset( static::$options['gitea_access_token'] ) ) {
|
||||
static::$options['gitea_access_token'] = null;
|
||||
$set_credentials = true;
|
||||
}
|
||||
if ( empty( static::$options['gitea_access_token'] ) &&
|
||||
in_array( 'gitea', $running_servers, true )
|
||||
) {
|
||||
$this->gitea_error_notices();
|
||||
}
|
||||
|
||||
if ( $set_credentials ) {
|
||||
add_site_option( 'github_updater', static::$options );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the remote file and parse headers.
|
||||
*
|
||||
* @param string $file Filename.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_remote_info( $file ) {
|
||||
return $this->get_remote_api_info( 'gitea', $file, "/repos/:owner/:repo/raw/:branch/{$file}" );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get remote info for tags.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_remote_tag() {
|
||||
return $this->get_remote_api_tag( 'gitea', '/repos/:owner/:repo/releases' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the remote CHANGES.md file.
|
||||
*
|
||||
* @param string $changes Changelog filename.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get_remote_changes( $changes ) {
|
||||
return $this->get_remote_api_changes( 'gitea', $changes, "/repos/:owner/:repo/raw/:branch/{$changes}" );
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and parse remote readme.txt.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get_remote_readme() {
|
||||
return $this->get_remote_api_readme( 'gitea', '/repos/:owner/:repo/raw/:branch/readme.txt' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the repository meta from API.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get_repo_meta() {
|
||||
return $this->get_remote_api_repo_meta( 'gitea', '/repos/:owner/:repo' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create array of branches and download links as array.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get_remote_branches() {
|
||||
return $this->get_remote_api_branches( 'gitea', '/repos/:owner/:repo/branches' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Gitea release asset.
|
||||
*
|
||||
* @return false
|
||||
*/
|
||||
public function get_release_asset() {
|
||||
// TODO: eventually figure this out.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct $this->type->download_link using Gitea API.
|
||||
*
|
||||
* @param boolean $branch_switch for direct branch changing.
|
||||
*
|
||||
* @return string $endpoint
|
||||
*/
|
||||
public function construct_download_link( $branch_switch = false ) {
|
||||
self::$method = 'download_link';
|
||||
$download_link_base = $this->get_api_url( '/repos/:owner/:repo/archive/', true );
|
||||
$endpoint = '';
|
||||
|
||||
/*
|
||||
* If a branch has been given, use branch.
|
||||
* If branch is master (default) and tags are used, use newest tag.
|
||||
*/
|
||||
if ( 'master' !== $this->type->branch || empty( $this->type->tags ) ) {
|
||||
$endpoint .= $this->type->branch . '.zip';
|
||||
} else {
|
||||
$endpoint .= $this->type->newest_tag . '.zip';
|
||||
}
|
||||
|
||||
// Create endpoint for branch switching.
|
||||
if ( $branch_switch ) {
|
||||
$endpoint = $branch_switch . '.zip';
|
||||
}
|
||||
|
||||
$endpoint = $this->add_access_token_endpoint( $this, $endpoint );
|
||||
$download_link = $download_link_base . $endpoint;
|
||||
|
||||
/**
|
||||
* Filter download link so developers can point to specific ZipFile
|
||||
* to use as a download link during a branch switch.
|
||||
*
|
||||
* @since 8.8.0
|
||||
*
|
||||
* @param string $download_link Download URL.
|
||||
* @param /stdClass $this->type Repository object.
|
||||
* @param string $branch_switch Branch or tag for rollback or branch switching.
|
||||
*/
|
||||
return apply_filters( 'github_updater_post_construct_download_link', $download_link, $this->type, $branch_switch );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Gitea API endpoints.
|
||||
*
|
||||
* @param Gitea_API|API $git
|
||||
* @param string $endpoint
|
||||
*
|
||||
* @return string $endpoint
|
||||
*/
|
||||
public function add_endpoints( $git, $endpoint ) {
|
||||
switch ( $git::$method ) {
|
||||
case 'file':
|
||||
case 'readme':
|
||||
case 'meta':
|
||||
case 'tags':
|
||||
case 'changes':
|
||||
case 'translation':
|
||||
case 'download_link':
|
||||
break;
|
||||
case 'branches':
|
||||
$endpoint = add_query_arg( 'per_page', '100', $endpoint );
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
$endpoint = $this->add_access_token_endpoint( $git, $endpoint );
|
||||
|
||||
return $endpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse API response call and return only array of tag numbers.
|
||||
*
|
||||
* @param \stdClass|array $response Response from API call for tags.
|
||||
*
|
||||
* @return \stdClass|array Array of tag numbers, object is error.
|
||||
*/
|
||||
public function parse_tag_response( $response ) {
|
||||
if ( $this->validate_response( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$arr = [];
|
||||
array_map(
|
||||
function ( $e ) use ( &$arr ) {
|
||||
$arr[] = $e->tag_name;
|
||||
|
||||
return $arr;
|
||||
},
|
||||
(array) $response
|
||||
);
|
||||
|
||||
return $arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse API response and return array of meta variables.
|
||||
*
|
||||
* @param \stdClass|array $response Response from API call.
|
||||
*
|
||||
* @return array $arr Array of meta variables.
|
||||
*/
|
||||
public function parse_meta_response( $response ) {
|
||||
if ( $this->validate_response( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
$arr = [];
|
||||
$response = [ $response ];
|
||||
|
||||
array_filter(
|
||||
$response,
|
||||
function ( $e ) use ( &$arr ) {
|
||||
$arr['private'] = $e->private;
|
||||
$arr['last_updated'] = $e->updated_at;
|
||||
$arr['watchers'] = $e->watchers_count;
|
||||
$arr['forks'] = $e->forks_count;
|
||||
$arr['open_issues'] = isset( $e->open_issues_count ) ? $e->open_issues_count : 0;
|
||||
}
|
||||
);
|
||||
|
||||
return $arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse API response and return array with changelog in base64.
|
||||
*
|
||||
* @param \stdClass|array $response Response from API call.
|
||||
*
|
||||
* @return array|\stdClass $arr Array of changes in base64, object if error.
|
||||
*/
|
||||
public function parse_changelog_response( $response ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse API response and return array of branch data.
|
||||
*
|
||||
* @param \stdClass $response API response.
|
||||
*
|
||||
* @return array Array of branch data.
|
||||
*/
|
||||
public function parse_branch_response( $response ) {
|
||||
if ( $this->validate_response( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
$branches = [];
|
||||
foreach ( $response as $branch ) {
|
||||
$branches[ $branch->name ]['download'] = $this->construct_download_link( $branch->name );
|
||||
$branches[ $branch->name ]['commit_hash'] = $branch->commit->id;
|
||||
$branches[ $branch->name ]['commit_timestamp'] = $branch->commit->timestamp;
|
||||
}
|
||||
return $branches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse tags and create download links.
|
||||
*
|
||||
* @param \stdClass|array $response Response from API call.
|
||||
* @param array $repo_type
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function parse_tags( $response, $repo_type ) {
|
||||
$tags = [];
|
||||
$rollback = [];
|
||||
|
||||
foreach ( (array) $response as $tag ) {
|
||||
$download_link = implode(
|
||||
'/',
|
||||
[
|
||||
$repo_type['base_uri'],
|
||||
'repos',
|
||||
$this->type->owner,
|
||||
$this->type->slug,
|
||||
'archive/',
|
||||
]
|
||||
);
|
||||
$tags[] = $tag;
|
||||
$rollback[ $tag ] = $download_link . $tag . '.zip';
|
||||
}
|
||||
|
||||
return [ $tags, $rollback ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add settings for Gitea Access Token.
|
||||
*
|
||||
* @param array $auth_required
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_settings( $auth_required ) {
|
||||
if ( $auth_required['gitea'] ) {
|
||||
add_settings_section(
|
||||
'gitea_settings',
|
||||
esc_html__( 'Gitea Access Token', 'github-updater' ),
|
||||
[ $this, 'print_section_gitea_token' ],
|
||||
'github_updater_gitea_install_settings'
|
||||
);
|
||||
}
|
||||
|
||||
if ( $auth_required['gitea_private'] ) {
|
||||
add_settings_section(
|
||||
'gitea_id',
|
||||
esc_html__( 'Gitea Private Settings', 'github-updater' ),
|
||||
[ $this, 'print_section_gitea_info' ],
|
||||
'github_updater_gitea_install_settings'
|
||||
);
|
||||
}
|
||||
|
||||
if ( $auth_required['gitea'] ) {
|
||||
add_settings_field(
|
||||
'gitea_access_token',
|
||||
esc_html__( 'Gitea Access Token', 'github-updater' ),
|
||||
[ Singleton::get_instance( 'Settings', $this ), 'token_callback_text' ],
|
||||
'github_updater_gitea_install_settings',
|
||||
'gitea_settings',
|
||||
[
|
||||
'id' => 'gitea_access_token',
|
||||
'token' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add values for individual repo add_setting_field().
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function add_repo_setting_field() {
|
||||
$setting_field['page'] = 'github_updater_gitea_install_settings';
|
||||
$setting_field['section'] = 'gitea_id';
|
||||
$setting_field['callback_method'] = [
|
||||
Singleton::get_instance( 'Settings', $this ),
|
||||
'token_callback_text',
|
||||
];
|
||||
|
||||
return $setting_field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add subtab to Settings page.
|
||||
*/
|
||||
private function add_settings_subtab() {
|
||||
add_filter(
|
||||
'github_updater_add_settings_subtabs',
|
||||
function ( $subtabs ) {
|
||||
return array_merge( $subtabs, [ 'gitea' => esc_html__( 'Gitea', 'github-updater' ) ] );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the Gitea Settings text.
|
||||
*/
|
||||
public function print_section_gitea_info() {
|
||||
esc_html_e( 'Enter your repository specific Gitea Access Token.', 'github-updater' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the Gitea Access Token Settings text.
|
||||
*/
|
||||
public function print_section_gitea_token() {
|
||||
esc_html_e( 'Enter your Gitea Access Token.', 'github-updater' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add remote install settings fields.
|
||||
*
|
||||
* @param string $type
|
||||
*/
|
||||
public function add_install_settings_fields( $type ) {
|
||||
add_settings_field(
|
||||
'gitea_access_token',
|
||||
esc_html__( 'Gitea Access Token', 'github-updater' ),
|
||||
[ $this, 'gitea_access_token' ],
|
||||
'github_updater_install_' . $type,
|
||||
$type
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gitea Access Token for remote install.
|
||||
*/
|
||||
public function gitea_access_token() {
|
||||
?>
|
||||
<label for="gitea_access_token">
|
||||
<input class="gitea_setting" type="password" style="width:50%;" id="gitea_access_token" name="gitea_access_token" value="" autocomplete="new-password">
|
||||
<br>
|
||||
<span class="description">
|
||||
<?php esc_html_e( 'Enter Gitea Access Token for private Gitea repositories.', 'github-updater' ); ?>
|
||||
</span>
|
||||
</label>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Display Gitea error admin notices.
|
||||
*/
|
||||
public function gitea_error_notices() {
|
||||
add_action( is_multisite() ? 'network_admin_notices' : 'admin_notices', [ $this, 'gitea_error' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate error message for missing Gitea Access Token.
|
||||
*/
|
||||
public function gitea_error() {
|
||||
$auth_required = $this->get_class_vars( 'Settings', 'auth_required' );
|
||||
$error_code = $this->get_error_codes();
|
||||
|
||||
if ( ! isset( $error_code['gitea'] ) &&
|
||||
empty( static::$options['gitea_access_token'] ) &&
|
||||
$auth_required['gitea']
|
||||
) {
|
||||
self::$error_code['gitea'] = [ 'error' => true ];
|
||||
if ( ! \PAnD::is_admin_notice_active( 'gitea-error-1' ) ) {
|
||||
return;
|
||||
}
|
||||
?>
|
||||
<div data-dismissible="gitea-error-1" class="error notice is-dismissible">
|
||||
<p>
|
||||
<?php esc_html_e( 'You must set a Gitea Access Token.', 'github-updater' ); ?>
|
||||
</p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add remote install feature, create endpoint.
|
||||
*
|
||||
* @param array $headers
|
||||
* @param array $install
|
||||
*
|
||||
* @return mixed $install
|
||||
*/
|
||||
public function remote_install( $headers, $install ) {
|
||||
$options['gitea_access_token'] = isset( static::$options['gitea_access_token'] ) ? static::$options['gitea_access_token'] : null;
|
||||
|
||||
$base = $headers['base_uri'] . '/api/v1';
|
||||
|
||||
$install['download_link'] = "{$base}/repos/{$install['github_updater_repo']}/archive/{$install['github_updater_branch']}.zip";
|
||||
|
||||
/*
|
||||
* Add/Save access token if present.
|
||||
*/
|
||||
if ( ! empty( $install['gitea_access_token'] ) ) {
|
||||
$install['options'][ $install['repo'] ] = $install['gitea_access_token'];
|
||||
$install['options']['gitea_access_token'] = $install['gitea_access_token'];
|
||||
}
|
||||
|
||||
$token = ! empty( $install['options']['gitea_access_token'] )
|
||||
? $install['options']['gitea_access_token']
|
||||
: $options['gitea_access_token'];
|
||||
|
||||
if ( ! empty( $token ) ) {
|
||||
$install['download_link'] = add_query_arg( 'access_token', $token, $install['download_link'] );
|
||||
}
|
||||
|
||||
if ( ! empty( static::$options['gitea_access_token'] ) ) {
|
||||
unset( $install['options']['gitea_access_token'] );
|
||||
}
|
||||
|
||||
return $install;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
/**
|
||||
* GitHub Updater
|
||||
*
|
||||
* @author Andy Fragen
|
||||
* @license GPL-2.0+
|
||||
* @link https://github.com/afragen/github-updater
|
||||
* @package github-updater
|
||||
*/
|
||||
|
||||
namespace Fragen\GitHub_Updater\API;
|
||||
|
||||
use Fragen\GitHub_Updater\API;
|
||||
use Fragen\GitHub_Updater\Traits\GHU_Trait;
|
||||
|
||||
/**
|
||||
* Class Language_Pack_API
|
||||
*/
|
||||
class Language_Pack_API extends API {
|
||||
use GHU_Trait;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param \stdClass $type
|
||||
*/
|
||||
public function __construct( $type ) {
|
||||
parent::__construct();
|
||||
self::$method = 'translation';
|
||||
$this->type = $type;
|
||||
$this->response = $this->get_repo_cache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/process Language Packs.
|
||||
*
|
||||
* @param array $headers Array of headers of Language Pack.
|
||||
*
|
||||
* @return bool When invalid response.
|
||||
*/
|
||||
public function get_language_pack( $headers ) {
|
||||
$response = ! empty( $this->response['languages'] ) ? $this->response['languages'] : false;
|
||||
|
||||
if ( ! $response ) {
|
||||
$response = $this->get_language_pack_json( $this->type->git, $headers, $response );
|
||||
|
||||
if ( $response ) {
|
||||
foreach ( $response as $locale ) {
|
||||
$package = $this->process_language_pack_package( $this->type->git, $locale, $headers );
|
||||
|
||||
$response->{$locale->language}->package = $package;
|
||||
$response->{$locale->language}->type = $this->type->type;
|
||||
$response->{$locale->language}->version = $this->type->local_version;
|
||||
}
|
||||
|
||||
$this->set_repo_cache( 'languages', $response );
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$this->type->language_packs = $response;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get language-pack.json from appropriate host.
|
||||
*
|
||||
* @param string $git ( github|bitbucket|gitlab|gitea ).
|
||||
* @param array $headers
|
||||
* @param mixed $response API response.
|
||||
*
|
||||
* @return array|bool|mixed
|
||||
*/
|
||||
private function get_language_pack_json( $git, $headers, $response ) {
|
||||
switch ( $git ) {
|
||||
case 'github':
|
||||
$response = $this->api( '/repos/' . $headers['owner'] . '/' . $headers['repo'] . '/contents/language-pack.json' );
|
||||
$response = isset( $response->content )
|
||||
? json_decode( base64_decode( $response->content ) )
|
||||
: null;
|
||||
break;
|
||||
case 'bitbucket':
|
||||
$response = $this->api( '/2.0/repositories/' . $headers['owner'] . '/' . $headers['repo'] . '/src/master/language-pack.json' );
|
||||
break;
|
||||
case 'gitlab':
|
||||
$id = rawurlencode( $headers['owner'] . '/' . $headers['repo'] );
|
||||
$response = $this->api( '/projects/' . $id . '/repository/files/language-pack.json' );
|
||||
$response = isset( $response->content )
|
||||
? json_decode( base64_decode( $response->content ) )
|
||||
: null;
|
||||
break;
|
||||
case 'gitea':
|
||||
$response = $this->api( '/repos/' . $headers['owner'] . '/' . $headers['repo'] . '/raw/master/language-pack.json' );
|
||||
$response = isset( $response->content )
|
||||
? json_decode( base64_decode( $response->content ) )
|
||||
: null;
|
||||
break;
|
||||
}
|
||||
|
||||
if ( $this->validate_response( $response ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process $package for update transient.
|
||||
*
|
||||
* @param string $git ( github|bitbucket|gitlab|gitea ).
|
||||
* @param string $locale
|
||||
* @param array $headers
|
||||
*
|
||||
* @return array|null|string
|
||||
*/
|
||||
private function process_language_pack_package( $git, $locale, $headers ) {
|
||||
$package = null;
|
||||
switch ( $git ) {
|
||||
case 'github':
|
||||
$package = [ 'https://github.com', $headers['owner'], $headers['repo'], 'blob/master' ];
|
||||
$package = implode( '/', $package ) . $locale->package;
|
||||
$package = add_query_arg( [ 'raw' => 'true' ], $package );
|
||||
break;
|
||||
case 'bitbucket':
|
||||
$package = [ 'https://bitbucket.org', $headers['owner'], $headers['repo'], 'raw/master' ];
|
||||
$package = implode( '/', $package ) . $locale->package;
|
||||
break;
|
||||
case 'gitlab':
|
||||
$package = [ 'https://gitlab.com', $headers['owner'], $headers['repo'], 'raw/master' ];
|
||||
$package = implode( '/', $package ) . $locale->package;
|
||||
break;
|
||||
case 'gitea':
|
||||
// TODO: make sure this works as expected.
|
||||
$package = [ $headers['uri'], 'raw/master' ];
|
||||
$package = implode( '/', $package ) . $local->package;
|
||||
break;
|
||||
}
|
||||
|
||||
return $package;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
/**
|
||||
* GitHub Updater
|
||||
*
|
||||
* @author Andy Fragen
|
||||
* @license GPL-2.0+
|
||||
* @link https://github.com/afragen/github-updater
|
||||
* @package github-updater
|
||||
*/
|
||||
|
||||
namespace Fragen\GitHub_Updater\API;
|
||||
|
||||
/*
|
||||
* Exit if called directly.
|
||||
*/
|
||||
if ( ! defined( 'WPINC' ) ) {
|
||||
die;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class Zipfile_API
|
||||
*
|
||||
* Remote install from a Zipfile.
|
||||
*
|
||||
* @author Andy Fragen
|
||||
*/
|
||||
class Zipfile_API {
|
||||
|
||||
/**
|
||||
* Add remote install settings fields.
|
||||
*
|
||||
* @param string $type plugin|theme.
|
||||
*/
|
||||
public function add_install_settings_fields( $type ) {
|
||||
add_settings_field(
|
||||
'zipfile_slug',
|
||||
esc_html__( 'Zipfile Slug', 'github-updater' ),
|
||||
[ $this, 'zipfile_slug' ],
|
||||
'github_updater_install_' . $type,
|
||||
$type
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set repo slug for remote install.
|
||||
*/
|
||||
public function zipfile_slug() {
|
||||
?>
|
||||
<label for="zipfile_slug">
|
||||
<input class="zipfile_setting" type="text" style="width:50%;" id="zipfile_slug" name="zipfile_slug" value="" placeholder="my-repo-slug">
|
||||
<br>
|
||||
<span class="description">
|
||||
<?php esc_html_e( 'Enter plugin or theme slug.', 'github-updater' ); ?>
|
||||
</span>
|
||||
</label>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Add remote install feature, create endpoint.
|
||||
*
|
||||
* @param array $headers
|
||||
* @param array $install
|
||||
*
|
||||
* @return mixed $install
|
||||
*/
|
||||
public function remote_install( $headers, $install ) {
|
||||
$install['download_link'] = ! empty( $headers['uri'] ) ? $headers['uri'] : $headers['original'];
|
||||
$install['github_updater_install_repo'] = $install['zipfile_slug'];
|
||||
|
||||
return $install;
|
||||
}
|
||||
}
|
||||
957
wp-content/plugins/github-updater/src/GitHub_Updater/Base.php
Normal file
957
wp-content/plugins/github-updater/src/GitHub_Updater/Base.php
Normal file
@@ -0,0 +1,957 @@
|
||||
<?php
|
||||
/**
|
||||
* GitHub Updater
|
||||
*
|
||||
* @author Andy Fragen
|
||||
* @license GPL-2.0+
|
||||
* @link https://github.com/afragen/github-updater
|
||||
* @package github-updater
|
||||
*/
|
||||
|
||||
namespace Fragen\GitHub_Updater;
|
||||
|
||||
use Fragen\Singleton;
|
||||
use Fragen\GitHub_Updater\Traits\GHU_Trait;
|
||||
use Fragen\GitHub_Updater\Traits\Basic_Auth_Loader;
|
||||
use Fragen\GitHub_Updater\API\Bitbucket_API;
|
||||
use Fragen\GitHub_Updater\API\Language_Pack_API;
|
||||
|
||||
/*
|
||||
* Exit if called directly.
|
||||
*/
|
||||
if ( ! defined( 'WPINC' ) ) {
|
||||
die;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class Base
|
||||
*
|
||||
* Update a WordPress plugin or theme from a Git-based repo.
|
||||
*
|
||||
* @author Andy Fragen
|
||||
*/
|
||||
class Base {
|
||||
use GHU_Trait, Basic_Auth_Loader;
|
||||
|
||||
/**
|
||||
* Variable for holding extra theme and plugin headers.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $extra_headers = [];
|
||||
|
||||
/**
|
||||
* Holds the values to be used in the fields callbacks.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $options;
|
||||
|
||||
/**
|
||||
* Holds git server types.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $git_servers = [ 'github' => 'GitHub' ];
|
||||
|
||||
/**
|
||||
* Holds extra repo header types.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $extra_repo_headers = [
|
||||
'Languages' => 'Languages',
|
||||
'CIJob' => 'CI Job',
|
||||
];
|
||||
|
||||
/**
|
||||
* Holds an array of installed git APIs.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $installed_apis = [ 'github_api' => true ];
|
||||
|
||||
/**
|
||||
* Stores the object calling Basic_Auth_Loader.
|
||||
*
|
||||
* @access public
|
||||
* @var \stdClass
|
||||
*/
|
||||
public $caller;
|
||||
|
||||
/**
|
||||
* Store details of all repositories that are installed.
|
||||
*
|
||||
* @var \stdClass
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->set_installed_apis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set boolean for installed API classes.
|
||||
*/
|
||||
protected function set_installed_apis() {
|
||||
if ( file_exists( __DIR__ . '/API/Bitbucket_API.php' ) ) {
|
||||
self::$installed_apis['bitbucket_api'] = true;
|
||||
self::$git_servers['bitbucket'] = 'Bitbucket';
|
||||
} else {
|
||||
self::$installed_apis['bitbucket_api'] = false;
|
||||
}
|
||||
|
||||
self::$installed_apis['bitbucket_server_api'] = file_exists( __DIR__ . '/API/Bitbucket_Server_API.php' );
|
||||
|
||||
if ( file_exists( __DIR__ . '/API/GitLab_API.php' ) ) {
|
||||
self::$installed_apis['gitlab_api'] = true;
|
||||
self::$git_servers['gitlab'] = 'GitLab';
|
||||
} else {
|
||||
self::$installed_apis['gitlab_api'] = false;
|
||||
}
|
||||
if ( file_exists( __DIR__ . '/API/Gitea_API.php' ) ) {
|
||||
self::$installed_apis['gitea_api'] = true;
|
||||
self::$git_servers['gitea'] = 'Gitea';
|
||||
} else {
|
||||
self::$installed_apis['gitea_api'] = false;
|
||||
}
|
||||
if ( file_exists( __DIR__ . '/API/Zipfile_API.php' ) ) {
|
||||
self::$installed_apis['zipfile_api'] = true;
|
||||
self::$git_servers['zipfile'] = 'Zipfile';
|
||||
} else {
|
||||
self::$installed_apis['zipfile_api'] = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load Plugin, Theme, and Settings with correct capabiltiies and on selective admin pages.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function load() {
|
||||
if ( ! apply_filters( 'github_updater_hide_settings', false ) ) {
|
||||
Singleton::get_instance( 'Settings', $this )->run();
|
||||
}
|
||||
if ( ! Singleton::get_instance( 'Init', $this )->can_update() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Run GitHub Updater upgrade functions.
|
||||
$upgrade = new GHU_Upgrade();
|
||||
$upgrade->run();
|
||||
|
||||
// Load plugin stylesheet.
|
||||
add_action(
|
||||
'admin_enqueue_scripts',
|
||||
function () {
|
||||
wp_register_style( 'github-updater', plugins_url( basename( GITHUB_UPDATER_DIR ) ) . '/css/github-updater.css' );
|
||||
wp_enqueue_style( 'github-updater' );
|
||||
}
|
||||
);
|
||||
|
||||
if ( isset( $_POST['ghu_refresh_cache'] ) ) {
|
||||
/**
|
||||
* Fires later in cycle when Refreshing Cache.
|
||||
*
|
||||
* @since 6.0.0
|
||||
*/
|
||||
do_action( 'ghu_refresh_transients' );
|
||||
}
|
||||
|
||||
$this->get_meta_plugins();
|
||||
$this->get_meta_themes();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs actual plugin metadata fetching.
|
||||
*/
|
||||
public function get_meta_plugins() {
|
||||
if ( Singleton::get_instance( 'Init', $this )->can_update() ) {
|
||||
Singleton::get_instance( 'Plugin', $this )->get_remote_plugin_meta();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs actual theme metadata fetching.
|
||||
*/
|
||||
public function get_meta_themes() {
|
||||
if ( Singleton::get_instance( 'Init', $this )->can_update() ) {
|
||||
Singleton::get_instance( 'Theme', $this )->get_remote_theme_meta();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX endpoint for REST updates.
|
||||
*/
|
||||
public function ajax_update() {
|
||||
Singleton::get_instance( 'Rest_Update', $this )->process_request();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run background processes.
|
||||
* Piggyback on built-in update function to get metadata.
|
||||
* Set update transients for remote management.
|
||||
*/
|
||||
public function background_update() {
|
||||
add_action( 'wp_update_plugins', [ $this, 'get_meta_plugins' ] );
|
||||
add_action( 'wp_update_themes', [ $this, 'get_meta_themes' ] );
|
||||
add_action( 'ghu_get_remote_plugin', [ $this, 'run_cron_batch' ], 10, 1 );
|
||||
add_action( 'ghu_get_remote_theme', [ $this, 'run_cron_batch' ], 10, 1 );
|
||||
add_action( 'wp_ajax_nopriv_ithemes_sync_request', [ $this, 'get_meta_remote_management' ] );
|
||||
add_action( 'update_option_auto_updater.lock', [ $this, 'get_meta_remote_management' ] );
|
||||
( new Remote_Management() )->set_update_transients();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls $this->get_meta_plugins() and $this->get_meta_themes()
|
||||
* for remote management services.
|
||||
*/
|
||||
public function get_meta_remote_management() {
|
||||
$this->get_meta_plugins();
|
||||
$this->get_meta_themes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows developers to use 'github_updater_set_options' hook to set access tokens or other settings.
|
||||
* Saves results of filter hook to self::$options.
|
||||
* Single plugin/theme should not be using both hooks.
|
||||
*
|
||||
* Hook requires return of associative element array.
|
||||
* $key === repo-name and $value === token
|
||||
* e.g. array( 'repo-name' => 'access_token' );
|
||||
*/
|
||||
public function set_options_filter() {
|
||||
$config = apply_filters( 'github_updater_set_options', [] );
|
||||
if ( empty( $config ) ) {
|
||||
$config = function_exists( 'apply_filters_deprecated' )
|
||||
? apply_filters_deprecated( 'github_updater_token_distribution', [ null ], '6.1.0', 'github_updater_set_options' )
|
||||
: apply_filters( 'github_updater_token_distribution', [] );
|
||||
}
|
||||
|
||||
if ( ! empty( $config ) ) {
|
||||
$config = $this->sanitize( $config );
|
||||
self::$options = array_merge( get_site_option( 'github_updater' ), $config );
|
||||
update_site_option( 'github_updater', self::$options );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add extra headers to get_plugins() or wp_get_themes().
|
||||
*
|
||||
* @param array $extra_headers
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_headers( $extra_headers ) {
|
||||
$ghu_extra_headers = [
|
||||
'RequiresWP' => 'Requires WP',
|
||||
'RequiresPHP' => 'Requires PHP',
|
||||
'ReleaseAsset' => 'Release Asset',
|
||||
];
|
||||
|
||||
$uri_types = [
|
||||
'PluginURI' => ' Plugin URI',
|
||||
'ThemeURI' => ' Theme URI',
|
||||
];
|
||||
|
||||
foreach ( self::$git_servers as $server ) {
|
||||
foreach ( $uri_types as $uri_key => $uri_value ) {
|
||||
$ghu_extra_headers[ $server . $uri_key ] = $server . $uri_value;
|
||||
}
|
||||
foreach ( self::$extra_repo_headers as $header_key => $header_value ) {
|
||||
$ghu_extra_headers[ $server . $header_key ] = $server . ' ' . $header_value;
|
||||
}
|
||||
}
|
||||
|
||||
self::$extra_headers = array_unique( array_merge( self::$extra_headers, $ghu_extra_headers ) );
|
||||
$extra_headers = array_merge( (array) $extra_headers, $ghu_extra_headers );
|
||||
ksort( self::$extra_headers );
|
||||
|
||||
return $extra_headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on wp-cron job to get remote repo meta in background.
|
||||
*
|
||||
* @param array $batches
|
||||
*/
|
||||
public function run_cron_batch( array $batches ) {
|
||||
foreach ( $batches as $repo ) {
|
||||
$this->get_remote_repo_meta( $repo );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get remote repo meta data for plugins or themes.
|
||||
* Calls remote APIs for data.
|
||||
*
|
||||
* @param \stdClass $repo
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_remote_repo_meta( $repo ) {
|
||||
$file = 'style.css';
|
||||
if ( false !== stripos( $repo->type, 'plugin' ) ) {
|
||||
$file = basename( $repo->file );
|
||||
}
|
||||
|
||||
$repo_api = Singleton::get_instance( 'API', $this )->get_repo_api( $repo->git, $repo );
|
||||
if ( null === $repo_api ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->{$repo->type} = $repo;
|
||||
$this->set_defaults( $repo->type );
|
||||
|
||||
if ( $repo_api->get_remote_info( $file ) ) {
|
||||
if ( ! self::is_wp_cli() ) {
|
||||
if ( ! apply_filters( 'github_updater_run_at_scale', false ) ) {
|
||||
$repo_api->get_repo_meta();
|
||||
$changelog = $this->get_changelog_filename( $repo );
|
||||
if ( $changelog ) {
|
||||
$repo_api->get_remote_changes( $changelog );
|
||||
}
|
||||
$repo_api->get_remote_readme();
|
||||
}
|
||||
if ( ! empty( self::$options['branch_switch'] ) ) {
|
||||
$repo_api->get_remote_branches();
|
||||
}
|
||||
}
|
||||
$repo_api->get_remote_tag();
|
||||
$repo->download_link = $repo_api->construct_download_link();
|
||||
$language_pack = new Language_Pack( $repo, new Language_Pack_API( $repo ) );
|
||||
$language_pack->run();
|
||||
}
|
||||
|
||||
$this->remove_hooks( $repo_api );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default values for plugin/theme.
|
||||
*
|
||||
* @param string $type
|
||||
*/
|
||||
protected function set_defaults( $type ) {
|
||||
if ( ! isset( self::$options['branch_switch'] ) ) {
|
||||
self::$options['branch_switch'] = null;
|
||||
}
|
||||
|
||||
if ( ! isset( $this->$type->slug ) ) {
|
||||
$this->$type = new \stdClass();
|
||||
$this->$type->slug = null;
|
||||
} elseif ( ! isset( self::$options[ $this->$type->slug ] ) ) {
|
||||
self::$options[ $this->$type->slug ] = null;
|
||||
add_site_option( 'github_updater', self::$options );
|
||||
}
|
||||
|
||||
$this->$type->remote_version = '0.0.0';
|
||||
$this->$type->newest_tag = '0.0.0';
|
||||
$this->$type->download_link = null;
|
||||
$this->$type->tags = [];
|
||||
$this->$type->rollback = [];
|
||||
$this->$type->branches = [];
|
||||
$this->$type->requires = null;
|
||||
$this->$type->tested = null;
|
||||
$this->$type->donate_link = null;
|
||||
$this->$type->contributors = [];
|
||||
$this->$type->downloaded = 0;
|
||||
$this->$type->last_updated = null;
|
||||
$this->$type->rating = 0;
|
||||
$this->$type->num_ratings = 0;
|
||||
$this->$type->transient = [];
|
||||
$this->$type->repo_meta = [];
|
||||
$this->$type->watchers = 0;
|
||||
$this->$type->forks = 0;
|
||||
$this->$type->open_issues = 0;
|
||||
$this->$type->requires = false;
|
||||
$this->$type->requires_php = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get filename of changelog and return.
|
||||
*
|
||||
* @param \stdClass $repo
|
||||
*
|
||||
* @return bool|string
|
||||
*/
|
||||
protected function get_changelog_filename( $repo ) {
|
||||
$changelogs = [ 'CHANGES.md', 'CHANGELOG.md', 'changes.md', 'changelog.md' ];
|
||||
$changes = null;
|
||||
$local_files = null;
|
||||
|
||||
if ( is_dir( $repo->local_path ) ) {
|
||||
$local_files = scandir( $repo->local_path, 0 );
|
||||
}
|
||||
|
||||
$changes = array_intersect( (array) $local_files, $changelogs );
|
||||
$changes = array_pop( $changes );
|
||||
|
||||
if ( ! empty( $changes ) ) {
|
||||
return $changes;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove hooks after use.
|
||||
*
|
||||
* @param \stdClass $repo_api
|
||||
*/
|
||||
public function remove_hooks( $repo_api ) {
|
||||
remove_filter( 'extra_theme_headers', [ $this, 'add_headers' ] );
|
||||
remove_filter( 'extra_plugin_headers', [ $this, 'add_headers' ] );
|
||||
|
||||
if ( $repo_api instanceof Bitbucket_API ) {
|
||||
$this->remove_authentication_hooks();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if dupicate wp-cron event exists.
|
||||
*
|
||||
* @param string $event Name of wp-cron event.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_duplicate_wp_cron_event( $event ) {
|
||||
$cron = _get_cron_array();
|
||||
foreach ( $cron as $timestamp => $cronhooks ) {
|
||||
if ( key( $cronhooks ) === $event ) {
|
||||
$this->is_cron_overdue( $cron, $timestamp );
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if wp-cron event is overdue by 24 hours and report error message.
|
||||
*
|
||||
* @param array $cron
|
||||
* @param int $timestamp
|
||||
*/
|
||||
public function is_cron_overdue( $cron, $timestamp ) {
|
||||
$overdue = ( ( time() - $timestamp ) / HOUR_IN_SECONDS ) > 24;
|
||||
if ( $overdue ) {
|
||||
$error_msg = esc_html__( 'There may be a problem with WP-Cron. A GitHub Updater WP-Cron event is overdue.', 'github-updater' );
|
||||
$error = new \WP_Error( 'github_updater_cron_error', $error_msg );
|
||||
Singleton::get_instance( 'Messages', $this )->create_error_message( $error );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for renaming of sources to ensure correct directory name.
|
||||
*
|
||||
* @since WordPress 4.4.0 The $hook_extra parameter became available.
|
||||
*
|
||||
* @param string $source
|
||||
* @param string $remote_source
|
||||
* @param \Plugin_Upgrader|\Theme_Upgrader $upgrader
|
||||
* @param array $hook_extra
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function upgrader_source_selection( $source, $remote_source, $upgrader, $hook_extra = null ) {
|
||||
global $wp_filesystem;
|
||||
$slug = null;
|
||||
$repo = null;
|
||||
$new_source = null;
|
||||
$upgrader_object = null;
|
||||
|
||||
/*
|
||||
* Rename plugins.
|
||||
*/
|
||||
if ( $upgrader instanceof \Plugin_Upgrader ) {
|
||||
$upgrader_object = Singleton::get_instance( 'Plugin', $this );
|
||||
if ( isset( $hook_extra['plugin'] ) ) {
|
||||
$slug = dirname( $hook_extra['plugin'] );
|
||||
$new_source = trailingslashit( $remote_source ) . $slug;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Rename themes.
|
||||
*/
|
||||
if ( $upgrader instanceof \Theme_Upgrader ) {
|
||||
$upgrader_object = Singleton::get_instance( 'Theme', $this );
|
||||
if ( isset( $hook_extra['theme'] ) ) {
|
||||
$slug = $hook_extra['theme'];
|
||||
$new_source = trailingslashit( $remote_source ) . $slug;
|
||||
}
|
||||
}
|
||||
|
||||
$repo = $this->get_repo_slugs( $slug, $upgrader_object );
|
||||
|
||||
/*
|
||||
* Not GitHub Updater plugin/theme.
|
||||
*/
|
||||
if ( ! isset( $_POST['github_updater_repo'] ) && empty( $repo ) ) {
|
||||
return $source;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remote install source.
|
||||
*/
|
||||
$install_options = $this->get_class_vars( 'Install', 'install' );
|
||||
if ( empty( $repo ) && isset( $install_options['github_updater_install_repo'] ) ) {
|
||||
$slug = $install_options['github_updater_install_repo'];
|
||||
$new_source = trailingslashit( $remote_source ) . $slug;
|
||||
self::$options['remote_install'] = true;
|
||||
}
|
||||
|
||||
Singleton::get_instance( 'Branch', $this )->set_branch_on_switch( $slug );
|
||||
|
||||
$new_source = $this->fix_misnamed_directory( $new_source, $remote_source, $upgrader_object, $slug );
|
||||
$new_source = $this->fix_release_asset_directory( $new_source, $remote_source, $upgrader_object, $slug );
|
||||
|
||||
$wp_filesystem->move( $source, $new_source );
|
||||
|
||||
return trailingslashit( $new_source );
|
||||
}
|
||||
|
||||
/**
|
||||
* Correctly rename an initially misnamed directory.
|
||||
* This usually occurs when initial installation not using GitHub Updater.
|
||||
* May cause plugin/theme deactivation.
|
||||
*
|
||||
* @param string $new_source
|
||||
* @param string $remote_source
|
||||
* @param Plugin|Theme $upgrader_object
|
||||
* @param string $slug
|
||||
*
|
||||
* @return string $new_source
|
||||
*/
|
||||
private function fix_misnamed_directory( $new_source, $remote_source, $upgrader_object, $slug ) {
|
||||
if ( ! array_key_exists( $slug, (array) $upgrader_object->config ) &&
|
||||
! isset( self::$options['remote_install'] )
|
||||
) {
|
||||
if ( $upgrader_object instanceof Plugin ) {
|
||||
foreach ( (array) $upgrader_object->config as $plugin ) {
|
||||
if ( $slug === $plugin->slug ) {
|
||||
$new_source = trailingslashit( $remote_source ) . $slug;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( $upgrader_object instanceof Theme ) {
|
||||
foreach ( (array) $upgrader_object->config as $theme ) {
|
||||
if ( $slug === $theme->slug ) {
|
||||
$new_source = trailingslashit( $remote_source ) . $slug;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $new_source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix the directory structure of certain release assests.
|
||||
*
|
||||
* GitLab release assets have a different download directory structure.
|
||||
* Bitbucket release assets need to be copied into a containing directory.
|
||||
*
|
||||
* @param string $new_source
|
||||
* @param string $remote_source
|
||||
* @param Plugin|Theme $upgrader_object
|
||||
* @param string $slug
|
||||
*
|
||||
* @return string $new_source
|
||||
*/
|
||||
private function fix_release_asset_directory( $new_source, $remote_source, $upgrader_object, $slug ) {
|
||||
global $wp_filesystem;
|
||||
if ( isset( $upgrader_object->config[ $slug ]->release_asset ) &&
|
||||
$upgrader_object->config[ $slug ]->release_asset ) {
|
||||
if ( 'gitlab' === $upgrader_object->config[ $slug ]->git ) {
|
||||
$new_source = trailingslashit( dirname( $remote_source ) ) . $slug;
|
||||
add_filter( 'upgrader_post_install', [ $this, 'upgrader_post_install' ], 10, 3 );
|
||||
}
|
||||
if ( 'bitbucket' === $upgrader_object->config[ $slug ]->git ) {
|
||||
$temp_source = trailingslashit( dirname( $remote_source ) ) . $slug;
|
||||
$wp_filesystem->move( $remote_source, $temp_source );
|
||||
wp_mkdir_p( $new_source );
|
||||
copy_dir( $temp_source, $new_source );
|
||||
$wp_filesystem->delete( $temp_source, true );
|
||||
}
|
||||
}
|
||||
|
||||
return $new_source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete $source when updating from GitLab Release Asset.
|
||||
*
|
||||
* @param bool $true
|
||||
* @param array $hook_extra
|
||||
* @param array $result
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function upgrader_post_install( $true, $hook_extra, $result ) {
|
||||
global $wp_filesystem;
|
||||
|
||||
$wp_filesystem->delete( $result['source'], true );
|
||||
remove_filter( 'upgrader_post_install', [ $this, 'upgrader_post_install' ] );
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set array with normal repo names.
|
||||
* Fix name even if installed without renaming originally, eg <repo>-master
|
||||
*
|
||||
* @param string $slug
|
||||
* @param Base|Plugin|Theme $upgrader_object
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_repo_slugs( $slug, $upgrader_object = null ) {
|
||||
$arr = [];
|
||||
$rename = explode( '-', $slug );
|
||||
array_pop( $rename );
|
||||
$rename = implode( '-', $rename );
|
||||
|
||||
if ( null === $upgrader_object ) {
|
||||
$upgrader_object = $this;
|
||||
}
|
||||
|
||||
$rename = isset( $upgrader_object->config[ $slug ] ) ? $slug : $rename;
|
||||
|
||||
foreach ( (array) $upgrader_object->config as $repo ) {
|
||||
// Check repo slug or directory name for match.
|
||||
$slug_check = [
|
||||
$repo->slug,
|
||||
dirname( $repo->file ),
|
||||
];
|
||||
|
||||
// Exact match.
|
||||
if ( \in_array( $slug, $slug_check, true ) ) {
|
||||
$arr['slug'] = $repo->slug;
|
||||
break;
|
||||
}
|
||||
|
||||
// Soft match, there may still be an exact $slug match.
|
||||
if ( \in_array( $rename, $slug_check, true ) ) {
|
||||
$arr['slug'] = $repo->slug;
|
||||
}
|
||||
}
|
||||
|
||||
return $arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update transient for rollback or branch switch.
|
||||
*
|
||||
* @param string $type plugin|theme.
|
||||
* @param \stdClass $repo
|
||||
* @param bool $set_transient Default false, if true then set update transient.
|
||||
*
|
||||
* @return array $rollback Rollback transient.
|
||||
*/
|
||||
protected function set_rollback_transient( $type, $repo, $set_transient = false ) {
|
||||
$repo_api = Singleton::get_instance( 'API', $this )->get_repo_api( $repo->git, $repo );
|
||||
$this->tag = isset( $_GET['rollback'] ) ? $_GET['rollback'] : false;
|
||||
$slug = 'plugin' === $type ? $repo->file : $repo->slug;
|
||||
$download_link = $repo_api->construct_download_link( $this->tag );
|
||||
|
||||
/**
|
||||
* Filter download link so developers can point to specific ZipFile
|
||||
* to use as a download link during a branch switch.
|
||||
*
|
||||
* @since 8.6.0
|
||||
*
|
||||
* @param string $download_link Download URL.
|
||||
* @param /stdClass $repo
|
||||
* @param string $this->tag Branch or tag for rollback.
|
||||
*/
|
||||
$download_link = apply_filters_deprecated(
|
||||
'github_updater_set_rollback_package',
|
||||
[ $download_link, $repo, $this->tag ],
|
||||
'8.8.0',
|
||||
'github_updater_post_construct_download_link'
|
||||
);
|
||||
|
||||
$rollback = [
|
||||
$type => $slug,
|
||||
'new_version' => $this->tag,
|
||||
'url' => $repo->uri,
|
||||
'package' => $download_link,
|
||||
'branch' => $repo->branch,
|
||||
'branches' => $repo->branches,
|
||||
'type' => $repo->type,
|
||||
];
|
||||
|
||||
if ( 'plugin' === $type ) {
|
||||
$rollback['slug'] = $repo->slug;
|
||||
$rollback = (object) $rollback;
|
||||
}
|
||||
|
||||
return $rollback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if wp-cron/background updating has finished.
|
||||
*
|
||||
* @param null $repo
|
||||
*
|
||||
* @return bool true when waiting for background job to finish.
|
||||
*/
|
||||
protected function waiting_for_background_update( $repo = null ) {
|
||||
$caches = [];
|
||||
if ( null !== $repo ) {
|
||||
$cache = isset( $repo->slug ) ? $this->get_repo_cache( $repo->slug ) : null;
|
||||
|
||||
return empty( $cache );
|
||||
}
|
||||
$repos = array_merge(
|
||||
Singleton::get_instance( 'Plugin', $this )->get_plugin_configs(),
|
||||
Singleton::get_instance( 'Theme', $this )->get_theme_configs()
|
||||
);
|
||||
foreach ( $repos as $git_repo ) {
|
||||
$caches[ $git_repo->slug ] = $this->get_repo_cache( $git_repo->slug );
|
||||
}
|
||||
$waiting = array_filter(
|
||||
$caches,
|
||||
function ( $e ) {
|
||||
return empty( $e );
|
||||
}
|
||||
);
|
||||
|
||||
return ! empty( $waiting );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create repo parts.
|
||||
*
|
||||
* @param string $repo
|
||||
* @param string $type plugin|theme.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function get_repo_parts( $repo, $type ) {
|
||||
$arr['bool'] = false;
|
||||
$pattern = '/' . strtolower( $repo ) . '_/';
|
||||
$type = preg_replace( $pattern, '', $type );
|
||||
$repo_types = [
|
||||
'GitHub' => 'github_' . $type,
|
||||
'Bitbucket' => 'bitbucket_' . $type,
|
||||
'GitLab' => 'gitlab_' . $type,
|
||||
'Gitea' => 'gitea_' . $type,
|
||||
];
|
||||
$repo_base_uris = [
|
||||
'GitHub' => 'https://github.com/',
|
||||
'Bitbucket' => 'https://bitbucket.org/',
|
||||
'GitLab' => 'https://gitlab.com/',
|
||||
'Gitea' => '',
|
||||
];
|
||||
|
||||
if ( array_key_exists( $repo, $repo_types ) ) {
|
||||
$arr['type'] = $repo_types[ $repo ];
|
||||
$arr['git_server'] = strtolower( $repo );
|
||||
$arr['base_uri'] = $repo_base_uris[ $repo ];
|
||||
$arr['bool'] = true;
|
||||
foreach ( self::$extra_repo_headers as $key => $value ) {
|
||||
$arr[ $key ] = $repo . ' ' . $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return correct update row opening and closing tags for Shiny Updates.
|
||||
*
|
||||
* @param string $repo_name
|
||||
* @param string $type plugin|theme.
|
||||
* @param bool $branch_switcher
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function update_row_enclosure( $repo_name, $type, $branch_switcher = false ) {
|
||||
global $wp_version;
|
||||
$wp_list_table = _get_list_table( 'WP_Plugins_List_Table' );
|
||||
$repo_base = $repo_name;
|
||||
$shiny_classes = ' notice inline notice-warning notice-alt';
|
||||
|
||||
if ( 'plugin' === $type ) {
|
||||
$repo_base = dirname( $repo_name );
|
||||
}
|
||||
|
||||
$open = '<tr class="plugin-update-tr" data-slug="' . esc_attr( $repo_base ) . '" data-plugin="' . esc_attr( $repo_name ) . '">
|
||||
<td colspan="' . $wp_list_table->get_column_count() . '" class="plugin-update colspanchange">
|
||||
<div class="update-message">';
|
||||
|
||||
$enclosure = [
|
||||
'open' => $open,
|
||||
'close' => '</div></td></tr>',
|
||||
];
|
||||
|
||||
if ( version_compare( $wp_version, '4.6', '>=' ) ) {
|
||||
$open_p = '<p>';
|
||||
$close_p = '</p>';
|
||||
if ( $branch_switcher ) {
|
||||
$open_p = '';
|
||||
$close_p = '';
|
||||
}
|
||||
$enclosure = [
|
||||
'open' => substr_replace( $open, $shiny_classes, -2, 0 ) . $open_p,
|
||||
'close' => $close_p . '</div></td></tr>',
|
||||
];
|
||||
}
|
||||
|
||||
return $enclosure;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make branch switch row.
|
||||
*
|
||||
* @param array $data Parameters for creating branch switching row.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function make_branch_switch_row( $data ) {
|
||||
$rollback = empty( $this->config[ $data['slug'] ]->rollback ) ? [] : $this->config[ $data['slug'] ]->rollback;
|
||||
|
||||
printf(
|
||||
/* translators: 1: branch name, 2: jQuery dropdown, 3: closing tag */
|
||||
esc_html__( 'Current branch is `%1$s`, try %2$sanother version%3$s', 'github-updater' ),
|
||||
$data['branch'],
|
||||
'<a href="#" onclick="jQuery(\'#' . $data['id'] . '\').toggle();return false;">',
|
||||
'</a>.'
|
||||
);
|
||||
|
||||
print '<ul id="' . $data['id'] . '" style="display:none; width: 100%;">';
|
||||
|
||||
if ( null !== $data['branches'] ) {
|
||||
foreach ( array_keys( $data['branches'] ) as $branch ) {
|
||||
printf(
|
||||
'<li><a href="%s%s" aria-label="' . esc_html__( 'Switch to branch ', 'github-updater' ) . $branch . '">%s</a></li>',
|
||||
$data['nonced_update_url'],
|
||||
'&rollback=' . rawurlencode( $branch ),
|
||||
esc_attr( $branch )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $rollback ) ) {
|
||||
$rollback = array_keys( $rollback );
|
||||
usort( $rollback, 'version_compare' );
|
||||
krsort( $rollback );
|
||||
$rollback = array_splice( $rollback, 0, 4, true );
|
||||
array_shift( $rollback ); // Dump current tag.
|
||||
foreach ( $rollback as $tag ) {
|
||||
printf(
|
||||
'<li><a href="%s%s" aria-label="' . esc_html__( 'Switch to release ', 'github-updater' ) . $tag . '">%s</a></li>',
|
||||
$data['nonced_update_url'],
|
||||
'&rollback=' . rawurlencode( $tag ),
|
||||
esc_attr( $tag )
|
||||
);
|
||||
}
|
||||
}
|
||||
if ( empty( $rollback ) ) {
|
||||
esc_html_e( 'No previous tags to rollback to.', 'github-updater' );
|
||||
}
|
||||
|
||||
print '</ul>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate update URL.
|
||||
*
|
||||
* @param string $type ( plugin or theme ).
|
||||
* @param string $action
|
||||
* @param string $repo_name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_update_url( $type, $action, $repo_name ) {
|
||||
$update_url = esc_attr(
|
||||
add_query_arg(
|
||||
[
|
||||
'action' => $action,
|
||||
$type => rawurlencode( $repo_name ),
|
||||
],
|
||||
self_admin_url( 'update.php' )
|
||||
)
|
||||
);
|
||||
|
||||
return $update_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse Enterprise, Languages, Release Asset, and CI Job headers for plugins and themes.
|
||||
*
|
||||
* @param array $header
|
||||
* @param array|\WP_Theme $headers
|
||||
* @param array $header_parts
|
||||
* @param array $repo_parts
|
||||
*
|
||||
* @return array $header
|
||||
*/
|
||||
protected function parse_extra_headers( $header, $headers, $header_parts, $repo_parts ) {
|
||||
$hosted_domains = [ 'github.com', 'bitbucket.org', 'gitlab.com' ];
|
||||
$theme = null;
|
||||
|
||||
$header['enterprise_uri'] = null;
|
||||
$header['enterprise_api'] = null;
|
||||
$header['languages'] = null;
|
||||
$header['ci_job'] = false;
|
||||
$header['release_asset'] = false;
|
||||
|
||||
if ( ! empty( $header['host'] ) && ! in_array( $header['host'], $hosted_domains, true ) ) {
|
||||
$header['enterprise_uri'] = $header['base_uri'];
|
||||
$header['enterprise_api'] = trim( $header['enterprise_uri'], '/' );
|
||||
switch ( $header_parts[0] ) {
|
||||
case 'GitHub':
|
||||
$header['enterprise_api'] .= '/api/v3';
|
||||
break;
|
||||
case 'GitLab':
|
||||
$header['enterprise_api'] .= '/api/v4';
|
||||
break;
|
||||
case 'Bitbucket':
|
||||
$header['enterprise_api'] .= '/rest/api';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $headers instanceof \WP_Theme ) {
|
||||
$theme = $headers;
|
||||
$headers = [];
|
||||
$headers['Release Asset'] = '';
|
||||
$header['release_asset'] = 'true' === $theme->get( 'Release Asset' );
|
||||
}
|
||||
|
||||
$self_hosted_parts = array_keys( self::$extra_repo_headers );
|
||||
foreach ( $self_hosted_parts as $part ) {
|
||||
if ( $theme instanceof \WP_Theme ) {
|
||||
$headers[ $repo_parts[ $part ] ] = $theme->get( $repo_parts[ $part ] );
|
||||
}
|
||||
if ( array_key_exists( $repo_parts[ $part ], $headers ) &&
|
||||
! empty( $headers[ $repo_parts[ $part ] ] )
|
||||
) {
|
||||
switch ( $part ) {
|
||||
case 'Languages':
|
||||
$header['languages'] = $headers[ $repo_parts[ $part ] ];
|
||||
break;
|
||||
case 'CIJob':
|
||||
$header['ci_job'] = $headers[ $repo_parts[ $part ] ];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$header['release_asset'] = ! $header['release_asset'] && isset( $headers['Release Asset'] ) ? 'true' === $headers['Release Asset'] : $header['release_asset'];
|
||||
|
||||
return $header;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
/**
|
||||
* GitHub Updater
|
||||
*
|
||||
* @author Andy Fragen
|
||||
* @license GPL-2.0+
|
||||
* @link https://github.com/afragen/github-updater
|
||||
* @package github-updater
|
||||
*/
|
||||
|
||||
namespace Fragen\GitHub_Updater;
|
||||
|
||||
/*
|
||||
* Exit if called directly.
|
||||
*/
|
||||
if ( ! defined( 'WPINC' ) ) {
|
||||
die;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class Bootstrap
|
||||
*/
|
||||
class Bootstrap {
|
||||
/**
|
||||
* Holds main plugin file.
|
||||
*
|
||||
* @var $file
|
||||
*/
|
||||
protected $file;
|
||||
|
||||
/**
|
||||
* Holds main plugin directory.
|
||||
*
|
||||
* @var $dir
|
||||
*/
|
||||
protected $dir;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $file Main plugin file.
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( $file ) {
|
||||
$this->file = $file;
|
||||
$this->dir = dirname( $file );
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the bootstrap.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run() {
|
||||
add_action(
|
||||
'init',
|
||||
function() {
|
||||
load_plugin_textdomain( 'github-updater' );
|
||||
}
|
||||
);
|
||||
|
||||
define( 'GITHUB_UPDATER_DIR', $this->dir );
|
||||
|
||||
// Load Autoloader.
|
||||
require_once $this->dir . '/vendor/autoload.php';
|
||||
|
||||
register_activation_hook( $this->file, array( new Init(), 'rename_on_activation' ) );
|
||||
( new Init() )->run();
|
||||
|
||||
/**
|
||||
* Initialize Persist Admin notices Dismissal.
|
||||
*
|
||||
* @link https://github.com/collizo4sky/persist-admin-notices-dismissal
|
||||
*/
|
||||
add_action( 'admin_init', array( 'PAnD', 'init' ) );
|
||||
}
|
||||
}
|
||||
108
wp-content/plugins/github-updater/src/GitHub_Updater/Branch.php
Normal file
108
wp-content/plugins/github-updater/src/GitHub_Updater/Branch.php
Normal file
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
/**
|
||||
* GitHub Updater
|
||||
*
|
||||
* @author Andy Fragen
|
||||
* @license GPL-2.0+
|
||||
* @link https://github.com/afragen/github-updater
|
||||
* @package github-updater
|
||||
*/
|
||||
|
||||
namespace Fragen\GitHub_Updater;
|
||||
|
||||
use Fragen\GitHub_Updater\Traits\GHU_Trait;
|
||||
|
||||
/*
|
||||
* Exit if called directly.
|
||||
*/
|
||||
if ( ! defined( 'WPINC' ) ) {
|
||||
die;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class Branch
|
||||
*/
|
||||
class Branch {
|
||||
use GHU_Trait;
|
||||
|
||||
/**
|
||||
* Holds repo cache data.
|
||||
*
|
||||
* @access public
|
||||
* @var null
|
||||
*/
|
||||
public $cache;
|
||||
|
||||
/**
|
||||
* Holds site options.
|
||||
*
|
||||
* @var array $options
|
||||
*/
|
||||
private static $options;
|
||||
|
||||
/**
|
||||
* Branch constructor.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param null $cache
|
||||
*/
|
||||
public function __construct( $cache = null ) {
|
||||
$this->cache = $cache;
|
||||
$this->load_options();
|
||||
self::$options = $this->get_class_vars( 'Base', 'options' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current repo branch.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param \stdClass $repo
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get_current_branch( $repo ) {
|
||||
$current_branch = ! empty( $this->cache['current_branch'] )
|
||||
? $this->cache['current_branch']
|
||||
: $repo->branch;
|
||||
|
||||
return $current_branch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set current branch on branch switch.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $repo Repository slug.
|
||||
*/
|
||||
public function set_branch_on_switch( $repo ) {
|
||||
$this->cache = $this->get_repo_cache( $repo );
|
||||
|
||||
if ( isset( $_GET['action'], $_GET['rollback'], $this->cache['branches'] ) &&
|
||||
( 'upgrade-plugin' === $_GET['action'] || 'upgrade-theme' === $_GET['action'] )
|
||||
) {
|
||||
$current_branch = array_key_exists( $_GET['rollback'], $this->cache['branches'] )
|
||||
? $_GET['rollback']
|
||||
: 'master';
|
||||
|
||||
$this->set_repo_cache( 'current_branch', $current_branch, $repo );
|
||||
self::$options[ 'current_branch_' . $repo ] = $current_branch;
|
||||
update_site_option( 'github_updater', self::$options );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set current branch on install and update options.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $install Array of install data.
|
||||
*/
|
||||
public function set_branch_on_install( $install ) {
|
||||
$this->set_repo_cache( 'current_branch', $install['github_updater_branch'], $install['repo'] );
|
||||
self::$options[ 'current_branch_' . $install['repo'] ] = $install['github_updater_branch'];
|
||||
update_site_option( 'github_updater', self::$options );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/**
|
||||
* GitHub Updater
|
||||
*
|
||||
* @author Andy Fragen
|
||||
* @license GPL-2.0+
|
||||
* @link https://github.com/afragen/github-updater
|
||||
* @package github-updater
|
||||
*/
|
||||
|
||||
namespace Fragen\GitHub_Updater;
|
||||
|
||||
use Fragen\GitHub_Updater\Traits\GHU_Trait;
|
||||
|
||||
/**
|
||||
* Exit if called directly.
|
||||
*/
|
||||
if ( ! defined( 'WPINC' ) ) {
|
||||
die;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class GHU_Upgrade
|
||||
*/
|
||||
class GHU_Upgrade {
|
||||
use GHU_Trait;
|
||||
|
||||
/**
|
||||
* DB version.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $db_version = 8312;
|
||||
|
||||
/**
|
||||
* Run update check against db_version.
|
||||
*/
|
||||
public function run() {
|
||||
$options = $this->get_class_vars( 'Base', 'options' );
|
||||
$db_version = isset( $options['db_version'] ) ? (int) $options['db_version'] : 6000;
|
||||
|
||||
if ( $db_version === $this->db_version ) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch ( $db_version ) {
|
||||
case $db_version < $this->db_version:
|
||||
$this->delete_flush_cache();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
$options = array_merge( (array) $options, [ 'db_version' => (int) $this->db_version ] );
|
||||
update_site_option( 'github_updater', $options );
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush caches and delete cached options.
|
||||
*/
|
||||
private function delete_flush_cache() {
|
||||
wp_cache_flush();
|
||||
$this->delete_all_cached_data();
|
||||
}
|
||||
}
|
||||
151
wp-content/plugins/github-updater/src/GitHub_Updater/Init.php
Normal file
151
wp-content/plugins/github-updater/src/GitHub_Updater/Init.php
Normal file
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
/**
|
||||
* GitHub Updater
|
||||
*
|
||||
* @author Andy Fragen
|
||||
* @license GPL-2.0+
|
||||
* @link https://github.com/afragen/github-updater
|
||||
* @package github-updater
|
||||
*/
|
||||
|
||||
namespace Fragen\GitHub_Updater;
|
||||
|
||||
use Fragen\GitHub_Updater\Traits\GHU_Trait;
|
||||
use Fragen\GitHub_Updater\Traits\Basic_Auth_Loader;
|
||||
|
||||
/*
|
||||
* Exit if called directly.
|
||||
*/
|
||||
if ( ! defined( 'WPINC' ) ) {
|
||||
die;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class Init
|
||||
*/
|
||||
class Init extends Base {
|
||||
use GHU_Trait, Basic_Auth_Loader;
|
||||
|
||||
/**
|
||||
* Constuctor.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
$this->load_options();
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename on activation.
|
||||
*
|
||||
* Correctly renames the slug when GitHub Updater is installed
|
||||
* via FTP or from plugin upload.
|
||||
*
|
||||
* Set current branch to `develop` if appropriate.
|
||||
*
|
||||
* `rename()` causes activation to fail.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function rename_on_activation() {
|
||||
$plugin_dir = trailingslashit( WP_PLUGIN_DIR );
|
||||
$slug = isset( $_GET['plugin'] ) ? $_GET['plugin'] : false;
|
||||
$exploded = explode( '-', dirname( $slug ) );
|
||||
|
||||
if ( in_array( 'develop', $exploded, true ) ) {
|
||||
$options = $this->get_class_vars( 'Base', 'options' );
|
||||
update_site_option( 'github_updater', array_merge( $options, [ 'current_branch_github-updater' => 'develop' ] ) );
|
||||
}
|
||||
|
||||
if ( $slug && 'github-updater/github-updater.php' !== $slug ) {
|
||||
@rename( $plugin_dir . dirname( $slug ), $plugin_dir . 'github-updater' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Let's get going.
|
||||
*/
|
||||
public function run() {
|
||||
if ( ! static::is_heartbeat() ) {
|
||||
$this->load_hooks();
|
||||
}
|
||||
|
||||
if ( static::is_wp_cli() ) {
|
||||
include_once __DIR__ . '/WP_CLI/CLI.php';
|
||||
include_once __DIR__ . '/WP_CLI/CLI_Integration.php';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load relevant action/filter hooks.
|
||||
* Use 'init' hook for user capabilities.
|
||||
*/
|
||||
protected function load_hooks() {
|
||||
add_action( 'init', [ $this, 'load' ] );
|
||||
add_action( 'init', [ $this, 'background_update' ] );
|
||||
add_action( 'init', [ $this, 'set_options_filter' ] );
|
||||
add_action( 'wp_ajax_github-updater-update', [ $this, 'ajax_update' ] );
|
||||
add_action( 'wp_ajax_nopriv_github-updater-update', [ $this, 'ajax_update' ] );
|
||||
|
||||
// Load hook for shiny updates Basic Authentication headers.
|
||||
if ( self::is_doing_ajax() ) {
|
||||
$this->load_authentication_hooks();
|
||||
}
|
||||
|
||||
add_filter( 'extra_theme_headers', [ $this, 'add_headers' ] );
|
||||
add_filter( 'extra_plugin_headers', [ $this, 'add_headers' ] );
|
||||
add_filter( 'upgrader_source_selection', [ $this, 'upgrader_source_selection' ], 10, 4 );
|
||||
|
||||
// Needed for updating from update-core.php.
|
||||
if ( ! self::is_doing_ajax() ) {
|
||||
add_filter( 'upgrader_pre_download', [ $this, 'upgrader_pre_download' ], 10, 3 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks current user capabilities and admin pages.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function can_update() {
|
||||
global $pagenow;
|
||||
|
||||
// WP-CLI access has full capabilities.
|
||||
if ( static::is_wp_cli() ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$can_user_update = current_user_can( 'update_plugins' ) && current_user_can( 'update_themes' );
|
||||
$this->load_options();
|
||||
|
||||
$admin_pages = [
|
||||
'plugins.php',
|
||||
'plugin-install.php',
|
||||
'themes.php',
|
||||
'theme-install.php',
|
||||
'update-core.php',
|
||||
'update.php',
|
||||
'options-general.php',
|
||||
'options.php',
|
||||
'settings.php',
|
||||
'edit.php',
|
||||
];
|
||||
|
||||
// Needed for sequential shiny updating.
|
||||
if ( isset( $_POST['action'] ) && in_array( $_POST['action'], [ 'update-plugin', 'update-theme' ], true ) ) {
|
||||
$admin_pages[] = 'admin-ajax.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter $admin_pages to be able to adjust the pages where GitHub Updater runs.
|
||||
*
|
||||
* @since 8.0.0
|
||||
*
|
||||
* @param array $admin_pages Default array of admin pages where GitHub Updater runs.
|
||||
*/
|
||||
$admin_pages = array_unique( apply_filters( 'github_updater_add_admin_pages', $admin_pages ) );
|
||||
|
||||
return $can_user_update && in_array( $pagenow, $admin_pages, true );
|
||||
}
|
||||
}
|
||||
560
wp-content/plugins/github-updater/src/GitHub_Updater/Install.php
Normal file
560
wp-content/plugins/github-updater/src/GitHub_Updater/Install.php
Normal file
@@ -0,0 +1,560 @@
|
||||
<?php
|
||||
/**
|
||||
* GitHub Updater
|
||||
*
|
||||
* @author Andy Fragen
|
||||
* @license GPL-2.0+
|
||||
* @link https://github.com/afragen/github-updater
|
||||
* @package github-updater
|
||||
*/
|
||||
|
||||
namespace Fragen\GitHub_Updater;
|
||||
|
||||
use Fragen\Singleton;
|
||||
use Fragen\GitHub_Updater\Traits\GHU_Trait;
|
||||
use Fragen\GitHub_Updater\Traits\Basic_Auth_Loader;
|
||||
use Fragen\GitHub_Updater\WP_CLI\CLI_Plugin_Installer_Skin;
|
||||
use Fragen\GitHub_Updater\WP_CLI\CLI_Theme_Installer_Skin;
|
||||
|
||||
/*
|
||||
* Exit if called directly.
|
||||
*/
|
||||
if ( ! defined( 'WPINC' ) ) {
|
||||
die;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class Install
|
||||
*
|
||||
* Install <author>/<repo> directly from GitHub Updater.
|
||||
*/
|
||||
class Install {
|
||||
use GHU_Trait, Basic_Auth_Loader;
|
||||
|
||||
/**
|
||||
* Class options.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $install = [];
|
||||
|
||||
/**
|
||||
* Hold local copy of GitHub Updater options.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
private static $options;
|
||||
|
||||
/**
|
||||
* Hold local copy of installed APIs.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
private static $installed_apis;
|
||||
|
||||
/**
|
||||
* Hold local copy of git servers.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
private static $git_servers;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
self::$options = $this->get_class_vars( 'Base', 'options' );
|
||||
self::$installed_apis = $this->get_class_vars( 'Base', 'installed_apis' );
|
||||
self::$git_servers = $this->get_class_vars( 'Base', 'git_servers' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Let's set up the Install tabs.
|
||||
* Need class-wp-upgrader.php for upgrade classes.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run() {
|
||||
$this->load_js();
|
||||
$this->add_settings_tabs();
|
||||
require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Load javascript for Install.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function load_js() {
|
||||
add_action(
|
||||
'admin_enqueue_scripts',
|
||||
function () {
|
||||
wp_register_script( 'ghu-install', plugins_url( basename( GITHUB_UPDATER_DIR ) . '/js/ghu-install-vanilla.js' ), [], false, true );
|
||||
wp_enqueue_script( 'ghu-install' );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds Install tabs to Settings page.
|
||||
*/
|
||||
public function add_settings_tabs() {
|
||||
$install_tabs = [];
|
||||
if ( current_user_can( 'install_plugins' ) ) {
|
||||
$install_tabs['github_updater_install_plugin'] = esc_html__( 'Install Plugin', 'github-updater' );
|
||||
}
|
||||
if ( current_user_can( 'install_themes' ) ) {
|
||||
$install_tabs['github_updater_install_theme'] = esc_html__( 'Install Theme', 'github-updater' );
|
||||
}
|
||||
add_filter(
|
||||
'github_updater_add_settings_tabs',
|
||||
function ( $tabs ) use ( $install_tabs ) {
|
||||
return array_merge( $tabs, $install_tabs );
|
||||
}
|
||||
);
|
||||
add_action(
|
||||
'github_updater_add_admin_page',
|
||||
function ( $tab ) {
|
||||
$this->add_admin_page( $tab );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Settings page data via action hook.
|
||||
*
|
||||
* @uses 'github_updater_add_admin_page' action hook
|
||||
*
|
||||
* @param string $tab Name of tab.
|
||||
*/
|
||||
public function add_admin_page( $tab ) {
|
||||
if ( 'github_updater_install_plugin' === $tab ) {
|
||||
$this->install( 'plugin' );
|
||||
$this->create_form( 'plugin' );
|
||||
}
|
||||
if ( 'github_updater_install_theme' === $tab ) {
|
||||
$this->install( 'theme' );
|
||||
$this->create_form( 'theme' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Install remote plugin or theme.
|
||||
*
|
||||
* @param string $type
|
||||
* @param array $config
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function install( $type, $config = null ) {
|
||||
$this->set_install_post_data( $config );
|
||||
|
||||
if ( isset( $_POST['option_page'] ) && 'github_updater_install' === $_POST['option_page'] ) {
|
||||
if ( empty( $_POST['github_updater_branch'] ) ) {
|
||||
$_POST['github_updater_branch'] = 'master';
|
||||
}
|
||||
|
||||
// Exit early if no repo entered.
|
||||
if ( empty( $_POST['github_updater_repo'] ) ) {
|
||||
echo '<h3>';
|
||||
esc_html_e( 'A repository URI is required.', 'github-updater' );
|
||||
echo '</h3>';
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Transform URI to owner/repo.
|
||||
$headers = $this->parse_header_uri( $_POST['github_updater_repo'] );
|
||||
$_POST['github_updater_repo'] = $headers['owner_repo'];
|
||||
|
||||
self::$install = $this->sanitize( $_POST );
|
||||
self::$install['repo'] = self::$install['github_updater_install_repo'] = $headers['repo'];
|
||||
|
||||
/*
|
||||
* Create GitHub endpoint.
|
||||
* Save Access Token if present.
|
||||
* Check for GitHub Self-Hosted.
|
||||
*/
|
||||
if ( 'github' === self::$install['github_updater_api'] ) {
|
||||
self::$install = Singleton::get_instance( 'API\GitHub_API', $this, new \stdClass() )->remote_install( $headers, self::$install );
|
||||
}
|
||||
|
||||
/*
|
||||
* Create Bitbucket endpoint and instantiate class Bitbucket_API.
|
||||
* Save private setting if present.
|
||||
* Ensures `maybe_authenticate_http()` is available.
|
||||
*/
|
||||
if ( 'bitbucket' === self::$install['github_updater_api'] ) {
|
||||
$this->load_authentication_hooks();
|
||||
if ( self::$installed_apis['bitbucket_api'] ) {
|
||||
self::$install = Singleton::get_instance( 'API\Bitbucket_API', $this, new \stdClass() )->remote_install( $headers, self::$install );
|
||||
}
|
||||
|
||||
if ( self::$installed_apis['bitbucket_server_api'] ) {
|
||||
self::$install = Singleton::get_instance( 'API\Bitbucket_Server_API', $this, new \stdClass() )->remote_install( $headers, self::$install );
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Create GitLab endpoint.
|
||||
* Save Access Token if present.
|
||||
* Check for GitLab Self-Hosted.
|
||||
*/
|
||||
if ( 'gitlab' === self::$install['github_updater_api'] ) {
|
||||
if ( self::$installed_apis['gitlab_api'] ) {
|
||||
self::$install = Singleton::get_instance( 'API\GitLab_API', $this, new \stdClass() )->remote_install( $headers, self::$install );
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Create Gitea endpoint.
|
||||
* Save Access Token if present.
|
||||
*/
|
||||
if ( 'gitea' === self::$install['github_updater_api'] ) {
|
||||
if ( self::$installed_apis['gitea_api'] ) {
|
||||
self::$install = Singleton::get_instance( 'API\Gitea_API', $this, new \stdClass() )->remote_install( $headers, self::$install );
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Install from Zipfile.
|
||||
*/
|
||||
if ( 'zipfile' === self::$install['github_updater_api'] ) {
|
||||
self::$install = Singleton::get_instance( 'API\Zipfile_API', $this )->remote_install( $headers, self::$install );
|
||||
}
|
||||
|
||||
if ( isset( self::$install['options'] ) ) {
|
||||
$this->save_options_on_install( self::$install['options'] );
|
||||
}
|
||||
|
||||
$url = self::$install['download_link'];
|
||||
$upgrader = $this->get_upgrader( $type, $url );
|
||||
|
||||
// Install the repo from the $source urldecode() and save branch setting.
|
||||
if ( $upgrader && $upgrader->install( $url ) ) {
|
||||
Singleton::get_instance( 'Branch', $this )->set_branch_on_install( self::$install );
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save options set during installation.
|
||||
*
|
||||
* @param array $install_options Array of options from remote install process.
|
||||
* @return void
|
||||
*/
|
||||
private function save_options_on_install( $install_options ) {
|
||||
self::$options = array_merge( self::$options, $install_options );
|
||||
update_site_option( 'github_updater', self::$options );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set remote install data into $_POST.
|
||||
*
|
||||
* @param array $config Data for a remote install.
|
||||
*/
|
||||
private function set_install_post_data( $config ) {
|
||||
if ( ! isset( $config['uri'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$headers = $this->parse_header_uri( $config['uri'] );
|
||||
$api = false !== strpos( $headers['host'], '.com' )
|
||||
? rtrim( $headers['host'], '.com' )
|
||||
: rtrim( $headers['host'], '.org' );
|
||||
|
||||
$api = isset( $config['git'] ) ? $config['git'] : $api;
|
||||
|
||||
$_POST['github_updater_repo'] = $config['uri'];
|
||||
$_POST['github_updater_branch'] = $config['branch'];
|
||||
$_POST['github_updater_api'] = $api;
|
||||
$_POST['option_page'] = 'github_updater_install';
|
||||
|
||||
switch ( $api ) {
|
||||
case 'github':
|
||||
$_POST['github_access_token'] = $config['private'] ?: null;
|
||||
break;
|
||||
case 'bitbucket':
|
||||
$_POST['is_private'] = $config['private'] ? '1' : null;
|
||||
break;
|
||||
case 'gitlab':
|
||||
$_POST['gitlab_access_token'] = $config['private'] ?: null;
|
||||
break;
|
||||
case 'gitea':
|
||||
$_POST['gitea_access_token'] = $config['private'] ?: null;
|
||||
break;
|
||||
case 'zipfile':
|
||||
$_POST['zipfile_slug'] = $config['slug'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate upgrader for remote installation.
|
||||
*
|
||||
* @param string $type 'plugin' | 'theme'.
|
||||
* @param string $url URL of the repository to be installed.
|
||||
*
|
||||
* @return bool|\Plugin_Upgrader|\Theme_Upgrader
|
||||
*/
|
||||
private function get_upgrader( $type, $url ) {
|
||||
$nonce = wp_nonce_url( $url );
|
||||
$upgrader = false;
|
||||
|
||||
if ( 'plugin' === $type ) {
|
||||
$plugin = self::$install['repo'];
|
||||
|
||||
// Create a new instance of Plugin_Upgrader.
|
||||
$skin = static::is_wp_cli()
|
||||
? new CLI_Plugin_Installer_Skin()
|
||||
: new \Plugin_Installer_Skin( compact( 'type', 'url', 'nonce', 'plugin' ) );
|
||||
$upgrader = new \Plugin_Upgrader( $skin );
|
||||
add_filter(
|
||||
'install_plugin_complete_actions',
|
||||
[
|
||||
$this,
|
||||
'install_plugin_complete_actions',
|
||||
],
|
||||
10,
|
||||
3
|
||||
);
|
||||
}
|
||||
|
||||
if ( 'theme' === $type ) {
|
||||
$theme = self::$install['repo'];
|
||||
|
||||
// Create a new instance of Theme_Upgrader.
|
||||
$skin = static::is_wp_cli()
|
||||
? new CLI_Theme_Installer_Skin()
|
||||
: new \Theme_Installer_Skin( compact( 'type', 'url', 'nonce', 'theme' ) );
|
||||
$upgrader = new \Theme_Upgrader( $skin );
|
||||
add_filter(
|
||||
'install_theme_complete_actions',
|
||||
[
|
||||
$this,
|
||||
'install_theme_complete_actions',
|
||||
],
|
||||
10,
|
||||
3
|
||||
);
|
||||
}
|
||||
|
||||
return $upgrader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Install Plugin or Install Theme page.
|
||||
*
|
||||
* @param string $type
|
||||
*/
|
||||
public function create_form( $type ) {
|
||||
// Bail if installing.
|
||||
if ( isset( $_POST['option_page'] ) && 'github_updater_install' === $_POST['option_page'] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->register_settings( $type ); ?>
|
||||
<form method="post">
|
||||
<?php
|
||||
settings_fields( 'github_updater_install' );
|
||||
do_settings_sections( 'github_updater_install_' . $type );
|
||||
if ( 'plugin' === $type ) {
|
||||
submit_button( esc_html__( 'Install Plugin', 'github-updater' ) );
|
||||
}
|
||||
if ( 'theme' === $type ) {
|
||||
submit_button( esc_html__( 'Install Theme', 'github-updater' ) );
|
||||
}
|
||||
?>
|
||||
</form>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Add settings sections.
|
||||
*
|
||||
* @param string $type
|
||||
*/
|
||||
public function register_settings( $type ) {
|
||||
$repo_type = null;
|
||||
|
||||
// Place translatable strings into variables.
|
||||
if ( 'plugin' === $type ) {
|
||||
$repo_type = esc_html__( 'Plugin', 'github-updater' );
|
||||
}
|
||||
if ( 'theme' === $type ) {
|
||||
$repo_type = esc_html__( 'Theme', 'github-updater' );
|
||||
}
|
||||
|
||||
register_setting(
|
||||
'github_updater_install',
|
||||
'github_updater_install_' . $type,
|
||||
[ $this, 'sanitize' ]
|
||||
);
|
||||
|
||||
add_settings_section(
|
||||
$type,
|
||||
/* translators: variable is 'Plugin' or 'Theme' */
|
||||
sprintf( esc_html__( 'GitHub Updater Install %s', 'github-updater' ), $repo_type ),
|
||||
[],
|
||||
'github_updater_install_' . $type
|
||||
);
|
||||
|
||||
add_settings_field(
|
||||
$type . '_repo',
|
||||
/* translators: variable is 'Plugin' or 'Theme' */
|
||||
sprintf( esc_html__( '%s URI', 'github-updater' ), $repo_type ),
|
||||
[ $this, 'get_repo' ],
|
||||
'github_updater_install_' . $type,
|
||||
$type
|
||||
);
|
||||
|
||||
add_settings_field(
|
||||
$type . '_branch',
|
||||
esc_html__( 'Repository Branch', 'github-updater' ),
|
||||
[ $this, 'branch' ],
|
||||
'github_updater_install_' . $type,
|
||||
$type
|
||||
);
|
||||
|
||||
add_settings_field(
|
||||
$type . '_api',
|
||||
esc_html__( 'Remote Repository Host', 'github-updater' ),
|
||||
[ $this, 'install_api' ],
|
||||
'github_updater_install_' . $type,
|
||||
$type
|
||||
);
|
||||
|
||||
/**
|
||||
* Action hook to add git API install settings fields.
|
||||
*
|
||||
* @since 8.0.0
|
||||
*
|
||||
* @param string $type 'plugin'|'theme'.
|
||||
*/
|
||||
do_action( 'github_updater_add_install_settings_fields', $type );
|
||||
|
||||
// Load install settings fields for existing APIs that are not loaded.
|
||||
$running_servers = $this->get_running_git_servers();
|
||||
$git_servers = $this->get_class_vars( 'Base', 'git_servers' );
|
||||
$servers_not_running = array_diff( array_flip( $git_servers ), $running_servers );
|
||||
if ( ! empty( $servers_not_running ) ) {
|
||||
foreach ( array_keys( $servers_not_running ) as $server ) {
|
||||
$class = 'API\\' . $server . '_API';
|
||||
Singleton::get_instance( $class, $this )->add_install_settings_fields( $type );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Repo setting.
|
||||
*/
|
||||
public function get_repo() {
|
||||
?>
|
||||
<label for="github_updater_repo">
|
||||
<input type="text" style="width:50%;" id="github_updater_repo" name="github_updater_repo" value="" autofocus>
|
||||
<br>
|
||||
<span class="description">
|
||||
<?php esc_html_e( 'URI is case sensitive.', 'github-updater' ); ?>
|
||||
</span>
|
||||
</label>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Branch setting.
|
||||
*/
|
||||
public function branch() {
|
||||
?>
|
||||
<label for="github_updater_branch">
|
||||
<input type="text" style="width:50%;" id="github_updater_branch" name="github_updater_branch" value="" placeholder="master">
|
||||
<br>
|
||||
<span class="description">
|
||||
<?php esc_html_e( 'Enter branch name or leave empty for `master`', 'github-updater' ); ?>
|
||||
</span>
|
||||
</label>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* API setting.
|
||||
*/
|
||||
public function install_api() {
|
||||
?>
|
||||
<label for="github_updater_api">
|
||||
<select id="github_updater_api" name="github_updater_api">
|
||||
<?php foreach ( self::$git_servers as $key => $value ) : ?>
|
||||
<?php if ( self::$installed_apis[ $key . '_api' ] ) : ?>
|
||||
<option value="<?php esc_attr_e( $key ); ?>" <?php selected( $key ); ?> >
|
||||
<?php esc_html_e( $value ); ?>
|
||||
</option>
|
||||
<?php endif ?>
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
</label>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove activation links after plugin installation as no method to get $plugin_file.
|
||||
*
|
||||
* @param array $install_actions
|
||||
* @param mixed $api
|
||||
* @param string $plugin_file
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function install_plugin_complete_actions( $install_actions, $api, $plugin_file ) {
|
||||
unset( $install_actions['activate_plugin'], $install_actions['network_activate'] );
|
||||
|
||||
return $install_actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix activation links after theme installation, no method to get proper theme name.
|
||||
*
|
||||
* @param array $install_actions
|
||||
* @param mixed $api
|
||||
* @param mixed $theme_info
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function install_theme_complete_actions( $install_actions, $api, $theme_info ) {
|
||||
if ( isset( $install_actions['preview'] ) ) {
|
||||
unset( $install_actions['preview'] );
|
||||
}
|
||||
|
||||
$stylesheet = self::$install['repo'];
|
||||
$activate_link = add_query_arg(
|
||||
[
|
||||
'action' => 'activate',
|
||||
// 'template' => rawurlencode( $template ),
|
||||
'stylesheet' => rawurlencode( $stylesheet ),
|
||||
],
|
||||
admin_url( 'themes.php' )
|
||||
);
|
||||
$activate_link = esc_url( wp_nonce_url( $activate_link, 'switch-theme_' . $stylesheet ) );
|
||||
|
||||
$install_actions['activate'] = '<a href="' . $activate_link . '" class="activatelink"><span aria-hidden="true">' . esc_attr__( 'Activate', 'github-updater' ) . '</span><span class="screen-reader-text">' . esc_attr__( 'Activate', 'github-updater' ) . ' “' . $stylesheet . '”</span></a>';
|
||||
|
||||
if ( is_network_admin() && current_user_can( 'manage_network_themes' ) ) {
|
||||
$network_activate_link = add_query_arg(
|
||||
[
|
||||
'action' => 'enable',
|
||||
'theme' => rawurlencode( $stylesheet ),
|
||||
],
|
||||
network_admin_url( 'themes.php' )
|
||||
);
|
||||
$network_activate_link = esc_url( wp_nonce_url( $network_activate_link, 'enable-theme_' . $stylesheet ) );
|
||||
|
||||
$install_actions['network_enable'] = '<a href="' . $network_activate_link . '" target="_parent">' . esc_attr_x( 'Network Enable', 'This refers to a network activation in a multisite installation', 'github-updater' ) . '</a>';
|
||||
unset( $install_actions['activate'] );
|
||||
}
|
||||
ksort( $install_actions );
|
||||
|
||||
return $install_actions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
/**
|
||||
* GitHub Updater
|
||||
*
|
||||
* @author Andy Fragen
|
||||
* @license GPL-2.0+
|
||||
* @link https://github.com/afragen/github-updater
|
||||
* @package github-updater
|
||||
*/
|
||||
|
||||
namespace Fragen\GitHub_Updater;
|
||||
|
||||
use Fragen\Singleton;
|
||||
use Fragen\GitHub_Updater\Traits\GHU_Trait;
|
||||
use Fragen\GitHub_Updater\API\Language_Pack_API;
|
||||
|
||||
/**
|
||||
* Exit if called directly.
|
||||
*/
|
||||
if ( ! defined( 'WPINC' ) ) {
|
||||
die;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class Language_Pack
|
||||
*/
|
||||
class Language_Pack {
|
||||
use GHU_Trait;
|
||||
|
||||
/**
|
||||
* Variable containing the plugin/theme object.
|
||||
*
|
||||
* @var Plugin|Theme
|
||||
*/
|
||||
protected $repo;
|
||||
|
||||
/**
|
||||
* Variable containing the Language_Pack_API.
|
||||
*
|
||||
* @var Language_Pack_API
|
||||
*/
|
||||
private $repo_api;
|
||||
|
||||
/**
|
||||
* Language_Pack constructor.
|
||||
*
|
||||
* @param Plugin|Theme $repo Plugin/Theme object.
|
||||
* @param Language_Pack_API $api Language_Pack_API object.
|
||||
*/
|
||||
public function __construct( $repo, Language_Pack_API $api ) {
|
||||
if ( null === $repo->languages ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->repo = $repo;
|
||||
$this->repo_api = $api;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do the Language Pack integration.
|
||||
*/
|
||||
public function run() {
|
||||
if ( null === $this->repo ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$headers = $this->parse_header_uri( $this->repo->languages );
|
||||
$this->repo_api->get_language_pack( $headers );
|
||||
|
||||
add_filter( 'site_transient_update_plugins', [ $this, 'update_site_transient' ] );
|
||||
add_filter( 'site_transient_update_themes', [ $this, 'update_site_transient' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add language translations to update_plugins or update_themes transients.
|
||||
*
|
||||
* @param mixed $transient Update transient.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function update_site_transient( $transient ) {
|
||||
$locales = get_available_languages();
|
||||
$locales = ! empty( $locales ) ? $locales : [ get_locale() ];
|
||||
$repos = [];
|
||||
|
||||
if ( ! isset( $transient->translations ) ) {
|
||||
return $transient;
|
||||
}
|
||||
|
||||
if ( 'site_transient_update_plugins' === current_filter() ) {
|
||||
$repos = Singleton::get_instance( 'Plugin', $this )->get_plugin_configs();
|
||||
$translations = wp_get_installed_translations( 'plugins' );
|
||||
}
|
||||
if ( 'site_transient_update_themes' === current_filter() ) {
|
||||
$repos = Singleton::get_instance( 'Theme', $this )->get_theme_configs();
|
||||
$translations = wp_get_installed_translations( 'themes' );
|
||||
}
|
||||
|
||||
$repos = array_filter(
|
||||
$repos,
|
||||
function ( $e ) {
|
||||
return isset( $e->language_packs );
|
||||
}
|
||||
);
|
||||
|
||||
foreach ( $repos as $repo ) {
|
||||
foreach ( $locales as $locale ) {
|
||||
$lang_pack_mod = isset( $repo->language_packs->$locale )
|
||||
? strtotime( $repo->language_packs->$locale->updated )
|
||||
: 0;
|
||||
$translation_mod = isset( $translations[ $repo->slug ][ $locale ] )
|
||||
? strtotime( $translations[ $repo->slug ][ $locale ]['PO-Revision-Date'] )
|
||||
: 0;
|
||||
if ( $lang_pack_mod > $translation_mod ) {
|
||||
$transient->translations[] = (array) $repo->language_packs->$locale;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$transient->translations = array_unique( $transient->translations, SORT_REGULAR );
|
||||
|
||||
return $transient;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
<?php
|
||||
/**
|
||||
* GitHub Updater
|
||||
*
|
||||
* @author Andy Fragen
|
||||
* @license GPL-2.0+
|
||||
* @link https://github.com/afragen/github-updater
|
||||
* @package github-updater
|
||||
*/
|
||||
|
||||
namespace Fragen\GitHub_Updater;
|
||||
|
||||
use Fragen\GitHub_Updater\Traits\GHU_Trait;
|
||||
|
||||
/*
|
||||
* Exit if called directly.
|
||||
*/
|
||||
if ( ! defined( 'WPINC' ) ) {
|
||||
die;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class Messages
|
||||
*/
|
||||
class Messages {
|
||||
use GHU_Trait;
|
||||
|
||||
/**
|
||||
* Holds WP_Error message.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $error_message = '';
|
||||
|
||||
/**
|
||||
* Display message when API returns other than 200 or 404.
|
||||
*
|
||||
* @param string $type
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function create_error_message( $type = '' ) {
|
||||
global $pagenow;
|
||||
|
||||
$update_pages = [ 'update-core.php', 'plugins.php', 'themes.php' ];
|
||||
$settings_pages = [ 'settings.php', 'options-general.php' ];
|
||||
|
||||
if (
|
||||
( ( ! isset( $_GET['page'] ) || 'github-updater' !== $_GET['page'] ) &&
|
||||
in_array( $pagenow, $settings_pages, true ) ) ||
|
||||
! in_array( $pagenow, array_merge( $update_pages, $settings_pages ), true )
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( is_admin() && ! static::is_doing_ajax() ) {
|
||||
switch ( $type ) {
|
||||
case is_wp_error( $type ):
|
||||
self::$error_message = $type->get_error_message();
|
||||
add_action(
|
||||
is_multisite() ? 'network_admin_notices' : 'admin_notices',
|
||||
[
|
||||
$this,
|
||||
'show_wp_error',
|
||||
]
|
||||
);
|
||||
break;
|
||||
case 'waiting':
|
||||
if ( ! apply_filters( 'github_updater_disable_wpcron', false ) ) {
|
||||
add_action( is_multisite() ? 'network_admin_notices' : 'admin_notices', [ $this, 'waiting' ] );
|
||||
}
|
||||
// no break.
|
||||
case 'git':
|
||||
default:
|
||||
add_action(
|
||||
is_multisite() ? 'network_admin_notices' : 'admin_notices',
|
||||
[
|
||||
$this,
|
||||
'show_403_error_message',
|
||||
]
|
||||
);
|
||||
add_action(
|
||||
is_multisite() ? 'network_admin_notices' : 'admin_notices',
|
||||
[
|
||||
$this,
|
||||
'show_401_error_message',
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create error message for 403 error.
|
||||
* Usually 403 as API rate limit max out.
|
||||
*/
|
||||
public function show_403_error_message() {
|
||||
$_403 = false;
|
||||
$error_code = $this->get_error_codes();
|
||||
foreach ( (array) $error_code as $repo ) {
|
||||
if ( ( ! $_403 && isset( $repo['code'], $repo['git'] ) )
|
||||
&& 403 === $repo['code'] && 'github' === $repo['git'] ) {
|
||||
$_403 = true;
|
||||
if ( ! \PAnD::is_admin_notice_active( '403-error-1' ) ) {
|
||||
return;
|
||||
} ?>
|
||||
<div data-dismissible="403-error-1" class="notice-error notice is-dismissible">
|
||||
<p>
|
||||
<?php
|
||||
esc_html_e( 'GitHub Updater Error Code:', 'github-updater' );
|
||||
echo ' ' . $repo['code'];
|
||||
?>
|
||||
<br>
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %s: wait time */
|
||||
esc_html__( 'GitHub API’s rate limit will reset in %s minutes.', 'github-updater' ),
|
||||
$repo['wait']
|
||||
);
|
||||
echo '<br>';
|
||||
printf(
|
||||
/* translators: %s: GitHub personal access token URL */
|
||||
wp_kses_post( __( 'It looks like you are running into GitHub API rate limits. Be sure and configure a <a href="%s">Personal Access Token</a> to avoid this issue.', 'github-updater' ) ),
|
||||
esc_url( 'https://help.github.com/articles/creating-an-access-token-for-command-line-use/' )
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create error message or 401 (Authentication Error) error.
|
||||
* Usually 401 as private repo with no token set or incorrect user/pass.
|
||||
*/
|
||||
public function show_401_error_message() {
|
||||
$_401 = false;
|
||||
$error_code = $this->get_error_codes();
|
||||
foreach ( (array) $error_code as $repo ) {
|
||||
if ( ( ! $_401 && isset( $repo['code'] ) ) && 401 === $repo['code'] ) {
|
||||
$_401 = true;
|
||||
if ( ! \PAnD::is_admin_notice_active( '401-error-1' ) ) {
|
||||
return;
|
||||
}
|
||||
?>
|
||||
<div data-dismissible="401-error-1" class="notice-error notice is-dismissible">
|
||||
<p>
|
||||
<?php
|
||||
esc_html_e( 'GitHub Updater Error Code:', 'github-updater' );
|
||||
echo ' ' . $repo['code'];
|
||||
?>
|
||||
<br>
|
||||
<?php esc_html_e( 'There is probably an access token or password error on the GitHub Updater Settings page.', 'github-updater' ); ?>
|
||||
</p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate error message for WP_Error.
|
||||
*/
|
||||
public function show_wp_error() {
|
||||
?>
|
||||
<div class="notice-error notice">
|
||||
<p>
|
||||
<?php
|
||||
esc_html_e( 'GitHub Updater Error Code:', 'github-updater' );
|
||||
echo ' ' . self::$error_message;
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate information message when waiting for WP-Cron to finish.
|
||||
*/
|
||||
public function waiting() {
|
||||
?>
|
||||
<div class="notice-info notice is-dismissible">
|
||||
<p>
|
||||
<?php esc_html_e( 'GitHub Updater Information', 'github-updater' ); ?>
|
||||
<br>
|
||||
<?php esc_html_e( 'Please be patient while WP-Cron finishes making API calls.', 'github-updater' ); ?>
|
||||
</p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
443
wp-content/plugins/github-updater/src/GitHub_Updater/Plugin.php
Normal file
443
wp-content/plugins/github-updater/src/GitHub_Updater/Plugin.php
Normal file
@@ -0,0 +1,443 @@
|
||||
<?php
|
||||
/**
|
||||
* GitHub Updater
|
||||
*
|
||||
* @author Andy Fragen
|
||||
* @license GPL-2.0+
|
||||
* @link https://github.com/afragen/github-updater
|
||||
* @package github-updater
|
||||
*/
|
||||
|
||||
namespace Fragen\GitHub_Updater;
|
||||
|
||||
use Fragen\Singleton;
|
||||
use Fragen\GitHub_Updater\Traits\GHU_Trait;
|
||||
|
||||
/*
|
||||
* Exit if called directly.
|
||||
*/
|
||||
if ( ! defined( 'WPINC' ) ) {
|
||||
die;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class Plugin
|
||||
*
|
||||
* Update a WordPress plugin from a GitHub repo.
|
||||
*
|
||||
* @author Andy Fragen
|
||||
* @author Codepress
|
||||
* @link https://github.com/codepress/github-plugin-updater
|
||||
*/
|
||||
class Plugin extends Base {
|
||||
use GHU_Trait;
|
||||
|
||||
/**
|
||||
* Rollback variable
|
||||
*
|
||||
* @var string branch
|
||||
*/
|
||||
public $tag = false;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
$this->load_options();
|
||||
|
||||
// Get details of installed git sourced plugins.
|
||||
$this->config = $this->get_plugin_meta();
|
||||
|
||||
if ( null === $this->config ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of configurations for the known plugins.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_plugin_configs() {
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get details of Git-sourced plugins from those that are installed.
|
||||
*
|
||||
* @return array Indexed array of associative arrays of plugin details.
|
||||
*/
|
||||
protected function get_plugin_meta() {
|
||||
// Ensure get_plugins() function is available.
|
||||
include_once ABSPATH . '/wp-admin/includes/plugin.php';
|
||||
|
||||
$plugins = get_plugins();
|
||||
$git_plugins = [];
|
||||
|
||||
/**
|
||||
* Filter to add plugins not containing appropriate header line.
|
||||
*
|
||||
* @since 5.4.0
|
||||
* @access public
|
||||
*
|
||||
* @param array $additions Listing of plugins to add.
|
||||
* Default null.
|
||||
* @param array $plugins Listing of all plugins.
|
||||
* @param string 'plugin' Type being passed.
|
||||
*/
|
||||
$additions = apply_filters( 'github_updater_additions', null, $plugins, 'plugin' );
|
||||
$plugins = array_merge( $plugins, (array) $additions );
|
||||
|
||||
foreach ( (array) $plugins as $plugin => $headers ) {
|
||||
$git_plugin = [];
|
||||
|
||||
foreach ( (array) static::$extra_headers as $value ) {
|
||||
$header = null;
|
||||
|
||||
if ( empty( $headers[ $value ] ) || false === stripos( $value, 'Plugin' ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$header_parts = explode( ' ', $value );
|
||||
$repo_parts = $this->get_repo_parts( $header_parts[0], 'plugin' );
|
||||
|
||||
if ( $repo_parts['bool'] ) {
|
||||
$header = $this->parse_header_uri( $headers[ $value ] );
|
||||
if ( empty( $header ) ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$header = $this->parse_extra_headers( $header, $headers, $header_parts, $repo_parts );
|
||||
$current_branch = "current_branch_{$header['repo']}";
|
||||
$branch = isset( static::$options[ $current_branch ] )
|
||||
? static::$options[ $current_branch ]
|
||||
: false;
|
||||
|
||||
$git_plugin['type'] = 'plugin';
|
||||
$git_plugin['git'] = $repo_parts['git_server'];
|
||||
$git_plugin['uri'] = "{$header['base_uri']}/{$header['owner_repo']}";
|
||||
$git_plugin['enterprise'] = $header['enterprise_uri'];
|
||||
$git_plugin['enterprise_api'] = $header['enterprise_api'];
|
||||
$git_plugin['owner'] = $header['owner'];
|
||||
$git_plugin['slug'] = $header['repo'];
|
||||
$git_plugin['branch'] = $branch ?: 'master';
|
||||
$git_plugin['file'] = $plugin;
|
||||
$git_plugin['local_path'] = WP_PLUGIN_DIR . "/{$header['repo']}/";
|
||||
|
||||
$plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $git_plugin['file'] );
|
||||
$git_plugin['author'] = $plugin_data['AuthorName'];
|
||||
$git_plugin['name'] = $plugin_data['Name'];
|
||||
$git_plugin['homepage'] = $plugin_data['PluginURI'];
|
||||
$git_plugin['local_version'] = strtolower( $plugin_data['Version'] );
|
||||
$git_plugin['sections']['description'] = $plugin_data['Description'];
|
||||
$git_plugin['languages'] = $header['languages'];
|
||||
$git_plugin['ci_job'] = $header['ci_job'];
|
||||
$git_plugin['release_asset'] = $header['release_asset'];
|
||||
$git_plugin['broken'] = ( empty( $header['owner'] ) || empty( $header['repo'] ) );
|
||||
|
||||
$git_plugin['banners']['high'] =
|
||||
file_exists( WP_PLUGIN_DIR . "/{$header['repo']}/assets/banner-1544x500.png" )
|
||||
? WP_PLUGIN_URL . "/{$header['repo']}/assets/banner-1544x500.png"
|
||||
: null;
|
||||
|
||||
$git_plugin['banners']['low'] =
|
||||
file_exists( WP_PLUGIN_DIR . "/{$header['repo']}/assets/banner-772x250.png" )
|
||||
? WP_PLUGIN_URL . "/{$header['repo']}/assets/banner-772x250.png"
|
||||
: null;
|
||||
|
||||
$git_plugin['icons'] = [];
|
||||
$icons = [
|
||||
'svg' => 'icon.svg',
|
||||
'1x_png' => 'icon-128x128.png',
|
||||
'1x_jpg' => 'icon-128x128.jpg',
|
||||
'2x_png' => 'icon-256x256.png',
|
||||
'2x_jpg' => 'icon-256x256.jpg',
|
||||
];
|
||||
foreach ( $icons as $key => $filename ) {
|
||||
$key = preg_replace( '/_png|_jpg/', '', $key );
|
||||
$git_plugin['icons'][ $key ] = file_exists( $git_plugin['local_path'] . 'assets/' . $filename )
|
||||
? WP_PLUGIN_URL . "/{$git_plugin['slug']}/assets/{$filename}"
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
// Exit if not git hosted plugin.
|
||||
if ( empty( $git_plugin ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$git_plugins[ $git_plugin['slug'] ] = (object) $git_plugin;
|
||||
}
|
||||
|
||||
return $git_plugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get remote plugin meta to populate $config plugin objects.
|
||||
* Calls to remote APIs to get data.
|
||||
*/
|
||||
public function get_remote_plugin_meta() {
|
||||
$plugins = [];
|
||||
foreach ( (array) $this->config as $plugin ) {
|
||||
/**
|
||||
* Filter to set if WP-Cron is disabled or if user wants to return to old way.
|
||||
*
|
||||
* @since 7.4.0
|
||||
* @access public
|
||||
*
|
||||
* @param bool
|
||||
*/
|
||||
if ( ! $this->waiting_for_background_update( $plugin ) || static::is_wp_cli()
|
||||
|| apply_filters( 'github_updater_disable_wpcron', false )
|
||||
) {
|
||||
$this->get_remote_repo_meta( $plugin );
|
||||
} else {
|
||||
$plugins[ $plugin->slug ] = $plugin;
|
||||
}
|
||||
|
||||
// current_filter() check due to calling hook for shiny updates, don't show row twice.
|
||||
if ( ! $plugin->release_asset && 'init' === current_filter() &&
|
||||
( ! is_multisite() || is_network_admin() )
|
||||
) {
|
||||
add_action( "after_plugin_row_{$plugin->file}", [ $this, 'plugin_branch_switcher' ], 15, 3 );
|
||||
}
|
||||
}
|
||||
|
||||
$schedule_event = defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ? is_main_site() : true;
|
||||
|
||||
if ( $schedule_event ) {
|
||||
if ( ! wp_next_scheduled( 'ghu_get_remote_plugin' ) &&
|
||||
! $this->is_duplicate_wp_cron_event( 'ghu_get_remote_plugin' ) &&
|
||||
! apply_filters( 'github_updater_disable_wpcron', false )
|
||||
) {
|
||||
wp_schedule_single_event( time(), 'ghu_get_remote_plugin', [ $plugins ] );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! static::is_wp_cli() ) {
|
||||
$this->load_pre_filters();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load pre-update filters.
|
||||
*/
|
||||
public function load_pre_filters() {
|
||||
add_filter( 'plugin_row_meta', [ $this, 'plugin_row_meta' ], 10, 2 );
|
||||
add_filter( 'plugins_api', [ $this, 'plugins_api' ], 99, 3 );
|
||||
add_filter( 'site_transient_update_plugins', [ $this, 'update_site_transient' ], 15, 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add branch switch row to plugins page.
|
||||
*
|
||||
* @param string $plugin_file
|
||||
* @param \stdClass $plugin_data
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function plugin_branch_switcher( $plugin_file, $plugin_data ) {
|
||||
if ( empty( static::$options['branch_switch'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$enclosure = $this->update_row_enclosure( $plugin_file, 'plugin', true );
|
||||
$plugin = $this->get_repo_slugs( dirname( $plugin_file ) );
|
||||
$nonced_update_url = wp_nonce_url(
|
||||
$this->get_update_url( 'plugin', 'upgrade-plugin', $plugin_file ),
|
||||
'upgrade-plugin_' . $plugin_file
|
||||
);
|
||||
|
||||
if ( ! empty( $plugin ) ) {
|
||||
$id = $plugin['slug'] . '-id';
|
||||
$branches = isset( $this->config[ $plugin['slug'] ]->branches )
|
||||
? $this->config[ $plugin['slug'] ]->branches
|
||||
: null;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get current branch.
|
||||
$repo = $this->config[ $plugin['slug'] ];
|
||||
$branch = Singleton::get_instance( 'Branch', $this )->get_current_branch( $repo );
|
||||
|
||||
$branch_switch_data = [];
|
||||
$branch_switch_data['slug'] = $plugin['slug'];
|
||||
$branch_switch_data['nonced_update_url'] = $nonced_update_url;
|
||||
$branch_switch_data['id'] = $id;
|
||||
$branch_switch_data['branch'] = $branch;
|
||||
$branch_switch_data['branches'] = $branches;
|
||||
|
||||
/*
|
||||
* Create after_plugin_row_
|
||||
*/
|
||||
echo $enclosure['open'];
|
||||
$this->make_branch_switch_row( $branch_switch_data );
|
||||
echo $enclosure['close'];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add 'View details' link to plugins page.
|
||||
*
|
||||
* @param array $links
|
||||
* @param string $file
|
||||
*
|
||||
* @return array $links
|
||||
*/
|
||||
public function plugin_row_meta( $links, $file ) {
|
||||
$regex_pattern = '/<a href="(.*)">(.*)<\/a>/';
|
||||
$repo = dirname( $file );
|
||||
|
||||
/*
|
||||
* Sanity check for some commercial plugins.
|
||||
*/
|
||||
if ( ! isset( $links[2] ) ) {
|
||||
return $links;
|
||||
}
|
||||
|
||||
preg_match( $regex_pattern, $links[2], $matches );
|
||||
|
||||
/*
|
||||
* Remove 'Visit plugin site' link in favor or 'View details' link.
|
||||
*/
|
||||
if ( array_key_exists( $repo, $this->config ) ) {
|
||||
if ( null !== $repo ) {
|
||||
unset( $links[2] );
|
||||
$links[] = sprintf(
|
||||
'<a href="%s" class="thickbox">%s</a>',
|
||||
esc_url(
|
||||
add_query_arg(
|
||||
[
|
||||
'tab' => 'plugin-information',
|
||||
'plugin' => $repo,
|
||||
'TB_iframe' => 'true',
|
||||
'width' => 600,
|
||||
'height' => 550,
|
||||
],
|
||||
network_admin_url( 'plugin-install.php' )
|
||||
)
|
||||
),
|
||||
esc_html__( 'View details', 'github-updater' )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put changelog in plugins_api, return WP.org data as appropriate
|
||||
*
|
||||
* @param bool $false
|
||||
* @param string $action
|
||||
* @param \stdClass $response
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function plugins_api( $false, $action, $response ) {
|
||||
if ( ! ( 'plugin_information' === $action ) ) {
|
||||
return $false;
|
||||
}
|
||||
|
||||
$plugin = isset( $this->config[ $response->slug ] ) ? $this->config[ $response->slug ] : false;
|
||||
|
||||
// Skip if waiting for background update.
|
||||
if ( $this->waiting_for_background_update( $plugin ) ) {
|
||||
return $false;
|
||||
}
|
||||
|
||||
// wp.org plugin.
|
||||
if ( ! $plugin || ( $plugin->dot_org && 'master' === $plugin->branch ) ) {
|
||||
return $false;
|
||||
}
|
||||
|
||||
$response->slug = $plugin->slug;
|
||||
$response->plugin_name = $plugin->name;
|
||||
$response->name = $plugin->name;
|
||||
$response->author = $plugin->author;
|
||||
$response->homepage = $plugin->homepage;
|
||||
$response->donate_link = $plugin->donate_link;
|
||||
$response->version = $plugin->remote_version;
|
||||
$response->sections = $plugin->sections;
|
||||
$response->requires = $plugin->requires;
|
||||
$response->requires_php = $plugin->requires_php;
|
||||
$response->tested = $plugin->tested;
|
||||
$response->downloaded = $plugin->downloaded;
|
||||
$response->last_updated = $plugin->last_updated;
|
||||
$response->download_link = $plugin->download_link;
|
||||
$response->banners = $plugin->banners;
|
||||
$response->icons = ! empty( $plugin->icons ) ? $plugin->icons : [];
|
||||
$response->contributors = $plugin->contributors;
|
||||
if ( ! $this->is_private( $plugin ) ) {
|
||||
$response->num_ratings = $plugin->num_ratings;
|
||||
$response->rating = $plugin->rating;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook into site_transient_update_plugins to update from GitHub.
|
||||
*
|
||||
* @param \stdClass $transient
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function update_site_transient( $transient ) {
|
||||
foreach ( (array) $this->config as $plugin ) {
|
||||
if ( $this->can_update_repo( $plugin ) ) {
|
||||
$response = [
|
||||
'slug' => $plugin->slug,
|
||||
'plugin' => $plugin->file,
|
||||
'new_version' => $plugin->remote_version,
|
||||
'url' => $plugin->uri,
|
||||
'package' => $plugin->download_link,
|
||||
'icons' => $plugin->icons,
|
||||
'tested' => $plugin->tested,
|
||||
'requires_php' => $plugin->requires_php,
|
||||
'branch' => $plugin->branch,
|
||||
'branches' => array_keys( $plugin->branches ),
|
||||
'type' => "{$plugin->git}-{$plugin->type}",
|
||||
];
|
||||
|
||||
// Skip on RESTful updating.
|
||||
if ( isset( $_GET['action'], $_GET['plugin'] ) &&
|
||||
'github-updater-update' === $_GET['action'] &&
|
||||
$response['slug'] === $_GET['plugin']
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Pull update from dot org if not overriding.
|
||||
if ( ! $this->override_dot_org( 'plugin', $plugin ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$transient->response[ $plugin->file ] = (object) $response;
|
||||
} else {
|
||||
/**
|
||||
* Filter to return array of overrides to dot org.
|
||||
*
|
||||
* @since 8.5.0
|
||||
* @return array
|
||||
*/
|
||||
$overrides = apply_filters( 'github_updater_override_dot_org', [] );
|
||||
if ( isset( $transient->response[ $plugin->file ] ) && in_array( $plugin->file, $overrides, true ) ) {
|
||||
unset( $transient->response[ $plugin->file ] );
|
||||
}
|
||||
}
|
||||
|
||||
// Set transient on rollback.
|
||||
if ( isset( $_GET['plugin'], $_GET['rollback'] ) && $plugin->file === $_GET['plugin']
|
||||
) {
|
||||
$transient->response[ $plugin->file ] = $this->set_rollback_transient( 'plugin', $plugin );
|
||||
}
|
||||
}
|
||||
|
||||
return $transient;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
<?php
|
||||
/**
|
||||
* GitHub Updater
|
||||
*
|
||||
* @author Andy Fragen
|
||||
* @license GPL-2.0+
|
||||
* @link https://github.com/afragen/github-updater
|
||||
* @package github-updater
|
||||
* @uses https://meta.trac.wordpress.org/browser/sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/readme
|
||||
*/
|
||||
|
||||
namespace Fragen\GitHub_Updater;
|
||||
|
||||
use WordPressdotorg\Plugin_Directory\Readme\Parser;
|
||||
use Parsedown;
|
||||
|
||||
/*
|
||||
* Exit if called directly.
|
||||
*/
|
||||
if ( ! defined( 'WPINC' ) ) {
|
||||
die;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class Readme_Parser
|
||||
*/
|
||||
class Readme_Parser extends Parser {
|
||||
|
||||
/**
|
||||
* Holds absolute filepath to temp readme file.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $readme_path;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* Convert file contents string to temporary file.
|
||||
* Pass file path into class-parser.php.
|
||||
* Delete temporary file when finished.
|
||||
*
|
||||
* @param string $file
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( $file ) {
|
||||
$file_path = trailingslashit( get_temp_dir() ) . md5( $file ) . '-tmp-readme.txt';
|
||||
|
||||
/**
|
||||
* Filter location of temporary readme filepath.
|
||||
*
|
||||
* @since 8.7.0
|
||||
*
|
||||
* @param string $file_path Absolute filepath to temp readme file.
|
||||
*/
|
||||
$this->readme_path = apply_filters( 'github_updater_temp_readme_filepath', $file_path );
|
||||
$this->readme_path = file_put_contents( $this->readme_path, $file ) ? $this->readme_path : false;
|
||||
parent::__construct( $this->readme_path );
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse text into markdown.
|
||||
*
|
||||
* @param string $text
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parse_markdown( $text ) {
|
||||
static $markdown = null;
|
||||
|
||||
if ( null === $markdown ) {
|
||||
$markdown = new Parsedown();
|
||||
}
|
||||
|
||||
return $markdown->text( $text );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return parsed readme.txt as array.
|
||||
*
|
||||
* @return array $data
|
||||
*/
|
||||
public function parse_data() {
|
||||
$data = [];
|
||||
foreach ( get_object_vars( $this ) as $key => $value ) {
|
||||
$data[ $key ] = 'contributors' === $key ? $this->create_contributors( $value ) : $value;
|
||||
}
|
||||
$data = $this->faq_as_h4( $data );
|
||||
$data = $this->readme_section_as_h4( 'changelog', $data );
|
||||
$data = $this->readme_section_as_h4( 'description', $data );
|
||||
|
||||
@unlink( $this->readme_path );
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize contributors.
|
||||
*
|
||||
* @param array $users
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function sanitize_contributors( $users ) {
|
||||
return $users;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create contributor data.
|
||||
*
|
||||
* @param array $users
|
||||
*
|
||||
* @return array $contributors
|
||||
*/
|
||||
private function create_contributors( $users ) {
|
||||
global $wp_version;
|
||||
$contributors = [];
|
||||
foreach ( (array) $users as $contributor ) {
|
||||
$contributors[ $contributor ]['display_name'] = $contributor;
|
||||
$contributors[ $contributor ]['profile'] = '//profiles.wordpress.org/' . $contributor;
|
||||
$contributors[ $contributor ]['avatar'] = 'https://wordpress.org/grav-redirect.php?user=' . $contributor;
|
||||
if ( version_compare( $wp_version, '5.1-alpha', '<' ) ) {
|
||||
$contributors[ $contributor ] = '//profiles.wordpress.org/' . $contributor;
|
||||
}
|
||||
}
|
||||
|
||||
return $contributors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts FAQ from dictionary list to h4 style.
|
||||
*
|
||||
* @param array $data Array of parsed readme data.
|
||||
*
|
||||
* @return array $data
|
||||
*/
|
||||
public function faq_as_h4( $data ) {
|
||||
if ( empty( $data['faq'] ) ) {
|
||||
return $data;
|
||||
}
|
||||
unset( $data['sections']['faq'] );
|
||||
$data['sections']['faq'] = '';
|
||||
foreach ( $data['faq'] as $question => $answer ) {
|
||||
$data['sections']['faq'] .= "<h4>{$question}</h4>\n{$answer}\n";
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts wp.org readme section items to h4 style.
|
||||
*
|
||||
* @param string $section Readme section.
|
||||
* @param array $data Array of parsed readme data.
|
||||
*
|
||||
* @return array $data
|
||||
*/
|
||||
public function readme_section_as_h4( $section, $data ) {
|
||||
if ( empty( $data['sections'][ $section ] ) || false !== strpos( $data['sections'][ $section ], '<h4>' ) ) {
|
||||
return $data;
|
||||
}
|
||||
$pattern = '~<p>=(.*)=</p>~';
|
||||
$replace = '<h4>$1</h4>';
|
||||
|
||||
$data['sections'][ $section ] = preg_replace( $pattern, $replace, $data['sections'][ $section ] );
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace parent method as some users don't have `mb_strrpos()`.
|
||||
*
|
||||
* @access protected
|
||||
*
|
||||
* @param string $desc
|
||||
* @param int $length
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function trim_length( $desc, $length = 150 ) {
|
||||
if ( mb_strlen( $desc ) > $length ) {
|
||||
$desc = mb_substr( $desc, 0, $length ) . ' …';
|
||||
|
||||
// If not a full sentence, and one ends within 20% of the end, trim it to that.
|
||||
if ( function_exists( 'mb_strrpos' ) ) {
|
||||
$pos = mb_strrpos( $desc, '.' );
|
||||
} else {
|
||||
$pos = strrpos( $desc, '.' );
|
||||
}
|
||||
if ( $pos > ( 0.8 * $length ) && '.' !== mb_substr( $desc, -1 ) ) {
|
||||
$desc = mb_substr( $desc, 0, $pos + 1 );
|
||||
}
|
||||
}
|
||||
|
||||
return trim( $desc );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
<?php
|
||||
/**
|
||||
* GitHub Updater
|
||||
*
|
||||
* @author Andy Fragen
|
||||
* @license GPL-2.0+
|
||||
* @link https://github.com/afragen/github-updater
|
||||
* @package github-updater
|
||||
*/
|
||||
|
||||
namespace Fragen\GitHub_Updater;
|
||||
|
||||
use Fragen\GitHub_Updater\Traits\GHU_Trait;
|
||||
use Fragen\Singleton;
|
||||
|
||||
/**
|
||||
* Class Remote_Management
|
||||
*/
|
||||
class Remote_Management {
|
||||
use GHU_Trait;
|
||||
|
||||
/**
|
||||
* Holds the values for remote management settings.
|
||||
*
|
||||
* @var array $option_remote
|
||||
*/
|
||||
public static $options_remote;
|
||||
|
||||
/**
|
||||
* Supported remote management services.
|
||||
*
|
||||
* @var array $remote_management
|
||||
*/
|
||||
public static $remote_management = [
|
||||
'ithemes_sync' => 'iThemes Sync',
|
||||
'infinitewp' => 'InfiniteWP',
|
||||
'managewp' => 'ManageWP',
|
||||
'mainwp' => 'MainWP',
|
||||
];
|
||||
|
||||
/**
|
||||
* Holds the value for the Remote Management API key.
|
||||
*
|
||||
* @var string $api_key
|
||||
*/
|
||||
private static $api_key;
|
||||
|
||||
/**
|
||||
* Remote_Management constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->load_options();
|
||||
$this->ensure_api_key_is_set();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load site options.
|
||||
*/
|
||||
private function load_options() {
|
||||
self::$options_remote = get_site_option( 'github_updater_remote_management', [] );
|
||||
self::$api_key = get_site_option( 'github_updater_api_key' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure api key is set.
|
||||
*/
|
||||
public function ensure_api_key_is_set() {
|
||||
if ( ! self::$api_key ) {
|
||||
update_site_option( 'github_updater_api_key', md5( uniqid( \rand(), true ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load needed action/filter hooks.
|
||||
*/
|
||||
public function load_hooks() {
|
||||
add_action( 'admin_init', [ $this, 'remote_management_page_init' ] );
|
||||
add_action(
|
||||
'github_updater_update_settings',
|
||||
function ( $post_data ) {
|
||||
$this->save_settings( $post_data );
|
||||
}
|
||||
);
|
||||
add_filter( 'github_updater_add_admin_pages', [ $this, 'extra_admin_pages' ] );
|
||||
$this->add_settings_tabs();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return list of pages where GitHub Updater loads/runs.
|
||||
*
|
||||
* @param array $admin_pages Default list of pages where GitHub Updater loads.
|
||||
*
|
||||
* @return array $admin_pages
|
||||
*/
|
||||
public function extra_admin_pages( $admin_pages = [] ) {
|
||||
$extra_admin_pages = [];
|
||||
foreach ( array_keys( self::$remote_management ) as $key ) {
|
||||
if ( ! empty( self::$options_remote[ $key ] ) ) {
|
||||
$extra_admin_pages = [ 'index.php' ];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return array_merge( $admin_pages, $extra_admin_pages );
|
||||
}
|
||||
|
||||
/**
|
||||
* Save Remote Management settings.
|
||||
*
|
||||
* @uses 'github_updater_update_settings' action hook
|
||||
* @uses 'github_updater_save_redirect' filter hook
|
||||
*
|
||||
* @param array $post_data $_POST data.
|
||||
*/
|
||||
public function save_settings( $post_data ) {
|
||||
if ( isset( $post_data['option_page'] ) &&
|
||||
'github_updater_remote_management' === $post_data['option_page']
|
||||
) {
|
||||
$options = isset( $post_data['github_updater_remote_management'] )
|
||||
? $post_data['github_updater_remote_management']
|
||||
: [];
|
||||
|
||||
update_site_option( 'github_updater_remote_management', (array) $this->sanitize( $options ) );
|
||||
|
||||
add_filter(
|
||||
'github_updater_save_redirect',
|
||||
function ( $option_page ) {
|
||||
return array_merge( $option_page, [ 'github_updater_remote_management' ] );
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds Remote Management tab to Settings page.
|
||||
*/
|
||||
public function add_settings_tabs() {
|
||||
$install_tabs = [ 'github_updater_remote_management' => esc_html__( 'Remote Management', 'github-updater' ) ];
|
||||
add_filter(
|
||||
'github_updater_add_settings_tabs',
|
||||
function ( $tabs ) use ( $install_tabs ) {
|
||||
return array_merge( $tabs, $install_tabs );
|
||||
}
|
||||
);
|
||||
add_filter(
|
||||
'github_updater_add_admin_page',
|
||||
function ( $tab, $action ) {
|
||||
$this->add_admin_page( $tab, $action );
|
||||
},
|
||||
10,
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Settings page data via action hook.
|
||||
*
|
||||
* @uses 'github_updater_add_admin_page' action hook
|
||||
*
|
||||
* @param string $tab Tab name.
|
||||
* @param string $action Form action.
|
||||
*/
|
||||
public function add_admin_page( $tab, $action ) {
|
||||
if ( 'github_updater_remote_management' === $tab ) {
|
||||
$action = add_query_arg( 'tab', $tab, $action ); ?>
|
||||
<form class="settings" method="post" action="<?php esc_attr_e( $action ); ?>">
|
||||
<?php
|
||||
settings_fields( 'github_updater_remote_management' );
|
||||
do_settings_sections( 'github_updater_remote_settings' );
|
||||
submit_button();
|
||||
?>
|
||||
</form>
|
||||
<?php
|
||||
$reset_api_action = add_query_arg( [ 'github_updater_reset_api_key' => true ], $action );
|
||||
?>
|
||||
<form class="settings no-sub-tabs" method="post" action="<?php esc_attr_e( $reset_api_action ); ?>">
|
||||
<?php submit_button( esc_html__( 'Reset RESTful key', 'github-updater' ) ); ?>
|
||||
</form>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Settings for Remote Management.
|
||||
*/
|
||||
public function remote_management_page_init() {
|
||||
register_setting(
|
||||
'github_updater_remote_management',
|
||||
'github_updater_remote_settings',
|
||||
[ $this, 'sanitize' ]
|
||||
);
|
||||
|
||||
add_settings_section(
|
||||
'remote_management',
|
||||
esc_html__( 'Remote Management', 'github-updater' ),
|
||||
[ $this, 'print_section_remote_management' ],
|
||||
'github_updater_remote_settings'
|
||||
);
|
||||
|
||||
foreach ( self::$remote_management as $id => $name ) {
|
||||
add_settings_field(
|
||||
$id,
|
||||
null,
|
||||
[ $this, 'token_callback_checkbox_remote' ],
|
||||
'github_updater_remote_settings',
|
||||
'remote_management',
|
||||
[
|
||||
'id' => $id,
|
||||
'title' => esc_html( $name ),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the Remote Management text.
|
||||
*/
|
||||
public function print_section_remote_management() {
|
||||
if ( empty( self::$api_key ) ) {
|
||||
$this->load_options();
|
||||
}
|
||||
$api_url = add_query_arg(
|
||||
[
|
||||
'action' => 'github-updater-update',
|
||||
'key' => self::$api_key,
|
||||
],
|
||||
admin_url( 'admin-ajax.php' )
|
||||
);
|
||||
?>
|
||||
<p>
|
||||
<?php
|
||||
printf(
|
||||
wp_kses_post(
|
||||
/* translators: %s: Link to wiki */
|
||||
__( 'Please refer to the <a href="%s">wiki</a> for complete list of attributes. RESTful endpoints begin at:', 'github-updater' )
|
||||
),
|
||||
'https://github.com/afragen/github-updater/wiki/Remote-Management---RESTful-Endpoints'
|
||||
);
|
||||
?>
|
||||
<br>
|
||||
<span style="font-family:monospace;"><?php echo $api_url; ?></span>
|
||||
<p>
|
||||
<?php esc_html_e( 'Use of Remote Management services may result increase some page load speeds only for `admin` level users in the dashboard.', 'github-updater' ); ?>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the settings option array and print one of its values.
|
||||
* For remote management settings.
|
||||
*
|
||||
* @param array $args Checkbox args.
|
||||
*
|
||||
* @return bool|void
|
||||
*/
|
||||
public function token_callback_checkbox_remote( $args ) {
|
||||
$checked = isset( self::$options_remote[ $args['id'] ] ) ? self::$options_remote[ $args['id'] ] : null;
|
||||
?>
|
||||
<label for="<?php esc_attr_e( $args['id'] ); ?>">
|
||||
<input type="checkbox" id="<?php esc_attr_e( $args['id'] ); ?>" name="github_updater_remote_management[<?php esc_attr_e( $args['id'] ); ?>]" value="1" <?php checked( '1', $checked ); ?> >
|
||||
<?php echo $args['title']; ?>
|
||||
</label>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset RESTful API key.
|
||||
* Deleting site option will cause it to be re-created.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function reset_api_key() {
|
||||
if ( isset( $_REQUEST['tab'], $_REQUEST['github_updater_reset_api_key'] ) &&
|
||||
'github_updater_remote_management' === $_REQUEST['tab']
|
||||
) {
|
||||
$_POST = $_REQUEST;
|
||||
$_POST['_wp_http_referer'] = $_SERVER['HTTP_REFERER'];
|
||||
delete_site_option( 'github_updater_api_key' );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set site transients for 'update_plugins' and 'update_themes' for remote management.
|
||||
*
|
||||
* Only call if any remote management options are present and only if on a page specified
|
||||
* to run remote management.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_update_transients() {
|
||||
if ( empty( self::$options_remote ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$remote_management_pages = $this->extra_admin_pages();
|
||||
if ( $this->is_current_page( $remote_management_pages ) ) {
|
||||
add_filter( 'github_updater_add_admin_pages', [ $this, 'extra_admin_pages' ] );
|
||||
add_filter( 'site_transient_update_plugins', [ Singleton::get_instance( 'Plugin', $this ), 'update_site_transient' ], 10, 1 );
|
||||
add_filter( 'site_transient_update_themes', [ Singleton::get_instance( 'Theme', $this ), 'update_site_transient' ], 10, 1 );
|
||||
|
||||
Singleton::get_instance( 'Base', $this )->get_meta_remote_management();
|
||||
|
||||
$current_plugins = get_site_transient( 'update_plugins' );
|
||||
$current_themes = get_site_transient( 'update_themes' );
|
||||
set_site_transient( 'update_plugins', $current_plugins );
|
||||
set_site_transient( 'update_themes', $current_themes );
|
||||
|
||||
remove_filter( 'github_updater_add_admin_pages', [ $this, 'extra_admin_pages' ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
<?php
|
||||
/**
|
||||
* GitHub Updater
|
||||
*
|
||||
* @author Andy Fragen, Mikael Lindqvist
|
||||
* @license GPL-2.0+
|
||||
* @link https://github.com/afragen/github-updater
|
||||
* @package github-updater
|
||||
*/
|
||||
|
||||
namespace Fragen\GitHub_Updater;
|
||||
|
||||
use Fragen\Singleton;
|
||||
|
||||
/*
|
||||
* Exit if called directly.
|
||||
*/
|
||||
if ( ! defined( 'WPINC' ) ) {
|
||||
die;
|
||||
}
|
||||
|
||||
require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
|
||||
|
||||
/**
|
||||
* Class Rest_Update
|
||||
*
|
||||
* Updates a single plugin or theme, in a way suitable for rest requests.
|
||||
* This class inherits from Base in order to be able to call the
|
||||
* set_defaults function.
|
||||
*/
|
||||
class Rest_Update extends Base {
|
||||
/**
|
||||
* Holds REST Upgrader Skin.
|
||||
*
|
||||
* @var Rest_Upgrader_Skin $upgrader_skin
|
||||
*/
|
||||
protected $upgrader_skin;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
$this->load_options();
|
||||
$this->upgrader_skin = new Rest_Upgrader_Skin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update plugin.
|
||||
*
|
||||
* @param string $plugin_slug
|
||||
* @param string $tag
|
||||
*
|
||||
* @throws \UnexpectedValueException Plugin not found or not updatable.
|
||||
*/
|
||||
public function update_plugin( $plugin_slug, $tag = 'master' ) {
|
||||
$plugin = null;
|
||||
$is_plugin_active = false;
|
||||
|
||||
foreach ( (array) Singleton::get_instance( 'Plugin', $this )->get_plugin_configs() as $config_entry ) {
|
||||
if ( $config_entry->slug === $plugin_slug ) {
|
||||
$plugin = $config_entry;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $plugin ) {
|
||||
throw new \UnexpectedValueException( 'Plugin not found or not updatable with GitHub Updater: ' . $plugin_slug );
|
||||
}
|
||||
|
||||
if ( is_plugin_active( $plugin->file ) ) {
|
||||
$is_plugin_active = true;
|
||||
}
|
||||
|
||||
$this->get_remote_repo_meta( $plugin );
|
||||
$repo_api = Singleton::get_instance( 'API', $this )->get_repo_api( $plugin->git, $plugin );
|
||||
|
||||
$update = [
|
||||
'slug' => $plugin->slug,
|
||||
'plugin' => $plugin->file,
|
||||
'new_version' => null,
|
||||
'url' => $plugin->uri,
|
||||
'package' => $repo_api->construct_download_link( $tag ),
|
||||
];
|
||||
|
||||
add_filter(
|
||||
'site_transient_update_plugins',
|
||||
function ( $current ) use ( $plugin, $update ) {
|
||||
$current->response[ $plugin->file ] = (object) $update;
|
||||
|
||||
return $current;
|
||||
}
|
||||
);
|
||||
|
||||
$upgrader = new \Plugin_Upgrader( $this->upgrader_skin );
|
||||
$upgrader->upgrade( $plugin->file );
|
||||
|
||||
if ( $is_plugin_active ) {
|
||||
$activate = is_multisite() ? activate_plugin( $plugin->file, null, true ) : activate_plugin( $plugin->file );
|
||||
if ( ! $activate ) {
|
||||
$this->upgrader_skin->messages[] = 'Plugin reactivated successfully.';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a single theme.
|
||||
*
|
||||
* @param string $theme_slug
|
||||
* @param string $tag
|
||||
*
|
||||
* @throws \UnexpectedValueException Theme not found or not updatable.
|
||||
*/
|
||||
public function update_theme( $theme_slug, $tag = 'master' ) {
|
||||
$theme = null;
|
||||
|
||||
foreach ( (array) Singleton::get_instance( 'Theme', $this )->get_theme_configs() as $config_entry ) {
|
||||
if ( $config_entry->slug === $theme_slug ) {
|
||||
$theme = $config_entry;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $theme ) {
|
||||
throw new \UnexpectedValueException( 'Theme not found or not updatable with GitHub Updater: ' . $theme_slug );
|
||||
}
|
||||
|
||||
$this->get_remote_repo_meta( $theme );
|
||||
$repo_api = Singleton::get_instance( 'API', $this )->get_repo_api( $theme->git, $theme );
|
||||
|
||||
$update = [
|
||||
'theme' => $theme->slug,
|
||||
'new_version' => null,
|
||||
'url' => $theme->uri,
|
||||
'package' => $repo_api->construct_download_link( $tag ),
|
||||
];
|
||||
|
||||
add_filter(
|
||||
'site_transient_update_themes',
|
||||
function ( $current ) use ( $theme, $update ) {
|
||||
$current->response[ $theme->slug ] = $update;
|
||||
|
||||
return $current;
|
||||
}
|
||||
);
|
||||
|
||||
$upgrader = new \Theme_Upgrader( $this->upgrader_skin );
|
||||
$upgrader->upgrade( $theme->slug );
|
||||
}
|
||||
|
||||
/**
|
||||
* Is there an error?
|
||||
*/
|
||||
public function is_error() {
|
||||
return $this->upgrader_skin->error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get messages during update.
|
||||
*/
|
||||
public function get_messages() {
|
||||
return $this->upgrader_skin->messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process request.
|
||||
*
|
||||
* Relies on data in $_REQUEST, prints out json and exits.
|
||||
* If the request came through a webhook, and if the branch in the
|
||||
* webhook matches the branch specified by the url, use the latest
|
||||
* update available as specified in the webhook payload.
|
||||
*
|
||||
* @throws \UnexpectedValueException Under multiple bad or missing params.
|
||||
*/
|
||||
public function process_request() {
|
||||
$start = microtime( true );
|
||||
try {
|
||||
if ( ! isset( $_REQUEST['key'] ) ||
|
||||
get_site_option( 'github_updater_api_key' ) !== $_REQUEST['key']
|
||||
) {
|
||||
throw new \UnexpectedValueException( 'Bad API key.' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow access into the REST Update process.
|
||||
*
|
||||
* @since 7.6.0
|
||||
* @access public
|
||||
*/
|
||||
do_action( 'github_updater_pre_rest_process_request' );
|
||||
|
||||
$tag = 'master';
|
||||
if ( isset( $_REQUEST['tag'] ) ) {
|
||||
$tag = $_REQUEST['tag'];
|
||||
} elseif ( isset( $_REQUEST['committish'] ) ) {
|
||||
$tag = $_REQUEST['committish'];
|
||||
}
|
||||
|
||||
$this->get_webhook_source();
|
||||
$current_branch = $this->get_local_branch();
|
||||
$override = isset( $_REQUEST['override'] );
|
||||
if ( $tag !== $current_branch && ! $override ) {
|
||||
throw new \UnexpectedValueException( 'Webhook tag and current branch are not matching. Consider using `override` query arg.' );
|
||||
}
|
||||
|
||||
if ( isset( $_REQUEST['plugin'] ) ) {
|
||||
$this->update_plugin( $_REQUEST['plugin'], $tag );
|
||||
} elseif ( isset( $_REQUEST['theme'] ) ) {
|
||||
$this->update_theme( $_REQUEST['theme'], $tag );
|
||||
} else {
|
||||
throw new \UnexpectedValueException( 'No plugin or theme specified for update.' );
|
||||
}
|
||||
} catch ( \Exception $e ) {
|
||||
$http_response = [
|
||||
'success' => false,
|
||||
'messages' => $e->getMessage(),
|
||||
'webhook' => $_GET,
|
||||
'elapsed_time' => round( ( microtime( true ) - $start ) * 1000, 2 ) . ' ms',
|
||||
];
|
||||
$this->log_exit( $http_response, 417 );
|
||||
}
|
||||
|
||||
$response = [
|
||||
'success' => true,
|
||||
'messages' => $this->get_messages(),
|
||||
'webhook' => $_GET,
|
||||
'elapsed_time' => round( ( microtime( true ) - $start ) * 1000, 2 ) . ' ms',
|
||||
];
|
||||
|
||||
if ( $this->is_error() ) {
|
||||
$response['success'] = false;
|
||||
$this->log_exit( $response, 417 );
|
||||
}
|
||||
$this->log_exit( $response, 200 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current branch of the local repository referenced in the webhook.
|
||||
*
|
||||
* @return string $current_branch Default return is 'master'.
|
||||
*/
|
||||
private function get_local_branch() {
|
||||
$repo = false;
|
||||
if ( isset( $_REQUEST['plugin'] ) ) {
|
||||
$repos = Singleton::get_instance( 'Plugin', $this )->get_plugin_configs();
|
||||
$repo = isset( $repos[ $_REQUEST['plugin'] ] ) ? $repos[ $_REQUEST['plugin'] ] : false;
|
||||
}
|
||||
if ( isset( $_REQUEST['theme'] ) ) {
|
||||
$repos = Singleton::get_instance( 'Theme', $this )->get_theme_configs();
|
||||
$repo = isset( $repos[ $_REQUEST['theme'] ] ) ? $repos[ $_REQUEST['theme'] ] : false;
|
||||
}
|
||||
$current_branch = $repo ?
|
||||
Singleton::get_instance( 'Branch', $this )->get_current_branch( $repo ) :
|
||||
'master';
|
||||
|
||||
return $current_branch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the source of the webhook to $_GET variable.
|
||||
*/
|
||||
private function get_webhook_source() {
|
||||
switch ( $_SERVER ) {
|
||||
case isset( $_SERVER['HTTP_X_GITHUB_EVENT'] ):
|
||||
$webhook_source = 'GitHub webhook';
|
||||
break;
|
||||
case isset( $_SERVER['HTTP_X_EVENT_KEY'] ):
|
||||
$webhook_source = 'Bitbucket webhook';
|
||||
break;
|
||||
case isset( $_SERVER['HTTP_X_GITLAB_EVENT'] ):
|
||||
$webhook_source = 'GitLab webhook';
|
||||
break;
|
||||
case isset( $_SERVER['HTTP_X_GITEA_EVENT'] ):
|
||||
$webhook_source = 'Gitea webhook';
|
||||
break;
|
||||
default:
|
||||
$webhook_source = 'browser';
|
||||
break;
|
||||
}
|
||||
$_GET['webhook_source'] = $webhook_source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append $response to debug.log and wp_die().
|
||||
*
|
||||
* @param array $response
|
||||
* @param int $code
|
||||
*
|
||||
* 128 == JSON_PRETTY_PRINT
|
||||
* 64 == JSON_UNESCAPED_SLASHES
|
||||
*/
|
||||
private function log_exit( $response, $code ) {
|
||||
$json_encode_flags = 128 | 64;
|
||||
|
||||
error_log( json_encode( $response, $json_encode_flags ) );
|
||||
|
||||
/**
|
||||
* Action hook after processing REST process.
|
||||
*
|
||||
* @since 8.6.0
|
||||
*
|
||||
* @param array $response
|
||||
* @param int $code HTTP response.
|
||||
*/
|
||||
do_action( 'github_updater_post_rest_process_request', $response, $code );
|
||||
|
||||
unset( $response['success'] );
|
||||
if ( 200 === $code ) {
|
||||
wp_die( wp_send_json_success( $response, $code ) );
|
||||
} else {
|
||||
wp_die( wp_send_json_error( $response, $code ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
/**
|
||||
* GitHub Updater
|
||||
*
|
||||
* @author Andy Fragen, Mikael Lindqvist
|
||||
* @license GPL-2.0+
|
||||
* @link https://github.com/afragen/github-updater
|
||||
* @package github-updater
|
||||
*/
|
||||
|
||||
namespace Fragen\GitHub_Updater;
|
||||
|
||||
/*
|
||||
* Exit if called directly.
|
||||
*/
|
||||
if ( ! defined( 'WPINC' ) ) {
|
||||
die;
|
||||
}
|
||||
|
||||
require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
|
||||
|
||||
/**
|
||||
* Class Rest_Upgrader_Skin
|
||||
*
|
||||
* Extends WP_Upgrader_Skin and collects outputed messages for later
|
||||
* processing, rather than printing them out.
|
||||
*/
|
||||
class Rest_Upgrader_Skin extends \WP_Upgrader_Skin {
|
||||
/**
|
||||
* Holds messages.
|
||||
*
|
||||
* @var array $messages
|
||||
*/
|
||||
public $messages = [];
|
||||
|
||||
/**
|
||||
* Boolean if errors are present.
|
||||
*
|
||||
* @var bool $error
|
||||
*/
|
||||
public $error;
|
||||
|
||||
/**
|
||||
* Overrides the feedback method.
|
||||
* Adds the feedback string to the messages array.
|
||||
*
|
||||
* @param string $string
|
||||
*/
|
||||
public function feedback( $string ) {
|
||||
if ( isset( $this->upgrader->strings[ $string ] ) ) {
|
||||
$string = $this->upgrader->strings[ $string ];
|
||||
}
|
||||
|
||||
if ( false !== strpos( $string, '%' ) ) {
|
||||
$args = func_get_args();
|
||||
$args = array_splice( $args, 1 );
|
||||
if ( $args ) {
|
||||
$args = array_map( 'strip_tags', $args );
|
||||
$args = array_map( 'esc_html', $args );
|
||||
$string = vsprintf( $string, $args );
|
||||
}
|
||||
}
|
||||
if ( empty( $string ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->messages[] = $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the error flag to true, then let the base class handle the rest.
|
||||
*
|
||||
* @param mixed $errors
|
||||
*/
|
||||
public function error( $errors ) {
|
||||
$this->error = true;
|
||||
parent::error( $errors );
|
||||
}
|
||||
|
||||
/**
|
||||
* Do nothing.
|
||||
*
|
||||
* @param mixed $type
|
||||
*/
|
||||
protected function decrement_update_count( $type ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Do nothing.
|
||||
*/
|
||||
public function header() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Do nothing.
|
||||
*/
|
||||
public function footer() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,804 @@
|
||||
<?php
|
||||
/**
|
||||
* GitHub Updater
|
||||
*
|
||||
* @author Andy Fragen
|
||||
* @license GPL-2.0+
|
||||
* @link https://github.com/afragen/github-updater
|
||||
* @package github-updater
|
||||
*/
|
||||
|
||||
namespace Fragen\GitHub_Updater;
|
||||
|
||||
use Fragen\Singleton;
|
||||
use Fragen\GitHub_Updater\Traits\GHU_Trait;
|
||||
|
||||
/*
|
||||
* Exit if called directly.
|
||||
*/
|
||||
if ( ! defined( 'WPINC' ) ) {
|
||||
die;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class Settings
|
||||
*
|
||||
* Add a settings page.
|
||||
*
|
||||
* @author Andy Fragen
|
||||
*/
|
||||
class Settings extends Base {
|
||||
use GHU_Trait;
|
||||
|
||||
/**
|
||||
* Holds the plugin basename.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $ghu_plugin_name = 'github-updater/github-updater.php';
|
||||
|
||||
/**
|
||||
* Holds boolean on whether or not the repo requires authentication.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $auth_required = [
|
||||
'github_private' => false,
|
||||
'github_enterprise' => false,
|
||||
'bitbucket_private' => false,
|
||||
'bitbucket_server' => false,
|
||||
'gitlab_private' => false,
|
||||
'gitlab_enterprise' => false,
|
||||
'gitea_private' => false,
|
||||
];
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
$this->refresh_caches();
|
||||
$this->load_options();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for cache refresh.
|
||||
*/
|
||||
protected function refresh_caches() {
|
||||
if ( isset( $_POST['ghu_refresh_cache'] ) && ! ( $this instanceof Messages ) ) {
|
||||
$this->delete_all_cached_data();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Let's get going.
|
||||
*/
|
||||
public function run() {
|
||||
$this->load_hooks();
|
||||
|
||||
// Need to ensure these classes are activated here for hooks to fire.
|
||||
if ( $this->is_current_page( [ 'options.php', 'options-general.php', 'settings.php' ] ) ) {
|
||||
Singleton::get_instance( 'Install', $this )->run();
|
||||
Singleton::get_instance( 'Remote_Management', $this )->load_hooks();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load relevant action/filter hooks.
|
||||
*/
|
||||
protected function load_hooks() {
|
||||
add_action( is_multisite() ? 'network_admin_menu' : 'admin_menu', [ $this, 'add_plugin_page' ] );
|
||||
add_action( 'network_admin_edit_github-updater', [ $this, 'update_settings' ] );
|
||||
|
||||
add_filter(
|
||||
is_multisite()
|
||||
? 'network_admin_plugin_action_links_' . $this->ghu_plugin_name
|
||||
: 'plugin_action_links_' . $this->ghu_plugin_name,
|
||||
[ $this, 'plugin_action_links' ]
|
||||
);
|
||||
|
||||
if ( $this->is_current_page( [ 'options.php', 'options-general.php', 'settings.php', 'edit.php' ] ) ) {
|
||||
add_action( 'admin_init', [ $this, 'update_settings' ] );
|
||||
add_action( 'admin_init', [ $this, 'page_init' ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Define tabs for Settings page.
|
||||
* By defining in a method, strings can be translated.
|
||||
*
|
||||
* @access private
|
||||
* @return array
|
||||
*/
|
||||
private function settings_tabs() {
|
||||
$tabs = [ 'github_updater_settings' => esc_html__( 'Settings', 'github-updater' ) ];
|
||||
|
||||
/**
|
||||
* Filter settings tabs.
|
||||
*
|
||||
* @since 8.0.0
|
||||
*
|
||||
* @param array $tabs Array of default tabs.
|
||||
*/
|
||||
return apply_filters( 'github_updater_add_settings_tabs', $tabs );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the Settings Sub-tabs.
|
||||
*
|
||||
* @access private
|
||||
* @return array
|
||||
*/
|
||||
private function settings_sub_tabs() {
|
||||
$subtabs = [ 'github_updater' => esc_html__( 'GitHub Updater', 'github-updater' ) ];
|
||||
$gits = $this->get_running_git_servers();
|
||||
$gits[] = in_array( 'gitlabce', $gits, true ) ? 'gitlab' : null;
|
||||
$gits = array_unique( $gits );
|
||||
|
||||
$git_subtab = [];
|
||||
$ghu_subtabs = [];
|
||||
|
||||
/**
|
||||
* Filter subtabs to be able to add subtab from git API class.
|
||||
*
|
||||
* @since 8.0.0
|
||||
*
|
||||
* @param array $ghu_subtabs Array of added subtabs.
|
||||
*
|
||||
* @return array $subtabs Array of subtabs.
|
||||
*/
|
||||
$ghu_subtabs = apply_filters( 'github_updater_add_settings_subtabs', $ghu_subtabs );
|
||||
|
||||
foreach ( $gits as $git ) {
|
||||
if ( array_key_exists( $git, $ghu_subtabs ) ) {
|
||||
$git_subtab[ $git ] = $ghu_subtabs[ $git ];
|
||||
}
|
||||
}
|
||||
$subtabs = array_merge( $subtabs, $git_subtab );
|
||||
|
||||
return $subtabs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add options page.
|
||||
*/
|
||||
public function add_plugin_page() {
|
||||
$parent = is_multisite() ? 'settings.php' : 'options-general.php';
|
||||
$capability = is_multisite() ? 'manage_network' : 'manage_options';
|
||||
|
||||
add_submenu_page(
|
||||
$parent,
|
||||
esc_html__( 'GitHub Updater Settings', 'github-updater' ),
|
||||
esc_html__( 'GitHub Updater', 'github-updater' ),
|
||||
$capability,
|
||||
'github-updater',
|
||||
[ $this, 'create_admin_page' ]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders setting tabs.
|
||||
*
|
||||
* Walks through the object's tabs array and prints them one by one.
|
||||
* Provides the heading for the settings page.
|
||||
*
|
||||
* @access private
|
||||
*/
|
||||
private function options_tabs() {
|
||||
$current_tab = isset( $_GET['tab'] ) ? esc_attr( $_GET['tab'] ) : 'github_updater_settings';
|
||||
echo '<nav class="nav-tab-wrapper" aria-label="Secondary menu">';
|
||||
foreach ( $this->settings_tabs() as $key => $name ) {
|
||||
$active = ( $current_tab === $key ) ? 'nav-tab-active' : '';
|
||||
echo '<a class="nav-tab ' . $active . '" href="?page=github-updater&tab=' . $key . '">' . $name . '</a>';
|
||||
}
|
||||
echo '</nav>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the settings sub-tabs.
|
||||
*
|
||||
* @access private
|
||||
*/
|
||||
private function options_sub_tabs() {
|
||||
$current_tab = isset( $_GET['subtab'] ) ? esc_attr( $_GET['subtab'] ) : 'github_updater';
|
||||
echo '<nav class="nav-tab-wrapper" aria-label="Tertiary menu">';
|
||||
foreach ( $this->settings_sub_tabs() as $key => $name ) {
|
||||
$active = ( $current_tab === $key ) ? 'nav-tab-active' : '';
|
||||
echo '<a class="nav-tab ' . $active . '" href="?page=github-updater&tab=github_updater_settings&subtab=' . $key . '">' . $name . '</a>';
|
||||
}
|
||||
echo '</nav>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Options page callback.
|
||||
*/
|
||||
public function create_admin_page() {
|
||||
$action = is_multisite() ? 'edit.php?action=github-updater' : 'options.php';
|
||||
$tab = isset( $_GET['tab'] ) ? esc_attr( $_GET['tab'] ) : 'github_updater_settings';
|
||||
$subtab = isset( $_GET['subtab'] ) ? esc_attr( $_GET['subtab'] ) : 'github_updater';
|
||||
$logo = plugins_url( basename( GITHUB_UPDATER_DIR ) . '/assets/GitHub_Updater_logo_small.png' ); ?>
|
||||
<div class="wrap github-updater-settings">
|
||||
<h1>
|
||||
<a href="https://github.com/afragen/github-updater" target="_blank"><img src="<?php esc_attr_e( $logo ); ?>" alt="GitHub Updater logo" /></a><br>
|
||||
<?php esc_html_e( 'GitHub Updater', 'github-updater' ); ?>
|
||||
</h1>
|
||||
<?php $this->options_tabs(); ?>
|
||||
<?php $this->admin_page_notices(); ?>
|
||||
<?php if ( 'github_updater_settings' === $tab ) : ?>
|
||||
<?php $this->options_sub_tabs(); ?>
|
||||
<form class="settings" method="post" action="<?php esc_attr_e( $action ); ?>">
|
||||
<?php
|
||||
settings_fields( 'github_updater' );
|
||||
if ( 'github_updater' === $subtab ) {
|
||||
do_settings_sections( 'github_updater_install_settings' );
|
||||
$this->add_hidden_settings_sections();
|
||||
} else {
|
||||
do_settings_sections( 'github_updater_' . $subtab . '_install_settings' );
|
||||
$this->display_ghu_repos( $subtab );
|
||||
$this->add_hidden_settings_sections( $subtab );
|
||||
}
|
||||
submit_button();
|
||||
?>
|
||||
</form>
|
||||
<?php $refresh_transients = add_query_arg( [ 'github_updater_refresh_transients' => true ], $action ); ?>
|
||||
<form class="settings" method="post" action="<?php esc_attr_e( $refresh_transients ); ?>">
|
||||
<?php submit_button( esc_html__( 'Refresh Cache', 'github-updater' ), 'primary', 'ghu_refresh_cache' ); ?>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
/**
|
||||
* Action hook to add admin page data to appropriate $tab.
|
||||
*
|
||||
* @since 8.0.0
|
||||
*
|
||||
* @param string $tab Name of tab.
|
||||
* @param string $action Save action for appropriate WordPress installation.
|
||||
* Single site or Multisite.
|
||||
*/
|
||||
do_action( 'github_updater_add_admin_page', $tab, $action );
|
||||
?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Display appropriate notice for Settings page actions.
|
||||
*/
|
||||
private function admin_page_notices() {
|
||||
$display = ( isset( $_GET['updated'] ) && is_multisite() )
|
||||
|| isset( $_GET['reset'] )
|
||||
|| isset( $_GET['refresh_transients'] );
|
||||
|
||||
if ( $display ) {
|
||||
echo '<div class="updated"><p>';
|
||||
}
|
||||
if ( ( isset( $_GET['updated'] ) && '1' === $_GET['updated'] ) && is_multisite() ) {
|
||||
esc_html_e( 'Settings saved.', 'github-updater' );
|
||||
} elseif ( isset( $_GET['reset'] ) && '1' === $_GET['reset'] ) {
|
||||
esc_html_e( 'RESTful key reset.', 'github-updater' );
|
||||
} elseif ( isset( $_GET['refresh_transients'] ) && '1' === $_GET['refresh_transients'] ) {
|
||||
esc_html_e( 'Cache refreshed.', 'github-updater' );
|
||||
}
|
||||
if ( $display ) {
|
||||
echo '</p></div>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register and add settings.
|
||||
* Check to see if it's a private repo.
|
||||
*/
|
||||
public function page_init() {
|
||||
if ( static::is_doing_ajax() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
register_setting(
|
||||
'github_updater',
|
||||
'github_updater',
|
||||
[ $this, 'sanitize' ]
|
||||
);
|
||||
|
||||
$this->ghu_tokens();
|
||||
|
||||
/*
|
||||
* Add basic plugin settings.
|
||||
*/
|
||||
add_settings_section(
|
||||
'github_updater_settings',
|
||||
esc_html__( 'GitHub Updater Settings', 'github-updater' ),
|
||||
[ $this, 'print_section_ghu_settings' ],
|
||||
'github_updater_install_settings'
|
||||
);
|
||||
|
||||
add_settings_field(
|
||||
'branch_switch',
|
||||
null,
|
||||
[ $this, 'token_callback_checkbox' ],
|
||||
'github_updater_install_settings',
|
||||
'github_updater_settings',
|
||||
[
|
||||
'id' => 'branch_switch',
|
||||
'title' => esc_html__( 'Enable Branch Switching', 'github-updater' ),
|
||||
]
|
||||
);
|
||||
|
||||
/**
|
||||
* Hook to add Git API settings.
|
||||
*
|
||||
* @since 8.0.0
|
||||
*
|
||||
* @param array $auth_required Array containing authorization needs of git APIs.
|
||||
*/
|
||||
do_action( 'github_updater_add_settings', static::$auth_required );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return settings fields for private repositories.
|
||||
*/
|
||||
public function ghu_tokens() {
|
||||
$ghu_options_keys = [];
|
||||
$ghu_plugins = Singleton::get_instance( 'Plugin', $this )->get_plugin_configs();
|
||||
$ghu_themes = Singleton::get_instance( 'Theme', $this )->get_theme_configs();
|
||||
$ghu_tokens = array_merge( $ghu_plugins, $ghu_themes );
|
||||
|
||||
foreach ( $ghu_tokens as $token ) {
|
||||
$type = '<span class="dashicons dashicons-admin-plugins"></span> ';
|
||||
$setting_field = [];
|
||||
$ghu_options_keys[ $token->slug ] = null;
|
||||
|
||||
/*
|
||||
* Check to see if it's a private repo or Enterprise and set variables.
|
||||
*/
|
||||
$this->set_auth_required( $token );
|
||||
|
||||
/*
|
||||
* Next if not a private repo or token field not empty.
|
||||
*/
|
||||
if ( ! $this->is_private( $token ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( 'theme' === $token->type ) {
|
||||
$type = '<span class="dashicons dashicons-admin-appearance"></span> ';
|
||||
}
|
||||
|
||||
$setting_field['id'] = $token->slug;
|
||||
$setting_field['title'] = $type . esc_html( $token->name );
|
||||
|
||||
$repo_setting_field = apply_filters( 'github_updater_add_repo_setting_field', [], $token, $token->git );
|
||||
|
||||
if ( empty( $repo_setting_field ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$setting_field = array_merge( $setting_field, $repo_setting_field );
|
||||
$setting_field['callback'] = $token->slug;
|
||||
|
||||
$title = 'token_callback_checkbox' !== $setting_field['callback_method'][1] ? $setting_field['title'] : null;
|
||||
add_settings_field(
|
||||
$setting_field['id'],
|
||||
$title,
|
||||
$setting_field['callback_method'],
|
||||
$setting_field['page'],
|
||||
$setting_field['section'],
|
||||
[
|
||||
'id' => $setting_field['callback'],
|
||||
'token' => true,
|
||||
'title' => $setting_field['title'],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! $this->waiting_for_background_update() ) {
|
||||
$this->unset_stale_options( $ghu_options_keys, $ghu_tokens );
|
||||
} else {
|
||||
Singleton::get_instance( 'Messages', $this )->create_error_message( 'waiting' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check current saved options and unset if repos not present.
|
||||
*
|
||||
* @param array $ghu_options_keys
|
||||
* @param array $ghu_tokens
|
||||
*/
|
||||
public function unset_stale_options( $ghu_options_keys, $ghu_tokens ) {
|
||||
$running_servers = $this->get_running_git_servers();
|
||||
$ghu_unset_keys = array_diff_key( static::$options, $ghu_options_keys );
|
||||
$always_unset = [
|
||||
'db_version',
|
||||
'branch_switch',
|
||||
'github_access_token',
|
||||
'github_enterprise_token',
|
||||
];
|
||||
|
||||
if ( in_array( 'bitbucket', $running_servers, true ) ) {
|
||||
$always_unset = array_merge(
|
||||
$always_unset,
|
||||
[
|
||||
'bitbucket_username',
|
||||
'bitbucket_password',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
if ( in_array( 'bbserver', $running_servers, true ) ) {
|
||||
$always_unset = array_merge(
|
||||
$always_unset,
|
||||
[
|
||||
'bitbucket_server_username',
|
||||
'bitbucket_server_password',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
array_map(
|
||||
function ( $e ) use ( &$ghu_unset_keys ) {
|
||||
unset( $ghu_unset_keys[ $e ] );
|
||||
},
|
||||
$always_unset
|
||||
);
|
||||
|
||||
$auth_required = static::$auth_required;
|
||||
$auth_required_unset = [
|
||||
'github_enterprise' => 'github_enterprise_token',
|
||||
'gitlab' => 'gitlab_access_token',
|
||||
'gitlab_enterprise' => 'gitlab_enterprise_token',
|
||||
'gitea' => 'gitea_access_token',
|
||||
];
|
||||
|
||||
array_map(
|
||||
function ( $e ) use ( &$ghu_unset_keys, $auth_required, $auth_required_unset ) {
|
||||
$key = array_search( $e, $auth_required_unset, true );
|
||||
if ( $auth_required[ $key ] ) {
|
||||
unset( $ghu_unset_keys[ $e ] );
|
||||
}
|
||||
},
|
||||
$auth_required_unset
|
||||
);
|
||||
|
||||
// Unset if current_branch AND if associated with repo.
|
||||
array_map(
|
||||
function ( $e ) use ( &$ghu_unset_keys, $ghu_tokens, &$reset_keys ) {
|
||||
$key = array_search( $e, $ghu_unset_keys, true );
|
||||
$repo = str_replace( 'current_branch_', '', $key );
|
||||
if ( array_key_exists( $key, $ghu_unset_keys )
|
||||
&& false !== strpos( $key, 'current_branch' )
|
||||
) {
|
||||
unset( $ghu_unset_keys[ $key ] );
|
||||
}
|
||||
if ( ! array_key_exists( $repo, $ghu_tokens ) ) {
|
||||
$reset_keys[ $key ] = $e;
|
||||
}
|
||||
},
|
||||
$ghu_unset_keys
|
||||
);
|
||||
$ghu_unset_keys = array_merge( $ghu_unset_keys, (array) $reset_keys );
|
||||
|
||||
if ( ! empty( $ghu_unset_keys ) ) {
|
||||
foreach ( $ghu_unset_keys as $key => $value ) {
|
||||
unset( static::$options[ $key ] );
|
||||
}
|
||||
update_site_option( 'github_updater', static::$options );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if it's an enterprise or private repo and set variables.
|
||||
*
|
||||
* @param \stdClass $token Repo data.
|
||||
*/
|
||||
private function set_auth_required( $token ) {
|
||||
// Set booleans for Enterprise repos.
|
||||
if ( $token->enterprise ) {
|
||||
static::$auth_required['github_enterprise'] = static::$auth_required['github_enterprise']
|
||||
?: 'github' === $token->git;
|
||||
static::$auth_required['gitlab_enterprise'] = static::$auth_required['gitlab_enterprise']
|
||||
?: 'gitlab' === $token->git;
|
||||
static::$auth_required['bitbucket_server'] = static::$auth_required['bitbucket_server']
|
||||
?: 'bitbucket' === $token->git;
|
||||
}
|
||||
|
||||
// Set booleans for private repos.
|
||||
if ( $this->is_private( $token ) ) {
|
||||
static::$auth_required['github_private'] = static::$auth_required['github_private']
|
||||
?: 'github' === $token->git;
|
||||
static::$auth_required['bitbucket_private'] = static::$auth_required['bitbucket_private']
|
||||
?: 'bitbucket' === $token->git;
|
||||
static::$auth_required['gitlab_private'] = static::$auth_required['gitlab_private']
|
||||
?: 'gitlab' === $token->git;
|
||||
static::$auth_required['gitea_private'] = static::$auth_required['gitea_private']
|
||||
?: 'gitea' === $token->git;
|
||||
}
|
||||
|
||||
// Always set to true.
|
||||
static::$auth_required['gitlab'] = true;
|
||||
static::$auth_required['gitea'] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the GitHub Updater Settings text.
|
||||
*/
|
||||
public function print_section_ghu_settings() {
|
||||
$this->display_dot_org_overrides();
|
||||
echo '<p>' . esc_html__( 'Check to enable branch switching from the Plugins or Themes page.', 'github-updater' ) . '</p>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Display plugins/themes that are overridden using the filter hook.
|
||||
*
|
||||
* @uses `github_updater_override_dot_org` filter hook
|
||||
* @return void
|
||||
*/
|
||||
private function display_dot_org_overrides() {
|
||||
$plugins = Singleton::get_instance( 'Plugin', $this )->get_plugin_configs();
|
||||
$themes = Singleton::get_instance( 'Theme', $this )->get_theme_configs();
|
||||
$dashicon_plugin = '<span class="dashicons dashicons-admin-plugins"></span> ';
|
||||
$dashicon_theme = '<span class="dashicons dashicons-admin-appearance"></span> ';
|
||||
|
||||
/**
|
||||
* Filter to return array of overrides to dot org.
|
||||
*
|
||||
* @since 8.5.0
|
||||
* @return array
|
||||
*/
|
||||
$overrides = apply_filters( 'github_updater_override_dot_org', [] );
|
||||
|
||||
if ( ! empty( $overrides ) ) {
|
||||
echo '<h4>' . esc_html__( 'Overridden Plugins and Themes', 'github-updater' ) . '</h4>';
|
||||
echo '<p>' . esc_html__( 'The following plugins or themes might exist on wp.org, but any updates will be downloaded from their respective git repositories.', 'github-updater' ) . '</p>';
|
||||
|
||||
foreach ( $plugins as $plugin ) {
|
||||
if ( in_array( $plugin->file, $overrides, true ) ) {
|
||||
echo '<p>' . $dashicon_plugin . $plugin->name . '</p>';
|
||||
}
|
||||
}
|
||||
foreach ( $themes as $theme ) {
|
||||
if ( in_array( $theme->slug, $overrides, true ) ) {
|
||||
echo '<p>' . $dashicon_theme . $theme->name . '</p>';
|
||||
}
|
||||
}
|
||||
echo '<br>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the settings option array and print one of its values.
|
||||
*
|
||||
* @param array $args
|
||||
*/
|
||||
public function token_callback_text( $args ) {
|
||||
$name = isset( static::$options[ $args['id'] ] ) ? esc_attr( static::$options[ $args['id'] ] ) : '';
|
||||
$type = isset( $args['token'] ) ? 'password' : 'text';
|
||||
?>
|
||||
<label for="<?php esc_attr( $args['id'] ); ?>">
|
||||
<input class="ghu-callback-text" type="<?php esc_attr_e( $type ); ?>" id="<?php esc_attr( $args['id'] ); ?>" name="github_updater[<?php esc_attr_e( $args['id'] ); ?>]" value="<?php esc_attr_e( $name ); ?>">
|
||||
</label>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the settings option array and print one of its values.
|
||||
*
|
||||
* @param array $args
|
||||
*/
|
||||
public function token_callback_checkbox( $args ) {
|
||||
$checked = isset( static::$options[ $args['id'] ] ) ? static::$options[ $args['id'] ] : null;
|
||||
?>
|
||||
<label for="<?php esc_attr_e( $args['id'] ); ?>">
|
||||
<input type="checkbox" id="<?php esc_attr_e( $args['id'] ); ?>" name="github_updater[<?php esc_attr_e( $args['id'] ); ?>]" value="1" <?php checked( '1', $checked ); ?> >
|
||||
<?php echo $args['title']; ?>
|
||||
</label>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Update settings for single site or network activated.
|
||||
*
|
||||
* @link http://wordpress.stackexchange.com/questions/64968/settings-api-in-multisite-missing-update-message
|
||||
* @link http://benohead.com/wordpress-network-wide-plugin-settings/
|
||||
*/
|
||||
public function update_settings() {
|
||||
if ( isset( $_POST['option_page'] ) &&
|
||||
'github_updater' === $_POST['option_page']
|
||||
) {
|
||||
$options = $this->filter_options();
|
||||
update_site_option( 'github_updater', $this->sanitize( $options ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Save $options in add-on classes.
|
||||
*
|
||||
* @since 8.0.0
|
||||
*/
|
||||
do_action( 'github_updater_update_settings', $_POST );
|
||||
|
||||
$this->redirect_on_save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter options to remove unchecked checkbox options.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @return array|mixed
|
||||
*/
|
||||
private function filter_options() {
|
||||
$options = static::$options;
|
||||
|
||||
// Remove checkbox options, only after background update complete.
|
||||
if ( ! $this->waiting_for_background_update() ) {
|
||||
$options = array_filter(
|
||||
$options,
|
||||
function ( $e ) {
|
||||
return '1' !== $e;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
$options = array_merge( $options, $_POST['github_updater'] );
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect to correct Settings tab on Save.
|
||||
*/
|
||||
protected function redirect_on_save() {
|
||||
$update = false;
|
||||
$refresh_transients = $this->refresh_transients();
|
||||
$reset_api_key = Singleton::get_instance( 'Remote_Management', $this )->reset_api_key();
|
||||
|
||||
/**
|
||||
* Filter to add to $option_page array.
|
||||
*
|
||||
* @since 8.0.0
|
||||
* @return array
|
||||
*/
|
||||
$option_page = apply_filters( 'github_updater_save_redirect', [ 'github_updater' ] );
|
||||
|
||||
if ( ( isset( $_POST['action'] ) && 'update' === $_POST['action'] ) &&
|
||||
( isset( $_POST['option_page'] ) && in_array( $_POST['option_page'], $option_page, true ) )
|
||||
) {
|
||||
$update = true;
|
||||
}
|
||||
|
||||
$redirect_url = is_multisite() ? network_admin_url( 'settings.php' ) : admin_url( 'options-general.php' );
|
||||
|
||||
if ( $update || $refresh_transients || $reset_api_key ) {
|
||||
$query = isset( $_POST['_wp_http_referer'] ) ? parse_url( $_POST['_wp_http_referer'], PHP_URL_QUERY ) : null;
|
||||
parse_str( $query, $arr );
|
||||
$arr['tab'] = ! empty( $arr['tab'] ) ? $arr['tab'] : 'github_updater_settings';
|
||||
$arr['subtab'] = ! empty( $arr['subtab'] ) ? $arr['subtab'] : 'github_updater';
|
||||
|
||||
$location = add_query_arg(
|
||||
[
|
||||
'page' => 'github-updater',
|
||||
'tab' => $arr['tab'],
|
||||
'subtab' => $arr['subtab'],
|
||||
'refresh_transients' => $refresh_transients,
|
||||
'reset' => $reset_api_key,
|
||||
'updated' => $update,
|
||||
],
|
||||
$redirect_url
|
||||
);
|
||||
wp_safe_redirect( $location );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear GitHub Updater transients.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function refresh_transients() {
|
||||
if ( isset( $_REQUEST['github_updater_refresh_transients'] ) ) {
|
||||
$_POST = $_REQUEST;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add setting link to plugin page.
|
||||
* Applied to the list of links to display on the plugins page (beside the activate/deactivate links).
|
||||
*
|
||||
* @link http://codex.wordpress.org/Plugin_API/Filter_Reference/plugin_action_links_(plugin_file_name)
|
||||
*
|
||||
* @param array $links
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function plugin_action_links( $links ) {
|
||||
$settings_page = is_multisite() ? 'settings.php' : 'options-general.php';
|
||||
$link = [ '<a href="' . esc_url( network_admin_url( $settings_page ) ) . '?page=github-updater">' . esc_html__( 'Settings', 'github-updater' ) . '</a>' ];
|
||||
|
||||
return array_merge( $links, $link );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create settings sections that are hidden.
|
||||
* Required to preserve subtab settings during saves.
|
||||
*
|
||||
* @param array $subtab Subtab to display.
|
||||
*/
|
||||
private function add_hidden_settings_sections( $subtab = [] ) {
|
||||
$subtabs = array_keys( $this->settings_sub_tabs() );
|
||||
$hide_tabs = array_diff( $subtabs, (array) $subtab, [ 'github_updater' ] );
|
||||
if ( ! empty( $subtab ) ) {
|
||||
echo '<div id="github_updater" class="hide-github-updater-settings">';
|
||||
do_settings_sections( 'github_updater_install_settings' );
|
||||
echo '</div>';
|
||||
}
|
||||
foreach ( $hide_tabs as $hide_tab ) {
|
||||
echo '<div id="' . $hide_tab . '" class="hide-github-updater-settings">';
|
||||
do_settings_sections( 'github_updater_' . $hide_tab . '_install_settings' );
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write out listing of installed plugins and themes using GitHub Updater.
|
||||
* Places a lock dashicon after the repo name if it's a private repo.
|
||||
* Places a WordPress dashicon after the repo name if it's in dot org.
|
||||
*
|
||||
* @param string $git (github|bitbucket|bbserver|gitlab|gitea)
|
||||
*/
|
||||
private function display_ghu_repos( $git ) {
|
||||
$lock_title = esc_html__( 'This is a private repository.', 'github-updater' );
|
||||
$broken_title = esc_html__( 'This repository has not connected to the API or was unable to connect.', 'github-updater' );
|
||||
$dot_org_title = esc_html__( 'This repository is hosted on WordPress.org.', 'github-updater' );
|
||||
|
||||
$plugins = Singleton::get_instance( 'Plugin', $this )->get_plugin_configs();
|
||||
$themes = Singleton::get_instance( 'Theme', $this )->get_theme_configs();
|
||||
$repos = array_merge( $plugins, $themes );
|
||||
$bbserver = [ 'bitbucket', 'bbserver' ];
|
||||
|
||||
$type_repos = array_filter(
|
||||
$repos,
|
||||
function ( $e ) use ( $git, $bbserver ) {
|
||||
if ( ! empty( $e->enterprise ) && in_array( $git, $bbserver, true ) ) {
|
||||
return false !== stripos( $e->git, 'bitbucket' ) && 'bbserver' === $git;
|
||||
}
|
||||
|
||||
return false !== stripos( $e->git, $git );
|
||||
}
|
||||
);
|
||||
|
||||
$display_data = array_map(
|
||||
function ( $e ) {
|
||||
return [
|
||||
'type' => $e->type,
|
||||
'slug' => $e->slug,
|
||||
'file' => isset( $e->file ) ? $e->file : $e->slug,
|
||||
'branch' => $e->branch,
|
||||
'name' => $e->name,
|
||||
'private' => isset( $e->is_private ) ? $e->is_private : false,
|
||||
'broken' => ! isset( $e->remote_version ) || '0.0.0' === $e->remote_version,
|
||||
'dot_org' => isset( $e->dot_org ) ? $e->dot_org : false,
|
||||
];
|
||||
},
|
||||
$type_repos
|
||||
);
|
||||
|
||||
$lock = ' <span title="' . $lock_title . '" class="dashicons dashicons-lock"></span>';
|
||||
$broken = ' <span title="' . $broken_title . '" style="color:#f00;" class="dashicons dashicons-warning"></span>';
|
||||
$dot_org = ' <span title="' . $dot_org_title . '" class="dashicons dashicons-wordpress"></span></span>';
|
||||
printf( '<h2>' . esc_html__( 'Installed Plugins and Themes', 'github-updater' ) . '</h2>' );
|
||||
foreach ( $display_data as $data ) {
|
||||
$dashicon = false !== strpos( $data['type'], 'theme' )
|
||||
? '<span class="dashicons dashicons-admin-appearance"></span> '
|
||||
: '<span class="dashicons dashicons-admin-plugins"></span> ';
|
||||
$is_private = $data['private'] ? $lock : null;
|
||||
$is_broken = $data['broken'] ? $broken : null;
|
||||
$override = $this->override_dot_org( $data['type'], $data );
|
||||
$is_dot_org = $data['dot_org'] && ! $override ? $dot_org : null;
|
||||
printf( '<p>' . $dashicon . $data['name'] . $is_private . $is_dot_org . $is_broken . '</p>' );
|
||||
}
|
||||
}
|
||||
}
|
||||
676
wp-content/plugins/github-updater/src/GitHub_Updater/Theme.php
Normal file
676
wp-content/plugins/github-updater/src/GitHub_Updater/Theme.php
Normal file
@@ -0,0 +1,676 @@
|
||||
<?php
|
||||
/**
|
||||
* GitHub Updater
|
||||
*
|
||||
* @author Andy Fragen
|
||||
* @license GPL-2.0+
|
||||
* @link https://github.com/afragen/github-updater
|
||||
* @package github-updater
|
||||
*/
|
||||
|
||||
namespace Fragen\GitHub_Updater;
|
||||
|
||||
use Fragen\Singleton;
|
||||
|
||||
/*
|
||||
* Exit if called directly.
|
||||
*/
|
||||
if ( ! defined( 'WPINC' ) ) {
|
||||
die;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class Theme
|
||||
*
|
||||
* Update a WordPress theme from a GitHub repo.
|
||||
*
|
||||
* @author Andy Fragen
|
||||
* @author Seth Carstens
|
||||
* @link https://github.com/WordPress-Phoenix/whitelabel-framework
|
||||
* @author UCF Web Communications
|
||||
* @link https://github.com/UCF/Theme-Updater
|
||||
*/
|
||||
class Theme extends Base {
|
||||
/**
|
||||
* Rollback variable.
|
||||
*
|
||||
* @var number
|
||||
*/
|
||||
protected $tag = false;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
$this->load_options();
|
||||
|
||||
// Get details of installed git sourced themes.
|
||||
$this->config = $this->get_theme_meta();
|
||||
|
||||
if ( null === $this->config ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of configurations for the known themes.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_theme_configs() {
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete cache of current theme.
|
||||
* This is needed in case `wp_get_theme()` is called in earlier or in a mu-plugin.
|
||||
* This action results in the extra headers not being added.
|
||||
*
|
||||
* @link https://github.com/afragen/github-updater/issues/586
|
||||
*/
|
||||
private function delete_current_theme_cache() {
|
||||
$cache_hash = md5( get_stylesheet_directory() );
|
||||
wp_cache_delete( 'theme-' . $cache_hash, 'themes' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads in WP_Theme class of each theme.
|
||||
* Populates variable array.
|
||||
*
|
||||
* @return array Indexed array of associative arrays of theme details.
|
||||
*/
|
||||
protected function get_theme_meta() {
|
||||
$this->delete_current_theme_cache();
|
||||
$git_themes = [];
|
||||
$themes = wp_get_themes( [ 'errors' => null ] );
|
||||
|
||||
/**
|
||||
* Filter to add themes not containing appropriate header line.
|
||||
*
|
||||
* @since 5.4.0
|
||||
* @access public
|
||||
*
|
||||
* @param array $additions Listing of themes to add.
|
||||
* Default null.
|
||||
* @param array $themes Listing of all themes.
|
||||
* @param string 'theme' Type being passed.
|
||||
*/
|
||||
$additions = apply_filters( 'github_updater_additions', null, $themes, 'theme' );
|
||||
|
||||
foreach ( (array) $themes as $theme ) {
|
||||
$git_theme = [];
|
||||
|
||||
foreach ( (array) static::$extra_headers as $value ) {
|
||||
$header = null;
|
||||
$repo_uri = $theme->get( $value );
|
||||
|
||||
/**
|
||||
* Get $repo_uri from themes added to GitHub Updater via hook.
|
||||
*/
|
||||
foreach ( (array) $additions as $addition ) {
|
||||
if ( $theme->stylesheet === $addition['slug'] ) {
|
||||
if ( ! empty( $addition[ $value ] ) ) {
|
||||
$repo_uri = $addition[ $value ];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( empty( $repo_uri ) || false === stripos( $value, 'Theme' ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$header_parts = explode( ' ', $value );
|
||||
$repo_parts = $this->get_repo_parts( $header_parts[0], 'theme' );
|
||||
|
||||
if ( $repo_parts['bool'] ) {
|
||||
$header = $this->parse_header_uri( $repo_uri );
|
||||
if ( empty( $header ) || $theme->stylesheet !== $header['repo'] ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$header = $this->parse_extra_headers( $header, $theme, $header_parts, $repo_parts );
|
||||
$current_branch = "current_branch_{$header['repo']}";
|
||||
$branch = isset( static::$options[ $current_branch ] )
|
||||
? static::$options[ $current_branch ]
|
||||
: false;
|
||||
|
||||
$git_theme['type'] = 'theme';
|
||||
$git_theme['git'] = $repo_parts['git_server'];
|
||||
$git_theme['uri'] = "{$header['base_uri']}/{$header['owner_repo']}";
|
||||
$git_theme['enterprise'] = $header['enterprise_uri'];
|
||||
$git_theme['enterprise_api'] = $header['enterprise_api'];
|
||||
$git_theme['owner'] = $header['owner'];
|
||||
$git_theme['slug'] = $header['repo'];
|
||||
$git_theme['file'] = "{$header['repo']}/style.css";
|
||||
$git_theme['name'] = $theme->get( 'Name' );
|
||||
$git_theme['theme_uri'] = $theme->get( 'ThemeURI' );
|
||||
$git_theme['homepage'] = $theme->get( 'ThemeURI' );
|
||||
$git_theme['author'] = $theme->get( 'Author' );
|
||||
$git_theme['local_version'] = strtolower( $theme->get( 'Version' ) );
|
||||
$git_theme['sections']['description'] = $theme->get( 'Description' );
|
||||
$git_theme['local_path'] = get_theme_root() . '/' . $git_theme['slug'] . '/';
|
||||
$git_theme['branch'] = $branch ?: 'master';
|
||||
$git_theme['languages'] = $header['languages'];
|
||||
$git_theme['ci_job'] = $header['ci_job'];
|
||||
$git_theme['release_asset'] = $header['release_asset'];
|
||||
$git_theme['broken'] = ( empty( $header['owner'] ) || empty( $header['repo'] ) );
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// Exit if not git hosted theme.
|
||||
if ( empty( $git_theme ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$git_themes[ $git_theme['slug'] ] = (object) $git_theme;
|
||||
}
|
||||
|
||||
return $git_themes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get remote theme meta to populate $config theme objects.
|
||||
* Calls to remote APIs to get data.
|
||||
*/
|
||||
public function get_remote_theme_meta() {
|
||||
$themes = [];
|
||||
foreach ( (array) $this->config as $theme ) {
|
||||
/**
|
||||
* Filter to set if WP-Cron is disabled or if user wants to return to old way.
|
||||
*
|
||||
* @since 7.4.0
|
||||
* @access public
|
||||
*
|
||||
* @param bool
|
||||
*/
|
||||
if ( ! $this->waiting_for_background_update( $theme ) || static::is_wp_cli()
|
||||
|| apply_filters( 'github_updater_disable_wpcron', false )
|
||||
) {
|
||||
$this->get_remote_repo_meta( $theme );
|
||||
} else {
|
||||
$themes[ $theme->slug ] = $theme;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add update row to theme row, only in multisite.
|
||||
*/
|
||||
if ( is_multisite() ) {
|
||||
add_action( 'after_theme_row', [ $this, 'remove_after_theme_row' ], 10, 2 );
|
||||
if ( ! $this->tag ) {
|
||||
add_action( "after_theme_row_{$theme->slug}", [ $this, 'wp_theme_update_row' ], 10, 2 );
|
||||
if ( ! $theme->release_asset ) {
|
||||
add_action( "after_theme_row_{$theme->slug}", [ $this, 'multisite_branch_switcher' ], 15, 2 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$schedule_event = defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ? is_main_site() : true;
|
||||
|
||||
if ( $schedule_event ) {
|
||||
if ( ! wp_next_scheduled( 'ghu_get_remote_theme' ) &&
|
||||
! $this->is_duplicate_wp_cron_event( 'ghu_get_remote_theme' ) &&
|
||||
! apply_filters( 'github_updater_disable_wpcron', false )
|
||||
) {
|
||||
wp_schedule_single_event( time(), 'ghu_get_remote_theme', [ $themes ] );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! static::is_wp_cli() ) {
|
||||
$this->load_pre_filters();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load pre-update filters.
|
||||
*/
|
||||
public function load_pre_filters() {
|
||||
if ( ! is_multisite() ) {
|
||||
add_filter( 'wp_prepare_themes_for_js', [ $this, 'customize_theme_update_html' ] );
|
||||
}
|
||||
add_filter( 'themes_api', [ $this, 'themes_api' ], 99, 3 );
|
||||
add_filter( 'site_transient_update_themes', [ $this, 'update_site_transient' ], 15, 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Put changelog in themes_api, return WP.org data as appropriate.
|
||||
*
|
||||
* @param bool $false
|
||||
* @param string $action
|
||||
* @param \stdClass $response
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function themes_api( $false, $action, $response ) {
|
||||
if ( ! ( 'theme_information' === $action ) ) {
|
||||
return $false;
|
||||
}
|
||||
|
||||
$theme = isset( $this->config[ $response->slug ] ) ? $this->config[ $response->slug ] : false;
|
||||
|
||||
// Skip if waiting for background update.
|
||||
if ( $this->waiting_for_background_update( $theme ) ) {
|
||||
return $false;
|
||||
}
|
||||
|
||||
// wp.org theme.
|
||||
if ( ! $theme ) {
|
||||
return $false;
|
||||
}
|
||||
|
||||
$response->slug = $theme->slug;
|
||||
$response->name = $theme->name;
|
||||
$response->homepage = $theme->homepage;
|
||||
$response->donate_link = $theme->donate_link;
|
||||
$response->version = $theme->remote_version;
|
||||
$response->sections = $theme->sections;
|
||||
$response->description = implode( "\n", $theme->sections );
|
||||
$response->author = $theme->author;
|
||||
$response->preview_url = $theme->theme_uri;
|
||||
$response->requires = $theme->requires;
|
||||
$response->tested = $theme->tested;
|
||||
$response->downloaded = $theme->downloaded;
|
||||
$response->last_updated = $theme->last_updated;
|
||||
$response->rating = $theme->rating;
|
||||
$response->num_ratings = $theme->num_ratings;
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add custom theme update row, from /wp-admin/includes/update.php
|
||||
* Display update details or rollback links for multisite installation.
|
||||
*
|
||||
* @param string $theme_key
|
||||
* @param array $theme
|
||||
*
|
||||
* @author Seth Carstens
|
||||
*/
|
||||
public function wp_theme_update_row( $theme_key, $theme ) {
|
||||
$current = get_site_transient( 'update_themes' );
|
||||
|
||||
$themes_allowedtags = [
|
||||
'a' => [
|
||||
'href' => [],
|
||||
'title' => [],
|
||||
],
|
||||
'abbr' => [ 'title' => [] ],
|
||||
'acronym' => [ 'title' => [] ],
|
||||
'code' => [],
|
||||
'em' => [],
|
||||
'strong' => [],
|
||||
];
|
||||
$theme_name = wp_kses( $theme['Name'], $themes_allowedtags );
|
||||
// $wp_list_table = _get_list_table( 'WP_MS_Themes_List_Table' );
|
||||
$details_url = esc_attr(
|
||||
add_query_arg(
|
||||
[
|
||||
'tab' => 'theme-information',
|
||||
'theme' => $theme_key,
|
||||
'TB_iframe' => 'true',
|
||||
'width' => 270,
|
||||
'height' => 400,
|
||||
],
|
||||
self_admin_url( 'theme-install.php' )
|
||||
)
|
||||
);
|
||||
$nonced_update_url = wp_nonce_url(
|
||||
$this->get_update_url( 'theme', 'upgrade-theme', $theme_key ),
|
||||
'upgrade-theme_' . $theme_key
|
||||
);
|
||||
$enclosure = $this->update_row_enclosure( $theme_key, 'theme' );
|
||||
|
||||
if ( isset( $current->response[ $theme_key ] ) ) {
|
||||
$response = $current->response[ $theme_key ];
|
||||
echo $enclosure['open'];
|
||||
|
||||
printf(
|
||||
/* translators: %s: theme name */
|
||||
esc_html__( 'There is a new version of %s available.', 'github-updater' ),
|
||||
$theme_name
|
||||
);
|
||||
printf(
|
||||
/* translators: %s: details URL, theme name */
|
||||
' <a href="%s" class="thickbox" title="%s"> ',
|
||||
$details_url,
|
||||
$theme_name
|
||||
);
|
||||
if ( empty( $response['package'] ) ) {
|
||||
printf(
|
||||
/* translators: %s: theme version */
|
||||
esc_html__( 'View version %s details.', 'github-updater' ),
|
||||
$response['new_version']
|
||||
);
|
||||
echo '</a> <em>';
|
||||
esc_html_e( 'Automatic update is unavailable for this theme.', 'github-updater' );
|
||||
echo '</em>';
|
||||
} else {
|
||||
printf(
|
||||
/* translators: 1: version number, 2: closing anchor tag, 3: update URL */
|
||||
esc_html__( 'View version %1$s details%2$s or %3$supdate now%2$s.', 'github-updater' ),
|
||||
$response['new_version'],
|
||||
'</a>',
|
||||
sprintf(
|
||||
/* translators: %s: theme name */
|
||||
'<a href="' . $nonced_update_url . '" class="update-link" aria-label="' . esc_html__( 'Update %s now', 'github-updater' ) . '">',
|
||||
$theme_name
|
||||
)
|
||||
);
|
||||
}
|
||||
echo $enclosure['close'];
|
||||
|
||||
do_action( "in_theme_update_message-$theme_key", $theme, $response );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create branch switcher row for multisite installation.
|
||||
*
|
||||
* @param string $theme_key
|
||||
* @param array $theme
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function multisite_branch_switcher( $theme_key, $theme ) {
|
||||
if ( empty( static::$options['branch_switch'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$enclosure = $this->update_row_enclosure( $theme_key, 'theme', true );
|
||||
$id = $theme_key . '-id';
|
||||
$branches = isset( $this->config[ $theme_key ]->branches )
|
||||
? $this->config[ $theme_key ]->branches
|
||||
: null;
|
||||
$nonced_update_url = wp_nonce_url(
|
||||
$this->get_update_url( 'theme', 'upgrade-theme', $theme_key ),
|
||||
'upgrade-theme_' . $theme_key
|
||||
);
|
||||
|
||||
// Get current branch.
|
||||
$repo = $this->config[ $theme_key ];
|
||||
$branch = Singleton::get_instance( 'Branch', $this )->get_current_branch( $repo );
|
||||
|
||||
$branch_switch_data = [];
|
||||
$branch_switch_data['slug'] = $theme_key;
|
||||
$branch_switch_data['nonced_update_url'] = $nonced_update_url;
|
||||
$branch_switch_data['id'] = $id;
|
||||
$branch_switch_data['branch'] = $branch;
|
||||
$branch_switch_data['branches'] = $branches;
|
||||
|
||||
/*
|
||||
* Create after_theme_row_
|
||||
*/
|
||||
echo $enclosure['open'];
|
||||
$this->make_branch_switch_row( $branch_switch_data );
|
||||
echo $enclosure['close'];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove default after_theme_row_$stylesheet.
|
||||
*
|
||||
* @author @grappler
|
||||
*
|
||||
* @param string $theme_key
|
||||
* @param array $theme
|
||||
*/
|
||||
public function remove_after_theme_row( $theme_key, $theme ) {
|
||||
$themes = $this->get_theme_configs();
|
||||
|
||||
foreach ( static::$git_servers as $server ) {
|
||||
$repo_header = $server . ' Theme URI';
|
||||
$repo_uri = $theme->get( $repo_header );
|
||||
|
||||
/**
|
||||
* Filter to add themes not containing appropriate header line.
|
||||
*
|
||||
* @since 5.4.0
|
||||
* @access public
|
||||
*
|
||||
* @param array $additions Listing of themes to add.
|
||||
* Default null.
|
||||
* @param array $themes Listing of all themes.
|
||||
* @param string 'theme' Type being passed.
|
||||
*/
|
||||
$additions = apply_filters( 'github_updater_additions', null, $themes, 'theme' );
|
||||
foreach ( (array) $additions as $addition ) {
|
||||
if ( $theme_key === $addition['slug'] ) {
|
||||
if ( ! empty( $addition[ $server . ' Theme URI' ] ) ) {
|
||||
$repo_uri = $addition[ $server . ' Theme URI' ];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( empty( $repo_uri ) ) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if ( array_key_exists( $theme_key, $themes ) ) {
|
||||
remove_action( "after_theme_row_$theme_key", 'wp_theme_update_row' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call theme messaging for single site installation.
|
||||
*
|
||||
* @author Seth Carstens
|
||||
*
|
||||
* @param array $prepared_themes
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function customize_theme_update_html( $prepared_themes ) {
|
||||
foreach ( (array) $this->config as $theme ) {
|
||||
if ( empty( $prepared_themes[ $theme->slug ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! empty( $prepared_themes[ $theme->slug ]['hasUpdate'] ) ) {
|
||||
$prepared_themes[ $theme->slug ]['update'] = $this->append_theme_actions_content( $theme );
|
||||
} else {
|
||||
$prepared_themes[ $theme->slug ]['description'] .= $this->append_theme_actions_content( $theme );
|
||||
}
|
||||
if ( ! $theme->release_asset ) {
|
||||
$prepared_themes[ $theme->slug ]['description'] .= $this->single_install_switcher( $theme );
|
||||
}
|
||||
}
|
||||
|
||||
return $prepared_themes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create theme update messaging for single site installation.
|
||||
*
|
||||
* @author Seth Carstens
|
||||
*
|
||||
* @access protected
|
||||
*
|
||||
* @param \stdClass $theme
|
||||
*
|
||||
* @return string (content buffer)
|
||||
*/
|
||||
protected function append_theme_actions_content( $theme ) {
|
||||
$details_url = esc_attr(
|
||||
add_query_arg(
|
||||
[
|
||||
'tab' => 'theme-information',
|
||||
'theme' => $theme->slug,
|
||||
'TB_iframe' => 'true',
|
||||
'width' => 270,
|
||||
'height' => 400,
|
||||
],
|
||||
self_admin_url( 'theme-install.php' )
|
||||
)
|
||||
);
|
||||
$nonced_update_url = wp_nonce_url(
|
||||
$this->get_update_url( 'theme', 'upgrade-theme', $theme->slug ),
|
||||
'upgrade-theme_' . $theme->slug
|
||||
);
|
||||
|
||||
$current = get_site_transient( 'update_themes' );
|
||||
|
||||
/**
|
||||
* Display theme update links.
|
||||
*/
|
||||
ob_start();
|
||||
if ( isset( $current->response[ $theme->slug ] ) ) {
|
||||
?>
|
||||
<p>
|
||||
<strong>
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %s: theme name */
|
||||
esc_html__( 'There is a new version of %s available.', 'github-updater' ),
|
||||
$theme->name
|
||||
);
|
||||
printf(
|
||||
' <a href="%s" class="thickbox open-plugin-details-modal" title="%s">',
|
||||
$details_url,
|
||||
esc_attr( $theme->name )
|
||||
);
|
||||
printf(
|
||||
/* translators: 1: version number, 2: closing anchor tag, 3: update URL */
|
||||
esc_html__( 'View version %1$s details%2$s or %3$supdate now%2$s.', 'github-updater' ),
|
||||
$theme->remote_version = isset( $theme->remote_version ) ? $theme->remote_version : null,
|
||||
'</a>',
|
||||
sprintf(
|
||||
/* translators: %s: theme name */
|
||||
'<a aria-label="' . esc_html__( 'Update %s now', 'github-updater' ) . '" id="update-theme" data-slug="' . $theme->slug . '" href="' . $nonced_update_url . '">',
|
||||
$theme->name
|
||||
)
|
||||
);
|
||||
?>
|
||||
</strong>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
return trim( ob_get_clean(), '1' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Display rollback/branch switcher for single site installation.
|
||||
*
|
||||
* @access protected
|
||||
*
|
||||
* @param \stdClass $theme
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function single_install_switcher( $theme ) {
|
||||
$nonced_update_url = wp_nonce_url(
|
||||
$this->get_update_url( 'theme', 'upgrade-theme', $theme->slug ),
|
||||
'upgrade-theme_' . $theme->slug
|
||||
);
|
||||
$rollback_url = sprintf( '%s%s', $nonced_update_url, '&rollback=' );
|
||||
|
||||
if ( ! isset( static::$options['branch_switch'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
ob_start();
|
||||
if ( '1' === static::$options['branch_switch'] ) {
|
||||
printf(
|
||||
/* translators: 1: branch name, 2: jQuery dropdown, 3: closing tag */
|
||||
'<p>' . esc_html__( 'Current branch is `%1$s`, try %2$sanother version%3$s', 'github-updater' ),
|
||||
$theme->branch,
|
||||
'<a href="#" onclick="jQuery(\'#ghu_versions\').toggle();return false;">',
|
||||
'</a>.</p>'
|
||||
);
|
||||
?>
|
||||
<div id="ghu_versions" style="display:none; width: 100%;">
|
||||
<label><select style="width: 60%;" onchange="if(jQuery(this).val() != '') { jQuery(this).parent().next().show(); jQuery(this).parent().next().attr('href','<?php echo esc_url( $rollback_url ); ?>'+jQuery(this).val()); } else jQuery(this).parent().next().hide();">
|
||||
<option value=""><?php esc_html_e( 'Choose a Version', 'github-updater' ); ?>…</option>
|
||||
<?php
|
||||
if ( isset( $theme->branches ) ) {
|
||||
foreach ( array_keys( $theme->branches ) as $branch ) {
|
||||
echo '<option>' . $branch . '</option>';
|
||||
}
|
||||
}
|
||||
if ( ! empty( $theme->rollback ) ) {
|
||||
$rollback = array_keys( $theme->rollback );
|
||||
usort( $rollback, 'version_compare' );
|
||||
krsort( $rollback );
|
||||
$rollback = array_splice( $rollback, 0, 4, true );
|
||||
array_shift( $rollback ); // Dump current tag.
|
||||
foreach ( $rollback as $tag ) {
|
||||
echo '<option>' . $tag . '</option>';
|
||||
}
|
||||
}
|
||||
if ( empty( $theme->rollback ) ) {
|
||||
echo '<option>' . esc_html__( 'No previous tags to rollback to.', 'github-updater' ) . '</option></select></label>';
|
||||
}
|
||||
?>
|
||||
</select></label>
|
||||
<a style="display: none;" class="button-primary" href="?"><?php esc_html_e( 'Install', 'github-updater' ); ?></a>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
return trim( ob_get_clean(), '1' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook into site_transient_update_themes to update.
|
||||
* Finds newest tag and compares to current tag.
|
||||
*
|
||||
* @param array $transient
|
||||
*
|
||||
* @return array|\stdClass
|
||||
*/
|
||||
public function update_site_transient( $transient ) {
|
||||
foreach ( (array) $this->config as $theme ) {
|
||||
if ( $this->can_update_repo( $theme ) ) {
|
||||
$response = [
|
||||
'theme' => $theme->slug,
|
||||
'new_version' => $theme->remote_version,
|
||||
'url' => $theme->uri,
|
||||
'package' => $theme->download_link,
|
||||
'branch' => $theme->branch,
|
||||
'branches' => array_keys( $theme->branches ),
|
||||
'type' => "{$theme->git}-{$theme->type}",
|
||||
];
|
||||
|
||||
// Skip on RESTful updating.
|
||||
if ( isset( $_GET['action'], $_GET['theme'] ) &&
|
||||
'github-updater-update' === $_GET['action'] &&
|
||||
$response['theme'] === $_GET['theme']
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Pull update from dot org if not overriding.
|
||||
if ( ! $this->override_dot_org( 'theme', $theme ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$transient->response[ $theme->slug ] = $response;
|
||||
} else {
|
||||
/**
|
||||
* Filter to return array of overrides to dot org.
|
||||
*
|
||||
* @since 8.5.0
|
||||
* @return array
|
||||
*/
|
||||
$overrides = apply_filters( 'github_updater_override_dot_org', [] );
|
||||
if ( isset( $transient->response[ $theme->slug ] ) && in_array( $theme->slug, $overrides, true ) ) {
|
||||
unset( $transient->response[ $theme->slug ] );
|
||||
}
|
||||
}
|
||||
|
||||
// Set transient for rollback.
|
||||
if ( isset( $_GET['theme'], $_GET['rollback'] ) && $theme->slug === $_GET['theme']
|
||||
) {
|
||||
$transient->response[ $theme->slug ] = $this->set_rollback_transient( 'theme', $theme );
|
||||
}
|
||||
}
|
||||
|
||||
return $transient;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,365 @@
|
||||
<?php
|
||||
/**
|
||||
* GitHub Updater
|
||||
*
|
||||
* @author Andy Fragen
|
||||
* @license GPL-2.0+
|
||||
* @link https://github.com/afragen/github-updater
|
||||
* @package github-updater
|
||||
*/
|
||||
|
||||
namespace Fragen\GitHub_Updater\Traits;
|
||||
|
||||
use Fragen\GitHub_Updater\Readme_Parser as Readme_Parser;
|
||||
|
||||
/**
|
||||
* Trait API_Common
|
||||
*/
|
||||
trait API_Common {
|
||||
|
||||
/**
|
||||
* Holds loose class method name.
|
||||
*
|
||||
* @var null
|
||||
*/
|
||||
protected static $method;
|
||||
|
||||
/**
|
||||
* Decode API responses that are base64 encoded.
|
||||
*
|
||||
* @param string $git (github|bitbucket|gitlab|gitea)
|
||||
* @param mixed $response API response.
|
||||
* @return mixed $response
|
||||
*/
|
||||
private function decode_response( $git, $response ) {
|
||||
switch ( $git ) {
|
||||
case 'github':
|
||||
case 'gitlab':
|
||||
$response = isset( $response->content ) ? base64_decode( $response->content ) : $response;
|
||||
break;
|
||||
case 'bbserver':
|
||||
$response = isset( $response->lines ) ? $this->bbserver_recombine_response( $response ) : $response;
|
||||
break;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse API response that returns as stdClass.
|
||||
*
|
||||
* @param string $git (github|bitbucket|gitlab|gitea)
|
||||
* @param mixed $response API response.
|
||||
* @return mixed $response
|
||||
*/
|
||||
private function parse_response( $git, $response ) {
|
||||
switch ( $git ) {
|
||||
case 'bitbucket':
|
||||
case 'bbserver':
|
||||
$response = isset( $response->values ) ? $response->values : $response;
|
||||
break;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse API response to release asset URI.
|
||||
*
|
||||
* @param string $git (github|bitbucket|gitlab|gitea)
|
||||
* @param string $request Query to API->api().
|
||||
* @param mixed $response API response.
|
||||
* @return string $response Release asset download link.
|
||||
*/
|
||||
private function parse_release_asset( $git, $request, $response ) {
|
||||
switch ( $git ) {
|
||||
case 'github':
|
||||
$download_link = isset( $response->assets[0] ) && ! is_wp_error( $response ) ? $response->assets[0]->browser_download_url : null;
|
||||
|
||||
// Private repo.
|
||||
$response = ( null !== $download_link && ( property_exists( $this->type, 'is_private' ) && $this->type->is_private ) ) ? $response->assets[0]->url : $download_link;
|
||||
break;
|
||||
case 'bitbucket':
|
||||
$download_base = $this->get_api_url( $request, true );
|
||||
$response = isset( $response->values[0] ) && ! is_wp_error( $response ) ? $download_base . '/' . $response->values[0]->name : null;
|
||||
break;
|
||||
case 'bbserver':
|
||||
// TODO: make work.
|
||||
break;
|
||||
case 'gitlab':
|
||||
$response = $this->get_api_url( $request );
|
||||
break;
|
||||
case 'gitea':
|
||||
break;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the remote file and parse headers.
|
||||
*
|
||||
* @param string $git github|bitbucket|gitlab|gitea)
|
||||
* @param string $file Filename.
|
||||
* @param string $request API request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_remote_api_info( $git, $file, $request ) {
|
||||
$response = isset( $this->response[ $file ] ) ? $this->response[ $file ] : false;
|
||||
|
||||
if ( ! $response ) {
|
||||
self::$method = 'file';
|
||||
$response = $this->api( $request );
|
||||
$response = $this->decode_response( $git, $response );
|
||||
}
|
||||
|
||||
if ( $response && is_string( $response ) && ! is_wp_error( $response ) ) {
|
||||
$response = $this->get_file_headers( $response, $this->type->type );
|
||||
$this->set_repo_cache( $file, $response );
|
||||
$this->set_repo_cache( 'repo', $this->type->slug );
|
||||
}
|
||||
|
||||
if ( ! is_array( $response ) || $this->validate_response( $response ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$response['dot_org'] = $this->get_dot_org_data();
|
||||
$this->set_file_info( $response );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get remote info for tags.
|
||||
*
|
||||
* @param string $git github|bitbucket|gitlab|gitea)
|
||||
* @param string $request API request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_remote_api_tag( $git, $request ) {
|
||||
$repo_type = $this->return_repo_type();
|
||||
$response = isset( $this->response['tags'] ) ? $this->response['tags'] : false;
|
||||
|
||||
if ( ! $response ) {
|
||||
self::$method = 'tags';
|
||||
$response = $this->api( $request );
|
||||
|
||||
if ( ! $response ) {
|
||||
$response = new \stdClass();
|
||||
$response->message = 'No tags found';
|
||||
}
|
||||
|
||||
if ( $response ) {
|
||||
$response = $this->parse_tag_response( $response );
|
||||
$this->set_repo_cache( 'tags', $response );
|
||||
}
|
||||
}
|
||||
|
||||
if ( $this->validate_response( $response ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$tags = $this->parse_tags( $response, $repo_type );
|
||||
$this->sort_tags( $tags );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the remote CHANGES.md file.
|
||||
*
|
||||
* @param string $git github|bitbucket|gitlab|gitea)
|
||||
* @param string $changes Changelog filename.
|
||||
* @param string $request API request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_remote_api_changes( $git, $changes, $request ) {
|
||||
$response = isset( $this->response['changes'] ) ? $this->response['changes'] : false;
|
||||
|
||||
// Set $response from local file if no update available.
|
||||
if ( ! $response && ! $this->can_update_repo( $this->type ) ) {
|
||||
$response = $this->get_local_info( $this->type, $changes );
|
||||
}
|
||||
|
||||
if ( ! $response ) {
|
||||
self::$method = 'changes';
|
||||
$response = $this->api( $request );
|
||||
$response = $this->decode_response( $git, $response );
|
||||
}
|
||||
|
||||
if ( ! $response && ! is_wp_error( $response ) ) {
|
||||
$response = new \stdClass();
|
||||
$response->message = 'No changelog found';
|
||||
}
|
||||
|
||||
if ( $this->validate_response( $response ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( $response && ! isset( $this->response['changes'] ) ) {
|
||||
$parser = new \Parsedown();
|
||||
$response = $parser->text( $response );
|
||||
$this->set_repo_cache( 'changes', $response );
|
||||
}
|
||||
|
||||
$this->type->sections['changelog'] = $response;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and parse remote readme.txt.
|
||||
*
|
||||
* @param string $git github|bitbucket|gitlab|gitea)
|
||||
* @param string $request API request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_remote_api_readme( $git, $request ) {
|
||||
if ( ! $this->local_file_exists( 'readme.txt' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$response = isset( $this->response['readme'] ) ? $this->response['readme'] : false;
|
||||
|
||||
// Set $response from local file if no update available.
|
||||
if ( ! $response && ! $this->can_update_repo( $this->type ) ) {
|
||||
$response = $this->get_local_info( $this->type, 'readme.txt' );
|
||||
}
|
||||
|
||||
if ( ! $response ) {
|
||||
self::$method = 'readme';
|
||||
$response = $this->api( $request );
|
||||
$response = $this->decode_response( $git, $response );
|
||||
}
|
||||
|
||||
if ( ! $response && ! is_wp_error( $response ) ) {
|
||||
$response = new \stdClass();
|
||||
$response->message = 'No readme found';
|
||||
}
|
||||
|
||||
if ( $this->validate_response( $response ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( $response && ! isset( $this->response['readme'] ) ) {
|
||||
$parser = new Readme_Parser( $response );
|
||||
$response = $parser->parse_data();
|
||||
$this->set_repo_cache( 'readme', $response );
|
||||
}
|
||||
|
||||
$this->set_readme_info( $response );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the repository meta from API.
|
||||
*
|
||||
* @param string $git github|bitbucket|gitlab|gitea)
|
||||
* @param string $request API request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_remote_api_repo_meta( $git, $request ) {
|
||||
$response = isset( $this->response['meta'] ) ? $this->response['meta'] : false;
|
||||
|
||||
if ( ! $response ) {
|
||||
self::$method = 'meta';
|
||||
$response = $this->api( $request );
|
||||
|
||||
if ( $response ) {
|
||||
$response = $this->parse_meta_response( $response );
|
||||
$this->set_repo_cache( 'meta', $response );
|
||||
}
|
||||
}
|
||||
|
||||
if ( $this->validate_response( $response ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->type->repo_meta = $response;
|
||||
$this->add_meta_repo_object();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create array of branches and download links as array.
|
||||
*
|
||||
* @param string $git github|bitbucket|gitlab|gitea)
|
||||
* @param string $request API request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_remote_api_branches( $git, $request ) {
|
||||
$branches = [];
|
||||
$response = isset( $this->response['branches'] ) ? $this->response['branches'] : false;
|
||||
|
||||
if ( $this->exit_no_update( $response, true ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! $response ) {
|
||||
self::$method = 'branches';
|
||||
$response = $this->api( $request );
|
||||
$response = $this->parse_response( $git, $response );
|
||||
|
||||
if ( $this->validate_response( $response ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( $response ) {
|
||||
$branches = $this->parse_branch_response( $response );
|
||||
$this->type->branches = $branches;
|
||||
$this->set_repo_cache( 'branches', $branches );
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
$this->type->branches = $response;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get API release asset download link.
|
||||
*
|
||||
* @param string $git (github|bitbucket|gitlab|gitea)
|
||||
* @param string $request Query for API->api().
|
||||
* @return string $response Release asset URI.
|
||||
*/
|
||||
public function get_api_release_asset( $git, $request ) {
|
||||
$response = isset( $this->response['release_asset'] ) ? $this->response['release_asset'] : false;
|
||||
|
||||
if ( $response && $this->exit_no_update( $response ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! $response ) {
|
||||
self::$method = 'release_asset';
|
||||
$response = $this->api( $request );
|
||||
$response = $this->parse_release_asset( $git, $request, $response );
|
||||
|
||||
if ( ! $response && ! is_wp_error( $response ) ) {
|
||||
$response = new \stdClass();
|
||||
$response->message = 'No release asset found';
|
||||
}
|
||||
}
|
||||
|
||||
if ( $response && ! isset( $this->response['release_asset'] ) ) {
|
||||
$this->set_repo_cache( 'release_asset', $response );
|
||||
}
|
||||
|
||||
if ( $this->validate_response( $response ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
<?php
|
||||
/**
|
||||
* GitHub Updater
|
||||
*
|
||||
* @author Andy Fragen
|
||||
* @license GPL-2.0+
|
||||
* @link https://github.com/afragen/github-updater
|
||||
* @package github-updater
|
||||
*/
|
||||
|
||||
namespace Fragen\GitHub_Updater\Traits;
|
||||
|
||||
use Fragen\Singleton;
|
||||
use Fragen\GitHub_Updater\Install;
|
||||
use Fragen\GitHub_Updater\API\Bitbucket_API;
|
||||
use Fragen\GitHub_Updater\API\Bitbucket_Server_API;
|
||||
|
||||
/*
|
||||
* Exit if called directly.
|
||||
*/
|
||||
if ( ! defined( 'WPINC' ) ) {
|
||||
die;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trait Basic_Auth_Loader
|
||||
*/
|
||||
trait Basic_Auth_Loader {
|
||||
/**
|
||||
* Stores array of git servers requiring Basic Authentication.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $basic_auth_required = [ 'Bitbucket' ];
|
||||
|
||||
/**
|
||||
* Load hooks for Bitbucket authentication headers.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function load_authentication_hooks() {
|
||||
add_filter( 'http_request_args', [ $this, 'maybe_basic_authenticate_http' ], 5, 2 );
|
||||
add_filter( 'http_request_args', [ $this, 'http_release_asset_auth' ], 15, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove hooks for Bitbucket authentication headers.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function remove_authentication_hooks() {
|
||||
remove_filter( 'http_request_args', [ $this, 'maybe_basic_authenticate_http' ] );
|
||||
remove_filter( 'http_request_args', [ $this, 'http_release_asset_auth' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Basic Authentication $args to http_request_args filter hook
|
||||
* for private repositories only.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $args Args passed to the URL.
|
||||
* @param string $url The URL.
|
||||
*
|
||||
* @return array $args
|
||||
*/
|
||||
public function maybe_basic_authenticate_http( $args, $url ) {
|
||||
$credentials = $this->get_credentials( $url );
|
||||
|
||||
if ( $credentials['private'] && $credentials['isset'] && ! $credentials['api.wordpress'] ) {
|
||||
$username = $credentials['username'];
|
||||
$password = $credentials['password'];
|
||||
|
||||
$args['headers']['Authorization'] = 'Basic ' . base64_encode( "$username:$password" );
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get credentials (username/password) for Basic Authentication.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @param string $url The URL.
|
||||
*
|
||||
* @return array $credentials
|
||||
*/
|
||||
private function get_credentials( $url ) {
|
||||
$headers = parse_url( $url );
|
||||
$type = $this->get_class_vars( 'Base', 'caller' );
|
||||
$username_key = null;
|
||||
$password_key = null;
|
||||
$credentials = [
|
||||
'username' => null,
|
||||
'password' => null,
|
||||
'api.wordpress' => 'api.wordpress.org' === $headers['host'],
|
||||
'isset' => false,
|
||||
'private' => false,
|
||||
];
|
||||
$hosts = [ 'bitbucket.org', 'api.bitbucket.org' ];
|
||||
|
||||
$repos = array_merge(
|
||||
Singleton::get_instance( 'Plugin', $this )->get_plugin_configs(),
|
||||
Singleton::get_instance( 'Theme', $this )->get_theme_configs()
|
||||
);
|
||||
|
||||
$slug = isset( $_REQUEST['slug'] ) ? $_REQUEST['slug'] : false;
|
||||
$slug = ! $slug && isset( $_REQUEST['plugin'] ) ? $_REQUEST['plugin'] : $slug;
|
||||
$slug = ! $slug && isset( $_REQUEST['theme'] ) ? $_REQUEST['theme'] : $slug;
|
||||
|
||||
// Set for bulk upgrade.
|
||||
if ( ! $slug ) {
|
||||
$plugins = isset( $_REQUEST['plugins'] )
|
||||
? array_map( 'dirname', explode( ',', $_REQUEST['plugins'] ) )
|
||||
: [];
|
||||
$themes = isset( $_REQUEST['themes'] )
|
||||
? explode( ',', $_REQUEST['themes'] )
|
||||
: [];
|
||||
$bulk_update = array_merge( $plugins, $themes );
|
||||
if ( ! empty( $bulk_update ) ) {
|
||||
$slug = array_filter(
|
||||
$bulk_update,
|
||||
function ( $e ) use ( $url ) {
|
||||
return false !== strpos( $url, $e );
|
||||
}
|
||||
);
|
||||
$slug = array_pop( $slug );
|
||||
}
|
||||
}
|
||||
|
||||
$type = $slug &&
|
||||
isset( $repos[ $slug ] ) && property_exists( $repos[ $slug ], 'git' )
|
||||
? $repos[ $slug ]->git
|
||||
: $type;
|
||||
|
||||
// Set for WP-CLI.
|
||||
if ( ! $slug ) {
|
||||
foreach ( $repos as $repo ) {
|
||||
if ( property_exists( $repo, 'download_link' ) && $url === $repo->download_link ) {
|
||||
$type = $repo->git;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set for Remote Install.
|
||||
$type = isset( $_POST['github_updater_api'], $_POST['github_updater_repo'] ) &&
|
||||
false !== strpos( $url, basename( $_POST['github_updater_repo'] ) )
|
||||
? $_POST['github_updater_api']
|
||||
: $type;
|
||||
|
||||
switch ( $type ) {
|
||||
case 'bitbucket':
|
||||
case $type instanceof Bitbucket_API:
|
||||
case $type instanceof Bitbucket_Server_API:
|
||||
$bitbucket_org = in_array( $headers['host'], $hosts, true );
|
||||
$username_key = $bitbucket_org ? 'bitbucket_username' : 'bitbucket_server_username';
|
||||
$password_key = $bitbucket_org ? 'bitbucket_password' : 'bitbucket_server_password';
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: can use `( $this->caller )::$options` in PHP7.
|
||||
$caller = $this->get_class_vars( 'Base', 'caller' );
|
||||
static::$options = $caller instanceof Install ? $caller::$options : static::$options;
|
||||
|
||||
if ( isset( static::$options[ $username_key ], static::$options[ $password_key ] ) ) {
|
||||
$credentials['username'] = static::$options[ $username_key ];
|
||||
$credentials['password'] = static::$options[ $password_key ];
|
||||
$credentials['isset'] = true;
|
||||
$credentials['private'] = $this->is_repo_private( $url );
|
||||
}
|
||||
|
||||
return $credentials;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if repo is private.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @param string $url The URL.
|
||||
*
|
||||
* @return bool true if private
|
||||
*/
|
||||
private function is_repo_private( $url ) {
|
||||
// Used when updating.
|
||||
$slug = isset( $_REQUEST['rollback'], $_REQUEST['plugin'] ) ? dirname( $_REQUEST['plugin'] ) : false;
|
||||
$slug = isset( $_REQUEST['rollback'], $_REQUEST['theme'] ) ? $_REQUEST['theme'] : $slug;
|
||||
$slug = isset( $_REQUEST['slug'] ) ? $_REQUEST['slug'] : $slug;
|
||||
|
||||
if ( $slug && array_key_exists( $slug, static::$options ) &&
|
||||
1 === (int) static::$options[ $slug ] &&
|
||||
false !== stripos( $url, $slug )
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Used for remote install tab.
|
||||
if ( isset( $_POST['option_page'], $_POST['is_private'] ) &&
|
||||
'github_updater_install' === $_POST['option_page']
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Used for refreshing cache.
|
||||
foreach ( array_keys( static::$options ) as $option ) {
|
||||
if ( 1 === (int) static::$options[ $option ] &&
|
||||
false !== strpos( $url, $option )
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes Basic Authentication header for Bitbucket Release Assets.
|
||||
* Storage in AmazonS3 buckets, uses Query String Request Authentication Alternative.
|
||||
*
|
||||
* @access public
|
||||
* @link http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html#RESTAuthenticationQueryStringAuth
|
||||
*
|
||||
* @param array $args The URL arguments passed.
|
||||
* @param string $url The URL.
|
||||
*
|
||||
* @return array $args
|
||||
*/
|
||||
public function http_release_asset_auth( $args, $url ) {
|
||||
$arr_url = parse_url( $url );
|
||||
if ( isset( $arr_url['host'] ) && 'bbuseruploads.s3.amazonaws.com' === $arr_url['host'] ) {
|
||||
unset( $args['headers']['Authorization'] );
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads authentication hooks when updating from update-core.php.
|
||||
*
|
||||
* @param bool $reply
|
||||
* @param string $package Update package URL, unused.
|
||||
* @param \Plugin_Upgrader|\Theme_Upgrader $class Upgrader object.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function upgrader_pre_download( $reply, $package, $class ) {
|
||||
if ( $class instanceof \Plugin_Upgrader &&
|
||||
property_exists( $class->skin, 'plugin_info' )
|
||||
) {
|
||||
$headers = $class->skin->plugin_info;
|
||||
foreach ( self::$basic_auth_required as $git_server ) {
|
||||
$ghu_header = $headers[ $git_server . ' Plugin URI' ];
|
||||
if ( ! empty( $ghu_header ) ) {
|
||||
$this->load_authentication_hooks();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( $class instanceof \Theme_Upgrader &&
|
||||
property_exists( $class->skin, 'theme_info' )
|
||||
) {
|
||||
$theme = $class->skin->theme_info;
|
||||
foreach ( self::$basic_auth_required as $git_server ) {
|
||||
$ghu_header = $theme->get( $git_server . ' Theme URI' );
|
||||
if ( ! empty( $ghu_header ) ) {
|
||||
$this->load_authentication_hooks();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
remove_filter( 'upgrader_pre_download', [ $this, 'upgrader_pre_download' ] );
|
||||
|
||||
return $reply;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,420 @@
|
||||
<?php
|
||||
/**
|
||||
* GitHub Updater
|
||||
*
|
||||
* @author Andy Fragen
|
||||
* @license GPL-2.0+
|
||||
* @link https://github.com/afragen/github-updater
|
||||
* @package github-updater
|
||||
*/
|
||||
|
||||
namespace Fragen\GitHub_Updater\Traits;
|
||||
|
||||
use Fragen\Singleton;
|
||||
|
||||
/**
|
||||
* Trait GHU_Trait
|
||||
*/
|
||||
trait GHU_Trait {
|
||||
/**
|
||||
* Checks to see if a heartbeat is resulting in activity.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_heartbeat() {
|
||||
return isset( $_POST['action'] ) && 'heartbeat' === $_POST['action'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if WP_CLI.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_wp_cli() {
|
||||
return defined( 'WP_CLI' ) && WP_CLI;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if DOING_AJAX.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_doing_ajax() {
|
||||
return defined( 'DOING_AJAX' ) && DOING_AJAX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load site options.
|
||||
*/
|
||||
public function load_options() {
|
||||
$base = Singleton::get_instance( 'Base', $this );
|
||||
$base::$options = get_site_option( 'github_updater', [] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check current page.
|
||||
*
|
||||
* @param array $pages
|
||||
* @return bool
|
||||
*/
|
||||
public function is_current_page( array $pages ) {
|
||||
global $pagenow;
|
||||
return in_array( $pagenow, $pages, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns repo cached data.
|
||||
*
|
||||
* @access protected
|
||||
*
|
||||
* @param string|bool $repo Repo name or false.
|
||||
*
|
||||
* @return array|bool The repo cache. False if expired.
|
||||
*/
|
||||
public function get_repo_cache( $repo = false ) {
|
||||
if ( ! $repo ) {
|
||||
$repo = isset( $this->type->slug ) ? $this->type->slug : 'ghu';
|
||||
}
|
||||
$cache_key = 'ghu-' . md5( $repo );
|
||||
$cache = get_site_option( $cache_key );
|
||||
|
||||
if ( empty( $cache['timeout'] ) || time() > $cache['timeout'] ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets repo data for cache in site option.
|
||||
*
|
||||
* @access protected
|
||||
*
|
||||
* @param string $id Data Identifier.
|
||||
* @param mixed $response Data to be stored.
|
||||
* @param string|bool $repo Repo name or false.
|
||||
* @param string|bool $timeout Timeout for cache.
|
||||
* Default is $hours (12 hours).
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function set_repo_cache( $id, $response, $repo = false, $timeout = false ) {
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return false;
|
||||
}
|
||||
$hours = $this->get_class_vars( 'API', 'hours' );
|
||||
if ( ! $repo ) {
|
||||
$repo = isset( $this->type->slug ) ? $this->type->slug : 'ghu';
|
||||
}
|
||||
$cache_key = 'ghu-' . md5( $repo );
|
||||
$timeout = $timeout ? $timeout : '+' . $hours . ' hours';
|
||||
|
||||
/**
|
||||
* Allow filtering of cache timeout for repo information.
|
||||
*
|
||||
* @since 8.7.1
|
||||
*
|
||||
* @param string $timeout Timeout value used with strtotime().
|
||||
* @param string $id Data Identifier.
|
||||
* @param mixed $response Data to be stored.
|
||||
* @param string|bool $repo Repo name or false.
|
||||
*/
|
||||
$timeout = apply_filters( 'github_updater_repo_cache_timeout', $timeout, $id, $response, $repo );
|
||||
|
||||
$this->response['timeout'] = strtotime( $timeout );
|
||||
$this->response[ $id ] = $response;
|
||||
|
||||
update_site_option( $cache_key, $this->response );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for class variables.
|
||||
*
|
||||
* @param string $class_name Name of class.
|
||||
* @param string $var Name of variable.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get_class_vars( $class_name, $var ) {
|
||||
$class = Singleton::get_instance( $class_name, $this );
|
||||
$reflection_obj = new \ReflectionObject( $class );
|
||||
if ( ! $reflection_obj->hasProperty( $var ) ) {
|
||||
return false;
|
||||
}
|
||||
$property = $reflection_obj->getProperty( $var );
|
||||
$property->setAccessible( true );
|
||||
|
||||
return $property->getValue( $class );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns static class variable $error_code.
|
||||
*
|
||||
* @return array self::$error_code
|
||||
*/
|
||||
public function get_error_codes() {
|
||||
return $this->get_class_vars( 'API', 'error_code' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to check if plugin or theme object is able to be updated.
|
||||
*
|
||||
* @param \stdClass $type
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function can_update_repo( $type ) {
|
||||
$wp_version = get_bloginfo( 'version' );
|
||||
|
||||
$wp_version_ok = ! empty( $type->requires )
|
||||
? version_compare( $wp_version, $type->requires, '>=' )
|
||||
: true;
|
||||
$php_version_ok = ! empty( $type->requires_php )
|
||||
? version_compare( phpversion(), $type->requires_php, '>=' )
|
||||
: true;
|
||||
$remote_is_newer = isset( $type->remote_version )
|
||||
? version_compare( $type->remote_version, $type->local_version, '>' )
|
||||
: false;
|
||||
|
||||
/**
|
||||
* Filter $remote_is_newer if you use another method to test for updates.
|
||||
*
|
||||
* @since 8.7.0
|
||||
* @param bool $remote_is_newer
|
||||
* @param \stdClass $type Plugin/Theme data.
|
||||
*/
|
||||
$remote_is_newer = apply_filters( 'github_updater_remote_is_newer', $remote_is_newer, $type );
|
||||
|
||||
return $remote_is_newer && $wp_version_ok && $php_version_ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all `ghu-` prefixed data from options table.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function delete_all_cached_data() {
|
||||
global $wpdb;
|
||||
|
||||
$table = is_multisite() ? $wpdb->base_prefix . 'sitemeta' : $wpdb->base_prefix . 'options';
|
||||
$column = is_multisite() ? 'meta_key' : 'option_name';
|
||||
$delete_string = 'DELETE FROM ' . $table . ' WHERE ' . $column . ' LIKE %s LIMIT 1000';
|
||||
|
||||
$wpdb->query( $wpdb->prepare( $delete_string, [ '%ghu-%' ] ) );
|
||||
|
||||
wp_cron();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this a private repo with a token/checked or needing token/checked?
|
||||
* Test for whether remote_version is set ( default = 0.0.0 ) or
|
||||
* a repo option is set/not empty.
|
||||
*
|
||||
* @param \stdClass $repo
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_private( $repo ) {
|
||||
if ( ! isset( $repo->remote_version ) && ! self::is_doing_ajax() ) {
|
||||
return true;
|
||||
}
|
||||
if ( isset( $repo->remote_version ) && ! self::is_doing_ajax() ) {
|
||||
return ( '0.0.0' === $repo->remote_version ) || ! empty( self::$options[ $repo->slug ] );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do we override dot org updates?
|
||||
*
|
||||
* @param string $type (plugin|theme)
|
||||
* @param \stdClass $repo Repository object.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function override_dot_org( $type, $repo ) {
|
||||
// Correctly account for dashicon in Settings page.
|
||||
$icon = is_array( $repo );
|
||||
$repo = is_array( $repo ) ? (object) $repo : $repo;
|
||||
$dot_org_master = ! $icon ? $repo->dot_org && 'master' === $repo->branch : true;
|
||||
|
||||
$transient_key = 'plugin' === $type ? $repo->file : null;
|
||||
$transient_key = 'theme' === $type ? $repo->slug : $transient_key;
|
||||
|
||||
/**
|
||||
* Filter update to override dot org.
|
||||
*
|
||||
* @since 8.5.0
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
$override = in_array( $transient_key, apply_filters( 'github_updater_override_dot_org', [] ), true );
|
||||
|
||||
return ! $dot_org_master || $override || $this->deprecate_override_constant();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated dot org override constant.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function deprecate_override_constant() {
|
||||
if ( defined( 'GITHUB_UPDATER_OVERRIDE_DOT_ORG' ) && GITHUB_UPDATER_OVERRIDE_DOT_ORG ) {
|
||||
error_log( 'GITHUB_UPDATER_OVERRIDE_DOT_ORG constant deprecated. Use `github_updater_override_dot_org` filter hook.' );
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize each setting field as needed.
|
||||
*
|
||||
* @param array $input Contains all settings fields as array keys.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function sanitize( $input ) {
|
||||
$new_input = [];
|
||||
foreach ( array_keys( (array) $input ) as $id ) {
|
||||
$new_input[ sanitize_file_name( $id ) ] = sanitize_text_field( $input[ $id ] );
|
||||
}
|
||||
|
||||
return $new_input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of the running git servers.
|
||||
*
|
||||
* @access public
|
||||
* @return array $gits
|
||||
*/
|
||||
public function get_running_git_servers() {
|
||||
$plugins = Singleton::get_instance( 'Plugin', $this )->get_plugin_configs();
|
||||
$themes = Singleton::get_instance( 'Theme', $this )->get_theme_configs();
|
||||
|
||||
$repos = array_merge( $plugins, $themes );
|
||||
$gits = array_map(
|
||||
function ( $e ) {
|
||||
if ( ! empty( $e->enterprise ) ) {
|
||||
if ( 'bitbucket' === $e->git ) {
|
||||
return 'bbserver';
|
||||
}
|
||||
if ( 'gitlab' === $e->git ) {
|
||||
return 'gitlabce';
|
||||
}
|
||||
}
|
||||
|
||||
return $e->git;
|
||||
},
|
||||
$repos
|
||||
);
|
||||
|
||||
return array_unique( array_values( $gits ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse URI param returning array of parts.
|
||||
*
|
||||
* @param string $repo_header
|
||||
*
|
||||
* @return array $header
|
||||
*/
|
||||
protected function parse_header_uri( $repo_header ) {
|
||||
$header_parts = parse_url( $repo_header );
|
||||
$header_path = pathinfo( $header_parts['path'] );
|
||||
$header['original'] = $repo_header;
|
||||
$header['scheme'] = isset( $header_parts['scheme'] ) ? $header_parts['scheme'] : null;
|
||||
$header['host'] = isset( $header_parts['host'] ) ? $header_parts['host'] : null;
|
||||
$header['owner'] = trim( $header_path['dirname'], '/' );
|
||||
$header['repo'] = $header_path['filename'];
|
||||
$header['owner_repo'] = implode( '/', [ $header['owner'], $header['repo'] ] );
|
||||
$header['base_uri'] = str_replace( $header_parts['path'], '', $repo_header );
|
||||
$header['uri'] = isset( $header['scheme'] ) ? trim( $repo_header, '/' ) : null;
|
||||
|
||||
$header = $this->sanitize( $header );
|
||||
|
||||
return $header;
|
||||
}
|
||||
|
||||
/**
|
||||
* Take remote file contents as string or array and parse and reduce headers.
|
||||
*
|
||||
* @param string|array $contents File contents or array of file headers.
|
||||
* @param string $type plugin|theme.
|
||||
*
|
||||
* @return array $all_headers Reduced array of all headers.
|
||||
*/
|
||||
public function get_file_headers( $contents, $type ) {
|
||||
$all_headers = [];
|
||||
$default_plugin_headers = [
|
||||
'Name' => 'Plugin Name',
|
||||
'PluginURI' => 'Plugin URI',
|
||||
'Version' => 'Version',
|
||||
'Description' => 'Description',
|
||||
'Author' => 'Author',
|
||||
'AuthorURI' => 'Author URI',
|
||||
'TextDomain' => 'Text Domain',
|
||||
'DomainPath' => 'Domain Path',
|
||||
'Network' => 'Network',
|
||||
];
|
||||
|
||||
$default_theme_headers = [
|
||||
'Name' => 'Theme Name',
|
||||
'ThemeURI' => 'Theme URI',
|
||||
'Description' => 'Description',
|
||||
'Author' => 'Author',
|
||||
'AuthorURI' => 'Author URI',
|
||||
'Version' => 'Version',
|
||||
'Template' => 'Template',
|
||||
'Status' => 'Status',
|
||||
'Tags' => 'Tags',
|
||||
'TextDomain' => 'Text Domain',
|
||||
'DomainPath' => 'Domain Path',
|
||||
];
|
||||
|
||||
if ( 'plugin' === $type ) {
|
||||
$all_headers = $default_plugin_headers;
|
||||
}
|
||||
if ( 'theme' === $type ) {
|
||||
$all_headers = $default_theme_headers;
|
||||
}
|
||||
|
||||
/*
|
||||
* Merge extra headers and default headers.
|
||||
*/
|
||||
$all_headers = array_merge( self::$extra_headers, $all_headers );
|
||||
$all_headers = array_unique( $all_headers );
|
||||
|
||||
/*
|
||||
* Make sure we catch CR-only line endings.
|
||||
*/
|
||||
if ( is_string( $contents ) ) {
|
||||
$file_data = str_replace( "\r", "\n", $contents );
|
||||
|
||||
foreach ( $all_headers as $field => $regex ) {
|
||||
if ( preg_match( '/^[ \t\/*#@]*' . preg_quote( $regex, '/' ) . ':(.*)$/mi', $file_data, $match ) && $match[1] ) {
|
||||
$all_headers[ $field ] = _cleanup_header_comment( $match[1] );
|
||||
} else {
|
||||
$all_headers[ $field ] = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$all_headers = is_array( $contents ) ? $contents : $all_headers;
|
||||
|
||||
// Reduce array to only headers with data.
|
||||
$all_headers = array_filter(
|
||||
$all_headers,
|
||||
function ( $e ) {
|
||||
return ! empty( $e );
|
||||
}
|
||||
);
|
||||
|
||||
return $all_headers;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
/**
|
||||
* GitHub Updater
|
||||
*
|
||||
* @author Andy Fragen
|
||||
* @license GPL-2.0+
|
||||
* @link https://github.com/afragen/github-updater
|
||||
* @package github-updater
|
||||
*/
|
||||
|
||||
namespace Fragen\GitHub_Updater\WP_CLI;
|
||||
|
||||
use WP_CLI;
|
||||
use WP_CLI_Command;
|
||||
use Fragen\Singleton;
|
||||
|
||||
// Add WP-CLI commands.
|
||||
WP_CLI::add_command( 'github-updater', 'Fragen\\GitHub_Updater\\WP_CLI\\CLI' );
|
||||
|
||||
/**
|
||||
* Manage GitHub Updater commands.
|
||||
*
|
||||
* Class GitHub_Updater_CLI
|
||||
*/
|
||||
class CLI extends WP_CLI_Command {
|
||||
/**
|
||||
* Clear GitHub Updater cache.
|
||||
*
|
||||
* ## OPTIONS
|
||||
*
|
||||
* <delete>
|
||||
* : delete the cache
|
||||
*
|
||||
* ## EXAMPLES
|
||||
*
|
||||
* wp github-updater cache delete
|
||||
*
|
||||
* @param array $args Array of arguments.
|
||||
*
|
||||
* @subcommand cache
|
||||
*/
|
||||
public function cache( $args ) {
|
||||
list($action) = $args;
|
||||
if ( 'delete' === $action ) {
|
||||
Singleton::get_instance( 'CLI_Common', $this )->delete_all_cached_data();
|
||||
WP_CLI::success( 'GitHub Updater cache has been cleared.' );
|
||||
} else {
|
||||
WP_CLI::error( sprintf( 'Incorrect command syntax, see %s for proper syntax.', '`wp help github-updater cache`' ) );
|
||||
}
|
||||
WP_CLI::success( 'WP-Cron is now running.' );
|
||||
WP_CLI::runcommand( 'cron event run --due-now' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset GitHub Updater REST API key.
|
||||
*
|
||||
* ## EXAMPLES
|
||||
*
|
||||
* wp github-updater reset-api-key
|
||||
*
|
||||
* @subcommand reset-api-key
|
||||
*/
|
||||
public function reset_api_key() {
|
||||
delete_site_option( 'github_updater_api_key' );
|
||||
Singleton::get_instance( 'Remote_Management', $this )->ensure_api_key_is_set();
|
||||
$api_key = get_site_option( 'github_updater_api_key' );
|
||||
$api_url = add_query_arg(
|
||||
[
|
||||
'action' => 'github-updater-update',
|
||||
'key' => $api_key,
|
||||
],
|
||||
admin_url( 'admin-ajax.php' )
|
||||
);
|
||||
|
||||
WP_CLI::success( 'GitHub Updater REST API key has been reset.' );
|
||||
WP_CLI::success( sprintf( 'The current RESTful endpoint is `%s`', $api_url ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
/**
|
||||
* GitHub Updater
|
||||
*
|
||||
* @author Andy Fragen
|
||||
* @license GPL-2.0+
|
||||
* @link https://github.com/afragen/github-updater
|
||||
* @package github-updater
|
||||
*/
|
||||
|
||||
namespace Fragen\GitHub_Updater\WP_CLI;
|
||||
|
||||
/**
|
||||
* Class CLI_Common
|
||||
*/
|
||||
class CLI_Common {
|
||||
/**
|
||||
* Delete all `ghu-` prefixed data from options table.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function delete_all_cached_data() {
|
||||
global $wpdb;
|
||||
|
||||
$table = is_multisite() ? $wpdb->base_prefix . 'sitemeta' : $wpdb->base_prefix . 'options';
|
||||
$column = is_multisite() ? 'meta_key' : 'option_name';
|
||||
$delete_string = 'DELETE FROM ' . $table . ' WHERE ' . $column . ' LIKE %s LIMIT 1000';
|
||||
|
||||
$wpdb->query( $wpdb->prepare( $delete_string, [ '%ghu-%' ] ) );
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
<?php
|
||||
/**
|
||||
* GitHub Updater
|
||||
*
|
||||
* @author Andy Fragen
|
||||
* @license GPL-2.0+
|
||||
* @link https://github.com/afragen/github-updater
|
||||
* @package github-updater
|
||||
*/
|
||||
|
||||
namespace Fragen\GitHub_Updater\WP_CLI;
|
||||
|
||||
use WP_CLI;
|
||||
use WP_CLI_Command;
|
||||
use Fragen\Singleton;
|
||||
|
||||
// Add WP-CLI commands.
|
||||
$class = new CLI_Integration();
|
||||
WP_CLI::add_command( 'plugin install-git', [ $class, 'install_plugin' ] );
|
||||
WP_CLI::add_command( 'theme install-git', [ $class, 'install_theme' ] );
|
||||
|
||||
/**
|
||||
* Class CLI_Integration
|
||||
*/
|
||||
class CLI_Integration extends WP_CLI_Command {
|
||||
/**
|
||||
* CLI_Integration constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Off to the races.
|
||||
*/
|
||||
public function run() {
|
||||
add_filter( 'site_transient_update_plugins', [ Singleton::get_instance( 'Plugin', $this ), 'update_site_transient' ], 10, 1 );
|
||||
add_filter( 'site_transient_update_themes', [ Singleton::get_instance( 'Theme', $this ), 'update_site_transient' ], 10, 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Install plugin from GitHub, Bitbucket, GitLab, or Gitea using GitHub Updater.
|
||||
*
|
||||
* ## OPTIONS
|
||||
*
|
||||
* <uri>
|
||||
* : URI to the repo being installed
|
||||
*
|
||||
* [--branch=<branch_name>]
|
||||
* : String indicating the branch name to be installed
|
||||
* ---
|
||||
* default: master
|
||||
* ---
|
||||
*
|
||||
* [--token=<access_token>]
|
||||
* : GitHub, GitLab, or Gitea access token if not already saved
|
||||
*
|
||||
* [--bitbucket-private]
|
||||
* : Indicates a private Bitbucket repository
|
||||
*
|
||||
* [--slug=<slug>]
|
||||
* : Optional string indicating the plugin slug
|
||||
|
||||
* [--github]
|
||||
* : Optional to denote a GitHub repository
|
||||
* Required when installing from a self-hosted GitHub installation
|
||||
*
|
||||
* [--bitbucket]
|
||||
* : Optional switch to denote a Bitbucket repository
|
||||
* Required when installing from a self-hosted Bitbucket installation
|
||||
*
|
||||
* [--gitlab]
|
||||
* : Optional switch to denote a GitLab repository
|
||||
* Required when installing from a self-hosted GitLab installation
|
||||
*
|
||||
* [--gitea]
|
||||
* : Optional switch to denote a Gitea repository
|
||||
* Required when installing from a Gitea installation
|
||||
*
|
||||
* [--zipfile]
|
||||
* : Optional switch to denote a Zipfile
|
||||
* Required when installing from a Zipfile
|
||||
*
|
||||
* ## EXAMPLES
|
||||
*
|
||||
* wp plugin install-git https://github.com/afragen/my-plugin
|
||||
*
|
||||
* wp plugin install-git https://github.com/afragen/my-plugin --branch=develop --github
|
||||
*
|
||||
* wp plugin install-git https://bitbucket.org/afragen/my-private-plugin --bitbucket-private
|
||||
*
|
||||
* wp plugin install-git https://github.com/afragen/my-private-plugin --token=lks9823evalki
|
||||
*
|
||||
* @param array $args An array of $uri.
|
||||
* @param array $assoc_args Array of optional arguments.
|
||||
*
|
||||
* @subcommand install-git
|
||||
*/
|
||||
public function install_plugin( $args, $assoc_args ) {
|
||||
list($uri) = $args;
|
||||
$cli_config = $this->process_args( $uri, $assoc_args );
|
||||
Singleton::get_instance( 'Install', $this )->install( 'plugin', $cli_config );
|
||||
|
||||
$headers = parse_url( $uri, PHP_URL_PATH );
|
||||
$slug = basename( $headers );
|
||||
$this->process_branch( $cli_config, $slug );
|
||||
WP_CLI::success( sprintf( 'Plugin %s installed.', "'$slug'" ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Install theme from GitHub, Bitbucket, GitLab, or Gitea using GitHub Updater.
|
||||
*
|
||||
* ## OPTIONS
|
||||
*
|
||||
* <uri>
|
||||
* : URI to the repo being installed
|
||||
*
|
||||
* [--branch=<branch_name>]
|
||||
* : String indicating the branch name to be installed
|
||||
* ---
|
||||
* default: master
|
||||
* ---
|
||||
*
|
||||
* [--token=<access_token>]
|
||||
* : GitHub or GitLab access token if not already saved
|
||||
*
|
||||
* [--bitbucket-private]
|
||||
* : Indicates a private Bitbucket repository
|
||||
*
|
||||
* [--slug=<slug>]
|
||||
* : Optional string indicating the theme slug
|
||||
*
|
||||
* [--github]
|
||||
* : Optional to denote a GitHub repository
|
||||
* Required when installing from a self-hosted GitHub installation
|
||||
*
|
||||
* [--bitbucket]
|
||||
* : Optional switch to denote a Bitbucket repository
|
||||
* Required when installing from a self-hosted Bitbucket installation
|
||||
*
|
||||
* [--gitlab]
|
||||
* : Optional switch to denote a GitLab repository
|
||||
* Required when installing from a self-hosted GitLab installation
|
||||
*
|
||||
* [--gitea]
|
||||
* : Optional switch to denote a Gitea repository
|
||||
* Required when installing from a Gitea installation
|
||||
*
|
||||
* [--zipfile]
|
||||
* : Optional switch to denote a Zipfile
|
||||
* Required when installing from a Zipfile
|
||||
*
|
||||
* ## EXAMPLES
|
||||
*
|
||||
* wp theme install-git https://github.com/afragen/my-theme
|
||||
*
|
||||
* wp theme install-git https://bitbucket.org/afragen/my-theme --branch=develop --bitbucket
|
||||
*
|
||||
* wp theme install-git https://bitbucket.org/afragen/my-private-theme --bitbucket-private
|
||||
*
|
||||
* wp theme install-git https://github.com/afragen/my-private-theme --token=lks9823evalki
|
||||
*
|
||||
* @param array $args An array of $uri.
|
||||
* @param array $assoc_args Array of optional arguments.
|
||||
*
|
||||
* @subcommand install-git
|
||||
*/
|
||||
public function install_theme( $args, $assoc_args ) {
|
||||
list($uri) = $args;
|
||||
$cli_config = $this->process_args( $uri, $assoc_args );
|
||||
Singleton::get_instance( 'Install', $this )->install( 'theme', $cli_config );
|
||||
|
||||
$headers = parse_url( $uri, PHP_URL_PATH );
|
||||
$slug = basename( $headers );
|
||||
$this->process_branch( $cli_config, $slug );
|
||||
WP_CLI::success( sprintf( 'Theme %s installed.', "'$slug'" ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Process WP-CLI config data.
|
||||
*
|
||||
* @param string $uri URI to process.
|
||||
* @param array $assoc_args Args to process.
|
||||
*
|
||||
* @return array $cli_config
|
||||
*/
|
||||
private function process_args( $uri, $assoc_args ) {
|
||||
$token = isset( $assoc_args['token'] ) ? $assoc_args['token'] : false;
|
||||
$bitbucket_private = isset( $assoc_args['bitbucket-private'] ) ? $assoc_args['bitbucket-private'] : false;
|
||||
$cli_config = [];
|
||||
$cli_config['uri'] = $uri;
|
||||
$cli_config['private'] = $token ?: $bitbucket_private;
|
||||
$cli_config['branch'] = isset( $assoc_args['branch'] ) ? $assoc_args['branch'] : 'master';
|
||||
$cli_config['slug'] = isset( $assoc_args['slug'] ) ? $assoc_args['slug'] : null;
|
||||
|
||||
switch ( $assoc_args ) {
|
||||
case isset( $assoc_args['github'] ):
|
||||
$cli_config['git'] = 'github';
|
||||
break;
|
||||
case isset( $assoc_args['bitbucket'] ):
|
||||
$cli_config['git'] = 'bitbucket';
|
||||
break;
|
||||
case isset( $assoc_args['gitlab'] ):
|
||||
$cli_config['git'] = 'gitlab';
|
||||
break;
|
||||
case isset( $assoc_args['gitea'] ):
|
||||
$cli_config['git'] = 'gitea';
|
||||
break;
|
||||
case isset( $assoc_args['zipfile'] ):
|
||||
$cli_config['git'] = 'zipfile';
|
||||
break;
|
||||
}
|
||||
|
||||
return $cli_config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process branch setting for WP-CLI.
|
||||
*
|
||||
* @param array $cli_config Config args.
|
||||
* @param string $slug Repository slug.
|
||||
*/
|
||||
private function process_branch( $cli_config, $slug ) {
|
||||
$branch_data['github_updater_branch'] = $cli_config['branch'];
|
||||
$branch_data['repo'] = $slug;
|
||||
|
||||
Singleton::get_instance( 'Branch', $this )->set_branch_on_install( $branch_data );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use custom installer skins to display error messages.
|
||||
*/
|
||||
require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
|
||||
|
||||
/**
|
||||
* Class GitHub_Upgrader_CLI_Plugin_Installer_Skin
|
||||
*/
|
||||
class CLI_Plugin_Installer_Skin extends \Plugin_Installer_Skin {
|
||||
public function header() {
|
||||
}
|
||||
|
||||
public function footer() {
|
||||
}
|
||||
|
||||
public function error( $errors ) {
|
||||
if ( is_wp_error( $errors ) ) {
|
||||
WP_CLI::error( $errors->get_error_message() . "\n" . $errors->get_error_data() );
|
||||
}
|
||||
}
|
||||
|
||||
public function feedback( $string ) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class GitHub_Upgrader_CLI_Theme_Installer_Skin
|
||||
*/
|
||||
class CLI_Theme_Installer_Skin extends \Theme_Installer_Skin {
|
||||
public function header() {
|
||||
}
|
||||
|
||||
public function footer() {
|
||||
}
|
||||
|
||||
public function error( $errors ) {
|
||||
if ( is_wp_error( $errors ) ) {
|
||||
WP_CLI::error( $errors->get_error_message() . "\n" . $errors->get_error_data() );
|
||||
}
|
||||
}
|
||||
|
||||
public function feedback( $string ) {
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user