Add SEO, sitemap, robots, search, Meilisearch and security files
- JSON-LD: Organization, WebSite with SearchAction, BreadcrumbList - SitemapController: sitemapindex with main + paginated events (images/videos) - RobotsController: dynamic robots.txt with sitemap URL - SearchController: /search with Meilisearch (TODO) - Meilisearch added to dev and prod docker-compose - Breadcrumbs added to all controllers - .well-known: security.txt, humans.txt, dnt-policy.txt - PGP public key in public/key.asc - SecurityController: /mot-de-passe + .well-known/change-password route Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -138,6 +138,17 @@ services:
|
||||
- ngrok
|
||||
entrypoint: sh /sync.sh
|
||||
|
||||
meilisearch:
|
||||
image: getmeili/meilisearch:latest
|
||||
container_name: e_ticket_meilisearch
|
||||
environment:
|
||||
MEILI_MASTER_KEY: e_ticket
|
||||
MEILI_ENV: development
|
||||
ports:
|
||||
- "7700:7700"
|
||||
volumes:
|
||||
- meilisearch-data:/meili_data
|
||||
|
||||
redisinsight:
|
||||
image: redis/redisinsight:latest
|
||||
container_name: e_ticket_redisinsight
|
||||
@@ -153,3 +164,4 @@ volumes:
|
||||
bun-modules:
|
||||
vault-data:
|
||||
minio-data:
|
||||
meilisearch-data:
|
||||
|
||||
@@ -106,7 +106,18 @@ services:
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
meilisearch:
|
||||
image: getmeili/meilisearch:latest
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
MEILI_MASTER_KEY: e-ticket
|
||||
MEILI_ENV: production
|
||||
MEILI_NO_ANALYTICS: true
|
||||
volumes:
|
||||
- meilisearch-data:/meili_data
|
||||
|
||||
volumes:
|
||||
db-master-data:
|
||||
db-slave-data:
|
||||
redis-data:
|
||||
meilisearch-data:
|
||||
|
||||
51
key.asc
Normal file
51
key.asc
Normal file
@@ -0,0 +1,51 @@
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
xsFNBGl/HrcBEACarK9KzE+J9MhuojbJyR4wU65Cf1fJEbS9NfhbHplaaELDv8+e
|
||||
+aeCliEvgZI25UFnHzhvrSeQD1AKkhbWQRxmUDFCDzylhNSwpbdrdckvGEaV90dN
|
||||
b98a7hFjk42b+IuDpxESlqIoB5+sq9/iQhT9pUQcAwVPF/vgZmaT3dKlCRJQYXaA
|
||||
pqBGERweEvI5P+zZgy2uNjAwkNzBSORY9M5K9SeiDkRb27MJKh98CTykSIYc+Qwa
|
||||
IGsFyX/7ZqmnfR5xTzdN1Q/jgUKS2gvdXxzS6qH1mKbngH9M4mMrT4QdmDDLYLIY
|
||||
jAzSmUjXWBEYvAgEIJ2LSSyvGkuOFfnQ1iRn78ahYClI62SIJXoVIvG9L+3Yxi+6
|
||||
c/Yd3ILx0b+m7PNoJsCmh6S/oBMBzKJpW2N2SEGpOhhN6wisfBuLwliNUFji9B6a
|
||||
CZ375ebj032B7FhWbgPg0IKj0T9sJLq+grRubxF8KvEk8HwOkJ+D8ocbPitFV1wM
|
||||
Vq+2IyM1V0Cqp4eUBpsumKhqrejoIaCWrXiaSRFJBi2bWbGG5np152RAZsEntXHX
|
||||
gyA+zWLYiCVcyv+Lshqao9NEw+V2S1m+wkMcYB5EyzoyEkTeQMZduF3DmjZIQJ1N
|
||||
k6X9G3hiqTY89PjmwZ1h8O5allXAyRNgmi+UmjZ3L5EI/hqdK0MKFA0oxQARAQAB
|
||||
zSBFLUNvc3BsYXkgPGNvbnRhY3RAZS1jb3NwbGF5LmZyPsLBhwQTAQgAMRYhBJBl
|
||||
Q9S6KNCt9/BrsTk4SZ4B7ZAkBQJpfx64AhsDBAsJCAcFFQgJCgsFFgIDAQAACgkQ
|
||||
OThJngHtkCR14Q//R3gXwwl9KVtnta+JSLP72BhjSXSEXYj3moTUK6ZXANqCfOJ/
|
||||
LI1loN+pH/+8WaBRpVv4Z7fLoqWfvjkj0sFUVT6bvPcbZMYxtWTgT/sTEiM1maup
|
||||
9ChTgE7/TSWbC2PckyishUjrqSHGpg9RTwh8P+W+0TWzCEAlrGMwgidFfRaVY44V
|
||||
20Id/guAxJy2w+BhUlZ0YRDKZSrI26ezJQEskfPi07IzGZTM3mgx77U2gYj+P6ig
|
||||
zmYzyH1ISdTDTNgs+bulqfoKgWC2uPDKTYmhUckq8bhaLyPcGEspwcMVunY6pCu/
|
||||
U69zvZZVshIk0Tn2Z+pGr47N5c8R6FAi5nDuzrxy2kJaH6uV8AsfkYlzJm033hJB
|
||||
H3XqaJYMAr0Xm30RhecuOAFKdxnPLSxTNhllovULaL6IvXnBY4+/PD9yFjaqrJQW
|
||||
omTxNQvL6vwLm15KDP93Toukeu249ZLDMU74VTK+4O9wxdy54SX6u1m+xloVAisU
|
||||
+KDyjEDqeOZ5ofkVn4OH6EXVeGyIRra6q/0xI5NZlPaBao3jGyEz1XbExG6fn3QC
|
||||
c/nA/QBCaPSB/xfVTo0hNM2qiv32+y+x3eCEckFjYNBDtqFZlt3Lh0H3LbTALLSK
|
||||
I9k82fg9DWQIYts43e3LjAIh/5aV8Xg3F2Bn/o+vOSbFygQbPX3dE3NlG4LOwU0E
|
||||
aX8euAEQANIpA/G/fdDXcvYbjyzCDFXkA1MUv+GbU+4u7giA59/Mkajxr6o3qEXj
|
||||
IfsQQTCNe14B/D8yRtt4fCL+Zj7O01T730RpQuUiF+aV0S0I80QS+X2vZN3SdBXV
|
||||
TOKRYKXk8lqt0U1FWVAhyHR8cZ4bAp1sp50Klc8mln8G96CTmQ+ffV8QQq85jFWU
|
||||
zsDBEG0DxNhNigt+EOBOEbAOGvJlXjVFHZ1pIFTbTUDM3ExS1IFJoze2c0cu/2at
|
||||
KZNnsRXxaGwhCFZLUYRbR2m+XQyYnIcSIPekq0H091qXjcxmrsCtaF0SlCowF3gc
|
||||
765h0PuPdTXc+2rRuUNnNwPugqs/qWmbjcKCyJF5Y1yqMbAwxzafskxNAooBkw4Q
|
||||
huGlIhRGNYauhwDWxWSev7sdZiAfyfn8nh4GgfF0T6Vx5ycRnp9ee8Bc5dM72T+q
|
||||
kF5Qnu38lNLsJbxzSi2jyiyeuJGpQRAItWSXUax1bzCjxsl9BBefuAdns+yc1jgk
|
||||
h2a4YC1Um/I6lDdVrDRiOCQ9In0LCesKL1plj/D8qG5CTl4YjzNHwlPIoMPiNgiJ
|
||||
vnQMvcdcYWvf4a0XvTWf3QbzalNYH0mvMWrTkeby7o6/Y7rmnPQmNYPGYz6v+ayg
|
||||
GwMi3MwtD/mVr2PCD8ICgXH5r9fySGpW5YAK50t7PYyfu621JiNxABEBAAHCwXYE
|
||||
GAEIACAWIQSQZUPUuijQrffwa7E5OEmeAe2QJAUCaX8euAIbDAAKCRA5OEmeAe2Q
|
||||
JMIvD/9NFCQA3jMpZ2WUhLtqf7XOHsu5ncuIDRg9VViBV73GsbfsJGnKekploLPw
|
||||
j8JZ0SVpYLTMmQN2Cki8ErZYmHyAn65xGPjT/2uN1CWnFWq44fhiJoJu3mkwYKzD
|
||||
EAwviAVyMU7zSWhjP1qg2aqVe4c4Nb52tEdruiLfYSeSDbXwyzQqarmjwFhbwK4/
|
||||
YmYCq5WlRriD9AIfoef+sYfuKPNkegJP2JqekQ0vZ3EjXmBghwH4YG4HVKEHLT7n
|
||||
+8UcOLMln+lPFP+Ea++r8wmQbGEGSG/7V8GhIlta0kYg3iEZpLb9K0XZJ0PWYq5H
|
||||
uFkjePnCtROOhqQjIuXN9wdejICksNURbssjuIbWQ/mMx8xAoQcMZjr93z1icBsx
|
||||
nxLqlZhq1abnXVKkU7Eu+ynULGXNqfcF2Q6ApVSkp8uE5yOed3o9yRNb6zzyhLqB
|
||||
64NS74As8cLp2NwYIvwbAZz6EyGad/5L2hW5unbyXGWL/EKx2wQk6E9o9Jn35OOt
|
||||
E5zXzEsYfCTIp4Gt65kHl0U/E3pdvJhu71vuOlMQZzlP5i1WcZXOFbiKdtW+wf6K
|
||||
GCDvkAOtrItxaqTUCMD9lknJ+uVlvLtWvFWZxTUx1w7lLzPunA6usrayf77WROca
|
||||
BzdSeoY1RgWbeo/TziGUcW4KMiso/j13uOweG4NeNkrW1MAHsg==
|
||||
=5F8Q
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
1
public/.well-known/dnt-policy.txt
Normal file
1
public/.well-known/dnt-policy.txt
Normal file
@@ -0,0 +1 @@
|
||||
{"tracking": "N", "qualifiers": "adc", "controller": ["E-Cosplay", "contact@e-cosplay.fr"], "same-party": ["e-cosplay.fr", "ticket.e-cosplay.fr"], "policy": "https://ticket.e-cosplay.fr/rgpd"}
|
||||
12
public/.well-known/humans.txt
Normal file
12
public/.well-known/humans.txt
Normal file
@@ -0,0 +1,12 @@
|
||||
/* TEAM */
|
||||
Name: E-Cosplay
|
||||
Contact: contact@e-cosplay.fr
|
||||
Site: https://www.e-cosplay.fr
|
||||
Location: Beautor, France
|
||||
|
||||
/* SITE */
|
||||
Project: E-Ticket
|
||||
Language: French
|
||||
Standards: HTML5, CSS3, JavaScript
|
||||
Components: Symfony 8, PHP 8.4, PostgreSQL, Redis, Tailwind CSS
|
||||
Software: Caddy, Docker, Ansible, Meilisearch
|
||||
6
public/.well-known/security.txt
Normal file
6
public/.well-known/security.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
Contact: mailto:contact@e-cosplay.fr
|
||||
Expires: 2027-03-18T00:00:00.000Z
|
||||
Preferred-Languages: fr, en
|
||||
Canonical: https://ticket.e-cosplay.fr/.well-known/security.txt
|
||||
Policy: https://ticket.e-cosplay.fr/mentions-legales
|
||||
Encryption: https://ticket.e-cosplay.fr/key.asc
|
||||
51
public/key.asc
Normal file
51
public/key.asc
Normal file
@@ -0,0 +1,51 @@
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
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
|
||||
=5F8Q
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
@@ -11,6 +11,10 @@ class HomeController extends AbstractController
|
||||
#[Route('/', name: 'app_home')]
|
||||
public function index(): Response
|
||||
{
|
||||
return $this->render('home/index.html.twig');
|
||||
return $this->render('home/index.html.twig', [
|
||||
'breadcrumbs' => [
|
||||
['name' => 'Accueil', 'url' => '/'],
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,12 @@ class RedirectController extends AbstractController
|
||||
return $this->redirectToRoute('app_home');
|
||||
}
|
||||
|
||||
return $this->render('pages/external_redirect.twig');
|
||||
return $this->render('pages/external_redirect.twig', [
|
||||
'url' => $url,
|
||||
'breadcrumbs' => [
|
||||
['name' => 'Accueil', 'url' => '/'],
|
||||
['name' => 'Redirection externe', 'url' => '/external-redirect'],
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
38
src/Controller/RobotsController.php
Normal file
38
src/Controller/RobotsController.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
|
||||
class RobotsController extends AbstractController
|
||||
{
|
||||
#[Route('/robots.txt', name: 'app_robots', methods: ['GET'])]
|
||||
public function index(UrlGeneratorInterface $urlGenerator): Response
|
||||
{
|
||||
$sitemapUrl = $urlGenerator->generate('app_sitemap', [], UrlGeneratorInterface::ABSOLUTE_URL);
|
||||
|
||||
$content = <<<TXT
|
||||
User-agent: *
|
||||
Allow: /
|
||||
Disallow: /external-redirect
|
||||
Disallow: /my-csp-report
|
||||
Disallow: /track/
|
||||
Disallow: /mentions-legales
|
||||
Disallow: /cgu
|
||||
Disallow: /cgv
|
||||
Disallow: /hebergement
|
||||
Disallow: /cookies
|
||||
Disallow: /rgpd
|
||||
Disallow: /unsubscribe/
|
||||
|
||||
Sitemap: {$sitemapUrl}
|
||||
TXT;
|
||||
|
||||
return new Response($content, 200, [
|
||||
'Content-Type' => 'text/plain',
|
||||
]);
|
||||
}
|
||||
}
|
||||
26
src/Controller/SearchController.php
Normal file
26
src/Controller/SearchController.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
class SearchController extends AbstractController
|
||||
{
|
||||
#[Route('/search', name: 'app_search', methods: ['GET'])]
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$query = $request->query->get('q', '');
|
||||
|
||||
return $this->render('search/index.html.twig', [
|
||||
'query' => $query,
|
||||
'results' => [],
|
||||
'breadcrumbs' => [
|
||||
['name' => 'Accueil', 'url' => '/'],
|
||||
['name' => 'Recherche', 'url' => '/search'],
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,20 @@ class SecurityController extends AbstractController
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/mot-de-passe', name: 'app_change_password')]
|
||||
#[Route('/.well-known/change-password')]
|
||||
public function changePassword(): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
|
||||
|
||||
return $this->render('security/change_password.html.twig', [
|
||||
'breadcrumbs' => [
|
||||
['name' => 'Accueil', 'url' => '/'],
|
||||
['name' => 'Modifier mon mot de passe', 'url' => '/mot-de-passe'],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/deconnexion', name: 'app_logout')]
|
||||
public function logout(): void
|
||||
{
|
||||
|
||||
75
src/Controller/SitemapController.php
Normal file
75
src/Controller/SitemapController.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
|
||||
class SitemapController extends AbstractController
|
||||
{
|
||||
private const MAX_URLS_PER_SITEMAP = 50000;
|
||||
|
||||
#[Route('/sitemap.xml', name: 'app_sitemap', methods: ['GET'])]
|
||||
public function index(): Response
|
||||
{
|
||||
// TODO: count events to determine number of event sitemap pages
|
||||
$eventPages = 1;
|
||||
|
||||
$sitemaps = [
|
||||
['loc' => $this->generateUrl('app_sitemap_main', [], UrlGeneratorInterface::ABSOLUTE_URL)],
|
||||
];
|
||||
|
||||
for ($i = 1; $i <= $eventPages; $i++) {
|
||||
$sitemaps[] = [
|
||||
'loc' => $this->generateUrl('app_sitemap_events', ['page' => $i], UrlGeneratorInterface::ABSOLUTE_URL),
|
||||
];
|
||||
}
|
||||
|
||||
return new Response(
|
||||
$this->renderView('sitemap/index.xml.twig', ['sitemaps' => $sitemaps]),
|
||||
200,
|
||||
['Content-Type' => 'text/xml'],
|
||||
);
|
||||
}
|
||||
|
||||
#[Route('/sitemap-main.xml', name: 'app_sitemap_main', methods: ['GET'])]
|
||||
public function main(): Response
|
||||
{
|
||||
$urls = [
|
||||
[
|
||||
'loc' => $this->generateUrl('app_home', [], UrlGeneratorInterface::ABSOLUTE_URL),
|
||||
'changefreq' => 'daily',
|
||||
'priority' => '1.0',
|
||||
],
|
||||
[
|
||||
'loc' => $this->generateUrl('app_search', [], UrlGeneratorInterface::ABSOLUTE_URL),
|
||||
'changefreq' => 'weekly',
|
||||
'priority' => '0.5',
|
||||
],
|
||||
];
|
||||
|
||||
return new Response(
|
||||
$this->renderView('sitemap/urlset.xml.twig', ['urls' => $urls]),
|
||||
200,
|
||||
['Content-Type' => 'text/xml'],
|
||||
);
|
||||
}
|
||||
|
||||
#[Route('/sitemap-events-{page}.xml', name: 'app_sitemap_events', requirements: ['page' => '\d+'], methods: ['GET'])]
|
||||
public function events(int $page = 1): Response
|
||||
{
|
||||
$offset = ($page - 1) * self::MAX_URLS_PER_SITEMAP;
|
||||
|
||||
// TODO: fetch events from DB with limit/offset
|
||||
// $events = $eventRepository->findBy([], ['id' => 'ASC'], self::MAX_URLS_PER_SITEMAP, $offset);
|
||||
$urls = [];
|
||||
|
||||
return new Response(
|
||||
$this->renderView('sitemap/urlset.xml.twig', ['urls' => $urls]),
|
||||
200,
|
||||
['Content-Type' => 'text/xml'],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -23,12 +23,20 @@ class UnsubscribeController extends AbstractController
|
||||
|
||||
return $this->render('unsubscribe/confirmed.html.twig', [
|
||||
'email' => $email,
|
||||
'breadcrumbs' => [
|
||||
['name' => 'Accueil', 'url' => '/'],
|
||||
['name' => 'Desinscription', 'url' => '/unsubscribe/' . $token],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->render('unsubscribe/index.html.twig', [
|
||||
'email' => $email,
|
||||
'token' => $token,
|
||||
'breadcrumbs' => [
|
||||
['name' => 'Accueil', 'url' => '/'],
|
||||
['name' => 'Desinscription', 'url' => '/unsubscribe/' . $token],
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,65 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}{% endblock %}</title>
|
||||
{% block meta %}
|
||||
<meta name="description" content="{% block description %}E-Ticket - Plateforme de vente de tickets evenementiels pour associations{% endblock %}">
|
||||
{% endblock %}
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Organization",
|
||||
"name": "E-Ticket",
|
||||
"url": "https://ticket.e-cosplay.fr",
|
||||
"logo": "https://ticket.e-cosplay.fr/logo.png",
|
||||
"email": "contact@e-cosplay.fr",
|
||||
"telephone": "+33679348802",
|
||||
"address": {
|
||||
"@type": "PostalAddress",
|
||||
"streetAddress": "42 rue de Saint-Quentin",
|
||||
"addressLocality": "Beautor",
|
||||
"postalCode": "02800",
|
||||
"addressCountry": "FR"
|
||||
},
|
||||
"sameAs": [
|
||||
"https://www.facebook.com/assocationecosplay",
|
||||
"https://www.e-cosplay.fr"
|
||||
]
|
||||
}
|
||||
</script>
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "WebSite",
|
||||
"name": "E-Ticket",
|
||||
"url": "https://ticket.e-cosplay.fr",
|
||||
"potentialAction": {
|
||||
"@type": "SearchAction",
|
||||
"target": {
|
||||
"@type": "EntryPoint",
|
||||
"urlTemplate": "https://ticket.e-cosplay.fr/search?q={search_term_string}"
|
||||
},
|
||||
"query-input": "required name=search_term_string"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% if breadcrumbs is defined and breadcrumbs is not empty %}
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "BreadcrumbList",
|
||||
"itemListElement": [
|
||||
{% for breadcrumb in breadcrumbs %}
|
||||
{
|
||||
"@type": "ListItem",
|
||||
"position": {{ loop.index }},
|
||||
"name": "{{ breadcrumb.name }}",
|
||||
"item": "{{ breadcrumb.url }}"
|
||||
}{% if not loop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
]
|
||||
}
|
||||
</script>
|
||||
{% endif %}
|
||||
{% block stylesheets %}{% endblock %}
|
||||
{% block javascripts %}
|
||||
{{ vite_asset('app.js') }}
|
||||
|
||||
7
templates/search/index.html.twig
Normal file
7
templates/search/index.html.twig
Normal file
@@ -0,0 +1,7 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}Recherche - E-Ticket{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
|
||||
{% endblock %}
|
||||
8
templates/sitemap/index.xml.twig
Normal file
8
templates/sitemap/index.xml.twig
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
{% for sitemap in sitemaps %}
|
||||
<sitemap>
|
||||
<loc>{{ sitemap.loc }}</loc>
|
||||
</sitemap>
|
||||
{% endfor %}
|
||||
</sitemapindex>
|
||||
34
templates/sitemap/urlset.xml.twig
Normal file
34
templates/sitemap/urlset.xml.twig
Normal file
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
|
||||
xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"
|
||||
xmlns:video="http://www.google.com/schemas/sitemap-video/1.1">
|
||||
{% for url in urls %}
|
||||
<url>
|
||||
<loc>{{ url.loc }}</loc>
|
||||
{% if url.changefreq is defined %}<changefreq>{{ url.changefreq }}</changefreq>{% endif %}
|
||||
{% if url.priority is defined %}<priority>{{ url.priority }}</priority>{% endif %}
|
||||
{% if url.lastmod is defined %}<lastmod>{{ url.lastmod }}</lastmod>{% endif %}
|
||||
{% if url.images is defined %}
|
||||
{% for image in url.images %}
|
||||
<image:image>
|
||||
<image:loc>{{ image.loc }}</image:loc>
|
||||
{% if image.title is defined %}<image:title>{{ image.title }}</image:title>{% endif %}
|
||||
{% if image.caption is defined %}<image:caption>{{ image.caption }}</image:caption>{% endif %}
|
||||
</image:image>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if url.videos is defined %}
|
||||
{% for video in url.videos %}
|
||||
<video:video>
|
||||
<video:thumbnail_loc>{{ video.thumbnail }}</video:thumbnail_loc>
|
||||
<video:title>{{ video.title }}</video:title>
|
||||
<video:description>{{ video.description }}</video:description>
|
||||
{% if video.content_loc is defined %}<video:content_loc>{{ video.content_loc }}</video:content_loc>{% endif %}
|
||||
{% if video.player_loc is defined %}<video:player_loc>{{ video.player_loc }}</video:player_loc>{% endif %}
|
||||
{% if video.duration is defined %}<video:duration>{{ video.duration }}</video:duration>{% endif %}
|
||||
</video:video>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</url>
|
||||
{% endfor %}
|
||||
</urlset>
|
||||
Reference in New Issue
Block a user