diff --git a/includes/structured-data.php b/includes/structured-data.php new file mode 100644 index 0000000..fc1d7d8 --- /dev/null +++ b/includes/structured-data.php @@ -0,0 +1,319 @@ + "https://schema.org", + "@type" => "WebSite", + "name" => "kaubuntu.re", + "description" => "Plateforme multimédia indépendante du parti panifricaniste et indépendantiste réunionnais Ka-Ubuntu", + "url" => $baseUrl, + "potentialAction" => [ + "@type" => "SearchAction", + "target" => [ + "@type" => "EntryPoint", + "urlTemplate" => $baseUrl . "/recherche.php?q={search_term_string}" + ], + "query-input" => "required name=search_term_string" + ], + "publisher" => [ + "@type" => "Organization", + "name" => "Ka-Ubuntu", + "url" => $baseUrl, + "logo" => [ + "@type" => "ImageObject", + "url" => $baseUrl . "/img/logo.png" + ] + ] + ]; + + return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); +} + +/** + * Génère le JSON-LD pour un objet VideoObject + * + * @param array $videoData Données de la vidéo depuis l'API PeerTube + * @param array $video Données formatées de la vidéo + * @return string JSON-LD pour la vidéo + */ +function generateVideoObjectJsonLd($videoData, $video) { + $baseUrl = getBaseUrl(); + $videoUrl = $baseUrl . "/video.php?id=" . $video['id']; + + // Construire l'URL de la vignette + $thumbnailUrl = isset($videoData['thumbnailPath']) + ? PEERTUBE_URL . $videoData['thumbnailPath'] + : $baseUrl . "/img/default-thumbnail.jpg"; + + // Formater la durée en format ISO 8601 (PT1H30M pour 1h30min) + $duration = formatDurationISO8601($video['duration'] ?? 0); + + // Construire les données de base + $data = [ + "@context" => "https://schema.org", + "@type" => "VideoObject", + "name" => $video['title'], + "description" => !empty($video['description']) + ? truncateText(strip_tags($video['description']), 300) + : "Regardez cette vidéo sur kaubuntu.re", + "url" => $videoUrl, + "thumbnailUrl" => $thumbnailUrl, + "uploadDate" => formatDateISO8601($video['date']), + "duration" => $duration, + "publisher" => [ + "@type" => "Organization", + "name" => "kaubuntu.re", + "url" => $baseUrl, + "logo" => [ + "@type" => "ImageObject", + "url" => $baseUrl . "/img/logo.png" + ] + ] + ]; + + // Ajouter les informations de la chaîne/créateur + if (!empty($video['channel'])) { + $data["creator"] = [ + "@type" => "Person", + "name" => $video['channel'], + "url" => PEERTUBE_URL . "/c/" . ($videoData['channel']['name'] ?? '') + ]; + } + + // Ajouter les statistiques d'interaction + if (isset($video['views']) && $video['views'] > 0) { + $data["interactionStatistic"] = [ + "@type" => "InteractionCounter", + "interactionType" => [ + "@type" => "WatchAction" + ], + "userInteractionCount" => $video['views'] + ]; + } + + // Ajouter les likes si disponibles + if (isset($video['likes']) && $video['likes'] > 0) { + if (!isset($data["interactionStatistic"])) { + $data["interactionStatistic"] = []; + } else { + // Convertir en array si c'était un seul élément + if (isset($data["interactionStatistic"]["@type"])) { + $data["interactionStatistic"] = [$data["interactionStatistic"]]; + } + } + + $data["interactionStatistic"][] = [ + "@type" => "InteractionCounter", + "interactionType" => [ + "@type" => "LikeAction" + ], + "userInteractionCount" => $video['likes'] + ]; + } + + // Ajouter les tags/mots-clés si disponibles + if (!empty($video['tags'])) { + $data["keywords"] = implode(", ", $video['tags']); + } + + // Ajouter les informations de licence si disponibles + if (isset($videoData['licence']) && !empty($videoData['licence'])) { + $licenceId = $videoData['licence']['id']; + $licenceMapping = [ + 1 => "https://creativecommons.org/licenses/by/4.0/", + 2 => "https://creativecommons.org/licenses/by-sa/4.0/", + 3 => "https://creativecommons.org/licenses/by-nd/4.0/", + 4 => "https://creativecommons.org/licenses/by-nc/4.0/", + 5 => "https://creativecommons.org/licenses/by-nc-sa/4.0/", + 6 => "https://creativecommons.org/licenses/by-nc-nd/4.0/", + 7 => "https://creativecommons.org/publicdomain/zero/1.0/" + ]; + + if (isset($licenceMapping[$licenceId])) { + $data["license"] = $licenceMapping[$licenceId]; + } + } + + // Ajouter la catégorie si disponible + if (isset($videoData['category']) && !empty($videoData['category'])) { + $data["genre"] = $videoData['category']['label'] ?? 'Vidéo'; + } + + // Ajouter les dimensions si c'est un short (format portrait) + if (isset($video['aspectRatio']) && $video['aspectRatio'] <= 1) { + $data["videoFrameSize"] = "Portrait"; + } + + return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); +} + +/** + * Génère le JSON-LD pour les fils d'Ariane (BreadcrumbList) + * + * @param array $breadcrumbs Tableau des fils d'Ariane [['name' => 'Nom', 'url' => 'URL']] + * @return string JSON-LD pour les fils d'Ariane + */ +function generateBreadcrumbJsonLd($breadcrumbs) { + $baseUrl = getBaseUrl(); + + $listItems = []; + foreach ($breadcrumbs as $index => $crumb) { + $listItems[] = [ + "@type" => "ListItem", + "position" => $index + 1, + "name" => $crumb['name'], + "item" => $crumb['url'] + ]; + } + + $data = [ + "@context" => "https://schema.org", + "@type" => "BreadcrumbList", + "itemListElement" => $listItems + ]; + + return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); +} + +/** + * Génère le JSON-LD pour une page de collection de vidéos + * + * @param string $name Nom de la collection + * @param string $description Description de la collection + * @param array $videos Tableau des vidéos + * @param string $url URL de la page de collection + * @return string JSON-LD pour la collection + */ +function generateVideoCollectionJsonLd($name, $description, $videos, $url) { + $baseUrl = getBaseUrl(); + + $videoItems = []; + foreach ($videos as $video) { + $videoItems[] = [ + "@type" => "VideoObject", + "name" => $video['title'], + "url" => $baseUrl . "/video.php?id=" . $video['id'], + "thumbnailUrl" => $video['thumbnail'], + "uploadDate" => formatDateISO8601($video['date']), + "duration" => formatDurationISO8601($video['duration'] ?? 0) + ]; + } + + $data = [ + "@context" => "https://schema.org", + "@type" => "CollectionPage", + "name" => $name, + "description" => $description, + "url" => $url, + "mainEntity" => [ + "@type" => "ItemList", + "itemListElement" => $videoItems, + "numberOfItems" => count($videos) + ], + "publisher" => [ + "@type" => "Organization", + "name" => "kaubuntu.re", + "url" => $baseUrl + ] + ]; + + return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); +} + +/** + * Formate une durée en secondes au format ISO 8601 (PTnHnMnS) + * + * @param int $seconds Durée en secondes + * @return string Durée au format ISO 8601 + */ +function formatDurationISO8601($seconds) { + $hours = floor($seconds / 3600); + $minutes = floor(($seconds % 3600) / 60); + $remainingSeconds = $seconds % 60; + + $duration = 'PT'; + + if ($hours > 0) { + $duration .= $hours . 'H'; + } + if ($minutes > 0) { + $duration .= $minutes . 'M'; + } + if ($remainingSeconds > 0 || ($hours === 0 && $minutes === 0)) { + $duration .= $remainingSeconds . 'S'; + } + + return $duration; +} + +/** + * Formate une date au format ISO 8601 + * + * @param string $dateString Date à formater + * @return string Date au format ISO 8601 + */ +function formatDateISO8601($dateString) { + try { + $date = new DateTime($dateString); + return $date->format('c'); // Format ISO 8601 + } catch (Exception $e) { + // En cas d'erreur, retourner la date actuelle + return (new DateTime())->format('c'); + } +} + +/** + * Tronque un texte à une longueur donnée + * + * @param string $text Texte à tronquer + * @param int $length Longueur maximale + * @return string Texte tronqué + */ +function truncateText($text, $length = 200) { + if (strlen($text) <= $length) { + return $text; + } + + $truncated = substr($text, 0, $length); + $lastSpace = strrpos($truncated, ' '); + + if ($lastSpace !== false) { + $truncated = substr($truncated, 0, $lastSpace); + } + + return $truncated . '...'; +} + +/** + * Obtient l'URL de base du site + * + * @return string URL de base + */ +function getBaseUrl() { + $scheme = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') ? 'https' : 'http'; + $host = $_SERVER['HTTP_HOST'] ?? 'localhost'; + return $scheme . '://' . $host; +} + +/** + * Génère et affiche un script JSON-LD + * + * @param string $jsonLd Données JSON-LD + */ +function outputJsonLd($jsonLd) { + echo '' . "\n"; +} + +?> diff --git a/index.php b/index.php index 6c0d74c..b27ccd5 100644 --- a/index.php +++ b/index.php @@ -1,6 +1,8 @@ @@ -46,6 +48,19 @@ setSecurityHeaders(); + + + 'Accueil', 'url' => getBaseUrl()] + ]; + $breadcrumbJsonLd = generateBreadcrumbJsonLd($breadcrumbs); + outputJsonLd($breadcrumbJsonLd); + ?> diff --git a/recherche.php b/recherche.php index 26c389e..127b2ec 100644 --- a/recherche.php +++ b/recherche.php @@ -1,6 +1,8 @@ 0) { "> + + + + 'Accueil', 'url' => getBaseUrl()], + ['name' => 'Recherche', 'url' => getBaseUrl() . '/recherche.php'], + ['name' => $searchTitle, 'url' => $searchUrl] + ]; + $breadcrumbJsonLd = generateBreadcrumbJsonLd($breadcrumbs); + outputJsonLd($breadcrumbJsonLd); + ?> + diff --git a/video.php b/video.php index 4912249..6fcd0c3 100644 --- a/video.php +++ b/video.php @@ -3,6 +3,8 @@ require_once 'includes/config.php'; // Inclure le convertisseur Markdown require_once 'includes/lib/markdown.php'; +// Inclure les fonctions de données structurées +require_once 'includes/structured-data.php'; // Appliquer les en-têtes de sécurité setSecurityHeaders(); @@ -139,6 +141,22 @@ if (empty($videoData) || isset($videoData['error'])) { + + + + 'Accueil', 'url' => getBaseUrl()], + ['name' => $video['title'], 'url' => getBaseUrl() . '/video.php?id=' . $video['id']] + ]; + $breadcrumbJsonLd = generateBreadcrumbJsonLd($breadcrumbs); + outputJsonLd($breadcrumbJsonLd); + ?> +