...`): * read → JSON {id,title,content,excerpt,lang,status,author,date,cats} * read_full → JSON con slug, metas, categorías y grupo Polylang * exists → imprime el ID de la traducción en (0 si no hay) * create (lee {title,content} por stdin) * → crea el post traducido, lo enlaza con Polylang y mete metas; * imprime el nuevo ID. * clone (lee payload JSON por stdin) * → inserta/actualiza un post con ID explícito, categorías y metas. * save_translations → guarda un grupo Polylang exacto leído por stdin. * * Ver issue rafa/feadulta#75. */ // Bootstrap portable. Si WP no está cargado (modo standalone), cargar wp-load. // Local (docker): /var/www/html/wp-load.php (por defecto). // Prod: export FEA_WP_LOAD=/web/wp-nuevo/wp-load.php // (Si se ejecuta vía `wp eval-file`, ABSPATH ya está definido y no se recarga.) if (!defined('ABSPATH')) { $_SERVER['REQUEST_URI'] = $_SERVER['REQUEST_URI'] ?? '/'; $_SERVER['HTTP_HOST'] = $_SERVER['HTTP_HOST'] ?? 'localhost'; require_once (getenv('FEA_WP_LOAD') ?: '/var/www/html/wp-load.php'); } if (!function_exists('pll_set_post_language')) { fwrite(STDERR, "Polylang no disponible\n"); exit(2); } $cmd = $argv[1] ?? ''; function out_json($data): void { echo wp_json_encode($data); } function meta_payload(int $id): array { $raw = get_post_meta($id); $out = []; foreach ($raw as $key => $values) { if (in_array($key, ['_edit_lock', '_edit_last'], true)) { continue; } $out[$key] = array_map('maybe_unserialize', (array) $values); } return $out; } function normalize_meta_input(array $payload): array { $meta = $payload['meta'] ?? []; if (!is_array($meta)) { return []; } $out = []; foreach ($meta as $key => $values) { if (!is_string($key) || $key === '') { continue; } if (!is_array($values)) { $values = [$values]; } $out[$key] = $values; } return $out; } function set_meta_payload(int $id, array $meta): void { foreach ($meta as $key => $values) { delete_post_meta($id, $key); foreach ($values as $value) { add_post_meta($id, $key, maybe_serialize($value)); } } } switch ($cmd) { case 'read': { $id = (int) ($argv[2] ?? 0); $p = get_post($id); if (!$p) { fwrite(STDERR, "post $id no existe\n"); exit(3); } out_json([ 'id' => $p->ID, 'title' => $p->post_title, 'content' => $p->post_content, 'excerpt' => $p->post_excerpt, 'lang' => function_exists('pll_get_post_language') ? pll_get_post_language($id) : '', 'status' => $p->post_status, 'author' => (int) $p->post_author, 'date' => $p->post_date, 'cats' => wp_get_post_categories($id), ]); break; } case 'read_full': { $id = (int) ($argv[2] ?? 0); $p = get_post($id); if (!$p) { fwrite(STDERR, "post $id no existe\n"); exit(3); } out_json([ 'id' => $p->ID, 'title' => $p->post_title, 'content' => $p->post_content, 'excerpt' => $p->post_excerpt, 'slug' => $p->post_name, 'lang' => function_exists('pll_get_post_language') ? pll_get_post_language($id) : '', 'status' => $p->post_status, 'author' => (int) $p->post_author, 'date' => $p->post_date, 'date_gmt' => $p->post_date_gmt, 'type' => $p->post_type, 'cats' => wp_get_post_categories($id), 'cat_slugs' => array_values(array_map(static fn($t) => $t->slug, get_the_terms($id, 'category') ?: [])), 'meta' => meta_payload($id), 'translations' => function_exists('pll_get_post_translations') ? pll_get_post_translations($id) : [], ]); break; } case 'exists': { $es = (int) ($argv[2] ?? 0); $lang = (string) ($argv[3] ?? ''); $t = (int) pll_get_post($es, $lang); if ($t && !get_post($t)) $t = 0; // enlace colgado a un post borrado echo $t; break; } case 'unlink': { // Borra la traducción en y la saca del grupo (para --force / limpieza). $es = (int) ($argv[2] ?? 0); $lang = (string) ($argv[3] ?? ''); $t = (int) pll_get_post($es, $lang); if ($t && get_post($t)) wp_delete_post($t, true); $tr = function_exists('pll_get_post_translations') ? pll_get_post_translations($es) : ['es' => $es]; unset($tr[$lang]); if ($tr) pll_save_post_translations($tr); echo $t; break; } case 'create': { $es = (int) ($argv[2] ?? 0); $lang = (string) ($argv[3] ?? ''); $status = (string) ($argv[4] ?? 'draft'); $src = get_post($es); if (!$src) { fwrite(STDERR, "post fuente $es no existe\n"); exit(3); } $payload = json_decode(file_get_contents('php://stdin'), true); if (!is_array($payload) || empty($payload['title'])) { fwrite(STDERR, "payload inválido por stdin\n"); exit(4); } // ¿ya existe (y vivo)? idempotencia dura. $existing = (int) pll_get_post($es, $lang); if ($existing && !get_post($existing)) $existing = 0; if ($existing) { echo $existing; break; } $new_id = wp_insert_post([ 'post_title' => wp_slash($payload['title']), 'post_content' => wp_slash($payload['content'] ?? ''), 'post_excerpt' => wp_slash($payload['excerpt'] ?? ''), 'post_name' => sanitize_title($payload['title']), 'post_status' => $status, 'post_type' => 'post', 'post_author' => (int) $src->post_author, 'post_date' => $src->post_date, 'to_ping' => '', 'pinged' => '', ], true); if (is_wp_error($new_id)) { fwrite(STDERR, $new_id->get_error_message() . "\n"); exit(5); } // Idioma primero, para que las categorías traducidas casen con el idioma del post. pll_set_post_language($new_id, $lang); // Categorías: mapea cada categoría ES a su traducción en el idioma destino // (las categorías de carta ya están traducidas: cartasemana 6→en 3077, fr 3083…). $cats = wp_get_post_categories($es); $mapped = []; foreach ($cats as $c) { $tc = function_exists('pll_get_term') ? (int) pll_get_term($c, $lang) : 0; $mapped[] = $tc ?: $c; // traducida si existe; si no, la ES (fallback) } if ($mapped) wp_set_post_categories($new_id, array_values(array_unique($mapped))); // Enlace de traducción (preservando el grupo existente). $tr = function_exists('pll_get_post_translations') ? pll_get_post_translations($es) : ['es' => $es]; if (!$tr) $tr = ['es' => $es]; $tr[$lang] = $new_id; pll_save_post_translations($tr); // Metas de trazabilidad. update_post_meta($new_id, 'traduccion_automatica', '1'); update_post_meta($new_id, 'traduccion_origen', $es); update_post_meta($new_id, 'traduccion_modelo', $payload['model'] ?? ''); update_post_meta($new_id, 'traduccion_fecha', gmdate('c')); echo $new_id; break; } case 'clone': { $target = (int) ($argv[2] ?? 0); $lang = (string) ($argv[3] ?? ''); $status = (string) ($argv[4] ?? 'draft'); if ($target <= 0 || $lang === '') { fwrite(STDERR, "uso: clone \n"); exit(6); } $payload = json_decode(file_get_contents('php://stdin'), true); if (!is_array($payload) || empty($payload['title'])) { fwrite(STDERR, "payload inválido por stdin\n"); exit(4); } $postarr = [ 'post_title' => wp_slash($payload['title']), 'post_content' => wp_slash($payload['content'] ?? ''), 'post_excerpt' => wp_slash($payload['excerpt'] ?? ''), 'post_status' => $status ?: ($payload['status'] ?? 'draft'), 'post_type' => $payload['type'] ?? 'post', 'post_author' => (int) ($payload['author'] ?? 1), 'post_date' => $payload['date'] ?? current_time('mysql'), 'post_date_gmt'=> $payload['date_gmt'] ?? current_time('mysql', true), 'post_name' => $payload['slug'] ?? '', 'to_ping' => '', 'pinged' => '', ]; $existing = get_post($target); if ($existing) { $postarr['ID'] = $target; $new_id = wp_update_post($postarr, true); } else { $postarr['import_id'] = $target; $new_id = wp_insert_post($postarr, true); } if (is_wp_error($new_id)) { fwrite(STDERR, $new_id->get_error_message() . "\n"); exit(5); } if ((int) $new_id !== $target) { fwrite(STDERR, "ID preservado falló: esperado $target, creado $new_id\n"); exit(7); } pll_set_post_language($new_id, $lang); $cats = []; foreach ((array) ($payload['cat_slugs'] ?? []) as $slug) { $term = get_term_by('slug', (string) $slug, 'category'); if ($term && !is_wp_error($term)) { $cats[] = (int) $term->term_id; } } if (!$cats) { $cats = array_values(array_unique(array_map('intval', (array) ($payload['cats'] ?? [])))); } wp_set_post_categories($new_id, $cats); set_meta_payload($new_id, normalize_meta_input($payload)); clean_post_cache($new_id); echo $new_id; break; } case 'save_translations': { $payload = json_decode(file_get_contents('php://stdin'), true); if (!is_array($payload) || empty($payload['translations']) || !is_array($payload['translations'])) { fwrite(STDERR, "payload inválido por stdin\n"); exit(4); } $tr = []; foreach ($payload['translations'] as $lang => $id) { $id = (int) $id; if (!is_string($lang) || $lang === '' || $id <= 0 || !get_post($id)) { continue; } $tr[$lang] = $id; } if (count($tr) < 2) { fwrite(STDERR, "grupo insuficiente\n"); exit(8); } pll_save_post_translations($tr); out_json($tr); break; } default: fwrite(STDERR, "subcomando desconocido: '$cmd'\n"); exit(1); }