File: /wordpress/plugins/wp-cloud-client/1.2.2/src/Support/RollbackManager.php
<?php
declare(strict_types=1);
namespace VPlugins\WPCloudClient\Support;
/**
* Manages rollback state for plugins and themes.
*
* Before each update the current version is persisted in a WordPress option.
* If the update causes a regression, the trigger action can be called with
* { "rollback": true } to restore the previous version from wordpress.org.
*/
final class RollbackManager {
/** WordPress option key that stores the pre-update versions. */
private const OPTION_KEY = 'wpcc_pre_update_versions';
/** Legacy option key used before multi-type support. */
private const LEGACY_OPTION_KEY = 'wpcc_pre_update_version';
/**
* Persist the current version and download URL before an update begins.
*
* Call this immediately before invoking the WordPress upgrader so that the
* version is available for rollback even if the current request ends
* abnormally after the files have been replaced.
*
* @param string $type Update type: 'plugin'|'theme'.
* @param string|null $slug Item identifier (slug for plugins/themes, null for core).
* @param string|null $version Version to record. If null, VPLUGINS_WPCC_VERSION is used.
* @param string|null $downloadUrl Download URL for reinstalling this version during rollback.
* @return void
*/
public function recordPreUpdateVersion( string $type = 'plugin', ?string $slug = null, ?string $version = null, ?string $downloadUrl = null ): void {
$key = $this->buildVersionKey( $type, $slug );
if ( null === $version ) {
$version = VPLUGINS_WPCC_VERSION;
}
$versions = (array) get_option( self::OPTION_KEY, [] );
$versions[ $key ] = [
'version' => $version,
'download_url' => $downloadUrl ?? '',
];
update_option( self::OPTION_KEY, $versions, false );
}
/**
* Return true if a rollback target version has been recorded.
*
* @param string $type Update type: 'plugin'|'theme'.
* @param string|null $slug Item identifier (slug for plugins/themes, null for core).
* @return bool
*/
public function canRollback( string $type = 'plugin', ?string $slug = null ): bool {
return null !== $this->getPreviousVersion( $type, $slug );
}
/**
* Return the version string that was in place before the last update.
*
* @param string $type Update type: 'plugin'|'theme'.
* @param string|null $slug Item identifier (slug for plugins/themes, null for core).
* @return string|null Version string, or null if no rollback state exists.
*/
public function getPreviousVersion( string $type = 'plugin', ?string $slug = null ): ?string {
$key = $this->buildVersionKey( $type, $slug );
$versions = (array) get_option( self::OPTION_KEY, [] );
$entry = $versions[ $key ] ?? null;
if ( null === $entry ) {
return null;
}
// Handle both old (string) and new (array) formats.
if ( is_array( $entry ) ) {
$version = (string) ( $entry['version'] ?? '' );
} else {
$version = (string) $entry;
}
return '' !== $version ? $version : null;
}
/**
* Return the download URL recorded for rollback.
*
* @param string $type Update type: 'plugin'|'theme'.
* @param string|null $slug Item identifier (slug for plugins/themes, null for core).
* @return string|null Download URL, or null if not available.
*/
public function getDownloadUrl( string $type = 'plugin', ?string $slug = null ): ?string {
$key = $this->buildVersionKey( $type, $slug );
$versions = (array) get_option( self::OPTION_KEY, [] );
$entry = $versions[ $key ] ?? null;
if ( ! is_array( $entry ) ) {
return null;
}
$url = (string) ( $entry['download_url'] ?? '' );
return '' !== $url ? $url : null;
}
/**
* Remove the recorded pre-update version.
*
* Call this after a successful rollback or when the recorded version is no
* longer needed (e.g., after confirming a new deployment is stable).
*
* @param string $type Update type: 'plugin'|'theme'.
* @param string|null $slug Item identifier (slug for plugins/themes, null for core).
* @return void
*/
public function clearPreviousVersion( string $type = 'plugin', ?string $slug = null ): void {
$key = $this->buildVersionKey( $type, $slug );
$versions = (array) get_option( self::OPTION_KEY, [] );
unset( $versions[ $key ] );
if ( empty( $versions ) ) {
delete_option( self::OPTION_KEY );
} else {
update_option( self::OPTION_KEY, $versions, false );
}
}
/**
* Migrate data from the legacy single-value option key.
*
* Early versions stored a single version string under 'wpcc_pre_update_version'.
* This method migrates that data to the new multi-type format and removes the old key.
*
* @return void
*/
public function migrateFromLegacyKey(): void {
$old = get_option( self::LEGACY_OPTION_KEY );
if ( false === $old ) {
return;
}
$versions = (array) get_option( self::OPTION_KEY, [] );
if ( is_array( $old ) ) {
$versions = array_merge( $old, $versions );
} else {
$versions['plugin'] = [
'version' => (string) $old,
'download_url' => '',
];
}
update_option( self::OPTION_KEY, $versions, false );
delete_option( self::LEGACY_OPTION_KEY );
}
/**
* Build a unique key for storing version state.
*
* @param string $type Update type.
* @param string|null $slug Item identifier.
* @return string Unique key for storage.
*/
private function buildVersionKey( string $type, ?string $slug ): string {
if ( null === $slug || '' === $slug ) {
return $type;
}
return "{$type}:{$slug}";
}
}