if (!defined('ABSPATH')) exit; if (!defined('TN_ADMIN_PANEL_BUDGET')) define('TN_ADMIN_PANEL_BUDGET', 0.50); if (!function_exists('tn_admin_panel_can')) { function tn_admin_panel_can() { return current_user_can('manage_options'); } } if (!function_exists('tn_admin_panel_backup')) { function tn_admin_panel_backup($post) { update_post_meta($post->ID, '_tn_admin_prev', base64_encode(wp_json_encode(array( 'content' => $post->post_content, 'excerpt' => $post->post_excerpt, 'status' => $post->post_status, 'time' => current_time('mysql'), )))); $force = function () { return 5; }; add_filter('wp_revisions_to_keep', $force, 99); wp_save_post_revision($post->ID); remove_filter('wp_revisions_to_keep', $force, 99); } } if (!function_exists('tn_admin_source_name')) { function tn_admin_source_name($pid) { $map = array( 'lapatilla.com' => 'Lapatilla', 'ambito.com' => 'Ámbito', 'albertonews.com' => 'AlbertoNews', 'dw.com' => 'DW', 'diarioavance.com' => 'Diario Avance', 'elnacional.com' => 'El Nacional', 'elpais.com' => 'El País', 'vidaextra.com' => 'VidaExtra', 'bbc.com' => 'BBC', 'infobae.com' => 'Infobae', 'primicia.com.ve' => 'Primicia', 'esconusted.com' => 'Es Con Usted', 'okdiario.com' => 'OkDiario', 'elpitazo.net' => 'El Pitazo', 'runrun.es' => 'Runrun.es', 'efe.com' => 'EFE', 'clarin.com' => 'Clarín', 'cnn.com' => 'CNN', 'elmundo.es' => 'El Mundo', 'rt.com' => 'RT', 'eltiempo.com' => 'El Tiempo', 'noticiasaldiayalahora.co' => 'Noticias Al Día y a la Hora', ); $link = get_post_meta($pid, 'original_link', true); if (!$link) return ''; $host = preg_replace('/^www\./', '', strtolower((string) parse_url($link, PHP_URL_HOST))); if ($host === '') return ''; foreach ($map as $d => $n) { if ($host === $d || substr($host, -strlen('.' . $d)) === '.' . $d) return $n; } $p = explode('.', $host); $i = max(0, count($p) - 2); $label = $p[$i]; if (in_array($label, array('com','co','net','org','gob','edu'), true) && count($p) >= 3) $label = $p[count($p) - 3]; return ucwords(str_replace('-', ' ', $label)); } } if (!function_exists('tn_admin_img_identity')) { function tn_admin_img_identity(DOMElement $img) { $cand = ''; foreach (array('data-src','data-lazy-src','data-lazyload','data-original','src') as $a) { $v = trim($img->getAttribute($a)); if ($v !== '' && stripos($v, 'data:image') !== 0) { $cand = $v; break; } } if ($cand === '') { $ss = trim($img->getAttribute('srcset')); if ($ss !== '') { $first = explode(',', $ss); $cand = trim(explode(' ', trim($first[0]))[0]); } } if ($cand === '') return ''; $u = html_entity_decode($cand, ENT_QUOTES); $u = preg_replace('/[?#].*$/', '', $u); $u = preg_replace('#^[a-z]+://#i', '', $u); $u = preg_replace('#^//#', '', $u); $u = preg_replace('/^www\./i', '', $u); return strtolower(rawurldecode($u)); } } if (!function_exists('tn_admin_dedupe_images')) { function tn_admin_dedupe_images(DOMDocument $doc, DOMXPath $xp) { $root = $doc->getElementById('__tnroot'); if (!$root) return 0; $tokens = array(); $walk = function ($node) use (&$walk, &$tokens) { foreach ($node->childNodes as $c) { if ($c->nodeType === XML_TEXT_NODE) { if (trim(preg_replace('/[\s\x{00a0}]+/u', ' ', $c->nodeValue)) !== '') $tokens[] = array('t' => 'text'); continue; } if (!($c instanceof DOMElement)) continue; $tag = strtolower($c->nodeName); if ($tag === 'img') { $tokens[] = array('t' => 'img', 'node' => $c); continue; } if (in_array($tag, array('iframe','video','audio','blockquote','script','embed','object','svg'), true)) { $tokens[] = array('t' => 'media'); continue; } $walk($c); } }; $walk($root); $lastId = null; $broken = true; $uniq = 0; $toRemove = array(); foreach ($tokens as $tk) { if ($tk['t'] !== 'img') { $broken = true; continue; } $idn = tn_admin_img_identity($tk['node']); if ($idn === '') $idn = "\0u" . (++$uniq); if ($lastId !== null && !$broken && $idn === $lastId) { $toRemove[] = $tk['node']; } else { $lastId = $idn; $broken = false; } } foreach ($toRemove as $imgn) { $p = $imgn->parentNode; if ($p) $p->removeChild($imgn); while ($p instanceof DOMElement && $p->getAttribute('id') !== '__tnroot' && $p->parentNode) { $hasMedia = false; foreach (array('img','iframe','video','audio','picture','blockquote','script','embed','object','svg') as $t) { if ($p->getElementsByTagName($t)->length > 0) { $hasMedia = true; break; } } $txt = trim(preg_replace('/[\s\x{00a0}]+/u', '', $p->textContent)); if (!$hasMedia && $txt === '') { $up = $p->parentNode; $up->removeChild($p); $p = $up; } else break; } } return count($toRemove); } } if (!function_exists('tn_admin_curar_safe')) { function tn_admin_curar_safe($pid, $html) { $rep = array('source' => '', 'footer' => 'ya_existe', 'credits' => array(), 'junk' => array(), 'dupes' => 0); if (trim($html) === '') return array($html, $rep); $src = tn_admin_source_name((int) $pid); $rep['source'] = $src; $has_footer = stripos($html, 'Fuente de TenemosNoticias.com:') !== false; $prev = libxml_use_internal_errors(true); libxml_clear_errors(); $doc = new DOMDocument(); $doc->loadHTML('
' . $html . '
', LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD | LIBXML_NONET); libxml_clear_errors(); libxml_use_internal_errors($prev); $xp = new DOMXPath($doc); $rep['dupes'] = tn_admin_dedupe_images($doc, $xp); $prot = function ($n) { if (!($n instanceof DOMElement)) return false; foreach (array('img','iframe','blockquote','script','video','audio','source','picture') as $t) { if ($n->getElementsByTagName($t)->length > 0) return true; } if (preg_match('/lazyload|wp-block-embed|twitter|instagram|tiktok|fb-|embed/i', $n->getAttribute('class') . ' ' . $n->getAttribute('id'))) return true; foreach ($n->attributes as $a) { if (stripos($a->name, 'data-src') !== false) return true; } return false; }; $junk = array( '/^Publicidad( \/ Contenido Patrocinado)?$/iu', '/^Contenido Patrocinado( \/ Recomendado para ti)?$/iu', '/^Recomendado para ti$/iu', '/Para leer la nota completa/iu', '/Sigue en directo/iu', '/Venezuela atraviesa un plan de TRES FASES/iu', ); foreach (iterator_to_array($xp->query('//p|//h2|//h3|//h4')) as $n) { if (!($n instanceof DOMElement) || !$n->parentNode) continue; if ($prot($n)) continue; $t = trim(preg_replace('/\s+/u', ' ', $n->textContent)); if ($t === '') continue; $isj = false; foreach ($junk as $p) { if (preg_match($p, $t)) { $isj = true; break; } } if ($isj) { $rep['junk'][] = mb_substr($t, 0, 45); $n->parentNode->removeChild($n); continue; } if ($n->getElementsByTagName('a')->length > 0) continue; $iscred = false; if ($src !== '' && mb_strtolower($t) === mb_strtolower($src)) $iscred = true; if (!$iscred && preg_match('/^Por:?\s+[a-z0-9.\-]+\.[a-z]{2,}$/iu', $t)) $iscred = true; if ($iscred) { $rep['credits'][] = mb_substr($t, 0, 45); $n->parentNode->removeChild($n); } } foreach (iterator_to_array($xp->query('//*[@id="mc_embed_signup"]')) as $n) { if ($n instanceof DOMElement && !$prot($n) && $n->parentNode) { $rep['junk'][] = '#mc_embed_signup'; $n->parentNode->removeChild($n); } } foreach (iterator_to_array($xp->query('//p')) as $n) { if (!($n instanceof DOMElement) || !$n->parentNode) continue; if ($prot($n)) continue; if ($n->getElementsByTagName('a')->length > 0) continue; $t = trim(preg_replace('/\x{00a0}|\s+/u', '', $n->textContent)); if ($t === '') $n->parentNode->removeChild($n); } foreach (iterator_to_array($xp->query('//*[contains(@style,"padding-bottom") or contains(@style,"padding-top") or contains(@style,"aspect-ratio")]')) as $n) { if (!($n instanceof DOMElement) || !$n->parentNode) continue; $hm = $n->getElementsByTagName('img')->length || $n->getElementsByTagName('iframe')->length || $n->getElementsByTagName('video')->length || $n->getElementsByTagName('picture')->length; if ($hm) { $st = preg_replace('/(?:^|;)\s*(?:padding-bottom|padding-top|aspect-ratio)\s*:[^;]*/i', '', (string) $n->getAttribute('style')); $st = trim($st, " ;"); if ($st === '') { $n->removeAttribute('style'); } else { $n->setAttribute('style', $st); } } elseif (!$prot($n)) { $rep['junk'][] = 'aspect_vacio'; $n->parentNode->removeChild($n); } } for ($pass = 0; $pass < 2; $pass++) { foreach (iterator_to_array($xp->query('//div|//span|//section|//figure|//aside|//ul|//ol')) as $n) { if (!($n instanceof DOMElement) || !$n->parentNode) continue; if ($n->getAttribute('id') === '__tnroot') continue; if ($prot($n)) continue; if ($n->getElementsByTagName('a')->length > 0) continue; $t2 = trim(preg_replace('/\x{00a0}|\s+/u', '', $n->textContent)); if ($t2 === '') $n->parentNode->removeChild($n); } } $root = $doc->getElementById('__tnroot'); $clean = ''; if ($root) { foreach ($root->childNodes as $c) { $clean .= $doc->saveHTML($c); } } $clean = trim($clean); $clean = preg_replace('/^(?:\s*\s*)+/i', '', $clean); $clean = preg_replace('/(?:\s*\s*)+$/i', '', $clean); $clean = preg_replace('/(?:\s*){2,}/i', '
', $clean); if (!$has_footer && $src !== '') { $clean .= "\n

Fuente de TenemosNoticias.com: " . esc_html($src) . "

"; $rep['footer'] = 'anadido'; } return array($clean, $rep); } } add_action('wp_footer', function () { if (!is_singular('post') || !tn_admin_panel_can()) return; $pid = (int) get_queried_object_id(); $status = esc_html(get_post_status($pid)); $nonce = esc_attr(wp_create_nonce('tn_admin_panel_' . $pid)); $ajax = esc_url(admin_url('admin-ajax.php')); $html = << #tn-adm-panel{display:none;margin:14px 0;padding:12px;border:1px solid #e9ecef;border-radius:10px;background:#f8f9fa;box-shadow:0 3px 10px rgba(0,0,0,.06);font-family:inherit} #tn-adm-panel .tn-adm-h{font-size:12px;font-weight:700;color:#868e96;text-transform:uppercase;letter-spacing:.04em;margin-bottom:8px} #tn-adm-panel .tn-adm-row{display:flex;flex-wrap:wrap;gap:8px} #tn-adm-panel button{flex:1 1 auto;min-width:120px;height:40px;border:none;border-radius:6px;color:#fff;font-size:14px;font-weight:600;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:6px} #tn-adm-panel button:disabled{opacity:.5;cursor:default} #tn-adm-b-draft{background:#868e96}#tn-adm-b-pending{background:#f08c00}#tn-adm-b-enrich{background:#1c7ed6}#tn-adm-b-clean{background:#0ca678} #tn-adm-panel .tn-adm-st{font-weight:700;color:#212529} #tn-adm-toast-wrap{position:fixed;z-index:2147483000;right:18px;bottom:18px;display:flex;flex-direction:column;gap:10px;max-width:340px;width:calc(100vw - 36px);font-family:inherit;pointer-events:none} .tn-adm-toast{pointer-events:auto;display:flex;align-items:flex-start;gap:10px;padding:12px 13px;border-radius:10px;background:#fff;color:#212529;font-size:13.5px;line-height:1.45;box-shadow:0 10px 28px rgba(0,0,0,.16);border-left:4px solid #adb5bd;transform:translateX(120%);opacity:0;transition:transform .34s cubic-bezier(.22,1,.36,1),opacity .34s} .tn-adm-toast.tn-show{transform:translateX(0);opacity:1} .tn-adm-toast.tn-ok{border-left-color:#0ca678}.tn-adm-toast.tn-err{border-left-color:#e03131}.tn-adm-toast.tn-info{border-left-color:#1c7ed6} .tn-adm-toast .tn-ic{flex:0 0 auto;width:20px;text-align:center;font-size:15px;margin-top:1px} .tn-adm-toast.tn-ok .tn-ic{color:#0ca678}.tn-adm-toast.tn-err .tn-ic{color:#e03131}.tn-adm-toast.tn-info .tn-ic{color:#1c7ed6} .tn-adm-toast .tn-bd{flex:1 1 auto;word-break:break-word} .tn-adm-toast .tn-cl{flex:0 0 auto;cursor:pointer;color:#adb5bd;font-size:18px;line-height:1;background:none;border:none;padding:0;margin:-1px -2px 0 0;width:auto;min-width:0;height:auto} .tn-adm-toast .tn-cl:hover{color:#495057} @media (max-width:600px){#tn-adm-toast-wrap{right:10px;left:10px;bottom:10px;width:auto;max-width:none}} .tn-adm-ov{position:fixed;inset:0;z-index:2147483600;display:flex;align-items:center;justify-content:center;padding:18px;background:rgba(20,22,28,.55);opacity:0;transition:opacity .2s;font-family:inherit} .tn-adm-ov.tn-show{opacity:1} .tn-adm-ov .tn-adm-modal{width:100%;max-width:380px;background:#fff;border-radius:14px;box-shadow:0 18px 50px rgba(0,0,0,.3);padding:20px;transform:translateY(14px) scale(.97);transition:transform .22s cubic-bezier(.22,1,.36,1)} .tn-adm-ov.tn-show .tn-adm-modal{transform:translateY(0) scale(1)} .tn-adm-ov .tn-adm-mh{display:flex;align-items:center;gap:11px;margin-bottom:12px} .tn-adm-ov .tn-adm-mi{flex:0 0 auto;width:40px;height:40px;border-radius:50%;display:flex;align-items:center;justify-content:center;color:#fff;font-size:17px} .tn-adm-ov .tn-adm-mt{font-size:17px;font-weight:700;color:#212529;line-height:1.2} .tn-adm-ov .tn-adm-mb{font-size:14px;line-height:1.5;color:#495057;margin-bottom:20px} .tn-adm-ov .tn-adm-mf{display:flex;gap:10px;justify-content:flex-end} .tn-adm-ov .tn-adm-mf button{border:none;border-radius:8px;height:42px;padding:0 18px;font-size:14px;font-weight:600;cursor:pointer;font-family:inherit;display:flex;align-items:center} .tn-adm-ov .tn-adm-no{background:#f1f3f5;color:#495057} .tn-adm-ov .tn-adm-no:hover{background:#e9ecef} .tn-adm-ov .tn-adm-yes{color:#fff} .tn-adm-ov .tn-adm-yes:hover{filter:brightness(.94)} @media (max-width:600px){.tn-adm-ov{align-items:flex-end;padding:0}.tn-adm-ov .tn-adm-modal{max-width:none;border-radius:16px 16px 0 0;padding:22px 18px calc(22px + env(safe-area-inset-bottom));transform:translateY(100%)}.tn-adm-ov.tn-show .tn-adm-modal{transform:translateY(0)}}
Admin — estado: {$status}
HTML; echo $html; }, 50); add_action('wp_ajax_tn_admin_article_action', function () { $pid = isset($_POST['pid']) ? (int) $_POST['pid'] : 0; if (!$pid || !tn_admin_panel_can() || !check_ajax_referer('tn_admin_panel_' . $pid, 'nonce', false)) { wp_send_json_error(array('msg' => 'No autorizado'), 403); } $op = isset($_POST['op']) ? sanitize_key($_POST['op']) : ''; $post = get_post($pid); if (!$post || $post->post_type !== 'post') wp_send_json_error(array('msg' => 'Articulo invalido'), 400); if (function_exists('set_time_limit')) @set_time_limit(180); if ($op === 'draft' || $op === 'pending') { $new_status = ($op === 'draft') ? 'draft' : 'pending'; $r = wp_update_post(array('ID' => $pid, 'post_status' => $new_status), true); if (is_wp_error($r)) wp_send_json_error(array('msg' => $r->get_error_message())); wp_send_json_success(array('msg' => 'Estado cambiado a ' . $new_status, 'reload' => true)); } if ($op === 'clean') { if (!function_exists('tn_admin_curar_safe')) wp_send_json_error(array('msg' => 'Limpiador no disponible')); $in = $post->post_content; list($clean, $rep) = tn_admin_curar_safe($pid, $in); if ($clean === $in) wp_send_json_success(array('msg' => 'Nada que limpiar (sin relleno ni firmas redundantes).', 'reload' => false)); foreach (array('twitter-tweet','instagram-media','tiktok-embed','fb-post',' 'Abortado por seguridad: la limpieza habria afectado un embed/imagen (' . $m . '). No se guardo nada.')); } } tn_admin_panel_backup($post); $r = wp_update_post(array('ID' => $pid, 'post_content' => $clean), true); if (is_wp_error($r)) wp_send_json_error(array('msg' => $r->get_error_message())); $parts = array(); if (!empty($rep['credits'])) $parts[] = count($rep['credits']) . ' firma(s) de fuente'; if (!empty($rep['junk'])) $parts[] = count($rep['junk']) . ' bloque(s) de relleno'; if (!empty($rep['dupes'])) $parts[] = $rep['dupes'] . ' imagen(es) duplicada(s)'; if ($rep['footer'] === 'anadido') $parts[] = 'pie de fuente anadido (' . $rep['source'] . ')'; $msg = 'Limpiado: ' . (empty($parts) ? 'ajustes menores' : implode(', ', $parts)) . '. Creditos, imagenes distintas y embeds conservados. Respaldo guardado.'; wp_send_json_success(array('msg' => $msg, 'reload' => true)); } if ($op === 'enrich') { if (!class_exists('TN_AI_Compiler\\Enrichment\\Runner')) wp_send_json_error(array('msg' => 'AI Compiler no disponible')); tn_admin_panel_backup($post); $cleanup = function () use ($pid) { global $wpdb; $wpdb->query($wpdb->prepare("DELETE FROM {$wpdb->postmeta} WHERE post_id = %d AND (meta_key LIKE %s OR meta_key LIKE %s)", $pid, $wpdb->esc_like('_tn_enrich_') . '%', $wpdb->esc_like('_tn_autopublish_') . '%')); clean_post_cache($pid); }; try { $budget = new \TN_AI_Compiler\Enrichment\Budget((float) TN_ADMIN_PANEL_BUDGET); $runner = new \TN_AI_Compiler\Enrichment\Runner(array('budget' => $budget)); $run_id = 'manual-' . $pid . '-' . time(); $r = $runner->process_one($pid, 1, $run_id, false); } catch (\Throwable $e) { $cleanup(); wp_send_json_error(array('msg' => 'Error al enriquecer: ' . $e->getMessage())); } if ((isset($r['status']) ? $r['status'] : '') === 'review' && !empty($r['draft_id'])) { $draft = get_post((int) $r['draft_id']); if ($draft instanceof \WP_Post) { wp_update_post(array('ID' => $pid, 'post_content' => $draft->post_content, 'post_excerpt' => $draft->post_excerpt), true); wp_delete_post((int) $r['draft_id'], true); } $cleanup(); wp_send_json_success(array('msg' => 'Enriquecido: modelo ' . (isset($r['model']) ? $r['model'] : '?') . ', ratio ' . round((float) (isset($r['ratio']) ? $r['ratio'] : 0), 2) . ', $' . round((float) (isset($r['cost_usd']) ? $r['cost_usd'] : 0), 3) . '. Respaldo guardado.', 'reload' => true)); } $cleanup(); wp_send_json_error(array('msg' => 'No se completo: ' . (isset($r['reason']) ? $r['reason'] : (isset($r['status']) ? $r['status'] : 'desconocido')))); } wp_send_json_error(array('msg' => 'Operacion desconocida')); }); if (!defined('ABSPATH')) exit; if (!defined('TN_ADMIN_PANEL_BUDGET')) define('TN_ADMIN_PANEL_BUDGET', 0.50); if (!function_exists('tn_admin_panel_can')) { function tn_admin_panel_can() { return current_user_can('manage_options'); } } if (!function_exists('tn_admin_panel_backup')) { function tn_admin_panel_backup($post) { update_post_meta($post->ID, '_tn_admin_prev', base64_encode(wp_json_encode(array( 'content' => $post->post_content, 'excerpt' => $post->post_excerpt, 'status' => $post->post_status, 'time' => current_time('mysql'), )))); $force = function () { return 5; }; add_filter('wp_revisions_to_keep', $force, 99); wp_save_post_revision($post->ID); remove_filter('wp_revisions_to_keep', $force, 99); } } if (!function_exists('tn_admin_source_name')) { function tn_admin_source_name($pid) { $map = array( 'lapatilla.com' => 'Lapatilla', 'ambito.com' => 'Ámbito', 'albertonews.com' => 'AlbertoNews', 'dw.com' => 'DW', 'diarioavance.com' => 'Diario Avance', 'elnacional.com' => 'El Nacional', 'elpais.com' => 'El País', 'vidaextra.com' => 'VidaExtra', 'bbc.com' => 'BBC', 'infobae.com' => 'Infobae', 'primicia.com.ve' => 'Primicia', 'esconusted.com' => 'Es Con Usted', 'okdiario.com' => 'OkDiario', 'elpitazo.net' => 'El Pitazo', 'runrun.es' => 'Runrun.es', 'efe.com' => 'EFE', 'clarin.com' => 'Clarín', 'cnn.com' => 'CNN', 'elmundo.es' => 'El Mundo', 'rt.com' => 'RT', 'eltiempo.com' => 'El Tiempo', 'noticiasaldiayalahora.co' => 'Noticias Al Día y a la Hora', ); $link = get_post_meta($pid, 'original_link', true); if (!$link) return ''; $host = preg_replace('/^www\./', '', strtolower((string) parse_url($link, PHP_URL_HOST))); if ($host === '') return ''; foreach ($map as $d => $n) { if ($host === $d || substr($host, -strlen('.' . $d)) === '.' . $d) return $n; } $p = explode('.', $host); $i = max(0, count($p) - 2); $label = $p[$i]; if (in_array($label, array('com','co','net','org','gob','edu'), true) && count($p) >= 3) $label = $p[count($p) - 3]; return ucwords(str_replace('-', ' ', $label)); } } if (!function_exists('tn_admin_img_identity')) { function tn_admin_img_identity(DOMElement $img) { $cand = ''; foreach (array('data-src','data-lazy-src','data-lazyload','data-original','src') as $a) { $v = trim($img->getAttribute($a)); if ($v !== '' && stripos($v, 'data:image') !== 0) { $cand = $v; break; } } if ($cand === '') { $ss = trim($img->getAttribute('srcset')); if ($ss !== '') { $first = explode(',', $ss); $cand = trim(explode(' ', trim($first[0]))[0]); } } if ($cand === '') return ''; $u = html_entity_decode($cand, ENT_QUOTES); $u = preg_replace('/[?#].*$/', '', $u); $u = preg_replace('#^[a-z]+://#i', '', $u); $u = preg_replace('#^//#', '', $u); $u = preg_replace('/^www\./i', '', $u); return strtolower(rawurldecode($u)); } } if (!function_exists('tn_admin_dedupe_images')) { function tn_admin_dedupe_images(DOMDocument $doc, DOMXPath $xp) { $root = $doc->getElementById('__tnroot'); if (!$root) return 0; $tokens = array(); $walk = function ($node) use (&$walk, &$tokens) { foreach ($node->childNodes as $c) { if ($c->nodeType === XML_TEXT_NODE) { if (trim(preg_replace('/[\s\x{00a0}]+/u', ' ', $c->nodeValue)) !== '') $tokens[] = array('t' => 'text'); continue; } if (!($c instanceof DOMElement)) continue; $tag = strtolower($c->nodeName); if ($tag === 'img') { $tokens[] = array('t' => 'img', 'node' => $c); continue; } if (in_array($tag, array('iframe','video','audio','blockquote','script','embed','object','svg'), true)) { $tokens[] = array('t' => 'media'); continue; } $walk($c); } }; $walk($root); $lastId = null; $broken = true; $uniq = 0; $toRemove = array(); foreach ($tokens as $tk) { if ($tk['t'] !== 'img') { $broken = true; continue; } $idn = tn_admin_img_identity($tk['node']); if ($idn === '') $idn = "\0u" . (++$uniq); if ($lastId !== null && !$broken && $idn === $lastId) { $toRemove[] = $tk['node']; } else { $lastId = $idn; $broken = false; } } foreach ($toRemove as $imgn) { $p = $imgn->parentNode; if ($p) $p->removeChild($imgn); while ($p instanceof DOMElement && $p->getAttribute('id') !== '__tnroot' && $p->parentNode) { $hasMedia = false; foreach (array('img','iframe','video','audio','picture','blockquote','script','embed','object','svg') as $t) { if ($p->getElementsByTagName($t)->length > 0) { $hasMedia = true; break; } } $txt = trim(preg_replace('/[\s\x{00a0}]+/u', '', $p->textContent)); if (!$hasMedia && $txt === '') { $up = $p->parentNode; $up->removeChild($p); $p = $up; } else break; } } return count($toRemove); } } if (!function_exists('tn_admin_curar_safe')) { function tn_admin_curar_safe($pid, $html) { $rep = array('source' => '', 'footer' => 'ya_existe', 'credits' => array(), 'junk' => array(), 'dupes' => 0); if (trim($html) === '') return array($html, $rep); $src = tn_admin_source_name((int) $pid); $rep['source'] = $src; $has_footer = stripos($html, 'Fuente de TenemosNoticias.com:') !== false; $prev = libxml_use_internal_errors(true); libxml_clear_errors(); $doc = new DOMDocument(); $doc->loadHTML('
' . $html . '
', LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD | LIBXML_NONET); libxml_clear_errors(); libxml_use_internal_errors($prev); $xp = new DOMXPath($doc); $rep['dupes'] = tn_admin_dedupe_images($doc, $xp); $prot = function ($n) { if (!($n instanceof DOMElement)) return false; foreach (array('img','iframe','blockquote','script','video','audio','source','picture') as $t) { if ($n->getElementsByTagName($t)->length > 0) return true; } if (preg_match('/lazyload|wp-block-embed|twitter|instagram|tiktok|fb-|embed/i', $n->getAttribute('class') . ' ' . $n->getAttribute('id'))) return true; foreach ($n->attributes as $a) { if (stripos($a->name, 'data-src') !== false) return true; } return false; }; $junk = array( '/^Publicidad( \/ Contenido Patrocinado)?$/iu', '/^Contenido Patrocinado( \/ Recomendado para ti)?$/iu', '/^Recomendado para ti$/iu', '/Para leer la nota completa/iu', '/Sigue en directo/iu', '/Venezuela atraviesa un plan de TRES FASES/iu', ); foreach (iterator_to_array($xp->query('//p|//h2|//h3|//h4')) as $n) { if (!($n instanceof DOMElement) || !$n->parentNode) continue; if ($prot($n)) continue; $t = trim(preg_replace('/\s+/u', ' ', $n->textContent)); if ($t === '') continue; $isj = false; foreach ($junk as $p) { if (preg_match($p, $t)) { $isj = true; break; } } if ($isj) { $rep['junk'][] = mb_substr($t, 0, 45); $n->parentNode->removeChild($n); continue; } if ($n->getElementsByTagName('a')->length > 0) continue; $iscred = false; if ($src !== '' && mb_strtolower($t) === mb_strtolower($src)) $iscred = true; if (!$iscred && preg_match('/^Por:?\s+[a-z0-9.\-]+\.[a-z]{2,}$/iu', $t)) $iscred = true; if ($iscred) { $rep['credits'][] = mb_substr($t, 0, 45); $n->parentNode->removeChild($n); } } foreach (iterator_to_array($xp->query('//*[@id="mc_embed_signup"]')) as $n) { if ($n instanceof DOMElement && !$prot($n) && $n->parentNode) { $rep['junk'][] = '#mc_embed_signup'; $n->parentNode->removeChild($n); } } foreach (iterator_to_array($xp->query('//p')) as $n) { if (!($n instanceof DOMElement) || !$n->parentNode) continue; if ($prot($n)) continue; if ($n->getElementsByTagName('a')->length > 0) continue; $t = trim(preg_replace('/\x{00a0}|\s+/u', '', $n->textContent)); if ($t === '') $n->parentNode->removeChild($n); } foreach (iterator_to_array($xp->query('//*[contains(@style,"padding-bottom") or contains(@style,"padding-top") or contains(@style,"aspect-ratio")]')) as $n) { if (!($n instanceof DOMElement) || !$n->parentNode) continue; $hm = $n->getElementsByTagName('img')->length || $n->getElementsByTagName('iframe')->length || $n->getElementsByTagName('video')->length || $n->getElementsByTagName('picture')->length; if ($hm) { $st = preg_replace('/(?:^|;)\s*(?:padding-bottom|padding-top|aspect-ratio)\s*:[^;]*/i', '', (string) $n->getAttribute('style')); $st = trim($st, " ;"); if ($st === '') { $n->removeAttribute('style'); } else { $n->setAttribute('style', $st); } } elseif (!$prot($n)) { $rep['junk'][] = 'aspect_vacio'; $n->parentNode->removeChild($n); } } for ($pass = 0; $pass < 2; $pass++) { foreach (iterator_to_array($xp->query('//div|//span|//section|//figure|//aside|//ul|//ol')) as $n) { if (!($n instanceof DOMElement) || !$n->parentNode) continue; if ($n->getAttribute('id') === '__tnroot') continue; if ($prot($n)) continue; if ($n->getElementsByTagName('a')->length > 0) continue; $t2 = trim(preg_replace('/\x{00a0}|\s+/u', '', $n->textContent)); if ($t2 === '') $n->parentNode->removeChild($n); } } $root = $doc->getElementById('__tnroot'); $clean = ''; if ($root) { foreach ($root->childNodes as $c) { $clean .= $doc->saveHTML($c); } } $clean = trim($clean); $clean = preg_replace('/^(?:\s*\s*)+/i', '', $clean); $clean = preg_replace('/(?:\s*\s*)+$/i', '', $clean); $clean = preg_replace('/(?:\s*){2,}/i', '
', $clean); if (!$has_footer && $src !== '') { $clean .= "\n

Fuente de TenemosNoticias.com: " . esc_html($src) . "

"; $rep['footer'] = 'anadido'; } return array($clean, $rep); } } add_action('wp_footer', function () { if (!is_singular('post') || !tn_admin_panel_can()) return; $pid = (int) get_queried_object_id(); $status = esc_html(get_post_status($pid)); $nonce = esc_attr(wp_create_nonce('tn_admin_panel_' . $pid)); $ajax = esc_url(admin_url('admin-ajax.php')); $html = << #tn-adm-panel{display:none;margin:14px 0;padding:12px;border:1px solid #e9ecef;border-radius:10px;background:#f8f9fa;box-shadow:0 3px 10px rgba(0,0,0,.06);font-family:inherit} #tn-adm-panel .tn-adm-h{font-size:12px;font-weight:700;color:#868e96;text-transform:uppercase;letter-spacing:.04em;margin-bottom:8px} #tn-adm-panel .tn-adm-row{display:flex;flex-wrap:wrap;gap:8px} #tn-adm-panel button{flex:1 1 auto;min-width:120px;height:40px;border:none;border-radius:6px;color:#fff;font-size:14px;font-weight:600;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:6px} #tn-adm-panel button:disabled{opacity:.5;cursor:default} #tn-adm-b-draft{background:#868e96}#tn-adm-b-pending{background:#f08c00}#tn-adm-b-enrich{background:#1c7ed6}#tn-adm-b-clean{background:#0ca678}#tn-adm-b-headline{background:#9c36b5} #tn-adm-panel .tn-adm-st{font-weight:700;color:#212529} #tn-adm-toast-wrap{position:fixed;z-index:2147483000;right:18px;bottom:18px;display:flex;flex-direction:column;gap:10px;max-width:340px;width:calc(100vw - 36px);font-family:inherit;pointer-events:none} .tn-adm-toast{pointer-events:auto;display:flex;align-items:flex-start;gap:10px;padding:12px 13px;border-radius:10px;background:#fff;color:#212529;font-size:13.5px;line-height:1.45;box-shadow:0 10px 28px rgba(0,0,0,.16);border-left:4px solid #adb5bd;transform:translateX(120%);opacity:0;transition:transform .34s cubic-bezier(.22,1,.36,1),opacity .34s} .tn-adm-toast.tn-show{transform:translateX(0);opacity:1} .tn-adm-toast.tn-ok{border-left-color:#0ca678}.tn-adm-toast.tn-err{border-left-color:#e03131}.tn-adm-toast.tn-info{border-left-color:#1c7ed6} .tn-adm-toast .tn-ic{flex:0 0 auto;width:20px;text-align:center;font-size:15px;margin-top:1px} .tn-adm-toast.tn-ok .tn-ic{color:#0ca678}.tn-adm-toast.tn-err .tn-ic{color:#e03131}.tn-adm-toast.tn-info .tn-ic{color:#1c7ed6} .tn-adm-toast .tn-bd{flex:1 1 auto;word-break:break-word} .tn-adm-toast .tn-cl{flex:0 0 auto;cursor:pointer;color:#adb5bd;font-size:18px;line-height:1;background:none;border:none;padding:0;margin:-1px -2px 0 0;width:auto;min-width:0;height:auto} .tn-adm-toast .tn-cl:hover{color:#495057} @media (max-width:600px){#tn-adm-toast-wrap{right:10px;left:10px;bottom:10px;width:auto;max-width:none}} .tn-adm-ov{position:fixed;inset:0;z-index:2147483600;display:flex;align-items:center;justify-content:center;padding:18px;background:rgba(20,22,28,.55);opacity:0;transition:opacity .2s;font-family:inherit} .tn-adm-ov.tn-show{opacity:1} .tn-adm-ov .tn-adm-modal{width:100%;max-width:380px;background:#fff;border-radius:14px;box-shadow:0 18px 50px rgba(0,0,0,.3);padding:20px;transform:translateY(14px) scale(.97);transition:transform .22s cubic-bezier(.22,1,.36,1)} .tn-adm-ov.tn-show .tn-adm-modal{transform:translateY(0) scale(1)} .tn-adm-ov .tn-adm-mh{display:flex;align-items:center;gap:11px;margin-bottom:12px} .tn-adm-ov .tn-adm-mi{flex:0 0 auto;width:40px;height:40px;border-radius:50%;display:flex;align-items:center;justify-content:center;color:#fff;font-size:17px} .tn-adm-ov .tn-adm-mt{font-size:17px;font-weight:700;color:#212529;line-height:1.2} .tn-adm-ov .tn-adm-mb{font-size:14px;line-height:1.5;color:#495057;margin-bottom:20px} .tn-adm-ov .tn-adm-mf{display:flex;gap:10px;justify-content:flex-end} .tn-adm-ov .tn-adm-mf button{border:none;border-radius:8px;height:42px;padding:0 18px;font-size:14px;font-weight:600;cursor:pointer;font-family:inherit;display:flex;align-items:center} .tn-adm-ov .tn-adm-no{background:#f1f3f5;color:#495057} .tn-adm-ov .tn-adm-no:hover{background:#e9ecef} .tn-adm-ov .tn-adm-yes{color:#fff} .tn-adm-ov .tn-adm-yes:hover{filter:brightness(.94)} @media (max-width:600px){.tn-adm-ov{align-items:flex-end;padding:0}.tn-adm-ov .tn-adm-modal{max-width:none;border-radius:16px 16px 0 0;padding:22px 18px calc(22px + env(safe-area-inset-bottom));transform:translateY(100%)}.tn-adm-ov.tn-show .tn-adm-modal{transform:translateY(0)}}
Admin — estado: {$status}
HTML; echo $html; }, 50); add_action('wp_ajax_tn_admin_article_action', function () { $pid = isset($_POST['pid']) ? (int) $_POST['pid'] : 0; if (!$pid || !tn_admin_panel_can() || !check_ajax_referer('tn_admin_panel_' . $pid, 'nonce', false)) { wp_send_json_error(array('msg' => 'No autorizado'), 403); } $op = isset($_POST['op']) ? sanitize_key($_POST['op']) : ''; $post = get_post($pid); if (!$post || $post->post_type !== 'post') wp_send_json_error(array('msg' => 'Articulo invalido'), 400); if (function_exists('set_time_limit')) @set_time_limit(180); if ($op === 'draft' || $op === 'pending') { $new_status = ($op === 'draft') ? 'draft' : 'pending'; $r = wp_update_post(array('ID' => $pid, 'post_status' => $new_status), true); if (is_wp_error($r)) wp_send_json_error(array('msg' => $r->get_error_message())); wp_send_json_success(array('msg' => 'Estado cambiado a ' . $new_status, 'reload' => true)); } if ($op === 'clean') { if (!function_exists('tn_admin_curar_safe')) wp_send_json_error(array('msg' => 'Limpiador no disponible')); $in = $post->post_content; list($clean, $rep) = tn_admin_curar_safe($pid, $in); if ($clean === $in) wp_send_json_success(array('msg' => 'Nada que limpiar (sin relleno ni firmas redundantes).', 'reload' => false)); foreach (array('twitter-tweet','instagram-media','tiktok-embed','fb-post',' 'Abortado por seguridad: la limpieza habria afectado un embed/imagen (' . $m . '). No se guardo nada.')); } } tn_admin_panel_backup($post); $r = wp_update_post(array('ID' => $pid, 'post_content' => $clean), true); if (is_wp_error($r)) wp_send_json_error(array('msg' => $r->get_error_message())); $parts = array(); if (!empty($rep['credits'])) $parts[] = count($rep['credits']) . ' firma(s) de fuente'; if (!empty($rep['junk'])) $parts[] = count($rep['junk']) . ' bloque(s) de relleno'; if (!empty($rep['dupes'])) $parts[] = $rep['dupes'] . ' imagen(es) duplicada(s)'; if ($rep['footer'] === 'anadido') $parts[] = 'pie de fuente anadido (' . $rep['source'] . ')'; $msg = 'Limpiado: ' . (empty($parts) ? 'ajustes menores' : implode(', ', $parts)) . '. Creditos, imagenes distintas y embeds conservados. Respaldo guardado.'; wp_send_json_success(array('msg' => $msg, 'reload' => true)); } if ($op === 'enrich') { if (!class_exists('TN_AI_Compiler\\Enrichment\\Runner')) wp_send_json_error(array('msg' => 'AI Compiler no disponible')); tn_admin_panel_backup($post); $cleanup = function () use ($pid) { global $wpdb; $wpdb->query($wpdb->prepare("DELETE FROM {$wpdb->postmeta} WHERE post_id = %d AND (meta_key LIKE %s OR meta_key LIKE %s)", $pid, $wpdb->esc_like('_tn_enrich_') . '%', $wpdb->esc_like('_tn_autopublish_') . '%')); clean_post_cache($pid); }; try { $budget = new \TN_AI_Compiler\Enrichment\Budget((float) TN_ADMIN_PANEL_BUDGET); $runner = new \TN_AI_Compiler\Enrichment\Runner(array('budget' => $budget)); $run_id = 'manual-' . $pid . '-' . time(); $r = $runner->process_one($pid, 1, $run_id, false); } catch (\Throwable $e) { $cleanup(); wp_send_json_error(array('msg' => 'Error al enriquecer: ' . $e->getMessage())); } if ((isset($r['status']) ? $r['status'] : '') === 'review' && !empty($r['draft_id'])) { $draft = get_post((int) $r['draft_id']); if ($draft instanceof \WP_Post) { wp_update_post(array('ID' => $pid, 'post_content' => $draft->post_content, 'post_excerpt' => $draft->post_excerpt), true); wp_delete_post((int) $r['draft_id'], true); } $cleanup(); wp_send_json_success(array('msg' => 'Enriquecido: modelo ' . (isset($r['model']) ? $r['model'] : '?') . ', ratio ' . round((float) (isset($r['ratio']) ? $r['ratio'] : 0), 2) . ', $' . round((float) (isset($r['cost_usd']) ? $r['cost_usd'] : 0), 3) . '. Respaldo guardado.', 'reload' => true)); } $cleanup(); wp_send_json_error(array('msg' => 'No se completo: ' . (isset($r['reason']) ? $r['reason'] : (isset($r['status']) ? $r['status'] : 'desconocido')))); } if ($op === 'headline') { if (!class_exists('TN_AI_Compiler\Gemini_Provider')) wp_send_json_error(array('msg' => 'Proveedor de IA no disponible')); $old_title = (string) $post->post_title; $old_slug = (string) $post->post_name; if (mb_strlen($old_title) <= 70 && strlen($old_slug) <= 70) { wp_send_json_success(array('msg' => 'El titulo y el slug ya tienen una longitud adecuada. Nada que curar.', 'reload' => false)); } $body_txt = trim(preg_replace('/\s+/u', ' ', (string) wp_strip_all_tags($post->post_content))); $ctx_title = mb_substr($old_title, 0, 400); $ctx_body = mb_substr($body_txt, 0, 800); $system = 'Eres editor de titulares de un portal de noticias venezolano (espanol, es_VE). Redactas titulares claros, naturales y concisos. Devuelve EXCLUSIVAMENTE un objeto JSON valido, sin texto adicional.'; $user = "El campo titulo viene contaminado con el cuerpo del articulo. A partir del material, redacta:\n1) \"title\": un titular de noticia en espanol, natural, de 45 a 70 caracteres, sin punto final, sin el nombre del medio ni del sitio, conservando hechos y entidades clave (personas, lugares, cifras).\n2) \"slug\": version corta del titular en minusculas con guiones (kebab-case), sin tildes, maximo 60 caracteres.\n\nTITULO CRUDO:\n" . $ctx_title . "\n\nCUERPO (extracto):\n" . $ctx_body . "\n\nResponde solo: {\"title\":\"...\",\"slug\":\"...\"}"; try { $provider = new \TN_AI_Compiler\Gemini_Provider(); if (!$provider->is_configured()) wp_send_json_error(array('msg' => 'IA no configurada (falta API key)')); $resp = $provider->generate_with_options(array( 'model' => 'gemini-2.5-flash-lite', 'system_instruction' => $system, 'user_content' => $user, 'temperature' => 0.4, 'max_output_tokens' => 256, 'thinking_budget' => 0, 'response_mime_type' => 'application/json', )); } catch (\Throwable $e) { wp_send_json_error(array('msg' => 'Error de IA: ' . $e->getMessage())); } $data = \TN_AI_Compiler\Gemini_Provider::decode_json((string) (isset($resp['text']) ? $resp['text'] : '')); $new_title = isset($data['title']) ? sanitize_text_field((string) $data['title']) : ''; $new_title = rtrim(trim(preg_replace('/\s+/u', ' ', $new_title)), " \t."); if ($new_title === '' || mb_strlen($new_title) < 12 || mb_strlen($new_title) > 140) { wp_send_json_error(array('msg' => 'La IA no devolvio un titular valido. No se cambio nada.')); } $base = sanitize_title($new_title); if (strlen($base) > 75) { $base = preg_replace('/-[^-]*$/', '', substr($base, 0, 75)); } if ($base === '') $base = sanitize_title($old_slug); $new_slug = wp_unique_post_slug($base, $pid, $post->post_status, $post->post_type, $post->post_parent); tn_admin_panel_backup($post); update_post_meta($pid, '_tn_admin_prev_headline', base64_encode(wp_json_encode(array( 'title' => $old_title, 'slug' => $old_slug, 'time' => current_time('mysql'), )))); $changed_slug = ($new_slug !== $old_slug); $r = wp_update_post(array('ID' => $pid, 'post_title' => $new_title, 'post_name' => $new_slug), true); if (is_wp_error($r)) wp_send_json_error(array('msg' => $r->get_error_message())); $msg = 'Titulo actualizado: ' . esc_html($new_title); if ($changed_slug && $old_slug !== '') $msg .= '. Slug nuevo: la URL anterior redirige 301 a la nueva.'; $msg .= ' Respaldo guardado.'; wp_send_json_success(array('msg' => $msg, 'reload' => true)); } wp_send_json_error(array('msg' => 'Operacion desconocida')); });