=' ) && (int) get_option( 'afsd_im_schema' ) === SCHEMA ) { // Releases at or after 0.5.0 use additive-compatible options/schema here. Keep ALL jobs, settings, cursors and manifests. Reviewed_Store::update_option( self::VERSION_OPTION, VERSION, false ); Activity::add( 'upgraded' ); return; } Reviewed_Store::wp_clear_scheduled_hook( 'afsd_im_continue' ); Reviewed_Store::update_option( 'afsd_im_legacy_scheduler_cleanup', true, false ); if ( 'Action Scheduler' === Scheduler::backend() ) { as_unschedule_all_actions( 'afsd_im_continue', array(), Engine::GROUP ); } // Merge without lowering an existing generation or discarding another administrator's choices. Settings::patch( array() ); // The earlier 0.2.0 build used a differently named run option and aggregate columns. $job = get_option( Workflow::OPTION, false ); $old = get_option( 'afsd_im_run', false ); if ( false === $job && is_array( $old ) ) { $job = array( 'active' => in_array( $old['status'] ?? '', array( 'running', 'paused' ), true ), 'mode' => 'scan' === ( $old['mode'] ?? '' ) ? 'scan' : 'optimize', 'cursor' => 0, 'started' => (int) ( $old['started'] ?? 0 ), 'finished' => (int) ( $old['finished'] ?? 0 ), 'id' => bin2hex( random_bytes( 12 ) ), 'cancelled' => false, ); Reviewed_Store::update_option( Workflow::OPTION, $job, false ); } Reviewed_Store::update_option( 'afsd_im_totals_cursor', 0, false ); Reviewed_Store::update_option( 'afsd_im_totals_migrated', false, false ); Reviewed_Store::update_option( 'afsd_im_records_cursor', 0, false ); Reviewed_Store::update_option( 'afsd_im_records_migrated', false, false ); Reviewed_Store::update_option( Scheduler::NEEDED, true, false ); Reviewed_Store::update_option( self::VERSION_OPTION, VERSION, false ); Activity::add( 'upgraded' ); } finally { Repository::release( $lock, 'upgrade' ); } } /** At most 25 source records per worker turn. Copies/exclusions/attachment IDs remain intact. */ public static function records(): void { global $wpdb; if ( get_option( 'afsd_im_records_migrated', false ) ) { return; } $cursor = (int) get_option( 'afsd_im_records_cursor', 0 ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Bounded migration of plugin source rows and URL index; each batch must see the latest committed cursor/revision. $rows = $wpdb->get_results( $wpdb->prepare( 'SELECT * FROM %i WHERE id>%d ORDER BY id ASC LIMIT 25', Repository::table( 'sources' ), $cursor ), ARRAY_A ); foreach ( $rows as $raw ) { $r = Repository::decode( $raw ); $cursor = (int) $r['id']; if ( ! $r['attachment_id'] && ! empty( $r['details']['theme_files'] ) && ! Theme::is_source( $r ) ) { self::theme( $r ); } elseif ( 'grouped' !== $r['state'] ) { Repository::update( $cursor, array( 'manifest' => $r['manifest'] ) ); } } Reviewed_Store::update_option( 'afsd_im_records_cursor', $cursor, false ); if ( count( $rows ) < 25 ) { Reviewed_Store::update_option( 'afsd_im_records_migrated', true, false ); } } /** * Move one theme-picture record of an earlier release to the theme-picture form, its copies keyed by their files; a * record whose theme files are gone is held for review. * * @param array $r The decoded source row. */ private static function theme( array $r ): void { global $wpdb; $d = $r['details']; $files = array(); $keys = array(); $ok = true; foreach ( $d['theme_files'] as $old_key => $saved ) { $f = Theme::resolve( (string) ( $saved['url'] ?? '' ) ); if ( ! $f || ! isset( $saved['width'], $saved['height'] ) ) { $ok = false; break; } $key = $f['root'] . '/' . $f['relative']; $keys[ $old_key ] = $key; $files[ $key ] = array( 'root' => $f['root'], 'relative' => $f['relative'], 'url' => $f['url'], 'width' => (int) $saved['width'], 'height' => (int) $saved['height'], ); } if ( ! $ok || ! $files ) { // A theme switched or is disabled. Keep the legacy descriptor and files for review; do not guess. Repository::update( (int) $r['id'], array( 'state' => $r['excluded'] ? 'excluded' : 'held', 'requested' => 0, 'reason' => 'legacy_review', 'manifest' => $r['manifest'], ) ); return; } $d['kind'] = 'theme'; $d['legacy_copy_prefix'] = 'd' . $r['id']; $d['theme_files'] = $files; $m = $r['manifest']; foreach ( array( 'variants', 'results', 'fingerprints' ) as $field ) { if ( ! isset( $m[ $field ] ) ) { continue; } $mapped = array(); foreach ( $m[ $field ] as $key => $v ) { $mapped[ $keys[ $key ] ?? $key ] = $v; } $m[ $field ] = $mapped; } if ( ! empty( $m['schema'] ) ) { $m['meta_hash'] = hash( 'sha256', wp_json_encode( $files ) ); } Repository::update( (int) $r['id'], array( 'details' => $d, 'manifest' => $m, ) ); foreach ( $files as $f ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Bounded migration of plugin source rows and URL index; each batch must see the latest committed cursor/revision. Reviewed_Store::database_query( $wpdb->prepare( 'INSERT IGNORE INTO %i (url_key,attachment_id,source_id) VALUES (%s,0,%d)', Repository::table( 'urls' ), hash( 'sha256', 'theme-url:' . $f['url'] ), (int) $r['id'] ) ); } } }