'object', 'properties' => $props]; if ($required) $schema['required'] = $required; return ['name' => $name, 'description' => $desc, 'scope' => $scope, 'handler' => 'AskTamMCP\\Extensions\\Batch4::' . $method, 'inputSchema' => $schema]; }; $registry->register($def('wpml_list_languages', 'List all WPML languages: code, name, native name, active flag, and which is the default.', [], [], 'read', 'listLanguages')); $registry->register($def('wpml_get_post_language', 'Get the WPML language code of a post.', ['post_id' => ['type' => 'integer']], ['post_id'], 'read', 'postLanguage')); $registry->register($def('wpml_get_translations', 'Get the full translation group of a post: for each language, the translated post id, title, status, and whether it exists.', ['post_id' => ['type' => 'integer']], ['post_id'], 'read', 'getTranslations')); $registry->register($def('wpml_missing_translations', 'List the WPML languages a post is NOT yet translated into.', ['post_id' => ['type' => 'integer']], ['post_id'], 'read', 'missingTranslations')); $registry->register($def('wpml_audit', 'Audit published posts/pages for missing translations across all active WPML languages. Returns per-language missing counts and the offenders.', ['post_types' => ['type' => 'array', 'items' => ['type' => 'string'], 'default' => ['page', 'post']], 'per_page' => ['type' => 'integer', 'default' => 50]], [], 'read', 'audit')); } private static function languages(): array { return (array) apply_filters('wpml_active_languages', null, ['skip_missing' => 0]); } private static function elementType(int $id): string { return 'post_' . get_post_type($id); } public static function listLanguages(array $args): array { $langs = self::languages(); $default = apply_filters('wpml_default_language', null); $out = []; foreach ($langs as $code => $l) { $out[] = ['code' => $code, 'name' => $l['translated_name'] ?? ($l['native_name'] ?? $code), 'native_name' => $l['native_name'] ?? '', 'active' => !empty($l['active']), 'is_default' => $code === $default]; } return ['languages' => $out, 'default' => $default, 'count' => count($out)]; } public static function postLanguage(array $args): array { $id = (int) ($args['post_id'] ?? 0); if (!$id) throw new \RuntimeException('post_id is required.'); $details = apply_filters('wpml_post_language_details', null, $id); $code = is_array($details) ? ($details['language_code'] ?? null) : null; return ['post_id' => $id, 'language' => $code, 'details' => $details ?: null]; } public static function getTranslations(array $args): array { $id = (int) ($args['post_id'] ?? 0); if (!$id) throw new \RuntimeException('post_id is required.'); $type = self::elementType($id); $trid = apply_filters('wpml_element_trid', null, $id, $type); if (!$trid) return ['post_id' => $id, 'trid' => null, 'translations' => [], 'note' => 'No translation group (trid) found for this post.']; $translations = apply_filters('wpml_get_element_translations', null, $trid, $type); $map = []; foreach ((array) $translations as $code => $t) { $tid = (int) ($t->element_id ?? 0); $map[$code] = [ 'post_id' => $tid, 'title' => $tid ? get_the_title($tid) : '', 'status' => $tid ? get_post_status($tid) : 'missing', 'is_source' => !empty($t->original), 'exists' => (bool) $tid, ]; } return ['post_id' => $id, 'trid' => $trid, 'translations' => $map, 'count' => count($map)]; } public static function missingTranslations(array $args): array { $id = (int) ($args['post_id'] ?? 0); $group = self::getTranslations($args); $have = array_keys($group['translations']); $all = array_keys(self::languages()); $missing = array_values(array_diff($all, $have)); return ['post_id' => $id, 'missing_languages' => $missing, 'missing_count' => count($missing), 'translated_into' => $have]; } public static function audit(array $args): array { $types = $args['post_types'] ?? ['page', 'post']; $per = (int) ($args['per_page'] ?? 50); $default = apply_filters('wpml_default_language', null); $allLangs = array_keys(self::languages()); // Audit the default-language originals. $posts = get_posts(['post_type' => $types, 'post_status' => 'publish', 'posts_per_page' => $per, 'fields' => 'ids', 'suppress_filters' => false]); $perLang = array_fill_keys($allLangs, 0); $offenders = []; foreach ($posts as $pid) { $pid = (int) $pid; $details = apply_filters('wpml_post_language_details', null, $pid); $lang = is_array($details) ? ($details['language_code'] ?? '') : ''; if ($default && $lang && $lang !== $default) continue; // only audit source-language posts $g = self::getTranslations(['post_id' => $pid]); $have = array_keys($g['translations']); $missing = array_values(array_diff($allLangs, $have)); if ($missing) { foreach ($missing as $m) $perLang[$m]++; $offenders[] = ['post_id' => $pid, 'title' => get_the_title($pid), 'missing' => $missing]; } } return ['audited' => count($posts), 'languages' => $allLangs, 'missing_per_language' => $perLang, 'offenders' => $offenders, 'offender_count' => count($offenders)]; } }