*/ final class Migrations { /** * The ID of this plugin. * * @since 2.4.0 * @access private * @var string $plugin_name The ID of this plugin. */ private $plugin_name; /** * The current version of the plugin. * * @since 2.4.0 * @access protected * @var string $version The current version of the plugin. */ protected $version; /** * The migrations of the plugin. * * @since 2.4.0 * @access private * @var array> $migrations The migrations of the plugin. */ private $migrations = array( '2.4.0' => array( 'actirise_migrate_240', ), '2.5.1' => array( 'actirise_migrate_251', ), '2.5.3' => array( 'actirise_migrate_253', ), '2.5.5' => array( 'actirise_migrate_255', ), '2.5.6' => array( 'actirise_migrate_256', ), '2.6.0' => array( 'actirise_migrate_260', ), '2.6.3' => array( 'actirise_migrate_263', ), '2.6.5' => array( 'actirise_migrate_265', ), '2.6.7' => array( 'actirise_migrate_267', ), '2.6.9' => array( 'actirise_migrate_269', ), '3.0.0' => array( 'actirise_migrate_300', ), '3.0.2' => array( 'actirise_migrate_302', ), '3.2.0' => array( 'actirise_migrate_320', ), '3.2.2' => array( 'actirise_migrate_322', ), '4.3.0' => array( 'actirise_migrate_430', ), '4.3.2' => array( 'actirise_migrate_432', ), ); /** * Initialize the class and set its properties. * * @since 2.4.0 * @param string $plugin_name The name of this plugin. * @param string $version The version of this plugin. */ public function __construct( $plugin_name, $version ) { $this->plugin_name = $plugin_name; $this->version = $version; } /** * Run the migrations of the plugin. * * Reached from `admin_init` and from the hourly cron fallback * ({@see \Actirise\Includes\Cron::run_migrations()}). Both paths go through * the same cheap gate, so the lock is only taken when there is work to do, * not on every admin page load. * * Once the lock is held, the state is read again from the database: the * gate answered from this request's options snapshot, and a run that * finished since then (the lock was free again) left it stale. * * @since 2.4.0 * @return void */ public function migrate() { if ( wp_doing_ajax() ) { return; } if ( ! $this->is_pending( Options::get( 'version', '0.0.0' ), Options::get( 'migrations', array() ) ) ) { return; } if ( ! MigrationsLock::acquire() ) { return; } try { /** @var string $last_version */ $last_version = Options::get_fresh( 'version', '0.0.0' ); /** @var array $applied */ $applied = Options::get_fresh( 'migrations', array() ); if ( $this->is_pending( $last_version, $applied ) ) { $this->run( $last_version, $applied ); } } finally { MigrationsLock::release(); } } /** * Whether a migration or the version bookkeeping is still owed. * * @since 4.3.2 * @param mixed $last_version The stored plugin version. * @param mixed $applied The recorded migrations. * @return bool */ private function is_pending( $last_version, $applied ) { if ( $last_version !== $this->version ) { return true; } return array() !== array_diff_key( $this->migrations, is_array( $applied ) ? $applied : array() ); } /** * Play every migration not yet applied, then record the version. * * @since 4.3.2 * @param string $last_version The stored plugin version, read under the lock. * @param array $applied The recorded migrations, read under the lock. * @return void */ private function run( $last_version, $applied ) { $migration = $applied; $updated = false; foreach ( $this->migrations as $version => $methods ) { if ( ! isset( $migration[ $version ] ) ) { foreach ( $methods as $method ) { $this->$method(); } $migration[ $version ] = true; $updated = true; } } if ( $updated === true ) { Options::update( 'migrations', $migration ); } if ( $this->version !== $last_version ) { Options::update( 'version', $this->version ); Options::update( 'version-lastupdate', time() ); if ( $last_version !== '0.0.0' ) { EventAnnotation::add_event( 'Plugin update to v' . $this->version, 'The plugin was updated from version ' . $last_version . ' to version ' . $this->version, 'website_update', $last_version, $this->version, true ); } } } /** * Migrate to version 2.4.0 * * @since 2.4.0 * @return void */ private function actirise_migrate_240() { if ( Options::get( 'adstxt-active', false ) !== true || Options::get( 'adstxt-actirise', '' ) === '' ) { return; } $path_ads_txt = ABSPATH . 'ads.txt'; if ( file_exists( $path_ads_txt ) ) { return; } AdsTxt::update_file(); } /** * Migrate to version 2.5.1 * * @since 2.5.1 * @return void */ private function actirise_migrate_251() { $old_logs = get_option( $this->plugin_name . '_logs', false ); if ( $old_logs !== false ) { Options::update( 'logs', $old_logs ); delete_option( $this->plugin_name . '_logs' ); } } /** * Migrate to version 2.5.3 * * @since 2.5.3 * @return void */ private function actirise_migrate_253() { delete_option( 'actirise-api_version' ); delete_option( 'actirise-api-lastupdate' ); wp_clear_scheduled_hook( 'actirise_cron_get_api_version' ); } /** * Migrate to version 2.5.5 * * @since 2.5.5 * @return void */ private function actirise_migrate_255() { delete_option( 'actirise-debug-last-update' ); wp_clear_scheduled_hook( 'actirise_cron_update_debug_token' ); } /** * Migrate to version 2.5.6 * * @since 2.5.6 * @return void */ private function actirise_migrate_256() { $cron = new Cron(); $cron->update_adstxt(); } /** * Migrate to version 2.6.0 * * @since 2.6.0 * @return void */ private function actirise_migrate_260() { $prefix = 'actirise-'; delete_option( $prefix . 'adstxt-lastupdate' ); delete_option( $prefix . 'presizeddiv-lastupdate' ); delete_option( $prefix . 'fastcmp-lastupdate' ); delete_option( $prefix . 'init-plugin' ); /** @var \wpdb $wpdb */ global $wpdb; $table_name = $wpdb->prefix . 'actirise_options'; $options = $wpdb->get_results( // phpcs:ignore $wpdb->prepare( "SELECT option_name, option_value, autoload FROM $wpdb->options WHERE option_name LIKE %s", // @phpstan-ignore argument.type $prefix . '%' ), ARRAY_A ); if ( empty( $options ) ) { return; } foreach ( $options as $option ) { $option_name = substr( $option['option_name'], strlen( $prefix ) ); // @phpstan-ignore argument.type $option_value = $option['option_value']; $autoload = 1; if ( Options::exists( $option_name ) ) { $wpdb->query( // phpcs:ignore $wpdb->prepare( // @phpstan-ignore argument.type // @phpstan-ignore-next-line argument.type "UPDATE $table_name SET option_value = %s, autoload = %d WHERE option_name = %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared $option_value, $autoload, $option_name ) ); } else { $wpdb->query( // phpcs:ignore $wpdb->prepare( // @phpstan-ignore argument.type // @phpstan-ignore-next-line argument.type "INSERT INTO $table_name (option_name, option_value, autoload) VALUES (%s, %s, %d)", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared $option_name, $option_value, $autoload ) ); } } if ( ! Options::exists( 'debug-enabled' ) ) { Options::update( 'debug-enabled', true ); } $wpdb->query( // phpcs:ignore $wpdb->prepare( // @phpstan-ignore argument.type "DELETE FROM $wpdb->options WHERE option_name LIKE %s", // @phpstan-ignore argument.type $prefix . '%' ) ); } /** * Migrate to version 2.6.3 * * @since 2.6.3 * @return void */ private function actirise_migrate_263() { if ( ! Options::exists( 'custom1' ) ) { Options::update( 'custom1', 'author_ID' ); } if ( ! Options::exists( 'custom2' ) ) { Options::update( 'custom2', 'category_0_slug' ); } if ( ! Options::exists( 'custom3' ) ) { Options::update( 'custom3', 'post_ID' ); } if ( ! Options::exists( 'custom4' ) ) { Options::update( 'custom4', '' ); } if ( ! Options::exists( 'custom5' ) ) { Options::update( 'custom5', '' ); } } /** * Migrate to version 2.6.5 * * @since 2.6.5 * @return void */ private function actirise_migrate_265() { Options::update( 'cta-cache-200', false ); } /** * Migrate to version 2.6.7 * * @since 2.6.7 * @return void */ private function actirise_migrate_267() { Options::update( 'cta-cache-200', false ); } /** * Migrate to version 2.6.9 * * @since 2.6.9 * @return void */ private function actirise_migrate_269() { Options::update( 'fastcmp-consentV2', true ); Options::update( 'fastcmp-googleTagsStatus', false ); Options::update( 'fastcmp-googleAdStorage', false ); Options::update( 'fastcmp-googleAnalyticsStorage', false ); Options::update( 'fastcmp-googlePersonalizationStorage', false ); Options::update( 'fastcmp-googleFunctionalityStorage', false ); Options::update( 'fastcmp-googleSecurityStorage', false ); Options::update( 'fastcmp-googleAdPersonalization', false ); Options::update( 'fastcmp-googleAdUserData', false ); } /** * Migrate to version 3.0.0 * * @since 3.0.0 * @return void */ private function actirise_migrate_300() { $options_to_migrate = array( 'adstxt-active', 'adstxt-update', 'adstxt-file', 'presizeddiv-active', ); foreach ( $options_to_migrate as $option_name ) { $option_value = Options::get( $option_name ); if ( $option_value === 'true' ) { Options::update( $option_name, true ); } elseif ( $option_value === 'false' ) { Options::update( $option_name, false ); } } if ( is_multisite() ) { AdsTxt::delete_file(); } } /** * Migrate to version 3.0.2 * * @since 3.0.2 * @return void */ private function actirise_migrate_302() { Core::check_website_status(); } /** * Migrate to version 3.2.0 * * @since 3.2.0 * @return void */ private function actirise_migrate_320() { if ( Options::get( 'settings-analytics-token', '' ) !== '' && Options::get( 'settings-analytics-userid', '' ) === '' ) { Helpers::get_user_id(); } if ( Options::get( 'presizeddiv-init' ) !== true ) { Options::delete( 'presizeddiv-init' ); } } /** * Migrate to version 3.2.2 * * @since 3.3 * @return void */ private function actirise_migrate_322() { if ( Options::exists( 'cta-cache-200' ) ) { Options::delete( 'cta-cache-200' ); } } /** * Migrate to version 4.3.0 * * Two one-off announcements a site updated from an earlier version owes * Actirise, neither of which any regular flow will make for it. * * **The push capability.** Sites already set up before this version hold a * token and a website uuid, so nothing reports for them: the report is * triggered when the uuid first lands, which for them happened long ago, and * the daily self-test would otherwise leave the API unaware they can receive * pushes until its next run. Queueing here also covers installs with WP-Cron * disabled, where the self-test is never scheduled at all. * * **The FastCMP configuration.** Actirise holds nothing for a publisher who * never edits anything, which is most of them, and nothing else would ever * establish the source of truth. Only *armed* here: the publication itself is * deferred to a background job, because at update time the domain uid may be * unresolved and the credentials missing, the rollout is enabled per website * only after the plugin ships, and a synchronous outbound call inside a * plugin-update admin request would hang the dashboard whenever the API is * slow. * * @since 4.3.0 * @return void */ private function actirise_migrate_430() { $cron = new Cron(); $cron->queue_rest_selftest(); ConfigSync::arm_seed(); $cron->queue_fastcmp_initial_sync(); } /** * Migrate to version 4.3.2 * * **Re-arm the FastCMP first publication** wherever it gave up without one. * The seed the 4.3.0 migration armed retired after a week of hourly * attempts, and two populations spent that week without publishing: sites * the API refused with a `404` while the rollout flag was still off for * them, and sites without credentials yet. The flag is now on for every * website, so a fresh week of attempts publishes for the first, and the * sign-up re-arm covers the second ({@see Core::update_website_options()}). * From this release the seed no longer retires: hours without credentials * are not counted, and a spent budget slows it to a daily retry * ({@see ConfigSync::run_seed()}). * * Gated on the held version inside {@see ConfigSync::rearm_seed()}: an * install that already holds a configuration is left alone and spends no * request. * * @since 4.3.2 * @return void */ private function actirise_migrate_432() { if ( ! ConfigSync::rearm_seed() ) { return; } $cron = new Cron(); $cron->queue_fastcmp_initial_sync(); } }