dbxapp 4.1.3
CMS, Shop, Workflows und modulare Geschäftsanwendungen
Loading...
Searching...
No Matches
dbxContentSitemap.class.php
Go to the documentation of this file.
1<?php
2namespace dbx\dbxContent;
3
4require_once __DIR__ . '/dbxContentPageCache.class.php';
5require_once __DIR__ . '/dbxContentPermalinkIndex.class.php';
6require_once __DIR__ . '/dbxContentRenderer.class.php';
7require_once __DIR__ . '/dbxContentHome.class.php';
8
10
11 private const CACHE_TTL_SECONDS = 900;
12
13 public static function cachePath(): string {
14 return dbxContentPageCache::baseDir() . 'meta/sitemap.xml';
15 }
16
17 public static function invalidate(): void {
18 @unlink(self::cachePath());
19 }
20
21 public static function serve(): void {
22 if (function_exists('session_status') && session_status() === PHP_SESSION_ACTIVE) {
23 session_write_close();
24 }
25
26 $refresh = (int) dbx()->get_request_var('refresh', 0, 'int') === 1;
27 $xml = $refresh ? null : self::readCache();
28 if ($xml === null) {
29 $xml = self::build();
30 self::writeCache($xml);
31 }
32
33 header('Content-Type: application/xml; charset=UTF-8');
34 header('X-Content-Type-Options: nosniff');
35 echo $xml;
36 exit;
37 }
38
39 public static function rebuild(): array {
40 self::invalidate();
41 $xml = self::build();
42 self::writeCache($xml);
43
44 return self::statsFromXml($xml);
45 }
46
47 public static function stats(): array {
48 $path = self::cachePath();
49 if (!is_file($path) || !is_readable($path)) {
50 return array(
51 'exists' => false,
52 'urls' => 0,
53 'size' => 0,
54 'generated_at' => '',
55 'path' => $path,
56 );
57 }
58
59 $xml = file_get_contents($path);
60 $stats = self::statsFromXml(is_string($xml) ? $xml : '');
61 $stats['exists'] = true;
62 $stats['size'] = (int) @filesize($path);
63 $stats['generated_at'] = date('d.m.Y H:i:s', (int) @filemtime($path));
64 $stats['path'] = $path;
65 return $stats;
66 }
67
68 public static function serveRobots(): void {
69 if (function_exists('session_status') && session_status() === PHP_SESSION_ACTIVE) {
70 session_write_close();
71 }
72
73 $base = rtrim((string) dbx()->get_base_url(), '/') . '/';
74 header('Content-Type: text/plain; charset=UTF-8');
75 header('X-Content-Type-Options: nosniff');
76 echo "User-agent: *\nAllow: /\n\nSitemap: " . $base . "sitemap.xml\n";
77 exit;
78 }
79
80 private static function readCache(): ?string {
81 $path = self::cachePath();
82 if (!is_file($path) || !is_readable($path)) {
83 return null;
84 }
85 $mtime = (int) @filemtime($path);
86 if ($mtime <= 0 || time() - $mtime > self::CACHE_TTL_SECONDS) {
87 return null;
88 }
89
90 $xml = file_get_contents($path);
91 if (!is_string($xml) || trim($xml) === '') {
92 return null;
93 }
94
95 // Cache aus Versionen vor der SEO-Bereinigung nicht weiter ausliefern.
96 if (strpos($xml, '?dbx_') !== false
97 || preg_match('#<loc>[^<]+/home/?</loc>#i', $xml)) {
98 return null;
99 }
100
101 return $xml;
102 }
103
104 private static function writeCache(string $xml): void {
105 if (trim($xml) === '') {
106 return;
107 }
108
109 dbxContentPageCache::ensureDirs();
110 @file_put_contents(self::cachePath(), $xml, LOCK_EX);
111 }
112
113 private static function build(): string {
114 $db = dbx()->get_system_obj('dbxDB');
115 $base = rtrim((string) dbx()->get_base_url(), '/') . '/';
116 $renderer = new dbxContentRenderer();
117 $entries = array();
118 $homeCid = dbxContentHome::masterCid();
119 $masterLng = dbxContentLngSync::masterLng();
120 // Die öffentliche URL enthält aktuell kein Sprachsegment. Deshalb darf
121 // jede flache URL nur einmal und in der maßgeblichen Sprache erscheinen.
122 // Weitere Sprachen werden erst mit eigenen kanonischen URLs aufgenommen.
123 $lngs = array($masterLng);
124
125 foreach ($lngs as $lng) {
126 $lng = strtolower(trim((string) $lng));
127 if ($lng === '') {
128 continue;
129 }
130 foreach (self::collectPublicPages($db, $renderer, $lng) as $page) {
131 $permalink = trim((string) ($page['permalink'] ?? ''), '/');
132 if ($permalink === '') {
133 continue;
134 }
135
136 $loc = $lng === $masterLng && (int) ($page['cid'] ?? 0) === $homeCid
137 ? $base
138 : $base . $permalink;
139 $key = strtolower($loc);
140 if (!isset($entries[$key])) {
141 $entries[$key] = array(
142 'loc' => $loc,
143 'lastmod' => (string) ($page['lastmod'] ?? ''),
144 );
145 } elseif (($page['lastmod'] ?? '') > ($entries[$key]['lastmod'] ?? '')) {
146 $entries[$key]['lastmod'] = (string) ($page['lastmod'] ?? '');
147 }
148 }
149 }
150
151 ksort($entries);
152
153 $lines = array('<?xml version="1.0" encoding="UTF-8"?>');
154 $lines[] = '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">';
155
156 foreach ($entries as $entry) {
157 $lines[] = ' <url>';
158 $lines[] = ' <loc>' . self::xmlEsc((string) $entry['loc']) . '</loc>';
159 $lastmod = trim((string) ($entry['lastmod'] ?? ''));
160 if ($lastmod !== '') {
161 $lines[] = ' <lastmod>' . self::xmlEsc($lastmod) . '</lastmod>';
162 }
163 $lines[] = ' </url>';
164 }
165
166 $lines[] = '</urlset>';
167 return implode("\n", $lines) . "\n";
168 }
169
170 private static function collectPublicPages($db, dbxContentRenderer $renderer, string $lng): array {
171 $pages = array();
172 $seen = array();
173
174 if (is_object($db)) {
175 $rows = $db->select(
176 dbxContentLng::ddContent($lng),
177 'activ = 1',
178 'id,permalink,folder,update_date,meta_robots',
179 'id',
180 'ASC',
181 '',
182 0,
183 0,
184 0
185 );
186 if (is_array($rows)) {
187 foreach ($rows as $row) {
188 if (!is_array($row)) {
189 continue;
190 }
191 $cid = (int) ($row['id'] ?? 0);
192 $permalink = trim((string) ($row['permalink'] ?? ''), '/');
193 if ($cid <= 0 || $permalink === '' || isset($seen[$cid])) {
194 continue;
195 }
196 $seen[$cid] = 1;
197 if (self::isNoindex((string) ($row['meta_robots'] ?? ''))) {
198 continue;
199 }
200 if (!self::isPublicRights($renderer->getPublicFolderRights((int) ($row['folder'] ?? 0)))) {
201 continue;
202 }
203
204 $pages[] = array(
205 'cid' => $cid,
206 'permalink' => $permalink,
207 'lastmod' => self::formatLastmod((string) ($row['update_date'] ?? '')),
208 );
209 }
210 }
211 }
212
213 if (dbxContentPageCache::isConfigEnabled()) {
214 $index = dbxContentPermalinkIndex::load($lng);
215 foreach ($index as $permalink => $row) {
216 if (!is_array($row)) {
217 continue;
218 }
219 if ((int) ($row['activ'] ?? 0) !== 1) {
220 continue;
221 }
222 if (self::isNoindex((string) ($row['meta_robots'] ?? ''))) {
223 continue;
224 }
225 if (!self::isPublicRights((string) ($row['rights'] ?? '*'))) {
226 continue;
227 }
228
229 $cid = (int) ($row['cid'] ?? 0);
230 $permalink = trim((string) $permalink, '/');
231 if ($permalink === '' || $cid <= 0 || isset($seen[$cid])) {
232 continue;
233 }
234
235 $seen[$cid] = 1;
236 $pages[] = array(
237 'cid' => $cid,
238 'permalink' => $permalink,
239 'lastmod' => self::lastmodForCid($cid),
240 );
241 }
242 }
243
244 return $pages;
245 }
246
247 private static function statsFromXml(string $xml): array {
248 return array(
249 'exists' => trim($xml) !== '',
250 'urls' => substr_count($xml, '<url>'),
251 'size' => strlen($xml),
252 'generated_at' => date('d.m.Y H:i:s'),
253 'path' => self::cachePath(),
254 );
255 }
256
257 private static function isPublicRights(string $rights): bool {
258 $rights = trim($rights);
259 return $rights === '' || $rights === '*';
260 }
261
262 private static function isNoindex(string $robots): bool {
263 return in_array('noindex', array_map('trim', explode(',', strtolower($robots))), true);
264 }
265
266 private static function lastmodForCid(int $cid): string {
267 $meta = dbxContentPageCache::readPageMeta($cid);
268 if (is_array($meta)) {
269 $saved = trim((string) ($meta['saved_at'] ?? ''));
270 if ($saved !== '') {
271 return self::formatLastmod($saved);
272 }
273 $seo = is_array($meta['seo'] ?? null) ? $meta['seo'] : array();
274 $update = trim((string) ($seo['update_date'] ?? ''));
275 if ($update !== '') {
276 return self::formatLastmod($update);
277 }
278 }
279 return '';
280 }
281
282 private static function formatLastmod(string $value): string {
283 $value = trim($value);
284 if ($value === '') {
285 return '';
286 }
287
288 $ts = strtotime($value);
289 if ($ts === false) {
290 return '';
291 }
292
293 return gmdate('Y-m-d', $ts);
294 }
295
296 private static function xmlEsc(string $value): string {
297 return htmlspecialchars($value, ENT_XML1 | ENT_QUOTES, 'UTF-8');
298 }
299}
300
301?>
get_base_url()
Liefert die Basis-URL des aktuellen Requests.
Definition dbxApi.php:1488
if(!defined( 'IMG_WEBP')) define( 'IMG_WEBP'
exit
Definition index.php:146
DBX schema administration.
if($cinematic===''||!str_contains($cinematic, 'data-dbx-cinema')) $page