From ee40a074d780a46dbababe353a390d4fa1800a53 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 11 Jul 2026 18:32:48 +0800 Subject: [PATCH] refactor(db): drop option/data migration from DB migrator Keep dbDelta table schema only. Remove aksrt_basic_settings dual-write, layout/cms/resource/order/plate option migrators, and part-1 trait. Plate CPT activation no longer gated on migration flag. --- inc/bootstrap.php | 1 - inc/classes/class-aksrt-db-migrator.php | 368 +----------------- inc/classes/class-aksrt-forum.php | 4 +- .../trait-aksrt-db-migrator-part-1.php | 198 ---------- 4 files changed, 19 insertions(+), 552 deletions(-) delete mode 100644 inc/classes/db-traits/trait-aksrt-db-migrator-part-1.php diff --git a/inc/bootstrap.php b/inc/bootstrap.php index 64e5ec91..0f6e259e 100644 --- a/inc/bootstrap.php +++ b/inc/bootstrap.php @@ -200,7 +200,6 @@ require_once AKSRT_DIR . '/inc/classes/class-aksrt-email-rollback.php'; require_once AKSRT_DIR . '/inc/classes/class-aksrt-user-credits-admin.php'; // 加载数据表迁移器(CMS / 积分 / 下载 token) -require_once AKSRT_DIR . '/inc/classes/db-traits/trait-aksrt-db-migrator-part-1.php'; require_once AKSRT_DIR . '/inc/classes/db-traits/trait-aksrt-db-migrator-part-2.php'; require_once AKSRT_DIR . '/inc/classes/db-traits/trait-aksrt-db-migrator-part-3.php'; require_once AKSRT_DIR . '/inc/classes/class-aksrt-db-migrator.php'; diff --git a/inc/classes/class-aksrt-db-migrator.php b/inc/classes/class-aksrt-db-migrator.php index 1cb0ac76..73e6dd19 100644 --- a/inc/classes/class-aksrt-db-migrator.php +++ b/inc/classes/class-aksrt-db-migrator.php @@ -2,20 +2,12 @@ /** * AKSRT DB Migrator * - * 维护 CMS / 积分 / 下载 token 等运行时所需的数据表。 + * Maintains runtime tables (credits / download tokens / shop / forum support, etc.). + * Schema only via dbDelta — no option/data migration, no legacy option dual-write. * - * 使用 dbDelta 幂等创建,所有表带主题前缀 aksrt_。 - * - * 表清单(v3.5 新增): - * - {prefix}aksrt_credit_log 积分流水 - * - {prefix}aksrt_signin_log 每日签到记录 - * - {prefix}aksrt_invite 邀请关系 - * - {prefix}aksrt_dl_token 下载短期 token - * - {prefix}aksrt_notification 用户通知收件箱 - * - * 触发方式: - * 1. switch_theme / after_switch_theme 钩子 - * 2. 检测 option 'aksrt_db_schema_version' 与 AKSRT_DB_SCHEMA_VERSION 不一致 + * Trigger: + * 1. after_switch_theme + * 2. admin_init when aksrt_db_schema_version !== SCHEMA_VERSION * * @package AKSRT */ @@ -23,18 +15,22 @@ defined('ABSPATH') || exit; class AKSRT_DB_Migrator { - use AKSRT_DB_Migrator_Part_1; use AKSRT_DB_Migrator_Part_2; use AKSRT_DB_Migrator_Part_3; - - /** 当前 schema 版本,更改后会触发 dbDelta。 */ + /** Current schema version; changing it re-runs dbDelta. */ const SCHEMA_VERSION = '1.13.0'; const OPT_KEY = 'aksrt_db_schema_version'; /** - * 初始化钩子。 + * Historical flag still readable by older code paths. + * Plate CPT is now native when registered; prefer post_type_exists(). + */ + const OPT_PLATE_CPT_MIGRATED = 'aksrt_plate_cpt_migrated'; + + /** + * Register hooks. */ public static function init(): void { add_action('after_switch_theme', [self::class, 'maybe_migrate']); @@ -42,345 +38,15 @@ class AKSRT_DB_Migrator { } /** - * 若版本不一致则执行迁移。 + * Run table schema upgrade when version mismatches. */ public static function maybe_migrate(): void { $current = get_option(self::OPT_KEY, ''); - if ($current !== self::SCHEMA_VERSION) { - self::run(); - update_option(self::OPT_KEY, self::SCHEMA_VERSION); - } - - // 选项级(option)数据迁移:独立版本号控制,与表结构解耦。 - self::maybe_migrate_options(); - } - - const OPT_LAYOUT_MIGRATED = 'aksrt_archive_layout_migrated'; - const OPT_RESOURCE_MIGRATED = 'aksrt_resource_cpt_migrated'; - const OPT_CMS_FORMAT_MIGRATED = 'aksrt_cms_format_migrated'; - const OPT_RESOURCE_PAY_META_CURSOR = 'aksrt_resource_pay_meta_cursor'; - const OPT_PLATE_CPT_MIGRATED = 'aksrt_plate_cpt_migrated'; - - /** - * 一次性 option 迁移: - * - 旧 aksrt_basic_settings.post_layout / aksrt_settings.default_archive_layout - * → 统一为 aksrt_settings.archive_layout - * - 旧 aksrt_resource CPT 文章 → post + _aksrt_is_resource = 1 - * - v4 形态归一:清理 site_profile / opening_mode / archive_layout 等历史字段 - * 并删除全部分类的 aksrt_flow term meta - */ - public static function maybe_migrate_options(): void { - self::maybe_migrate_layout(); - self::maybe_migrate_resource_cpt(); - self::maybe_migrate_cms_format(); - self::maybe_migrate_resource_pay_meta(); - self::maybe_migrate_orders_to_repository(); - self::maybe_migrate_board_taxonomy_to_plate_cpt(); - } - - private static function maybe_migrate_layout(): void { - if (get_option(self::OPT_LAYOUT_MIGRATED)) return; - - $settings = get_option('aksrt_settings', []); - if (!is_array($settings)) $settings = []; - - $basic = get_option('aksrt_basic_settings', []); - if (!is_array($basic)) $basic = []; - - $allowed = ['card', 'list', 'masonry', 'grid-cms']; - - $existing_archive = $settings['archive_layout'] ?? ''; - if (!in_array($existing_archive, $allowed, true)) { - // 优先取旧 default_archive_layout,其次旧 post_layout - $candidate = $settings['default_archive_layout'] - ?? $basic['post_layout'] - ?? $settings['post_layout'] - ?? 'card'; - if (!in_array($candidate, $allowed, true)) $candidate = 'card'; - $settings['archive_layout'] = $candidate; - } - - unset($settings['default_archive_layout']); - unset($settings['post_layout']); - update_option('aksrt_settings', $settings); - - if (isset($basic['post_layout'])) { - unset($basic['post_layout']); - update_option('aksrt_basic_settings', $basic); - } - - update_option(self::OPT_LAYOUT_MIGRATED, time()); - } - - /** - * 把历史 aksrt_resource CPT 文章迁移到 post: - * - post_type = aksrt_resource → post - * - _aksrt_is_resource = 1 - * - * 仅在 admin/cron 上下文且有数据时分批执行;幂等。 - */ - private static function maybe_migrate_resource_cpt(): void { - if (get_option(self::OPT_RESOURCE_MIGRATED)) return; - if (!is_admin() && !(defined('DOING_CRON') && DOING_CRON)) return; - - global $wpdb; - - // 仅查 post_type 字段,避免拉全量行 - $ids = $wpdb->get_col("SELECT ID FROM {$wpdb->posts} WHERE post_type = 'aksrt_resource' LIMIT 500"); - if (empty($ids)) { - // 无历史数据:直接打标记 - update_option(self::OPT_RESOURCE_MIGRATED, time()); + if ($current === self::SCHEMA_VERSION) { return; } - foreach ($ids as $id) { - $id = (int) $id; - // 转 post_type - $wpdb->update( - $wpdb->posts, - ['post_type' => 'post'], - ['ID' => $id], - ['%s'], - ['%d'] - ); - update_post_meta($id, '_aksrt_is_resource', 1); - clean_post_cache($id); - } - - // 若一批迁移满 500,留待下次触发;否则收尾 - if (count($ids) < 500) { - update_option(self::OPT_RESOURCE_MIGRATED, time()); - // 触发重写规则刷新(因为 /resource/{slug} 旧链接需要 301 重定向) - delete_option('rewrite_rules'); - } + self::run(); + update_option(self::OPT_KEY, self::SCHEMA_VERSION); } - - /** - * v4 形态归一迁移: - * - aksrt_settings 中的 site_profile / opening_mode / archive_layout - * / default_archive_layout / post_layout 全部 unset - * - aksrt_basic_settings 中的 opening_mode / post_layout 全部 unset - * - 全部分类的 aksrt_flow term meta 删除 - * - * 幂等。 - */ - private static function maybe_migrate_cms_format(): void { - if (get_option(self::OPT_CMS_FORMAT_MIGRATED)) return; - - // 1. aksrt_settings - $settings = get_option('aksrt_settings', []); - if (is_array($settings)) { - $changed = false; - foreach (['site_profile', 'opening_mode', 'archive_layout', 'default_archive_layout', 'post_layout'] as $k) { - if (array_key_exists($k, $settings)) { - unset($settings[$k]); - $changed = true; - } - } - if ($changed) { - update_option('aksrt_settings', $settings); - } - } - - // 2. aksrt_basic_settings - $basic = get_option('aksrt_basic_settings', []); - if (is_array($basic)) { - $changed = false; - foreach (['opening_mode', 'post_layout'] as $k) { - if (array_key_exists($k, $basic)) { - unset($basic[$k]); - $changed = true; - } - } - if ($changed) { - update_option('aksrt_basic_settings', $basic); - } - } - - // 3. 删除全部分类的 aksrt_flow term meta - global $wpdb; - if ($wpdb instanceof wpdb) { - $wpdb->delete($wpdb->termmeta, ['meta_key' => 'aksrt_flow'], ['%s']); - } - - update_option(self::OPT_CMS_FORMAT_MIGRATED, time()); - } - - private static function maybe_migrate_resource_pay_meta(): void { - if (get_option(AKSRT_Domain_Model::RESOURCE_PAY_META_MIGRATED)) return; - if (!is_admin() && !(defined('DOING_CRON') && DOING_CRON)) return; - - global $wpdb; - - $batch_size = 200; - $cursor = (int) get_option(self::OPT_RESOURCE_PAY_META_CURSOR, 0); - $candidate_keys = [ - AKSRT_Domain_Model::PAY_META_KEY, - AKSRT_Domain_Model::PAY_TYPE_KEY, - AKSRT_Domain_Model::PAY_PRICE_KEY, - AKSRT_Domain_Model::RESOURCE_MARKER_KEY, - '_aksrt_res_pay_type', - '_aksrt_res_price', - '_aksrt_res_original_price', - '_aksrt_res_vip1_price', - '_aksrt_res_vip2_price', - '_aksrt_res_version', - '_aksrt_res_size', - '_aksrt_res_license', - '_aksrt_res_compat', - '_aksrt_res_demo_url', - '_aksrt_res_downloads', - '_aksrt_res_require_login', - '_aksrt_res_require_captcha', - 'download_count', - ]; - - $placeholders = implode(', ', array_fill(0, count($candidate_keys), '%s')); - $sql = "SELECT DISTINCT pm.post_id - FROM {$wpdb->postmeta} pm - INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id - WHERE p.post_type IN ('post', 'aksrt_resource') - AND pm.meta_key IN ({$placeholders}) - AND pm.post_id > %d - ORDER BY pm.post_id ASC - LIMIT %d"; - - $params = array_merge($candidate_keys, [$cursor, $batch_size]); - $ids = $wpdb->get_col($wpdb->prepare($sql, $params)); - - if (empty($ids)) { - delete_option(self::OPT_RESOURCE_PAY_META_CURSOR); - update_option(AKSRT_Domain_Model::RESOURCE_PAY_META_MIGRATED, time()); - return; - } - - foreach ($ids as $post_id) { - self::migrate_resource_pay_meta_post((int) $post_id); - } - - update_option(self::OPT_RESOURCE_PAY_META_CURSOR, max(array_map('intval', $ids))); - - if (count($ids) < $batch_size) { - delete_option(self::OPT_RESOURCE_PAY_META_CURSOR); - update_option(AKSRT_Domain_Model::RESOURCE_PAY_META_MIGRATED, time()); - } - } - - private static function maybe_migrate_orders_to_repository(): void { - if (get_option(AKSRT_Domain_Model::ORDER_MIGRATED_OPTION)) return; - if (!class_exists('AKSRT_Order_Repository')) return; - - AKSRT_Order_Repository::get_instance()->migrate_from_option(); - update_option(AKSRT_Domain_Model::ORDER_MIGRATED_OPTION, time()); - } - - /** - * §7.0 板块 taxonomy → plate CPT 一次性迁移。 - * - * 将 aksrt_forum_board taxonomy terms 转为 aksrt_forum_plate CPT posts, - * 保留 term meta → post meta 映射、层级关系、版主数据。 - * 同时在每个帖子上写入 _aksrt_plate_id 指向新 plate post。 - * 旧 taxonomy 数据保留不删(向后兼容)。 - */ - private static function maybe_migrate_board_taxonomy_to_plate_cpt(): void { - if (get_option(self::OPT_PLATE_CPT_MIGRATED)) return; - if (!is_admin() && !(defined('DOING_CRON') && DOING_CRON)) return; - if (!post_type_exists('aksrt_forum_plate')) return; - - $terms = get_terms([ - 'taxonomy' => 'aksrt_forum_board', - 'hide_empty' => false, - 'orderby' => 'meta_value_num', - 'meta_key' => '_aksrt_sort_order', - 'order' => 'ASC', - ]); - - if (is_wp_error($terms) || empty($terms)) { - update_option(self::OPT_PLATE_CPT_MIGRATED, time()); - return; - } - - $term_to_plate = []; - - foreach ($terms as $term) { - $existing = get_posts([ - 'post_type' => 'aksrt_forum_plate', - 'meta_key' => '_aksrt_migrated_from_term', - 'meta_value' => (string) $term->term_id, - 'numberposts' => 1, - 'post_status' => 'any', - 'fields' => 'ids', - ]); - - if (!empty($existing)) { - $term_to_plate[$term->term_id] = (int) $existing[0]; - continue; - } - - $plate_id = wp_insert_post([ - 'post_type' => 'aksrt_forum_plate', - 'post_title' => $term->name, - 'post_content' => $term->description ?: '', - 'post_excerpt' => wp_trim_words($term->description ?: '', 30, ''), - 'post_status' => 'publish', - 'post_parent' => 0, - 'menu_order' => (int) get_term_meta($term->term_id, '_aksrt_sort_order', true), - ], true); - - if (is_wp_error($plate_id)) { - continue; - } - - $term_to_plate[$term->term_id] = $plate_id; - - update_post_meta($plate_id, '_aksrt_migrated_from_term', $term->term_id); - update_post_meta($plate_id, '_aksrt_sort_order', (int) get_term_meta($term->term_id, '_aksrt_sort_order', true)); - update_post_meta($plate_id, '_aksrt_icon', (string) get_term_meta($term->term_id, '_aksrt_icon', true)); - - $moderators = get_term_meta($term->term_id, '_aksrt_moderators', true); - if (is_array($moderators) && !empty($moderators)) { - update_post_meta($plate_id, '_aksrt_moderators', $moderators); - } - - $roles = get_term_meta($term->term_id, '_aksrt_moderator_roles', true); - if (is_array($roles) && !empty($roles)) { - update_post_meta($plate_id, '_aksrt_moderator_roles', $roles); - } - - $topic_count = (int) get_term_meta($term->term_id, '_aksrt_topic_count', true); - if ($topic_count <= 0) { - $topic_count = (int) $term->count; - } - update_post_meta($plate_id, '_aksrt_posts_count', $topic_count); - } - - // Resolve parent hierarchy - foreach ($terms as $term) { - if ($term->parent > 0 && isset($term_to_plate[$term->term_id], $term_to_plate[$term->parent])) { - wp_update_post([ - 'ID' => $term_to_plate[$term->term_id], - 'post_parent' => $term_to_plate[$term->parent], - ]); - } - } - - // Assign _aksrt_plate_id on existing topics - global $wpdb; - foreach ($term_to_plate as $term_id => $plate_id) { - $topic_ids = $wpdb->get_col($wpdb->prepare( - "SELECT tr.object_id FROM {$wpdb->term_relationships} tr - INNER JOIN {$wpdb->term_taxonomy} tt ON tt.term_taxonomy_id = tr.term_taxonomy_id - WHERE tt.term_id = %d AND tt.taxonomy = 'aksrt_forum_board'", - $term_id - )); - - foreach ($topic_ids as $topic_id) { - update_post_meta((int) $topic_id, '_aksrt_plate_id', $plate_id); - } - } - - delete_option('rewrite_rules'); - update_option(self::OPT_PLATE_CPT_MIGRATED, time()); - } - } diff --git a/inc/classes/class-aksrt-forum.php b/inc/classes/class-aksrt-forum.php index 62284311..c2ade5e9 100644 --- a/inc/classes/class-aksrt-forum.php +++ b/inc/classes/class-aksrt-forum.php @@ -132,8 +132,8 @@ class AKSRT_Forum { } public function is_plate_cpt_active(): bool { - return post_type_exists('aksrt_forum_plate') - && (bool) get_option(AKSRT_DB_Migrator::OPT_PLATE_CPT_MIGRATED); + // Plate CPT is the native board model; no option migration gate. + return post_type_exists('aksrt_forum_plate'); } public function get_config(): array { diff --git a/inc/classes/db-traits/trait-aksrt-db-migrator-part-1.php b/inc/classes/db-traits/trait-aksrt-db-migrator-part-1.php deleted file mode 100644 index ad463333..00000000 --- a/inc/classes/db-traits/trait-aksrt-db-migrator-part-1.php +++ /dev/null @@ -1,198 +0,0 @@ -post_type === 'aksrt_resource', - !empty($legacy_downloads), - ]); - - if ($raw_pay_type === '' && $has_resource_marker) { - $raw_pay_type = AKSRT_Domain_Model::PAY_TYPE_DOWNLOAD; - } - - $pay_meta = AKSRT_Domain_Model::sanitize_pay_meta([ - 'pay_type' => $raw_pay_type, - 'price' => self::first_numeric([ - $canonical_meta['price'] ?? null, - self::meta_value($all_meta, AKSRT_Domain_Model::PAY_PRICE_KEY), - self::meta_value($all_meta, '_aksrt_res_price'), - ]), - 'original_price' => self::first_numeric([ - $canonical_meta['original_price'] ?? null, - self::meta_value($all_meta, '_aksrt_res_original_price'), - ]), - 'vip1_price' => self::first_numeric([ - $canonical_meta['vip1_price'] ?? null, - self::meta_value($all_meta, '_aksrt_res_vip1_price'), - ]), - 'vip2_price' => self::first_numeric([ - $canonical_meta['vip2_price'] ?? null, - self::meta_value($all_meta, '_aksrt_res_vip2_price'), - ]), - 'version' => self::first_non_empty_string([ - $canonical_meta['version'] ?? '', - self::meta_value($all_meta, '_aksrt_res_version'), - ]), - 'size' => self::first_non_empty_string([ - $canonical_meta['size'] ?? '', - self::meta_value($all_meta, '_aksrt_res_size'), - ]), - 'license' => self::first_non_empty_string([ - $canonical_meta['license'] ?? '', - self::meta_value($all_meta, '_aksrt_res_license'), - ]), - 'compat' => self::first_non_empty_string([ - $canonical_meta['compat'] ?? '', - self::meta_value($all_meta, '_aksrt_res_compat'), - ]), - 'demo_url' => self::first_non_empty_string([ - $canonical_meta['demo_url'] ?? '', - self::meta_value($all_meta, '_aksrt_res_demo_url'), - ]), - 'video_url' => self::first_non_empty_string([ - $canonical_meta['video_url'] ?? '', - ]), - 'downloads' => self::first_array([ - $canonical_meta['downloads'] ?? [], - $legacy_downloads, - ]), - 'attributes' => self::first_array([ - $canonical_meta['attributes'] ?? [], - ]), - 'gallery' => self::first_array([ - $canonical_meta['gallery'] ?? [], - ]), - 'require_login' => self::first_boolean([ - $canonical_meta['require_login'] ?? null, - self::meta_value($all_meta, '_aksrt_res_require_login'), - ]), - 'require_captcha' => self::first_boolean([ - $canonical_meta['require_captcha'] ?? null, - self::meta_value($all_meta, '_aksrt_res_require_captcha'), - ]), - ]); - - if ($pay_meta['pay_type'] === AKSRT_Domain_Model::PAY_TYPE_NO && $has_resource_marker) { - $pay_meta['pay_type'] = AKSRT_Domain_Model::PAY_TYPE_DOWNLOAD; - } - - $pay_meta = AKSRT_Domain_Model::sanitize_pay_meta($pay_meta); - - update_post_meta($post_id, AKSRT_Domain_Model::PAY_META_KEY, $pay_meta); - update_post_meta($post_id, AKSRT_Domain_Model::PAY_TYPE_KEY, (string) $pay_meta['pay_type']); - update_post_meta($post_id, AKSRT_Domain_Model::PAY_PRICE_KEY, (float) $pay_meta['price']); - update_post_meta( - $post_id, - AKSRT_Domain_Model::RESOURCE_MARKER_KEY, - AKSRT_Domain_Model::is_resource_pay_type((string) $pay_meta['pay_type']) ? 1 : 0 - ); - - clean_post_cache($post_id); - } - - /** - * @param array> $all_meta - * @return mixed - */ - private static function meta_value(array $all_meta, string $key) { - if (!isset($all_meta[$key][0])) { - return null; - } - - return maybe_unserialize($all_meta[$key][0]); - } - - /** - * @param array $candidates - */ - private static function first_non_empty_string(array $candidates): string { - foreach ($candidates as $candidate) { - if (is_array($candidate)) { - continue; - } - - $value = trim((string) $candidate); - if ($value !== '') { - return $value; - } - } - - return ''; - } - - /** - * @param array $candidates - */ - private static function first_numeric(array $candidates): float { - foreach ($candidates as $candidate) { - if ($candidate === null || $candidate === '') { - continue; - } - - if (is_numeric($candidate)) { - return (float) $candidate; - } - } - - return 0.0; - } - - /** - * @param array $candidates - */ - private static function first_boolean(array $candidates): bool { - foreach ($candidates as $candidate) { - if ($candidate === null || $candidate === '') { - continue; - } - - return !empty($candidate); - } - - return false; - } - - /** - * @param array $candidates - * @return array - */ - private static function first_array(array $candidates): array { - foreach ($candidates as $candidate) { - if (is_array($candidate) && !empty($candidate)) { - return $candidate; - } - } - - return []; - } -}