prefix}apgt_translations, * 结构参考 website-dev 的 translate_list。 * * @package Aipogeo_Context_Translate */ defined( 'ABSPATH' ) || exit; // 建表与结构升级只能直查 $wpdb:表名由 $wpdb->prefix 拼出(不含用户输入,且表名 // 不支持 %s 占位符),ALTER 一类的 schema 变更本来就是安装逻辑该做的事, // 一次性执行也没有缓存可言。 // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching // phpcs:disable WordPress.DB.DirectDatabaseQuery.SchemaChange // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared // phpcs:disable PluginCheck.Security.DirectDB.UnescapedDBParameter /** * Class APGT_Install */ class APGT_Install { /** * 数据表结构版本。表结构变化时递增,触发按需升级。 */ const DB_VERSION = '1.2.0'; /** * 存放数据表版本号的 option 名。 */ const DB_VERSION_OPTION = 'apgt_db_version'; /** * 取语言包数据表全名(含前缀)。 * * @return string */ public static function table_name() { global $wpdb; return $wpdb->prefix . 'apgt_translations'; } /** * 取页面翻译进度表全名(含前缀)。 * * @return string */ public static function progress_table_name() { global $wpdb; return $wpdb->prefix . 'apgt_page_progress'; } /** * 检测并按需建表 / 升级。 * * 每次插件加载时调用(开销极小:仅比对一个 option); * 激活钩子也会调用,保证首次安装即建表。 * * @return void */ public static function maybe_install() { $installed = get_option( self::DB_VERSION_OPTION ); if ( self::DB_VERSION === $installed ) { return; } self::install(); update_option( self::DB_VERSION_OPTION, self::DB_VERSION ); } /** * 用 dbDelta 建表 / 同步结构。 * * dbDelta 会对比现有结构,安全地新增缺失的列/索引,不会删数据。 * * @return void */ public static function install() { global $wpdb; require_once ABSPATH . 'wp-admin/includes/upgrade.php'; $table = self::table_name(); $charset_collate = $wpdb->get_charset_collate(); // 注意:dbDelta 对 SQL 格式敏感(两个空格、PRIMARY KEY 写法、每个字段独立成行)。 $sql = "CREATE TABLE {$table} ( id bigint(20) unsigned NOT NULL AUTO_INCREMENT, text_source longtext NOT NULL, text_trans longtext NOT NULL, language_from varchar(16) NOT NULL DEFAULT '', language_to varchar(16) NOT NULL DEFAULT '', source_url text NULL, source_md5 char(32) NOT NULL DEFAULT '', engine varchar(32) NOT NULL DEFAULT 'ai', type varchar(8) NOT NULL DEFAULT 'text', updated_at datetime NULL, PRIMARY KEY (id), UNIQUE KEY uk_text_lang (language_to,language_from,source_md5), KEY source_md5 (source_md5), KEY engine (engine) ) {$charset_collate};"; dbDelta( $sql ); // 页面翻译进度。 // // 不能拿 translations.source_url 当页面进度用:同一段文本(导航、页脚) // 会出现在多个页面上,而那张表按原文 md5 去重、source_url 是 upsert 时 // 覆盖写的,翻译 B 页面会把共享文本的归属从 A 抢走,A 又变回「未翻译」。 // 「全部翻译」和后台 worker 照此判断就会无休止地重复翻译同一批页面。 $progress = self::progress_table_name(); $sql = "CREATE TABLE {$progress} ( id bigint(20) unsigned NOT NULL AUTO_INCREMENT, page_path text NOT NULL, path_md5 char(32) NOT NULL DEFAULT '', language_to varchar(16) NOT NULL DEFAULT '', total int(10) unsigned NOT NULL DEFAULT 0, done int(10) unsigned NOT NULL DEFAULT 0, updated_at datetime NULL, PRIMARY KEY (id), UNIQUE KEY uk_page_lang (path_md5,language_to), KEY language_to (language_to) ) {$charset_collate};"; dbDelta( $sql ); self::migrate_source_url_to_path(); } /** * 把历史数据里的 source_url 从完整地址改成站内路径。 * * 1.0.0 存的是 http://host/path,站点换域名或改 https 后,页面维度的 * 「已翻译」标记会全部对不上号。译文本身按原文 md5 索引,不受影响。 * * 只剥掉开头的 scheme://host[:port],已是路径的记录不会被匹配到,可重复执行。 * * @return void */ private static function migrate_source_url_to_path() { global $wpdb; $table = self::table_name(); $wpdb->query( // phpcs:ignore WordPress.DB.PreparedSQL, WordPress.DB.DirectDatabaseQuery "UPDATE {$table} SET source_url = REGEXP_REPLACE(source_url, '^[a-zA-Z][a-zA-Z0-9+.-]*://[^/]*', '') WHERE source_url REGEXP '^[a-zA-Z][a-zA-Z0-9+.-]*://'" ); // 形如 http://example.com(无路径)的记录剥完会变成空串,补成首页。 $wpdb->query( // phpcs:ignore WordPress.DB.PreparedSQL, WordPress.DB.DirectDatabaseQuery "UPDATE {$table} SET source_url = '/' WHERE source_url = ''" ); } /** * 卸载时删除数据表(供 uninstall.php 调用)。 * * @return void */ public static function drop_table() { global $wpdb; // 表名由前缀 + 常量拼接,非用户输入,直接执行。 foreach ( array( self::table_name(), self::progress_table_name() ) as $table ) { $wpdb->query( "DROP TABLE IF EXISTS {$table}" ); // phpcs:ignore WordPress.DB.PreparedSQL } } }