dbXapp 2.0
RAD, CMS, Module und Runtime-IDE fuer dbXapp
Loading...
Searching...
No Matches
dbxContent.class.php
Go to the documentation of this file.
1<?php
2namespace dbx\dbxContent;
3
4require_once __DIR__ . '/include/dbxContent_bootstrap.php';
5
6class dbxContent {
7
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, '/');
13
14 if ($rel === '' || strpos($rel, '..') !== false || preg_match('~(^|/)\.~', $rel)) {
15 return '';
16 }
17
18 if (!preg_match('~^(media|dbxContent/media)/~i', $rel)) {
19 return '';
20 }
21
22 return $rel;
23 }
24
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);
29 }
30
31 private function serve_media_file($rel, $mime = '', $file_name = '') {
32 $rel = $this->normalize_media_path($rel);
33 if ($rel === '') {
34 http_response_code(403);
35 return '';
36 }
37
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);
43 return '';
44 }
45
46 $mime = trim((string)$mime);
47 if ($mime === '') {
48 $detected = function_exists('mime_content_type') ? mime_content_type($file) : '';
49 $mime = $detected ?: 'application/octet-stream';
50 }
51
52 $file_name = trim((string)$file_name);
53 if ($file_name === '') $file_name = basename($file);
54 $file_name = str_replace('"', '', basename($file_name));
55
56 if (function_exists('session_status') && session_status() === PHP_SESSION_ACTIVE) {
57 session_write_close();
58 }
59
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');
71 exit;
72 }
73
74 $start = 0;
75 $end = $size > 0 ? $size - 1 : 0;
76 $status = 200;
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);
84 }
85 if ($start <= $end && $size > 0) {
86 $status = 206;
87 http_response_code(206);
88 header('Content-Range: bytes ' . $start . '-' . $end . '/' . $size);
89 } else {
90 http_response_code(416);
91 header('Content-Range: bytes */' . $size);
92 exit;
93 }
94 }
95
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');
105 if ($out) {
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;
111 echo $chunk;
112 $remaining -= strlen($chunk);
113 if (function_exists('connection_aborted') && connection_aborted()) break;
114 }
115 fclose($out);
116 }
117 exit;
118 }
119
120 private function media_missing_key($id, $reason, $extra = '') {
121 if ($reason === 'file_missing' && trim((string)$extra) !== '') {
122 return ltrim(str_replace('\\', '/', (string)$extra), '/');
123 }
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;
127 }
128
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));
133 }
134 http_response_code((int)$code);
135 return '';
136 }
137
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';
143 return 'file';
144 }
145
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';
151 }
152 return '';
153 }
154
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'] ?? ''),
160 'image/jpeg',
161 'thumb-' . (string)($row['file_name'] ?? '')
162 );
163 }
164 $thumb_url = $this->external_video_thumb_url($row);
165 if ($thumb_url !== '') {
166 header('Location: ' . $thumb_url, true, 302);
167 exit;
168 }
169 return $this->media_serve_fail(404, (int)($row['id'] ?? 0), 'external_unavailable');
170 }
171
172 private function serve_media() {
173 $id = (int)dbx()->get_modul_var('dbx_mid', 0, 'int');
174 if ($id <= 0) {
175 return $this->media_serve_fail(404, $id, 'invalid_id');
176 }
177
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');
182 }
183 if ((int)($row['active'] ?? 0) !== 1) {
184 return $this->media_serve_fail(404, $id, 'inactive');
185 }
186
187 if ($this->media_row_type($row) === 'external_video') {
188 return $this->serve_external_media($row);
189 }
190
191 $want_thumb = (int)dbx()->get_modul_var('dbx_thumb', 0, 'int') === 1;
192 $rel = '';
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;
199 } else {
200 $rel = (string)($row['file_path'] ?? '');
201 }
202 if ($rel === '') {
203 return $this->media_serve_fail(404, $id, 'empty_path');
204 }
205
206 $rel_norm = $this->normalize_media_path($rel);
207 if ($rel_norm === '') {
208 return $this->media_serve_fail(403, $id, 'invalid_path', $rel);
209 }
210
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'] ?? '')
219 );
220 }
221 return $this->media_serve_fail(404, $id, 'file_missing', $rel);
222 }
223
224 return $this->serve_media_file($rel, $mime, $file_name);
225 }
226
227 private function runContentPage(): string {
228 $obj = dbx()->get_include_obj('dbxContent_content');
229 return $obj->run();
230 }
231
232 private function isCacheablePageAction(string $action): bool {
233 return in_array($action, array('show', 'cms'), true)
235 }
236
237 private function isCacheablePageOutput(string $content): bool {
238 if (trim($content) === '' || http_response_code() >= 400) {
239 return false;
240 }
241
242 foreach (array('alert alert-warning', 'Keine dbx_cid', 'nicht gefunden!') as $marker) {
243 if (stripos($content, $marker) !== false) {
244 return false;
245 }
246 }
247
248 return true;
249 }
250
251 public function run($action='') {
252 $uid =dbx()->user();
253 $mid =dbx()->get_system_var('dbx_modul_id');
254 $modul =dbx()->get_system_var('dbx_modul');
255 //dbx()->set_system_var('dbx_page', 'content');
256
257 $content="";
258 if (!$action) $action=dbx()->get_modul_var('dbx_run1','show');
259
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);
264 if ($usePageCache) {
265 $cachedContent = dbxContentPageCache::readContent($cacheCid);
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;
270 }
271 }
272
273 switch ($action) {
274 case 'cms':
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');
280 break;
281 }
282 $content = $this->runContentPage();
283 break;
284 }
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');
291 } else {
292 $content=$obj->run('view');
293 }
294 break;
295
296 case 'content':
297 if (dbx()->get_modul_var('dbx_run2', '', 'parameter') === 'edit') {
299 $content = $this->runContentPage();
300 break;
301 }
302 $obj=dbx()->get_include_obj('dbxContent_treeview');
303 $content=$obj->run('view');
304 break;
305 }
306 $obj=dbx()->get_include_obj('dbxContent_content');
307 $content=$obj->run();
308 break;
309
310 case 'edit':
312 $content = $this->runContentPage();
313 break;
314 }
315 $obj=dbx()->get_include_obj('dbxContent_treeview');
316 $content=$obj->run('view');
317 break;
318
319 case 'tree_view':
320 //case 'tree_page':
321 $obj=dbx()->get_include_obj('dbxContent_treeview');
322 $content=$obj->run($action);
323 break;
324
325 //case 'run':
326 case 'show':
327 $obj=dbx()->get_include_obj('dbxContent_content');
328 $content=$obj->run();
329 break;
330
331 case 'media':
332 $content=$this->serve_media();
333 break;
334
335 case 'help':
336 $obj=dbx()->get_include_obj('dbxContentContextHelp');
337 $content=is_object($obj) ? $obj->run() : '';
338 break;
339
340 case 'consent':
341 $obj=dbx()->get_include_obj('dbxContentConsent');
342 $content=is_object($obj) ? $obj->run() : '';
343 break;
344
345 case 'sitemap':
347 break;
348
349 case 'robots':
351 break;
352
353 default:
354 $content.="<span class='warning action_msg'>Modul=($modul) Action=($action) is undef.</span>";
355 } // switch
356
357 if ($usePageCache && $this->isCacheablePageOutput((string)$content)) {
358 if (dbxContentPageCache::writeContent($cacheCid, (string)$content)) {
359 dbx()->debug("#CONTENT-CACHE write module=dbxContent cid=($cacheCid)");
360 }
361 }
362 if ($isPermalinkPageRoute) {
363 dbx()->set_system_var('dbx_content_permalink_request', 0);
364 }
365
366 return $content;
367 } // run()
368
369} // class
370
371?>
static writeContent(int $cid, string $html, array $meta=array(), string $lng='')
static readContent(int $cid, string $lng='')
exit
Definition index.php:532
DBX schema administration.
$_SERVER['REQUEST_URI']