feat(batch4): WPML translation tools — languages, post language, translation map, missing-translation audit
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
/**
|
||||
* Batch 4 — WPML translation tools (Pro-equivalent, net-new).
|
||||
*
|
||||
* Reimplements the multilingual capabilities of the elementor-mcp Pro tier for WPML,
|
||||
* using WPML's public filter API only (no WPML source copied). GPL-2.0-or-later. See NOTICE.md.
|
||||
*
|
||||
* Stays inert unless WPML (SitePress) is active. Registered via asktam_mcp_register_tools.
|
||||
*/
|
||||
|
||||
namespace AskTamMCP\Extensions;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class Batch4 {
|
||||
|
||||
public static function register($registry): void {
|
||||
if (!defined('ICL_SITEPRESS_VERSION')) return; // WPML-only batch.
|
||||
|
||||
$def = function (string $name, string $desc, array $props, array $required, string $scope, string $method) {
|
||||
$schema = ['type' => '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)];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user