File: //proc/thread-self/cwd/wp-content/plugins/git-updater/src/Git_Updater/Base.php
<?php
/**
* Git Updater
*
* @author Andy Fragen
* @license MIT
* @link https://github.com/afragen/git-updater
* @package git-updater
*/
namespace Fragen\Git_Updater;
use Fragen\Singleton;
use Fragen\Git_Updater\Traits\GU_Trait;
use Fragen\Git_Updater\Traits\Basic_Auth_Loader;
use Fragen\Git_Updater\API\Language_Pack_API;
use Fragen\Git_Updater\Branch;
use Plugin_Upgrader;
use ReflectionClass;
use stdClass;
use Theme_Upgrader;
use WP_Error;
use WP_Upgrader;
/*
* 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 GU_Trait;
use 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;
/**
* Holds plugin data.
*
* @var stdClass
*/
protected $plugin;
/**
* Holds theme data.
*
* @var stdClass
*/
protected $theme;
/**
* Constructor.
*/
public function __construct() {
static::$options = get_site_option( 'git_updater', [] );
$this->set_installed_apis();
$this->add_extra_headers();
}
/**
* Set boolean for installed API classes.
*/
protected function set_installed_apis() {
/**
* Filter to add active git servers.
*
* @since 10.0.0
* @param array static::$git_servers Array of git servers.
*/
static::$git_servers = apply_filters( 'gu_git_servers', static::$git_servers );
/**
* Filter to add installed APIs.
*
* @since 10.0.0
* @param array static::$installed_apis Array of installed APIs.
*/
static::$installed_apis = apply_filters( 'gu_installed_apis', static::$installed_apis );
}
/**
* Load Plugin, Theme, and Settings with correct capabiltiies and on selective admin pages.
*
* @return bool
*/
public function load() {
if ( Singleton::get_instance( 'Init', $this )->can_update() ) {
Singleton::get_instance( 'Settings', $this )->run();
Singleton::get_instance( 'Add_Ons', $this )->load_hooks();
}
// Run Git Updater upgrade functions.
( new GU_Upgrade() )->run();
if ( $this->is_current_page( [ 'plugins.php', 'themes.php', 'theme-install.php' ] ) ) {
// Load plugin stylesheet.
add_action(
'admin_enqueue_scripts',
function () {
wp_register_style( 'git-updater', plugins_url( basename( dirname( __DIR__, 2 ) ) ) . '/css/git-updater.css', [], $this->get_plugin_version() );
wp_enqueue_style( 'git-updater' );
}
);
}
if ( isset( $_POST['_wpnonce'], $_POST['gu_refresh_cache'] ) && wp_verify_nonce( sanitize_key( wp_unslash( $_POST['_wpnonce'] ) ), 'gu_refresh_cache' ) ) {
/**
* Fires later in cycle when Refreshing Cache.
*
* @since 10.0.0
*/
do_action( 'gu_refresh_transients' );
}
$this->get_meta_plugins();
$this->get_meta_themes();
return true;
}
/**
* Performs actual plugin metadata fetching.
*/
public function get_meta_plugins() {
Singleton::get_instance( 'Plugin', $this )->get_remote_plugin_meta();
}
/**
* Performs actual theme metadata fetching.
*/
public function get_meta_themes() {
Singleton::get_instance( 'Theme', $this )->get_remote_theme_meta();
}
/**
* 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( 'gu_get_remote_plugin', [ $this, 'run_cron_batch' ], 10, 1 );
add_action( 'gu_get_remote_theme', [ $this, 'run_cron_batch' ], 10, 1 );
}
/**
* Allows developers to use 'gu_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() {
/**
* Filter the plugin options.
*
* @since 10.0.0
*
* @return array
*/
$config = apply_filters( 'gu_set_options', [] );
foreach ( array_keys( self::$git_servers ) as $git ) {
unset( $config[ "{$git}_access_token" ], $config[ "{$git}_enterprise_token" ] );
}
if ( ! empty( $config ) ) {
$config = $this->sanitize( $config );
self::$options = array_merge( get_site_option( 'git_updater' ), $config );
update_site_option( 'git_updater', self::$options );
}
}
/**
* Make and return extra headers.
*
* @return array
*/
public function add_extra_headers() {
$gu_extra_headers = [
'RequiresWP' => 'Requires WP',
'ReleaseAsset' => 'Release Asset',
'PrimaryBranch' => 'Primary Branch',
'PluginID' => 'Plugin ID',
'ThemeID' => 'Theme ID',
'Security' => 'Security',
];
$uri_types = [
'PluginURI' => ' Plugin URI',
'ThemeURI' => ' Theme URI',
];
foreach ( self::$git_servers as $server ) {
foreach ( $uri_types as $uri_key => $uri_value ) {
$gu_extra_headers[ $server . $uri_key ] = $server . $uri_value;
}
foreach ( self::$extra_repo_headers as $header_key => $header_value ) {
$gu_extra_headers[ $server . $header_key ] = $server . ' ' . $header_value;
}
}
self::$extra_headers = array_unique( array_merge( self::$extra_headers, $gu_extra_headers ) );
ksort( self::$extra_headers );
return self::$extra_headers;
}
/**
* Runs on wp-cron job to get remote repo meta in background.
*
* @param array $batches Cron event args, array of repo objects.
*/
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 Repo object.
*
* @return bool|stdClass
*/
public function get_remote_repo_meta( $repo ) {
// Exit if non-privileged user and bypassing wp-cron.
/**
* Exit if bypassing wp-cron.
*
* @since 10.0.0
*
* @param bool
*/
$disable_wp_cron = (bool) apply_filters( 'gu_disable_wpcron', false );
if ( $disable_wp_cron && ! Singleton::get_instance( 'Init', $this )->can_update() ) {
return;
}
$file = 'style.css';
if ( false !== stripos( $repo->type, 'plugin' ) ) {
$file = basename( $repo->file );
}
$repo_api = Singleton::get_instance( 'API\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() ) {
$repo_api->get_repo_contents();
$repo_api->get_remote_readme();
$repo_api->get_remote_changes( '' );
$repo_api->get_repo_meta();
$repo_api->get_repo_assets();
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->add_assets( $repo_api );
}
do_action( 'get_remote_repo_meta', $repo, $repo_api );
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace
$caller = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 2 )[1]['class'];
// Return data if being called from Git Updater REST API.
if ( 'Fragen\Git_Updater\REST\REST_API' === $caller
|| 'Fragen\Git_Updater\Init' === $caller
) {
return $repo;
}
return true;
}
/**
* Set default values for plugin/theme.
*
* @param string $type (plugin|theme).
*/
protected function set_defaults( $type ) {
if ( ! isset( $this->$type->slug ) ) {
$this->$type = new stdClass();
$this->$type->slug = '';
} elseif ( ! isset( self::$options[ $this->$type->slug ] ) ) {
self::$options[ $this->$type->slug ] = '';
add_site_option( 'git_updater', self::$options );
}
$this->$type->remote_version = '0.0.0';
$this->$type->newest_tag = '0.0.0';
$this->$type->download_link = '';
$this->$type->tags = [];
$this->$type->branches = [];
$this->$type->requires = '';
$this->$type->tested = '';
$this->$type->donate_link = '';
$this->$type->contributors = [];
$this->$type->downloaded = 0;
$this->$type->last_updated = '';
$this->$type->added = '';
$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;
}
/**
* Add assets from remote repo.
*
* @param GitHub_API|Bitbucket_API|GitLab_API|Gitea_API $repo API object.
*
* @return void
*/
public function add_assets( $repo ) {
$assets = $repo->response['assets'] ?? false;
if ( ! $assets || is_object( $assets ) ) {
return;
}
$banner_sizes = [
'low_png' => 'banner-772x250.png',
'low_jpg' => 'banner-772x250.jpg',
'low_png_rtl' => 'banner-772x250-rtl.png',
'low_jpg_rtl' => 'banner-772x250-rtl.jpg',
'high_png' => 'banner-1544x500.png',
'high_jpg' => 'banner-1544x500.jpg',
'high_png_rtl' => 'banner-1544x500-rtl.png',
'high_jpg_rtl' => 'banner-1544x500-rtl.jpg',
];
$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 ( $banner_sizes as $key => $size ) {
if ( isset( $assets[ $size ] ) ) {
$key = preg_replace( '/_png|_jpg|_rtl/', '', $key );
$repo->type->banners[ $key ] = $assets[ $size ];
}
}
foreach ( $icons as $key => $filename ) {
if ( isset( $assets[ $filename ] ) ) {
$key = preg_replace( '/_png|_jpg/', '', $key );
$repo->type->icons[ $key ] = $assets[ $filename ];
}
}
}
/**
* Used for renaming of sources to ensure correct directory name.
*
* @since WordPress 4.4.0 The $hook_extra parameter became available.
*
* @param string $source File path of $source.
* @param string $remote_source File path of $remote_source.
* @param WP_Upgrader $upgrader An Upgrader object.
* @param array $hook_extra Array of hook data.
*
* @return string|WP_Error
*/
public function upgrader_source_selection( string $source, string $remote_source, WP_Upgrader $upgrader, $hook_extra = [] ) {
global $wp_filesystem;
$slug = null;
$repo = null;
$new_source = null;
$upgrader_object = null;
$remote_source = $wp_filesystem->wp_content_dir() . 'upgrade/';
/*
* 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;
} elseif ( wp_doing_ajax() && check_ajax_referer( 'updates' ) && isset( $_POST['slug'] ) ) {
$slug = sanitize_key( wp_unslash( $_POST['slug'] ) );
$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;
} elseif ( wp_doing_ajax() && check_ajax_referer( 'updates' ) && isset( $_POST['slug'] ) ) {
$slug = sanitize_key( wp_unslash( $_POST['slug'] ) );
$new_source = trailingslashit( $remote_source ) . $slug;
}
}
$repo = $this->get_repo_slugs( $slug, $upgrader_object );
// Not Git Updater plugin/theme.
// phpcs:ignore WordPress.Security.NonceVerification.Missing
if ( ! isset( $_POST['git_updater_repo'] ) && empty( $repo ) ) {
return $source;
}
( new Branch() )->set_branch_on_switch( $slug );
/*
* Remote install source.
*/
$install_options = $this->get_class_vars( 'Fragen\Git_Updater\Install', 'install' );
if ( empty( $repo ) && isset( $install_options['git_updater_install_repo'] ) ) {
$slug = $install_options['git_updater_install_repo'];
$new_source = trailingslashit( $remote_source ) . $slug;
self::$options['remote_install'] = true;
}
$new_source = $this->fix_misnamed_directory( $new_source, $remote_source, $upgrader_object, $slug );
if ( basename( dirname( $source ) ) === basename( $new_source ) ) {
$new_source = $source;
}
if ( trailingslashit( strtolower( $source ) ) !== trailingslashit( strtolower( $new_source ) ) ) {
$result = move_dir( $source, $new_source, true );
if ( is_wp_error( $result ) ) {
return $result;
}
}
// Clean up $new_source directory.
add_action( 'upgrader_install_package_result', [ $this, 'delete_upgrade_source' ], 10, 1 );
return trailingslashit( $new_source );
}
/**
* Correctly rename an initially misnamed directory.
* This usually occurs when initial installation not using Git Updater.
* May cause plugin/theme deactivation.
*
* @param string $new_source File path of $new_source.
* @param string $remote_source File path of $remote_source.
* @param Plugin|Theme $upgrader_object An Upgrader object.
* @param string $slug Repository slug.
*
* @return string $new_source
*/
private function fix_misnamed_directory( $new_source, $remote_source, $upgrader_object, $slug ) {
$config = $upgrader_object instanceof Plugin ? $upgrader_object->get_plugin_configs() : [];
$config = $upgrader_object instanceof Theme ? $upgrader_object->get_theme_configs() : $config;
$parts = explode( '-', $slug );
$maybe_did_hash = array_pop( $parts );
$maybe_slug = implode( '-', $parts );
if ( isset( $config[ $maybe_slug ], $config[ $maybe_slug ]->slug_did ) ) {
if ( $maybe_did_hash === $this->get_did_hash( $config[ $maybe_slug ]->did ) ) {
return trailingslashit( $remote_source ) . $config[ $maybe_slug ]->slug_did;
}
}
if ( basename( $new_source ) === $slug ) {
return $new_source;
}
if ( ! array_key_exists( $slug, (array) $config ) && ! isset( self::$options['remote_install'] ) ) {
$repo = $this->get_repo_slugs( $slug, $upgrader_object );
$repo['slug'] = $repo['slug'] ?? $slug;
$slug = $slug === $repo['slug'] ? $slug : $repo['slug'];
$new_source = trailingslashit( $remote_source ) . $slug;
}
return $new_source;
}
/**
* Return correct update row opening and closing tags for Shiny Updates.
*
* @param string $repo_name Repo name.
* @param string $type plugin|theme.
* @param bool $branch_switcher Boolean for using branch switcher, default is false.
*
* @return array
*/
public 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';
$active = '';
if ( 'plugin' === $type ) {
$repo_base = dirname( $repo_name );
if ( is_plugin_active( $repo_name ) ) {
$active = ' active';
}
} else {
$theme = wp_get_theme( $repo_name );
if ( $theme->is_allowed( 'network' ) ) {
$active = ' active';
}
}
$multisite_theme_open = ! $branch_switcher && 'theme' === $type ? " id='{$repo_name}' data-slug='{$repo_name}'" : null;
$open = '<tr class="git-updater plugin-update-tr' . $active . '"' . $multisite_theme_open . '>
<td colspan="' . $wp_list_table->get_column_count() . '" class="plugin-update colspanchange">
<div class="">';
$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;
}
/**
* Generate update URL.
*
* @param string $type ( plugin or theme ).
* @param string $action Query action.
* @param string $repo_name Repo name.
*
* @return string
*/
public 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;
}
/**
* Add git host based icons.
*
* @param array $links Row meta action links.
* @param string $file Plugin or theme file.
*
* @return array $links
*/
public function row_meta_icons( $links, $file ) {
$icon = $this->get_git_icon( $file, false );
if ( ! is_null( $icon ) ) {
$links[] = $icon;
}
return $links;
}
/**
* Get git host based icon as an HTML img element.
*
* @param string $file Plugin or theme file.
* @param bool $add_padding Whether or not to adding padding to the icon.
* When used in row meta, icon should not have padding;
* when used in branch switching row, icon should have padding.
* @return string
*/
public function get_git_icon( $file, $add_padding ) {
$type = str_contains( current_filter(), 'plugin' ) ? 'plugin' : 'theme';
$type_cap = ucfirst( $type );
$filepath = 'plugin' === $type ? WP_PLUGIN_DIR . "/$file" : get_theme_root() . "/$file/style.css";
$git['headers'] = [ "GitHub{$type_cap}URI" => "GitHub {$type_cap} URI" ];
$git['icons'] = [ 'github' => basename( dirname( __DIR__, 2 ) ) . '/assets/github-logo.svg' ];
$git = apply_filters( 'gu_get_git_icon_data', $git, $type_cap );
// Skip on mu-plugins or drop-ins.
$file_data = file_exists( $filepath ) ? get_file_data( $filepath, $git['headers'] ) : [];
/**
* Filter to add plugins not containing appropriate header line.
* Insert repositories added via Git Updater Additions plugin.
*
* @since 10.0.0
* @access public
* @link https://github.com/afragen/git-updater-additions
*
* @param array Listing of plugins/themes to add.
* Default null.
* @param array Listing of all plugins/themes.
* @param string $type Type being passed, plugin|theme'.
*/
$additions = apply_filters( 'gu_additions', null, [], $type );
foreach ( (array) $additions as $slug => $headers ) {
if ( $slug === $file ) {
$file_data = array_merge( $file_data, $headers );
break;
}
}
foreach ( $file_data as $key => $value ) {
if ( ! empty( $value ) ) {
$githost = str_replace( "{$type_cap}URI", '', $key );
$padding = is_rtl() ? 'padding-left: 6px;' : 'padding-right: 6px;';
$icon = sprintf(
'<img title="%1$s" src="%2$s" style="vertical-align:text-bottom;%3$s" height="16" width="16" alt="%4$s" />',
__( 'Updates via Git Updater', 'git-updater' ),
plugins_url( $git['icons'][ strtolower( $githost ) ] ),
$add_padding ? $padding : '',
$githost
);
break;
}
}
return $icon ?? null;
}
}