-
![Miniature de la vidéo: <?php echo htmlspecialchars($video['title']); ?>](<?php echo $video['thumbnail']; ?>)
+
@@ -474,38 +474,7 @@ if (defined('CASTOPOD_ENABLED') && CASTOPOD_ENABLED && defined('CASTOPOD_URL') &
echo '
Aucune vidéo disponible pour le moment
';
} else {
foreach ($recentVideos as $video):
- ?>
-
-
-
![Miniature de la vidéo: <?php echo htmlspecialchars($video['title']); ?>](<?php echo $video['thumbnail']; ?>)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
![<?php echo $video['channel']; ?>](<?php echo $video['channelAvatar']; ?>)
-
-
-
-
-
- vues
-
-
-
-
-
-
@@ -536,36 +505,7 @@ if (defined('CASTOPOD_ENABLED') && CASTOPOD_ENABLED && defined('CASTOPOD_URL') &
echo '
Aucune vidéo disponible pour le moment
';
} else {
foreach ($trendingVideos as $video):
- ?>
-
-
-
![<?php echo htmlspecialchars($video['title']); ?>](<?php echo htmlspecialchars($video['thumbnail']); ?>)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
![<?php echo $video['channel']; ?>](<?php echo $video['channelAvatar']; ?>)
-
-
-
-
-
- vues
-
-
-
-
-
-
@@ -586,45 +526,18 @@ if (defined('CASTOPOD_ENABLED') && CASTOPOD_ENABLED && defined('CASTOPOD_URL') &
foreach ($displayCategories as $category):
if (!empty($category['videos'])):
?>
-
-
+
+
-
-
-
![<?php echo htmlspecialchars($video['title']); ?>](<?php echo htmlspecialchars($video['thumbnail']); ?>)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
![<?php echo $video['channel']; ?>](<?php echo $video['channelAvatar']; ?>)
-
-
-
-
-
- vues
-
-
-
-
-
+
@@ -707,7 +620,7 @@ if (defined('CASTOPOD_ENABLED') && CASTOPOD_ENABLED && defined('CASTOPOD_URL') &
// Afficher le bouton d'installation s'il existe
if (installButton) {
- installButton.style.display = 'block';
+ installButton.classList.remove('is-hidden');
installButton.addEventListener('click', function() {
deferredPrompt.prompt();
deferredPrompt.userChoice.then(function(choiceResult) {
@@ -715,7 +628,7 @@ if (defined('CASTOPOD_ENABLED') && CASTOPOD_ENABLED && defined('CASTOPOD_URL') &
console.log('PWA installée');
}
deferredPrompt = null;
- installButton.style.display = 'none';
+ installButton.classList.add('is-hidden');
});
});
}
@@ -725,7 +638,7 @@ if (defined('CASTOPOD_ENABLED') && CASTOPOD_ENABLED && defined('CASTOPOD_URL') &
window.addEventListener('appinstalled', function() {
console.log('PWA installée avec succès');
if (installButton) {
- installButton.style.display = 'none';
+ installButton.classList.add('is-hidden');
}
});
diff --git a/recherche.php b/recherche.php
index befb666..7613739 100644
--- a/recherche.php
+++ b/recherche.php
@@ -74,8 +74,8 @@ if ($resultsCount > 0) {
-
-
+
+
@@ -84,7 +84,7 @@ if ($resultsCount > 0) {
-
+
@@ -160,34 +160,7 @@ if ($resultsCount > 0) {
-
-
-
![<?php echo htmlspecialchars($video['title']); ?>](<?php echo $video['thumbnail']; ?>)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
![<?php echo htmlspecialchars($video['channel']); ?>](<?php echo $video['channelAvatar']; ?>)
-
-
-
-
-
- vues
-
-
-
-
-
+
diff --git a/tests/php/video-card-test.php b/tests/php/video-card-test.php
new file mode 100644
index 0000000..be596b8
--- /dev/null
+++ b/tests/php/video-card-test.php
@@ -0,0 +1,123 @@
+alert(1)'),
+ 'e() échappe les balises HTML'
+);
+assertEquals(
+ '"guillemets" 'apostrophes'',
+ e('"guillemets" \'apostrophes\''),
+ 'e() échappe guillemets et apostrophes (ENT_QUOTES)'
+);
+assertEquals('&', e('&'), 'e() échappe l\'esperluette');
+assertEquals('', e(null), 'e() convertit null en chaîne vide');
+assertEquals('42', e(42), 'e() convertit les nombres en chaîne');
+
+// --- renderVideoCard : structure de base -------------------------------------
+
+$video = [
+ 'id' => 'abc-123',
+ 'title' => 'Ma vidéo',
+ 'thumbnail' => 'https://videos.example/lazy/abc.jpg',
+ 'duration' => 125,
+ 'channel' => 'Ma chaîne',
+ 'channelAvatar' => 'https://videos.example/avatars/a.png',
+ 'views' => 42,
+ 'date' => date('Y-m-d H:i:s'),
+];
+
+$html = renderVideoCard($video);
+
+assertContains('class="video-card"', $html, 'renderVideoCard génère une carte vidéo');
+assertContains('data-video-id="abc-123"', $html, 'renderVideoCard expose l\'identifiant vidéo');
+assertContains('src="https://videos.example/lazy/abc.jpg"', $html, 'renderVideoCard affiche la vignette');
+assertContains('Ma vidéo
', $html, 'renderVideoCard affiche le titre');
+assertContains('Ma chaîne', $html, 'renderVideoCard affiche la chaîne');
+assertContains('class="channel-avatar"', $html, 'renderVideoCard affiche l\'avatar personnalisé');
+assertContains('2:05', $html, 'renderVideoCard affiche la durée formatée');
+
+// --- renderVideoCard : avatar par défaut --------------------------------------
+
+$videoDefaultAvatar = $video;
+$videoDefaultAvatar['channelAvatar'] = 'img/default-avatar.png';
+$htmlDefault = renderVideoCard($videoDefaultAvatar);
+
+assertContains('channel-avatar-placeholder', $htmlDefault, 'renderVideoCard utilise un placeholder pour l\'avatar par défaut');
+assertNotContains('class="channel-avatar"', $htmlDefault, 'renderVideoCard n\'affiche pas l\'image d\'avatar par défaut');
+
+$videoEmptyAvatar = $video;
+$videoEmptyAvatar['channelAvatar'] = '';
+assertContains(
+ 'channel-avatar-placeholder',
+ renderVideoCard($videoEmptyAvatar),
+ 'renderVideoCard utilise un placeholder si l\'avatar est vide'
+);
+
+// --- renderVideoCard : échappement XSS (SEC-1) ---------------------------------
+
+$maliciousTitle = '">
';
+$maliciousChannel = '