>|null status_key => row */ private static ?array $cache = null; public static function flush(): void { self::$cache = null; } /** * 全部状态,按 sort_order / id 正序。 * * @return list> */ public static function all(): array { global $wpdb; $table = \AKSRT\Core\DBMigrator::table('ticket_status'); if ($table === '') { return []; } $rows = $wpdb->get_results("SELECT * FROM {$table} ORDER BY sort_order ASC, id ASC", ARRAY_A); // phpcs:ignore WordPress.DB.DirectDatabaseQuery if (!is_array($rows)) { return []; } self::$cache = []; $out = []; foreach ($rows as $row) { $normalized = self::normalize($row); self::$cache[(string) $normalized['status_key']] = $normalized; $out[] = $normalized; } return $out; } /** @return array> status_key => row */ public static function map(): array { if (self::$cache === null) { self::all(); } return self::$cache ?? []; } /** @return array|null */ public static function find(string $status_key): ?array { $map = self::map(); return $map[$status_key] ?? null; } public static function exists(string $status_key): bool { return self::find($status_key) !== null; } public static function is_closed(string $status_key): bool { $row = self::find($status_key); return $row !== null && !empty($row['is_closed']); } /** 状态展示名;未知 key 回落 key 本身(工单可能引用已被删的状态)。 */ public static function label(string $status_key): string { $row = self::find($status_key); return $row !== null ? (string) $row['name'] : $status_key; } public static function color(string $status_key): string { $row = self::find($status_key); return $row !== null ? (string) $row['color'] : '#6b7280'; } /** * 状态机:从 $from 迁移到 $to 是否合法。 * * 只挡确定非法的组合,其余交给「目标状态必须存在」: * - 已完结状态(is_closed)不可再流转; * - 「已解决/关闭」类终态不允许从未接单一步跳过去(必须先接收或回复), * 避免管理员误把刚提交的工单直接关掉; * - 同状态原地提交视为合法(幂等,便于后台重复点击)。 */ public static function can_transition(string $from, string $to): bool { if ($to === '' || !self::exists($to)) { return false; } if ($from === $to) { return true; } if (self::is_closed($from)) { return false; } if ($from === self::KEY_PENDING && $to === self::KEY_CLOSED) { return false; } return true; } /** * @param array $row * @return array */ private static function normalize(array $row): array { return [ 'id' => (int) ($row['id'] ?? 0), 'status_key' => sanitize_key((string) ($row['status_key'] ?? '')), 'name' => sanitize_text_field((string) ($row['name'] ?? '')), 'color' => self::sanitize_color((string) ($row['color'] ?? '')), 'is_closed' => !empty($row['is_closed']), 'is_default' => !empty($row['is_default']), 'sort_order' => (int) ($row['sort_order'] ?? 0), 'created_at' => (string) ($row['created_at'] ?? ''), 'updated_at' => (string) ($row['updated_at'] ?? ''), ]; } /** 颜色只接受 #rgb / #rrggbb,其余回落默认灰。 */ public static function sanitize_color(string $color): string { $color = strtolower(trim($color)); if (preg_match('/^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/', $color) !== 1) { return '#6b7280'; } return $color; } /** 新建状态时生成唯一 status_key(ASCII 标签取 key,其余取短哈希)。 */ public static function make_key(string $name): string { $key = sanitize_key($name); if ($key === '') { $key = 'st' . substr(md5($name . (string) wp_rand(1, 999999)), 0, 8); } $base = $key; $suffix = 1; while (self::exists($key)) { $key = $base . $suffix; $suffix++; } return $key; } }