File: /home/nassaugo/public_html/wp-content/plugins/git-updater/src/Git_Updater/Branch.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 stdClass;
/*
* Exit if called directly.
*/
if ( ! defined( 'WPINC' ) ) {
die;
}
/**
* Class Branch
*/
class Branch {
use GU_Trait;
/**
* Holds Git Updater options
*
* @var array
*/
protected static $options;
/**
* Holds Git Updater Base class object.
*
* @var Fragen\Git_Updater\Base
*/
protected $base;
/**
* Holds rollback tag.
*
* @var string|bool
*/
protected $tag;
/**
* Holds current repo cache.
*
* @var array|bool
*/
protected $cache;
/**
* Holds data to be stored.
*
* @var string[]
*/
protected $response;
/**
* Constructor.
*/
public function __construct() {
self::$options = $this->get_class_vars( 'Fragen\Git_Updater\Base', 'options' );
$this->base = Singleton::get_instance( 'Fragen\Git_Updater\Base', $this );
}
/**
* Get the current repo branch.
*
* @access public
*
* @param stdClass $repo Repository object.
*
* @return mixed
*/
public function get_current_branch( $repo ) {
$cache = $this->get_repo_cache( $repo->slug );
$current_branch = ! empty( $cache['current_branch'] )
? $cache['current_branch']
: $repo->branch;
return $current_branch;
}
/**
* Update transient for rollback or branch switch.
*
* @param string $type plugin|theme.
* @param stdClass $repo Repo object.
*
* @return array $rollback Rollback transient.
*/
public function set_rollback_transient( $type, $repo ) {
$repo_api = Singleton::get_instance( 'API\API', $this )->get_repo_api( $repo->git, $repo );
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$this->tag = isset( $_GET['rollback'] ) ? sanitize_text_field( wp_unslash( $_GET['rollback'] ) ) : false;
$slug = 'plugin' === $type ? $repo->file : $repo->slug;
$download_link = $repo_api->construct_download_link( $this->tag );
/**
* Filter download link so developers can point to specific ZipFile
* to use as a download link during a branch switch.
*
* @since 10.0.0
*
* @param string $download_link Download URL.
* @param /stdClass $repo
* @param string $this->tag Branch or tag for rollback.
*/
$download_link = apply_filters( 'gu_post_construct_download_link', $download_link, $repo, $this->tag );
$repo->download_link = $download_link;
$rollback = [
$type => $slug,
'new_version' => $this->tag,
'url' => $repo->uri,
'package' => $repo->download_link,
'branch' => $repo->branch,
'branches' => $repo->branches,
'type' => $repo->type,
];
if ( 'plugin' === $type ) {
$rollback['slug'] = $repo->slug;
$rollback = (object) $rollback;
}
return $rollback;
}
/**
* Set current branch on branch switch.
* Exit early if not a rollback.
*
* @access public
*
* @param string $repo Repository slug.
* @return void
*/
public function set_branch_on_switch( $repo ) {
$this->cache = $this->get_repo_cache( $repo );
// phpcs:disable WordPress.Security.NonceVerification.Recommended
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$rollback = isset( $_GET['rollback'] ) ? wp_unslash( $_GET['rollback'] ) : false;
// Exit early if not a rollback, ie normal update.
if ( ! $rollback ) {
return;
}
$tag_array = isset( $this->cache['tags'] ) && is_array( $this->cache['tags'] );
$in_tag_array = $tag_array && in_array( $rollback, $this->cache['tags'], true );
if ( $in_tag_array ) {
$current_branch = $this->cache[ $repo ]['PrimaryBranch'] ?? 'master';
}
if ( ! $in_tag_array && isset( $_GET['action'], $this->cache['branches'] )
&& in_array( $_GET['action'], [ 'upgrade-plugin', 'upgrade-theme' ], true )
) {
// phpcs:enable
$current_branch = array_key_exists( $rollback, $this->cache['branches'] )
? sanitize_text_field( $rollback )
: 'master';
}
if ( isset( $current_branch ) ) {
$this->set_repo_cache( 'current_branch', $current_branch, $repo );
self::$options[ 'current_branch_' . $repo ] = $current_branch;
update_site_option( 'git_updater', self::$options );
}
}
/**
* Set current branch on install and update options.
*
* @access public
*
* @param array $install Array of install data.
*/
public function set_branch_on_install( $install ) {
$this->set_repo_cache( 'current_branch', $install['git_updater_branch'], $install['repo'] );
self::$options[ 'current_branch_' . $install['repo'] ] = $install['git_updater_branch'];
self::$options = isset( $install['options'] ) && is_array( $install['options'] )
? array_merge( self::$options, $install['options'] )
: self::$options;
update_site_option( 'git_updater', self::$options );
}
/**
* Add branch switch row to plugins page.
*
* @param string $plugin_file Plugin file.
*
* @return bool
*/
public function plugin_branch_switcher( $plugin_file ) {
if ( empty( self::$options['branch_switch'] ) ) {
return false;
}
$plugin_obj = Singleton::get_instance( 'Fragen\Git_Updater\Plugin', $this );
$config = $this->get_class_vars( 'Fragen\Git_Updater\Plugin', 'config' );
$plugin = $this->get_repo_slugs( dirname( $plugin_file ), $plugin_obj );
$this->base->get_remote_repo_meta( $config[ $plugin['slug'] ] );
$enclosure = $this->base->update_row_enclosure( $plugin_file, 'plugin', true );
$nonced_update_url = wp_nonce_url(
$this->base->get_update_url( 'plugin', 'upgrade-plugin', $plugin_file ),
'upgrade-plugin_' . $plugin_file
);
if ( ! empty( $plugin ) ) {
$id = $plugin['slug'] . '-id';
$branches = $config[ $plugin['slug'] ]->branches ?? null;
} else {
return false;
}
// Get current branch.
$repo = $config[ $plugin['slug'] ];
$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;
$branch_switch_data['release_asset'] = $repo->release_asset;
$branch_switch_data['primary_branch'] = $repo->primary_branch;
/*
* Create after_plugin_row_
*/
echo wp_kses_post( $enclosure['open'] );
$this->make_branch_switch_row( $branch_switch_data, $config );
echo wp_kses_post( $enclosure['close'] );
return true;
}
/**
* Create branch switcher row for theme multisite installation.
*
* @param string $theme_key Theme slug.
*
* @return bool
*/
public function multisite_branch_switcher( $theme_key ) {
if ( empty( self::$options['branch_switch'] ) ) {
return false;
}
$config = $this->get_class_vars( 'Fragen\Git_Updater\Theme', 'config' );
$this->base->get_remote_repo_meta( $config[ $theme_key ] );
$enclosure = $this->base->update_row_enclosure( $theme_key, 'theme', true );
$id = $theme_key . '-id';
$branches = $config[ $theme_key ]->branches ?? null;
$nonced_update_url = wp_nonce_url(
$this->base->get_update_url( 'theme', 'upgrade-theme', $theme_key ),
'upgrade-theme_' . $theme_key
);
// Get current branch.
$repo = $config[ $theme_key ];
$branch = $this->get_current_branch( $repo );
$branch_switch_data = [];
$branch_switch_data['slug'] = $theme_key;
$branch_switch_data['nonced_update_url'] = $nonced_update_url;
$branch_switch_data['id'] = $id;
$branch_switch_data['branch'] = $branch;
$branch_switch_data['branches'] = $branches;
$branch_switch_data['release_asset'] = $repo->release_asset;
$branch_switch_data['primary_branch'] = $repo->primary_branch;
/*
* Create after_theme_row_
*/
echo wp_kses_post( $enclosure['open'] );
$this->make_branch_switch_row( $branch_switch_data, $config );
echo wp_kses_post( $enclosure['close'] );
return true;
}
/**
* Display rollback/branch switcher for theme single site installation.
*
* @param stdClass $theme Theme object.
*
* @return string
*/
public function single_install_switcher( $theme ) {
$nonced_update_url = wp_nonce_url(
$this->base->get_update_url( 'theme', 'upgrade-theme', $theme->slug ),
'upgrade-theme_' . $theme->slug
);
$rollback_url = sprintf( '%s%s', $nonced_update_url, '&rollback=' );
ob_start();
if ( '1' === self::$options['branch_switch'] ) {
printf(
/* translators: 1: branch name, 2: jQuery dropdown, 3: closing tag */
'<p>' . esc_html__( 'Current branch is `%1$s`, try %2$sanother version%3$s', 'git-updater' ),
esc_attr( $theme->branch ),
'<a href="#" onclick="jQuery(\'#gu_versions\').toggle();return false;">',
'</a>.</p>'
);
?>
<div id="gu_versions" style="display:none; width: 100%;">
<label><select style="width: 60%;" onchange="if(jQuery(this).val() != '') { jQuery(this).parent().next().show(); jQuery(this).parent().next().attr('href','<?php echo esc_url( $rollback_url ); ?>'+jQuery(this).val()); } else jQuery(this).parent().next().hide();">
<option value=""><?php esc_html_e( 'Choose a Version', 'git-updater' ); ?>…</option>
<?php
// Disable branch switching to primary branch for release assets.
if ( $theme->release_asset ) {
unset( $theme->branches[ $theme->primary_branch ] );
}
if ( isset( $theme->branches ) ) {
foreach ( array_keys( $theme->branches ) as $branch ) {
echo '<option>' . esc_attr( $branch ) . '</option>';
}
}
if ( ! empty( $theme->tags ) ) {
uksort( $theme->tags, fn ( $a, $b ) => version_compare( ltrim( $b, 'v' ), trim( $a, 'v' ) ) );
/**
* Filter to return the number of tagged releases (rollbacks) in branch switching.
*
* @since 10.0.0
* @param int Number of rollbacks. Zero implies value not set.
*/
$num_rollbacks = absint( apply_filters( 'gu_number_rollbacks', 0 ) );
$tag_keys = array_keys( $theme->tags );
$rollback = 0 === $num_rollbacks
? array_slice( $tag_keys, 0, 1 )
: array_splice( $tag_keys, 0, $num_rollbacks );
foreach ( $rollback as $tag ) {
echo '<option>' . esc_attr( $tag ) . '</option>';
}
}
if ( empty( $theme->tags ) ) {
echo '<option>' . esc_html__( 'No previous tags to rollback to.', 'git-updater' ) . '</option></select></label>';
}
?>
</select></label>
<a style="display: none;" class="button-primary" href="?"><?php esc_html_e( 'Install', 'git-updater' ); ?></a>
</div>
<?php
}
return trim( ob_get_clean(), '1' );
}
/**
* Make branch switch row.
*
* @param array $data Parameters for creating branch switching row.
* @param array $config Array of repo objects.
*
* @return void
*/
public function make_branch_switch_row( $data, $config ) {
$rollback = empty( $config[ $data['slug'] ]->tags ) ? [] : $config[ $data['slug'] ]->tags;
// Make the branch switch row visually appear as if it is contained with the plugin/theme's row.
// We have to use JS for this because of the way:
// 1) the @class of the list table row is not filterabled; and
// 2) the list table CSS is written.
if ( 'plugin' === $config[ $data['slug'] ]->type ) {
$data_attr = 'data-plugin';
$file = $config[ $data['slug'] ]->file;
} else {
$data_attr = 'data-slug';
$file = $config[ $data['slug'] ]->slug;
}
echo '<script>';
// Remove the bottom "line" for the plugin's row.
printf(
"jQuery( 'tr:not([id])[" . esc_attr( $data_attr ) . "=\"%s\"]' ).addClass( 'update' );",
esc_attr( $file )
);
// Removes the bottom "line" for the shiny update row (if any).
printf(
"jQuery( 'tr[id][" . esc_attr( $data_attr ) . "=\"%s\"] td' ).css( 'box-shadow', 'none' );",
esc_attr( $file )
);
echo '</script>';
echo '<p>';
echo wp_kses_post( $this->base->get_git_icon( $file, true ) );
printf(
/* translators: 1: branch name, 2: jQuery dropdown, 3: closing tag */
esc_html__( 'Current branch is `%1$s`, try %2$sanother version%3$s', 'git-updater' ),
esc_attr( $data['branch'] ),
'<a href="#" onclick="jQuery(\'#' . esc_attr( $data['id'] ) . '\').toggle();return false;">',
'</a>.'
);
echo '</p>';
print '<ul id="' . esc_attr( $data['id'] ) . '" style="display:none; width: 100%;">';
// Disable branch switching to primary branch for release assets.
if ( $data['release_asset'] ) {
unset( $data['branches'][ $data['primary_branch'] ] );
}
/**
* Filter out branches for release assets if desired.
* Removes all branches from the branch switcher leaving only the tags.
*
* @since 10.0.0
*
* @return bool
*/
$no_release_asset_branches = (bool) apply_filters( 'gu_no_release_asset_branches', false );
$data['branches'] = $data['release_asset'] && $no_release_asset_branches ? [] : $data['branches'];
if ( null !== $data['branches'] ) {
foreach ( array_keys( $data['branches'] ) as $branch ) {
printf(
'<li><a href="%1$s%2$s" aria-label="' . esc_html__( 'Switch to branch ', 'git-updater' ) . '%3$s">%3$s</a></li>',
esc_url( $data['nonced_update_url'] ),
'&rollback=' . rawurlencode( $branch ),
esc_attr( $branch )
);
}
}
if ( ! empty( $rollback ) ) {
uksort( $rollback, fn ( $a, $b ) => version_compare( trim( $b, 'v' ), trim( $a, 'v' ) ) );
/**
* Filter to return the number of tagged releases (rollbacks) in branch switching.
*
* @since 10.0.0
* @param int Number of rollbacks. Zero implies value not set.
*/
$num_rollbacks = absint( apply_filters( 'gu_number_rollbacks', 0 ) );
$rollback_keys = array_keys( $rollback );
$rollback = 0 === $num_rollbacks
? array_slice( $rollback_keys, 0, 1 )
: array_splice( $rollback_keys, 0, $num_rollbacks );
if ( $data['release_asset'] ) {
/**
* Filter release asset rollbacks.
*
* @since 10.0.0
*
* @return array
*/
$release_asset_rollback = apply_filters( 'gu_release_asset_rollback', $rollback, $file );
if ( ! empty( $release_asset_rollback ) && is_array( $release_asset_rollback ) ) {
$rollback = $release_asset_rollback;
}
}
foreach ( $rollback as $tag ) {
printf(
'<li><a href="%1$s%2$s" aria-label="' . esc_html__( 'Switch to release ', 'git-updater' ) . '%3$s">%3$s</a></li>',
esc_url( $data['nonced_update_url'] ),
'&rollback=' . rawurlencode( $tag ),
esc_attr( $tag )
);
}
}
if ( empty( $rollback ) ) {
echo '<li>' . esc_html__( 'No previous tags to rollback to.', 'git-updater' ) . '</li>';
}
print '</ul>';
}
}