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>/';
$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(
'%s',
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;
}
}