8 private function normalize_media_path($path) {
9 $rel = rawurldecode((
string)$path);
10 $rel = str_replace(
'\\',
'/', $rel);
11 $rel = preg_replace(
'~^/?files/~i',
'', $rel);
12 $rel = ltrim($rel,
'/');
14 if ($rel ===
'' || strpos($rel,
'..') !==
false || preg_match(
'~(^|/)\.~', $rel)) {
18 if (!preg_match(
'~^(media|dbxContent/media)/~i', $rel)) {
25 private function files_path($path =
'') {
26 $base = rtrim(
dbx()->get_file_dir(),
'/\\') .
'/';
27 $full =
$base . ltrim((
string)$path,
'/\\');
28 return dbx()->os_path($full);
31 private function serve_media_file($rel, $mime =
'', $file_name =
'') {
32 $rel = $this->normalize_media_path($rel);
34 http_response_code(403);
38 $base = realpath($this->files_path());
39 $file = realpath($this->files_path($rel));
40 if (!
$base || !$file || strpos($file,
$base) !== 0 || !is_file($file) || !is_readable($file)) {
41 dbx()->log_missing($rel);
42 http_response_code(404);
46 $mime = trim((
string)$mime);
48 $detected = function_exists(
'mime_content_type') ? mime_content_type($file) :
'';
49 $mime = $detected ?:
'application/octet-stream';
52 $file_name = trim((
string)$file_name);
53 if ($file_name ===
'') $file_name = basename($file);
54 $file_name = str_replace(
'"',
'', basename($file_name));
56 if (function_exists(
'session_status') && session_status() === PHP_SESSION_ACTIVE) {
57 session_write_close();
60 $size = filesize($file);
61 $mtime = filemtime($file) ?: time();
62 $etag =
'"' . md5($file .
'|' . $size .
'|' . $mtime) .
'"';
63 $lastModified = gmdate(
'D, d M Y H:i:s', $mtime) .
' GMT';
64 $ifNoneMatch = trim((
string)(
$_SERVER[
'HTTP_IF_NONE_MATCH'] ??
''));
65 $ifModifiedSince = trim((
string)(
$_SERVER[
'HTTP_IF_MODIFIED_SINCE'] ??
''));
66 if ($ifNoneMatch === $etag || ($ifModifiedSince !==
'' && strtotime($ifModifiedSince) >= $mtime)) {
67 http_response_code(304);
68 header(
'ETag: ' . $etag);
69 header(
'Last-Modified: ' . $lastModified);
70 header(
'Cache-Control: public, max-age=31536000, immutable');
75 $end = $size > 0 ? $size - 1 : 0;
77 $range = isset(
$_SERVER[
'HTTP_RANGE']) ? trim((
string)
$_SERVER[
'HTTP_RANGE']) :
'';
78 if ($range !==
'' && preg_match(
'/bytes=(\d*)-(\d*)/i', $range, $m)) {
79 if ($m[1] !==
'') $start = max(0, (
int)$m[1]);
80 if ($m[2] !==
'') $end = min($end, (
int)$m[2]);
81 if ($m[1] ===
'' && $m[2] !==
'') {
82 $suffix = max(0, (
int)$m[2]);
83 $start = max(0, $size - $suffix);
85 if ($start <= $end && $size > 0) {
87 http_response_code(206);
88 header(
'Content-Range: bytes ' . $start .
'-' . $end .
'/' . $size);
90 http_response_code(416);
91 header(
'Content-Range: bytes */' . $size);
96 header(
'Content-Type: ' . $mime);
97 header(
'Content-Length: ' . max(0, $end - $start + 1));
98 header(
'Content-Disposition: inline; filename="' . $file_name .
'"');
99 header(
'Accept-Ranges: bytes');
100 header(
'X-Content-Type-Options: nosniff');
101 header(
'ETag: ' . $etag);
102 header(
'Last-Modified: ' . $lastModified);
103 header(
'Cache-Control: public, max-age=31536000, immutable');
104 $out = fopen($file,
'rb');
106 if ($start > 0) fseek($out, $start);
107 $remaining = $end - $start + 1;
108 while ($remaining > 0 && !feof($out)) {
109 $chunk = fread($out, min(1048576, $remaining));
110 if ($chunk ===
false || $chunk ===
'')
break;
112 $remaining -= strlen($chunk);
113 if (function_exists(
'connection_aborted') && connection_aborted())
break;
120 private function media_missing_key($id, $reason, $extra =
'') {
121 if ($reason ===
'file_missing' && trim((
string)$extra) !==
'') {
122 return ltrim(str_replace(
'\\',
'/', (
string)$extra),
'/');
124 $uri = trim((
string)(
$_SERVER[
'REQUEST_URI'] ??
''));
125 if ($uri !==
'')
return $uri;
126 return 'index.php?dbx_modul=dbxContent&dbx_run1=media&dbx_mid=' . (int)$id;
129 private function media_serve_fail($code, $id, $reason, $extra =
'') {
130 dbx()->debug(
'serve_media', array(
'id' => (
int)$id,
'code' => (
int)$code,
'reason' => (
string)$reason,
'extra' => (
string)$extra));
131 if ((
int)$code === 404) {
132 dbx()->log_missing($this->media_missing_key($id, $reason, $extra));
134 http_response_code((
int)$code);
138 private function media_row_type(array $row) {
139 $stored = strtolower(trim((
string)($row[
'media_type'] ??
'')));
140 if (in_array($stored, array(
'image',
'video',
'external_video',
'file'),
true))
return $stored;
141 if (strtolower((
string)($row[
'storage_type'] ??
'')) ===
'external')
return 'external_video';
142 if (trim((
string)($row[
'provider'] ??
'')) !==
'' || trim((
string)($row[
'embed_url'] ??
'')) !==
'')
return 'external_video';
146 private function external_video_thumb_url(array $row) {
147 $provider = strtolower(trim((
string)($row[
'provider'] ??
'')));
148 $provider_id = trim((
string)($row[
'provider_id'] ??
''));
149 if ($provider ===
'youtube' && preg_match(
'~^[A-Za-z0-9_-]{11}$~', $provider_id)) {
150 return 'https://img.youtube.com/vi/' . $provider_id .
'/hqdefault.jpg';
155 private function serve_external_media(array $row) {
156 $want_thumb = (int)
dbx()->get_modul_var(
'dbx_thumb', 0,
'int') === 1;
157 if ($want_thumb && !empty($row[
'thumb_file_path'])) {
158 return $this->serve_media_file(
159 (
string)($row[
'thumb_file_path'] ??
''),
161 'thumb-' . (
string)($row[
'file_name'] ??
'')
164 $thumb_url = $this->external_video_thumb_url($row);
165 if ($thumb_url !==
'') {
166 header(
'Location: ' . $thumb_url,
true, 302);
169 return $this->media_serve_fail(404, (
int)($row[
'id'] ?? 0),
'external_unavailable');
172 private function serve_media() {
173 $id = (int)
dbx()->get_modul_var(
'dbx_mid', 0,
'int');
175 return $this->media_serve_fail(404, $id,
'invalid_id');
178 $db =
dbx()->get_system_obj(
'dbxDB');
179 $row =
$db->select1(
'dbxMedia', $id);
180 if (!is_array($row)) {
181 return $this->media_serve_fail(404, $id,
'not_found');
183 if ((
int)($row[
'active'] ?? 0) !== 1) {
184 return $this->media_serve_fail(404, $id,
'inactive');
187 if ($this->media_row_type($row) ===
'external_video') {
188 return $this->serve_external_media($row);
191 $want_thumb = (int)
dbx()->get_modul_var(
'dbx_thumb', 0,
'int') === 1;
193 $mime = (string)($row[
'mime'] ??
'');
194 $file_name = (string)($row[
'file_name'] ??
'');
195 if ($want_thumb && !empty($row[
'thumb_file_path'])) {
196 $rel = (string)$row[
'thumb_file_path'];
197 $mime = preg_match(
'/\.webp$/i', $rel) ?
'image/webp' :
'image/jpeg';
198 $file_name =
'thumb-' . $file_name;
200 $rel = (string)($row[
'file_path'] ??
'');
203 return $this->media_serve_fail(404, $id,
'empty_path');
206 $rel_norm = $this->normalize_media_path($rel);
207 if ($rel_norm ===
'') {
208 return $this->media_serve_fail(403, $id,
'invalid_path', $rel);
211 $base = realpath($this->files_path());
212 $file = realpath($this->files_path($rel_norm));
213 if (!
$base || !$file || strpos($file,
$base) !== 0 || !is_file($file) || !is_readable($file)) {
214 if ($want_thumb && !empty($row[
'file_path'])) {
215 return $this->serve_media_file(
216 (
string)$row[
'file_path'],
217 (
string)($row[
'mime'] ??
''),
218 (
string)($row[
'file_name'] ??
'')
221 return $this->media_serve_fail(404, $id,
'file_missing', $rel);
224 return $this->serve_media_file($rel, $mime, $file_name);
227 private function runContentPage():
string {
228 $obj =
dbx()->get_include_obj(
'dbxContent_content');
232 private function isCacheablePageAction(
string $action):
bool {
233 return in_array($action, array(
'show',
'cms'),
true)
237 private function isCacheablePageOutput(
string $content):
bool {
238 if (trim($content) ===
'' || http_response_code() >= 400) {
242 foreach (array(
'alert alert-warning',
'Keine dbx_cid',
'nicht gefunden!') as $marker) {
243 if (stripos($content, $marker) !==
false) {
251 public function run($action=
'') {
253 $mid =
dbx()->get_system_var(
'dbx_modul_id');
254 $modul =
dbx()->get_system_var(
'dbx_modul');
258 if (!$action) $action=
dbx()->get_modul_var(
'dbx_run1',
'show');
260 $cacheCid = (int)
dbx()->get_system_var(
'dbx_content_route_cid', 0,
'int');
261 $isPermalinkPageRoute = in_array((
string)$action, array(
'show',
'cms'),
true)
262 && (int)
dbx()->get_system_var(
'dbx_content_permalink_request', 0,
'int') === 1;
263 $usePageCache = $cacheCid > 0 && $this->isCacheablePageAction((
string)$action);
266 if ($cachedContent !==
null) {
267 dbx()->set_system_var(
'dbx_content_permalink_request', 0);
268 dbx()->debug(
"#CONTENT-CACHE hit module=dbxContent cid=($cacheCid)");
269 return $cachedContent;
276 $work =
dbx()->get_modul_var(
'dbx_run2',
'show',
'parameter');
277 if ($work ===
'tree' || $work ===
'page') {
278 $obj =
dbx()->get_include_obj(
'dbxContent_treeview');
279 $content = $obj->run($work ===
'tree' ?
'tree_view' :
'tree_page');
282 $content = $this->runContentPage();
285 $work =
dbx()->get_modul_var(
'dbx_run2',
'show',
'parameter');
286 $obj=
dbx()->get_include_obj(
'dbxContent_treeview');
287 if ($work ===
'tree') {
288 $content=$obj->run(
'tree_view');
289 } elseif ($work ===
'page') {
290 $content=$obj->run(
'tree_page');
292 $content=$obj->run(
'view');
297 if (
dbx()->get_modul_var(
'dbx_run2',
'',
'parameter') ===
'edit') {
299 $content = $this->runContentPage();
302 $obj=
dbx()->get_include_obj(
'dbxContent_treeview');
303 $content=$obj->run(
'view');
306 $obj=
dbx()->get_include_obj(
'dbxContent_content');
307 $content=$obj->run();
312 $content = $this->runContentPage();
315 $obj=
dbx()->get_include_obj(
'dbxContent_treeview');
316 $content=$obj->run(
'view');
321 $obj=
dbx()->get_include_obj(
'dbxContent_treeview');
322 $content=$obj->run($action);
327 $obj=
dbx()->get_include_obj(
'dbxContent_content');
328 $content=$obj->run();
332 $content=$this->serve_media();
336 $obj=
dbx()->get_include_obj(
'dbxContentContextHelp');
337 $content=is_object($obj) ? $obj->run() :
'';
341 $obj=
dbx()->get_include_obj(
'dbxContentConsent');
342 $content=is_object($obj) ? $obj->run() :
'';
354 $content.=
"<span class='warning action_msg'>Modul=($modul) Action=($action) is undef.</span>";
357 if ($usePageCache && $this->isCacheablePageOutput((
string)$content)) {
359 dbx()->debug(
"#CONTENT-CACHE write module=dbxContent cid=($cacheCid)");
362 if ($isPermalinkPageRoute) {
363 dbx()->set_system_var(
'dbx_content_permalink_request', 0);
static writeContent(int $cid, string $html, array $meta=array(), string $lng='')