-
Notifications
You must be signed in to change notification settings - Fork 62
feat: refactor upgrade routine and add v0.6.0 migrations #321
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+882
−160
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
a3f61fb
feat: refactor upgrade routine and add v0.6.0 migrations
justlevine 7224768
tests: fix test location
justlevine acde4f9
fix: copilot feedback
justlevine ca7795f
Update includes/Admin/Upgrades/V0_6_0.php
justlevine 1bc71f8
Update includes/Admin/Upgrades/V0_6_0.php
justlevine 129453c
tests: backfill activation tests
justlevine 930e1b8
tests: backfill tests for Upgrades
justlevine 1b3e7a5
tests: backfill V0_6_0Test
justlevine 3c64c5d
Merge branch 'develop' into feat/upgrade-0.6.0
justlevine File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| <?php | ||
| /** | ||
| * Runs on plugin activation. | ||
| * | ||
| * @package WordPress\AI\Admin | ||
| * @since x.x.x | ||
| */ | ||
|
|
||
| declare( strict_types=1 ); | ||
|
|
||
| namespace WordPress\AI\Admin; | ||
|
|
||
| // Exit if accessed directly. | ||
| defined( 'ABSPATH' ) || exit; | ||
|
|
||
| /** | ||
| * Class - Activation. | ||
| * | ||
| * @internal | ||
| * | ||
| * @since x.x.x | ||
| */ | ||
| final class Activation { | ||
| /** | ||
| * Runs on plugin activation. | ||
| * | ||
| * @since x.x.x | ||
| */ | ||
| public static function activation_callback(): void { | ||
| // Check and run any pending upgrades. | ||
| Upgrades::do_upgrades(); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,140 @@ | ||
| <?php | ||
| /** | ||
| * Handles any routines or migrations necessary when upgrading to a new version of the plugin. | ||
| * | ||
| * @package WordPress\AI\Admin | ||
| * @since x.x.x | ||
| */ | ||
|
|
||
| declare( strict_types=1 ); | ||
|
|
||
| namespace WordPress\AI\Admin; | ||
|
|
||
| use WordPress\AI\Admin\Upgrades\V0_5_0; | ||
| use WordPress\AI\Admin\Upgrades\V0_6_0; | ||
|
|
||
| // Exit if accessed directly. | ||
| defined( 'ABSPATH' ) || exit; | ||
|
|
||
| /** | ||
| * Class - Upgrades. | ||
| * | ||
| * @internal | ||
| * | ||
| * @since x.x.x | ||
| */ | ||
| final class Upgrades { | ||
| /** | ||
| * The key to store the version. | ||
| * | ||
| * @since x.x.x | ||
| */ | ||
| private const VERSION_OPTION_KEY = 'wpai_version'; | ||
|
|
||
| /** | ||
| * The key to store failed upgrade information for the admin notice. | ||
| * | ||
| * @since x.x.x | ||
| */ | ||
| private const FAILED_UPGRADE_OPTION_KEY = 'wpai_failed_upgrade_message'; | ||
|
|
||
| /** | ||
| * Upgrade classes. | ||
| * | ||
| * New upgrade routine classes should be added here, in order of oldest to newest. | ||
| * | ||
| * @since x.x.x | ||
| * | ||
| * @var class-string<\WordPress\AI\Admin\Upgrades\Abstract_Upgrade>[] | ||
| */ | ||
| private const UPGRADE_CLASSES = array( // phpcs:ignore SlevomatCodingStandard.Classes.DisallowMultiConstantDefinition -- This is used as a single const. | ||
| V0_5_0::class, | ||
| V0_6_0::class, | ||
| ); | ||
|
|
||
| /** | ||
| * Initialize the class. | ||
| * | ||
| * @since x.x.x | ||
| */ | ||
| public function init(): void { | ||
| // Runs as a fallback, in case the activation hook is missed. | ||
| add_action( 'admin_init', array( $this, 'do_upgrades' ) ); | ||
|
|
||
| add_action( 'admin_notices', array( $this, 'failed_upgrade_notice' ) ); | ||
| } | ||
|
|
||
| /** | ||
| * Checks for and runs any pending upgrades. | ||
| * | ||
| * @since x.x.x | ||
| */ | ||
| public static function do_upgrades(): void { | ||
| $db_version = get_option( self::VERSION_OPTION_KEY, '' ); | ||
|
|
||
| foreach ( self::UPGRADE_CLASSES as $upgrade_class ) { | ||
| /** | ||
| * Skip upgrades for newer versions. | ||
| * @todo Remove the !empty() check once we no long need to support < v0.5.0 and '' means a new install. | ||
| */ | ||
| if ( ! empty( $db_version ) && version_compare( $db_version, $upgrade_class::$version, '>=' ) ) { | ||
| continue; | ||
| } | ||
|
|
||
| $upgrade = new $upgrade_class( $db_version ); | ||
| $result = $upgrade->run(); | ||
justlevine marked this conversation as resolved.
Show resolved
Hide resolved
justlevine marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // Store the error message and stop if the upgrade failed. | ||
| if ( is_wp_error( $result ) ) { | ||
| update_option( | ||
| self::FAILED_UPGRADE_OPTION_KEY, | ||
| array( | ||
| 'version' => $upgrade_class::$version, | ||
| 'error' => $result->get_error_message(), | ||
| ) | ||
| ); | ||
| return; | ||
| } | ||
|
|
||
| $db_version = $upgrade_class::$version; | ||
| } | ||
|
|
||
| // If all upgrades completed successfully, the plugin was successfully upgraded to the latest version. | ||
| delete_option( self::FAILED_UPGRADE_OPTION_KEY ); | ||
| update_option( self::VERSION_OPTION_KEY, WPAI_VERSION ); | ||
| } | ||
|
|
||
| /** | ||
| * Displays an admin notice if a plugin upgrade failed, with the error message. | ||
| * | ||
| * @since x.x.x | ||
| */ | ||
| public function failed_upgrade_notice(): void { | ||
| // Skip if there's no failures. | ||
| $failed_upgrade = get_option( self::FAILED_UPGRADE_OPTION_KEY, false ); | ||
| if ( ! $failed_upgrade ) { | ||
| return; | ||
| } | ||
|
|
||
| // If the error is set but empty, clean it up. | ||
| if ( empty( $failed_upgrade['version'] ) || empty( $failed_upgrade['error'] ) ) { | ||
| delete_option( self::FAILED_UPGRADE_OPTION_KEY ); | ||
| return; | ||
| } | ||
|
|
||
| // Display the error message. | ||
| wp_admin_notice( | ||
| sprintf( | ||
| /* translators: 1. The version the upgrade failed on, 2. The error message. */ | ||
| esc_html__( 'WordPress AI failed to upgrade to %1$s. Migration version %2$s failed with the following error: %3$s. Please deactivate and reactivate the plugin to try again.', 'ai' ), | ||
| WPAI_VERSION, | ||
| esc_html( $failed_upgrade['version'] ), | ||
| esc_html( $failed_upgrade['error'] ) | ||
| ), | ||
justlevine marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| array( | ||
| 'type' => 'error', | ||
| 'dismissible' => false, | ||
| ) | ||
| ); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| <?php | ||
| /** | ||
| * Abstract class for handling plugin upgrade routines. | ||
| * | ||
| * The class encapsulates the logic for performing any necessary database migrations or other upgrade tasks when the plugin is updated to a new version. Each class represents a specific plugin version where the upgrade became necessary and contains the logic to perform the upgrade from the previous version(s). | ||
| * | ||
| * Error are caught during the upgrade routine and stored in an option for use in an admin notice. | ||
| * | ||
| * @package WordPress\AI\Admin\Upgrades | ||
| * @since x.x.x | ||
| */ | ||
|
|
||
| declare( strict_types=1 ); | ||
|
|
||
| namespace WordPress\AI\Admin\Upgrades; | ||
|
|
||
| // Exit if accessed directly. | ||
| defined( 'ABSPATH' ) || exit; | ||
|
|
||
| /** | ||
| * Abstract class for handling plugin upgrade routines. | ||
| * | ||
| * @internal | ||
| * | ||
| * @since x.x.x | ||
| */ | ||
| abstract class Abstract_Upgrade { | ||
|
|
||
| /** | ||
| * The version number associated with this upgrade. | ||
| * | ||
| * This should be overloaded | ||
| * | ||
| * @since x.x.x | ||
| * | ||
| * @var string | ||
| */ | ||
| public static string $version = ''; | ||
|
|
||
| /** | ||
| * The version of the plugin in the database. | ||
| * | ||
| * This is the version we're migrating from. | ||
| * | ||
| * @since x.x.x | ||
| * | ||
| * @var string | ||
| */ | ||
| protected string $db_version = ''; | ||
|
|
||
| /** | ||
| * Constructor. | ||
| * | ||
| * @since x.x.x | ||
| * | ||
| * @param string $db_version The version of the plugin in the database. | ||
| * | ||
| * @throws \InvalidArgumentException Throws an exception if the provided version is invalid. | ||
| */ | ||
| public function __construct( string $db_version ) { | ||
| // @todo remove the !empty() check when we don't need to migrate from < 0.5.0 and can treat '' as a new install. | ||
| if ( ! empty( $db_version ) && ! version_compare( $db_version, '0.0.0', '>' ) ) { | ||
| throw new \InvalidArgumentException( 'Invalid database version provided for upgrade.' ); | ||
| } | ||
|
|
||
| $this->db_version = $db_version; | ||
| } | ||
|
|
||
| /** | ||
| * Performs the upgrade routine. | ||
| * | ||
| * @since x.x.x | ||
| * | ||
| * @return true|\WP_Error True on success, or a WP_Error on failure. | ||
| */ | ||
| public function run() { | ||
| if ( version_compare( $this->db_version, static::$version, '>=' ) ) { | ||
| // No upgrade needed. | ||
| return true; | ||
| } | ||
|
|
||
| try { | ||
| $this->upgrade(); | ||
| } catch ( \Throwable $e ) { | ||
| return new \WP_Error( 'wpai_upgrade_failed', $e->getMessage() ); | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| /** | ||
| * The upgrade process. | ||
| * | ||
| * @throws \Exception Throws an exception if the upgrade fails. | ||
| */ | ||
| abstract protected function upgrade(): void; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| <?php | ||
| /** | ||
| * Upgrade routines for version 0.5.0. | ||
| * | ||
| * Originally this file was located in `includes/Migrations/Credential_Migration.php`, which is why the since tags don't match. | ||
| * | ||
| * @package WordPress\AI\Admin\Upgrades | ||
| * @since x.x.x | ||
| */ | ||
|
|
||
| declare( strict_types=1 ); | ||
|
|
||
| namespace WordPress\AI\Admin\Upgrades; | ||
|
|
||
| // Exit if accessed directly. | ||
| defined( 'ABSPATH' ) || exit; | ||
|
|
||
| /** | ||
| * Upgrade routine for migrating provider credentials to the new Connectors-based storage format. | ||
| * | ||
| * @since x.x.x | ||
| * @internal | ||
| */ | ||
| class V0_5_0 extends Abstract_Upgrade { | ||
| /** | ||
| * The legacy option that stored all provider credentials as an array. | ||
| * | ||
| * @since x.x.x | ||
| * @var string | ||
| */ | ||
| private const OLD_OPTION = 'wp_ai_client_provider_credentials'; | ||
|
|
||
| /** | ||
| * The map of provider slugs to their new Connectors option names. | ||
| */ | ||
| private const PROVIDER_MAP = array( // phpcs:ignore SlevomatCodingStandard.Classes.DisallowMultiConstantDefinition -- This is used as a array. | ||
| 'openai' => 'connectors_ai_openai_api_key', | ||
| 'google' => 'connectors_ai_google_api_key', | ||
| 'anthropic' => 'connectors_ai_anthropic_api_key', | ||
| ); | ||
|
|
||
| /** | ||
| * {@inheritDoc} | ||
| * | ||
| * @since x.x.x | ||
| */ | ||
| public static string $version = '0.5.0'; | ||
|
|
||
| /** | ||
| * {@inheritDoc} | ||
| * | ||
| * Copies legacy provider credentials to the new per-provider options. | ||
| * | ||
| * Reads the old combined credentials option and, for each known provider, | ||
| * copies the credential to the new option only when the new option is empty. | ||
| * | ||
| * @since x.x.x | ||
| */ | ||
| protected function upgrade(): void { | ||
| $old_credentials = get_option( self::OLD_OPTION, array() ); | ||
|
|
||
| if ( empty( $old_credentials ) || ! is_array( $old_credentials ) ) { | ||
| return; | ||
| } | ||
|
|
||
| foreach ( self::PROVIDER_MAP as $provider => $new_option ) { | ||
| if ( empty( $old_credentials[ $provider ] ) ) { | ||
| continue; | ||
| } | ||
|
|
||
| // Only migrate if the new option slot is empty. | ||
| if ( '' !== get_option( $new_option, '' ) ) { | ||
| continue; | ||
| } | ||
|
|
||
| update_option( $new_option, $old_credentials[ $provider ] ); | ||
| } | ||
|
|
||
| delete_option( self::OLD_OPTION ); | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.