315 lines
8.1 KiB
PHP
315 lines
8.1 KiB
PHP
<?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 ) );
|
|
}
|
|
}
|
|
}
|