dbXapp 2.0
RAD, CMS, Module und Runtime-IDE fuer dbXapp
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';
7
9
10 private const CACHE_TTL_SECONDS = 900;
11
12 public static function cachePath(): string {
13 return dbxContentPageCache::baseDir() . 'meta/sitemap.xml';
14 }
15
16 public static function invalidate(): void {
17 @unlink(self::cachePath());
18 }
19
20 public static function serve(): void {
21 if (function_exists('session_status') && session_status() === PHP_SESSION_ACTIVE) {
22 session_write_close();
23 }
24
25 $refresh = (int) dbx()->get_request_var('refresh', 0, 'int') === 1;
26 $xml = $refresh ? null : self::readCache();
27 if ($xml === null) {
28 $xml = self::build();
29 self::writeCache($xml);
30 }
31
32 header('Content-Type: application/xml; charset=UTF-8');
33 header('X-Content-Type-Options: nosniff');
34 echo $xml;
35 exit;
36 }
37
38 public static function rebuild(): array {
39 self::invalidate();
40 $xml = self::build();
41 self::writeCache($xml);
42
43 return self::statsFromXml($xml);
44 }
45
46 public static function stats(): array {
47 $path = self::cachePath();
48 if (!is_file($path) || !is_readable($path)) {
49 return array(
50 'exists' => false,
51 'urls' => 0,
52 'size' => 0,
53 'generated_at' => '',
54 'path' => $path,
55 );
56 }
57
58 $xml = file_get_contents($path);
59 $stats = self::statsFromXml(is_string($xml) ? $xml : '');
60 $stats['exists'] = true;
61 $stats['size'] = (int) @filesize($path);
62 $stats['generated_at'] = date('d.m.Y H:i:s', (int) @filemtime($path));
63 $stats['path'] = $path;
64 return $stats;
65 }
66
67 public static function serveRobots(): void {
68 if (function_exists('session_status') && session_status() === PHP_SESSION_ACTIVE) {
69 session_write_close();
70 }
71
72 $base = rtrim((string) dbx()->get_base_url(), '/') . '/';
73 header('Content-Type: text/plain; charset=UTF-8');
74 header('X-Content-Type-Options: nosniff');
75 echo "User-agent: *\nAllow: /\n\nSitemap: " . $base . "sitemap.xml\n";
76 exit;
77 }
78
79 private static function readCache(): ?string {
80 $path = self::cachePath();
81 if (!is_file($path) || !is_readable($path)) {
82 return null;
83 }
84 $mtime = (int) @filemtime($path);
85 if ($mtime <= 0 || time() - $mtime > self::CACHE_TTL_SECONDS) {
86 return null;
87 }
88
89 $xml = file_get_contents($path);
90 return (is_string($xml) && trim($xml) !== '') ? $xml : null;
91 }
92
93 private static function writeCache(string $xml): void {
94 if (trim($xml) === '') {
95 return;
96 }
97
98 dbxContentPageCache::ensureDirs();
99 @file_put_contents(self::cachePath(), $xml, LOCK_EX);
100 }
101
102 private static function build(): string {
103 $db = dbx()->get_system_obj('dbxDB');
104 $base = rtrim((string) dbx()->get_base_url(), '/') . '/';
105 $lngs = function_exists('dbx_accessible_lngs') ? dbx_accessible_lngs() : array('de');
106 $renderer = new dbxContentRenderer();
107 $entries = array();
108
109 foreach ($lngs as $lng) {
110 $lng = strtolower(trim((string) $lng));
111 if ($lng === '') {
112 continue;
113 }
114
115 foreach (self::collectPublicPages($db, $renderer, $lng) as $page) {
116 $permalink = trim((string) ($page['permalink'] ?? ''), '/');
117 if ($permalink === '') {
118 continue;
119 }
120
121 $loc = $base . $permalink;
122 $key = strtolower($loc);
123 if (!isset($entries[$key])) {
124 $entries[$key] = array(
125 'loc' => $loc,
126 'lastmod' => (string) ($page['lastmod'] ?? ''),
127 );
128 } elseif (($page['lastmod'] ?? '') > ($entries[$key]['lastmod'] ?? '')) {
129 $entries[$key]['lastmod'] = (string) ($page['lastmod'] ?? '');
130 }
131 }
132 }
133
134 foreach (self::collectUserMenuLinks($base) as $link) {
135 $loc = trim((string) ($link['loc'] ?? ''));
136 if ($loc === '') {
137 continue;
138 }
139
140 $key = strtolower($loc);
141 if (!isset($entries[$key])) {
142 $entries[$key] = array(
143 'loc' => $loc,
144 'lastmod' => '',
145 );
146 }
147 }
148
149 ksort($entries);
150
151 $lines = array('<?xml version="1.0" encoding="UTF-8"?>');
152 $lines[] = '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">';
153
154 foreach ($entries as $entry) {
155 $lines[] = ' <url>';
156 $lines[] = ' <loc>' . self::xmlEsc((string) $entry['loc']) . '</loc>';
157 $lastmod = trim((string) ($entry['lastmod'] ?? ''));
158 if ($lastmod !== '') {
159 $lines[] = ' <lastmod>' . self::xmlEsc($lastmod) . '</lastmod>';
160 }
161 $lines[] = ' </url>';
162 }
163
164 $lines[] = '</urlset>';
165 return implode("\n", $lines) . "\n";
166 }
167
168 private static function collectPublicPages($db, dbxContentRenderer $renderer, string $lng): array {
169 $pages = array();
170 $seen = array();
171
172 if (is_object($db)) {
173 $rows = $db->select(
174 dbxContentLng::ddContent($lng),
175 'activ = 1',
176 'id,permalink,folder,update_date',
177 'id',
178 'ASC',
179 '',
180 0,
181 0,
182 0
183 );
184 if (is_array($rows)) {
185 foreach ($rows as $row) {
186 if (!is_array($row)) {
187 continue;
188 }
189 $cid = (int) ($row['id'] ?? 0);
190 $permalink = trim((string) ($row['permalink'] ?? ''), '/');
191 if ($cid <= 0 || $permalink === '' || isset($seen[$cid])) {
192 continue;
193 }
194 if (!self::isPublicRights($renderer->getPublicFolderRights((int) ($row['folder'] ?? 0)))) {
195 continue;
196 }
197
198 $seen[$cid] = 1;
199 $pages[] = array(
200 'cid' => $cid,
201 'permalink' => $permalink,
202 'lastmod' => self::formatLastmod((string) ($row['update_date'] ?? '')),
203 );
204 }
205 }
206 }
207
208 if (dbxContentPageCache::isConfigEnabled()) {
209 $index = dbxContentPermalinkIndex::load($lng);
210 foreach ($index as $permalink => $row) {
211 if (!is_array($row)) {
212 continue;
213 }
214 if ((int) ($row['activ'] ?? 0) !== 1) {
215 continue;
216 }
217 if (!self::isPublicRights((string) ($row['rights'] ?? '*'))) {
218 continue;
219 }
220
221 $cid = (int) ($row['cid'] ?? 0);
222 $permalink = trim((string) $permalink, '/');
223 if ($permalink === '' || $cid <= 0 || isset($seen[$cid])) {
224 continue;
225 }
226
227 $seen[$cid] = 1;
228 $pages[] = array(
229 'cid' => $cid,
230 'permalink' => $permalink,
231 'lastmod' => self::lastmodForCid($cid),
232 );
233 }
234 }
235
236 return $pages;
237 }
238
239 private static function collectUserMenuLinks(string $base): array {
240 $hrefs = array(
241 '?dbx_modul=dbxLogin&dbx_run1=run',
242 '?dbx_modul=dbxShop&dbx_run1=catalog',
243 '?dbx_modul=dbxShop&dbx_run1=cart',
244 '?dbx_modul=dbxShop&dbx_run1=orders',
245 '?dbx_modul=dbxShop&dbx_run1=withdrawal',
246 '?dbx_modul=dbxContact&dbx_run1=form',
247 '?dbx_modul=dbxContact&dbx_run1=my',
248 '?dbx_modul=dbxUser&dbx_run1=user&dbx_run2=edit_profil',
249 );
250
251 $links = array();
252 foreach ($hrefs as $href) {
253 $loc = self::sitemapLocFromHref($href, $base);
254 if ($loc !== '') {
255 $links[] = array('loc' => $loc);
256 }
257 }
258
259 return $links;
260 }
261
262 private static function sitemapLocFromHref(string $href, string $base): string {
263 $href = html_entity_decode(trim($href), ENT_QUOTES | ENT_HTML5, 'UTF-8');
264 if ($href === '' || $href === '#' || $href[0] === '#') {
265 return '';
266 }
267 if (preg_match('/^(?:javascript:|mailto:|tel:)/i', $href)) {
268 return '';
269 }
270 if (preg_match('/^https?:\/\//i', $href)) {
271 return $href;
272 }
273 if ($href[0] === '/') {
274 $parts = parse_url($base);
275 $scheme = (string) ($parts['scheme'] ?? 'http');
276 $host = (string) ($parts['host'] ?? '');
277 $port = isset($parts['port']) ? ':' . (int) $parts['port'] : '';
278 return $host !== '' ? $scheme . '://' . $host . $port . $href : rtrim($base, '/') . $href;
279 }
280 if ($href[0] === '?') {
281 return rtrim($base, '/') . '/' . $href;
282 }
283
284 return rtrim($base, '/') . '/' . ltrim($href, '/');
285 }
286
287 private static function statsFromXml(string $xml): array {
288 return array(
289 'exists' => trim($xml) !== '',
290 'urls' => substr_count($xml, '<url>'),
291 'size' => strlen($xml),
292 'generated_at' => date('d.m.Y H:i:s'),
293 'path' => self::cachePath(),
294 );
295 }
296
297 private static function isPublicRights(string $rights): bool {
298 $rights = trim($rights);
299 return $rights === '' || $rights === '*';
300 }
301
302 private static function lastmodForCid(int $cid): string {
303 $meta = dbxContentPageCache::readPageMeta($cid);
304 if (is_array($meta)) {
305 $saved = trim((string) ($meta['saved_at'] ?? ''));
306 if ($saved !== '') {
307 return self::formatLastmod($saved);
308 }
309 $seo = is_array($meta['seo'] ?? null) ? $meta['seo'] : array();
310 $update = trim((string) ($seo['update_date'] ?? ''));
311 if ($update !== '') {
312 return self::formatLastmod($update);
313 }
314 }
315 return '';
316 }
317
318 private static function formatLastmod(string $value): string {
319 $value = trim($value);
320 if ($value === '') {
321 return '';
322 }
323
324 $ts = strtotime($value);
325 if ($ts === false) {
326 return '';
327 }
328
329 return gmdate('Y-m-d', $ts);
330 }
331
332 private static function xmlEsc(string $value): string {
333 return htmlspecialchars($value, ENT_XML1 | ENT_QUOTES, 'UTF-8');
334 }
335}
336
337?>
$index['name']
Definition .dd.php:162
exit
Definition index.php:532
if( $syncRequest)
Definition index.php:520
DBX schema administration.