feat: social media aggregator (YouTube, Instagram, TikTok, WordPress)
This commit is contained in:
+48
-56
@@ -1,80 +1,72 @@
|
||||
<!-- Footer -->
|
||||
<?php
|
||||
$currentPage = basename($_SERVER['PHP_SELF']);
|
||||
?>
|
||||
<div class="footer">
|
||||
<div class="footer-header">
|
||||
<div class="footer-logo">
|
||||
<img src="img/logo.png" alt="kaubuntu.re">
|
||||
<img src="img/logo.png" alt="<?php echo htmlspecialchars(SITE_NAME); ?>">
|
||||
</div>
|
||||
|
||||
<div class="footer-contact-info">
|
||||
<div class="footer-contact">CONTACT</div>
|
||||
<div class="footer-email"><a href="mailto:<?php echo CONTACT_EMAIL; ?>"><?php echo CONTACT_EMAIL; ?></a></div>
|
||||
<div class="footer-email">
|
||||
<a href="mailto:<?php echo htmlspecialchars(CONTACT_EMAIL); ?>"><?php echo htmlspecialchars(CONTACT_EMAIL); ?></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer-columns">
|
||||
<div class="footer-column">
|
||||
<h3 class="footer-title">Catégories</h3>
|
||||
<div>
|
||||
<ul class="footer-links">
|
||||
<li><a href="index.php" <?php echo ($currentPage === 'index.php') ? 'class="active"' : ''; ?>>Accueil</a></li>
|
||||
<li><a href="direct.php" <?php echo ($currentPage === 'direct.php') ? 'class="active"' : ''; ?>>Direct</a></li>
|
||||
|
||||
<?php
|
||||
if (defined('PRIORITY_CATEGORIES') && !empty(PRIORITY_CATEGORIES)) {
|
||||
foreach (PRIORITY_CATEGORIES as $id => $name) {
|
||||
$isActive = ($currentPage === 'categories.php' && $currentCategoryId === $id);
|
||||
echo '<li><a href="categories.php?id=' . $id . '"' . ($isActive ? ' class="active"' : '') . '>' . htmlspecialchars($name) . '</a></li>';
|
||||
}
|
||||
}
|
||||
?>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer-column">
|
||||
<h3 class="footer-title">Hashtags</h3>
|
||||
<div>
|
||||
<ul class="footer-links">
|
||||
<?php
|
||||
if (defined('IMPORTANT_TAGS') && !empty(IMPORTANT_TAGS)):
|
||||
foreach (IMPORTANT_TAGS as $tag):
|
||||
$encodedTag = urlencode('#' . $tag);
|
||||
$isActive = ($isTagSearch && strtolower($currentTag) === strtolower($tag));
|
||||
?>
|
||||
<li><a href="recherche.php?q=<?php echo $encodedTag; ?>" <?php echo $isActive ? 'class="active"' : ''; ?>><?php echo htmlspecialchars($tag); ?></a></li>
|
||||
<?php
|
||||
endforeach;
|
||||
endif;
|
||||
?>
|
||||
</ul>
|
||||
</div>
|
||||
<h3 class="footer-title">Réseaux sociaux</h3>
|
||||
<ul class="footer-links">
|
||||
<li>
|
||||
<a href="<?php echo htmlspecialchars(YOUTUBE_URL); ?>" target="_blank" rel="noopener noreferrer">
|
||||
<i class="fab fa-youtube icon-youtube" aria-hidden="true"></i> YouTube
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="<?php echo htmlspecialchars(INSTAGRAM_URL); ?>" target="_blank" rel="noopener noreferrer">
|
||||
<i class="fab fa-instagram icon-instagram" aria-hidden="true"></i> Instagram
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="<?php echo htmlspecialchars(TIKTOK_URL); ?>" target="_blank" rel="noopener noreferrer">
|
||||
<i class="fab fa-tiktok icon-tiktok" aria-hidden="true"></i> TikTok
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="footer-column">
|
||||
<h3 class="footer-title">Informations légales</h3>
|
||||
<div>
|
||||
<ul class="footer-links">
|
||||
<li><a href="mentions-legales.php" <?php echo ($currentPage === 'mentions-legales.php') ? 'class="active"' : ''; ?>>Mentions légales</a></li>
|
||||
<li>
|
||||
<a href="<?php echo LEGAL_SOURCE_CODE_URL; ?>" target="_blank" rel="noopener noreferrer">
|
||||
<i class="fab fa-git-alt"></i> Code source
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<ul class="footer-links">
|
||||
<li>
|
||||
<a href="mentions-legales.php" <?php echo ($currentPage === 'mentions-legales.php') ? 'class="active"' : ''; ?>>
|
||||
Mentions légales
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="<?php echo htmlspecialchars(LEGAL_SOURCE_CODE_URL); ?>" target="_blank" rel="noopener noreferrer">
|
||||
<i class="fab fa-git-alt" aria-hidden="true"></i> Code source
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer-social">
|
||||
<a target="_blank" rel="me noreferrer" href="<?php echo MASTODON_URL; ?>"><i class="fab fa-mastodon icon-mastodon"></i></a>
|
||||
<a target="_blank" rel="noreferrer" href="<?php echo FACEBOOK_URL; ?>"><i class="fab fa-facebook icon-facebook"></i></a>
|
||||
<a target="_blank" rel="noreferrer" href="<?php echo YOUTUBE_URL; ?>"><i class="fab fa-youtube icon-youtube"></i></a>
|
||||
<a target="_blank" rel="noreferrer" href="<?php echo INSTAGRAM_URL; ?>"><i class="fab fa-instagram icon-instagram"></i></a>
|
||||
<a target="_blank" rel="noreferrer" href="<?php echo X_URL; ?>"><i class="fab fa-x-twitter icon-x"></i></a>
|
||||
<a target="_blank" rel="noreferrer" href="<?php echo TIKTOK_URL; ?>"><i class="fab fa-tiktok icon-tiktok"></i></a>
|
||||
<a target="_blank" rel="noopener noreferrer" href="<?php echo htmlspecialchars(YOUTUBE_URL); ?>" aria-label="YouTube">
|
||||
<i class="fab fa-youtube icon-youtube" aria-hidden="true"></i>
|
||||
</a>
|
||||
<a target="_blank" rel="noopener noreferrer" href="<?php echo htmlspecialchars(INSTAGRAM_URL); ?>" aria-label="Instagram">
|
||||
<i class="fab fa-instagram icon-instagram" aria-hidden="true"></i>
|
||||
</a>
|
||||
<a target="_blank" rel="noopener noreferrer" href="<?php echo htmlspecialchars(TIKTOK_URL); ?>" aria-label="TikTok">
|
||||
<i class="fab fa-tiktok icon-tiktok" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="footer-copyright">
|
||||
<?php echo LEGAL_COPYRIGHT; ?> <?php echo date('Y'); ?> - Licence libre <a href="<?php echo LEGAL_LICENSE_URL; ?>" target="_blank" rel="noopener noreferrer">GNU AGPL-V3</a>
|
||||
<?php echo htmlspecialchars(LEGAL_COPYRIGHT); ?> <?php echo date('Y'); ?> —
|
||||
Licence libre <a href="<?php echo htmlspecialchars(LEGAL_LICENSE_URL); ?>" target="_blank" rel="noopener noreferrer">GNU AGPL-V3</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+15
-32
@@ -1,47 +1,30 @@
|
||||
<!-- Header avec barre de recherche et icônes -->
|
||||
<!-- Header -->
|
||||
<header class="header" role="banner">
|
||||
<div class="search-container">
|
||||
<form action="recherche.php" method="get" role="search" aria-label="Recherche de vidéos">
|
||||
<label for="search-input" class="sr-only">Rechercher des vidéos</label>
|
||||
<input type="text" id="search-input" name="q" placeholder="Rechercher..." aria-describedby="search-help">
|
||||
<button type="submit" aria-label="Lancer la recherche">
|
||||
<i class="fas fa-search" aria-hidden="true"></i>
|
||||
</button>
|
||||
<div id="search-help" class="sr-only">Tapez vos mots-clés pour rechercher des vidéos</div>
|
||||
</form>
|
||||
<div class="header-brand">
|
||||
<a href="/" class="header-logo-link" aria-label="Accueil <?php echo htmlspecialchars(SITE_NAME); ?>">
|
||||
<img src="img/logo.png" alt="<?php echo htmlspecialchars(SITE_NAME); ?>" class="header-logo-img">
|
||||
<span class="header-site-name"><?php echo htmlspecialchars(SITE_NAME); ?></span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<nav class="social-icons" aria-label="Réseaux sociaux">
|
||||
<a target="_blank" rel="me noreferrer" href="<?php echo MASTODON_URL; ?>" class="icon-button" aria-label="Suivre sur Mastodon">
|
||||
<i class="fab fa-mastodon icon-mastodon" aria-hidden="true"></i>
|
||||
<nav class="social-icons" aria-label="Nos réseaux sociaux">
|
||||
<a target="_blank" rel="noreferrer" href="<?php echo htmlspecialchars(YOUTUBE_URL); ?>"
|
||||
class="icon-button" aria-label="YouTube">
|
||||
<i class="fab fa-youtube icon-youtube" aria-hidden="true"></i>
|
||||
</a>
|
||||
<a target="_blank" rel="noreferrer" href="<?php echo INSTAGRAM_URL; ?>" class="icon-button" aria-label="Suivre sur Instagram">
|
||||
<a target="_blank" rel="noreferrer" href="<?php echo htmlspecialchars(INSTAGRAM_URL); ?>"
|
||||
class="icon-button" aria-label="Instagram">
|
||||
<i class="fab fa-instagram icon-instagram" aria-hidden="true"></i>
|
||||
</a>
|
||||
<a target="_blank" rel="noreferrer" href="<?php echo TIKTOK_URL; ?>" class="icon-button" aria-label="Suivre sur TikTok">
|
||||
<a target="_blank" rel="noreferrer" href="<?php echo htmlspecialchars(TIKTOK_URL); ?>"
|
||||
class="icon-button" aria-label="TikTok">
|
||||
<i class="fab fa-tiktok icon-tiktok" aria-hidden="true"></i>
|
||||
</a>
|
||||
<div class="more-social-container">
|
||||
<button type="button" class="icon-button more-social-toggle" aria-expanded="false" aria-controls="social-dropdown" aria-label="Voir plus de réseaux sociaux">
|
||||
<i class="fas fa-ellipsis-h" aria-hidden="true"></i>
|
||||
</button>
|
||||
<div id="social-dropdown" class="more-social-dropdown" role="menu">
|
||||
<a target="_blank" rel="noreferrer" href="<?php echo FACEBOOK_URL; ?>" class="more-social-item" role="menuitem">
|
||||
<i class="fab fa-facebook icon-facebook" aria-hidden="true"></i> Facebook
|
||||
</a>
|
||||
<a target="_blank" rel="noreferrer" href="<?php echo YOUTUBE_URL; ?>" class="more-social-item" role="menuitem">
|
||||
<i class="fab fa-youtube icon-youtube" aria-hidden="true"></i> YouTube
|
||||
</a>
|
||||
<a target="_blank" rel="noreferrer" href="<?php echo X_URL; ?>" class="more-social-item" role="menuitem">
|
||||
<i class="fab fa-x-twitter icon-x" aria-hidden="true"></i> X
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="action-icons">
|
||||
<?php if (defined('DONATIONS_ENABLED') && DONATIONS_ENABLED && !empty(PAYPAL_ME_URL)): ?>
|
||||
<a href="dons.php" class="icon-button donation-link" aria-label="Soutenir KA UBUNTU" title="Faire un don">
|
||||
<a href="dons.php" class="icon-button donation-link" aria-label="Soutenir <?php echo htmlspecialchars(SITE_NAME); ?>" title="Faire un don">
|
||||
<i class="fas fa-heart" aria-hidden="true"></i>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
+46
-76
@@ -1,92 +1,62 @@
|
||||
<!-- Menu mobile (masqué par défaut) -->
|
||||
<div class="mobile-menu">
|
||||
<button class="mobile-menu-close">
|
||||
<i class="fas fa-times"></i>
|
||||
<?php
|
||||
$currentPage = basename($_SERVER['PHP_SELF']);
|
||||
$isHome = ($currentPage === 'index.php' || $currentPage === '/');
|
||||
?>
|
||||
<!-- Menu mobile -->
|
||||
<div class="mobile-menu" id="mobile-menu" role="dialog" aria-label="Menu de navigation" aria-modal="true">
|
||||
<button class="mobile-menu-close" aria-label="Fermer le menu">
|
||||
<i class="fas fa-times" aria-hidden="true"></i>
|
||||
</button>
|
||||
|
||||
<div class="search-container">
|
||||
<form action="recherche.php" method="get">
|
||||
<input type="text" name="q" placeholder="Rechercher...">
|
||||
<button type="submit"><i class="fas fa-search"></i></button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<a href="index.php" class="nav-item <?php echo ($currentPage === 'index.php') ? 'active' : ''; ?>">
|
||||
<i class="fas fa-home"></i> Accueil
|
||||
</a>
|
||||
<a href="direct.php" class="nav-item <?php echo ($currentPage === 'direct.php') ? 'active' : ''; ?>">
|
||||
<i class="fas fa-broadcast-tower"></i> Direct
|
||||
<nav>
|
||||
<a href="/"
|
||||
class="nav-item <?php echo $isHome ? 'active' : ''; ?>">
|
||||
<i class="fas fa-home" aria-hidden="true"></i> Accueil
|
||||
</a>
|
||||
|
||||
<hr class="nav-divider">
|
||||
|
||||
<?php
|
||||
// Afficher les catégories prioritaires
|
||||
if (defined('PRIORITY_CATEGORIES') && !empty(PRIORITY_CATEGORIES)) {
|
||||
// Tableau associatif des icônes pour les catégories
|
||||
$categoryIcons = [
|
||||
1 => 'fas fa-music', // Musique
|
||||
2 => 'fas fa-film', // Films
|
||||
3 => 'fas fa-car', // Véhicules
|
||||
4 => 'fas fa-palette', // Jeux
|
||||
5 => 'fas fa-running', // Sport
|
||||
6 => 'fas fa-laugh', // Humour
|
||||
7 => 'fas fa-gamepad', // Art
|
||||
8 => 'fas fa-person', // Personnalités
|
||||
9 => 'fas fa-face-grin-tears', // Comédie
|
||||
10 => 'fas fa-tv', // Divertissement
|
||||
11 => 'fas fa-globe', // Actualité & Politique
|
||||
12 => 'fas fa-chalkboard-user', // Tutoriel
|
||||
13 => 'fas fa-graduation-cap', // Education
|
||||
14 => 'fas fa-fist-raised', // Activisme
|
||||
15 => 'fas fa-microscope', // Science & Technologie
|
||||
16 => 'fas fa-paw', // Animaux
|
||||
17 => 'fas fa-child', // Enfants
|
||||
18 => 'fas fa-utensils' // Cuisine
|
||||
];
|
||||
<p class="mobile-section-title">Nos réseaux</p>
|
||||
|
||||
foreach (PRIORITY_CATEGORIES as $id => $name) {
|
||||
$isActive = ($currentPage === 'categories.php' && $currentCategoryId === $id);
|
||||
$icon = isset($categoryIcons[$id]) ? $categoryIcons[$id] : 'fas fa-folder';
|
||||
echo '<a href="categories.php?id=' . $id . '" class="nav-item ' . ($isActive ? 'active' : '') . '">';
|
||||
echo '<i class="' . $icon . '"></i> ' . htmlspecialchars($name);
|
||||
echo '</a>';
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<hr class="nav-divider">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="mobile-section-title">Hashtags populaires</h3>
|
||||
<?php
|
||||
if (defined('IMPORTANT_TAGS') && !empty(IMPORTANT_TAGS)):
|
||||
foreach (IMPORTANT_TAGS as $tag):
|
||||
$encodedTag = urlencode('#' . $tag);
|
||||
$isActive = ($isTagSearch && strtolower($currentTag) === strtolower($tag));
|
||||
?>
|
||||
<a href="recherche.php?q=<?php echo $encodedTag; ?>" class="nav-item <?php echo $isActive ? 'active' : ''; ?>">
|
||||
<i class="fas fa-hashtag"></i> <?php echo htmlspecialchars($tag); ?>
|
||||
<a href="<?php echo $isHome ? '#youtube' : 'index.php#youtube'; ?>"
|
||||
class="nav-item">
|
||||
<i class="fab fa-youtube icon-youtube" aria-hidden="true"></i> YouTube
|
||||
</a>
|
||||
<?php
|
||||
endforeach;
|
||||
endif;
|
||||
?>
|
||||
</div>
|
||||
<a href="<?php echo $isHome ? '#instagram' : 'index.php#instagram'; ?>"
|
||||
class="nav-item">
|
||||
<i class="fab fa-instagram icon-instagram" aria-hidden="true"></i> Instagram
|
||||
</a>
|
||||
<a href="<?php echo $isHome ? '#tiktok' : 'index.php#tiktok'; ?>"
|
||||
class="nav-item">
|
||||
<i class="fab fa-tiktok icon-tiktok" aria-hidden="true"></i> TikTok
|
||||
</a>
|
||||
<a href="<?php echo $isHome ? '#actualites' : 'index.php#actualites'; ?>"
|
||||
class="nav-item">
|
||||
<i class="fas fa-newspaper" aria-hidden="true"></i> Actualités
|
||||
</a>
|
||||
|
||||
<?php if (defined('DONATIONS_ENABLED') && DONATIONS_ENABLED && !empty(PAYPAL_ME_URL)): ?>
|
||||
<hr class="nav-divider">
|
||||
<a href="dons.php" class="nav-item donation-nav-link <?php echo ($currentPage === 'dons.php') ? 'active' : ''; ?>">
|
||||
<i class="fas fa-heart" aria-hidden="true"></i> Soutenir
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</nav>
|
||||
|
||||
<hr class="nav-divider">
|
||||
|
||||
<div>
|
||||
<h3 class="mobile-section-title">Suivez-nous</h3>
|
||||
<p class="mobile-section-title">Suivez-nous</p>
|
||||
<div class="mobile-social-icons">
|
||||
<a target="_blank" rel="me noreferrer" href="<?php echo MASTODON_URL; ?>"><i class="fab fa-mastodon icon-mastodon"></i></a>
|
||||
<a target="_blank" rel="noreferrer" href="<?php echo FACEBOOK_URL; ?>"><i class="fab fa-facebook icon-facebook"></i></a>
|
||||
<a target="_blank" rel="noreferrer" href="<?php echo INSTAGRAM_URL; ?>"><i class="fab fa-instagram icon-instagram"></i></a>
|
||||
<a target="_blank" rel="noreferrer" href="<?php echo TIKTOK_URL; ?>"><i class="fab fa-tiktok icon-tiktok"></i></a>
|
||||
<a target="_blank" rel="noreferrer" href="<?php echo YOUTUBE_URL; ?>"><i class="fab fa-youtube icon-youtube"></i></a>
|
||||
<a target="_blank" rel="noreferrer" href="<?php echo X_URL; ?>"><i class="fab fa-x-twitter icon-x"></i></a>
|
||||
<a target="_blank" rel="noopener noreferrer" href="<?php echo htmlspecialchars(YOUTUBE_URL); ?>" aria-label="YouTube">
|
||||
<i class="fab fa-youtube icon-youtube" aria-hidden="true"></i>
|
||||
</a>
|
||||
<a target="_blank" rel="noopener noreferrer" href="<?php echo htmlspecialchars(INSTAGRAM_URL); ?>" aria-label="Instagram">
|
||||
<i class="fab fa-instagram icon-instagram" aria-hidden="true"></i>
|
||||
</a>
|
||||
<a target="_blank" rel="noopener noreferrer" href="<?php echo htmlspecialchars(TIKTOK_URL); ?>" aria-label="TikTok">
|
||||
<i class="fab fa-tiktok icon-tiktok" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+32
-92
@@ -181,107 +181,47 @@ function validateCSRFToken($token) {
|
||||
* Applique des en-têtes de sécurité HTTP
|
||||
*/
|
||||
function setSecurityHeaders() {
|
||||
// Protection contre le clickjacking (permettre les iframes du même site)
|
||||
header('X-Frame-Options: SAMEORIGIN');
|
||||
|
||||
// Protection contre le MIME sniffing
|
||||
header('X-Content-Type-Options: nosniff');
|
||||
|
||||
// Protection XSS basique
|
||||
header('X-XSS-Protection: 1; mode=block');
|
||||
|
||||
// Politique de référent
|
||||
header('Referrer-Policy: strict-origin-when-cross-origin');
|
||||
|
||||
// Content Security Policy avec support Mastodon et PeerTube
|
||||
$mastodonDomain = '';
|
||||
$peertubeDomain = '';
|
||||
|
||||
// Extraire le domaine Mastodon si configuré
|
||||
if (defined('MASTODON_INSTANCE_URL')) {
|
||||
$mastodonParsed = parse_url(MASTODON_INSTANCE_URL);
|
||||
if ($mastodonParsed && isset($mastodonParsed['host'])) {
|
||||
$mastodonDomain = $mastodonParsed['scheme'] . '://' . $mastodonParsed['host'];
|
||||
}
|
||||
}
|
||||
|
||||
// Extraire le domaine PeerTube si configuré
|
||||
if (defined('PEERTUBE_URL')) {
|
||||
$peertubeParsed = parse_url(PEERTUBE_URL);
|
||||
if ($peertubeParsed && isset($peertubeParsed['host'])) {
|
||||
$peertubeDomain = $peertubeParsed['scheme'] . '://' . $peertubeParsed['host'];
|
||||
}
|
||||
}
|
||||
|
||||
// Détecter si on est en développement local
|
||||
$isLocalDev = in_array($_SERVER['HTTP_HOST'] ?? '', ['127.0.0.1:8080', '127.0.0.1:8001', 'localhost:8080', 'localhost:8001', '127.0.0.1', 'localhost']);
|
||||
|
||||
$csp = "default-src 'self'; ";
|
||||
|
||||
$isLocalDev = in_array(
|
||||
$_SERVER['HTTP_HOST'] ?? '',
|
||||
['127.0.0.1:8080', '127.0.0.1:8001', 'localhost:8080', 'localhost:8001', '127.0.0.1', 'localhost']
|
||||
);
|
||||
|
||||
// Domaines des 5 plateformes sociales
|
||||
$fbScripts = 'https://connect.facebook.net';
|
||||
$fbFrames = 'https://www.facebook.com https://staticxx.facebook.com https://www.facebook.net';
|
||||
$xScripts = 'https://platform.twitter.com';
|
||||
$xFrames = 'https://platform.twitter.com https://syndication.twitter.com https://cdn.syndication.twimg.com';
|
||||
$igScripts = 'https://www.instagram.com';
|
||||
$igFrames = 'https://www.instagram.com';
|
||||
$ttScripts = 'https://www.tiktok.com https://lf16-tiktok-web.ttwstatic.com';
|
||||
$ttFrames = 'https://www.tiktok.com';
|
||||
$ytImages = 'https://i.ytimg.com https://yt3.ggpht.com';
|
||||
|
||||
$csp = "default-src 'self'; ";
|
||||
$csp .= "style-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com; ";
|
||||
$csp .= "script-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com https://plausible.io; "; // PLAUSIBLE UPDATED
|
||||
|
||||
// Images : autoriser les domaines externes plus HTTPS général en dev
|
||||
$imgSrc = "'self' data: " . ($mastodonDomain ? $mastodonDomain : '') . " " . ($peertubeDomain ? $peertubeDomain : '');
|
||||
if ($isLocalDev) {
|
||||
$imgSrc .= " https: http:";
|
||||
} else {
|
||||
$imgSrc .= " https:";
|
||||
}
|
||||
$csp .= "img-src " . $imgSrc . "; ";
|
||||
|
||||
$csp .= "font-src 'self' https://cdnjs.cloudflare.com; ";
|
||||
|
||||
// Frames : autoriser PeerTube et HTTPS général
|
||||
$frameSrc = "'self' " . ($peertubeDomain ? $peertubeDomain : '');
|
||||
if ($isLocalDev) {
|
||||
$frameSrc .= " https: http:";
|
||||
} else {
|
||||
$frameSrc .= " https:";
|
||||
}
|
||||
$csp .= "frame-src " . $frameSrc . "; ";
|
||||
|
||||
// Connexions : autoriser Mastodon et PeerTube
|
||||
$connectSrc = "'self' https://plausible.io " . ($mastodonDomain ? $mastodonDomain : '') . " " . ($peertubeDomain ? $peertubeDomain : '');
|
||||
if ($isLocalDev) {
|
||||
$connectSrc .= " ws: wss:"; // WebSockets pour le dev
|
||||
}
|
||||
$csp .= "connect-src " . $connectSrc . "; ";
|
||||
|
||||
// Médias : toujours autoriser 'self', Mastodon et PeerTube
|
||||
$mediaSrc = "'self'";
|
||||
|
||||
// Ajouter l'instance Mastodon (pour les médias stockés sur l'instance)
|
||||
if ($mastodonDomain) {
|
||||
$mediaSrc .= " " . $mastodonDomain;
|
||||
}
|
||||
|
||||
// Ajouter PeerTube
|
||||
if ($peertubeDomain) {
|
||||
$mediaSrc .= " " . $peertubeDomain;
|
||||
}
|
||||
|
||||
// Ajouter l'URL S3 Mastodon si configurée (pour les médias externalisés)
|
||||
if (defined('MASTODON_S3_MEDIA_URL') && !empty(MASTODON_S3_MEDIA_URL)) {
|
||||
$s3Parsed = parse_url(MASTODON_S3_MEDIA_URL);
|
||||
if ($s3Parsed && isset($s3Parsed['host'])) {
|
||||
$s3Domain = $s3Parsed['scheme'] . '://' . $s3Parsed['host'];
|
||||
$mediaSrc .= " " . $s3Domain;
|
||||
}
|
||||
}
|
||||
|
||||
if ($isLocalDev) {
|
||||
$mediaSrc .= " https: http:";
|
||||
} else {
|
||||
$mediaSrc .= " https:";
|
||||
}
|
||||
$csp .= "media-src " . $mediaSrc . "; ";
|
||||
|
||||
$csp .= "script-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com https://plausible.io "
|
||||
. "{$fbScripts} {$xScripts} {$igScripts} {$ttScripts}; ";
|
||||
$csp .= "img-src 'self' data: {$ytImages} https://www.facebook.com https://pbs.twimg.com https://abs.twimg.com"
|
||||
. ($isLocalDev ? " https: http:" : " https:") . "; ";
|
||||
$csp .= "frame-src 'self' {$fbFrames} {$xFrames} {$igFrames} {$ttFrames} https://www.youtube.com"
|
||||
. ($isLocalDev ? " http:" : "") . "; ";
|
||||
$csp .= "connect-src 'self' https://plausible.io https://www.googleapis.com https://www.youtube.com "
|
||||
. "https://www.facebook.com https://graph.facebook.com https://connect.facebook.net "
|
||||
. "https://platform.twitter.com https://syndication.twitter.com https://cdn.syndication.twimg.com https://api.twitter.com "
|
||||
. "https://www.instagram.com https://www.tiktok.com"
|
||||
. ($isLocalDev ? " ws: wss:" : "") . "; ";
|
||||
$csp .= "media-src 'self' https:; ";
|
||||
$csp .= "object-src 'none'; ";
|
||||
$csp .= "base-uri 'self';";
|
||||
|
||||
|
||||
header('Content-Security-Policy: ' . $csp);
|
||||
|
||||
// HTTPS strict transport security (seulement si HTTPS)
|
||||
|
||||
if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') {
|
||||
header('Strict-Transport-Security: max-age=31536000; includeSubDomains');
|
||||
}
|
||||
|
||||
+33
-68
@@ -1,88 +1,53 @@
|
||||
<!-- Sidebar de navigation -->
|
||||
<nav class="sidebar" role="navigation" aria-label="Navigation principale">
|
||||
<a href="/" class="logo" aria-label="Retour à l'accueil">
|
||||
<img src="img/logo.png" alt="Logo kaubuntu.re">
|
||||
<img src="img/logo.png" alt="Logo <?php echo htmlspecialchars(SITE_NAME); ?>">
|
||||
</a>
|
||||
|
||||
<?php
|
||||
// Détecter la page courante et ses paramètres
|
||||
$currentPage = basename($_SERVER['PHP_SELF']);
|
||||
$currentCategoryId = isset($_GET['id']) ? intval($_GET['id']) : null;
|
||||
$currentQuery = isset($_GET['q']) ? trim($_GET['q']) : '';
|
||||
$isTagSearch = !empty($currentQuery) && substr($currentQuery, 0, 1) === '#';
|
||||
$currentTag = $isTagSearch ? substr($currentQuery, 1) : '';
|
||||
$isHome = ($currentPage === 'index.php' || $currentPage === '/');
|
||||
?>
|
||||
|
||||
<div class="sidebar-nav">
|
||||
<a href="/" class="nav-item <?php echo ($currentPage === 'index.php') ? 'active' : ''; ?>" data-title="Accueil" aria-current="<?php echo ($currentPage === 'index.php') ? 'page' : 'false'; ?>">
|
||||
<a href="/"
|
||||
class="nav-item <?php echo $isHome ? 'active' : ''; ?>"
|
||||
data-title="Accueil"
|
||||
aria-current="<?php echo $isHome ? 'page' : 'false'; ?>">
|
||||
<i class="fas fa-home" aria-hidden="true"></i> <span>Accueil</span>
|
||||
</a>
|
||||
<a href="direct.php" class="nav-item <?php echo ($currentPage === 'direct.php') ? 'active' : ''; ?>" data-title="Direct" aria-current="<?php echo ($currentPage === 'direct.php') ? 'page' : 'false'; ?>">
|
||||
<i class="fas fa-broadcast-tower" aria-hidden="true"></i> <span>Direct</span>
|
||||
|
||||
<div class="nav-divider"></div>
|
||||
|
||||
<a href="<?php echo $isHome ? '#youtube' : 'index.php#youtube'; ?>"
|
||||
class="nav-item"
|
||||
data-title="YouTube">
|
||||
<i class="fab fa-youtube icon-youtube" aria-hidden="true"></i> <span>YouTube</span>
|
||||
</a>
|
||||
<a href="<?php echo $isHome ? '#instagram' : 'index.php#instagram'; ?>"
|
||||
class="nav-item"
|
||||
data-title="Instagram">
|
||||
<i class="fab fa-instagram icon-instagram" aria-hidden="true"></i> <span>Instagram</span>
|
||||
</a>
|
||||
<a href="<?php echo $isHome ? '#tiktok' : 'index.php#tiktok'; ?>"
|
||||
class="nav-item"
|
||||
data-title="TikTok">
|
||||
<i class="fab fa-tiktok icon-tiktok" aria-hidden="true"></i> <span>TikTok</span>
|
||||
</a>
|
||||
<a href="<?php echo $isHome ? '#actualites' : 'index.php#actualites'; ?>"
|
||||
class="nav-item"
|
||||
data-title="Actualités">
|
||||
<i class="fas fa-newspaper" aria-hidden="true"></i> <span>Actualités</span>
|
||||
</a>
|
||||
|
||||
<?php if (defined('DONATIONS_ENABLED') && DONATIONS_ENABLED && !empty(PAYPAL_ME_URL)): ?>
|
||||
<a href="dons.php" class="nav-item donation-nav-link <?php echo ($currentPage === 'dons.php') ? 'active' : ''; ?>" data-title="Soutenir" aria-current="<?php echo ($currentPage === 'dons.php') ? 'page' : 'false'; ?>">
|
||||
<div class="nav-divider"></div>
|
||||
<a href="dons.php"
|
||||
class="nav-item donation-nav-link <?php echo ($currentPage === 'dons.php') ? 'active' : ''; ?>"
|
||||
data-title="Soutenir"
|
||||
aria-current="<?php echo ($currentPage === 'dons.php') ? 'page' : 'false'; ?>">
|
||||
<i class="fas fa-heart" aria-hidden="true"></i> <span>Soutenir</span>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="nav-divider"></div>
|
||||
|
||||
<?php
|
||||
// Afficher les catégories prioritaires
|
||||
if (defined('PRIORITY_CATEGORIES') && !empty(PRIORITY_CATEGORIES)) {
|
||||
// Tableau associatif des icônes pour les catégories
|
||||
$categoryIcons = [
|
||||
1 => 'fas fa-music', // Musique
|
||||
2 => 'fas fa-film', // Films
|
||||
3 => 'fas fa-car', // Véhicules
|
||||
4 => 'fas fa-palette', // Jeux
|
||||
5 => 'fas fa-running', // Sport
|
||||
6 => 'fas fa-laugh', // Humour
|
||||
7 => 'fas fa-gamepad', // Art
|
||||
8 => 'fas fa-person', // Personnalités
|
||||
9 => 'fas fa-face-grin-tears', // Comédie
|
||||
10 => 'fas fa-tv', // Divertissement
|
||||
11 => 'fas fa-globe', // Actualité & Politique
|
||||
12 => 'fas fa-chalkboard-user', // Tutoriel
|
||||
13 => 'fas fa-graduation-cap', // Education
|
||||
14 => 'fas fa-fist-raised', // Activisme
|
||||
15 => 'fas fa-microscope', // Science & Technologie
|
||||
16 => 'fas fa-paw', // Animaux
|
||||
17 => 'fas fa-child', // Enfants
|
||||
18 => 'fas fa-utensils' // Cuisine
|
||||
];
|
||||
|
||||
foreach (PRIORITY_CATEGORIES as $id => $name) {
|
||||
$isActive = ($currentPage === 'categories.php' && $currentCategoryId === $id);
|
||||
$icon = isset($categoryIcons[$id]) ? $categoryIcons[$id] : 'fas fa-folder';
|
||||
echo '<a href="categories.php?id=' . $id . '" class="nav-item ' . ($isActive ? 'active' : '') . '" data-title="' . htmlspecialchars($name) . '" aria-current="' . ($isActive ? 'page' : 'false') . '">';
|
||||
echo '<i class="' . $icon . '" aria-hidden="true"></i> <span>' . htmlspecialchars($name) . '</span>';
|
||||
echo '</a>';
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="nav-divider"></div>
|
||||
|
||||
<div class="category-title" role="heading" aria-level="2">
|
||||
<i class="fas fa-hashtag" aria-hidden="true"></i> <span>Hashtags</span>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
if (defined('IMPORTANT_TAGS') && !empty(IMPORTANT_TAGS)):
|
||||
foreach (IMPORTANT_TAGS as $tag):
|
||||
$encodedTag = urlencode('#' . $tag);
|
||||
$isActive = ($isTagSearch && strtolower($currentTag) === strtolower($tag));
|
||||
?>
|
||||
<a href="recherche.php?q=<?php echo $encodedTag; ?>" class="tag-item <?php echo $isActive ? 'active' : ''; ?>" data-title="<?php echo htmlspecialchars($tag); ?>" aria-current="<?php echo $isActive ? 'page' : 'false'; ?>">
|
||||
<i class="fas fa-hashtag tag-icon" aria-hidden="true"></i> <span><?php echo htmlspecialchars($tag); ?></span>
|
||||
</a>
|
||||
<?php
|
||||
endforeach;
|
||||
endif;
|
||||
?>
|
||||
</div>
|
||||
|
||||
</nav>
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
/**
|
||||
* Récupération des statistiques de followers pour chaque plateforme sociale.
|
||||
* Toutes les fonctions retournent null en cas d'échec — l'affichage reste optionnel.
|
||||
* Cache 24h pour limiter les appels externes.
|
||||
*/
|
||||
|
||||
function formatFollowerCount(int $n): string {
|
||||
if ($n >= 1_000_000) return number_format($n / 1_000_000, 1, '.', '') . 'M';
|
||||
if ($n >= 1_000) return number_format($n / 1_000, 1, '.', '') . 'K';
|
||||
return (string) $n;
|
||||
}
|
||||
|
||||
function _statsCurlFetch(string $url, array $headers = []): ?string {
|
||||
$ch = curl_init();
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 8,
|
||||
CURLOPT_CONNECTTIMEOUT => 5,
|
||||
CURLOPT_FOLLOWLOCATION => false,
|
||||
CURLOPT_SSL_VERIFYPEER => true,
|
||||
CURLOPT_SSL_VERIFYHOST => 2,
|
||||
CURLOPT_USERAGENT => 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/124.0 Safari/537.36',
|
||||
CURLOPT_HTTPHEADER => $headers,
|
||||
]);
|
||||
$body = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
return ($body && $httpCode === 200) ? $body : null;
|
||||
}
|
||||
|
||||
function getYouTubeSubscriberCount(): ?int {
|
||||
// Priority 1: YouTube Data API v3 (exact count)
|
||||
if (defined('YOUTUBE_API_KEY') && !empty(YOUTUBE_API_KEY)) {
|
||||
$channelId = getYouTubeChannelId();
|
||||
if ($channelId) {
|
||||
$cacheKey = 'stat_yt_' . $channelId;
|
||||
$cached = $GLOBALS['simple_api_cache']->get($cacheKey, []);
|
||||
if ($cached !== null) return (int) $cached ?: null;
|
||||
|
||||
$data = callYouTubeApi('channels', ['part' => 'statistics', 'id' => $channelId]);
|
||||
$count = isset($data['items'][0]['statistics']['subscriberCount'])
|
||||
? (int) $data['items'][0]['statistics']['subscriberCount']
|
||||
: null;
|
||||
|
||||
if ($count !== null) {
|
||||
$GLOBALS['simple_api_cache']->set($cacheKey, [], $count, 86400);
|
||||
return $count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Priority 2: parse ytInitialData from public channel page
|
||||
$handle = '';
|
||||
if (defined('YOUTUBE_HANDLE') && !empty(YOUTUBE_HANDLE)) {
|
||||
$handle = ltrim(YOUTUBE_HANDLE, '@');
|
||||
} elseif (defined('YOUTUBE_CHANNEL_HANDLE') && !empty(YOUTUBE_CHANNEL_HANDLE)) {
|
||||
$handle = ltrim(YOUTUBE_CHANNEL_HANDLE, '@');
|
||||
}
|
||||
|
||||
$pageUrl = $handle
|
||||
? 'https://www.youtube.com/@' . urlencode($handle)
|
||||
: (defined('YOUTUBE_CHANNEL_ID') && YOUTUBE_CHANNEL_ID
|
||||
? 'https://www.youtube.com/channel/' . urlencode(YOUTUBE_CHANNEL_ID)
|
||||
: null);
|
||||
|
||||
if (!$pageUrl) return null;
|
||||
|
||||
$cacheKey = 'stat_yt_page_' . preg_replace('/[^a-z0-9]/i', '_', $handle ?: YOUTUBE_CHANNEL_ID);
|
||||
$cached = $GLOBALS['simple_api_cache']->get($cacheKey, []);
|
||||
if ($cached !== null) return (int) $cached ?: null;
|
||||
|
||||
$body = _statsCurlFetch($pageUrl, ['Accept-Language: fr-FR,fr;q=0.9,en;q=0.8']);
|
||||
if (!$body) return null;
|
||||
|
||||
// ytInitialData embeds exact subscriberCount as a quoted integer string
|
||||
if (preg_match('/"subscriberCount"\s*:\s*"(\d+)"/', $body, $m)) {
|
||||
$count = (int) $m[1];
|
||||
$GLOBALS['simple_api_cache']->set($cacheKey, [], $count, 86400);
|
||||
return $count;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getInstagramFollowers(): ?int {
|
||||
if (!defined('INSTAGRAM_HANDLE') || empty(INSTAGRAM_HANDLE)) return null;
|
||||
|
||||
$handle = ltrim(INSTAGRAM_HANDLE, '@');
|
||||
$cacheKey = 'stat_ig_' . $handle;
|
||||
$cached = $GLOBALS['simple_api_cache']->get($cacheKey, []);
|
||||
if ($cached !== null) return (int) $cached ?: null;
|
||||
|
||||
$body = _statsCurlFetch(
|
||||
'https://www.instagram.com/api/v1/users/web_profile_info/?username=' . urlencode($handle),
|
||||
['X-IG-App-ID: 936619743392459', 'Accept: application/json']
|
||||
);
|
||||
if (!$body) return null;
|
||||
|
||||
$data = json_decode($body, true);
|
||||
$count = $data['data']['user']['edge_followed_by']['count'] ?? null;
|
||||
|
||||
if ($count !== null) {
|
||||
$count = (int) $count;
|
||||
$GLOBALS['simple_api_cache']->set($cacheKey, [], $count, 86400);
|
||||
}
|
||||
return $count;
|
||||
}
|
||||
|
||||
function getTikTokFollowers(): ?int {
|
||||
if (!defined('TIKTOK_HANDLE') || empty(TIKTOK_HANDLE)) return null;
|
||||
|
||||
$handle = ltrim(TIKTOK_HANDLE, '@');
|
||||
$cacheKey = 'stat_tt_' . $handle;
|
||||
$cached = $GLOBALS['simple_api_cache']->get($cacheKey, []);
|
||||
if ($cached !== null) return (int) $cached ?: null;
|
||||
|
||||
$body = _statsCurlFetch('https://www.tiktok.com/embed/@' . urlencode($handle));
|
||||
if (!$body) return null;
|
||||
|
||||
preg_match('/"followerCount"\s*:\s*(\d+)/', $body, $m);
|
||||
$count = isset($m[1]) ? (int) $m[1] : null;
|
||||
|
||||
if ($count !== null) {
|
||||
$GLOBALS['simple_api_cache']->set($cacheKey, [], $count, 86400);
|
||||
}
|
||||
return $count;
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
<?php
|
||||
/**
|
||||
* Intégration YouTube Data API v3
|
||||
*/
|
||||
|
||||
function callYouTubeApi(string $endpoint, array $params = []): array {
|
||||
if (!defined('YOUTUBE_API_KEY') || empty(YOUTUBE_API_KEY)) return [];
|
||||
|
||||
$params['key'] = YOUTUBE_API_KEY;
|
||||
$url = 'https://www.googleapis.com/youtube/v3/' . $endpoint . '?' . http_build_query($params);
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 15,
|
||||
CURLOPT_CONNECTTIMEOUT => 8,
|
||||
CURLOPT_FOLLOWLOCATION => false,
|
||||
CURLOPT_SSL_VERIFYPEER => true,
|
||||
CURLOPT_SSL_VERIFYHOST => 2,
|
||||
CURLOPT_USERAGENT => 'KaubuntuRe/2.0',
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$error = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($response === false || !empty($error) || $httpCode < 200 || $httpCode >= 300) {
|
||||
error_log('YouTube API error: ' . $error . ' (HTTP ' . $httpCode . ')');
|
||||
return [];
|
||||
}
|
||||
|
||||
return json_decode($response, true) ?: [];
|
||||
}
|
||||
|
||||
function getYouTubeChannelId(): string {
|
||||
if (defined('YOUTUBE_CHANNEL_ID') && !empty(YOUTUBE_CHANNEL_ID)) {
|
||||
return YOUTUBE_CHANNEL_ID;
|
||||
}
|
||||
|
||||
if (!defined('YOUTUBE_API_KEY') || empty(YOUTUBE_API_KEY)) return '';
|
||||
if (!defined('YOUTUBE_CHANNEL_HANDLE') || empty(YOUTUBE_CHANNEL_HANDLE)) return '';
|
||||
|
||||
$handle = ltrim(YOUTUBE_CHANNEL_HANDLE, '@');
|
||||
$cached = $GLOBALS['simple_api_cache']->get('yt_channel_id_' . $handle, []);
|
||||
if ($cached !== null) return (string) $cached;
|
||||
|
||||
$data = callYouTubeApi('channels', ['part' => 'snippet', 'forHandle' => $handle]);
|
||||
$channelId = $data['items'][0]['id'] ?? '';
|
||||
|
||||
if ($channelId) {
|
||||
$GLOBALS['simple_api_cache']->set('yt_channel_id_' . $handle, [], $channelId, 86400);
|
||||
}
|
||||
|
||||
return $channelId;
|
||||
}
|
||||
|
||||
function isYouTubeShort(string $isoDuration): bool {
|
||||
preg_match('/PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?/', $isoDuration, $m);
|
||||
$seconds = (int)($m[1] ?? 0) * 3600 + (int)($m[2] ?? 0) * 60 + (int)($m[3] ?? 0);
|
||||
return $seconds > 0 && $seconds <= 60;
|
||||
}
|
||||
|
||||
function getYouTubeLatestVideos(int $count = 6): array {
|
||||
// Priorité 1 : YouTube Data API v3
|
||||
if (defined('YOUTUBE_API_KEY') && !empty(YOUTUBE_API_KEY)) {
|
||||
$channelId = getYouTubeChannelId();
|
||||
if ($channelId) {
|
||||
$shortsTarget = min(3, $count - 1);
|
||||
$regularTarget = $count - $shortsTarget;
|
||||
|
||||
$cacheKey = 'yt_videos2_' . $channelId . '_' . $count;
|
||||
$cached = $GLOBALS['simple_api_cache']->get($cacheKey, []);
|
||||
if ($cached !== null) return $cached;
|
||||
|
||||
// On récupère 50 vidéos pour avoir suffisamment de chaque type
|
||||
$searchData = callYouTubeApi('search', [
|
||||
'part' => 'snippet',
|
||||
'channelId' => $channelId,
|
||||
'order' => 'date',
|
||||
'type' => 'video',
|
||||
'maxResults' => 50,
|
||||
]);
|
||||
|
||||
if (!empty($searchData['items'])) {
|
||||
$videoIds = array_map(fn($i) => $i['id']['videoId'], $searchData['items']);
|
||||
|
||||
// videos.list coûte 1 unité quota (vs 100 pour search)
|
||||
$detailData = callYouTubeApi('videos', [
|
||||
'part' => 'contentDetails',
|
||||
'id' => implode(',', $videoIds),
|
||||
]);
|
||||
|
||||
$durations = [];
|
||||
foreach ($detailData['items'] ?? [] as $item) {
|
||||
$durations[$item['id']] = $item['contentDetails']['duration'] ?? '';
|
||||
}
|
||||
|
||||
$shorts = [];
|
||||
$regular = [];
|
||||
|
||||
foreach ($searchData['items'] as $item) {
|
||||
$videoId = $item['id']['videoId'];
|
||||
$isShort = isset($durations[$videoId])
|
||||
? isYouTubeShort($durations[$videoId])
|
||||
: false;
|
||||
|
||||
$video = [
|
||||
'id' => $videoId,
|
||||
'title' => html_entity_decode($item['snippet']['title'], ENT_QUOTES | ENT_HTML5, 'UTF-8'),
|
||||
'thumbnail' => $item['snippet']['thumbnails']['high']['url']
|
||||
?? $item['snippet']['thumbnails']['default']['url'],
|
||||
'publishedAt' => $item['snippet']['publishedAt'],
|
||||
'url' => $isShort
|
||||
? 'https://www.youtube.com/shorts/' . $videoId
|
||||
: 'https://www.youtube.com/watch?v=' . $videoId,
|
||||
'isShort' => $isShort,
|
||||
];
|
||||
|
||||
if ($isShort && count($shorts) < $shortsTarget) {
|
||||
$shorts[] = $video;
|
||||
} elseif (!$isShort && count($regular) < $regularTarget) {
|
||||
$regular[] = $video;
|
||||
}
|
||||
|
||||
if (count($shorts) >= $shortsTarget && count($regular) >= $regularTarget) break;
|
||||
}
|
||||
|
||||
// Shorts en premier, puis vidéos normales
|
||||
$videos = array_merge($shorts, $regular);
|
||||
|
||||
if (!empty($videos)) {
|
||||
$ttl = defined('CACHE_DURATION') ? CACHE_DURATION : 900;
|
||||
$GLOBALS['simple_api_cache']->set($cacheKey, [], $videos, $ttl);
|
||||
return $videos;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Priorité 2 : flux RSS public YouTube (sans clé API, nécessite YOUTUBE_CHANNEL_ID)
|
||||
return getYouTubeVideosFromRSS($count);
|
||||
}
|
||||
|
||||
function getYouTubeVideosFromRSS(int $count = 6): array {
|
||||
if (!defined('YOUTUBE_CHANNEL_ID') || empty(YOUTUBE_CHANNEL_ID)) return [];
|
||||
|
||||
$cacheKey = 'yt_rss_' . YOUTUBE_CHANNEL_ID . '_' . $count;
|
||||
$cached = $GLOBALS['simple_api_cache']->get($cacheKey, []);
|
||||
if ($cached !== null) return $cached;
|
||||
|
||||
$feedUrl = 'https://www.youtube.com/feeds/videos.xml?channel_id=' . urlencode(YOUTUBE_CHANNEL_ID);
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => $feedUrl,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 10,
|
||||
CURLOPT_CONNECTTIMEOUT => 5,
|
||||
CURLOPT_FOLLOWLOCATION => false,
|
||||
CURLOPT_SSL_VERIFYPEER => true,
|
||||
CURLOPT_SSL_VERIFYHOST => 2,
|
||||
CURLOPT_USERAGENT => 'KaubuntuRe/2.0',
|
||||
]);
|
||||
$xmlString = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if (!$xmlString || $httpCode !== 200) {
|
||||
error_log('YouTube RSS error: HTTP ' . $httpCode);
|
||||
return [];
|
||||
}
|
||||
|
||||
$xml = @simplexml_load_string($xmlString);
|
||||
if (!$xml) return [];
|
||||
|
||||
$videos = [];
|
||||
$ytNs = 'http://www.youtube.com/xml/schemas/2015';
|
||||
|
||||
foreach ($xml->entry as $entry) {
|
||||
if (count($videos) >= $count) break;
|
||||
$yt = $entry->children($ytNs);
|
||||
$videoId = (string) $yt->videoId;
|
||||
if (!$videoId) continue;
|
||||
|
||||
$videos[] = [
|
||||
'id' => $videoId,
|
||||
'title' => html_entity_decode((string) $entry->title, ENT_QUOTES | ENT_HTML5, 'UTF-8'),
|
||||
'thumbnail' => "https://i.ytimg.com/vi/{$videoId}/hqdefault.jpg",
|
||||
'publishedAt' => (string) $entry->published,
|
||||
'url' => "https://www.youtube.com/watch?v={$videoId}",
|
||||
'isShort' => false,
|
||||
];
|
||||
}
|
||||
|
||||
if (!empty($videos)) {
|
||||
$ttl = defined('CACHE_DURATION') ? CACHE_DURATION : 3600;
|
||||
$GLOBALS['simple_api_cache']->set($cacheKey, [], $videos, $ttl);
|
||||
}
|
||||
|
||||
return $videos;
|
||||
}
|
||||
+41
-1
@@ -111,6 +111,46 @@ function isValidWordPressUrl($url) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les articles WordPress récents
|
||||
*
|
||||
@@ -157,7 +197,7 @@ function formatWordPressPosts($posts) {
|
||||
// Nettoyer l'extrait HTML
|
||||
$excerpt = '';
|
||||
if (isset($post['excerpt']['rendered'])) {
|
||||
$excerpt = wp_strip_all_tags($post['excerpt']['rendered']);
|
||||
$excerpt = html_entity_decode(wp_strip_all_tags($post['excerpt']['rendered']), ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||
$excerpt = trim($excerpt);
|
||||
// Limiter à 150 caractères
|
||||
if (strlen($excerpt) > 150) {
|
||||
|
||||
Reference in New Issue
Block a user