diff --git a/css/styles.css b/css/styles.css index 1f535a4..7da385f 100644 --- a/css/styles.css +++ b/css/styles.css @@ -469,6 +469,24 @@ img { --mt-color-content-txt: var(--text-color); } +/* Override couleurs Mastodon - Theme rouge */ +.mt-container, .mt-container[data-theme=light], .mt-dialog, .mt-dialog[data-theme=light] { + --mt-color-btn-bg: var(--primary-red) !important; + --mt-color-btn-bg-hover: #cc0000 !important; + --mt-color-link: var(--primary-red) !important; +} + +.mt-container[data-theme=dark], .mt-dialog[data-theme=dark] { + --mt-color-btn-bg: var(--primary-red) !important; + --mt-color-btn-bg-hover: #ff6666 !important; + --mt-color-link: #ff6666 !important; +} + +/* Icône Mastodon en rouge */ +.mt-title i.fa-mastodon { + color: var(--primary-red) !important; +} + [data-theme="dark"] img { opacity: 0.9; } @@ -499,15 +517,35 @@ img { /* Hero Section */ .hero-mastodon-wrapper { - display: flex; + display: grid; + grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 30px; padding-top: 5px; + align-items: start; +} + +/* Responsive breakpoint intermédiaire pour les 3 éléments */ +@media (max-width: 1200px) and (min-width: 1025px) { + .hero-mastodon-wrapper { + grid-template-columns: 1fr; + grid-template-rows: auto auto; + } + + .timeline-wordpress-container { + grid-column: 1 / -1; + grid-template-columns: 1fr; + gap: 15px; + } + + .timeline-wordpress-container .wordpress-section { + width: 100%; + } } .hero { position: relative; - width: 50%; + width: 100%; height: 400px; background-color: #888; overflow: hidden; @@ -633,7 +671,7 @@ img { } #mt-container { - width: 50%; + width: 100%; height: 400px; border-radius: 8px; display: flex; @@ -1730,7 +1768,7 @@ img { } .hero-mastodon-wrapper { - flex-direction: column; + grid-template-columns: 1fr; } .hero, #mt-container { @@ -1738,10 +1776,15 @@ img { height: 300px; } - #mt-container { + .hero-mastodon-wrapper #mt-container { height: 600px; } + /* Pour le nouveau layout timeline-wordpress */ + .timeline-wordpress-container #mt-container { + height: 400px; + } + /* Réduire la taille des boutons du footer Mastodon en mobile */ .mt-footer .mt-btn-violet { font-size: 12px !important; diff --git a/css/wordpress-posts.css b/css/wordpress-posts.css new file mode 100644 index 0000000..27faf5c --- /dev/null +++ b/css/wordpress-posts.css @@ -0,0 +1,204 @@ +/* Styles pour la liste compacte d'articles WordPress */ + +/* Container principal pour timeline + wordpress */ +.timeline-wordpress-container { + display: grid; + grid-template-columns: 2fr 1fr; + gap: 20px; + margin: 0; + align-items: start; + width: 100%; +} + +.wordpress-container { + background: var(--card-bg); + border-radius: 12px; + border: 1px solid var(--border-color); + box-shadow: var(--card-shadow); + overflow: hidden; + height: 400px; + display: flex; + flex-direction: column; +} + +.wordpress-section { + width: 100%; + min-width: 0; +} + +/* Assurer que le container Mastodon garde son scroll */ +.timeline-wordpress-container .mt-container { + height: 400px !important; + overflow-y: auto !important; +} + +.wordpress-header { + background: var(--primary-red); + color: white; + padding: 12px 16px; + font-size: 1rem; + font-weight: bold; + display: flex; + align-items: center; + gap: 8px; +} + +.wordpress-header a { + color: white; + text-decoration: none; + transition: opacity 0.2s ease; +} + +.wordpress-header a:hover { + opacity: 0.8; + text-decoration: underline; +} + +.wordpress-posts-list { + flex: 1; + overflow-y: auto; + scrollbar-width: thin; + scrollbar-color: var(--primary-red) transparent; +} + +.wordpress-posts-list::-webkit-scrollbar { + width: 6px; +} + +.wordpress-posts-list::-webkit-scrollbar-track { + background: transparent; +} + +.wordpress-posts-list::-webkit-scrollbar-thumb { + background-color: var(--primary-red); + border-radius: 3px; +} + +.wordpress-post-item { + display: flex; + padding: 8px 12px; + border-bottom: 1px solid var(--border-color); + text-decoration: none; + color: inherit; + transition: background-color 0.2s ease; + gap: 8px; +} + +.wordpress-post-item:hover { + background: var(--hover-bg); + text-decoration: none; + color: inherit; +} + +.wordpress-post-item:last-child { + border-bottom: none; +} + +.wordpress-post-thumb { + width: 40px; + height: 40px; + border-radius: 6px; + overflow: hidden; + flex-shrink: 0; + background: var(--tag-bg); + display: flex; + align-items: center; + justify-content: center; + color: var(--text-secondary); +} + +.wordpress-post-thumb img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.wordpress-post-info { + flex: 1; + min-width: 0; +} + +.wordpress-post-title { + font-size: 0.8rem; + font-weight: 600; + color: var(--text-color); + margin: 0 0 4px 0; + line-height: 1.2; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} + + +.wordpress-post-meta { + font-size: 0.65rem; + color: var(--text-secondary); + display: flex; + gap: 6px; +} + +.wordpress-no-posts { + text-align: center; + color: var(--text-secondary); + padding: 20px; + font-style: italic; + font-size: 0.9rem; +} + +/* Ajustement du container Mastodon */ +.mt-container { + width: 100%; + min-width: 0; +} + +/* Responsive Design */ +@media (max-width: 1024px) { + /* Mobile/Tablette: layout vertical */ + .timeline-wordpress-container { + grid-template-columns: 1fr; + gap: 15px; + } + + .wordpress-section { + width: 100%; + } + + /* Maintenir la hauteur et le scroll du container Mastodon en mobile */ + .timeline-wordpress-container .mt-container { + height: 400px !important; + overflow-y: auto !important; + max-height: 400px !important; + } + + .wordpress-container { + height: auto; + } +} + +@media (max-width: 768px) { + .wordpress-post-thumb { + width: 50px; + height: 50px; + } + + .wordpress-post-item { + padding: 10px 12px; + gap: 10px; + } + + .wordpress-post-title { + font-size: 0.85rem; + } + + + .wordpress-post-meta { + font-size: 0.65rem; + } + + .wordpress-header { + padding: 10px 12px; + font-size: 0.9rem; + } + +} \ No newline at end of file diff --git a/includes/config.default.php b/includes/config.default.php index 50006dd..57b63b2 100644 --- a/includes/config.default.php +++ b/includes/config.default.php @@ -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); ?> diff --git a/includes/config.local.php.sample b/includes/config.local.php.sample index 8ec097e..6b0da38 100644 --- a/includes/config.local.php.sample +++ b/includes/config.local.php.sample @@ -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 // ========================================= diff --git a/includes/config.php b/includes/config.php index 93dd2c2..d7440b3 100644 --- a/includes/config.php +++ b/includes/config.php @@ -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)) { diff --git a/includes/sidebar.php b/includes/sidebar.php index 5e9133b..6d089b4 100644 --- a/includes/sidebar.php +++ b/includes/sidebar.php @@ -78,4 +78,5 @@ endif; ?> + diff --git a/includes/wordpress.php b/includes/wordpress.php new file mode 100644 index 0000000..aba2d06 --- /dev/null +++ b/includes/wordpress.php @@ -0,0 +1,199 @@ += 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); +} + +?> diff --git a/index.php b/index.php index d9b21c2..9778f44 100644 --- a/index.php +++ b/index.php @@ -24,6 +24,9 @@ setSecurityHeaders(); + + + @@ -151,13 +154,79 @@ setSecurityHeaders(); ?> -