'OR', ['key' => '_wp_attachment_image_alt', 'compare' => 'NOT EXISTS'], ['key' => '_wp_attachment_image_alt', 'value' => '', 'compare' => '='], ]; $existing = isset($args['meta_query']) && is_array($args['meta_query']) ? $args['meta_query'] : []; $existing[] = $missing_alt_group; $args['meta_query'] = $existing; return $args; } /** * Include the original filename in the attachment JS payload for display in the UI. */ public function add_original_filename(array $response, WP_Post $attachment, array $meta): array { if (!empty($meta['original_image'])) { $response['originalFilename'] = $meta['original_image']; } return $response; } /** * JOIN the postmeta tables necessary for searching stored filenames/alts in the list view. */ public function filter_posts_join(string $join, $query): string { if (!$this->is_list_query($query)) { return $join; } global $wpdb; $join .= " LEFT JOIN $wpdb->postmeta AS ai_m1 ON ($wpdb->posts.ID = ai_m1.post_id AND ai_m1.meta_key = '_wp_attachment_image_alt')"; $join .= " LEFT JOIN $wpdb->postmeta AS ai_m2 ON ($wpdb->posts.ID = ai_m2.post_id AND ai_m2.meta_key = '_wp_attached_file')"; return $join; } /** * Extend wp_query->s searching to include postmeta fields for filenames and alt text. */ public function filter_posts_search(string $search, $query): string { if (!$this->is_list_query($query)) { return $search; } $raw = (string) $query->get('s'); $raw = $raw !== '' ? sanitize_text_field($raw) : ''; if ($raw === '') { return ''; } global $wpdb; $like = '%' . $wpdb->esc_like($raw) . '%'; $parts = []; $parts[] = $wpdb->prepare("$wpdb->posts.post_title LIKE %s", $like); $parts[] = $wpdb->prepare("$wpdb->posts.post_name LIKE %s", $like); $parts[] = $wpdb->prepare("ai_m1.meta_value LIKE %s", $like); $parts[] = $wpdb->prepare("ai_m2.meta_value LIKE %s", $like); return ' AND ( ' . implode(' OR ', $parts) . ' ) '; } /** * Force DISTINCT selects for custom list queries to avoid duplicates from meta joins. */ public function filter_posts_distinct($distinct, $query) { if ($this->is_list_query($query)) { return 'DISTINCT'; } return $distinct; } /** * Retrieve the stored “recent languages” queue for the current user and type. */ public function get_recent_langs(string $type, ?string $provider = null): array { $key = 'ai_recent_langs_' . $type; $map_key = $key . '_map'; $base = get_user_meta(get_current_user_id(), $key, true); $base = is_array($base) ? array_values(array_filter($base, 'is_string')) : []; if ($provider === null || $provider === '') { return $base; } $normalized_provider = AIGUDE_Translation_Service::normalize_translation_provider($provider); $map = get_user_meta(get_current_user_id(), $map_key, true); $map = is_array($map) ? $map : []; $list = isset($map[$normalized_provider]) && is_array($map[$normalized_provider]) ? array_values(array_filter($map[$normalized_provider], 'is_string')) : []; return $list ?: $base; } /** * Push a normalized language code into the recent list, keeping order + max length. */ public function push_recent_lang(string $type, string $code, ?string $provider = null): void { $context = $type === 'target' ? 'target' : 'source'; $normalized_provider = $provider ? AIGUDE_Translation_Service::normalize_translation_provider($provider) : ''; if ($normalized_provider && $type === 'target') { $norm = AIGUDE_Translation_Service::normalize_provider_lang_code_generic($code, $normalized_provider, 'target'); } else { $norm = AIGUDE_Translation_Service::normalize_deepl_lang_code($code, $context); } if ($norm === '' || $norm === 'auto') { return; } $key = 'ai_recent_langs_' . $type; $map_key = $key . '_map'; $arr = get_user_meta(get_current_user_id(), $key, true); $arr = is_array($arr) ? $arr : []; $arr = array_values(array_unique(array_merge([$norm], array_filter($arr, 'is_string')))); $arr = array_slice($arr, 0, 5); update_user_meta(get_current_user_id(), $key, $arr); if ($provider !== null && $provider !== '') { $normalized_provider = AIGUDE_Translation_Service::normalize_translation_provider($provider); $map = get_user_meta(get_current_user_id(), $map_key, true); $map = is_array($map) ? $map : []; $prov_list = isset($map[$normalized_provider]) && is_array($map[$normalized_provider]) ? $map[$normalized_provider] : []; $prov_list = array_values(array_unique(array_merge([$norm], array_filter($prov_list, 'is_string')))); $prov_list = array_slice($prov_list, 0, 5); $map[$normalized_provider] = $prov_list; update_user_meta(get_current_user_id(), $map_key, $map); } } /** * Determine whether the provided query is one of the plugin's custom list views. */ private function is_list_query($query): bool { return $query instanceof WP_Query && $query->get('ai_tools_list'); } }