File: /home/nassaugo/public_html/wp-content/plugins/git-updater/src/Git_Updater/Theme.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 Theme
*
* Update a WordPress theme from a GitHub repo.
*
* @author Andy Fragen
* @author Seth Carstens
* @link https://github.com/WordPress-Phoenix/whitelabel-framework
* @author UCF Web Communications
* @link https://github.com/UCF/Theme-Updater
*/
class Theme {
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 themes.
$this->config = $this->get_theme_meta();
if ( null === $this->config ) {
return;
}
}
/**
* Returns an array of configurations for the known themes.
*
* @return array
*/
public function get_theme_configs() {
return $this->config;
}
/**
* Delete cache of current theme.
* This is needed in case `wp_get_theme()` is called in earlier or in a mu-plugin.
* This action results in the extra headers not being added.
*
* @link https://github.com/afragen/github-updater/issues/586
*/
private function delete_current_theme_cache() {
$cache_hash = md5( get_stylesheet_directory() );
wp_cache_delete( 'theme-' . $cache_hash, 'themes' );
}
/**
* Get details of Git-sourced themes from those that are installed.
* Populates variable array.
*
* @return array Indexed array of associative arrays of theme details.
*/
protected function get_theme_meta() {
$this->delete_current_theme_cache();
$git_themes = [];
$themes = wp_get_themes( [ 'errors' => null ] );
$paths = array_map(
function ( $theme ) {
$filepath = file_exists( "{$theme->theme_root}/{$theme->stylesheet}/style.css" )
? "{$theme->theme_root}/{$theme->stylesheet}/style.css"
: null;
return $filepath;
},
$themes
);
$paths = array_filter( $paths );
$repos_arr = [];
foreach ( $paths as $slug => $path ) {
$all_headers = $this->get_headers( 'theme' );
$repos_arr[ $slug ] = get_file_data( $path, $all_headers, 'theme' );
}
$themes = array_filter(
$repos_arr,
function ( $repo ) {
foreach ( $repo as $key => $value ) {
if ( in_array( $key, array_keys( self::$extra_headers ), true ) && false !== stripos( $key, 'theme' ) && ! empty( $value ) ) {
return $this->get_file_headers( $repo, 'theme' );
}
}
}
);
$additions = apply_filters( 'gu_additions', null, $themes, 'theme' );
$themes = array_merge( $themes, (array) $additions );
ksort( $themes );
foreach ( (array) $themes as $slug => $theme ) {
$git_theme = [];
$header = null;
$key = array_filter(
array_keys( $theme ),
function ( $key ) use ( $theme ) {
if ( false !== stripos( $key, 'themeuri' ) && ! empty( $theme[ $key ] ) & 'ThemeURI' !== $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], 'theme' );
if ( $repo_parts['bool'] ) {
$header = $this->parse_header_uri( $theme[ $key ] );
}
$header = $this->parse_extra_headers( $header, $theme, $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_theme['type'] = 'theme';
$git_theme['git'] = $repo_parts['git_server'];
$git_theme['did'] = $header['did'];
$git_theme['uri'] = "{$header['base_uri']}/{$header['owner_repo']}";
$git_theme['theme_uri'] = $header['owner_repo'];
$git_theme['enterprise'] = $header['enterprise_uri'];
$git_theme['enterprise_api'] = $header['enterprise_api'];
$git_theme['owner'] = $header['owner'];
$git_theme['slug'] = $header['repo'];
$git_theme['slug_did'] = $git_theme['did'] ? $git_theme['slug'] . '-' . $this->get_did_hash( $git_theme['did'] ) : null;
$git_theme['file'] = "{$header['repo']}/style.css";
$git_theme['branch'] = $branch;
$git_theme['primary_branch'] = $header['primary_branch'];
$git_theme['languages'] = $header['languages'];
$git_theme['ci_job'] = $header['ci_job'];
$git_theme['release_asset'] = $header['release_asset'];
if ( isset( $theme['Name'] ) ) {
$git_theme['local_path'] = trailingslashit( dirname( $paths[ $slug ] ) );
$git_theme['local_version'] = strtolower( $theme['Version'] );
$git_theme['author'] = $theme['Author'];
$git_theme['author_uri'] = $theme['AuthorURI'];
$git_theme['name'] = $theme['Name'];
$git_theme['license'] = $theme['License'];
$git_theme['homepage'] = $theme['ThemeURI'];
$git_theme['sections']['description'] = $theme['Description'];
$git_theme['update_uri'] = $theme['UpdateURI'];
$git_theme['security'] = $theme['Security'];
}
$git_theme['broken'] = ( empty( $header['owner'] ) || empty( $header['repo'] ) );
$git_theme['icons'] = [];
$git_theme['banners'] = [];
// Fix branch for .git VCS.
if ( isset( $git_theme['local_path'] ) && file_exists( $git_theme['local_path'] . '.git/HEAD' ) ) {
$git_branch = implode( '/', array_slice( explode( '/', file_get_contents( $git_theme['local_path'] . '.git/HEAD' ) ), 2 ) );
$git_plugin['branch'] = preg_replace( "/\r|\n/", '', $git_branch );
}
$git_themes[ $git_theme['slug'] ] = (object) $git_theme;
}
return $git_themes;
}
/**
* Get remote theme meta to populate $config theme objects.
* Calls to remote APIs to get data.
*/
public function get_remote_theme_meta() {
$themes = [];
/**
* 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 $theme ) {
if ( ! $this->waiting_for_background_update( $theme ) || static::is_wp_cli() || $disable_wp_cron
) {
$this->base->get_remote_repo_meta( $theme );
} else {
$themes[ $theme->slug ] = $theme;
}
/*
* Add update row to theme row, only in multisite.
*/
if ( is_multisite() ) {
add_action( 'after_theme_row', [ $this, 'remove_after_theme_row' ], 10, 1 );
if ( ! $this->tag ) {
add_action( "after_theme_row_{$theme->slug}", [ $this, 'wp_theme_update_row' ], 10, 2 );
add_action( "after_theme_row_{$theme->slug}", [ new Branch(), 'multisite_branch_switcher' ], 15, 1 );
}
}
}
$schedule_event = defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ? is_main_site() : true;
if ( $schedule_event && ! empty( $themes ) ) {
if ( ! $disable_wp_cron && ! $this->is_cron_event_scheduled( 'gu_get_remote_theme' ) ) {
wp_schedule_single_event( time(), 'gu_get_remote_theme', [ $themes ] );
}
}
if ( ! static::is_wp_cli() ) {
$this->load_pre_filters();
}
}
/**
* Load pre-update filters.
*/
public function load_pre_filters() {
if ( ! is_multisite() ) {
add_filter( 'wp_prepare_themes_for_js', [ $this, 'customize_theme_update_html' ] );
}
add_filter( 'themes_api', [ $this, 'themes_api' ], 99, 3 );
add_filter( 'site_transient_update_themes', [ $this, 'update_site_transient' ], 15, 1 );
}
/**
* Put changelog in themes_api, return WP.org data as appropriate.
*
* @param bool $result Default false.
* @param string $action The type of information being requested from the Theme Installation API.
* @param stdClass $response Theme API arguments.
*
* @return mixed
*/
public function themes_api( $result, $action, $response ) {
if ( 'theme_information' !== $action ) {
return $result;
}
$theme = $this->config[ $response->slug ] ?? false;
// Skip if waiting for background update.
if ( $this->waiting_for_background_update( $theme ) ) {
return $result;
}
// wp.org theme.
if ( ! $theme || ( isset( $theme->dot_org ) && $theme->dot_org ) ) {
return $result;
}
$response->did = $theme->did;
$response->slug = $theme->slug;
$response->name = $theme->name;
$response->homepage = $theme->homepage;
$response->donate_link = $theme->donate_link;
$response->version = $theme->remote_version;
$response->sections = $theme->sections;
$response->description = implode( "\n", $theme->sections );
$response->author = $theme->author;
$response->preview_url = $theme->theme_uri;
$response->requires = $theme->requires;
$response->tested = $theme->tested;
$response->downloaded = $theme->downloaded;
$response->last_updated = $theme->last_updated;
$response->rating = $theme->rating;
$response->num_ratings = $theme->num_ratings;
return $response;
}
/**
* Add custom theme update row, from /wp-admin/includes/update.php
* Display update details or rollback links for multisite installation.
*
* @param string $theme_key Theme slug.
* @param array $theme Array of theme data.
*
* @author Seth Carstens
*/
public function wp_theme_update_row( $theme_key, $theme ) {
$current = get_site_transient( 'update_themes' );
$themes_allowedtags = [
'a' => [
'href' => [],
'title' => [],
],
'abbr' => [ 'title' => [] ],
'acronym' => [ 'title' => [] ],
'code' => [],
'em' => [],
'strong' => [],
];
$theme_name = wp_kses( $theme['Name'], $themes_allowedtags );
// phpcs:ignore Squiz.PHP.CommentedOutCode.Found
// $wp_list_table = _get_list_table( 'WP_MS_Themes_List_Table' );
$details_url = esc_attr(
add_query_arg(
[
'tab' => 'theme-information',
'theme' => $theme_key,
'TB_iframe' => 'true',
'width' => 270,
'height' => 400,
],
self_admin_url( 'theme-install.php' )
)
);
$nonced_update_url = wp_nonce_url(
$this->base->get_update_url( 'theme', 'upgrade-theme', $theme_key ),
'upgrade-theme_' . $theme_key
);
$enclosure = $this->base->update_row_enclosure( $theme_key, 'theme' );
if ( isset( $current->response[ $theme_key ] ) ) {
$response = $current->response[ $theme_key ];
echo wp_kses_post( $enclosure['open'] );
printf(
/* translators: %s: theme name */
esc_html__( 'There is a new version of %s available.', 'git-updater' ),
esc_attr( $theme_name )
);
printf(
/* translators: %s: details URL, theme name */
' <a href="%s" class="thickbox" title="%s"> ',
esc_url( $details_url ),
esc_attr( $theme_name )
);
if ( empty( $response['package'] ) ) {
printf(
/* translators: %s: theme version */
esc_html__( 'View version %s details.', 'git-updater' ),
esc_attr( $response['new_version'] )
);
echo '</a> <em>';
esc_html_e( 'Automatic update is unavailable for this theme.', 'git-updater' );
echo '</em>';
} else {
printf(
/* translators: 1: version number, 2: closing anchor tag, 3: update URL */
esc_html__( 'View version %1$s details%2$s or %3$supdate now%2$s.', 'git-updater' ),
esc_attr( $response['new_version'] ),
'</a>',
sprintf(
/* translators: %s: theme name */
'<a href="' . esc_url( $nonced_update_url ) . '" class="update-link" aria-label="' . esc_html__( 'Update %s now', 'git-updater' ) . '">',
esc_attr( $theme_name )
)
);
}
echo wp_kses_post( $enclosure['close'] );
// phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
do_action( "in_theme_update_message-$theme_key", $theme, $response );
}
}
/**
* Remove default after_theme_row_$stylesheet.
*
* @author @grappler
*
* @param string $theme_key Theme slug.
*/
public function remove_after_theme_row( $theme_key ) {
$themes = $this->get_theme_configs();
if ( array_key_exists( $theme_key, $themes ) ) {
remove_action( "after_theme_row_$theme_key", 'wp_theme_update_row' );
}
}
/**
* Call theme messaging for single site installation.
*
* @author Seth Carstens
*
* @param array $prepared_themes Array of prepared themes.
*
* @return array
*/
public function customize_theme_update_html( $prepared_themes ) {
foreach ( (array) $this->config as $theme ) {
if ( empty( $prepared_themes[ $theme->slug ] ) ) {
continue;
}
if ( ! empty( $prepared_themes[ $theme->slug ]['hasUpdate'] ) ) {
$prepared_themes[ $theme->slug ]['update'] = $this->append_theme_actions_content( $theme );
} else {
$prepared_themes[ $theme->slug ]['description'] .= $this->append_theme_actions_content( $theme );
}
$ignore = $this->get_class_vars( 'Ignore', 'repos' );
if ( ! array_key_exists( $theme->slug, $ignore ) ) {
$prepared_themes[ $theme->slug ]['description'] .= ( new Branch() )->single_install_switcher( $theme );
}
// Add git host icon to theme panel.
$prepared_themes[ $theme->slug ]['description'] .= $this->base->get_git_icon( $theme->slug, false );
}
return $prepared_themes;
}
/**
* Create theme update messaging for single site installation.
*
* @author Seth Carstens
*
* @access protected
*
* @param stdClass $theme Theme object.
*
* @return string (content buffer)
*/
protected function append_theme_actions_content( $theme ) {
$details_url = esc_attr(
add_query_arg(
[
'tab' => 'theme-information',
'theme' => $theme->slug,
'TB_iframe' => 'true',
'width' => 270,
'height' => 400,
],
self_admin_url( 'theme-install.php' )
)
);
$nonced_update_url = wp_nonce_url(
$this->base->get_update_url( 'theme', 'upgrade-theme', $theme->slug ),
'upgrade-theme_' . $theme->slug
);
$current = get_site_transient( 'update_themes' );
/**
* Display theme update links.
*/
ob_start();
if ( isset( $current->response[ $theme->slug ] ) ) {
?>
<p>
<strong>
<?php
printf(
/* translators: %s: theme name */
esc_html__( 'There is a new version of %s available.', 'git-updater' ),
esc_attr( $theme->name )
);
printf(
' <a href="%s" class="thickbox open-plugin-details-modal" title="%s">',
esc_url( $details_url ),
esc_attr( $theme->name )
);
if ( ! empty( $current->response[ $theme->slug ]['package'] ) ) {
printf(
/* translators: 1: version number, 2: closing anchor tag, 3: update URL */
esc_html__( 'View version %1$s details%2$s or %3$supdate now%2$s.', 'git-updater' ),
$theme->remote_version = isset( $theme->remote_version ) ? esc_attr( $theme->remote_version ) : null,
'</a>',
sprintf(
/* translators: %s: theme name */
'<a aria-label="' . esc_html__( 'Update %s now', 'git-updater' ) . '" id="update-theme" data-slug="' . esc_attr( $theme->slug ) . '" href="' . esc_url( $nonced_update_url ) . '">',
esc_attr( $theme->name )
)
);
} else {
printf(
/* translators: 1: version number, 2: closing anchor tag, 3: update URL */
esc_html__( 'View version %1$s details%2$s.', 'git-updater' ),
$theme->remote_version = isset( $theme->remote_version ) ? esc_attr( $theme->remote_version ) : null,
'</a>'
);
printf(
/* translators: %s: opening/closing paragraph and italic tags */
esc_html__( '%1$sAutomatic update is unavailable for this theme.%2$s', 'git-updater' ),
'<p><i>',
'</i></p>'
);
}
?>
</strong>
</p>
<?php
}
return trim( ob_get_clean(), '1' );
}
/**
* Hook into site_transient_update_themes to update.
* Finds newest tag and compares to current tag.
*
* @param array $transient Theme update transient.
*
* @return array|stdClass
*/
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 $theme ) {
$theme_requires = $this->get_repo_requirements( $theme );
$response = [
'theme' => $theme->slug,
'url' => $theme->uri,
'branch' => $theme->branch,
'type' => "{$theme->git}-{$theme->type}",
'update-supported' => true,
'requires' => $theme_requires['RequiresWP'],
'requires_php' => $theme_requires['RequiresPHP'],
];
if ( property_exists( $theme, 'remote_version' ) && $theme->remote_version ) {
$response_api_checked = [
'new_version' => $theme->remote_version,
'package' => $theme->download_link,
'tested' => $theme->tested,
'requires' => $theme->requires,
'requires_php' => $theme->requires_php,
'branches' => array_keys( $theme->branches ),
'upgrade_notice' => isset( $theme->upgrade_notice ) ? implode( ' ', $theme->upgrade_notice ) : null,
];
$response = array_merge( $response, $response_api_checked );
}
if ( $this->can_update_repo( $theme ) ) {
// Skip on RESTful updating.
// phpcs:disable WordPress.Security.NonceVerification.Recommended
if ( isset( $_GET['action'], $_GET['theme'] )
&& 'git-updater-update' === $_GET['action']
&& $response['theme'] === $_GET['theme']
) {
continue;
}
// phpcs:enable
// Pull update from dot org if not overriding.
if ( ! $this->override_dot_org( 'theme', $theme ) ) {
continue;
}
// Update download link for release_asset non-primary branches.
if ( $theme->release_asset && $theme->primary_branch !== $theme->branch ) {
$response['package'] = isset( $theme->branches[ $theme->branch ] )
? $theme->branches[ $theme->branch ]['download']
: null;
}
$transient->response[ $theme->slug ] = $response;
} else {
// Add repo without update to $transient->no_update for Auto-updates link.
if ( ! isset( $transient->no_update[ $theme->slug ] ) ) {
$transient->no_update[ $theme->slug ] = $response;
}
$overrides = apply_filters( 'gu_override_dot_org', [] );
if ( isset( $transient->response[ $theme->slug ] ) && in_array( $theme->slug, $overrides, true ) ) {
unset( $transient->response[ $theme->slug ] );
}
}
// Set transient for rollback.
if ( isset( $_GET['_wpnonce'], $_GET['theme'], $_GET['rollback'] )
&& wp_verify_nonce( sanitize_key( wp_unslash( $_GET['_wpnonce'] ) ), 'upgrade-theme_' . $theme->slug )
) {
$transient->response[ $theme->slug ] = ( new Branch() )->set_rollback_transient( 'theme', $theme );
}
}
if ( property_exists( $transient, 'response' ) ) {
update_site_option( 'git_updater_theme_updates', $transient->response );
}
return $transient;
}
}