33 Commits
Author SHA1 Message Date
cedric 9109068b0b chore(sw): bump cache version to 26072026-1831 2026-07-26 18:31:36 +04:00
cedric 0d45b716fb fix: view more button on category page 2026-07-26 18:19:53 +04:00
cedric b12d66195d docs(readme): align CI checks description with workflows 2026-07-26 12:22:31 +04:00
cedric 050d583b6b ci: keep only PHP/JS lint in CI, move rest to local checks 2026-07-25 22:33:01 +04:00
cedric bc64981023 docs(deploy): clarify CI SSH key transfer to server 2026-07-25 22:01:10 +04:00
cedric 3620010212 feat: add ko-fi link 2026-07-25 21:52:29 +04:00
cedric aa86bf65e4 feat(donations): clickable OKI links in donation disclaimer 2026-07-25 21:52:29 +04:00
cedric e673b484ce feat(donations): default OKI links and transparency notice 2026-07-25 21:35:33 +04:00
cedric 5457f6fae0 style: feature trending hashtags card with accent bar and icon 2026-07-25 18:45:11 +04:00
cedric 33646f8d19 fix: make search results count visible in dark mode 2026-07-25 18:09:07 +04:00
cedric acd3a44c55 feat: add decorative emoji row above trending hashtags 2026-07-25 17:59:39 +04:00
cedric 609677f679 Revert "feat: add decorative emoji row above footer hashtags"
This reverts commit e5c7db1355.
2026-07-25 17:57:54 +04:00
cedric e5c7db1355 feat: add decorative emoji row above footer hashtags 2026-07-25 17:56:16 +04:00
cedric f04b3f5e64 docs: add git permission and 502 troubleshooting to deploy guide 2026-07-25 17:42:17 +04:00
cedric 9eddf5d9cd fix: prevent infinite redirect loop in nginx php block 2026-07-25 17:42:12 +04:00
cedric 9449f7e149 fix: restructure nginx sample for pre-certbot deployment 2026-07-25 17:31:53 +04:00
cedric dc217e77f1 fix: add fix for deploying 2026-07-25 17:05:05 +04:00
cedric 4a4b56296e feat: accept any .adoc file in PDF generation script 2026-07-25 16:51:05 +04:00
cedric 84013376f2 docs: prioritize nginx, fix sample config and use certbot snap 2026-07-25 16:44:24 +04:00
cedric dafb5e5753 fix: architecture schema in DEPLOY 2026-07-25 16:33:37 +04:00
cedric 4114b9e8e3 feat: limit default categories and hashtags to podcast scope 2026-07-25 15:19:36 +04:00
cedric 521ad8c7f1 feat: improve SEO with podcast JSON-LD, canonical and meta descriptions 2026-07-25 15:12:43 +04:00
cedric 576a72a0e1 fix: enable cache by default and skip caching empty castopod results 2026-07-24 20:55:35 +04:00
cedric 9cf7487f24 fix: shellcheck SC2164 in scripts/check.sh 2026-07-24 20:31:35 +04:00
cedric 681683c059 feat: add Gitea Actions CI/CD, local checks and deploy guide 2026-07-24 20:19:02 +04:00
cedric 7e8f078b60 docs: use generic example.com domain in samples and docs 2026-07-24 19:54:00 +04:00
cedric 7f1bdaa2ab feat: add styled PWA update modal with auto cache purge 2026-07-24 19:35:13 +04:00
cedric f760dfb72c feat: apply ANNU KUTE CED branding to icons and favicons 2026-07-23 22:09:40 +04:00
cedric 4e180c0248 config: default HERO_TYPE = video 2026-07-23 06:56:36 +04:00
cedric cc6580ccbb config: change default HERO_VIDEO_ID 2026-07-23 06:56:06 +04:00
cedric 5b1a78782a docs: add one-command README HTML/PDF export 2026-07-23 05:48:43 +04:00
cedric 3d28d3832f docs: rewrite README as bilingual FR/EN reference 2026-07-23 05:10:04 +04:00
cedric eaacd23702 docs: rebrand fork for ANNU KUTE CED podcast hub 2026-07-23 04:17:49 +04:00
41 changed files with 3393 additions and 980 deletions
+38
View File
@@ -0,0 +1,38 @@
name: Vérification PR
run-name: Vérification PR de ${{ gitea.actor }}
on:
pull_request:
branches:
- main
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
extensions: curl, intl, mbstring, xml
- name: Lint PHP (tous les fichiers, samples inclus)
run: |
fail=0
while IFS= read -r f; do
php -l "$f" > /dev/null || { echo "::error file=$f::Erreur de syntaxe PHP"; fail=1; }
done < <(find . -path ./.git -prune -o \( -name '*.php' -o -name '*.php.sample' \) -print)
[ "$fail" -eq 0 ] && echo "PHP lint OK" || exit 1
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Lint JS (sw.js et js/*.js)
run: |
for f in sw.js js/*.js; do
node --check "$f" || exit 1
done
echo "JS lint OK"
+65
View File
@@ -0,0 +1,65 @@
name: Déploiement PROD
run-name: ${{ gitea.actor }} déploie en PROD
on:
push:
branches:
- main
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
extensions: curl, intl, mbstring, xml
- name: Lint PHP (tous les fichiers, samples inclus)
run: |
fail=0
while IFS= read -r f; do
php -l "$f" > /dev/null || { echo "::error file=$f::Erreur de syntaxe PHP"; fail=1; }
done < <(find . -path ./.git -prune -o \( -name '*.php' -o -name '*.php.sample' \) -print)
[ "$fail" -eq 0 ] && echo "PHP lint OK" || exit 1
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Lint JS (sw.js et js/*.js)
run: |
for f in sw.js js/*.js; do
node --check "$f" || exit 1
done
echo "JS lint OK"
deploy:
needs: check
runs-on: ubuntu-latest
steps:
- name: Déployer sur le serveur
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_KEY }}
script: |
set -e
cd ${{ secrets.PROD_DEPLOY_PATH }}
# Annuler le bump de version du déploiement précédent pour
# garantir le fast-forward, puis récupérer la dernière version.
git checkout -- sw.js
git pull --ff-only origin main
# Bumper la version des caches du Service Worker : chaque
# déploiement déclenche le modal de mise à jour PWA chez les
# visiteurs (voir js/pwa-update.js). Ce bump n'est pas commité.
VERSION=$(date +%d%m%Y-%H%M)
sed -i "s/annu-kute-ced-static-[0-9]\{8\}-[0-9]\{4\}/annu-kute-ced-static-$VERSION/" sw.js
sed -i "s/annu-kute-ced-dynamic-[0-9]\{8\}-[0-9]\{4\}/annu-kute-ced-dynamic-$VERSION/" sw.js
echo "Version des caches bumpée : $VERSION"
+4
View File
@@ -25,6 +25,10 @@ temp/
# Fichiers de cache # Fichiers de cache
cache/ cache/
# Artefacts de documentation générés (docs/generate-readme-pdf.sh)
/*.html
/*.pdf
# Dossier pour les images d'annonces (tout ignorer sauf .gitkeep) # Dossier pour les images d'annonces (tout ignorer sauf .gitkeep)
uploads/* uploads/*
!uploads/.gitkeep !uploads/.gitkeep
+362
View File
@@ -0,0 +1,362 @@
= 🚀 DEPLOY — Mise en production et CI/CD
:toc: left
:toc-title: Sommaire
:toclevels: 3
Ce document décrit la mise en production complète d'*ANNU KUTE CED* sur un serveur fraîchement installé, puis l'activation du déploiement continu via *Gitea Actions*.
Il est complémentaire au link:README.adoc[README] (installation manuelle, configuration de l'application).
== 🧭 Vue d'ensemble
L'architecture retenue (identique à celle de pawol.nu) :
[source]
----
┌─────────────┐ push main ┌──────────────────┐ SSH (git pull) ┌─────────────┐
│ Dépôt git │ ─────────────▶│ Gitea Actions │ ─────────────────▶│ Serveur │
│ (LaBola) │ │ check → deploy │ │ production │
└─────────────┘ └──────────────────┘ └─────────────┘
----
. *Vérification* (`check-pr.yml` + job `check` de `deploy-prod.yml`) : lint PHP/JS. Les validations AsciiDoc, JSON, XML et shellcheck ne sont plus exécutées dans le CI : elles doivent être passées en local avec `scripts/check.sh`.
. *Déploiement* (`deploy-prod.yml`) : le runner se connecte en SSH au serveur, qui tient *un clone du dépôt*, fait `git pull --ff-only`, puis *bumpe la version des caches* du Service Worker (`sw.js`) pour déclencher le modal de mise à jour PWA chez les visiteurs.
Pourquoi un `git pull` sur le serveur plutôt qu'un rsync : tous les fichiers propres à l'instance (`config.local.php`, `.htaccess`, `sitemap.xml`, `robots.txt`, `site.webmanifest`, `mentions-legales.php`, `dons.php`, `uploads/`, `cache/`) sont ignorés par Git — un pull ne les écrase jamais. Le déploiement est ainsi sans risque pour la configuration de production.
== 📋 Prérequis
- Un serveur *Debian 12* ou *Ubuntu 24.04* fraîchement installé, avec accès `root` (ou `sudo`)
- Un nom de domaine dont le *DNS pointe vers le serveur* (enregistrement A/AAAA)
- Le dépôt Gitea : `git@labola.o-k-i.net:cedric/annu-kute-ced.git`
- Un *runner Gitea Actions* opérationnel sur l'instance LaBola (déjà le cas pour pawol.nu)
NOTE: Sur un hébergement mutualisé (pas d'accès root), adaptez : les fichiers sont déployés dans le docroot fourni par l'hébergeur, et la clé SSH s'ajoute via le panneau de contrôle (o2switch : *Clés SSH* dans cPanel).
== 1️⃣ Installation des paquets
[source,bash]
----
# Nginx + PHP-FPM (choix recommandé ; Apache possible, voir §5 option B)
apt update && apt upgrade -y
apt install -y nginx php-fpm \
php-curl php-intl php-mbstring php-xml \
git curl unzip
# Vérifier les extensions requises
php -m | grep -E 'curl|intl|mbstring|SimpleXML|json'
----
== 2️⃣ Utilisateur de déploiement
Le runner CI se connectera en SSH avec cet utilisateur. Ne *pas* utiliser root.
[source,bash]
----
adduser --disabled-password --gecos "Deploy annu-kute-ced" deploy
usermod -aG www-data deploy
# Répertoire de l'application
mkdir -p /var/www/annu-kute-ced
chown -R deploy:www-data /var/www/annu-kute-ced
# Répertoire SSH de l'utilisateur deploy (préparé pour les clés publiques)
mkdir -p /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
chown -R deploy:deploy /home/deploy/.ssh
----
== 3️⃣ Clone du dépôt
Le serveur de production tient un clone du dépôt. Le runner y exécutera `git pull` à chaque déploiement.
[source,bash]
----
# Clé SSH du serveur pour lire le dépôt (deploy key Gitea, lecture seule)
sudo -u deploy ssh-keygen -t ed25519 -f /home/deploy/.ssh/id_ed25519 -N ""
cat /home/deploy/.ssh/id_ed25519.pub
----
. Sur LaBola : *Settings du dépôt → Deploy Keys → Add deploy key* (coller la clé publique, lecture seule suffit).
. Puis :
+
[source,bash]
----
sudo -u deploy git clone git@labola.o-k-i.net:cedric/annu-kute-ced.git /var/www/annu-kute-ced
cd /var/www/annu-kute-ced
sudo -u deploy git config pull.ff only # sécurité : refuser tout pull non fast-forward
----
== 4️⃣ Fichiers d'instance
Ces fichiers sont ignorés par Git : ils vivent *uniquement sur le serveur* et ne seront jamais écrasés par les déploiements.
[source,bash]
----
cd /var/www/annu-kute-ced
sudo -u deploy cp includes/config.local.php.sample includes/config.local.php
sudo -u deploy cp site.webmanifest.sample site.webmanifest
sudo -u deploy cp robots.txt.sample robots.txt
sudo -u deploy cp sitemap.xml.sample sitemap.xml
sudo -u deploy cp mentions-legales.php.sample mentions-legales.php
# Uniquement si vous utilisez Apache (option B de l'étape 5) :
# sudo -u deploy cp conf/.htaccess.sample .htaccess
# Facultatif (page de dons) :
# sudo -u deploy cp dons.php.sample dons.php
----
Éditer ensuite les fichiers copiés :
- `includes/config.local.php` : `APP_HOST_NAME`, sources (PeerTube/Castopod/Mastodon), `CACHE_ENABLED=true` (recommandé pour Castopod), etc. — voir la link:README.adoc#fr-configuration[référence de configuration]
- `sitemap.xml`, `robots.txt`, `site.webmanifest` : remplacer `example.com` par le domaine réel
- `mentions-legales.php` : remplacer le placeholder `VOTRE-DATE-MAJ`
[source,bash]
----
# Le cache de l'API doit être accessible en écriture par PHP
mkdir -p /var/www/annu-kute-ced/cache
chown -R www-data:www-data /var/www/annu-kute-ced/cache
----
== 5️⃣ VirtualHost
=== Option A — Nginx + PHP-FPM (recommandée, configuration fournie)
Le fichier `conf/nginx.conf.sample` est l'équivalent Nginx complet du `.htaccess` : mêmes protections (fichiers de configuration, répertoires sensibles, dotfiles, pas de listing), masquage de l'extension `.php`, `no-cache` pour `sw.js` et `site.webmanifest`, plus le cache des assets et gzip.
Il est conçu pour un déploiement *en deux temps* : le bloc `:80` sert immédiatement le site en HTTP (prérequis de la validation Certbot, étape 6), et `certbot --nginx` créera ensuite le bloc `443` avec la redirection HTTPS. Aucun certificat n'est donc requis à cette étape — `nginx -t` doit passer tel quel.
[source,bash]
----
# Adapter conf/nginx.conf.sample :
# - server_name : votre domaine
# - root : /var/www/annu-kute-ced
# - fastcgi_pass : socket de votre version PHP (ex. /var/run/php/php8.3-fpm.sock)
cp conf/nginx.conf.sample /etc/nginx/sites-available/annu-kute-ced
ln -s /etc/nginx/sites-available/annu-kute-ced /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
----
=== Option B — Apache
[source,bash]
----
apt install -y apache2 php libapache2-mod-php
a2enmod rewrite headers
cat > /etc/apache2/sites-available/annu-kute-ced.conf <<'EOF'
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/annu-kute-ced
<Directory /var/www/annu-kute-ced>
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
EOF
a2ensite annu-kute-ced
systemctl reload apache2
----
Le `.htaccess` copié à l'étape 4 applique les règles de sécurité, le HTTPS forcé (actif après l'étape 6) et le `no-cache` de `sw.js`.
== 6️⃣ HTTPS (obligatoire pour la PWA)
Méthode recommandée par l'EFF : *Certbot via snap* (https://certbot.eff.org/instructions?ws=nginx&os=snap[instructions officielles^]). Prérequis : le domaine pointe vers le serveur et le site répond déjà en HTTP sur le port 80 (étape 5 terminée).
[source,bash]
----
# 1. Installer snapd (Ubuntu : déjà présent. Debian : apt + support classic)
apt install -y snapd
snap install core && snap refresh core
# 2. Retirer tout certbot installé via apt (évite les conflits de commande)
apt-get remove -y certbot || true
# 3. Installer Certbot et préparer la commande
snap install --classic certbot
ln -s /snap/bin/certbot /usr/local/bin/certbot
# 4. Obtenir le certificat ET laisser Certbot configurer Nginx automatiquement
certbot --nginx -d example.com -d www.example.com
# Variante prudente (ne fait qu'émettre le certificat, vhost édité à la main) :
# certbot certonly --nginx -d example.com -d www.example.com
# 5. Vérifier le renouvellement automatique (timer systemd/cron inclus avec le snap)
certbot renew --dry-run
----
NOTE: Pour Apache (option B), la méthode snap est identique, avec `certbot --apache` à l'étape 4.
Ouvrez ensuite `https://example.com` dans un navigateur : le cadenas doit apparaître dans la barre d'URL.
== 7️⃣ Vérification du site
[source,bash]
----
curl -I https://example.com
# Attendu : HTTP/2 200, en-têtes de sécurité (CSP, X-Frame-Options…)
curl -I https://example.com/sw.js
# Attendu : Cache-Control: no-cache
----
Ouvrir le site dans un navigateur : vidéos, podcasts, timeline et le bouton d'installation PWA doivent fonctionner.
== 8️⃣ Clé SSH du CI
C'est la clé utilisée par le runner Gitea Actions pour se connecter au serveur et déployer. Elle est *différente* de la deploy key de lecture (étape 3) : celle-ci doit pouvoir écrire dans le clone.
Générez la paire de clés *sur votre poste local* (pas sur le serveur), puis transférez la clé publique sur le serveur pour l'autoriser.
[source,bash]
----
# Sur votre poste local
ssh-keygen -t ed25519 -f annu-kute-ced-deploy -C "ci-gitea-annu-kute-ced"
# Affichez la clé publique (elle doit être copiée sur le serveur)
cat annu-kute-ced-deploy.pub
----
=== Transfert de la clé publique sur le serveur
Option A — copie directe avec `scp` (depuis votre poste) :
[source,bash]
----
# Sur votre poste local (adapter user@serveur si vous ne vous connectez pas en root)
scp annu-kute-ced-deploy.pub root@<IP_DU_SERVEUR>:/tmp/annu-kute-ced-deploy.pub
# Puis sur le serveur, ajoutez-la au fichier authorized_keys de deploy
sudo -u deploy mkdir -p /home/deploy/.ssh
sudo -u deploy tee -a /home/deploy/.ssh/authorized_keys < /tmp/annu-kute-ced-deploy.pub
----
Option B — connexion SSH sur le serveur, puis création manuelle du fichier :
[source,bash]
----
# Sur le serveur, connecté en root ou avec sudo
sudo -u deploy mkdir -p /home/deploy/.ssh
sudo -u deploy tee -a /home/deploy/.ssh/authorized_keys
# Coller le contenu de annu-kute-ced-deploy.pub, puis Ctrl+D
----
Quelle que soit la méthode, vérifiez les permissions :
[source,bash]
----
chown -R deploy:deploy /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keys
----
=== Test de la connexion CI
Avant d'enregistrer la clé privée dans Gitea, testez depuis votre poste que la connexion fonctionne avec la clé privée générée :
[source,bash]
----
# Sur votre poste local
ssh -i annu-kute-ced-deploy deploy@<IP_DU_SERVEUR> "whoami && hostname"
# Attendu : "deploy" et le nom du serveur, sans mot de passe demandé
----
La clé privée (`annu-kute-ced-deploy`, sans passphrase) ira dans les secrets Gitea à l'étape suivante. *Conservez-la en lieu sûr et ne la commitez jamais.*
== 9️⃣ Secrets Gitea
Dans LaBola : *Settings du dépôt → Actions → Secrets*, créer :
[cols="1,3",options="header"]
|===
| Secret | Valeur
| `SSH_HOST`
| Adresse du serveur (IP ou FQDN), port 22 par défaut (sinon `host:port`)
| `SSH_USER`
| `deploy`
| `SSH_KEY`
| Contenu *intégral* de la clé privée générée à l'étape 8
| `PROD_DEPLOY_PATH`
| `/var/www/annu-kute-ced`
|===
WARNING: `SSH_KEY` donne un accès shell au serveur avec les droits de `deploy`. Ne jamais la coller ailleurs que dans les secrets Gitea, et révoquer la clé publique (`authorized_keys`) en cas de doute.
== 🔟 Test du pipeline
. Pousser un commit sur `main` (par exemple une correction de coquille dans le README).
. Dans LaBola, onglet *Actions* : le workflow *Déploiement PROD* doit exécuter `check` puis `deploy` au vert.
. Côté serveur :
+
[source,bash]
----
cd /var/www/annu-kute-ced
git log -1 --oneline # doit correspondre au commit poussé
grep STATIC_CACHE_NAME sw.js # le suffixe de version a été bumpé à l'heure du déploiement
----
. Côté visiteur : au prochain chargement de page, le modal « Nouvelle version disponible » apparaît (Service Worker déjà installé lors d'une visite précédente).
== 🔄 Fonctionnement courant
[cols="1,3",options="header"]
|===
| Événement | Résultat
| Pull request vers `main`
| Workflow *Vérification PR* : lint PHP et JS bloquants en cas d'erreur
| Push sur `main`
| Workflow *Déploiement PROD* : lint PHP et JS, puis SSH → `git pull --ff-only` → bump de la version des caches `sw.js` → modal de mise à jour chez les visiteurs
| En local, avant de pousser
| `scripts/check.sh` exécute l'ensemble des vérifications qualité (PHP, JS, AsciiDoc, JSON, XML, shellcheck)
|===
Le bump de version dans `sw.js` est fait *sur le serveur uniquement* : le dépôt garde sa valeur de référence, le working tree du serveur est nettoyé (`git checkout -- sw.js`) avant chaque pull pour garantir le fast-forward.
== 🧰 Dépannage
[cols="1,3",options="header"]
|===
| Symptôme | Piste
| `git pull --ff-only` échoue sur le serveur
| Une modification locale existe : `git status` dans le docroot, puis `git checkout -- <fichier>` (le workflow le fait déjà pour `sw.js`)
| `Permission denied` sur `.git/FETCH_HEAD` (ou un autre fichier `.git`)
| Des fichiers du clone appartiennent à `root` (une commande `git` a été lancée en root, sans `sudo -u deploy`) : `chown -R deploy:www-data /var/www/annu-kute-ced`
| L'action ne se déclenche pas
| Vérifier que le runner act est en ligne (LaBola → *Site Administration → Actions → Runners*) et que Actions est activé pour le dépôt (*Settings → Units*)
| `Permission denied (publickey)` dans le job deploy
| Clé publique absente de `/home/deploy/.ssh/authorized_keys`, ou mauvais `SSH_USER`/`SSH_HOST`
| Les visiteurs ne reçoivent pas la mise à jour
| Vérifier `curl -I https://example.com/sw.js` → `Cache-Control: no-cache` requis (règles fournies dans `conf/`)
| Erreurs 500 sur les vidéos/podcasts
| `cache/` non accessible en écriture : `chown -R www-data:www-data cache/`
| `502 Bad Gateway` (Nginx)
| PHP-FPM injoignable : vérifier le socket réel avec `ls /var/run/php/` et adapter `fastcgi_pass` dans le vhost (ex. `php8.1-fpm.sock` sous Ubuntu 22.04, `php8.3-fpm.sock` sous 24.04) ; vérifier que le service tourne : `systemctl status php*-fpm`
| `php-intl` manquant
| `apt install php-intl` puis `systemctl restart php*-fpm` (Nginx) ou `systemctl restart apache2` (Apache)
|===
== 🔒 Notes de sécurité
- La clé privée du CI est *dédiée* à ce dépôt : une clé compromise ne donne accès qu'au compte `deploy`, sans sudo.
- La deploy key Gitea (étape 3) est en *lecture seule*.
- Pour restreindre davantage la clé du CI, on peut limiter les commandes dans `authorized_keys` (`command="..."`, `no-pty`) — facultatif, hors scope de ce guide.
- Les fichiers sensibles (`includes/`, `.htaccess`, `config.local.php`) sont bloqués en accès web par les configurations fournies dans `conf/`.
== 📞 Support
En cas de blocage : mailto:kontak@o-k-i.net[kontak@o-k-i.net]
+1774 -617
View File
File diff suppressed because it is too large Load Diff
+5 -3
View File
@@ -43,6 +43,8 @@ if ($categoryId && isset($allCategories[$categoryId])) {
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="csrf-token" content="<?php echo generateCSRFToken(); ?>"> <meta name="csrf-token" content="<?php echo generateCSRFToken(); ?>">
<title><?php echo htmlspecialchars($categoryName); ?> - <?php echo SITE_NAME; ?></title> <title><?php echo htmlspecialchars($categoryName); ?> - <?php echo SITE_NAME; ?></title>
<meta name="description" content="Découvrez toutes les vidéos de la catégorie <?php echo htmlspecialchars($categoryName); ?> sur <?php echo SITE_NAME; ?>. Contenu multimédia de qualité et exclusif.">
<link rel="canonical" href="<?php echo getBaseUrl() . '/categories.php?id=' . $categoryId; ?>">
<link rel="stylesheet" href="css/styles.css?v=<?php echo filemtime('css/styles.css'); ?>"> <link rel="stylesheet" href="css/styles.css?v=<?php echo filemtime('css/styles.css'); ?>">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css">
@@ -52,7 +54,7 @@ if ($categoryId && isset($allCategories[$categoryId])) {
<link rel="icon" type="image/png" sizes="16x16" href="img/favicon-16x16.png"> <link rel="icon" type="image/png" sizes="16x16" href="img/favicon-16x16.png">
<link rel="manifest" href="site.webmanifest"> <link rel="manifest" href="site.webmanifest">
<link rel="icon" type="image/x-icon" href="img/favicon.ico"> <link rel="icon" type="image/x-icon" href="img/favicon.ico">
<meta name="theme-color" content="#ffffff"> <meta name="theme-color" content="#FF0000">
<!-- Open Graph Meta Tags --> <!-- Open Graph Meta Tags -->
<meta property="og:title" content="Catégorie : <?php echo htmlspecialchars($categoryName); ?> - <?php echo SITE_NAME; ?>"> <meta property="og:title" content="Catégorie : <?php echo htmlspecialchars($categoryName); ?> - <?php echo SITE_NAME; ?>">
@@ -112,7 +114,7 @@ if ($categoryId && isset($allCategories[$categoryId])) {
<div class="section-logo"> <div class="section-logo">
<img src="img/logo.png" alt="<?php echo SITE_NAME; ?>"> <img src="img/logo.png" alt="<?php echo SITE_NAME; ?>">
</div> </div>
<h2 class="section-title">Catégorie : <?php echo htmlspecialchars($categoryName); ?></h2> <h1 class="section-title">Catégorie : <?php echo htmlspecialchars($categoryName); ?></h1>
</div> </div>
<?php if (empty($videos)): ?> <?php if (empty($videos)): ?>
@@ -167,6 +169,6 @@ if ($categoryId && isset($allCategories[$categoryId])) {
<?php include 'includes/mobile-menu.php'; ?> <?php include 'includes/mobile-menu.php'; ?>
<script src="js/main.js"></script> <script src="js/main.js"></script>
<script src="js/categories.js"></script> <script src="js/pwa-update.js?v=<?php echo filemtime('js/pwa-update.js'); ?>"></script>
</body> </body>
</html> </html>
+12
View File
@@ -55,3 +55,15 @@ RewriteRule ^([^/]+)$ $1.php [L]
RewriteCond %{HTTP:X-Forwarded-Proto} !https RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteCond %{HTTPS} !on RewriteCond %{HTTPS} !on
RewriteRule ^(.*) https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] RewriteRule ^(.*) https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# ======================
# CACHE
# ======================
# Le Service Worker et le manifest ne doivent pas être cachés longtemps :
# le navigateur doit pouvoir détecter les nouvelles versions du site.
<FilesMatch "^(sw\.js|site\.webmanifest)$">
<IfModule mod_headers.c>
Header set Cache-Control "no-cache, must-revalidate"
</IfModule>
</FilesMatch>
+64 -17
View File
@@ -1,25 +1,29 @@
# ======================
# ANNU KUTE CED — Configuration Nginx
# ======================
# Ce fichier est prévu pour un déploiement en deux temps :
# 1. Avant le certificat SSL : le bloc :80 ci-dessous sert le site en HTTP.
# C'est aussi le prérequis de `certbot --nginx` (validation sur le port 80).
# À ce stade, `nginx -t` doit passer sans aucun certificat.
# 2. Après `certbot --nginx` : Certbot crée le bloc 443 (SSL) et la
# redirection HTTP → HTTPS automatiquement. Un bloc 443 commenté est
# fourni en bas de fichier si vous préférez configurer SSL à la main.
server { server {
listen 80; listen 80;
server_name votre-domaine.com; server_name votre-domaine.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name votre-domaine.com;
root /path/to/your/site; root /path/to/your/site;
index index.php index.html; index index.php index.html;
# SSL Configuration (adaptez selon votre certificat)
ssl_certificate /path/to/your/certificate.crt;
ssl_certificate_key /path/to/your/private.key;
# ====================== # ======================
# SÉCURITÉ # SÉCURITÉ
# ====================== # ======================
# Bloquer l'accès aux fichiers de configuration # Bloquer l'accès aux fichiers de configuration
location ~* \.(php|inc|conf|config|local)$ { # (les .php de includes/ sont déjà bloqués par le bloc "répertoires
# sensibles" ci-dessous ; ne PAS ajouter php ici, sinon toute
# l'application serait inaccessible)
location ~* \.(inc|conf|config|local)$ {
deny all; deny all;
return 404; return 404;
} }
@@ -58,15 +62,18 @@ server {
rewrite ^/([^.]+)$ /$1.php last; rewrite ^/([^.]+)$ /$1.php last;
} }
# Rediriger les URLs avec .php vers les URLs sans extension
location ~ ^/(.+)\.php$ {
return 301 /$1;
}
# Traitement des fichiers PHP # Traitement des fichiers PHP
# $request_uri est la requête ORIGINALE du client (elle ne change pas
# lors des réécritures internes) : si elle contient .php, on redirige
# proprement vers l'URL sans extension ; sinon (réécriture interne
# @rewrite ou directive index), on sert le PHP. Cela évite la boucle
# infinie /index → /index.php → /index → …
location ~ \.php$ { location ~ \.php$ {
if ($request_uri ~ "^(/[^?]+)\.php") {
return 301 $1$is_args$args;
}
include fastcgi_params; include fastcgi_params;
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; # Adaptez selon votre version PHP fastcgi_pass unix:/var/run/php/php8.3-fpm.sock; # Adaptez selon votre version PHP
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param SCRIPT_NAME $fastcgi_script_name; fastcgi_param SCRIPT_NAME $fastcgi_script_name;
} }
@@ -75,6 +82,18 @@ server {
# OPTIMISATIONS # OPTIMISATIONS
# ====================== # ======================
# Le Service Worker et le manifest ne doivent pas être cachés longtemps :
# le navigateur doit pouvoir détecter les nouvelles versions du site.
# (location exacte = prioritaire sur les regex ci-dessous ; "expires -1"
# émet Cache-Control: no-cache sans annuler les add_header de sécurité)
location = /sw.js {
expires -1;
}
location = /site.webmanifest {
expires -1;
}
# Cache des fichiers statiques # Cache des fichiers statiques
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y; expires 1y;
@@ -94,3 +113,31 @@ server {
add_header X-XSS-Protection "1; mode=block" always; add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always;
} }
# ======================
# HTTPS (port 443) — après obtention du certificat
# ======================
# `certbot --nginx` génère automatiquement ce bloc à partir du bloc :80,
# avec la redirection HTTP → HTTPS. Pour une configuration SSL manuelle,
# décommentez et adaptez les chemins des certificats, en reprenant les
# mêmes sections SÉCURITÉ, RÉÉCRITURE D'URL, PHP et OPTIMISATIONS que
# le bloc :80 ci-dessus :
#
# server {
# listen 443 ssl http2;
# server_name votre-domaine.com;
# root /path/to/your/site;
# index index.php index.html;
#
# ssl_certificate /etc/letsencrypt/live/votre-domaine.com/fullchain.pem;
# ssl_certificate_key /etc/letsencrypt/live/votre-domaine.com/privkey.pem;
#
# # ... reprendre ici les mêmes location que dans le bloc :80 ...
# }
#
# # Et remplacez alors le contenu du bloc :80 par une simple redirection :
# # server {
# # listen 80;
# # server_name votre-domaine.com;
# # return 301 https://$server_name$request_uri;
# # }
+3
View File
@@ -13,6 +13,8 @@ setSecurityHeaders();
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="csrf-token" content="<?php echo generateCSRFToken(); ?>"> <meta name="csrf-token" content="<?php echo generateCSRFToken(); ?>">
<title><?php echo SITE_NAME; ?> - Ouverture prochaine</title> <title><?php echo SITE_NAME; ?> - Ouverture prochaine</title>
<meta name="description" content="<?php echo htmlspecialchars(SITE_DESCRIPTION); ?> Ouverture prochaine.">
<link rel="canonical" href="<?php echo 'https://' . $_SERVER['HTTP_HOST'] . '/countdown.php'; ?>">
<!-- Styles --> <!-- Styles -->
<link rel="stylesheet" href="css/countdown.css?v=<?php echo filemtime('css/countdown.css'); ?>"> <link rel="stylesheet" href="css/countdown.css?v=<?php echo filemtime('css/countdown.css'); ?>">
@@ -183,5 +185,6 @@ setSecurityHeaders();
<!-- Scripts --> <!-- Scripts -->
<script src="js/countdown.js"></script> <script src="js/countdown.js"></script>
<script src="js/pwa-update.js?v=<?php echo filemtime('js/pwa-update.js'); ?>"></script>
</body> </body>
</html> </html>
+39
View File
@@ -82,6 +82,45 @@
text-align: center; text-align: center;
} }
/* Avertissement de transparence sur la page de dons */
.donation-disclaimer {
max-width: 1200px;
margin: 0 auto 30px;
padding: 20px 24px;
background: var(--card-bg);
border: 1px solid var(--border-color);
border-left: 4px solid var(--primary-red);
border-radius: 12px;
color: var(--text-color);
box-shadow: var(--card-shadow);
}
.donation-disclaimer p {
margin: 0;
line-height: 1.6;
}
.donation-disclaimer i {
color: var(--primary-red);
margin-right: 10px;
}
.donation-disclaimer a {
color: var(--primary-red);
text-decoration: underline;
font-weight: 600;
}
.donation-disclaimer a:hover {
text-decoration: none;
}
@media (max-width: 1024px) {
.donation-disclaimer {
margin-bottom: 20px;
}
}
/* Interface de don */ /* Interface de don */
.donation-interface { .donation-interface {
background: var(--card-bg); background: var(--card-bg);
+1 -1
View File
@@ -44,7 +44,7 @@
.search-results-count { .search-results-count {
margin-bottom: 20px; margin-bottom: 20px;
color: #666; color: var(--text-secondary);
font-size: 14px; font-size: 14px;
} }
+165 -1
View File
@@ -3167,7 +3167,8 @@ i.icon-mastodon,
text-align: center; text-align: center;
margin-bottom: 20px; margin-bottom: 20px;
padding: 10px; padding: 10px;
background-color: #f9f9f9; background-color: var(--tag-bg);
color: var(--text-secondary);
border-radius: 4px; border-radius: 4px;
} }
@@ -3412,3 +3413,166 @@ i.icon-mastodon,
} }
/* ==========================================
Modal de mise à jour PWA (js/pwa-update.js)
========================================== */
.pwa-update-overlay {
position: fixed;
inset: 0;
background-color: rgba(0, 0, 0, 0.55);
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
z-index: 10000;
opacity: 0;
transition: opacity 0.25s ease;
}
.pwa-update-overlay.pwa-update-visible {
opacity: 1;
}
.pwa-update-modal {
background-color: var(--card-bg);
color: var(--text-color);
border: 1px solid var(--border-color);
border-radius: 12px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.35);
max-width: 420px;
width: 100%;
padding: 28px 24px 24px;
text-align: center;
transform: translateY(12px) scale(0.97);
transition: transform 0.25s ease;
}
.pwa-update-visible .pwa-update-modal {
transform: translateY(0) scale(1);
}
.pwa-update-logo {
width: 72px;
height: 72px;
border-radius: 50%;
margin-bottom: 12px;
}
.pwa-update-title {
margin: 0 0 8px;
font-size: 1.25rem;
font-weight: 700;
}
.pwa-update-text {
margin: 0 0 20px;
color: var(--text-secondary);
font-size: 0.95rem;
line-height: 1.5;
}
.pwa-update-actions {
display: flex;
gap: 12px;
justify-content: center;
flex-wrap: wrap;
}
.pwa-update-btn {
border: none;
border-radius: 8px;
padding: 10px 20px;
font-size: 0.95rem;
font-weight: 600;
cursor: pointer;
transition: background-color 0.2s ease, opacity 0.2s ease;
}
.pwa-update-btn:disabled {
opacity: 0.6;
cursor: wait;
}
.pwa-update-btn-primary {
background-color: var(--primary-red);
color: #ffffff;
}
.pwa-update-btn-primary:hover:not(:disabled) {
filter: brightness(0.9);
}
.pwa-update-btn-secondary {
background-color: transparent;
color: var(--text-color);
border: 1px solid var(--border-color);
}
.pwa-update-btn-secondary:hover:not(:disabled) {
background-color: var(--hover-bg);
}
.pwa-update-btn:focus-visible {
outline: 2px solid var(--primary-red);
outline-offset: 2px;
}
@media (max-width: 480px) {
.pwa-update-actions {
flex-direction: column;
}
.pwa-update-btn {
width: 100%;
}
}
/* Section « Tendances » (aside de l'accueil) : carte « mise en avant » */
.tags-section-container {
position: relative;
align-items: center;
justify-content: center;
text-align: center;
gap: 6px;
border: 1px solid var(--tag-border);
overflow: hidden;
}
/* Barre d'accent identitaire en haut de la carte (rouge → jaune → vert) */
.tags-section-container::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, var(--primary-red) 0%, #FFD700 50%, var(--primary-green) 100%);
}
/* Icône du titre */
.trending-title-icon {
color: var(--primary-red);
}
.tags-section-container .tags-section {
margin: 6px 0 2px;
padding: 4px 0 6px;
overflow-x: visible;
white-space: normal;
}
.tags-section-container .tag {
font-size: 1rem;
padding: 10px 22px;
border: 1px solid var(--tag-border);
}
/* Rangée d'emoji décorative au-dessus des hashtags « Tendances » (accueil) */
.trending-hashtag-emojis {
font-size: 1.35rem;
letter-spacing: 0.3em;
line-height: 1;
text-align: center;
margin: 4px 0 10px;
}
+6 -3
View File
@@ -23,6 +23,8 @@ $liveStream = getLiveStream();
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Direct - <?php echo SITE_NAME; ?></title> <title>Direct - <?php echo SITE_NAME; ?></title>
<meta name="description" content="Suivez nos diffusions en direct sur <?php echo SITE_NAME; ?>. Contenu en temps réel, discussions et événements exclusifs.">
<link rel="canonical" href="<?php echo getBaseUrl(); ?>/direct.php">
<link rel="stylesheet" href="css/styles.css?v=<?php echo filemtime('css/styles.css'); ?>"> <link rel="stylesheet" href="css/styles.css?v=<?php echo filemtime('css/styles.css'); ?>">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css">
@@ -32,7 +34,7 @@ $liveStream = getLiveStream();
<link rel="icon" type="image/png" sizes="16x16" href="img/favicon-16x16.png"> <link rel="icon" type="image/png" sizes="16x16" href="img/favicon-16x16.png">
<link rel="manifest" href="site.webmanifest"> <link rel="manifest" href="site.webmanifest">
<link rel="icon" type="image/x-icon" href="img/favicon.ico"> <link rel="icon" type="image/x-icon" href="img/favicon.ico">
<meta name="theme-color" content="#ffffff"> <meta name="theme-color" content="#FF0000">
<!-- Open Graph Meta Tags --> <!-- Open Graph Meta Tags -->
<meta property="og:title" content="Direct - <?php echo SITE_NAME; ?>"> <meta property="og:title" content="Direct - <?php echo SITE_NAME; ?>">
@@ -191,7 +193,7 @@ $liveStream = getLiveStream();
$dynamicTitle = NEXT_LIVE_TITLE; $dynamicTitle = NEXT_LIVE_TITLE;
} }
?> ?>
<h2><?php echo htmlspecialchars($dynamicTitle); ?></h2> <h1><?php echo htmlspecialchars($dynamicTitle); ?></h1>
<?php <?php
if (!empty(NEXT_LIVE_DATE)) { if (!empty(NEXT_LIVE_DATE)) {
$liveHour = $liveDate->format('H\hi'); $liveHour = $liveDate->format('H\hi');
@@ -260,7 +262,7 @@ $liveStream = getLiveStream();
?> ?>
<div class="no-live-message"> <div class="no-live-message">
<i class="fas fa-tv"></i> <i class="fas fa-tv"></i>
<h2>Aucun direct en cours</h2> <h1>Aucun direct en cours</h1>
<p>Revenez plus tard pour découvrir nos prochaines diffusions en direct.</p> <p>Revenez plus tard pour découvrir nos prochaines diffusions en direct.</p>
<a href="index.php" class="btn-primary">Retour à l'accueil</a> <a href="index.php" class="btn-primary">Retour à l'accueil</a>
</div> </div>
@@ -274,5 +276,6 @@ $liveStream = getLiveStream();
<?php include 'includes/footer.php'; ?> <?php include 'includes/footer.php'; ?>
<?php include 'includes/mobile-menu.php'; ?> <?php include 'includes/mobile-menu.php'; ?>
<script src="js/main.js"></script> <script src="js/main.js"></script>
<script src="js/pwa-update.js?v=<?php echo filemtime('js/pwa-update.js'); ?>"></script>
</body> </body>
</html> </html>
+58
View File
@@ -0,0 +1,58 @@
<!-- Injected by Asciidoctor when generating the print/PDF HTML variant.
Usage: asciidoctor -a docinfodir=docs -a docinfo=shared README.adoc -o README-print.html
Fixes color-emoji rendering in headless Chromium and adapts the layout for print. -->
<style>
/* Font stacks with an explicit emoji fallback. "Noto Color Emoji" must be
installed system-wide (package: fonts-noto-color-emoji). */
* {
font-family: "Noto Sans", "Noto Color Emoji", sans-serif !important;
}
pre, code, kbd, samp {
font-family: "Noto Sans Mono", "Noto Color Emoji", monospace !important;
}
@media print {
/* The left-floating TOC is screen-only; bring it back into the flow */
#header, #toc {
position: static !important;
width: auto !important;
max-width: none !important;
float: none !important;
height: auto !important;
overflow: visible !important;
}
#content {
margin: 0 !important;
padding: 0 !important;
max-width: none !important;
}
body {
margin: 0;
font-size: 10pt;
line-height: 1.35;
}
/* Keep headings with their content, break tables between rows only */
h1, h2, h3, h4, h5, h6 {
page-break-after: avoid;
}
tr, td, th, .admonitionblock, .imageblock {
page-break-inside: avoid;
}
/* Long code listings may overflow the page width: allow wrapping */
pre {
white-space: pre-wrap;
word-wrap: break-word;
}
img {
max-width: 100%;
}
/* Links are not clickable in a PDF: keep them readable, not blue */
a {
color: inherit;
text-decoration: none;
}
a[href^="http"]::after {
content: "";
}
}
</style>
+90
View File
@@ -0,0 +1,90 @@
#!/usr/bin/env bash
# Generate the HTML/PDF export(s) from any .adoc document (README.adoc by default).
#
# docs/generate-readme-pdf.sh # README.html + README.pdf
# docs/generate-readme-pdf.sh --html # README.html only
# docs/generate-readme-pdf.sh --pdf # README.pdf only
# docs/generate-readme-pdf.sh DEPLOY.adoc # DEPLOY.html + DEPLOY.pdf
# docs/generate-readme-pdf.sh DEPLOY.adoc --pdf # DEPLOY.pdf only
#
# Outputs land in the project root (named after the source) and are Git-ignored.
#
# Requirements:
# - asciidoctor (e.g. gem install asciidoctor)
# - chromium (headless PDF rendering)
# - fonts-noto-color-emoji (color emoji in the PDF)
#
# Why Chromium instead of asciidoctor-pdf (used by the VSCodium extension)?
# Its Prawn engine cannot embed color-emoji fonts (CBDT/COLR), so emoji
# disappear. Chromium handles them fine as long as "Noto Color Emoji"
# is installed. The docs/docinfo.html stylesheet (emoji fallback + print
# rules) is injected only for this build, via Asciidoctor's docinfo
# mechanism, and adapts the layout for print (TOC in flow, table/page
# breaks, wrapped code listings).
set -euo pipefail
cd "$(dirname "$0")/.." || exit 1
SRC="README.adoc"
MODE="--all"
for arg in "$@"; do
case "$arg" in
--html|--pdf|--all)
MODE="$arg"
;;
-h|--help)
sed -n '2,12p' "$0"
exit 0
;;
*.adoc)
SRC="$arg"
;;
*)
echo "Usage: $0 [fichier.adoc] [--html|--pdf|--all]" >&2
exit 1
;;
esac
done
if [[ ! -f "$SRC" ]]; then
echo "Erreur : fichier introuvable : $SRC" >&2
exit 1
fi
BASE="$(basename "$SRC" .adoc)"
HTML_OUT="$BASE.html"
PDF_OUT="$BASE.pdf"
build_html() {
asciidoctor -b html5 -a docinfodir=docs -a docinfo=shared "$SRC" -o "$1"
echo "HTML generated: $1"
}
build_pdf() {
# Resolve to an absolute path for the file:// URL
local html_path="$1"
[[ "$html_path" != /* ]] && html_path="$PWD/$html_path"
chromium --headless --disable-gpu --no-sandbox \
--no-pdf-header-footer \
--print-to-pdf="$PDF_OUT" \
"file://$html_path"
echo "PDF generated: $PDF_OUT"
}
case "$MODE" in
--html)
build_html "$HTML_OUT"
;;
--pdf)
TMP_HTML="$(mktemp --suffix=.html)"
trap 'rm -f "$TMP_HTML"' EXIT
build_html "$TMP_HTML" >/dev/null
build_pdf "$TMP_HTML"
;;
--all)
build_html "$HTML_OUT"
build_pdf "$HTML_OUT"
;;
esac
+61 -26
View File
@@ -1,6 +1,6 @@
<?php <?php
/** /**
* Page de dons - Template d'exemple * Page de dons - Template d'exemple pour ANNU KUTE CED
* Support via LiberaPay, Ko-fi et Stripe * Support via LiberaPay, Ko-fi et Stripe
* *
* Pour personnaliser cette page : * Pour personnaliser cette page :
@@ -16,6 +16,7 @@
require_once 'includes/config.php'; require_once 'includes/config.php';
require_once 'includes/security.php'; require_once 'includes/security.php';
require_once 'includes/structured-data.php';
// Vérifier si les dons sont activés // Vérifier si les dons sont activés
if (!defined('DONATIONS_ENABLED') || !DONATIONS_ENABLED) { if (!defined('DONATIONS_ENABLED') || !DONATIONS_ENABLED) {
@@ -48,6 +49,24 @@ $currencySymbol = $currency === 'EUR' ? '€' : '$';
$stripeOneTimeLinks = defined('STRIPE_ONE_TIME_LINKS') ? STRIPE_ONE_TIME_LINKS : []; $stripeOneTimeLinks = defined('STRIPE_ONE_TIME_LINKS') ? STRIPE_ONE_TIME_LINKS : [];
$stripeMonthlyLinks = defined('STRIPE_MONTHLY_LINKS') ? STRIPE_MONTHLY_LINKS : []; $stripeMonthlyLinks = defined('STRIPE_MONTHLY_LINKS') ? STRIPE_MONTHLY_LINKS : [];
/**
* Convertit les URLs d'un texte en liens cliquables tout en échappant le reste.
* Utilisé pour le message de transparence sur les dons.
*/
function linkUrlsInText(string $text): string {
$parts = preg_split('/(https?:\/\/[^\s<]+)/i', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
$result = '';
foreach ($parts as $i => $part) {
if ($i % 2 === 1) {
$url = htmlspecialchars($part, ENT_QUOTES, 'UTF-8');
$result .= '<a href="' . $url . '" target="_blank" rel="noopener noreferrer">' . $url . '</a>';
} else {
$result .= htmlspecialchars($part, ENT_QUOTES, 'UTF-8');
}
}
return $result;
}
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="fr"> <html lang="fr">
@@ -58,7 +77,8 @@ $stripeMonthlyLinks = defined('STRIPE_MONTHLY_LINKS') ? STRIPE_MONTHLY_LINKS : [
<!-- PERSONNALISEZ: Titre et description de votre page de dons --> <!-- PERSONNALISEZ: Titre et description de votre page de dons -->
<title>Soutenir <?php echo ORGANIZATION_NAME; ?> - Dons</title> <title>Soutenir <?php echo ORGANIZATION_NAME; ?> - Dons</title>
<meta name="description" content="Soutenez <?php echo ORGANIZATION_NAME; ?> par un don. Chaque contribution compte pour maintenir notre plateforme indépendante."> <meta name="description" content="Soutenez <?php echo ORGANIZATION_NAME; ?> par un don. Chaque contribution compte pour maintenir le hub multimédia du podcast.">
<link rel="canonical" href="<?php echo getBaseUrl() . '/dons.php'; ?>">
<!-- Styles --> <!-- Styles -->
<link rel="stylesheet" href="css/styles.css?v=<?php echo filemtime('css/styles.css'); ?>"> <link rel="stylesheet" href="css/styles.css?v=<?php echo filemtime('css/styles.css'); ?>">
@@ -75,25 +95,32 @@ $stripeMonthlyLinks = defined('STRIPE_MONTHLY_LINKS') ? STRIPE_MONTHLY_LINKS : [
<!-- Open Graph Meta Tags --> <!-- Open Graph Meta Tags -->
<meta property="og:title" content="Soutenir <?php echo ORGANIZATION_NAME; ?>"> <meta property="og:title" content="Soutenir <?php echo ORGANIZATION_NAME; ?>">
<meta property="og:description" content="Soutenez <?php echo ORGANIZATION_NAME; ?> par un don et aidez-nous à maintenir notre plateforme indépendante."> <meta property="og:description" content="Soutenez <?php echo ORGANIZATION_NAME; ?> par un don et aidez-nous à maintenir le hub multimédia du podcast.">
<meta property="og:image" content="<?php echo 'https://' . $_SERVER['HTTP_HOST'] . '/img/logo.png'; ?>"> <meta property="og:image" content="<?php echo getBaseUrl() . '/img/logo.png'; ?>">
<meta property="og:url" content="<?php echo 'https://' . $_SERVER['HTTP_HOST'] . '/dons.php'; ?>"> <meta property="og:url" content="<?php echo getBaseUrl() . '/dons.php'; ?>">
<meta property="og:type" content="website"> <meta property="og:type" content="website">
<meta property="og:site_name" content="<?php echo SITE_NAME; ?>"> <meta property="og:site_name" content="<?php echo SITE_NAME; ?>">
<meta property="og:locale" content="fr_FR">
<!-- Twitter Card Meta Tags -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Soutenir <?php echo ORGANIZATION_NAME; ?>">
<meta name="twitter:description" content="Soutenez <?php echo ORGANIZATION_NAME; ?> par un don et aidez-nous à maintenir le hub multimédia du podcast.">
<meta name="twitter:image" content="<?php echo getBaseUrl() . '/img/logo.png'; ?>">
<!-- Schema.org pour les dons --> <!-- Schema.org pour les dons -->
<script type="application/ld+json"> <?php
{ outputJsonLd(json_encode([
"@context": "https://schema.org", "@context" => "https://schema.org",
"@type": "Organization", "@type" => "Organization",
"name": "<?php echo SITE_NAME; ?>", "name" => SITE_NAME,
"description": "<?php echo SITE_DESCRIPTION; ?>", "description" => SITE_DESCRIPTION,
"url": "<?php echo 'https://' . $_SERVER['HTTP_HOST']; ?>", "url" => getBaseUrl(),
"potentialAction": { "potentialAction" => [
"@type": "DonateAction" "@type" => "DonateAction"
} ]
} ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
</script> ?>
<!-- Script pour éviter le flash en mode sombre --> <!-- Script pour éviter le flash en mode sombre -->
<script> <script>
@@ -118,24 +145,31 @@ $stripeMonthlyLinks = defined('STRIPE_MONTHLY_LINKS') ? STRIPE_MONTHLY_LINKS : [
<section class="donation-hero"> <section class="donation-hero">
<div class="donation-hero-content"> <div class="donation-hero-content">
<h1><i class="fas fa-heart"></i> Soutenir <?php echo ORGANIZATION_NAME; ?></h1> <h1><i class="fas fa-heart"></i> Soutenir <?php echo ORGANIZATION_NAME; ?></h1>
<p class="hero-subtitle">Votre soutien est essentiel pour maintenir notre plateforme indépendante</p> <p class="hero-subtitle">Votre soutien est essentiel pour maintenir le hub multimédia du podcast</p>
</div> </div>
</section> </section>
<!-- Section principale de don --> <!-- Section principale de don -->
<section class="donation-main"> <section class="donation-main">
<!-- Avertissement de transparence (optionnel) -->
<?php if (defined('DONATIONS_OKI_DISCLAIMER') && !empty(DONATIONS_OKI_DISCLAIMER)): ?>
<div class="donation-disclaimer" role="note">
<p><i class="fas fa-info-circle"></i> <?php echo linkUrlsInText(DONATIONS_OKI_DISCLAIMER); ?></p>
</div>
<?php endif; ?>
<div class="donation-content"> <div class="donation-content">
<div class="donation-message"> <div class="donation-message">
<!-- PERSONNALISEZ: Votre message de don --> <!-- PERSONNALISEZ: Votre message de don -->
<h2>Pourquoi nous soutenir ?</h2> <h2>Pourquoi nous soutenir ?</h2>
<p><?php echo SITE_NAME; ?> est une plateforme multimédia indépendante. <p><?php echo SITE_NAME; ?> est le hub multimédia du podcast ANNU KUTE CED.
Vos dons nous permettent de :</p> Vos dons nous permettent de :</p>
<ul> <ul>
<!-- PERSONNALISEZ: Vos objectifs --> <!-- PERSONNALISEZ: Vos objectifs -->
<li><i class="fas fa-server"></i> Maintenir nos serveurs et notre infrastructure</li> <li><i class="fas fa-server"></i> Maintenir nos serveurs et notre infrastructure</li>
<li><i class="fas fa-shield-alt"></i> Préserver notre indépendance et notre souveraineté numérique</li> <li><i class="fas fa-microphone-alt"></i> Poursuivre la production et la diffusion du podcast</li>
<li><i class="fas fa-tools"></i> Développer de nouvelles fonctionnalités</li> <li><i class="fas fa-tools"></i> Développer de nouvelles fonctionnalités pour le hub</li>
<li><i class="fas fa-users"></i> Soutenir la création de contenu libre et accessible</li> <li><i class="fas fa-users"></i> Soutenir la création de contenu libre et accessible sur le Fédivers</li>
</ul> </ul>
<p><strong>Chaque don, même petit, fait la différence !</strong></p> <p><strong>Chaque don, même petit, fait la différence !</strong></p>
</div> </div>
@@ -256,14 +290,14 @@ $stripeMonthlyLinks = defined('STRIPE_MONTHLY_LINKS') ? STRIPE_MONTHLY_LINKS : [
<p>Maintenir nos serveurs, notre plateforme et nos outils numériques représente des coûts mensuels importants.</p> <p>Maintenir nos serveurs, notre plateforme et nos outils numériques représente des coûts mensuels importants.</p>
</div> </div>
<div class="info-card"> <div class="info-card">
<i class="fas fa-tools"></i> <i class="fas fa-microphone-alt"></i>
<h4>Maintenance & Développement</h4> <h4>Production du podcast</h4>
<p>Assurer la sécurité, les mises à jour et l'évolution de nos outils technologiques demande un investissement constant.</p> <p>Vos contributions nous aident à assurer la régularité et la qualité des épisodes d'ANNU KUTE CED.</p>
</div> </div>
<div class="info-card"> <div class="info-card">
<i class="fas fa-shield-alt"></i> <i class="fas fa-shield-alt"></i>
<h4>Indépendance Numérique</h4> <h4>Indépendance Numérique</h4>
<p>Vos dons nous permettent de rester indépendants des plateformes commerciales et de préserver notre souveraineté numérique.</p> <p>Vos dons nous permettent de rester indépendants des plateformes commerciales et de préserver notre souveraineté numérique sur le Fédivers.</p>
</div> </div>
</div> </div>
</section> </section>
@@ -291,5 +325,6 @@ $stripeMonthlyLinks = defined('STRIPE_MONTHLY_LINKS') ? STRIPE_MONTHLY_LINKS : [
</script> </script>
<script src="js/main.js"></script> <script src="js/main.js"></script>
<script src="js/pwa-update.js?v=<?php echo filemtime('js/pwa-update.js'); ?>"></script>
</body> </body>
</html> </html>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 376 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 376 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 767 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 377 KiB

+47 -45
View File
@@ -1,16 +1,16 @@
<?php <?php
/** /**
* Configuration par défaut de FEDIVERSE OKI * Configuration par défaut d'ANNU KUTE CED
* *
* Ce fichier contient les paramètres de configuration par défaut. * Ce fichier contient les paramètres de configuration par défaut.
* Il est utilisé pour initialiser les variables non définies dans config.local.php. * Il est utilisé pour initialiser les variables non définies dans config.local.php.
*/ */
if (!defined('APP_HOST_NAME')) define('APP_HOST_NAME', 'fediverse.o-k-i.net'); if (!defined('APP_HOST_NAME')) define('APP_HOST_NAME', 'example.com');
if (!defined('ORGANIZATION_SHORT_NAME')) define('ORGANIZATION_SHORT_NAME', 'OKI'); if (!defined('ORGANIZATION_SHORT_NAME')) define('ORGANIZATION_SHORT_NAME', 'ANNU KUTE CED');
if (!defined('ORGANIZATION_NAME')) define('ORGANIZATION_NAME', 'ORGANISATION KA INTERNATIONALE'); if (!defined('ORGANIZATION_NAME')) define('ORGANIZATION_NAME', 'ANNU KUTE CED');
// Configuration de base - ces valeurs seront utilisées si elles ne sont pas définies dans config.local.php // Configuration de base - ces valeurs seront utilisées si elles ne sont pas définies dans config.local.php
if (!defined('PEERTUBE_URL')) define('PEERTUBE_URL', 'https://gade.o-k-i.net'); if (!defined('PEERTUBE_URL')) define('PEERTUBE_URL', 'https://gade.o-k-i.net');
@@ -38,11 +38,8 @@ if (!defined('SHOW_VIDEO_VIEWS')) define('SHOW_VIDEO_VIEWS', false); // Masquer
// format: [ID catégorie => Nom personnalisé] // format: [ID catégorie => Nom personnalisé]
if (!defined('PRIORITY_CATEGORIES')) { if (!defined('PRIORITY_CATEGORIES')) {
define('PRIORITY_CATEGORIES', [ define('PRIORITY_CATEGORIES', [
11 => 'Actualités & Politique', 10 => 'Divertissement',
15 => 'Science et Technologie', 15 => 'Science et Technologie'
4 => 'Art',
9 => 'Humour',
10 => 'Divertissement'
]); ]);
} }
@@ -61,8 +58,8 @@ if (!defined('MASTODON_MAX_POST_SHOW')) define('MASTODON_MAX_POST_SHOW', '10');
// if (!defined('MASTODON_S3_MEDIA_URL')) define('MASTODON_S3_MEDIA_URL', 'https://s3.eu-central-003.backblazeb2.com'); // if (!defined('MASTODON_S3_MEDIA_URL')) define('MASTODON_S3_MEDIA_URL', 'https://s3.eu-central-003.backblazeb2.com');
// Informations du site // Informations du site
if (!defined('SITE_NAME')) define('SITE_NAME', 'FEDIVERSE OKI'); if (!defined('SITE_NAME')) define('SITE_NAME', 'ANNU KUTE CED');
if (!defined('SITE_DESCRIPTION')) define('SITE_DESCRIPTION', 'Plateforme multimedia indépendante'); if (!defined('SITE_DESCRIPTION')) define('SITE_DESCRIPTION', 'Hub multimédia du podcast ANNU KUTE CED');
if (!defined('SITE_LOGO')) define('SITE_LOGO', 'img/logo.png'); if (!defined('SITE_LOGO')) define('SITE_LOGO', 'img/logo.png');
if (!defined('SITE_FAVICON')) define('SITE_FAVICON', 'img/favicon.png'); if (!defined('SITE_FAVICON')) define('SITE_FAVICON', 'img/favicon.png');
@@ -76,13 +73,13 @@ if (!defined('X_URL')) define('X_URL', '#');
if (!defined('INSTAGRAM_URL')) define('INSTAGRAM_URL', '#'); if (!defined('INSTAGRAM_URL')) define('INSTAGRAM_URL', '#');
if (!defined('YOUTUBE_URL')) define('YOUTUBE_URL', '#'); if (!defined('YOUTUBE_URL')) define('YOUTUBE_URL', '#');
if (!defined('TIKTOK_URL')) define('TIKTOK_URL', '#'); if (!defined('TIKTOK_URL')) define('TIKTOK_URL', '#');
if (!defined('MASTODON_URL')) define('MASTODON_URL', 'https://bokante.o-k-i.net/@admin'); if (!defined('MASTODON_URL')) define('MASTODON_URL', 'https://bokante.o-k-i.net/@cedric');
// Contacts // Contacts
if (!defined('CONTACT_EMAIL')) define('CONTACT_EMAIL', 'kontak.o-k-i.net'); if (!defined('CONTACT_EMAIL')) define('CONTACT_EMAIL', 'kontak@o-k-i.net');
// Mentions légales // Mentions légales
if (!defined('LEGAL_COPYRIGHT')) define('LEGAL_COPYRIGHT', 'OKI'); if (!defined('LEGAL_COPYRIGHT')) define('LEGAL_COPYRIGHT', 'ANNU KUTE CED');
if (!defined('LEGAL_WEBMASTER_NAME')) define('LEGAL_WEBMASTER_NAME', 'Cédric Famibelle-Pronzola'); if (!defined('LEGAL_WEBMASTER_NAME')) define('LEGAL_WEBMASTER_NAME', 'Cédric Famibelle-Pronzola');
if (!defined('LEGAL_WEBMASTER_EMAIL')) define('LEGAL_WEBMASTER_EMAIL', 'contact@cedric-pronzola.dev'); if (!defined('LEGAL_WEBMASTER_EMAIL')) define('LEGAL_WEBMASTER_EMAIL', 'contact@cedric-pronzola.dev');
if (!defined('LEGAL_HOST_NAME')) define('LEGAL_HOST_NAME', 'o2Switch'); if (!defined('LEGAL_HOST_NAME')) define('LEGAL_HOST_NAME', 'o2Switch');
@@ -92,15 +89,17 @@ if (!defined('LEGAL_HOST_ADDRESS')) define('LEGAL_HOST_ADDRESS', '222 boulevard
if (!defined('LEGAL_CONTACT_EMAIL')) define('LEGAL_CONTACT_EMAIL', 'kontak@o-k-i.net'); if (!defined('LEGAL_CONTACT_EMAIL')) define('LEGAL_CONTACT_EMAIL', 'kontak@o-k-i.net');
if (!defined('LEGAL_LICENSE')) define('LEGAL_LICENSE', 'GNU Affero General Public License version 3 (AGPL-V3)'); if (!defined('LEGAL_LICENSE')) define('LEGAL_LICENSE', 'GNU Affero General Public License version 3 (AGPL-V3)');
if (!defined('LEGAL_LICENSE_URL')) define('LEGAL_LICENSE_URL', 'https://www.gnu.org/licenses/agpl-3.0.html'); if (!defined('LEGAL_LICENSE_URL')) define('LEGAL_LICENSE_URL', 'https://www.gnu.org/licenses/agpl-3.0.html');
if (!defined('LEGAL_SOURCE_CODE_URL')) define('LEGAL_SOURCE_CODE_URL', 'https://codeberg.org/OKI/fediverse.o-k-i.net'); if (!defined('LEGAL_SOURCE_CODE_URL')) define('LEGAL_SOURCE_CODE_URL', 'https://labola.o-k-i.net/cedric/annu-kute-ced');
if (!defined('LEGAL_SERVICE_DESCRIPTION')) define('LEGAL_SERVICE_DESCRIPTION', 'est une plateforme multimédia indépendante.'); if (!defined('LEGAL_SERVICE_DESCRIPTION')) define('LEGAL_SERVICE_DESCRIPTION', 'est le hub multimédia du podcast ANNU KUTE CED.');
// Fonctionnalités // Fonctionnalités
define('ENABLE_SEARCH', true); define('ENABLE_SEARCH', true);
if (!defined('ENABLE_USER_ACCOUNTS')) define('ENABLE_USER_ACCOUNTS', false); if (!defined('ENABLE_USER_ACCOUNTS')) define('ENABLE_USER_ACCOUNTS', false);
// Cache // Cache
if (!defined('CACHE_ENABLED')) define('CACHE_ENABLED', false); // Activé par défaut : indispensable pour Castopod (rate limit strict sur les
// flux RSS) et Funkwhale. Sans cache, une requête part à chaque chargement.
if (!defined('CACHE_ENABLED')) define('CACHE_ENABLED', true);
if (!defined('CACHE_DURATION')) define('CACHE_DURATION', 3600); // En secondes (1 heure) if (!defined('CACHE_DURATION')) define('CACHE_DURATION', 3600); // En secondes (1 heure)
// ========================================= // =========================================
@@ -109,13 +108,13 @@ if (!defined('CACHE_DURATION')) define('CACHE_DURATION', 3600); // En secondes (
// Type de contenu à afficher dans la section hero // Type de contenu à afficher dans la section hero
// Options: 'live' (direct PeerTube), 'playlist' (playlist audio/vidéo), 'video' (vidéo unique), 'none' (masquer) // Options: 'live' (direct PeerTube), 'playlist' (playlist audio/vidéo), 'video' (vidéo unique), 'none' (masquer)
if (!defined('HERO_TYPE')) define('HERO_TYPE', 'live'); if (!defined('HERO_TYPE')) define('HERO_TYPE', 'video');
// Configuration pour le direct (HERO_TYPE = 'live') // Configuration pour le direct (HERO_TYPE = 'live')
if (!defined('LIVE_ACCOUNT_NAME')) define('LIVE_ACCOUNT_NAME', 'admin'); if (!defined('LIVE_ACCOUNT_NAME')) define('LIVE_ACCOUNT_NAME', 'annu_kute_ced');
// Configuration pour une vidéo unique (HERO_TYPE = 'video') // Configuration pour une vidéo unique (HERO_TYPE = 'video')
if (!defined('HERO_VIDEO_ID')) define('HERO_VIDEO_ID', '1aJ2u9euwF9fWKQhFxwFio'); if (!defined('HERO_VIDEO_ID')) define('HERO_VIDEO_ID', 'fDE6tvQMXRuBpE4eDDS7kj');
if (!defined('HERO_VIDEO_TITLE')) define('HERO_VIDEO_TITLE', 'Vidéo de présentation'); if (!defined('HERO_VIDEO_TITLE')) define('HERO_VIDEO_TITLE', 'Vidéo de présentation');
// Configuration pour les playlists (HERO_TYPE = 'playlist') // Configuration pour les playlists (HERO_TYPE = 'playlist')
@@ -148,7 +147,7 @@ if (!defined('CASTOPOD_URL')) define('CASTOPOD_URL', 'https://kute.o-k-i.net');
// Format: ['slug1', 'slug2', 'slug3'] // Format: ['slug1', 'slug2', 'slug3']
// Les épisodes de tous les podcasts seront mélangés et triés par date // Les épisodes de tous les podcasts seront mélangés et triés par date
if (!defined('CASTOPOD_PODCAST_SLUGS')) { if (!defined('CASTOPOD_PODCAST_SLUGS')) {
define('CASTOPOD_PODCAST_SLUGS', ['joukawouve']); define('CASTOPOD_PODCAST_SLUGS', ['annu_kute_cedric']);
} }
// Nombre d'épisodes à afficher (total, tous podcasts confondus) // Nombre d'épisodes à afficher (total, tous podcasts confondus)
@@ -173,21 +172,14 @@ if (!defined('TAG_SHORT')) define('TAG_SHORT', 'short');
// Hashtags importants à afficher dans la sidebar, footer et menu mobile // Hashtags importants à afficher dans la sidebar, footer et menu mobile
if (!defined('IMPORTANT_TAGS')) { if (!defined('IMPORTANT_TAGS')) {
define('IMPORTANT_TAGS', [ define('IMPORTANT_TAGS', [
'Chlordécone', 'ANNUKUTECED'
'RCI',
'Mé67',
'Traduction',
'fediverse'
]); ]);
} }
// Hashtags populaires à afficher sur la page d'accueil // Hashtags populaires à afficher sur la page d'accueil
if (!defined('POPULAR_TAGS')) { if (!defined('POPULAR_TAGS')) {
define('POPULAR_TAGS', [ define('POPULAR_TAGS', [
'fediverse', 'ANNUKUTECED'
'guadeloupe',
'Gaza',
'Arte'
]); ]);
} }
@@ -230,32 +222,43 @@ if (!defined('WORDPRESS_ENABLED')) define('WORDPRESS_ENABLED', false);
// ========================================= // =========================================
// Activation du système de dons par défaut // Activation du système de dons par défaut
if (!defined('DONATIONS_ENABLED')) define('DONATIONS_ENABLED', false); // Par défaut les dons sont activés et pointent vers les comptes de l'association
// ORGANISATION KA INTERNATIONALE (OKI), mainteneur de l'application.
// Modifiez ces valeurs dans config.local.php pour utiliser vos propres comptes.
if (!defined('DONATIONS_ENABLED')) define('DONATIONS_ENABLED', true);
// URLs des plateformes de don // URLs des plateformes de don
if (!defined('LIBERAPAY_URL')) define('LIBERAPAY_URL', ''); // Ex: https://liberapay.com/votre-compte/donate if (!defined('LIBERAPAY_URL')) define('LIBERAPAY_URL', 'https://liberapay.com/OKI/donate');
if (!defined('KOFI_URL')) define('KOFI_URL', ''); // Ex: https://ko-fi.com/votre-compte if (!defined('KOFI_URL')) define('KOFI_URL', 'https://ko-fi.com/J3J5TOZPI'); // Ex: https://ko-fi.com/votre-compte
if (!defined('STRIPE_ENABLED')) define('STRIPE_ENABLED', false); if (!defined('STRIPE_ENABLED')) define('STRIPE_ENABLED', false);
// Message de transparence affiché sur la page de dons.
// Renseignez ce texte lorsque les liens pointent vers les comptes d'une organisation
// tierce (par exemple OKI) afin d'expliquer aux visiteurs pourquoi.
// Les URLs présentes dans ce texte sont automatiquement transformées en liens cliquables.
if (!defined('DONATIONS_OKI_DISCLAIMER')) {
define('DONATIONS_OKI_DISCLAIMER', 'Les liens ci-dessous pointent vers les comptes de donation de l\'association ORGANISATION KA INTERNATIONALE (OKI) — o-k-i.net. Pour soutenir spécifiquement OKI : o-k-i.net/dons. Pour adapter cette page à votre propre instance, voir la documentation (lien "Code Source" en bas de la page)');
}
// Configuration Stripe // Configuration Stripe
if (!defined('STRIPE_ONE_TIME_LINKS')) { if (!defined('STRIPE_ONE_TIME_LINKS')) {
define('STRIPE_ONE_TIME_LINKS', [ define('STRIPE_ONE_TIME_LINKS', [
1 => '', // Lien Stripe pour don de 1€ 1 => 'https://don.o-k-i.net/b/aEUdTw5SD3SH7m0bII', // Lien pour don de 1€
5 => '', // Lien Stripe pour don de 5€ 5 => 'https://don.o-k-i.net/b/4gw6r480L1Kz5dScMO', // Lien pour don de 5€
10 => '', // Lien Stripe pour don de 10€ 10 => 'https://don.o-k-i.net/b/5kA02G1Cn4WLbCgeUV', // Lien pour don de 10€
20 => '', // Lien Stripe pour don de 20€ 20 => 'https://don.o-k-i.net/b/fZebLo94PfBpayc5kn', // Lien pour don de 20€
50 => '', // Lien Stripe pour don de 50€ 50 => 'https://don.o-k-i.net/b/6oE5n05SDdth49O004', // Lien pour don de 50€
'custom' => '' // Lien Stripe pour montant personnalisé 'custom' => 'https://don.o-k-i.net/b/6oE2aO94P88X9u8eV4' // Lien pour montant personnalisé
]); ]);
} }
if (!defined('STRIPE_MONTHLY_LINKS')) { if (!defined('STRIPE_MONTHLY_LINKS')) {
define('STRIPE_MONTHLY_LINKS', [ define('STRIPE_MONTHLY_LINKS', [
1 => '', // Lien Stripe pour don mensuel de 1€ 1 => 'https://don.o-k-i.net/b/7sI9Dgch14WL7m0fZ3', // Lien pour don mensuel de 1€
5 => '', // Lien Stripe pour don mensuel de 5€ 5 => 'https://don.o-k-i.net/b/8wM4iW4Ozbl95dS6ow', // Lien pour don mensuel de 5€
10 => '', // Lien Stripe pour don mensuel de 10€ 10 => 'https://don.o-k-i.net/b/8wM2aO80L74TcGk3ci', // Lien pour don mensuel de 10€
20 => '', // Lien Stripe pour don mensuel de 20€ 20 => 'https://don.o-k-i.net/b/00g7v894P88XgWAfZ7', // Lien pour don mensuel de 20€
50 => '', // Lien Stripe pour don mensuel de 50€ 50 => 'https://don.o-k-i.net/b/4gw8zc6WHgFtcGkbIP', // Lien pour don mensuel de 50€
]); ]);
} }
@@ -284,4 +287,3 @@ if (!defined('NEXT_LIVE_DATE')) define('NEXT_LIVE_DATE', '');
// Chemin vers l'image d'annonce du prochain live (relatif à la racine du site) // Chemin vers l'image d'annonce du prochain live (relatif à la racine du site)
// Exemple: 'uploads/next-live.jpg' // Exemple: 'uploads/next-live.jpg'
if (!defined('NEXT_LIVE_IMAGE')) define('NEXT_LIVE_IMAGE', 'uploads/next-live.jpg'); if (!defined('NEXT_LIVE_IMAGE')) define('NEXT_LIVE_IMAGE', 'uploads/next-live.jpg');
?>
+58 -49
View File
@@ -1,6 +1,6 @@
<?php <?php
/** /**
* Configuration locale pour l'instance de PeerTube * Configuration locale pour l'instance ANNU KUTE CED
* *
* Ce fichier est un exemple de configuration locale. * Ce fichier est un exemple de configuration locale.
* Pour l'utiliser: * Pour l'utiliser:
@@ -10,18 +10,20 @@
* Note: config.local.php ne doit pas être versionné dans git * Note: config.local.php ne doit pas être versionné dans git
*/ */
define('APP_HOST_NAME', 'fediverse.o-k-i.net'); define('APP_HOST_NAME', 'example.com');
// define('ORGANIZATION_SHORT_NAME', 'OKI'); // define('ORGANIZATION_SHORT_NAME', 'ANNU KUTE CED');
// define('ORGANIZATION_NAME', 'ORGANISATION KA INTERNATIONALE'); // define('ORGANIZATION_NAME', 'ANNU KUTE CED');
// ========================================= // =========================================
// Configuration de l'API PeerTube // Configuration de l'API PeerTube
// ========================================= // =========================================
// URL de l'API PeerTube (obligatoire) // URL de l'API PeerTube (obligatoire)
// define('PEERTUBE_URL', 'https://votre-instance.fr'); // Par défaut, le hub utilise la chaîne GADE du podcast :
// define('PEERTUBE_DISPLAY_NAME', 'votre-instance.fr'); // https://gade.o-k-i.net/c/annu_kute_ced/videos
// define('PEERTUBE_URL', 'https://gade.o-k-i.net');
// define('PEERTUBE_DISPLAY_NAME', 'gade.o-k-i.net');
// Clé d'API PeerTube (optionnelle) // Clé d'API PeerTube (optionnelle)
// define('API_KEY', 'votre_cle_api'); // define('API_KEY', 'votre_cle_api');
@@ -38,56 +40,51 @@ define('APP_HOST_NAME', 'fediverse.o-k-i.net');
// define('HERO_TYPE', 'live'); // define('HERO_TYPE', 'live');
// Configuration pour le direct (HERO_TYPE = 'live') // Configuration pour le direct (HERO_TYPE = 'live')
// define('LIVE_ACCOUNT_NAME', 'admin'); // Le compte PeerTube du podcast est 'annu_kute_ced'
// define('LIVE_ACCOUNT_NAME', 'annu_kute_ced');
// Configuration pour une vidéo unique (HERO_TYPE = 'video') // Configuration pour une vidéo unique (HERO_TYPE = 'video')
// ID de la vidéo (extrait de l'URL: https://gade.o-k-i.net/w/VIDEO_ID) // ID de la vidéo (extrait de l'URL: https://gade.o-k-i.net/w/VIDEO_ID)
// define('HERO_VIDEO_ID', '1aJ2u9euwF9fWKQhFxwFio'); // define('HERO_VIDEO_ID', 'fDE6tvQMXRuBpE4eDDS7kj');
// Titre de la vidéo (optionnel, pour l'accessibilité) // Titre de la vidéo (optionnel, pour l'accessibilité)
// define('HERO_VIDEO_TITLE', 'Vidéo de présentation'); // define('HERO_VIDEO_TITLE', 'Vidéo de présentation');
// Configuration pour les playlists (HERO_TYPE = 'playlist') // Configuration pour les playlists (HERO_TYPE = 'playlist')
// Type de plateforme: 'peertube', 'funkwhale', 'castopod' // Type de plateforme: 'peertube', 'funkwhale', 'castopod'
// define('PLAYLIST_PLATFORM', 'peertube'); // define('PLAYLIST_PLATFORM', 'castopod');
// URL de base de la plateforme de playlist // URL de base de la plateforme de playlist
// define('PLAYLIST_INSTANCE_URL', 'https://votre-instance.fr'); // define('PLAYLIST_INSTANCE_URL', 'https://kute.o-k-i.net');
// ID de la playlist à afficher // ID de la playlist à afficher
// define('PLAYLIST_ID', 'votre-playlist-uuid'); // Pour Castopod, utilisez le slug du podcast précédé de @ :
// define('PLAYLIST_ID', '@annu_kute_cedric');
// Titre de la playlist (optionnel, sinon récupéré via API) // Titre de la playlist (optionnel, sinon récupéré via API)
// define('PLAYLIST_TITLE', 'Ma Playlist'); // define('PLAYLIST_TITLE', 'ANNU KUTE CED');
// Description de la playlist (optionnel) // Description de la playlist (optionnel)
// define('PLAYLIST_DESCRIPTION', 'Description de la playlist'); // define('PLAYLIST_DESCRIPTION', 'Tous les épisodes du podcast ANNU KUTE CED');
// ========================================= // =========================================
// Filtres et tags // Filtres et tags
// ========================================= // =========================================
// Tag pour les vidéos sur l'indépendance // Tag pour les vidéos sur l'indépendance
// define('TAG_INDEPENDENCE', 'indépendance'); // define('TAG_INDEPENDANCE', 'indépendance');
// Tag pour les shorts // Tag pour les shorts
// define('TAG_SHORT', 'short'); // define('TAG_SHORT', 'short');
// Hashtags importants à afficher dans la sidebar, footer et menu mobile // Hashtags importants à afficher dans la sidebar, footer et menu mobile
define('IMPORTANT_TAGS', [ define('IMPORTANT_TAGS', [
'Chlordécone', 'ANNUKUTECED'
'RCI',
'Mé67',
'Traduction',
'fediverse'
]); ]);
// Hashtags populaires à afficher sur la page d'accueil // Hashtags populaires à afficher sur la page d'accueil
define('POPULAR_TAGS', [ define('POPULAR_TAGS', [
'fediverse', 'ANNUKUTECED'
'guadeloupe',
'Gaza',
'Arte'
]); ]);
// Durée maximale des shorts en secondes // Durée maximale des shorts en secondes
@@ -150,11 +147,8 @@ define('SHORTS_MAX_DURATION', 180); // 3 minutes
// 17 : Kids // 17 : Kids
// 18 : Food // 18 : Food
define('PRIORITY_CATEGORIES', [ define('PRIORITY_CATEGORIES', [
11 => 'Actualités & Politique', 10 => 'Divertissement',
15 => 'Science et Technologie', 15 => 'Science et Technologie'
4 => 'Art',
9 => 'Humour',
10 => 'Divertissement'
// Ajoutez d'autres catégories selon vos besoins // Ajoutez d'autres catégories selon vos besoins
]); ]);
@@ -163,10 +157,10 @@ define('PRIORITY_CATEGORIES', [
// ========================================= // =========================================
// Nom du site // Nom du site
// define('SITE_NAME', 'FEDIVERSE OKI'); // define('SITE_NAME', 'ANNU KUTE CED');
// Description du site // Description du site
// define('SITE_DESCRIPTION', 'Plateforme multimedia indépendante'); // define('SITE_DESCRIPTION', 'Hub multimédia du podcast ANNU KUTE CED');
// Logo du site // Logo du site
// define('SITE_LOGO', 'img/logo.png'); // define('SITE_LOGO', 'img/logo.png');
@@ -198,14 +192,15 @@ define('PRIORITY_CATEGORIES', [
// define('TIKTOK_URL', 'https://tiktok.com/@votrecompte'); // define('TIKTOK_URL', 'https://tiktok.com/@votrecompte');
// URL du compte Mastodon // URL du compte Mastodon
// define('MASTODON_URL', 'https://bokante.o-k-i.net/@admin'); // Compte BOKANTE du podcast : https://bokante.o-k-i.net/@cedric
// define('MASTODON_URL', 'https://bokante.o-k-i.net/@cedric');
// ========================================= // =========================================
// Contact // Contact
// ========================================= // =========================================
// Email de contact // Email de contact
// define('CONTACT_EMAIL', 'contact@votredomaine.com'); // define('CONTACT_EMAIL', 'kontak@o-k-i.net');
// ========================================= // =========================================
// Fonctionnalités // Fonctionnalités
@@ -235,7 +230,8 @@ define('PRIORITY_CATEGORIES', [
// ========================================= // =========================================
// URL de l'instance Mastodon // URL de l'instance Mastodon
// define('MASTODON_INSTANCE_URL', 'https://mastodon.social'); // Instance BOKANTE : https://bokante.o-k-i.net
// define('MASTODON_INSTANCE_URL', 'https://bokante.o-k-i.net');
// Format de date pour l'affichage des posts // Format de date pour l'affichage des posts
// define('MASTODON_DATE_FORMAT', 'fr-FR'); // define('MASTODON_DATE_FORMAT', 'fr-FR');
@@ -261,7 +257,7 @@ define('PRIORITY_CATEGORIES', [
// Mentions légales // Mentions légales
// ========================================= // =========================================
// define('LEGAL_COPYRIGHT', 'OKI'); // define('LEGAL_COPYRIGHT', 'ANNU KUTE CED');
// define('LEGAL_WEBMASTER_NAME', 'Cédric Famibelle-Pronzola'); // define('LEGAL_WEBMASTER_NAME', 'Cédric Famibelle-Pronzola');
// define('LEGAL_WEBMASTER_EMAIL', 'contact@cedric-pronzola.dev'); // define('LEGAL_WEBMASTER_EMAIL', 'contact@cedric-pronzola.dev');
// define('LEGAL_HOST_NAME', 'o2Switch'); // define('LEGAL_HOST_NAME', 'o2Switch');
@@ -271,8 +267,9 @@ define('PRIORITY_CATEGORIES', [
// define('LEGAL_CONTACT_EMAIL', 'kontak@o-k-i.net'); // define('LEGAL_CONTACT_EMAIL', 'kontak@o-k-i.net');
// define('LEGAL_LICENSE', 'GNU Affero General Public License version 3 (AGPL-V3)'); // define('LEGAL_LICENSE', 'GNU Affero General Public License version 3 (AGPL-V3)');
// define('LEGAL_LICENSE_URL', 'https://www.gnu.org/licenses/agpl-3.0.html'); // define('LEGAL_LICENSE_URL', 'https://www.gnu.org/licenses/agpl-3.0.html');
// define('LEGAL_SOURCE_CODE_URL', 'https://codeberg.org/OKI/fediverse.o-k-i.net'); // Source du fork sur LaBola
// define('LEGAL_SERVICE_DESCRIPTION', ' est une plateforme multimédia indépendante.'); // define('LEGAL_SOURCE_CODE_URL', 'https://labola.o-k-i.net/cedric/annu-kute-ced');
// define('LEGAL_SERVICE_DESCRIPTION', ' est le hub multimédia du podcast ANNU KUTE CED.');
// ========================================= // =========================================
// Système de compte à rebours / maintenance // Système de compte à rebours / maintenance
@@ -314,14 +311,15 @@ define('WORDPRESS_ENABLED', false);
// define('CASTOPOD_ENABLED', true); // define('CASTOPOD_ENABLED', true);
// URL de l'instance Castopod // URL de l'instance Castopod
// Instance KUTE : https://kute.o-k-i.net
// define('CASTOPOD_URL', 'https://kute.o-k-i.net'); // define('CASTOPOD_URL', 'https://kute.o-k-i.net');
// Liste des slugs de podcasts à afficher (tableau) // Liste des slugs de podcasts à afficher (tableau)
// Le podcast ANNU KUTE CED est accessible via : https://kute.o-k-i.net/@annu_kute_cedric
// Le feed RSS est : https://kute.o-k-i.net/@annu_kute_cedric/feed
// Les épisodes de tous les podcasts seront mélangés et triés par date // Les épisodes de tous les podcasts seront mélangés et triés par date
// define('CASTOPOD_PODCAST_SLUGS', [ // define('CASTOPOD_PODCAST_SLUGS', [
// 'joukawouve', // 'annu_kute_cedric'
// 'cspcc',
// 'radyobokaz'
// ]); // ]);
// Nombre d'épisodes à afficher (total, tous podcasts confondus) // Nombre d'épisodes à afficher (total, tous podcasts confondus)
@@ -351,10 +349,14 @@ define('WORDPRESS_ENABLED', false);
// ========================================= // =========================================
// Activer/désactiver le système de dons // Activer/désactiver le système de dons
// Par défaut les dons sont activés et pointent vers le compte Liberapay de OKI.
// Décommentez et passez à false pour désactiver complètement la page de dons.
// define('DONATIONS_ENABLED', true); // define('DONATIONS_ENABLED', true);
// URLs des plateformes de don // URLs des plateformes de don
// define('LIBERAPAY_URL', 'https://liberapay.com/votre-compte/donate'); // Les exemples ci-dessous reproduisent la configuration par défaut (OKI).
// Remplacez-les par vos propres comptes si vous déployez une instance indépendante.
// define('LIBERAPAY_URL', 'https://liberapay.com/OKI/donate');
// define('KOFI_URL', 'https://ko-fi.com/votre-compte'); // define('KOFI_URL', 'https://ko-fi.com/votre-compte');
// Activer/désactiver les dons via Stripe // Activer/désactiver les dons via Stripe
@@ -385,27 +387,34 @@ define('WORDPRESS_ENABLED', false);
// Devise pour les dons // Devise pour les dons
// define('DONATION_CURRENCY', 'EUR'); // define('DONATION_CURRENCY', 'EUR');
// Message de transparence affiché sur la page de dons.
// À renseigner si les liens pointent vers les comptes d'une organisation tierce
// (par exemple OKI) afin d'expliquer aux visiteurs pourquoi.
// Les URLs présentes dans ce texte sont automatiquement transformées en liens cliquables.
// Laissez vide ou commentez pour masquer ce message.
// define('DONATIONS_OKI_DISCLAIMER', 'Les liens ci-dessous pointent vers les comptes de donation de l\'association ORGANISATION KA INTERNATIONALE (OKI) — https://o-k-i.net. Pour soutenir spécifiquement OKI : https://o-k-i.net/dons. Pour adapter cette page à votre propre instance, voir la documentation : https://labola.o-k-i.net/cedric/annu-kute-ced');
// ========================================= // =========================================
// Texte de présentation du mouvement // Texte de présentation du podcast
// ========================================= // =========================================
// Titre de la section de présentation (par défaut: "À propos") // Titre de la section de présentation (par défaut: "À propos")
// define('MOVEMENT_TITLE', 'Notre mouvement'); // define('MOVEMENT_TITLE', 'Le podcast ANNU KUTE CED');
// Premier paragraphe de description (pour désactiver le bloc de présentation, commentez cette ligne) // Premier paragraphe de description (pour désactiver le bloc de présentation, commentez cette ligne)
// define('MOVEMENT_DESCRIPTION', 'Nous sommes une association à but non lucratif dédiée à la promotion de nos langues et au traitement de l\'actualité.'); // define('MOVEMENT_DESCRIPTION', 'ANNU KUTE CED est un podcast diffusé sur le Fédivers.');
// Deuxième paragraphe de description (optionnel) // Deuxième paragraphe de description (optionnel)
// define('MOVEMENT_DESCRIPTION_2', 'Parallèlement, nous proposons des alternatives aux géants du numérique comme les GAFAM, en privilégiant l\'utilisation de logiciels libres.'); // define('MOVEMENT_DESCRIPTION_2', 'Retrouvez nos épisodes sur Castopod, nos vidéos sur PeerTube et nos actualités sur Mastodon.');
// Image du mouvement à afficher dans la section de présentation // Image du podcast à afficher dans la section de présentation
// define('MOVEMENT_IMAGE', 'img/movement_presentation.png'); // define('MOVEMENT_IMAGE', 'img/movement_presentation.png');
// Texte alternatif pour l'image du mouvement (accessibilité) // Texte alternatif pour l'image (accessibilité)
// define('MOVEMENT_IMAGE_ALT', 'Texte alternatif pour l\'image du mouvement'); // define('MOVEMENT_IMAGE_ALT', 'Logo du podcast ANNU KUTE CED');
// Légende de l'image (peut contenir du HTML simple comme <br>) // Légende de l'image (peut contenir du HTML simple comme <br>)
// define('MOVEMENT_CAPTION', 'Légende de l\'image'); // define('MOVEMENT_CAPTION', 'ANNU KUTE CED');
// ========================================= // =========================================
@@ -419,7 +428,7 @@ define('WORDPRESS_ENABLED', false);
// define('NEXT_LIVE_TITLE', 'Prochain live'); // define('NEXT_LIVE_TITLE', 'Prochain live');
// Description de l'annonce du prochain live // Description de l'annonce du prochain live
// define('NEXT_LIVE_DESCRIPTION', 'Rejoignez-nous pour notre prochain live !'); // define('NEXT_LIVE_DESCRIPTION', 'Rejoignez-nous pour le prochain enregistrement d\'ANNU KUTE CED !');
// Date du prochain live (format: Y-m-d H:i:s) // Date du prochain live (format: Y-m-d H:i:s)
// define('NEXT_LIVE_DATE', '2025-10-11 10:00:00'); // define('NEXT_LIVE_DATE', '2025-10-11 10:00:00');
+4 -3
View File
@@ -664,7 +664,7 @@ function getCastopodEpisodes($castopodUrl = null, $podcastSlugs = null, $count =
} }
$castopodUrl = $castopodUrl ?? CASTOPOD_URL; $castopodUrl = $castopodUrl ?? CASTOPOD_URL;
$podcastSlugs = $podcastSlugs ?? (defined('CASTOPOD_PODCAST_SLUGS') ? CASTOPOD_PODCAST_SLUGS : ['joukawouve']); $podcastSlugs = $podcastSlugs ?? (defined('CASTOPOD_PODCAST_SLUGS') ? CASTOPOD_PODCAST_SLUGS : ['annu_kute_cedric']);
$count = $count ?? CASTOPOD_EPISODES_COUNT; $count = $count ?? CASTOPOD_EPISODES_COUNT;
// Convertir en tableau si c'est une chaîne unique (rétrocompatibilité) // Convertir en tableau si c'est une chaîne unique (rétrocompatibilité)
@@ -866,8 +866,9 @@ function getCastopodEpisodes($castopodUrl = null, $podcastSlugs = null, $count =
// Limiter au nombre demandé // Limiter au nombre demandé
$allEpisodes = array_slice($allEpisodes, 0, $count); $allEpisodes = array_slice($allEpisodes, 0, $count);
// Mettre en cache // Mettre en cache (uniquement si non vide : une erreur temporaire
if (defined('CACHE_ENABLED') && CACHE_ENABLED) { // — rate limit HTTP 429, timeout… — ne doit pas être figée en cache)
if (!empty($allEpisodes) && defined('CACHE_ENABLED') && CACHE_ENABLED) {
saveToCache($cacheKey, $allEpisodes); saveToCache($cacheKey, $allEpisodes);
} }
+1 -1
View File
@@ -94,6 +94,6 @@
</div> </div>
<div class="footer-copyright"> <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 LEGAL_COPYRIGHT; ?> <?php echo date('Y'); ?> - Licence libre <a href="<?php echo LEGAL_LICENSE_URL; ?>" target="_blank" rel="noopener noreferrer"><?php echo LEGAL_LICENSE; ?></a>
</div> </div>
</div> </div>
+18 -5
View File
@@ -310,18 +310,31 @@ function setSecurityHeaders() {
/** /**
* Valide l'origine de la requête pour les requêtes AJAX * Valide l'origine de la requête pour les requêtes AJAX
* *
* Accepte le header Origin, ou à défaut un Referer same-origin
* (certains navigateurs n'envoient pas Origin sur les requêtes same-origin).
*
* @return bool True si l'origine est valide * @return bool True si l'origine est valide
*/ */
function validateAjaxOrigin() { function validateAjaxOrigin() {
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
$host = $_SERVER['HTTP_HOST'] ?? ''; $host = $_SERVER['HTTP_HOST'] ?? '';
if (empty($host)) {
if (empty($origin) || empty($host)) {
return false; return false;
} }
$expectedOrigin = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http') . '://' . $host; $scheme = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http');
$expectedOrigin = $scheme . '://' . $host;
return $origin === $expectedOrigin; $origin = $_SERVER['HTTP_ORIGIN'] ?? '';
if (!empty($origin)) {
return $origin === $expectedOrigin;
}
// Fallback : vérifier le referer si Origin est absent
$referer = $_SERVER['HTTP_REFERER'] ?? '';
if (!empty($referer)) {
return strpos($referer, $expectedOrigin) === 0;
}
return false;
} }
?> ?>
+144 -1
View File
@@ -29,7 +29,7 @@ function generateWebSiteJsonLd() {
], ],
"publisher" => [ "publisher" => [
"@type" => "Organization", "@type" => "Organization",
"name" => "OKI", "name" => ORGANIZATION_NAME,
"url" => $baseUrl, "url" => $baseUrl,
"logo" => [ "logo" => [
"@type" => "ImageObject", "@type" => "ImageObject",
@@ -69,6 +69,8 @@ function generateVideoObjectJsonLd($videoData, $video) {
? truncateText(strip_tags($video['description']), 300) ? truncateText(strip_tags($video['description']), 300)
: "Regardez cette vidéo sur " . SITE_NAME, : "Regardez cette vidéo sur " . SITE_NAME,
"url" => $videoUrl, "url" => $videoUrl,
"embedUrl" => PEERTUBE_URL . "/videos/embed/" . $video['id'],
"inLanguage" => "fr-FR",
"thumbnailUrl" => $thumbnailUrl, "thumbnailUrl" => $thumbnailUrl,
"uploadDate" => formatDateISO8601($video['date']), "uploadDate" => formatDateISO8601($video['date']),
"duration" => $duration, "duration" => $duration,
@@ -159,6 +161,147 @@ function generateVideoObjectJsonLd($videoData, $video) {
return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
} }
/**
* Génère le JSON-LD pour un podcast (PodcastSeries + PodcastEpisode + AudioObject)
*
* @param array $episodes Épisodes formatés par getCastopodEpisodes()
* @return string JSON-LD pour le podcast, chaîne vide si aucun épisode
*/
function generatePodcastJsonLd($episodes) {
if (empty($episodes)) {
return '';
}
$baseUrl = getBaseUrl();
$first = $episodes[0];
// Informations de la série, déduites du flux RSS
$seriesName = !empty($first['podcastTitle']) ? $first['podcastTitle'] : SITE_NAME;
$seriesUrl = !empty($first['podcastLink']) ? $first['podcastLink'] : $baseUrl;
$seriesImage = !empty($first['image']) ? $first['image'] : $baseUrl . '/img/logo.png';
$webFeed = !empty($first['podcastSlug'])
? rtrim(CASTOPOD_URL, '/') . '/@' . $first['podcastSlug'] . '/feed'
: null;
$series = [
"@type" => "PodcastSeries",
"@id" => $seriesUrl . '#podcast',
"name" => $seriesName,
"url" => $seriesUrl,
"image" => $seriesImage,
"inLanguage" => "fr-FR",
"author" => [
"@type" => "Organization",
"name" => ORGANIZATION_NAME,
"url" => $baseUrl
],
"publisher" => [
"@type" => "Organization",
"name" => SITE_NAME,
"url" => $baseUrl,
"logo" => [
"@type" => "ImageObject",
"url" => $baseUrl . "/img/logo.png"
]
]
];
if ($webFeed) {
$series["webFeed"] = $webFeed;
}
$episodeItems = [];
foreach ($episodes as $episode) {
$item = [
"@type" => "PodcastEpisode",
"name" => $episode['title'],
"url" => $episode['link'],
"partOfSeries" => ["@id" => $seriesUrl . '#podcast']
];
if (!empty($episode['pubDate'])) {
$item["datePublished"] = formatDateISO8601($episode['pubDate']);
}
if (!empty($episode['image'])) {
$item["image"] = $episode['image'];
}
if (!empty($episode['description'])) {
$item["description"] = truncateText($episode['description'], 300);
}
$seconds = parseDurationToSeconds($episode['duration'] ?? '');
if ($seconds > 0) {
$item["timeRequired"] = formatDurationISO8601($seconds);
}
if (!empty($episode['audioUrl'])) {
$audio = [
"@type" => "AudioObject",
"contentUrl" => $episode['audioUrl']
];
$format = guessAudioEncodingFormat($episode['audioUrl']);
if ($format !== null) {
$audio["encodingFormat"] = $format;
}
$item["associatedMedia"] = $audio;
}
$episodeItems[] = $item;
}
$data = [
"@context" => "https://schema.org",
"@graph" => array_merge([$series], $episodeItems)
];
return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
}
/**
* Convertit une durée iTunes en secondes
* Formats acceptés : "3600", "90", "MM:SS", "HH:MM:SS"
*
* @param string $duration Durée brute issue du flux RSS
* @return int Durée en secondes (0 si non parsable)
*/
function parseDurationToSeconds($duration) {
$duration = trim((string) $duration);
if ($duration === '') {
return 0;
}
if (strpos($duration, ':') !== false) {
$parts = array_map('intval', explode(':', $duration));
$seconds = 0;
foreach ($parts as $part) {
$seconds = $seconds * 60 + $part;
}
return $seconds;
}
return max(0, (int) $duration);
}
/**
* Déduit le type MIME d'un fichier audio depuis son extension
*
* @param string $url URL du fichier audio
* @return string|null Type MIME (audio/mpeg, audio/mp4, audio/ogg) ou null
*/
function guessAudioEncodingFormat($url) {
$extension = strtolower(pathinfo(parse_url($url, PHP_URL_PATH) ?? '', PATHINFO_EXTENSION));
$map = [
'mp3' => 'audio/mpeg',
'm4a' => 'audio/mp4',
'mp4' => 'audio/mp4',
'ogg' => 'audio/ogg',
'opus' => 'audio/ogg',
'wav' => 'audio/wav'
];
return $map[$extension] ?? null;
}
/** /**
* Génère le JSON-LD pour les fils d'Ariane (BreadcrumbList) * Génère le JSON-LD pour les fils d'Ariane (BreadcrumbList)
* *
+32 -41
View File
@@ -13,6 +13,12 @@ if (defined('COUNTDOWN_ENABLED') && COUNTDOWN_ENABLED === true) {
require_once 'includes/structured-data.php'; require_once 'includes/structured-data.php';
// Appliquer les en-têtes de sécurité // Appliquer les en-têtes de sécurité
setSecurityHeaders(); setSecurityHeaders();
// Récupérer les épisodes Castopod (réutilisés pour le JSON-LD et l'affichage)
$castopodEpisodes = [];
if (defined('CASTOPOD_ENABLED') && CASTOPOD_ENABLED && defined('CASTOPOD_URL') && !empty(CASTOPOD_URL)) {
$castopodEpisodes = getCastopodEpisodes();
}
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="fr"> <html lang="fr">
@@ -21,6 +27,8 @@ setSecurityHeaders();
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="csrf-token" content="<?php echo generateCSRFToken(); ?>"> <meta name="csrf-token" content="<?php echo generateCSRFToken(); ?>">
<title><?php echo SITE_NAME; ?></title> <title><?php echo SITE_NAME; ?></title>
<meta name="description" content="<?php echo htmlspecialchars(SITE_DESCRIPTION); ?>">
<link rel="canonical" href="<?php echo getBaseUrl() . '/'; ?>">
<link rel="stylesheet" href="css/styles.css?v=<?php echo filemtime('css/styles.css'); ?>"> <link rel="stylesheet" href="css/styles.css?v=<?php echo filemtime('css/styles.css'); ?>">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css">
<link rel="stylesheet" href="css/mastodon-timeline.min.css?v=<?php echo filemtime('css/mastodon-timeline.min.css'); ?>"> <link rel="stylesheet" href="css/mastodon-timeline.min.css?v=<?php echo filemtime('css/mastodon-timeline.min.css'); ?>">
@@ -54,9 +62,9 @@ setSecurityHeaders();
<!-- Open Graph Meta Tags --> <!-- Open Graph Meta Tags -->
<meta property="og:title" content="<?php echo SITE_NAME; ?>"> <meta property="og:title" content="<?php echo SITE_NAME; ?>">
<meta property="og:description" content="Découvrez notre plateforme multimédia avec des vidéos, des shorts et des directs. Tendances, catégories et contenus exclusifs vous attendent."> <meta property="og:description" content="<?php echo htmlspecialchars(SITE_DESCRIPTION); ?>">
<meta property="og:image" content="<?php echo 'https://' . $_SERVER['HTTP_HOST'] . '/img/logo.png'; ?>"> <meta property="og:image" content="<?php echo getBaseUrl() . '/img/logo.png'; ?>">
<meta property="og:url" content="<?php echo 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; ?>"> <meta property="og:url" content="<?php echo getBaseUrl() . '/'; ?>">
<meta property="og:type" content="website"> <meta property="og:type" content="website">
<meta property="og:site_name" content="<?php echo SITE_NAME; ?>"> <meta property="og:site_name" content="<?php echo SITE_NAME; ?>">
<meta property="og:locale" content="fr_FR"> <meta property="og:locale" content="fr_FR">
@@ -64,8 +72,8 @@ setSecurityHeaders();
<!-- Twitter Card Meta Tags --> <!-- Twitter Card Meta Tags -->
<meta name="twitter:card" content="summary_large_image"> <meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="<?php echo SITE_NAME; ?>"> <meta name="twitter:title" content="<?php echo SITE_NAME; ?>">
<meta name="twitter:description" content="Découvrez notre plateforme multimédia avec des vidéos, des shorts et des directs. Tendances, catégories et contenus exclusifs vous attendent."> <meta name="twitter:description" content="<?php echo htmlspecialchars(SITE_DESCRIPTION); ?>">
<meta name="twitter:image" content="<?php echo 'https://' . $_SERVER['HTTP_HOST'] . '/img/logo.png'; ?>"> <meta name="twitter:image" content="<?php echo getBaseUrl() . '/img/logo.png'; ?>">
<!-- Données structurées JSON-LD pour le site web --> <!-- Données structurées JSON-LD pour le site web -->
<?php <?php
@@ -78,6 +86,11 @@ setSecurityHeaders();
]; ];
$breadcrumbJsonLd = generateBreadcrumbJsonLd($breadcrumbs); $breadcrumbJsonLd = generateBreadcrumbJsonLd($breadcrumbs);
outputJsonLd($breadcrumbJsonLd); outputJsonLd($breadcrumbJsonLd);
// Données structurées du podcast (PodcastSeries + PodcastEpisode)
if (!empty($castopodEpisodes)) {
outputJsonLd(generatePodcastJsonLd($castopodEpisodes));
}
?> ?>
<!-- Script pour éviter le flash en mode sombre --> <!-- Script pour éviter le flash en mode sombre -->
@@ -105,6 +118,7 @@ setSecurityHeaders();
<?php include 'includes/sidebar.php'; ?> <?php include 'includes/sidebar.php'; ?>
<!-- Contenu principal --> <!-- Contenu principal -->
<main class="main-content" id="main-content" role="main"> <main class="main-content" id="main-content" role="main">
<h1 class="sr-only"><?php echo SITE_NAME . ' — ' . SITE_DESCRIPTION; ?></h1>
<?php include 'includes/header.php'; ?> <?php include 'includes/header.php'; ?>
<!-- Hero and Mastodon container --> <!-- Hero and Mastodon container -->
<div class="hero-mastodon-wrapper"> <div class="hero-mastodon-wrapper">
@@ -130,8 +144,7 @@ setSecurityHeaders();
</div> </div>
<div class="castopod-episodes-list"> <div class="castopod-episodes-list">
<?php <?php
$castopodEpisodes = getCastopodEpisodes(); // $castopodEpisodes a été récupéré en début de page (JSON-LD + affichage)
if (empty($castopodEpisodes)) { if (empty($castopodEpisodes)) {
echo '<div class="castopod-no-episodes">Aucun épisode disponible</div>'; echo '<div class="castopod-no-episodes">Aucun épisode disponible</div>';
} else { } else {
@@ -390,7 +403,7 @@ setSecurityHeaders();
<section class="video-section" aria-labelledby="shorts-heading"> <section class="video-section" aria-labelledby="shorts-heading">
<div class="section-header"> <div class="section-header">
<div class="section-logo"> <div class="section-logo">
<img src="img/logo.png" alt="Logo <?php echo SITE_NAME; ?>" aria-hidden="true"> <img src="img/logo.png" alt="" aria-hidden="true">
</div> </div>
<h2 id="shorts-heading" class="section-title">Shorts</h2> <h2 id="shorts-heading" class="section-title">Shorts</h2>
</div> </div>
@@ -446,7 +459,7 @@ setSecurityHeaders();
<section class="video-section" aria-labelledby="recent-videos-heading"> <section class="video-section" aria-labelledby="recent-videos-heading">
<header class="section-header"> <header class="section-header">
<div class="section-logo"> <div class="section-logo">
<img src="img/logo.png" alt="Logo <?php echo SITE_NAME; ?>" aria-hidden="true"> <img src="img/logo.png" alt="" aria-hidden="true">
</div> </div>
<h2 id="recent-videos-heading" class="section-title">Dernières vidéos</h2> <h2 id="recent-videos-heading" class="section-title">Dernières vidéos</h2>
</header> </header>
@@ -508,7 +521,7 @@ setSecurityHeaders();
<section class="video-section" aria-labelledby="trending-videos-heading"> <section class="video-section" aria-labelledby="trending-videos-heading">
<header class="section-header"> <header class="section-header">
<div class="section-logo"> <div class="section-logo">
<img src="img/logo.png" alt="Logo <?php echo SITE_NAME; ?>" aria-hidden="true"> <img src="img/logo.png" alt="" aria-hidden="true">
</div> </div>
<h2 id="trending-videos-heading" class="section-title">Tendances</h2> <h2 id="trending-videos-heading" class="section-title">Tendances</h2>
</header> </header>
@@ -526,7 +539,7 @@ setSecurityHeaders();
?> ?>
<article class="video-card" data-video-id="<?php echo $video['id']; ?>"> <article class="video-card" data-video-id="<?php echo $video['id']; ?>">
<div class="video-thumbnail"> <div class="video-thumbnail">
<img src="<?php echo $video['thumbnail']; ?>" alt="<?php echo $video['title']; ?>"> <img src="<?php echo htmlspecialchars($video['thumbnail']); ?>" alt="<?php echo htmlspecialchars($video['title']); ?>">
<div class="video-play-icon"> <div class="video-play-icon">
<i class="fas fa-play-circle"></i> <i class="fas fa-play-circle"></i>
</div> </div>
@@ -577,7 +590,7 @@ setSecurityHeaders();
<section class="video-section" data-category-id="<?php echo $category['id']; ?>" aria-labelledby="category-heading-<?php echo $category['id']; ?>"> <section class="video-section" data-category-id="<?php echo $category['id']; ?>" aria-labelledby="category-heading-<?php echo $category['id']; ?>">
<header class="section-header"> <header class="section-header">
<div class="section-logo"> <div class="section-logo">
<img src="img/logo.png" alt="Logo <?php echo SITE_NAME; ?>" aria-hidden="true"> <img src="img/logo.png" alt="" aria-hidden="true">
</div> </div>
<h2 id="category-heading-<?php echo $category['id']; ?>" class="section-title"><?php echo htmlspecialchars($category['name']); ?></h2> <h2 id="category-heading-<?php echo $category['id']; ?>" class="section-title"><?php echo htmlspecialchars($category['name']); ?></h2>
</header> </header>
@@ -586,7 +599,7 @@ setSecurityHeaders();
<?php foreach ($category['videos'] as $video): ?> <?php foreach ($category['videos'] as $video): ?>
<article class="video-card" data-video-id="<?php echo $video['id']; ?>"> <article class="video-card" data-video-id="<?php echo $video['id']; ?>">
<div class="video-thumbnail"> <div class="video-thumbnail">
<img src="<?php echo $video['thumbnail']; ?>" alt="<?php echo $video['title']; ?>"> <img src="<?php echo htmlspecialchars($video['thumbnail']); ?>" alt="<?php echo htmlspecialchars($video['title']); ?>">
<div class="video-play-icon"> <div class="video-play-icon">
<i class="fas fa-play-circle"></i> <i class="fas fa-play-circle"></i>
</div> </div>
@@ -654,7 +667,8 @@ setSecurityHeaders();
<!-- Section Tendances Hashtags --> <!-- Section Tendances Hashtags -->
<aside class="tags-section-container" aria-labelledby="hashtags-heading"> <aside class="tags-section-container" aria-labelledby="hashtags-heading">
<h2 id="hashtags-heading" class="section-title centered">Tendances</h2> <h2 id="hashtags-heading" class="section-title centered"><i class="fas fa-fire trending-title-icon" aria-hidden="true"></i> Tendances</h2>
<p class="trending-hashtag-emojis" aria-hidden="true">🎙️🔥📈🔝⬆️↗️🚩🇲🇶</p>
<div class="tags-section"> <div class="tags-section">
<?php <?php
@@ -678,34 +692,11 @@ setSecurityHeaders();
<script src="js/mastodon-timeline.umd.js"></script> <script src="js/mastodon-timeline.umd.js"></script>
<script src="js/mastodon-config.php?v=<?php echo md5(MASTODON_INSTANCE_URL . MASTODON_DATE_FORMAT . MASTODON_BTN_SEE_MORE . MASTODON_BTN_RELOAD . MASTODON_MAX_POST_FETCH . MASTODON_MAX_POST_SHOW); ?>"></script> <script src="js/mastodon-config.php?v=<?php echo md5(MASTODON_INSTANCE_URL . MASTODON_DATE_FORMAT . MASTODON_BTN_SEE_MORE . MASTODON_BTN_RELOAD . MASTODON_MAX_POST_FETCH . MASTODON_MAX_POST_SHOW); ?>"></script>
<!-- PWA Service Worker --> <!-- PWA : enregistrement du Service Worker + modal de mise à jour -->
<script src="js/pwa-update.js?v=<?php echo filemtime('js/pwa-update.js'); ?>"></script>
<!-- PWA : bouton d'installation -->
<script nonce="<?php echo getCspNonce(); ?>"> <script nonce="<?php echo getCspNonce(); ?>">
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/sw.js')
.then(function(registration) {
console.log('Service Worker enregistré avec succès:', registration.scope);
// Écouter les mises à jour
registration.addEventListener('updatefound', function() {
const newWorker = registration.installing;
newWorker.addEventListener('statechange', function() {
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
// Nouvelle version disponible
console.log('Nouvelle version disponible');
if (confirm('Une nouvelle version est disponible. Voulez-vous recharger la page ?')) {
window.location.reload();
}
}
});
});
})
.catch(function(err) {
console.log('Échec de l\'enregistrement du Service Worker:', err);
});
});
}
// Gestion de l'installation PWA // Gestion de l'installation PWA
let deferredPrompt; let deferredPrompt;
const installButton = document.getElementById('install-pwa'); const installButton = document.getElementById('install-pwa');
-97
View File
@@ -1,97 +0,0 @@
document.addEventListener("DOMContentLoaded", () => {
// Gestion des clics sur les vidéos
const videoCards = document.querySelectorAll(".video-card");
for (const videoCard of videoCards) {
videoCard.addEventListener("click", function () {
const videoId = this.dataset.videoId;
if (videoId) {
window.location.href = `video.php?id=${videoId}`;
}
});
}
// Gestion du bouton "Voir plus"
const viewMoreBtn = document.querySelector(".view-more");
if (viewMoreBtn) {
viewMoreBtn.addEventListener("click", function () {
const page = Number.parseInt(this.dataset.page);
const categoryId =
document.querySelector(".video-section").dataset.categoryId;
// Changer le texte du bouton pendant le chargement
this.textContent = "Chargement...";
this.disabled = true;
// Préparer les données avec token CSRF
const formData = new FormData();
formData.append('csrf_token', document.querySelector('meta[name="csrf-token"]').getAttribute('content'));
// Faire la requête AJAX
fetch(
`ajax/load-more-videos.php?type=category&page=${page}&category=${categoryId}`,
{
method: 'POST',
headers: {
"X-Requested-With": "XMLHttpRequest",
},
body: formData
}
)
.then((response) => response.json())
.then((data) => {
if (data.success) {
// Ajouter les nouvelles vidéos à la grille
const videoGrid = document.querySelector(".video-grid");
const tempDiv = document.createElement("div");
tempDiv.innerHTML = data.html;
// Ajouter chaque vidéo à la grille
while (tempDiv.firstChild) {
videoGrid.appendChild(tempDiv.firstChild);
}
// Mettre à jour le numéro de page
this.dataset.page = data.page + 1;
// Réinitialiser le texte du bouton
this.textContent = "Voir plus";
this.disabled = false;
// Si plus de vidéos à charger, masquer le bouton
if (!data.hasMore) {
this.style.display = "none";
}
// Initialiser les clics sur les nouvelles vidéos
const cards = document.querySelectorAll(".video-card:not([data-click-initialized])")
for (const card of cards) {
card.setAttribute("data-click-initialized", "true");
card.addEventListener("click", function () {
const videoId = this.dataset.videoId;
if (videoId) {
window.location.href = `video.php?id=${videoId}`;
}
});
}
} else {
// Gérer l'erreur
this.textContent = "Erreur lors du chargement";
setTimeout(() => {
this.textContent = "Voir plus";
this.disabled = false;
}, 2000);
}
})
.catch((error) => {
console.error("Erreur:", error);
this.textContent = "Erreur lors du chargement";
setTimeout(() => {
this.textContent = "Voir plus";
this.disabled = false;
}, 2000);
});
});
}
});
+11 -3
View File
@@ -502,16 +502,24 @@ document.addEventListener('DOMContentLoaded', function() {
// Initialiser le lazy loading pour les nouvelles images // Initialiser le lazy loading pour les nouvelles images
initLazyLoading(); initLazyLoading();
} else { } else {
// En cas d'erreur, afficher un message et réactiver le bouton // En cas d'erreur, afficher un message temporaire et réactiver le bouton
console.error('Erreur lors du chargement des vidéos:', data.error); console.error('Erreur lors du chargement des vidéos:', data.error);
button.textContent = 'Voir plus'; const originalText = 'Voir plus';
button.textContent = 'Erreur de chargement';
button.disabled = false; button.disabled = false;
setTimeout(() => {
button.textContent = originalText;
}, 2000);
} }
}) })
.catch(error => { .catch(error => {
console.error('Erreur lors de la requête AJAX:', error); console.error('Erreur lors de la requête AJAX:', error);
button.textContent = 'Voir plus'; const originalText = 'Voir plus';
button.textContent = 'Erreur de chargement';
button.disabled = false; button.disabled = false;
setTimeout(() => {
button.textContent = originalText;
}, 2000);
}); });
}); });
}); });
+138
View File
@@ -0,0 +1,138 @@
/**
* Enregistrement du Service Worker et gestion des mises à jour.
*
* Quand une nouvelle version du site est déployée (sw.js modifié, suffixe de
* version des caches bumpé), le nouveau Service Worker s'installe en arrière-
* plan et reste en attente. Un modal propose alors la mise à jour :
* - « Mettre à jour » : le SW en attente prend le contrôle (SKIP_WAITING),
* les anciens caches sont purgés à l'activation, puis la page se recharge
* sur la nouvelle version (controllerchange).
* - « Plus tard » : le modal se ferme ; la mise à jour sera reproposée au
* prochain chargement de page.
*
* Aucune purge manuelle du cache navigateur n'est nécessaire.
*/
(function () {
'use strict';
if (!('serviceWorker' in navigator)) {
return;
}
let modalShown = false;
let refreshing = false;
// Recharge la page quand le nouveau Service Worker prend le contrôle
navigator.serviceWorker.addEventListener('controllerchange', function () {
if (refreshing) {
return;
}
refreshing = true;
window.location.reload();
});
/**
* Construit et affiche le modal de mise à jour (une seule fois).
* @param {ServiceWorker} waitingWorker Le SW en attente d'activation
*/
function showUpdateModal(waitingWorker) {
if (modalShown || !waitingWorker) {
return;
}
modalShown = true;
const overlay = document.createElement('div');
overlay.className = 'pwa-update-overlay';
overlay.setAttribute('role', 'dialog');
overlay.setAttribute('aria-modal', 'true');
overlay.setAttribute('aria-labelledby', 'pwa-update-title');
overlay.innerHTML =
'<div class="pwa-update-modal">' +
'<img class="pwa-update-logo" src="img/logo.png" alt="" aria-hidden="true">' +
'<h2 class="pwa-update-title" id="pwa-update-title">Nouvelle version disponible</h2>' +
'<p class="pwa-update-text">' +
'Une mise à jour du site est prête à être installée. ' +
'Elle est rapide et ne supprime aucune de vos données.' +
'</p>' +
'<div class="pwa-update-actions">' +
'<button type="button" class="pwa-update-btn pwa-update-btn-primary">' +
'Mettre à jour' +
'</button>' +
'<button type="button" class="pwa-update-btn pwa-update-btn-secondary">' +
'Plus tard' +
'</button>' +
'</div>' +
'</div>';
document.body.appendChild(overlay);
// Force le reflow pour que la transition CSS d'entrée joue
void overlay.offsetWidth;
overlay.classList.add('pwa-update-visible');
const primaryBtn = overlay.querySelector('.pwa-update-btn-primary');
const secondaryBtn = overlay.querySelector('.pwa-update-btn-secondary');
function closeModal() {
overlay.classList.remove('pwa-update-visible');
overlay.addEventListener('transitionend', function () {
overlay.remove();
}, { once: true });
}
primaryBtn.addEventListener('click', function () {
primaryBtn.disabled = true;
secondaryBtn.disabled = true;
primaryBtn.textContent = 'Mise à jour…';
// Le rechargement est déclenché par l'event controllerchange
waitingWorker.postMessage({ type: 'SKIP_WAITING' });
});
secondaryBtn.addEventListener('click', closeModal);
document.addEventListener('keydown', function onEscape(event) {
if (event.key === 'Escape' && document.body.contains(overlay)) {
closeModal();
document.removeEventListener('keydown', onEscape);
}
});
// Accessibilité : focus sur l'action principale
primaryBtn.focus();
}
/**
* Surveille l'installation d'un nouveau Service Worker.
* @param {ServiceWorkerRegistration} registration
*/
function trackInstalling(registration) {
registration.addEventListener('updatefound', function () {
const newWorker = registration.installing;
if (!newWorker) {
return;
}
newWorker.addEventListener('statechange', function () {
// installed + un contrôleur actif = mise à jour en attente
// (sans contrôleur, c'est la toute première installation)
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
showUpdateModal(registration.waiting);
}
});
});
}
window.addEventListener('load', function () {
navigator.serviceWorker.register('/sw.js')
.then(function (registration) {
// Cas où une mise à jour est déjà en attente (installée lors
// d'une visite précédente, jamais activée)
if (registration.waiting && navigator.serviceWorker.controller) {
showUpdateModal(registration.waiting);
}
trackInstalling(registration);
})
.catch(function (err) {
console.log('Échec de l\'enregistrement du Service Worker:', err);
});
});
})();
+25 -17
View File
@@ -1,9 +1,17 @@
<?php
// Inclure la configuration
require_once 'includes/config.php';
// Appliquer les en-têtes de sécurité
setSecurityHeaders();
?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="fr"> <html lang="fr">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mentions Légales - FEDIVERSE OKI</title> <title>Mentions Légales - <?php echo SITE_NAME; ?></title>
<meta name="description" content="Consultez les mentions légales de <?php echo SITE_NAME; ?>. Informations légales, conditions d'utilisation et politique de confidentialité.">
<link rel="canonical" href="<?php echo 'https://' . $_SERVER['HTTP_HOST'] . '/mentions-legales.php'; ?>">
<link rel="stylesheet" href="css/styles.css?v=<?php echo filemtime('css/styles.css'); ?>"> <link rel="stylesheet" href="css/styles.css?v=<?php echo filemtime('css/styles.css'); ?>">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css">
@@ -13,25 +21,25 @@
<link rel="icon" type="image/png" sizes="16x16" href="img/favicon-16x16.png"> <link rel="icon" type="image/png" sizes="16x16" href="img/favicon-16x16.png">
<link rel="manifest" href="site.webmanifest"> <link rel="manifest" href="site.webmanifest">
<link rel="icon" type="image/x-icon" href="img/favicon.ico"> <link rel="icon" type="image/x-icon" href="img/favicon.ico">
<meta name="theme-color" content="#ffffff"> <meta name="theme-color" content="#FF0000">
<!-- Open Graph Meta Tags --> <!-- Open Graph Meta Tags -->
<meta property="og:title" content="Mentions Légales - FEDIVERSE OKI"> <meta property="og:title" content="Mentions Légales - <?php echo SITE_NAME; ?>">
<meta property="og:description" content="Consultez les mentions légales de FEDIVERSE OKI. Informations légales, conditions d'utilisation et politique de confidentialité de notre plateforme multimédia."> <meta property="og:description" content="Consultez les mentions légales de <?php echo SITE_NAME; ?>. Informations légales, conditions d'utilisation et politique de confidentialité.">
<meta property="og:image" content="<?php echo 'https://' . $_SERVER['HTTP_HOST'] . '/img/logo.png'; ?>"> <meta property="og:image" content="<?php echo 'https://' . $_SERVER['HTTP_HOST'] . '/img/logo.png'; ?>">
<meta property="og:url" content="<?php echo 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; ?>"> <meta property="og:url" content="<?php echo 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; ?>">
<meta property="og:type" content="website"> <meta property="og:type" content="website">
<meta property="og:site_name" content="FEDIVERSE OKI"> <meta property="og:site_name" content="<?php echo SITE_NAME; ?>">
<meta property="og:locale" content="fr_FR"> <meta property="og:locale" content="fr_FR">
<!-- Twitter Card Meta Tags --> <!-- Twitter Card Meta Tags -->
<meta name="twitter:card" content="summary_large_image"> <meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Mentions Légales - FEDIVERSE OKI"> <meta name="twitter:title" content="Mentions Légales - <?php echo SITE_NAME; ?>">
<meta name="twitter:description" content="Consultez les mentions légales de FEDIVERSE OKI. Informations légales, conditions d'utilisation et politique de confidentialité de notre plateforme multimédia."> <meta name="twitter:description" content="Consultez les mentions légales de <?php echo SITE_NAME; ?>. Informations légales, conditions d'utilisation et politique de confidentialité.">
<meta name="twitter:image" content="<?php echo 'https://' . $_SERVER['HTTP_HOST'] . '/img/logo.png'; ?>"> <meta name="twitter:image" content="<?php echo 'https://' . $_SERVER['HTTP_HOST'] . '/img/logo.png'; ?>">
<!-- Script pour éviter le flash en mode sombre --> <!-- Script pour éviter le flash en mode sombre -->
<script> <script nonce="<?php echo getCspNonce(); ?>">
(function() { (function() {
const savedTheme = localStorage.getItem('theme'); const savedTheme = localStorage.getItem('theme');
const systemPrefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; const systemPrefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
@@ -44,12 +52,6 @@
</script> </script>
</head> </head>
<body> <body>
<?php
// Inclure la configuration
require_once 'includes/config.php';
// Appliquer les en-têtes de sécurité
setSecurityHeaders();
?>
<?php include 'includes/sidebar.php'; ?> <?php include 'includes/sidebar.php'; ?>
<!-- Contenu principal --> <!-- Contenu principal -->
<div class="main-content"> <div class="main-content">
@@ -58,9 +60,9 @@
<!-- Section Mentions Légales --> <!-- Section Mentions Légales -->
<div class="section-header"> <div class="section-header">
<div class="section-logo"> <div class="section-logo">
<img src="img/logo.png" alt="FEDIVERSE OKI"> <img src="img/logo.png" alt="<?php echo SITE_NAME; ?>">
</div> </div>
<h2 class="section-title">Mentions Légales</h2> <h1 class="section-title">Mentions Légales</h1>
</div> </div>
<div class="info-section"> <div class="info-section">
@@ -73,6 +75,7 @@
<h3 class="info-header">2. Description du service</h3> <h3 class="info-header">2. Description du service</h3>
<p class="info-text"> <p class="info-text">
<strong><?php echo SITE_NAME; ?></strong> <?php echo LEGAL_SERVICE_DESCRIPTION; ?> <strong><?php echo SITE_NAME; ?></strong> <?php echo LEGAL_SERVICE_DESCRIPTION; ?>
Le site agrège les contenus du podcast disponibles sur PeerTube, Castopod et Mastodon.
</p> </p>
<h3 class="info-header">3. Responsabilité des utilisateurs</h3> <h3 class="info-header">3. Responsabilité des utilisateurs</h3>
@@ -184,7 +187,11 @@
</p> </p>
<p class="info-text"> <p class="info-text">
Le code source de ce site est disponible sur Codeberg : <a href="<?php echo LEGAL_SOURCE_CODE_URL; ?>" target="_blank" rel="noopener noreferrer"><?php echo LEGAL_SOURCE_CODE_URL; ?></a> Le code source de ce site est disponible sur LaBola : <a href="<?php echo LEGAL_SOURCE_CODE_URL; ?>" target="_blank" rel="noopener noreferrer"><?php echo LEGAL_SOURCE_CODE_URL; ?></a>
</p>
<p class="info-text">
<strong>ANNU KUTE CED</strong> est un fork de <strong>FEDIVERSE OKI</strong>, développé par l'ORGANISATION KA INTERNATIONALE (OKI) sous licence AGPL-V3. Le fork est maintenu par le propriétaire du dépôt <em>cedric</em> (Cédric Famibelle-Pronzola), en accord avec la licence d'origine.
</p> </p>
<h3 class="info-header">9. Icônes et marques tierces</h3> <h3 class="info-header">9. Icônes et marques tierces</h3>
@@ -238,5 +245,6 @@
<?php include 'includes/footer.php'; ?> <?php include 'includes/footer.php'; ?>
<?php include 'includes/mobile-menu.php'; ?> <?php include 'includes/mobile-menu.php'; ?>
<script src="js/main.js"></script> <script src="js/main.js"></script>
<script src="js/pwa-update.js?v=<?php echo filemtime('js/pwa-update.js'); ?>"></script>
</body> </body>
</html> </html>
+14 -6
View File
@@ -53,6 +53,13 @@ if ($resultsCount > 0) {
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo !empty($query) ? 'Recherche: ' . htmlspecialchars($query) . ' - ' : 'Recherche - '; ?><?php echo SITE_NAME; ?></title> <title><?php echo !empty($query) ? 'Recherche: ' . htmlspecialchars($query) . ' - ' : 'Recherche - '; ?><?php echo SITE_NAME; ?></title>
<meta name="description" content="<?php echo !empty($query) ? 'Résultats de recherche pour « ' . htmlspecialchars($query) . ' » sur ' . SITE_NAME . '. Découvrez des vidéos correspondantes à votre recherche.' : 'Recherchez des vidéos sur ' . SITE_NAME . '. ' . SITE_DESCRIPTION; ?>">
<?php if (!empty($query)): ?>
<meta name="robots" content="noindex, follow">
<link rel="canonical" href="<?php echo getBaseUrl() . '/recherche.php?q=' . urlencode($query); ?>">
<?php else: ?>
<link rel="canonical" href="<?php echo getBaseUrl() . '/recherche.php'; ?>">
<?php endif; ?>
<link rel="stylesheet" href="css/styles.css?v=<?php echo filemtime('css/styles.css'); ?>"> <link rel="stylesheet" href="css/styles.css?v=<?php echo filemtime('css/styles.css'); ?>">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css">
@@ -62,11 +69,11 @@ if ($resultsCount > 0) {
<link rel="icon" type="image/png" sizes="16x16" href="img/favicon-16x16.png"> <link rel="icon" type="image/png" sizes="16x16" href="img/favicon-16x16.png">
<link rel="manifest" href="site.webmanifest"> <link rel="manifest" href="site.webmanifest">
<link rel="icon" type="image/x-icon" href="img/favicon.ico"> <link rel="icon" type="image/x-icon" href="img/favicon.ico">
<meta name="theme-color" content="#ffffff"> <meta name="theme-color" content="#FF0000">
<!-- Open Graph Meta Tags --> <!-- Open Graph Meta Tags -->
<meta property="og:title" content="<?php echo !empty($query) ? 'Recherche: ' . htmlspecialchars($query) . ' - ' : 'Recherche - '; ?><?php echo SITE_NAME; ?>"> <meta property="og:title" content="<?php echo !empty($query) ? 'Recherche: ' . htmlspecialchars($query) . ' - ' : 'Recherche - '; ?><?php echo SITE_NAME; ?>">
<meta property="og:description" content="<?php echo !empty($query) ? 'Résultats de recherche pour \"' . htmlspecialchars($query) . '\" sur ' . SITE_NAME . '. Découvrez des vidéos correspondantes à votre recherche.' : 'Recherchez des vidéos sur ' . SITE_NAME . '. Plateforme multimédia avec un contenu de qualité et exclusif.'; ?>"> <meta property="og:description" content="<?php echo !empty($query) ? 'Résultats de recherche pour « ' . htmlspecialchars($query) . ' » sur ' . SITE_NAME . '. Découvrez des vidéos correspondantes à votre recherche.' : 'Recherchez des vidéos sur ' . SITE_NAME . '. ' . SITE_DESCRIPTION; ?>">
<meta property="og:image" content="<?php echo 'https://' . $_SERVER['HTTP_HOST'] . '/img/logo.png'; ?>"> <meta property="og:image" content="<?php echo 'https://' . $_SERVER['HTTP_HOST'] . '/img/logo.png'; ?>">
<meta property="og:url" content="<?php echo 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; ?>"> <meta property="og:url" content="<?php echo 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; ?>">
<meta property="og:type" content="website"> <meta property="og:type" content="website">
@@ -76,7 +83,7 @@ if ($resultsCount > 0) {
<!-- Twitter Card Meta Tags --> <!-- Twitter Card Meta Tags -->
<meta name="twitter:card" content="summary_large_image"> <meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="<?php echo !empty($query) ? 'Recherche: ' . htmlspecialchars($query) . ' - ' : 'Recherche - '; ?><?php echo SITE_NAME; ?>"> <meta name="twitter:title" content="<?php echo !empty($query) ? 'Recherche: ' . htmlspecialchars($query) . ' - ' : 'Recherche - '; ?><?php echo SITE_NAME; ?>">
<meta name="twitter:description" content="<?php echo !empty($query) ? 'Résultats de recherche pour \"' . htmlspecialchars($query) . '\" sur ' . SITE_NAME . '. Découvrez des vidéos correspondantes à votre recherche.' : 'Recherchez des vidéos sur ' . SITE_NAME . '. Plateforme multimédia avec un contenu de qualité et exclusif.'; ?>"> <meta name="twitter:description" content="<?php echo !empty($query) ? 'Résultats de recherche pour « ' . htmlspecialchars($query) . ' » sur ' . SITE_NAME . '. Découvrez des vidéos correspondantes à votre recherche.' : 'Recherchez des vidéos sur ' . SITE_NAME . '. ' . SITE_DESCRIPTION; ?>">
<meta name="twitter:image" content="<?php echo 'https://' . $_SERVER['HTTP_HOST'] . '/img/logo.png'; ?>"> <meta name="twitter:image" content="<?php echo 'https://' . $_SERVER['HTTP_HOST'] . '/img/logo.png'; ?>">
<?php if (!empty($query) && !empty($currentPageVideos)): ?> <?php if (!empty($query) && !empty($currentPageVideos)): ?>
@@ -132,12 +139,12 @@ if ($resultsCount > 0) {
</div> </div>
<?php if (!empty($query)): ?> <?php if (!empty($query)): ?>
<?php if ($isTagSearch): ?> <?php if ($isTagSearch): ?>
<h2 class="section-title">Vidéos avec le hashtag : "<?php echo htmlspecialchars($searchTag); ?>"</h2> <h1 class="section-title">Vidéos avec le hashtag : "<?php echo htmlspecialchars($searchTag); ?>"</h1>
<?php else: ?> <?php else: ?>
<h2 class="section-title">Résultats pour : "<?php echo htmlspecialchars($query); ?>"</h2> <h1 class="section-title">Résultats pour : "<?php echo htmlspecialchars($query); ?>"</h1>
<?php endif; ?> <?php endif; ?>
<?php else: ?> <?php else: ?>
<h2 class="section-title">Rechercher des vidéos</h2> <h1 class="section-title">Rechercher des vidéos</h1>
<?php endif; ?> <?php endif; ?>
</div> </div>
@@ -261,5 +268,6 @@ if ($resultsCount > 0) {
<script src="js/main.js"></script> <script src="js/main.js"></script>
<script src="js/search.js"></script> <script src="js/search.js"></script>
<script src="js/pwa-update.js?v=<?php echo filemtime('js/pwa-update.js'); ?>"></script>
</body> </body>
</html> </html>
+1 -1
View File
@@ -22,4 +22,4 @@ Disallow: /conf/
Disallow: /cache/ Disallow: /cache/
# Sitemap # Sitemap
Sitemap: https://VOTRE-DOMAINE/sitemap.xml Sitemap: https://example.com/sitemap.xml
+110
View File
@@ -0,0 +1,110 @@
#!/usr/bin/env bash
# Lance en local l'ensemble des vérifications qualité du projet.
# À exécuter avant de pousser : scripts/check.sh
#
# Le CI (.gitea/workflows/) ne conserve que PHP lint et JS lint ; les autres
# vérifications (AsciiDoc, JSON, XML, shellcheck) sont désormais réservées au
# local et doivent être passées manuellement avec ce script.
#
# Un outil manquant n'est pas bloquant : le check correspondant est ignoré
# avec un avertissement. Le script retourne un code non nul si une vérification
# échoue.
set -u
cd "$(dirname "$0")/.." || exit 1
fail=0
warn=0
step() { printf '\n\033[1m== %s ==\033[0m\n' "$1"; }
# need <commande> <paquet> : vérifie la présence d'un outil
need() {
if ! command -v "$1" >/dev/null 2>&1; then
echo "⚠️ '$1' non installé — check ignoré (paquet : $2)"
warn=1
return 1
fi
}
step "PHP lint (*.php, *.php.sample)"
if need php php-cli; then
php_fail=0
while IFS= read -r f; do
if ! php -l "$f" > /dev/null; then
echo "$f"
php_fail=1
fi
done < <(find . -path ./.git -prune -o \( -name '*.php' -o -name '*.php.sample' \) -print)
if [ "$php_fail" -eq 0 ]; then
echo "✅ PHP OK"
else
fail=1
fi
fi
step "JS lint (sw.js, js/*.js)"
if need node nodejs; then
js_fail=0
for f in sw.js js/*.js; do
node --check "$f" || js_fail=1
done
if [ "$js_fail" -eq 0 ]; then
echo "✅ JS OK"
else
fail=1
fi
fi
step "AsciiDoc (README.adoc, DEPLOY.adoc)"
if need asciidoctor asciidoctor; then
adoc_fail=0
for f in README.adoc DEPLOY.adoc; do
[ -f "$f" ] || continue
asciidoctor -o "/tmp/check-$$.html" "$f" || adoc_fail=1
done
rm -f "/tmp/check-$$.html"
if [ "$adoc_fail" -eq 0 ]; then
echo "✅ AsciiDoc OK"
else
fail=1
fi
fi
step "JSON (site.webmanifest.sample)"
if need python3 python3; then
if python3 -m json.tool site.webmanifest.sample > /dev/null; then
echo "✅ JSON OK"
else
fail=1
fi
fi
step "XML (sitemap.xml.sample, browserconfig.xml)"
if need xmllint libxml2-utils; then
if xmllint --noout sitemap.xml.sample browserconfig.xml; then
echo "✅ XML OK"
else
fail=1
fi
fi
step "Shellcheck (scripts shell)"
if need shellcheck shellcheck; then
if shellcheck docs/generate-readme-pdf.sh scripts/check.sh; then
echo "✅ Shellcheck OK"
else
fail=1
fi
fi
echo
if [ "$fail" -ne 0 ]; then
echo "❌ Des vérifications ont échoué — corrigez avant de pousser."
exit 1
fi
if [ "$warn" -ne 0 ]; then
echo "⚠️ Checks disponibles OK, mais certains outils manquent (le CI exécutera tout)."
else
echo "✅ Tous les checks passent."
fi
+3 -3
View File
@@ -1,7 +1,7 @@
{ {
"name": "FEDIVERSE OKI - ORGANISATION KA INTERNATIONALE", "name": "ANNU KUTE CED - Hub multimédia du podcast",
"short_name": "FEDIVERSE OKI", "short_name": "ANNU KUTE CED",
"description": "Plateforme multimédia indépendante", "description": "Hub multimédia du podcast ANNU KUTE CED",
"start_url": "/", "start_url": "/",
"display": "standalone", "display": "standalone",
"background_color": "#ffffff", "background_color": "#ffffff",
+13 -13
View File
@@ -1,67 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url> <url>
<loc>https://VOTRE-DOMAINE/</loc> <loc>https://example.com/</loc>
<changefreq>daily</changefreq> <changefreq>daily</changefreq>
<priority>1.0</priority> <priority>1.0</priority>
</url> </url>
<url> <url>
<loc>https://VOTRE-DOMAINE/index</loc> <loc>https://example.com/index</loc>
<changefreq>daily</changefreq> <changefreq>daily</changefreq>
<priority>1.0</priority> <priority>1.0</priority>
</url> </url>
<url> <url>
<loc>https://VOTRE-DOMAINE/index.php</loc> <loc>https://example.com/index.php</loc>
<changefreq>daily</changefreq> <changefreq>daily</changefreq>
<priority>1.0</priority> <priority>1.0</priority>
</url> </url>
<url> <url>
<loc>https://VOTRE-DOMAINE/direct</loc> <loc>https://example.com/direct</loc>
<changefreq>hourly</changefreq> <changefreq>hourly</changefreq>
<priority>0.9</priority> <priority>0.9</priority>
</url> </url>
<url> <url>
<loc>https://VOTRE-DOMAINE/direct.php</loc> <loc>https://example.com/direct.php</loc>
<changefreq>hourly</changefreq> <changefreq>hourly</changefreq>
<priority>0.9</priority> <priority>0.9</priority>
</url> </url>
<url> <url>
<loc>https://VOTRE-DOMAINE/mentions-legales</loc> <loc>https://example.com/mentions-legales</loc>
<changefreq>monthly</changefreq> <changefreq>monthly</changefreq>
<priority>0.3</priority> <priority>0.3</priority>
</url> </url>
<url> <url>
<loc>https://VOTRE-DOMAINE/mentions-legales.php</loc> <loc>https://example.com/mentions-legales.php</loc>
<changefreq>monthly</changefreq> <changefreq>monthly</changefreq>
<priority>0.3</priority> <priority>0.3</priority>
</url> </url>
<url> <url>
<loc>https://VOTRE-DOMAINE/recherche</loc> <loc>https://example.com/recherche</loc>
<changefreq>weekly</changefreq> <changefreq>weekly</changefreq>
<priority>0.8</priority> <priority>0.8</priority>
</url> </url>
<url> <url>
<loc>https://VOTRE-DOMAINE/recherche.php</loc> <loc>https://example.com/recherche.php</loc>
<changefreq>weekly</changefreq> <changefreq>weekly</changefreq>
<priority>0.8</priority> <priority>0.8</priority>
</url> </url>
<url> <url>
<loc>https://VOTRE-DOMAINE/categories</loc> <loc>https://example.com/categories</loc>
<changefreq>weekly</changefreq> <changefreq>weekly</changefreq>
<priority>0.8</priority> <priority>0.8</priority>
</url> </url>
<url> <url>
<loc>https://VOTRE-DOMAINE/categories.php</loc> <loc>https://example.com/categories.php</loc>
<changefreq>weekly</changefreq> <changefreq>weekly</changefreq>
<priority>0.8</priority> <priority>0.8</priority>
</url> </url>
<url> <url>
<loc>https://VOTRE-DOMAINE/video</loc> <loc>https://example.com/video</loc>
<changefreq>weekly</changefreq> <changefreq>weekly</changefreq>
<priority>0.7</priority> <priority>0.7</priority>
</url> </url>
<url> <url>
<loc>https://VOTRE-DOMAINE/video.php</loc> <loc>https://example.com/video.php</loc>
<changefreq>weekly</changefreq> <changefreq>weekly</changefreq>
<priority>0.7</priority> <priority>0.7</priority>
</url> </url>
+13 -19
View File
@@ -1,6 +1,9 @@
const CACHE_NAME = 'fediverse-oki-08072026-0720'; // Version du cache : à bumper à chaque déploiement (format JJMMAAAA-HHMM).
const STATIC_CACHE_NAME = 'fediverse-oki-static-08072026-0720'; // Tout changement de ce fichier déclenche l'installation d'un nouveau
const DYNAMIC_CACHE_NAME = 'fediverse-oki-dynamic-08072026-0720'; // Service Worker chez les visiteurs ; les anciens caches sont purgés à
// l'activation (voir l'event 'activate' plus bas).
const STATIC_CACHE_NAME = 'annu-kute-ced-static-26072026-1831';
const DYNAMIC_CACHE_NAME = 'annu-kute-ced-dynamic-26072026-1831';
// Ressources à mettre en cache immédiatement // Ressources à mettre en cache immédiatement
const STATIC_ASSETS = [ const STATIC_ASSETS = [
@@ -12,7 +15,6 @@ const STATIC_ASSETS = [
'/css/video-page.css', '/css/video-page.css',
'/css/mastodon-timeline.min.css', '/css/mastodon-timeline.min.css',
'/js/main.js', '/js/main.js',
'/js/categories.js',
'/js/search.js', '/js/search.js',
'/js/mastodon-timeline.umd.js', '/js/mastodon-timeline.umd.js',
'/img/logo.png', '/img/logo.png',
@@ -46,13 +48,15 @@ self.addEventListener('install', event => {
console.log('Service Worker: Mise en cache des assets statiques'); console.log('Service Worker: Mise en cache des assets statiques');
return cache.addAll(STATIC_ASSETS); return cache.addAll(STATIC_ASSETS);
}) })
.then(() => {
return self.skipWaiting();
})
.catch(err => { .catch(err => {
console.error('Service Worker: Erreur lors de la mise en cache:', err); console.error('Service Worker: Erreur lors de la mise en cache:', err);
}) })
); );
// Note : pas de skipWaiting() ici. Lors d'une mise à jour, le nouveau SW
// reste en attente jusqu'à ce que l'utilisateur accepte la mise à jour
// via le modal (message SKIP_WAITING envoyé par js/pwa-update.js).
// Lors de la toute première visite (aucun SW actif), l'activation est
// immédiate.
}); });
// Activation du Service Worker // Activation du Service Worker
@@ -202,20 +206,10 @@ function isApiRequest(url) {
url.includes('mastodon-config.php'); url.includes('mastodon-config.php');
} }
// Gestion des messages du client // Gestion des messages du client : l'utilisateur a accepté la mise à jour,
// le SW en attente prend le contrôle (purge des anciens caches à l'activation).
self.addEventListener('message', event => { self.addEventListener('message', event => {
if (event.data && event.data.type === 'SKIP_WAITING') { if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting(); self.skipWaiting();
} }
}); });
// Notification de mise à jour
self.addEventListener('message', event => {
if (event.data && event.data.type === 'CHECK_UPDATE') {
// Vérifier s'il y a une mise à jour
event.ports[0].postMessage({
type: 'UPDATE_AVAILABLE',
version: CACHE_NAME
});
}
});
+7 -1
View File
@@ -120,6 +120,10 @@ if (empty($videoData) || isset($videoData['error'])) {
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo !empty($video['title']) ? htmlspecialchars($video['title']) . ' - ' : ''; ?><?php echo SITE_NAME; ?></title> <title><?php echo !empty($video['title']) ? htmlspecialchars($video['title']) . ' - ' : ''; ?><?php echo SITE_NAME; ?></title>
<meta name="description" content="<?php echo !empty($video['description']) ? htmlspecialchars(substr(strip_tags($video['description']), 0, 200)) . '...' : 'Regardez cette vidéo sur ' . SITE_NAME; ?>">
<?php if (!isset($videoNotFound) && !empty($video)): ?>
<link rel="canonical" href="<?php echo getBaseUrl() . '/video.php?id=' . $video['id']; ?>">
<?php endif; ?>
<link rel="stylesheet" href="css/styles.css?v=<?php echo filemtime('css/styles.css'); ?>"> <link rel="stylesheet" href="css/styles.css?v=<?php echo filemtime('css/styles.css'); ?>">
<link rel="stylesheet" href="css/video-page.css?v=<?php echo filemtime('css/video-page.css'); ?>"> <link rel="stylesheet" href="css/video-page.css?v=<?php echo filemtime('css/video-page.css'); ?>">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css">
@@ -130,7 +134,7 @@ if (empty($videoData) || isset($videoData['error'])) {
<link rel="icon" type="image/png" sizes="16x16" href="img/favicon-16x16.png"> <link rel="icon" type="image/png" sizes="16x16" href="img/favicon-16x16.png">
<link rel="manifest" href="site.webmanifest"> <link rel="manifest" href="site.webmanifest">
<link rel="icon" type="image/x-icon" href="img/favicon.ico"> <link rel="icon" type="image/x-icon" href="img/favicon.ico">
<meta name="theme-color" content="#ffffff"> <meta name="theme-color" content="#FF0000">
<!-- Meta tags pour le partage sur les réseaux sociaux --> <!-- Meta tags pour le partage sur les réseaux sociaux -->
<meta property="og:title" content="<?php echo !empty($video['title']) ? htmlspecialchars($video['title']) : 'Vidéo'; ?> - <?php echo SITE_NAME; ?>"> <meta property="og:title" content="<?php echo !empty($video['title']) ? htmlspecialchars($video['title']) : 'Vidéo'; ?> - <?php echo SITE_NAME; ?>">
@@ -141,6 +145,7 @@ if (empty($videoData) || isset($videoData['error'])) {
<meta property="og:url" content="<?php echo (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]"; ?>"> <meta property="og:url" content="<?php echo (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]"; ?>">
<meta property="og:type" content="video.other"> <meta property="og:type" content="video.other">
<meta property="og:site_name" content="<?php echo SITE_NAME; ?>"> <meta property="og:site_name" content="<?php echo SITE_NAME; ?>">
<meta property="og:locale" content="fr_FR">
<!-- Meta tags pour Twitter --> <!-- Meta tags pour Twitter -->
<meta name="twitter:card" content="summary_large_image"> <meta name="twitter:card" content="summary_large_image">
@@ -668,5 +673,6 @@ if (empty($videoData) || isset($videoData['error'])) {
}); });
</script> </script>
<script src="js/pwa-update.js?v=<?php echo filemtime('js/pwa-update.js'); ?>"></script>
</body> </body>
</html> </html>