10 private const CACHE_TTL_SECONDS = 900;
20 public static function serve(): void {
21 if (function_exists(
'session_status') && session_status() === PHP_SESSION_ACTIVE) {
22 session_write_close();
25 $refresh = (int)
dbx()->get_request_var(
'refresh', 0,
'int') === 1;
26 $xml = $refresh ? null : self::readCache();
29 self::writeCache($xml);
32 header(
'Content-Type: application/xml; charset=UTF-8');
33 header(
'X-Content-Type-Options: nosniff');
41 self::writeCache($xml);
43 return self::statsFromXml($xml);
46 public static function stats(): array {
48 if (!is_file($path) || !is_readable($path)) {
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;
68 if (function_exists(
'session_status') && session_status() === PHP_SESSION_ACTIVE) {
69 session_write_close();
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";
79 private static function readCache(): ?string {
80 $path = self::cachePath();
81 if (!is_file($path) || !is_readable($path)) {
84 $mtime = (int) @filemtime($path);
85 if ($mtime <= 0 || time() - $mtime > self::CACHE_TTL_SECONDS) {
89 $xml = file_get_contents($path);
90 return (is_string($xml) && trim($xml) !==
'') ? $xml :
null;
93 private static function writeCache(
string $xml): void {
94 if (trim($xml) ===
'') {
98 dbxContentPageCache::ensureDirs();
99 @file_put_contents(self::cachePath(), $xml, LOCK_EX);
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();
109 foreach ($lngs as $lng) {
110 $lng = strtolower(trim((
string) $lng));
115 foreach (self::collectPublicPages(
$db, $renderer, $lng) as $page) {
116 $permalink = trim((
string) ($page[
'permalink'] ??
''),
'/');
117 if ($permalink ===
'') {
121 $loc =
$base . $permalink;
122 $key = strtolower($loc);
123 if (!isset($entries[$key])) {
124 $entries[$key] = array(
126 'lastmod' => (
string) ($page[
'lastmod'] ??
''),
128 } elseif (($page[
'lastmod'] ??
'') > ($entries[$key][
'lastmod'] ??
'')) {
129 $entries[$key][
'lastmod'] = (string) ($page[
'lastmod'] ??
'');
134 foreach (self::collectUserMenuLinks(
$base) as $link) {
135 $loc = trim((
string) ($link[
'loc'] ??
''));
140 $key = strtolower($loc);
141 if (!isset($entries[$key])) {
142 $entries[$key] = array(
151 $lines = array(
'<?xml version="1.0" encoding="UTF-8"?>');
152 $lines[] =
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">';
154 foreach ($entries as $entry) {
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>';
161 $lines[] =
' </url>';
164 $lines[] =
'</urlset>';
165 return implode(
"\n", $lines) .
"\n";
168 private static function collectPublicPages(
$db, dbxContentRenderer $renderer,
string $lng): array {
172 if (is_object(
$db)) {
174 dbxContentLng::ddContent($lng),
176 'id,permalink,folder,update_date',
184 if (is_array($rows)) {
185 foreach ($rows as $row) {
186 if (!is_array($row)) {
189 $cid = (int) ($row[
'id'] ?? 0);
190 $permalink = trim((
string) ($row[
'permalink'] ??
''),
'/');
191 if ($cid <= 0 || $permalink ===
'' || isset($seen[$cid])) {
194 if (!self::isPublicRights($renderer->getPublicFolderRights((
int) ($row[
'folder'] ?? 0)))) {
201 'permalink' => $permalink,
202 'lastmod' => self::formatLastmod((
string) ($row[
'update_date'] ??
'')),
208 if (dbxContentPageCache::isConfigEnabled()) {
209 $index = dbxContentPermalinkIndex::load($lng);
210 foreach (
$index as $permalink => $row) {
211 if (!is_array($row)) {
214 if ((
int) ($row[
'activ'] ?? 0) !== 1) {
217 if (!self::isPublicRights((
string) ($row[
'rights'] ??
'*'))) {
221 $cid = (int) ($row[
'cid'] ?? 0);
222 $permalink = trim((
string) $permalink,
'/');
223 if ($permalink ===
'' || $cid <= 0 || isset($seen[$cid])) {
230 'permalink' => $permalink,
231 'lastmod' => self::lastmodForCid($cid),
239 private static function collectUserMenuLinks(
string $base): 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',
252 foreach ($hrefs as $href) {
253 $loc = self::sitemapLocFromHref($href,
$base);
255 $links[] = array(
'loc' => $loc);
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] ===
'#') {
267 if (preg_match(
'/^(?:javascript:|mailto:|tel:)/i', $href)) {
270 if (preg_match(
'/^https?:\/\//i', $href)) {
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;
280 if ($href[0] ===
'?') {
281 return rtrim(
$base,
'/') .
'/' . $href;
284 return rtrim(
$base,
'/') .
'/' . ltrim($href,
'/');
287 private static function statsFromXml(
string $xml): 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(),
297 private static function isPublicRights(
string $rights): bool {
298 $rights = trim($rights);
299 return $rights ===
'' || $rights ===
'*';
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'] ??
''));
307 return self::formatLastmod($saved);
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);
318 private static function formatLastmod(
string $value): string {
319 $value = trim($value);
324 $ts = strtotime($value);
329 return gmdate(
'Y-m-d', $ts);
332 private static function xmlEsc(
string $value): string {
333 return htmlspecialchars($value, ENT_XML1 | ENT_QUOTES,
'UTF-8');