$value ) { $post_type = (string) $post_type; $single = [ $post_type => $value ]; $mode = \BeyondWords\Settings\Preselect::get_mode( $post_type, $single ); if ( \BeyondWords\Settings\Preselect::MODE_ALL === $mode ) { $migrated[ $post_type ] = [ 'mode' => \BeyondWords\Settings\Preselect::MODE_ALL ]; } elseif ( \BeyondWords\Settings\Preselect::MODE_TERMS === $mode ) { $terms = \BeyondWords\Settings\Preselect::get_selected_terms( $post_type, $single ); if ( ! empty( $terms ) ) { $migrated[ $post_type ] = [ 'mode' => \BeyondWords\Settings\Preselect::MODE_TERMS, 'terms' => $terms, ]; } } // MODE_OFF (empty / unrecognised) → dropped. } update_option( 'beyondwords_preselect', $migrated, false ); } /** * v7.0.0: remove every option marked deprecated in `Utils::get_options()`. * * Multisite-aware because legacy installs may have stored these as site options. * * @since 7.0.0 */ public static function delete_deprecated_options(): void { foreach ( Utils::get_options( 'deprecated' ) as $option ) { if ( is_multisite() ) { delete_site_option( $option ); } else { delete_option( $option ); } } } /** * v7.0.0: migrate the legacy `beyondwords_disabled` opt-out to Embed "None". * * Carries the flag forward so previously-hidden players stay hidden; never * overwrites an existing Embed value, and batches to keep memory bounded. * * @since 7.0.0 */ public static function migrate_disabled_to_embed_none(): void { $batch = 100; do { $post_ids = get_posts( [ 'post_type' => 'any', 'post_status' => 'any', 'numberposts' => $batch, 'fields' => 'ids', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query 'meta_query' => [ [ 'key' => 'beyondwords_disabled', 'value' => '1', ], ], ] ); foreach ( $post_ids as $post_id ) { if ( '' === (string) get_post_meta( $post_id, 'beyondwords_embed', true ) ) { update_post_meta( $post_id, 'beyondwords_embed', \BeyondWords\Editor\Components\SettingsFields::EMBED_NONE ); } delete_post_meta( $post_id, 'beyondwords_disabled' ); } $found = count( $post_ids ); } while ( $found === $batch ); } /** * v7.0.0: normalise the removed script-only Source to Post + script. * * See doc/legacy-meta-migration.md. Batched to keep memory bounded, and * idempotent because a migrated post no longer matches the query. * * @since 7.0.0 */ public static function migrate_source_script_to_post_and_script(): void { $batch = 100; do { $post_ids = get_posts( [ 'post_type' => 'any', 'post_status' => 'any', 'numberposts' => $batch, 'fields' => 'ids', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query 'meta_query' => [ [ 'key' => 'beyondwords_source', 'value' => \BeyondWords\Editor\Components\SettingsFields::LEGACY_SOURCE_SCRIPT, ], ], ] ); foreach ( $post_ids as $post_id ) { // Pin the asset script-only was showing, so the player doesn't switch. if ( '' === (string) get_post_meta( $post_id, 'beyondwords_embed', true ) ) { $output = (string) get_post_meta( $post_id, 'beyondwords_output', true ); $audio = '' === $output || \BeyondWords\Editor\Components\SettingsFields::output_includes_audio( $output ); update_post_meta( $post_id, 'beyondwords_embed', $audio ? \BeyondWords\Editor\Components\SettingsFields::EMBED_AUDIO_SCRIPT : \BeyondWords\Editor\Components\SettingsFields::EMBED_VIDEO_SCRIPT ); } update_post_meta( $post_id, 'beyondwords_source', \BeyondWords\Editor\Components\SettingsFields::SOURCE_POST_AND_SCRIPT ); } $found = count( $post_ids ); } while ( $found === $batch ); } /** * v3.0.0: migrate `speechkit_settings.*` array values to top-level options. */ public static function migrate_settings(): void { $old_settings = get_option( 'speechkit_settings', [] ); if ( ! is_array( $old_settings ) || empty( $old_settings ) ) { return; } $settings_map = [ 'speechkit_api_key' => 'speechkit_api_key', 'speechkit_id' => 'speechkit_project_id', 'speechkit_merge_excerpt' => 'speechkit_prepend_excerpt', ]; foreach ( $settings_map as $old_key => $new_key ) { if ( array_key_exists( $old_key, $old_settings ) && ! get_option( $new_key ) ) { add_option( $new_key, $old_settings[ $old_key ] ); } } if ( false === get_option( 'speechkit_preselect' ) ) { add_option( 'speechkit_preselect', self::construct_preselect_setting() ); } } /** * Build a v3 preselect array from the v2 post-type + category settings. * * @return array|false `false` when the legacy `speechkit_settings` option is missing. */ public static function construct_preselect_setting(): array|false { $old_settings = get_option( 'speechkit_settings', [] ); if ( ! is_array( $old_settings ) || empty( $old_settings ) ) { return false; } $preselect = []; if ( array_key_exists( 'speechkit_select_post_types', $old_settings ) && ! empty( $old_settings['speechkit_select_post_types'] ) ) { $preselect = array_fill_keys( $old_settings['speechkit_select_post_types'], '1' ); } if ( array_key_exists( 'speechkit_selected_categories', $old_settings ) && ! empty( $old_settings['speechkit_selected_categories'] ) ) { $taxonomy = get_taxonomy( 'category' ); if ( $taxonomy && is_array( $taxonomy->object_type ) ) { foreach ( $taxonomy->object_type as $post_type ) { $preselect[ $post_type ] = [ 'category' => $old_settings['speechkit_selected_categories'], ]; } } } return $preselect; } /** * v3.7.0: copy `speechkit_*` option keys to `beyondwords_*`. * * Originals are left in place so plugin downgrades remain safe. */ public static function rename_plugin_settings(): void { $api_key = get_option( 'speechkit_api_key' ); $project_id = get_option( 'speechkit_project_id' ); $prepend_excerpt = get_option( 'speechkit_prepend_excerpt' ); $preselect = get_option( 'speechkit_preselect' ); if ( $api_key ) { update_option( 'beyondwords_api_key', $api_key, false ); } if ( $project_id ) { update_option( 'beyondwords_project_id', $project_id, false ); } if ( $prepend_excerpt ) { update_option( 'beyondwords_prepend_excerpt', $prepend_excerpt, false ); } if ( $preselect ) { update_option( 'beyondwords_preselect', $preselect, false ); } } }