2025-09-17 22:30:09 +04:00
|
|
|
<?php
|
|
|
|
|
/**
|
|
|
|
|
* Fonctions pour l'intégration WordPress via l'API REST
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Appelle l'API WordPress REST pour récupérer les articles
|
|
|
|
|
*
|
|
|
|
|
* @param string $endpoint Point de terminaison de l'API (ex: 'posts')
|
|
|
|
|
* @param array $params Paramètres optionnels pour la requête
|
|
|
|
|
* @return array Données retournées par l'API
|
|
|
|
|
*/
|
|
|
|
|
function callWordPressApi($endpoint, $params = []) {
|
|
|
|
|
// Vérifier que WordPress est activé et configuré
|
|
|
|
|
if (!defined('WORDPRESS_ENABLED') || !WORDPRESS_ENABLED || empty(WORDPRESS_URL)) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Validation de l'URL WordPress pour prévenir SSRF
|
|
|
|
|
if (!isValidWordPressUrl(WORDPRESS_URL)) {
|
|
|
|
|
error_log('SECURITY: Invalid WordPress URL detected: ' . WORDPRESS_URL);
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Construire l'URL de l'API
|
|
|
|
|
$url = WORDPRESS_URL . '/wp-json/wp/v2/' . ltrim($endpoint, '/');
|
|
|
|
|
|
|
|
|
|
// Ajouter les paramètres à l'URL
|
|
|
|
|
if (!empty($params)) {
|
|
|
|
|
$url .= '?' . http_build_query($params);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Initialiser cURL avec options de sécurité
|
|
|
|
|
$ch = curl_init();
|
|
|
|
|
curl_setopt($ch, CURLOPT_URL, $url);
|
|
|
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
|
|
|
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
|
|
|
|
|
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
|
|
|
|
|
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
|
|
|
|
|
curl_setopt($ch, CURLOPT_MAXREDIRS, 0);
|
|
|
|
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
|
|
|
|
|
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
|
|
|
|
|
curl_setopt($ch, CURLOPT_USERAGENT, 'KaubuntuRe-WordPress-Integration/1.0');
|
|
|
|
|
|
|
|
|
|
// Exécuter la requête
|
|
|
|
|
$response = curl_exec($ch);
|
|
|
|
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
|
|
|
$contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
|
|
|
|
|
$error = curl_error($ch);
|
|
|
|
|
curl_close($ch);
|
|
|
|
|
|
|
|
|
|
// Traiter la réponse
|
|
|
|
|
if ($response === false || !empty($error)) {
|
|
|
|
|
error_log('WordPress API error: ' . $error);
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($httpCode < 200 || $httpCode >= 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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-18 17:49:23 +04:00
|
|
|
/**
|
|
|
|
|
* Retourne le nombre total d'articles publiés sur WordPress.
|
|
|
|
|
* Lit le header X-WP-Total renvoyé par l'API REST.
|
|
|
|
|
*/
|
|
|
|
|
function getWordPressPostCount(): ?int {
|
|
|
|
|
if (!defined('WORDPRESS_ENABLED') || !WORDPRESS_ENABLED || empty(WORDPRESS_URL)) return null;
|
|
|
|
|
if (!isValidWordPressUrl(WORDPRESS_URL)) return null;
|
|
|
|
|
|
|
|
|
|
$cached = $GLOBALS['simple_api_cache']->get('wp_post_count', []);
|
|
|
|
|
if ($cached !== null) return (int) $cached ?: null;
|
|
|
|
|
|
|
|
|
|
$url = WORDPRESS_URL . '/wp-json/wp/v2/posts?per_page=1&_fields=id';
|
|
|
|
|
|
|
|
|
|
$ch = curl_init();
|
|
|
|
|
curl_setopt_array($ch, [
|
|
|
|
|
CURLOPT_URL => $url,
|
|
|
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
|
|
|
CURLOPT_HEADER => true,
|
|
|
|
|
CURLOPT_TIMEOUT => 10,
|
|
|
|
|
CURLOPT_CONNECTTIMEOUT => 5,
|
|
|
|
|
CURLOPT_FOLLOWLOCATION => false,
|
|
|
|
|
CURLOPT_SSL_VERIFYPEER => true,
|
|
|
|
|
CURLOPT_SSL_VERIFYHOST => 2,
|
|
|
|
|
CURLOPT_USERAGENT => 'KaubuntuRe-WordPress-Integration/1.0',
|
|
|
|
|
]);
|
|
|
|
|
$response = curl_exec($ch);
|
|
|
|
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
|
|
|
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
|
|
|
|
|
curl_close($ch);
|
|
|
|
|
|
|
|
|
|
if (!$response || $httpCode < 200 || $httpCode >= 300) return null;
|
|
|
|
|
|
|
|
|
|
$headers = substr($response, 0, $headerSize);
|
|
|
|
|
if (!preg_match('/X-WP-Total:\s*(\d+)/i', $headers, $m)) return null;
|
|
|
|
|
|
|
|
|
|
$count = (int) $m[1];
|
|
|
|
|
$GLOBALS['simple_api_cache']->set('wp_post_count', [], $count, 86400);
|
|
|
|
|
return $count;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-17 22:30:09 +04:00
|
|
|
/**
|
|
|
|
|
* 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;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-28 20:38:51 +04:00
|
|
|
// 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
|
2025-09-17 22:30:09 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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'])) {
|
2026-05-18 17:49:23 +04:00
|
|
|
$excerpt = html_entity_decode(wp_strip_all_tags($post['excerpt']['rendered']), ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
2025-09-17 22:30:09 +04:00
|
|
|
$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)[^>]*?>.*?</\\1>@si', '', $string);
|
|
|
|
|
$string = strip_tags($string);
|
|
|
|
|
$string = preg_replace('/\s+/', ' ', $string);
|
|
|
|
|
return trim($string);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
?>
|