128 lines
6.4 KiB
PHP
128 lines
6.4 KiB
PHP
<?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)];
|
|
}
|
|
}
|