File: /home/nassaugo/public_html/wp-content/plugins/git-updater/src/Git_Updater/Plugin.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\Branch;
use stdClass;
/*
* 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 {
use GU_Trait;
/**
* Holds Class Base object.
*
* @var Base
*/
protected $base;
/**
* Hold config array.
*
* @var array
*/
private $config;
/**
* Holds extra headers.
*
* @var array
*/
private static $extra_headers;
/**
* Holds options.
*
* @var array
*/
private static $options;
/**
* Rollback variable.
*
* @var string|bool
*/
protected $tag = false;
/**
* Constructor.
*/
public function __construct() {
$this->base = Singleton::get_instance( 'Base', $this );
self::$extra_headers = $this->get_class_vars( 'Base', 'extra_headers' );
self::$options = $this->get_class_vars( 'Base', 'options' );
$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 = [];
array_map(
function ( $plugin ) use ( &$paths ) {
$paths[ $plugin ] = WP_PLUGIN_DIR . "/{$plugin}";
return $paths;
},
array_keys( $plugins )
);
$repos_arr = [];
foreach ( $paths as $slug => $path ) {
$all_headers = $this->get_headers( 'plugin' );
$repos_arr[ $slug ] = get_file_data( $path, $all_headers, 'plugin' );
}
$plugins = array_filter(
$repos_arr,
function ( $repo ) {
foreach ( $repo as $key => $value ) {
if ( in_array( $key, array_keys( self::$extra_headers ), true ) && false !== stripos( $key, 'plugin' ) && ! empty( $value ) ) {
return $this->get_file_headers( $repo, 'plugin' );
}
}
}
);
$additions = apply_filters( 'gu_additions', null, $plugins, 'plugin' );
$plugins = array_merge( $plugins, (array) $additions );
ksort( $plugins );
foreach ( (array) $plugins as $slug => $plugin ) {
$git_plugin = [];
$header = null;
$key = array_filter(
array_keys( $plugin ),
function ( $key ) use ( $plugin ) {
if ( false !== stripos( $key, 'pluginuri' ) && ! empty( $plugin[ $key ] && 'PluginURI' !== $key ) ) {
return $key;
}
}
);
$key = array_pop( $key );
if ( null === $key || ! array_key_exists( $key, $all_headers ) ) {
continue;
}
$header_parts = explode( ' ', self::$extra_headers[ $key ] );
$repo_parts = $this->get_repo_parts( $header_parts[0], 'plugin' );
if ( $repo_parts['bool'] ) {
$header = $this->parse_header_uri( $plugin[ $key ] );
}
$header = $this->parse_extra_headers( $header, $plugin, $header_parts );
$current_branch = isset( $header['repo'] ) ? "current_branch_{$header['repo']}" : null;
if ( isset( self::$options[ $current_branch ] )
&& ( 'master' === self::$options[ $current_branch ] && 'master' !== $header['primary_branch'] )
) {
unset( self::$options[ $current_branch ] );
update_site_option( 'git_updater', self::$options );
}
$branch = self::$options[ $current_branch ] ?? $header['primary_branch'];
$git_plugin['type'] = 'plugin';
$git_plugin['git'] = $repo_parts['git_server'];
$git_plugin['did'] = $header['did'];
$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['slug_did'] = $git_plugin['did'] ? $git_plugin['slug'] . '-' . $this->get_did_hash( $git_plugin['did'] ) : null;
$git_plugin['file'] = $slug;
$git_plugin['branch'] = $branch;
$git_plugin['primary_branch'] = $header['primary_branch'];
$git_plugin['ci_job'] = $header['ci_job'];
$git_plugin['release_asset'] = $header['release_asset'];
$git_plugin['languages'] = $header['languages'];
$git_plugin['sections'] = [];
if ( isset( $plugin['Name'] ) ) {
$git_plugin['local_path'] = trailingslashit( dirname( $paths[ $slug ] ) );
$git_plugin['local_version'] = strtolower( $plugin['Version'] );
$git_plugin['author'] = $plugin['Author'];
$git_plugin['author_uri'] = $plugin['AuthorURI'];
$git_plugin['name'] = $plugin['Name'];
$git_plugin['homepage'] = $plugin['PluginURI'];
$git_plugin['sections']['description'] = $plugin['Description'];
$git_plugin['license'] = $plugin['License'];
$git_plugin['update_uri'] = $plugin['UpdateURI'];
$git_plugin['security'] = $plugin['Security'];
}
$git_plugin['broken'] = ( empty( $header['owner'] ) || empty( $header['repo'] ) );
$git_plugin['icons']['default'] = "https://s.w.org/plugins/geopattern-icon/{$git_plugin['slug']}.svg";
$git_plugin['banners'] = [];
// Fix branch for .git VCS.
if ( isset( $git_plugin['local_path'] ) && file_exists( $git_plugin['local_path'] . '.git/HEAD' ) ) {
$git_branch = implode( '/', array_slice( explode( '/', file_get_contents( $git_plugin['local_path'] . '.git/HEAD' ) ), 2 ) );
$git_plugin['branch'] = preg_replace( "/\r|\n/", '', $git_branch );
}
/**
* Filter config to fix repo slug.
* Eg change Gist ID to slug.
*
* @since 10.0.0
* @param array $plugin Plugin meta array.
*/
$git_plugin = apply_filters( 'gu_fix_repo_slug', $git_plugin );
$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 = [];
/**
* Filter repositories.
*
* @since 10.2.0
* @param array $this->config Array of repository objects.
*/
$config = apply_filters( 'gu_config_pre_process', $this->config );
$disable_wp_cron = (bool) apply_filters( 'gu_disable_wpcron', false );
foreach ( (array) $config as $plugin ) {
if ( ! $this->waiting_for_background_update( $plugin ) || static::is_wp_cli() || $disable_wp_cron ) {
$this->base->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 ( 'init' === current_filter()
&& ( ! is_multisite() || is_network_admin() )
) {
add_action( "after_plugin_row_{$plugin->file}", [ new Branch(), 'plugin_branch_switcher' ], 15, 1 );
}
}
$schedule_event = defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ? is_main_site() : true;
if ( $schedule_event && ! empty( $plugins ) ) {
if ( ! $disable_wp_cron && ! $this->is_cron_event_scheduled( 'gu_get_remote_plugin' ) ) {
wp_schedule_single_event( time(), 'gu_get_remote_plugin', [ $plugins ] );
}
}
if ( ! static::is_wp_cli() ) {
$this->load_pre_filters();
}
}
/**
* Load pre-update filters.
*/
public function load_pre_filters() {
add_filter( 'plugins_api', [ $this, 'plugins_api' ], 99, 3 );
add_filter( 'site_transient_update_plugins', [ $this, 'update_site_transient' ], 15, 1 );
}
/**
* Put changelog in plugins_api, return WP.org data as appropriate
*
* @param bool $result Default false.
* @param string $action The type of information being requested from the Plugin Installation API.
* @param stdClass $response Plugin API arguments.
*
* @return mixed
*/
public function plugins_api( $result, $action, $response ) {
if ( 'plugin_information' !== $action ) {
return $result;
}
$plugin = isset( $response->slug, $this->config[ $response->slug ] ) ? $this->config[ $response->slug ] : false;
// Skip if waiting for background update.
if ( $this->waiting_for_background_update( $plugin ) ) {
return $result;
}
// wp.org plugin.
if ( ! $plugin || ( ( isset( $plugin->dot_org ) && $plugin->dot_org ) && $plugin->primary_branch === $plugin->branch ) ) {
return $result;
}
$response->did = $plugin->did;
$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 ?: $plugin->local_version;
$response->sections = $plugin->sections;
// phpcs:ignore WordPress.WP.AlternativeFunctions.strip_tags_strip_tags
$response->short_description = substr( strip_tags( trim( $plugin->sections['description'] ) ), 0, 147 ) . '...';
$response->requires = $plugin->requires;
$response->requires_php = $plugin->requires_php;
$response->tested = $plugin->tested;
$response->downloaded = $plugin->downloaded ?: 0;
$response->active_installs = $response->downloaded;
$response->last_updated = $plugin->last_updated ?: '';
$response->added = $plugin->added ?: '';
$response->download_link = $plugin->download_link ?: '';
$response->banners = $plugin->banners;
$response->icons = $plugin->icons ?: [];
$response->contributors = $plugin->contributors;
$response->rating = $plugin->rating;
$response->num_ratings = $plugin->num_ratings;
return $response;
}
/**
* Hook into site_transient_update_plugins to update from GitHub.
*
* @param stdClass $transient Plugin update transient.
*
* @return mixed
*/
public function update_site_transient( $transient ) {
// needed to fix PHP 7.4 warning.
if ( ! is_object( $transient ) ) {
$transient = new stdClass();
}
/**
* Filter repositories.
*
* @since 10.2.0
* @param array $this->config Array of repository objects.
*/
$config = apply_filters( 'gu_config_pre_process', $this->config );
foreach ( (array) $config as $plugin ) {
$plugin_requires = $this->get_repo_requirements( $plugin );
$response = [
'slug' => $plugin->slug,
'plugin' => $plugin->file,
'url' => $plugin->uri,
'icons' => $plugin->icons,
'banners' => $plugin->banners,
'branch' => $plugin->branch,
'type' => "{$plugin->git}-{$plugin->type}",
'update-supported' => true,
'requires' => $plugin_requires['RequiresWP'],
'requires_php' => $plugin_requires['RequiresPHP'],
];
if ( property_exists( $plugin, 'remote_version' ) && $plugin->remote_version ) {
$response_api_checked = [
'new_version' => $plugin->remote_version,
'package' => $plugin->download_link,
'tested' => $plugin->tested,
'requires' => $plugin->requires,
'requires_php' => $plugin->requires_php,
'branches' => array_keys( $plugin->branches ),
'upgrade_notice' => isset( $plugin->upgrade_notice ) ? implode( ' ', $plugin->upgrade_notice ) : null,
];
$response = array_merge( $response, $response_api_checked );
}
if ( $this->can_update_repo( $plugin ) ) {
// Skip on RESTful updating.
// phpcs:disable WordPress.Security.NonceVerification.Recommended
if ( isset( $_GET['action'], $_GET['plugin'] )
&& 'git-updater-update' === $_GET['action']
&& $response['slug'] === $_GET['plugin']
) {
continue;
}
// phpcs:enable
// Pull update from dot org if not overriding.
if ( ! $this->override_dot_org( 'plugin', $plugin ) ) {
continue;
}
// Update download link for release_asset non-primary branches.
if ( $plugin->release_asset && $plugin->primary_branch !== $plugin->branch ) {
$response['package'] = isset( $plugin->branches[ $plugin->branch ] )
? $plugin->branches[ $plugin->branch ]['download']
: null;
}
$transient->response[ $plugin->file ] = (object) $response;
} else {
// Add repo without update to $transient->no_update for 'View details' link.
if ( ! isset( $transient->no_update[ $plugin->file ] ) ) {
$transient->no_update[ $plugin->file ] = (object) $response;
}
$overrides = apply_filters( 'gu_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['_wpnonce'], $_GET['plugin'], $_GET['rollback'] )
&& wp_verify_nonce( sanitize_key( wp_unslash( $_GET['_wpnonce'] ) ), 'upgrade-plugin_' . $plugin->file )
) {
$transient->response[ $plugin->file ] = ( new Branch() )->set_rollback_transient( 'plugin', $plugin );
}
}
if ( property_exists( $transient, 'response' ) ) {
update_site_option( 'git_updater_plugin_updates', $transient->response );
}
return $transient;
}
}