11 private const CACHE_TTL_SECONDS = 900;
21 public static function serve(): void {
22 if (function_exists(
'session_status') && session_status() === PHP_SESSION_ACTIVE) {
23 session_write_close();
26 $refresh = (int)
dbx()->get_request_var(
'refresh', 0,
'int') === 1;
27 $xml = $refresh ? null : self::readCache();
30 self::writeCache($xml);
33 header(
'Content-Type: application/xml; charset=UTF-8');
34 header(
'X-Content-Type-Options: nosniff');
42 self::writeCache($xml);
44 return self::statsFromXml($xml);
47 public static function stats(): array {
49 if (!is_file($path) || !is_readable($path)) {
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;
69 if (function_exists(
'session_status') && session_status() === PHP_SESSION_ACTIVE) {
70 session_write_close();
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";
80 private static function readCache(): ?string {
81 $path = self::cachePath();
82 if (!is_file($path) || !is_readable($path)) {
85 $mtime = (int) @filemtime($path);
86 if ($mtime <= 0 || time() - $mtime > self::CACHE_TTL_SECONDS) {
90 $xml = file_get_contents($path);
91 if (!is_string($xml) || trim($xml) ===
'') {
96 if (strpos($xml,
'?dbx_') !==
false
97 || preg_match(
'#<loc>[^<]+/home/?</loc>#i', $xml)) {
104 private static function writeCache(
string $xml): void {
105 if (trim($xml) ===
'') {
109 dbxContentPageCache::ensureDirs();
110 @file_put_contents(self::cachePath(), $xml, LOCK_EX);
113 private static function build(): string {
114 $db =
dbx()->get_system_obj(
'dbxDB');
118 $homeCid = dbxContentHome::masterCid();
119 $masterLng = dbxContentLngSync::masterLng();
123 $lngs = array($masterLng);
125 foreach ($lngs as
$lng) {
126 $lng = strtolower(trim((
string)
$lng));
131 $permalink = trim((
string) (
$page[
'permalink'] ??
''),
'/');
132 if ($permalink ===
'') {
136 $loc =
$lng === $masterLng && (int) (
$page[
'cid'] ?? 0) === $homeCid
138 :
$base . $permalink;
139 $key = strtolower($loc);
140 if (!isset($entries[$key])) {
141 $entries[$key] = array(
143 'lastmod' => (
string) (
$page[
'lastmod'] ??
''),
145 } elseif ((
$page[
'lastmod'] ??
'') > ($entries[$key][
'lastmod'] ??
'')) {
146 $entries[$key][
'lastmod'] = (string) (
$page[
'lastmod'] ??
'');
153 $lines = array(
'<?xml version="1.0" encoding="UTF-8"?>');
154 $lines[] =
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">';
156 foreach ($entries as $entry) {
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>';
163 $lines[] =
' </url>';
166 $lines[] =
'</urlset>';
167 return implode(
"\n", $lines) .
"\n";
170 private static function collectPublicPages(
$db, dbxContentRenderer
$renderer,
string $lng): array {
174 if (is_object(
$db)) {
176 dbxContentLng::ddContent(
$lng),
178 'id,permalink,folder,update_date,meta_robots',
186 if (is_array(
$rows)) {
187 foreach (
$rows as $row) {
188 if (!is_array($row)) {
191 $cid = (int) ($row[
'id'] ?? 0);
192 $permalink = trim((
string) ($row[
'permalink'] ??
''),
'/');
193 if ($cid <= 0 || $permalink ===
'' || isset($seen[$cid])) {
197 if (self::isNoindex((
string) ($row[
'meta_robots'] ??
''))) {
200 if (!self::isPublicRights(
$renderer->getPublicFolderRights((
int) ($row[
'folder'] ?? 0)))) {
206 'permalink' => $permalink,
207 'lastmod' => self::formatLastmod((
string) ($row[
'update_date'] ??
'')),
213 if (dbxContentPageCache::isConfigEnabled()) {
214 $index = dbxContentPermalinkIndex::load(
$lng);
215 foreach (
$index as $permalink => $row) {
216 if (!is_array($row)) {
219 if ((
int) ($row[
'activ'] ?? 0) !== 1) {
222 if (self::isNoindex((
string) ($row[
'meta_robots'] ??
''))) {
225 if (!self::isPublicRights((
string) ($row[
'rights'] ??
'*'))) {
229 $cid = (int) ($row[
'cid'] ?? 0);
230 $permalink = trim((
string) $permalink,
'/');
231 if ($permalink ===
'' || $cid <= 0 || isset($seen[$cid])) {
238 'permalink' => $permalink,
239 'lastmod' => self::lastmodForCid($cid),
247 private static function statsFromXml(
string $xml): 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(),
257 private static function isPublicRights(
string $rights): bool {
258 $rights = trim($rights);
259 return $rights ===
'' || $rights ===
'*';
262 private static function isNoindex(
string $robots): bool {
263 return in_array(
'noindex', array_map(
'trim', explode(
',', strtolower($robots))), true);
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'] ??
''));
271 return self::formatLastmod($saved);
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);
282 private static function formatLastmod(
string $value): string {
293 return gmdate(
'Y-m-d', $ts);
296 private static function xmlEsc(
string $value): string {
297 return htmlspecialchars(
$value, ENT_XML1 | ENT_QUOTES,
'UTF-8');