array( 'X-API-KEY' => $this->api_key, ), 'sslverify' => false, 'timeout' => 20, ); $response = wp_remote_get($url, $args); if (is_wp_error($response)) { return $response; // directamente WP_Error } $status_code = wp_remote_retrieve_response_code($response); $body = wp_remote_retrieve_body($response); if ($status_code === 200) { $datos = json_decode($body, true); if (json_last_error() !== JSON_ERROR_NONE) { return new WP_Error('json_error', 'Error al decodificar el JSON: ' . json_last_error_msg()); } return $datos; } else { $error = json_decode($body, true); return new WP_Error('http_error', 'Error: ' . ($error['error'] ?? 'Solicitud fallida')); } } // Obtener feeds principales public function getCibelesFeedsJson() { //Algunos hosts resuelven cibeles.net a AAAA y su salida IPv6 falla. Fuerce IPv4: add_action('http_api_curl', function($ch){ curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); }); $url = 'https://cibeles.net/get_feeds.php?rdn=' . uniqid(); $result = $this->get_remote_json($url); if (is_wp_error($result)) { wp_die(esc_html($result->get_error_message())); } $feeds = $result['feeds']; usort($feeds, function($a, $b) { $a_is_biblio = strpos($a['enlace'], 'biblioteca.cibeles.net') !== false ? 0 : 1; $b_is_biblio = strpos($b['enlace'], 'biblioteca.cibeles.net') !== false ? 0 : 1; if ($a_is_biblio !== $b_is_biblio) { return $a_is_biblio - $b_is_biblio; // biblioteca primero } // Si ambos son del mismo tipo (biblioteca o no), ordenar por nombre return strcmp(strtolower($a['nombre']), strtolower($b['nombre'])); }); return $feeds; } // Obtener tags public function getCibelesTagsJson() { $url = 'https://cibeles.net/get_feeds.php?tags=true&rdn=' . uniqid(); $result = $this->get_remote_json($url); if (is_wp_error($result)) { wp_die(esc_html($result->get_error_message())); } return $result['tags']; } // Obtener subfeeds dado un feed específico public function getSubfeeds($feed_url) { $feed_url = trailingslashit($feed_url) . 'feed/?subfeeds=true'; $result = $this->get_remote_json($feed_url); if (is_wp_error($result)) { wp_die(esc_html($result->get_error_message())); } return $result; // Devuelve directamente el array JSON de subfeeds } public function get_mis_fuentes() { $feeds = get_posts([ 'post_type' => 'ai_feed', 'posts_per_page' => -1, 'fields' => 'ids' ]); $urls = array_map(function ($post_id) { return get_post_meta($post_id, '_ai_feed_url', true); }, $feeds); return $urls; } public static function get_feed_items( $feed_url, $max_items = 50 ) { // No redescubrir si ya parece feed $rss_url = self::looks_like_feed_url( $feed_url ) ? $feed_url : self::discover_feed_url( $feed_url ); include_once ABSPATH . WPINC . '/class-simplepie.php'; $feed = new SimplePie(); $feed->set_feed_url( $rss_url ); $feed->enable_cache( false ); $feed->set_timeout( 15 ); $feed->init(); if ( $feed->error() ) { return new WP_Error( 'feed_error', $feed->error() ); } return array_slice( $feed->get_items(), 0, $max_items ); } public static function looks_like_feed_url( $url ) : bool { $u = strtolower( $url ); return (bool) preg_match('#(\.rss|\.atom|/feed/?$|\?feed=(rss2?|atom))#', $u); } public static function discover_feed_url( $html_url ) { // 1) Si ya parece feed, no toques nada if ( self::looks_like_feed_url( $html_url ) ) { return $html_url; } // 2) HEAD rápido para ver Content-Type $head = wp_remote_head( $html_url, ['timeout' => 10, 'redirection' => 5] ); if ( ! is_wp_error( $head ) ) { $ct = strtolower( wp_remote_retrieve_header( $head, 'content-type' ) ); if ( strpos( $ct, 'application/rss+xml' ) !== false || strpos( $ct, 'application/atom+xml' ) !== false || strpos( $ct, 'text/xml' ) !== false || strpos( $ct, 'application/xml' ) !== false ) { return $html_url; } } // 3) GET y validación rápida por raíz XML $res = wp_remote_get( $html_url, ['timeout' => 15, 'redirection' => 5] ); $body = wp_remote_retrieve_body( $res ); if ( empty( $body ) ) { return $html_url; // fallback } $trim = ltrim($body); if ( strncasecmp($trim, ' y buscar libxml_use_internal_errors(true); $doc = new DOMDocument(); @$doc->loadHTML( $body ); libxml_clear_errors(); $xpath = new DOMXPath($doc); $nodes = $xpath->query('//head/link[@rel and @type and @href]'); $original_host = parse_url($html_url, PHP_URL_HOST); $candidate_self = null; $candidate_same_host = null; $fallback = null; foreach ( $nodes as $link ) { $rel = strtolower($link->getAttribute('rel')); $type = strtolower($link->getAttribute('type')); $href = trim($link->getAttribute('href')); if ( $rel !== 'alternate' ) continue; if ( $type !== 'application/rss+xml' && $type !== 'application/atom+xml' ) continue; if ( $href === '' ) continue; // Normaliza a absoluta si es relativa if ( strpos($href, '//') === false && strpos($href, 'http') !== 0 ) { // construir absoluta simple $base = rtrim($html_url, '/'); if ($href[0] !== '/') { $href = $base . '/' . $href; } else { $parts = parse_url($html_url); $href = $parts['scheme'].'://'.$parts['host'].$href; } } $href = esc_url_raw($href); // prioriza rel=self si existe (tipico de Atom) if ( $link->hasAttribute('rel') && strtolower($link->getAttribute('rel')) === 'self' ) { $candidate_self = $href; } // prioriza mismo host $href_host = parse_url($href, PHP_URL_HOST); if ( $href_host && $original_host && $href_host === $original_host && ! $candidate_same_host ) { $candidate_same_host = $href; } // primer válido como último recurso if ( ! $fallback ) { $fallback = $href; } } // 5) Orden de preferencia if ( $candidate_self ) return $candidate_self; if ( $candidate_same_host ) return $candidate_same_host; if ( $fallback ) return $fallback; return $html_url; } public static function set_noticia_from_item( $item ,$feed_url, $feed_id) { $noticia = new stdClass(); $noticia->feed_url = $feed_url; $noticia->feed_id = $feed_id; $noticia->link = urldecode($item->get_permalink()); $noticia->title = $item->get_title(); $noticia->description = $item->get_description(); $noticia->content = $item->get_content(); $noticia->pub_date = $item->get_date( 'U' ) ? date_i18n( get_option( 'date_format' ) . ', ' . get_option( 'time_format' ), $item->get_date( 'U' ) ) : ''; $noticia->categories = $item->get_categories(); $categorias_texto = []; if ( $noticia->categories ) { foreach ( $noticia->categories as $cat ) { $categorias_texto[] = $cat->get_term(); } } $noticia->categories = $categorias_texto; $noticia->original_url = $item->get_item_tags( '', 'fuente' ); $noticia->guid = $item->get_id(); $noticia->author = $item->get_author() ? $item->get_author()->get_name() : ''; //$noticia->comments = $item->get_item_tags( '', 'comments' )[0]['data'] ?? ''; $enclosures = $item->get_enclosures(); if ( $enclosures ) { $noticia->enclosure = $enclosures[0]->get_link(); } $thumbnail_tag = $item->get_item_tags( 'http://search.yahoo.com/mrss/', 'thumbnail' ); if ( ! empty( $thumbnail_tag[0]['attribs']['']['url'] ) ) { $noticia->thumbnail = $thumbnail_tag[0]['attribs']['']['url']; } if (! empty( $noticia->original_url[0]['child']['']['original_url'][0]['data'] )) { $noticia->original_url = esc_url_raw( $noticia->original_url[0]['child']['']['original_url'][0]['data'] ); } else { $noticia->original_url = $noticia->link; // fallback } $noticia->description = aife_Format_Helper::clean_html( $noticia->description ); $noticia->content = aife_Format_Helper::clean_html( $noticia->content ); $noticia->content = str_replace( $noticia->description, '', $noticia->content ); return $noticia; } public static function get_guids_importados($feed_id) { $importadas = get_posts([ 'post_type' => 'aife_original', 'post_status' => 'any', 'fields' => 'ids', 'posts_per_page' => 500, 'meta_query' => [ [ 'key' => '_source_feed_id', 'value' => $feed_id, ], ], ]); $guids_importados = []; foreach ( $importadas as $post_id ) { $guids_importados[] = get_post_meta( $post_id, '_guid', true ); } return $guids_importados; } }