= 300) { error_log('WordPress API HTTP error: ' . $httpCode . ' for URL: ' . $url); return []; } // Validation du Content-Type if (!str_contains($contentType, 'application/json')) { error_log('WordPress API error: Invalid content type: ' . $contentType . ' for URL: ' . $url); return []; } // Décoder la réponse JSON $data = json_decode($response, true); if (json_last_error() !== JSON_ERROR_NONE) { error_log('WordPress API JSON decode error: ' . json_last_error_msg() . ' for URL: ' . $url); return []; } return $data ?: []; } /** * Valide l'URL WordPress pour prévenir les attaques SSRF * * @param string $url URL à valider * @return bool True si l'URL est valide et sûre */ function isValidWordPressUrl($url) { // Vérifier que l'URL est bien formée $parsed = parse_url($url); if (!$parsed || !isset($parsed['scheme']) || !isset($parsed['host'])) { return false; } // Autoriser uniquement HTTPS (ou HTTP en développement) if (!in_array($parsed['scheme'], ['https', 'http'])) { return false; } // Bloquer les adresses IP privées et locales $host = $parsed['host']; if (filter_var($host, FILTER_VALIDATE_IP)) { if (!filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) { return false; } } // Bloquer localhost et autres domaines dangereux $blockedHosts = ['localhost', '127.0.0.1', '::1', '0.0.0.0', 'metadata.google.internal']; if (in_array(strtolower($host), $blockedHosts)) { return false; } return true; } /** * Récupère les articles WordPress récents * * @param int $count Nombre d'articles à récupérer * @return array Liste des articles formatés */ function getWordPressPosts($count = null) { if ($count === null) { $count = defined('WORDPRESS_POSTS_COUNT') ? WORDPRESS_POSTS_COUNT : 6; } // Utiliser le cache pour les posts WordPress return callApiCached('wp-posts-' . $count, function() use ($count) { // Récupérer les posts via l'API WordPress REST $posts = callWordPressApi('posts', [ 'per_page' => $count, '_embed' => 'true' // Inclure les médias embarqués (featured image) ]); return formatWordPressPosts($posts); }, 900); // 15 minutes de cache } /** * Formate les données des articles WordPress * * @param array $posts Données brutes des articles * @return array Articles formatés */ function formatWordPressPosts($posts) { if (empty($posts)) { return []; } $formattedPosts = []; foreach ($posts as $post) { // Récupérer l'image mise en avant $featuredImage = ''; if (isset($post['_embedded']['wp:featuredmedia'][0]['source_url'])) { $featuredImage = $post['_embedded']['wp:featuredmedia'][0]['source_url']; } // Nettoyer l'extrait HTML $excerpt = ''; if (isset($post['excerpt']['rendered'])) { $excerpt = wp_strip_all_tags($post['excerpt']['rendered']); $excerpt = trim($excerpt); // Limiter à 150 caractères if (strlen($excerpt) > 150) { $excerpt = substr($excerpt, 0, 147) . '...'; } } // Formater la date $date = ''; if (isset($post['date'])) { $dateObj = new DateTime($post['date']); $date = $dateObj->format('d/m/Y'); } $formattedPosts[] = [ 'id' => $post['id'], 'title' => html_entity_decode($post['title']['rendered'], ENT_QUOTES, 'UTF-8'), 'excerpt' => $excerpt, 'featured_image' => $featuredImage, 'link' => $post['link'], 'date' => $date, 'author' => isset($post['_embedded']['author'][0]['name']) ? $post['_embedded']['author'][0]['name'] : '' ]; } return $formattedPosts; } /** * Fonction utilitaire pour nettoyer le HTML (similaire à wp_strip_all_tags de WordPress) * * @param string $string Chaîne à nettoyer * @return string Chaîne nettoyée */ function wp_strip_all_tags($string) { $string = preg_replace('@<(script|style)[^>]*?>.*?@si', '', $string); $string = strip_tags($string); $string = preg_replace('/\s+/', ' ', $string); return trim($string); } ?>