| 1 |
<?php |
| 2 |
/** |
| 3 |
* Desktop Mode — one-time data migrations. |
| 4 |
* |
| 5 |
* A tiny, option-versioned migration runner modeled on the lazy schema |
| 6 |
* installer in `includes/desktop-files/schema.php`: a stored option holds |
| 7 |
* the highest migration version that has run; on every admin load we |
| 8 |
* compare it against {@see DESKTOP_MODE_MIGRATION_VERSION} and run any |
| 9 |
* pending migrations exactly once. Guarded so it is a cheap no-op after |
| 10 |
* the first successful pass. |
| 11 |
* |
| 12 |
* @package WPDesktopMode |
| 13 |
*/ |
| 14 |
|
| 15 |
defined( 'ABSPATH' ) || exit; |
| 16 |
|
| 17 |
/** |
| 18 |
* Highest migration version shipped by the plugin. |
| 19 |
* |
| 20 |
* Bump this (and add a matching branch in |
| 21 |
* {@see desktop_mode_run_pending_migrations}) whenever a new one-time |
| 22 |
* migration is needed. |
| 23 |
* |
| 24 |
* - 1: native list windows flipped from opt-out (default ON) to opt-in |
| 25 |
* Beta (default OFF). Clears the five `native*Enabled` flags from every |
| 26 |
* user who had them persisted so the whole install reverts to opt-in. |
| 27 |
* - 2: post & taxonomy-term AI analysis was removed (the copilot now only |
| 28 |
* analyzes comments for spam, and the assistant finds content via native |
| 29 |
* WordPress search). Unschedules any queued `desktop_mode_ai_analyze_post` |
| 30 |
* / `desktop_mode_ai_analyze_term` cron events left over from prior versions. |
| 31 |
* - 3: the copilot dropped its self-managed AI credentials in favour of |
| 32 |
* WordPress 7.0 Connectors. Deletes the platform key option and strips the |
| 33 |
* per-user `apiKey` / `apiKeys` / `provider` / `transport` fields from the |
| 34 |
* stored OS settings so no provider secret lingers in the database. |
| 35 |
* |
| 36 |
* @since 0.9.1 |
| 37 |
*/ |
| 38 |
const DESKTOP_MODE_MIGRATION_VERSION = 3; |
| 39 |
|
| 40 |
/** Option storing the highest migration version that has run. autoload=no. */ |
| 41 |
const DESKTOP_MODE_MIGRATION_OPTION = 'desktop_mode_migration_version'; |
| 42 |
|
| 43 |
/** |
| 44 |
* Runs any pending migrations, then records the new high-water mark. |
| 45 |
* |
| 46 |
* Idempotent: bails immediately when the stored version is already at |
| 47 |
* or above the shipped version, so it is safe to fire on every request. |
| 48 |
* |
| 49 |
* @since 0.9.1 |
| 50 |
* |
| 51 |
* @return void |
| 52 |
*/ |
| 53 |
function desktop_mode_maybe_run_migrations() { |
| 54 |
$installed = (int) get_option( DESKTOP_MODE_MIGRATION_OPTION, 0 ); |
| 55 |
if ( $installed >= DESKTOP_MODE_MIGRATION_VERSION ) { |
| 56 |
return; |
| 57 |
} |
| 58 |
|
| 59 |
desktop_mode_run_pending_migrations( $installed ); |
| 60 |
|
| 61 |
update_option( DESKTOP_MODE_MIGRATION_OPTION, DESKTOP_MODE_MIGRATION_VERSION, false ); |
| 62 |
} |
| 63 |
add_action( 'admin_init', 'desktop_mode_maybe_run_migrations' ); |
| 64 |
|
| 65 |
/** |
| 66 |
* Dispatches each migration whose version is newer than what has run. |
| 67 |
* |
| 68 |
* @since 0.9.1 |
| 69 |
* |
| 70 |
* @param int $from The highest migration version already applied. |
| 71 |
* @return void |
| 72 |
*/ |
| 73 |
function desktop_mode_run_pending_migrations( $from ) { |
| 74 |
$from = (int) $from; |
| 75 |
|
| 76 |
if ( $from < 1 ) { |
| 77 |
desktop_mode_migrate_os_settings_optin(); |
| 78 |
} |
| 79 |
|
| 80 |
if ( $from < 2 ) { |
| 81 |
desktop_mode_migrate_unschedule_post_term_ai(); |
| 82 |
} |
| 83 |
|
| 84 |
if ( $from < 3 ) { |
| 85 |
desktop_mode_migrate_delete_ai_keys(); |
| 86 |
} |
| 87 |
} |
| 88 |
|
| 89 |
/** |
| 90 |
* Migration 1 — reset the native list windows to opt-in. |
| 91 |
* |
| 92 |
* The native Posts/Pages/Users/Plugins/Comments windows used to default |
| 93 |
* ON (opt-out). The shell persists the whole OS-settings object on every |
| 94 |
* change, so most active users already have these flags stored as `true` |
| 95 |
* and would keep the native UI even after the default flips. This clears |
| 96 |
* the five flags from every user who has the meta, leaving the rest of |
| 97 |
* their settings (wallpaper, accent, dock order, …) untouched. On the |
| 98 |
* next read the cleared keys fall back to the new `false` default, so the |
| 99 |
* whole install lands on opt-in and users re-enable each window from |
| 100 |
* OS Settings → Features → Beta features. |
| 101 |
* |
| 102 |
* Only users who actually have the meta are queried — fresh accounts and |
| 103 |
* users who never touched OS Settings are skipped entirely. |
| 104 |
* |
| 105 |
* @since 0.9.1 |
| 106 |
* |
| 107 |
* @return void |
| 108 |
*/ |
| 109 |
function desktop_mode_migrate_os_settings_optin() { |
| 110 |
$flags = array( |
| 111 |
'nativePostsEnabled', |
| 112 |
'nativePagesEnabled', |
| 113 |
'nativeUsersEnabled', |
| 114 |
'nativePluginsEnabled', |
| 115 |
'nativeCommentsEnabled', |
| 116 |
); |
| 117 |
|
| 118 |
$user_ids = get_users( |
| 119 |
array( |
| 120 |
'fields' => 'ID', |
| 121 |
'meta_key' => DESKTOP_MODE_OS_SETTINGS_META_KEY, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- one-time migration; the key is indexed in usermeta and the scan is guarded to run once. |
| 122 |
'meta_compare' => 'EXISTS', |
| 123 |
) |
| 124 |
); |
| 125 |
|
| 126 |
foreach ( $user_ids as $user_id ) { |
| 127 |
$raw = get_user_meta( (int) $user_id, DESKTOP_MODE_OS_SETTINGS_META_KEY, true ); |
| 128 |
if ( ! is_array( $raw ) ) { |
| 129 |
continue; |
| 130 |
} |
| 131 |
|
| 132 |
$changed = false; |
| 133 |
foreach ( $flags as $flag ) { |
| 134 |
if ( array_key_exists( $flag, $raw ) ) { |
| 135 |
unset( $raw[ $flag ] ); |
| 136 |
$changed = true; |
| 137 |
} |
| 138 |
} |
| 139 |
|
| 140 |
if ( ! $changed ) { |
| 141 |
continue; |
| 142 |
} |
| 143 |
|
| 144 |
// Re-save through the canonical sanitizer so the cleared flags are |
| 145 |
// backfilled with the new `false` default and the rest of the |
| 146 |
// settings array is normalized exactly as a client write would be. |
| 147 |
desktop_mode_save_os_settings( (int) $user_id, $raw ); |
| 148 |
} |
| 149 |
} |
| 150 |
|
| 151 |
/** |
| 152 |
* Migration 2 — unschedule leftover post/term AI analysis jobs. |
| 153 |
* |
| 154 |
* Post and taxonomy-term analysis was removed: the copilot now only |
| 155 |
* analyzes comments (for the spam score), and the AI assistant finds |
| 156 |
* content with native WordPress keyword search. Their cron callbacks no |
| 157 |
* longer exist, so any single-events still queued from a prior version |
| 158 |
* would simply no-op — but we clear them so the cron array stays tidy and |
| 159 |
* `wp cron event list` doesn't show orphaned hooks. |
| 160 |
* |
| 161 |
* Existing `_desktop_mode_ai_analysis` meta on posts/terms is left in place |
| 162 |
* (hidden, harmless, and cheap to ignore). |
| 163 |
* |
| 164 |
* @since 0.9.1 |
| 165 |
* |
| 166 |
* @return void |
| 167 |
*/ |
| 168 |
function desktop_mode_migrate_unschedule_post_term_ai() { |
| 169 |
wp_unschedule_hook( 'desktop_mode_ai_analyze_post' ); |
| 170 |
wp_unschedule_hook( 'desktop_mode_ai_analyze_term' ); |
| 171 |
} |
| 172 |
|
| 173 |
/** |
| 174 |
* Migration 3 — delete self-managed AI credentials. |
| 175 |
* |
| 176 |
* WordPress 7.0 owns provider credentials (Settings → Connectors), so the |
| 177 |
* copilot no longer stores keys of its own. Remove the platform key option and |
| 178 |
* strip the now-unused key / provider / model / transport fields from every |
| 179 |
* user's stored OS settings so no secret is left behind. The only `ai` field |
| 180 |
* that remains is `enabled` (the per-user assistant toggle), backfilled from |
| 181 |
* defaults on next read. |
| 182 |
* |
| 183 |
* @since 0.9.4 |
| 184 |
* |
| 185 |
* @return void |
| 186 |
*/ |
| 187 |
function desktop_mode_migrate_delete_ai_keys() { |
| 188 |
// Platform-wide key option (formerly `desktop_mode_ai_platform`). |
| 189 |
delete_option( 'desktop_mode_ai_platform' ); |
| 190 |
|
| 191 |
$user_ids = get_users( |
| 192 |
array( |
| 193 |
'fields' => 'ID', |
| 194 |
'meta_key' => DESKTOP_MODE_OS_SETTINGS_META_KEY, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- one-time migration; guarded to run once. |
| 195 |
'meta_compare' => 'EXISTS', |
| 196 |
) |
| 197 |
); |
| 198 |
|
| 199 |
foreach ( $user_ids as $user_id ) { |
| 200 |
$raw = get_user_meta( (int) $user_id, DESKTOP_MODE_OS_SETTINGS_META_KEY, true ); |
| 201 |
if ( ! is_array( $raw ) || ! isset( $raw['ai'] ) || ! is_array( $raw['ai'] ) ) { |
| 202 |
continue; |
| 203 |
} |
| 204 |
|
| 205 |
// Strip every legacy AI field: the self-managed credentials/transport, |
| 206 |
// plus the `provider` / `model` preferences — provider + model selection |
| 207 |
// is now delegated entirely to the Core AI Client. |
| 208 |
$changed = false; |
| 209 |
foreach ( array( 'apiKey', 'apiKeys', 'transport', 'provider', 'model' ) as $stale ) { |
| 210 |
if ( array_key_exists( $stale, $raw['ai'] ) ) { |
| 211 |
unset( $raw['ai'][ $stale ] ); |
| 212 |
$changed = true; |
| 213 |
} |
| 214 |
} |
| 215 |
|
| 216 |
if ( ! $changed ) { |
| 217 |
continue; |
| 218 |
} |
| 219 |
|
| 220 |
desktop_mode_save_os_settings( (int) $user_id, $raw ); |
| 221 |
} |
| 222 |
} |
| 223 |
|