Pirate Rogue, Pirate Crew and GitHub Updater
This commit is contained in:
365
plugins/github-updater/src/GitHub_Updater/Traits/API_Common.php
Normal file
365
plugins/github-updater/src/GitHub_Updater/Traits/API_Common.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
420
plugins/github-updater/src/GitHub_Updater/Traits/GHU_Trait.php
Normal file
420
plugins/github-updater/src/GitHub_Updater/Traits/GHU_Trait.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user