PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.12
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.12
1.1.12 1.1.11 1.1.10 1.1.9 1.1.8 1.1.7 1.1.6 1.1.5 1.1.4 1.1.3 1.1.2 1.1.1 1.1.0 1.0.1 1.0.0 0.9.8 0.9.7 0.9.6 0.9.4 0.9.5 0.9.3 0.9.2 0.9.1 0.9.0 0.8.9 All 36 releases
← All changes | includes/migrations.php +223 -2 1.1.2 → 1.1.12 View file →
@@ -55,10 +55,19 @@
55 55 * separate migration from 4.
56 56 * - 6: the Trash stopped registering a desktop icon. Removes the
57 57 * placement the shell had auto-placed for it and closes the hole that
58 58 * leaves in the icon column.
59 + * - 7: seeds a face for every agent that has none.
60 + * - 8: retires automatic AI comment scoring.
61 + * - 9: repairs the Comment Concierge's misspelled ability slug in every
62 + * agent's stored allowlist.
63 + * - 10: the first-run stamps and the shell tour. On a site with prior
64 + * desktop use, records that the site was enabled before the stamps
65 + * existed (`openstation_first_enabled_at` with `at: 0, via: backfill`)
66 + * and marks the `shell-tour` intro seen for every prior user, so an
67 + * update never greets a veteran with a first-boot tour.
59 68 */
60 -const OPENSTATION_MIGRATION_VERSION = 6;
69 +const OPENSTATION_MIGRATION_VERSION = 10;
61 70
62 71 /**
63 72 * Option storing the highest migration version that has run. autoload=no.
64 73 *
@@ -156,11 +165,195 @@
156 165
157 166 if ( $from < 6 ) {
158 167 openstation_migrate_close_recycle_bin_icon_gap();
159 168 }
169 +
170 + if ( $from < 7 ) {
171 + openstation_migrate_seed_agent_faces();
172 + }
173 +
174 + if ( $from < 8 ) {
175 + openstation_migrate_remove_comments_ai();
176 + }
177 +
178 + if ( $from < 9 ) {
179 + openstation_migrate_agent_ability_slugs();
180 + }
181 +
182 + if ( $from < 10 ) {
183 + openstation_migrate_first_run_stamps();
184 + }
160 185 }
161 186
162 187 /**
188 + * Migration 9 — repair a misspelled ability slug in stored agent
189 + * allowlists.
190 + *
191 + * The shipped Comment Concierge listed `desktop-mode/search-comments-on-post`,
192 + * an ability that was never registered: the real one is
193 + * `desktop-mode/search-comments-by-post`. The runner drops an unknown
194 + * slug without a word, so every seeded Concierge ran without the one
195 + * tool that reads a post's thread. Fixing the definition only reaches
196 + * sites that seed from now on; this rewrites the slug in place on every
197 + * agent that already stored it, whoever created the agent.
198 + *
199 + * Reads the meta directly rather than through the agents store, because
200 + * that module only loads while the Agents feature is on and an agent
201 + * row outlives the flag being turned off. The key is the frozen
202 + * `_desktop_mode_agent_abilities`; nothing is renamed.
203 + *
204 + * @return void
205 + */
206 +function openstation_migrate_agent_ability_slugs() {
207 + $meta_key = '_desktop_mode_agent_abilities';
208 + $renames = array(
209 + 'desktop-mode/search-comments-on-post' => 'desktop-mode/search-comments-by-post',
210 + );
211 +
212 + $user_ids = get_users(
213 + array(
214 + 'fields' => 'ID',
215 + 'meta_key' => $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.
216 + 'meta_compare' => 'EXISTS',
217 + )
218 + );
219 +
220 + foreach ( $user_ids as $user_id ) {
221 + $raw = get_user_meta( (int) $user_id, $meta_key, true );
222 + $slugs = is_string( $raw ) ? json_decode( $raw, true ) : $raw;
223 + if ( ! is_array( $slugs ) ) {
224 + continue;
225 + }
226 +
227 + $changed = false;
228 + foreach ( $slugs as $i => $slug ) {
229 + if ( is_string( $slug ) && isset( $renames[ $slug ] ) ) {
230 + $slugs[ $i ] = $renames[ $slug ];
231 + $changed = true;
232 + }
233 + }
234 + if ( ! $changed ) {
235 + continue;
236 + }
237 +
238 + // Stored as a JSON string, the shape the agents store writes.
239 + // Slashed because update_user_meta() unslashes its value.
240 + update_user_meta(
241 + (int) $user_id,
242 + $meta_key,
243 + wp_slash( (string) wp_json_encode( array_values( array_unique( $slugs ) ) ) )
244 + );
245 + }
246 +}
247 +
248 +/**
249 + * Migration 10 — the first-run stamps meet an install with a past.
250 + *
251 + * Two facts about a site that already had people in the shell, neither
252 + * of which the stamps can learn on their own:
253 + *
254 + * 1. The site HAS activated. `openstation_first_enabled_at` is written
255 + * the first time a user enables from here on, so without this an
256 + * old, busy install would look like one nobody ever turned on: the
257 + * activation nudge would show to its admins, and the deactivation
258 + * funnel would read "never enabled". The stamp is recorded as
259 + * `at: 0, via: backfill` — a real moment is not known and is not
260 + * invented, and every age computation treats it as unknown.
261 + * 2. Its users have already learned the shell. The tour is for a first
262 + * boot, and the slug it records lives in the seen-intros registry,
263 + * so marking it seen for every prior user is the whole opt-out.
264 + * "Reset what's-new dialogs" brings it back for anyone curious.
265 + *
266 + * On a site with no history both loops are empty, and the runner fires
267 + * at activation, where the activation hook that stamps the real install
268 + * moment runs right after it. The install stamp is deliberately not
269 + * written here: for the in-place update it belongs to the lazy
270 + * `admin_init` backfill, which is honest about being a backfill.
271 + *
272 + * @return void
273 + */
274 +function openstation_migrate_first_run_stamps() {
275 + $prior_users = openstation_users_with_prior_desktop_use();
276 + if ( empty( $prior_users ) ) {
277 + return;
278 + }
279 +
280 + if ( null === openstation_get_first_enabled_stamp() ) {
281 + add_option(
282 + OPENSTATION_FIRST_ENABLED_AT_OPTION,
283 + array(
284 + 'at' => 0,
285 + 'via' => 'backfill',
286 + ),
287 + '',
288 + false
289 + );
290 + }
291 +
292 + foreach ( $prior_users as $user_id ) {
293 + openstation_mark_intro_seen( $user_id, OPENSTATION_SHELL_TOUR_INTRO_SLUG );
294 + }
295 +}
296 +
297 +/**
298 + * Migration 7 — give the agents that predate faces a seed to grow one
299 + * from.
300 + *
301 + * Agents used to share a single grey robot glyph. They now carry a Mio
302 + * look, and an agent created from here on gets a seed at birth. The
303 + * ones already on the site do not, and without a seed there is nothing
304 + * to derive a face from.
305 + *
306 + * **This writes the seed and stops.** It does not write the face. The
307 + * face comes from `randomMioLook()`, which lives in TypeScript, and
308 + * porting it is exactly the wrong trade: it is a taste filter with a
309 + * dozen judgment calls in it, pinned by `mio-randomize.test.ts`, and a
310 + * PHP twin of it would drift with nothing watching. So the shell fills
311 + * the looks in on its next paint of the Agents section, rolling each
312 + * one from the seed written here.
313 + *
314 + * That is a client writing on the server's behalf, which is worth
315 + * naming rather than slipping past. It is safe because it is entirely
316 + * derived: the seed is `crc32` of the login, so two admins racing the
317 + * backfill produce byte-identical faces, and running it twice changes
318 + * nothing.
319 + *
320 + * The five shipped agents are unaffected: their faces are written out
321 + * in `default-definitions.php` and were never rolled.
322 + *
323 + * @return void
324 + */
325 +function openstation_migrate_seed_agent_faces() {
326 + // Agents is behind a feature flag, so on a site that has never
327 + // turned it on there is nothing to seed, and none of the module's
328 + // functions exist to call. A site that turns it on later creates
329 + // its agents through `openstation_agent_create`, which seeds them
330 + // at birth, so nothing is missed by returning here.
331 + if (
332 + ! function_exists( 'openstation_agent_get_agents' )
333 + || ! function_exists( 'openstation_agent_get_face_seed' )
334 + || ! defined( 'OPENSTATION_AGENT_FACE_SEED_META' )
335 + ) {
336 + return;
337 + }
338 +
339 + foreach ( openstation_agent_get_agents() as $agent ) {
340 + $user_id = isset( $agent->ID ) ? (int) $agent->ID : 0;
341 + if ( $user_id <= 0 ) {
342 + continue;
343 + }
344 + if ( openstation_agent_get_face_seed( $user_id ) > 0 ) {
345 + continue;
346 + }
347 + update_user_meta(
348 + $user_id,
349 + OPENSTATION_AGENT_FACE_SEED_META,
350 + crc32( (string) $agent->user_login )
351 + );
352 + }
353 +}
354 +
355 +/**
163 356 * Grid the desktop auto-placer lays icons out on: 16px of padding, a
164 357 * 96px column, a 110px row. Mirrored from `src/desktop-files/grid.ts`
165 358 * via {@see openstation_files_auto_place_orphans}, which is what wrote
166 359 * the coordinates this migration edits.
@@ -291,9 +484,9 @@
291 484 array_merge(
292 485 get_users(
293 486 array(
294 487 'fields' => 'ID',
295 - 'meta_key' => 'desktop_mode_mode', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- runs once per install; the key is indexed in usermeta and both callers are guarded to a single pass.
488 + 'meta_key' => 'desktop_mode_mode', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- the key is indexed in usermeta; the migration callers run once per install, and the deactivation feedback route once per admin submission.
296 489 'meta_compare' => 'EXISTS',
297 490 )
298 491 ),
299 492 get_users(
@@ -552,8 +745,33 @@
552 745 wp_unschedule_hook( 'desktop_mode_ai_analyze_term' );
553 746 }
554 747
555 748 /**
749 + * Migration 8 — retire the "Score new comments with AI" feature.
750 + *
751 + * Automatic AI scoring of incoming comments was removed: nothing
752 + * schedules `desktop_mode_ai_analyze_comment` any more, and the
753 + * `desktop_mode_comments_ai_moderation` option no longer gates
754 + * anything. Queued single-events would simply no-op, but we clear
755 + * them so the cron array stays tidy and `wp cron event list` doesn't
756 + * show an orphaned hook.
757 + *
758 + * The option row is dropped too — unlike a frozen identifier that
759 + * still has a reader, this one has none left, so leaving it would
760 + * only strand a value no code consults.
761 + *
762 + * Existing `_desktop_mode_ai_analysis` comment meta is left in place
763 + * (hidden, harmless, and still what the on-demand
764 + * `desktop-mode/analyze-comment` ability writes).
765 + *
766 + * @return void
767 + */
768 +function openstation_migrate_remove_comments_ai() {
769 + wp_unschedule_hook( 'desktop_mode_ai_analyze_comment' );
770 + delete_option( 'desktop_mode_comments_ai_moderation' );
771 +}
772 +
773 +/**
556 774 * Migration 3 — delete self-managed AI credentials.
557 775 *
558 776 * WordPress 7.0 owns provider credentials (Settings → Connectors), so the
559 777 * copilot no longer stores keys of its own. Remove the platform key option and
@@ -599,4 +817,7 @@
599 817
600 818 openstation_save_os_settings( (int) $user_id, $raw );
601 819 }
602 820 }
821 +
822 +// Presence owns a verified checkpoint so failures never advance unrelated migrations.
823 +add_action( 'admin_init', 'openstation_presence_migration_tick', 20 );