prefix . 'aitrfoel_translation_history'; // Ensure the table exists (may not on first run before re-activation) if ($wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table)) !== $table) { return; } $wpdb->insert($table, [ 'original_post_id' => absint($args['original_post_id'] ?? 0), 'translated_post_id' => absint($args['translated_post_id'] ?? 0), 'source_lang' => self::normalize_lang($args['source_lang'] ?? ''), 'target_lang' => self::normalize_lang($args['target_lang'] ?? ''), 'content_type' => sanitize_text_field($args['content_type'] ?? 'elementor'), 'word_count' => absint($args['word_count'] ?? 0), 'status' => sanitize_text_field($args['status'] ?? 'success'), 'duration' => floatval($args['duration'] ?? 0), 'user_id' => get_current_user_id(), 'created_at' => current_time('mysql'), ], ['%d', '%d', '%s', '%s', '%s', '%d', '%s', '%f', '%d', '%s']); } /** * Normalize a language value to a lowercase code. * Some engine paths pass full names ("German") and others codes ("de"), which split the * History stats into duplicates (DE + GERMAN) and inflated the language count. */ public static function normalize_lang($lang) { $lang = trim(sanitize_text_field((string) $lang)); if ($lang === '') { return ''; } $to_lower = static function ($str) { return function_exists('mb_strtolower') ? mb_strtolower($str, 'UTF-8') : strtolower($str); }; if (strlen($lang) > 3 && strpos($lang, '-') === false && strpos($lang, '_') === false && class_exists('AITRFOEL_Language_Mapper')) { $code = AITRFOEL_Language_Mapper::get_code_from_language_name($lang); if ($code) { return $to_lower($code); } } return $to_lower($lang); } /** * One-time migration: rewrite rows stored with full language names to codes. */ private function maybe_normalize_history_langs($table) { if (get_option('aitrfoel_history_langs_normalized')) { return; } global $wpdb; $safe_table = esc_sql($table); foreach (['source_lang', 'target_lang'] as $col) { if (!in_array($col, ['source_lang', 'target_lang'], true)) { continue; } // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table and whitelisted column names cannot be value placeholders. $values = $wpdb->get_col("SELECT DISTINCT `{$col}` FROM `{$safe_table}`"); foreach ((array) $values as $value) { $normalized = self::normalize_lang($value); if ($normalized !== '' && $normalized !== $value) { $wpdb->update($table, [$col => $normalized], [$col => $value], ['%s'], ['%s']); } } } update_option('aitrfoel_history_langs_normalized', 1, false); } /** * Render the History admin page */ public function render_page() { $per_page = 30; $current_page = max(1, absint($_GET['paged'] ?? 1)); $offset = ($current_page - 1) * $per_page; // Filters $filter_lang = sanitize_text_field($_GET['lang'] ?? ''); $filter_type = sanitize_text_field($_GET['ctype'] ?? ''); $filter_status = sanitize_text_field($_GET['status'] ?? ''); global $wpdb; $table = $wpdb->prefix . 'aitrfoel_translation_history'; // Check table exists $table_exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table)) === $table; $rows = []; $total = 0; $summary = ['total_translations' => 0, 'total_words' => 0, 'languages' => []]; if ($table_exists) { $this->maybe_normalize_history_langs($table); // Build WHERE clause $where = []; $params = []; if ($filter_lang) { $where[] = 'target_lang = %s'; $params[] = $filter_lang; } if ($filter_type) { $where[] = 'content_type = %s'; $params[] = $filter_type; } if ($filter_status) { $where[] = 'status = %s'; $params[] = $filter_status; } $where_sql = $where ? 'WHERE ' . implode(' AND ', $where) : ''; // Total count $count_query = "SELECT COUNT(*) FROM $table $where_sql"; $total = $params ? (int) $wpdb->get_var($wpdb->prepare($count_query, ...$params)) : (int) $wpdb->get_var($count_query); // Fetch rows $query = "SELECT * FROM $table $where_sql ORDER BY created_at DESC LIMIT %d OFFSET %d"; $all_params = array_merge($params, [$per_page, $offset]); $rows = $wpdb->get_results($wpdb->prepare($query, ...$all_params)); // Summary stats $summary['total_translations'] = (int) $wpdb->get_var("SELECT COUNT(*) FROM $table WHERE status = 'success'"); $summary['total_words'] = (int) $wpdb->get_var("SELECT COALESCE(SUM(word_count), 0) FROM $table WHERE status = 'success'"); $lang_rows = $wpdb->get_results("SELECT target_lang, COUNT(*) as cnt FROM $table WHERE status = 'success' GROUP BY target_lang ORDER BY cnt DESC"); foreach ($lang_rows as $lr) { $summary['languages'][$lr->target_lang] = (int) $lr->cnt; } } $total_pages = ceil($total / $per_page); $base_url = admin_url('admin.php?page=ai-translator-for-elementor-and-polylang-history'); ?>
| created_at))); ?> | 'ai-translator-for-elementor-and-polylang-history', 'lang' => $row->target_lang], admin_url('admin.php')); ?> target_lang)); ?> | content_type)); ?> | word_count); ?> | status === 'success'): ?> | |||