feat: show WordPress posts

This commit is contained in:
2025-09-17 22:30:09 +04:00
parent 63f947fefc
commit 57e0a0d35f
8 changed files with 556 additions and 10 deletions
+13
View File
@@ -133,4 +133,17 @@ if (!defined('COUNTDOWN_TIMEZONES')) {
'Kanaky' => 'Pacific/Noumea'
]);
}
// =========================================
// Intégration WordPress par défaut
// =========================================
// URL du site WordPress par défaut
if (!defined('WORDPRESS_URL')) define('WORDPRESS_URL', '');
// Nombre d'articles WordPress à afficher par défaut
if (!defined('WORDPRESS_POSTS_COUNT')) define('WORDPRESS_POSTS_COUNT', 6);
// Activation des articles WordPress par défaut
if (!defined('WORDPRESS_ENABLED')) define('WORDPRESS_ENABLED', false);
?>
+13
View File
@@ -252,6 +252,19 @@ define('COUNTDOWN_TIMEZONES', [
'Kanaky' => 'Pacific/Noumea'
]);
// =========================================
// Intégration WordPress
// =========================================
// URL du site WordPress pour récupérer les articles (sans trailing slash)
// define('WORDPRESS_URL', 'https://votre-site-wordpress.com');
// Nombre d'articles WordPress à afficher
// define('WORDPRESS_POSTS_COUNT', 6);
// Activer/désactiver l'affichage des articles WordPress
// define('WORDPRESS_ENABLED', true);
// =========================================
// Texte de présentation du mouvement
// =========================================
+3
View File
@@ -6,6 +6,9 @@ require_once __DIR__ . '/security.php';
// Charger le système de cache simple
require_once __DIR__ . '/simple-cache.php';
// Charger les fonctions WordPress
require_once __DIR__ . '/wordpress.php';
// Charger d'abord la configuration locale si elle existe
$config_local_file = __DIR__ . '/config.local.php';
if (file_exists($config_local_file)) {
+1
View File
@@ -78,4 +78,5 @@
endif;
?>
</div>
</nav>
+199
View File
@@ -0,0 +1,199 @@
<?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;
}
/**
* 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;
}
// 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);
}
/**
* 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)[^>]*?>.*?</\\1>@si', '', $string);
$string = strip_tags($string);
$string = preg_replace('/\s+/', ' ', $string);
return trim($string);
}
?>