cgt-wp-theme/inc/class-cgt-gitea-updater.php
2026-11-12 22:07:17 +01:00

210 lines
7.1 KiB
PHP

<?php
/**
* Module de mise à jour automatique du thème depuis le dépôt public Gitea.
*
* - Interroge directement raw/branch/main/style.css sans aucun token API.
* - Télécharge /archive/main.zip lors d'une mise à jour en un clic depuis WordPress.
* - Renomme automatiquement le dossier extrait vers get_stylesheet().
* - Ne touche jamais aux réglages enregistrés en base (wp_options), menus ou médias.
*
* @package CGT_FHA
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class CGT_FHA_Gitea_Updater {
public const REPO_URL = 'https://git.cgt-fha.fr/Union_Locale_CGT_Foix_Haute-Ariege/cgt-wp-theme';
public const RAW_STYLE_URL = 'https://git.cgt-fha.fr/Union_Locale_CGT_Foix_Haute-Ariege/cgt-wp-theme/raw/branch/main/style.css';
public const PACKAGE_URL = 'https://git.cgt-fha.fr/Union_Locale_CGT_Foix_Haute-Ariege/cgt-wp-theme/archive/main.zip';
public const VERSION_TRANSIENT = 'cgt_fha_gitea_remote_version';
public const ERROR_TRANSIENT = 'cgt_fha_gitea_remote_error';
public const CACHE_TTL = 6 * HOUR_IN_SECONDS;
/**
* Enregistre les hooks natifs WordPress de vérification et d'installation des mises à jour.
*/
public static function init(): void {
add_filter( 'pre_set_site_transient_update_themes', array( __CLASS__, 'check_for_update' ) );
add_filter( 'upgrader_source_selection', array( __CLASS__, 'fix_extracted_folder_name' ), 10, 4 );
add_action( 'upgrader_process_complete', array( __CLASS__, 'purge_version_cache' ), 10, 2 );
}
/**
* Injecte l'état de mise à jour du thème dans le transient update_themes.
*
* @param mixed $transient Objet transient WordPress.
* @return mixed
*/
public static function check_for_update( $transient ) {
if ( ! is_object( $transient ) || empty( $transient->checked ) ) {
return $transient;
}
$theme_slug = get_stylesheet();
$current_version = (string) wp_get_theme( $theme_slug )->get( 'Version' );
if ( '' === $current_version ) {
return $transient;
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$force_check = ! empty( $_GET['force-check'] );
$remote_version = self::get_remote_version( $force_check );
if ( null === $remote_version || '' === $remote_version ) {
return $transient;
}
$update_data = array(
'theme' => $theme_slug,
'new_version' => $remote_version,
'url' => self::REPO_URL,
'package' => self::PACKAGE_URL,
);
if ( ! isset( $transient->response ) || ! is_array( $transient->response ) ) {
$transient->response = array();
}
if ( ! isset( $transient->no_update ) || ! is_array( $transient->no_update ) ) {
$transient->no_update = array();
}
if ( version_compare( $remote_version, $current_version, '>' ) ) {
$transient->response[ $theme_slug ] = $update_data;
unset( $transient->no_update[ $theme_slug ] );
} else {
$transient->no_update[ $theme_slug ] = $update_data;
unset( $transient->response[ $theme_slug ] );
}
return $transient;
}
/**
* Récupère la version distante dans style.css sur la branche main (avec cache Transient).
*
* @param bool $force_check Ignore le cache si une vérification manuelle est demandée.
* @return string|null
*/
private static function get_remote_version( bool $force_check = false ): ?string {
if ( ! $force_check ) {
$cached = get_transient( self::VERSION_TRANSIENT );
if ( is_string( $cached ) && '' !== $cached ) {
return $cached;
}
if ( false !== get_transient( self::ERROR_TRANSIENT ) ) {
return null;
}
}
$response = wp_remote_get(
self::RAW_STYLE_URL,
array(
'timeout' => 10,
'sslverify' => true,
'headers' => array(
'Accept' => 'text/plain',
),
'user-agent' => 'WordPress/' . get_bloginfo( 'version' ) . '; ' . home_url( '/' ),
)
);
if ( is_wp_error( $response ) || 200 !== (int) wp_remote_retrieve_response_code( $response ) ) {
set_transient( self::ERROR_TRANSIENT, 1, 15 * MINUTE_IN_SECONDS );
return null;
}
$body = (string) wp_remote_retrieve_body( $response );
if ( '' === $body || ! preg_match( '/^\s*\*?\s*Version\s*:\s*([0-9]+\.[0-9]+(?:\.[0-9]+)*(?:-[A-Za-z0-9._-]+)?)/mi', $body, $matches ) ) {
set_transient( self::ERROR_TRANSIENT, 1, 15 * MINUTE_IN_SECONDS );
return null;
}
$remote_version = trim( $matches[1] );
delete_transient( self::ERROR_TRANSIENT );
set_transient( self::VERSION_TRANSIENT, $remote_version, self::CACHE_TTL );
return $remote_version;
}
/**
* Renomme automatiquement le dossier extrait de l'archive Gitea vers get_stylesheet().
*
* @param string|WP_Error $source Chemin du dossier extrait.
* @param string $remote_source Dossier temporaire parent.
* @param WP_Upgrader $upgrader Instance du gestionnaire de mise à jour.
* @param array $hook_extra Métadonnées sur l'élément mis à jour.
* @return string|WP_Error
*/
public static function fix_extracted_folder_name( $source, $remote_source, $upgrader, $hook_extra ) {
if ( is_wp_error( $source ) || empty( $source ) || empty( $remote_source ) ) {
return $source;
}
$theme_slug = get_stylesheet();
$is_single = isset( $hook_extra['theme'] ) && $hook_extra['theme'] === $theme_slug;
$is_bulk = isset( $hook_extra['themes'] ) && is_array( $hook_extra['themes'] ) && in_array( $theme_slug, $hook_extra['themes'], true );
if ( ! $is_single && ! $is_bulk ) {
return $source;
}
// Vérification d'intégrité (indispensable en mise à jour groupée) sur le Text Domain du thème.
$style_path = trailingslashit( (string) $source ) . 'style.css';
if ( ! file_exists( $style_path ) ) {
return $source;
}
$header_chunk = file_get_contents( $style_path, false, null, 0, 2048 );
if ( false === $header_chunk || ! preg_match( '/^\s*\*?\s*Text\s+Domain\s*:\s*cgt-fha\b/mi', $header_chunk ) ) {
return $source;
}
$corrected_source = trailingslashit( (string) $remote_source ) . $theme_slug . '/';
if ( untrailingslashit( (string) $source ) === untrailingslashit( $corrected_source ) ) {
return $source;
}
global $wp_filesystem;
if ( ! $wp_filesystem ) {
require_once ABSPATH . 'wp-admin/includes/file.php';
WP_Filesystem();
}
if ( ! $wp_filesystem ) {
return $source;
}
if ( $wp_filesystem->is_dir( $corrected_source ) ) {
$wp_filesystem->delete( $corrected_source, true );
}
if ( ! $wp_filesystem->move( (string) $source, $corrected_source, true ) ) {
return new WP_Error(
'cgt_fha_rename_failed',
__( 'Impossible de renommer l\'archive du thème vers le dossier actif.', 'cgt-fha' )
);
}
return $corrected_source;
}
/**
* Purge uniquement les transients de version après une mise à jour de thème.
*
* @param WP_Upgrader $upgrader Instance du gestionnaire de mise à jour.
* @param array $hook_extra Métadonnées de la mise à jour terminée.
*/
public static function purge_version_cache( $upgrader, $hook_extra ): void {
if ( 'theme' !== ( $hook_extra['type'] ?? '' ) ) {
return;
}
delete_transient( self::VERSION_TRANSIENT );
delete_transient( self::ERROR_TRANSIENT );
}
}
CGT_FHA_Gitea_Updater::init();