dbXapp 2.0
RAD, CMS, Module und Runtime-IDE fuer dbXapp
Loading...
Searching...
No Matches
dbxMenu.class.php
Go to the documentation of this file.
1<?php
2namespace dbx\dbxMenu;
3
4Class dbxMenu {
5 Public $oTPL;
6
7 public function __construct() {
8 $this->oTPL=dbx()->get_system_obj('dbxTPL');
9 }
10
11
12 private function get_menu_tpl($menu='undef') {
13 $access=1; $content='';
14 if (strpos($menu, '_admin')) $access=dbx()->can('admin');
15 if (strpos($menu, '-admin')) $access=dbx()->can('admin');
16 if (strpos($menu, 'admin_')) $access=dbx()->can('admin');
17 if (strpos($menu, 'admin-')) $access=dbx()->can('admin');
18
19 dbx()->debug("load get_menu_tpl=($menu)");
20
21 if ($access) {
22 $data=array();
23 $oWeb = dbx()->get_system_obj('dbxWebApp');
24 $baseSelf = $oWeb->normalize_self_url(dbx()->get_system_var('dbx_self_url','?','*'));
25 $data['self'] = $oWeb->route_param_prefix($baseSelf);
26 $edit = (int) dbx()->get_system_var('dbx_edit', 0, 'int');
27 $nextEdit = $this->next_editor_level($edit);
28 $data['edit_level'] = $edit;
29 $data['edit_next_level'] = $nextEdit;
30 $data['edit_toggle_url'] = $this->editor_level_url($baseSelf, $nextEdit);
31 $data['edit_level_menu'] = $this->render_editor_level_menu($baseSelf, $edit);
32 $data['edit_state_class'] = $edit ? 'is-active' : 'is-disabled';
33 $data['edit_toggle_title'] = 'Editor Level ' . $edit . ': ' . $this->editor_level_label($edit) .
34 ' | Level auswählen';
35 $lng = strtolower(trim((string) dbx()->get_system_var('dbx_lng', 'de')));
36 $lngMeta = $this->language_meta($lng);
37 $data['lng_active'] = $lng;
38 $data['lng_active_flag'] = $lngMeta['flag'];
39 $data['lng_active_label'] = $lngMeta['label'];
40 $data['lng_menu'] = $this->render_language_menu($baseSelf, $lng);
41 $data['lng_toggle_title'] = 'Sprache: ' . $lngMeta['label'];
42 $activeDesign = $this->active_frontend_design();
43 $data['design_active'] = $activeDesign;
44 $data['design_active_label'] = $this->design_label($activeDesign);
45 $data['design_menu'] = $this->render_design_menu($baseSelf, $activeDesign);
46 $activeSkin = $this->normalize_design_skin($activeDesign, dbx()->get_skin());
47 $data['skin_menu'] = $this->render_skin_menu($activeDesign, $activeSkin);
48 $data['skin_section_label'] = $activeDesign === 'fleurop' ? 'Skin' : 'Farbschema';
49 $data['design_toggle_title'] = 'Darstellung: ' . $data['design_active_label'] . ' / ' .
50 $this->skin_label($activeDesign, $activeSkin);
51 $openContactCount = $this->open_contact_count($menu);
52 $data['contact_open_count'] = $openContactCount;
53 $data['contact_open_title'] = $openContactCount === 1
54 ? '1 offene Kontaktanfrage'
55 : $openContactCount . ' offene Kontaktanfragen';
56 $data['contact_open_badge'] = $openContactCount > 0
57 ? '<span class="dbx-contact-open-badge" aria-label="' . $openContactCount . ' offene Kontaktanfragen">'
58 . $openContactCount . '</span>'
59 : '';
60 $cartCount = $this->shop_cart_count();
61 $data['shop_cart_count'] = $cartCount;
62 $data['shop_cart_badge'] = $this->shop_cart_badge($cartCount);
63 $i = dbx()->next_id();
64 $content=$this->oTPL->get_tpl($menu,$data,'htm',$i);
65 $content=$this->replace_cms_placeholders($content);
66 }
67
68 return $content;
69 }
70
71 private function open_contact_count(string $menu): int {
72 if (stripos($menu, 'admin') === false || !dbx()->can('admin')) {
73 return 0;
74 }
75
76 try {
77 $tickets = dbx()->get_include_obj('dbxContactTicket', 'dbxContact');
78 if (is_object($tickets) && method_exists($tickets, 'openCount')) {
79 return max(0, (int) $tickets->openCount());
80 }
81 } catch (\Throwable $e) {
82 dbx()->debug('[dbxMenu] offene Kontaktanfragen konnten nicht gezaehlt werden: ' . $e->getMessage());
83 }
84
85 return 0;
86 }
87
88 private function next_editor_level(int $edit): int {
89 $levels = array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
90 $idx = array_search($edit, $levels, true);
91
92 if ($idx === false) {
93 return 0;
94 }
95
96 return $levels[($idx + 1) % count($levels)];
97 }
98
99 private function editor_level_label(int $edit): string {
100 switch ($edit) {
101 case 0: return 'aus';
102 case 1: return 'Templates ohne dbx';
103 case 2: return 'Templates dbx';
104 case 3: return 'alle Modul-Templates';
105 case 4: return 'FD Definitionen';
106 case 5: return 'DD Definitionen';
107 case 6: return 'Modul-/Include-Class';
108 case 7: return 'myX System-Class';
109 case 8: return 'Config';
110 case 9: return 'alles';
111 }
112
113 return 'Reserve';
114 }
115
116 private function editor_level_icon(int $edit): string {
117 switch ($edit) {
118 case 0: return 'bi-x-circle';
119 case 1:
120 case 2:
121 case 3: return 'bi-pencil-square';
122 case 4: return 'bi-card-checklist';
123 case 5: return 'bi-database';
124 case 6: return 'bi-braces';
125 case 7: return 'bi-cpu';
126 case 8: return 'bi-gear';
127 case 9: return 'bi-files';
128 }
129
130 return 'bi-question-circle';
131 }
132
133 private function render_editor_level_menu(string $baseSelf, int $activeEdit): string {
134 $levels = array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
135 $html = '';
136
137 foreach ($levels as $level) {
138 $class = $level === $activeEdit ? ' class="is-active"' : '';
139 $url = htmlspecialchars($this->editor_level_url($baseSelf, $level), ENT_QUOTES, 'UTF-8');
140 $label = htmlspecialchars($this->editor_level_label($level), ENT_QUOTES, 'UTF-8');
141 $icon = htmlspecialchars($this->editor_level_icon($level), ENT_QUOTES, 'UTF-8');
142
143 $html .= '<li' . $class . '>';
144 $html .= '<a class="dbxEditLevel" href="' . $url . '" title="Editor Level ' . $level . ': ' . $label . '">';
145 $html .= '<i class="bi ' . $icon . '"></i>';
146 $html .= '<span>Level ' . $level . ' - ' . $label . '</span>';
147 $html .= '</a>';
148 $html .= '</li>';
149 }
150
151 return $html;
152 }
153
154 private function editor_level_url(string $url, int $edit): string {
155 $oWeb = dbx()->get_system_obj('dbxWebApp');
156 return $oWeb->append_route_params(
157 $oWeb->normalize_self_url($url),
158 array('dbx_edit' => (string) $edit)
159 );
160 }
161
162 private function language_catalog(): array {
163 return array(
164 'de' => array('label' => 'Deutsch', 'flag' => 'de'),
165 'en' => array('label' => 'English', 'flag' => 'gb'),
166 'es' => array('label' => 'Español', 'flag' => 'es'),
167 );
168 }
169
170 private function language_options(): array {
171 $config = dbx()->get_config('dbx');
172 $accessible = $config['accessible_lng'] ?? 'de,en,es';
173
174 if (!is_array($accessible)) {
175 $accessible = array_filter(array_map('trim', explode(',', (string) $accessible)));
176 }
177
178 $catalog = $this->language_catalog();
179 $options = array();
180
181 foreach ($accessible as $code) {
182 $code = strtolower(trim((string) $code));
183 if ($code === '' || !isset($catalog[$code])) {
184 continue;
185 }
186 $options[$code] = $catalog[$code];
187 }
188
189 if (empty($options)) {
190 $options['de'] = $catalog['de'];
191 }
192
193 return $options;
194 }
195
196 private function language_meta(string $lng): array {
197 $options = $this->language_options();
198 $lng = strtolower(trim($lng));
199
200 if (isset($options[$lng])) {
201 return $options[$lng];
202 }
203
204 return reset($options);
205 }
206
207 private function language_url(string $url, string $lng): string {
208 $oWeb = dbx()->get_system_obj('dbxWebApp');
209 return $oWeb->append_route_params(
210 $oWeb->normalize_self_url($url),
211 array('dbx_lng' => strtolower(trim($lng)))
212 );
213 }
214
215 private function render_language_menu(string $baseSelf, string $activeLng): string {
216 $activeLng = strtolower(trim($activeLng));
217 $html = '';
218
219 foreach ($this->language_options() as $code => $meta) {
220 $class = $code === $activeLng ? ' class="is-active"' : '';
221 $url = htmlspecialchars($this->language_url($baseSelf, $code), ENT_QUOTES, 'UTF-8');
222 $label = htmlspecialchars($meta['label'], ENT_QUOTES, 'UTF-8');
223 $flag = htmlspecialchars($meta['flag'], ENT_QUOTES, 'UTF-8');
224 $codeLabel = htmlspecialchars(strtoupper($code), ENT_QUOTES, 'UTF-8');
225
226 $html .= '<li' . $class . '>';
227 $html .= '<a class="dbxLngOpt" href="' . $url . '" title="' . $label . '">';
228 $html .= '<span class="dbx-lng-flag" data-dbx-flag="' . $flag . '" aria-hidden="true"></span>';
229 $html .= '<span class="dbx-lng-code">' . $codeLabel . '</span>';
230 $html .= '</a>';
231 $html .= '</li>';
232 }
233
234 return $html;
235 }
236
241 private function frontend_design_options(): array {
242 $options = array();
243 $designRoot = dbx_get_base_dir() . 'dbx/design';
244
245 foreach (glob($designRoot . '/*', GLOB_ONLYDIR) ?: array() as $dir) {
246 $name = basename($dir);
247 if ($name === '' || $name[0] === '_' || $name[0] === '-') {
248 continue;
249 }
250 if (!preg_match('/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/', $name)) {
251 continue;
252 }
253 if (!is_file(dbx_os_path_file($dir . '/htm/default.htm'))) {
254 continue;
255 }
256
257 $options[$name] = $this->design_label($name);
258 }
259
260 if (empty($options)) {
261 $options['dbxapp'] = 'dbXapp';
262 }
263
264 uasort($options, static function ($a, $b) {
265 return strnatcasecmp((string)$a, (string)$b);
266 });
267
268 return $options;
269 }
270
271 private function design_label(string $design): string {
272 if (strtolower($design) === 'dbxapp') {
273 return 'dbXapp';
274 }
275
276 $label = trim(str_replace(array('-', '_'), ' ', $design));
277 return $label !== '' ? ucfirst($label) : $design;
278 }
279
280 private function active_frontend_design(): string {
281 $config = dbx()->get_config('dbx');
282 $design = trim((string)dbx()->get_system_var('dbx_design', $config['default_design_user'] ?? 'dbxapp'));
283
284 if ($design === 'user') {
285 $design = (string)($config['default_design_user'] ?? 'dbxapp');
286 } elseif ($design === 'admin') {
287 $design = (string)($config['default_design_admin'] ?? 'dbxapp');
288 }
289
290 $options = $this->frontend_design_options();
291 if (!isset($options[$design])) {
292 $default = (string)($config['default_design_user'] ?? 'dbxapp');
293 $design = isset($options[$default]) ? $default : (string)array_key_first($options);
294 }
295
296 return $design;
297 }
298
299 private function design_url(string $url, string $design): string {
300 $oWeb = dbx()->get_system_obj('dbxWebApp');
301 $params = array('dbx_design' => $design);
302
303 if (strtolower($design) === 'fleurop') {
304 $params['dbx_color'] = $this->normalize_design_skin($design, dbx()->get_skin());
305 }
306
307 return $oWeb->append_route_params(
308 $oWeb->normalize_self_url($url),
309 $params
310 );
311 }
312
313 private function render_design_menu(string $baseSelf, string $activeDesign): string {
314 $html = '';
315
316 foreach ($this->frontend_design_options() as $design => $label) {
317 $active = $design === $activeDesign;
318 $class = $active ? ' class="is-active"' : '';
319 $url = htmlspecialchars($this->design_url($baseSelf, $design), ENT_QUOTES, 'UTF-8');
320 $designEsc = htmlspecialchars($design, ENT_QUOTES, 'UTF-8');
321 $labelEsc = htmlspecialchars($label, ENT_QUOTES, 'UTF-8');
322
323 $html .= '<li' . $class . '>';
324 $html .= '<a class="dbx-design-opt' . ($active ? ' is-active' : '') . '" href="' . $url . '" data-design="' . $designEsc . '" title="Design ' . $labelEsc . '">';
325 $html .= '<i class="bi bi-window-sidebar dbx-design-icon" aria-hidden="true"></i>';
326 $html .= '<span>' . $labelEsc . '</span>';
327 if ($active) {
328 $html .= '<i class="bi bi-check2 dbx-design-check" aria-hidden="true"></i>';
329 }
330 $html .= '</a>';
331 $html .= '</li>';
332 }
333
334 return $html;
335 }
336
337 private function skin_options(string $design): array {
338 if (strtolower($design) === 'fleurop') {
339 return array(
340 'hell' => array('label' => 'Light', 'icon' => 'bi-sun'),
341 'dunkel' => array('label' => 'Dark', 'icon' => 'bi-moon-stars'),
342 );
343 }
344
345 return array(
346 'hell' => array('label' => 'Hell', 'icon' => 'bi-sun'),
347 'gelb' => array('label' => 'Gelb', 'icon' => 'bi-brightness-high'),
348 'rot' => array('label' => 'Rot', 'icon' => 'bi-droplet-fill'),
349 'gruen' => array('label' => 'Gruen', 'icon' => 'bi-tree'),
350 'blau' => array('label' => 'Blau', 'icon' => 'bi-droplet'),
351 'dunkel' => array('label' => 'Dunkel', 'icon' => 'bi-moon'),
352 );
353 }
354
355 private function normalize_design_skin(string $design, string $skin): string {
356 if (strtolower($design) === 'fleurop') {
357 return $skin === 'dunkel' ? 'dunkel' : 'hell';
358 }
359
360 return array_key_exists($skin, $this->skin_options($design)) ? $skin : 'blau';
361 }
362
363 private function skin_label(string $design, string $skin): string {
364 $options = $this->skin_options($design);
365 return (string)($options[$skin]['label'] ?? $skin);
366 }
367
368 private function render_skin_menu(string $design, string $activeSkin): string {
369 $html = '';
370
371 foreach ($this->skin_options($design) as $skin => $meta) {
372 $active = $skin === $activeSkin;
373 $skinEsc = htmlspecialchars($skin, ENT_QUOTES, 'UTF-8');
374 $labelEsc = htmlspecialchars((string)$meta['label'], ENT_QUOTES, 'UTF-8');
375 $iconEsc = htmlspecialchars((string)$meta['icon'], ENT_QUOTES, 'UTF-8');
376
377 $html .= '<li' . ($active ? ' class="is-active"' : '') . '>';
378 $html .= '<a href="#" class="dbx-skin-opt' . ($active ? ' is-active' : '') . '" data-skin="' . $skinEsc . '">';
379 $html .= '<i class="bi ' . $iconEsc . '" aria-hidden="true"></i><span>' . $labelEsc . '</span>';
380 if ($active) {
381 $html .= '<i class="bi bi-check2 dbx-skin-check" aria-hidden="true"></i>';
382 }
383 $html .= '</a></li>';
384 }
385
386 return $html;
387 }
388
389 private function h($value): string {
390 return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
391 }
392
393 private function shop_cart_count(): int {
394 if (session_status() === PHP_SESSION_NONE) {
395 if (headers_sent()) {
396 return 0;
397 }
398 session_start();
399 }
400 $cart = $_SESSION['dbxShop_cart'] ?? array();
401 if (!is_array($cart)) {
402 return 0;
403 }
404 $count = 0;
405 foreach ($cart as $qty) {
406 $count += max(0, (int)$qty);
407 }
408 return $count;
409 }
410
411 private function shop_cart_badge(int $count): string {
412 if ($count <= 0) {
413 return '';
414 }
415 $label = $count === 1 ? '1 Artikel im Warenkorb' : $count . ' Artikel im Warenkorb';
416 return '<span class="dbx-shop-cart-menu-badge" aria-label="' . $this->h($label) . '">' . $this->h((string)$count) . '</span>';
417 }
418
419 private function user_avatar_url(int $uid): string {
420 $file = '';
421 try {
422 $db = dbx()->get_system_obj('dbxDB');
423 $rec = $db->select1('dbxUser', $uid, 'avatar', 0);
424 if (is_array($rec)) {
425 $file = trim((string)($rec['avatar'] ?? ''));
426 }
427 } catch (\Throwable $e) {
428 $file = trim((string)dbx()->user('avatar'));
429 }
430
431 if ($file === '' || !preg_match('/^avatar-[0-9]+\.(?:webp|png|jpe?g|gif)$/i', $file)) {
432 $file = 'avatar-0.png';
433 }
434
436 $public = 'files/user/avatar/' . $file;
437 if (is_file(dbx_os_path_file($base . $public))) {
438 return dbx()->get_base_url() . $public;
439 }
440
441 $legacy = 'dbx/modules/dbxUser/img/avatar/' . $file;
442 if (is_file(dbx_os_path_file($base . $legacy))) {
443 return dbx()->get_base_url() . $legacy;
444 }
445
446 return dbx()->get_base_url() . 'files/user/avatar/avatar-0.png';
447 }
448
449 private function user_menu_html(int $uid): string {
450 $avatar = $this->h($this->user_avatar_url($uid));
451 $cartCount = $this->shop_cart_count();
452 $cartBadge = $this->shop_cart_badge($cartCount);
453 return '<li class="align-right dbx-user-avatar-menu">'
454 . '<a title="Benutzer" style="cursor:pointer;">'
455 . '<span class="dbx-user-menu-avatar-wrap">'
456 . '<img class="dbx-user-menu-avatar" src="' . $avatar . '?' . time() . '" alt="Benutzer">'
457 . '<span class="dbx-user-menu-mail" aria-hidden="true"><i class="bi bi-envelope-fill"></i></span>'
458 . '</span>'
459 . '</a>'
460 . '<ul>'
461 . '<li><a href="?dbx_modul=dbxShop&amp;dbx_run1=catalog"><i class="bi bi-bag"></i> Shop</a></li>'
462 . '<li><a class="dbx-shop-cart-menu-link" href="?dbx_modul=dbxShop&amp;dbx_run1=cart"><span class="dbx-shop-cart-menu-icon"><i class="bi bi-basket"></i>' . $cartBadge . '</span> Warenkorb</a></li>'
463 . '<li><a href="?dbx_modul=dbxShop&amp;dbx_run1=orders"><i class="bi bi-receipt"></i> Meine Bestellungen</a></li>'
464 . '<li><a href="?dbx_modul=dbxShop&amp;dbx_run1=withdrawal"><i class="bi bi-arrow-counterclockwise"></i> Widerruf / Retoure</a></li>'
465 . '<li><a href="?dbx_modul=dbxContact&amp;dbx_run1=form"><i class="bi bi-envelope-plus"></i> Neue Anfrage</a></li>'
466 . '<li><a href="?dbx_modul=dbxContact&amp;dbx_run1=my"><i class="bi bi-envelope"></i> Meine Anfragen</a></li>'
467 . '<li><a href="?dbx_modul=dbxUser&amp;dbx_run1=user&amp;dbx_run2=edit_profil"><i class="bi bi-person-circle"></i> Mein Profil</a></li>'
468 . '</ul>'
469 . '</li>';
470 }
471
472 private function replace_cms_placeholders($content) {
473 if (!is_string($content) || strpos($content, '[cms:') === false) {
474 return $content;
475 }
476
477 $hasFlatMarker = stripos($content, '[cms:flat]') !== false;
478 $flatMarkerToken = '###DBX_CMS_FLAT_MARKER_' . md5((string)microtime(true)) . '###';
479 $flatMarkerHtml = '';
480
481 $content = preg_replace_callback('/\‍[cms:([^\‍]]*)\‍]/i', function ($match) use ($hasFlatMarker, $flatMarkerToken, &$flatMarkerHtml) {
482 $raw = trim((string)$match[1]);
483
484 if (preg_match('/^flat$/i', $raw)) {
485 return $flatMarkerToken;
486 }
487
488 $params = array();
489 parse_str(str_replace('&amp;', '&', $raw), $params);
490
491 $root = (int)($params['root'] ?? 0);
492 $flat = (int)($params['flat'] ?? 1);
493 $splitFlat = $hasFlatMarker && $flat === 1;
494
495 $obj = dbx()->get_include_obj('dbxContent_menu');
496 if ($obj && method_exists($obj, 'render_placeholder')) {
497 if ($splitFlat && method_exists($obj, 'render_flat_marker')) {
498 $flatMarkerHtml .= $obj->render_flat_marker($root, $flat);
499 }
500 return $obj->render_placeholder($root, $flat, $splitFlat);
501 }
502
503 return '';
504 }, $content);
505
506 return str_replace($flatMarkerToken, $flatMarkerHtml, $content);
507 }
508
509 public function run() {
510 $action=dbx()->get_modul_var('dbx_run1' ,'a-undef','*');
511 $menu =dbx()->get_modul_var('tpl' ,'b-undef','*');
512 $uid =dbx()->user();
513 $mid =dbx()->get_system_var('dbx_activ_modul_id',-1);
514 $content="Menu ($menu) not found"; // default output !
515
516 dbx()->debug("#Modul Menu# action=($action) Menu=($menu) Modul-id=($mid)");
517
518 switch ($action) {
519 case 'load':
520 $content=$this->get_menu_tpl($menu);
521 break;
522
523 case 'content':
524 $obj=dbx()->get_include_obj('dbxContent_menu');
525 $content=$obj->run();
526 break;
527 }
528
529
530 if ($uid > 0) {
531 $LoginOut ='LogOut'; $login_out = 'logout'; $login_icon = 'bi-power';
532 } else {
533 $LoginOut ='LogIn' ; $login_out = 'login'; $login_icon = 'bi-person-circle';
534 }
535 $ProfileLink = $this->user_menu_html((int)$uid);
536
537 $AdminMenu='';
538 $design=dbx()->get_system_var('dbx_activ_design');
539 $page =dbx()->get_system_var('dbx_activ_page');
540 $lng =dbx()->get_system_var('dbx_activ_lng');
541
542 if (dbx()->can('admin')) $AdminMenu="Admin";
543 $content=str_replace('{dbx:Admin}' ,$AdminMenu,$content);
544 $content=str_replace('{dbx:LogInOut}' ,$LoginOut ,$content);
545 $content=str_replace('{dbx:login_out}',$login_out,$content);
546 $content=str_replace('{dbx:login_icon}',$login_icon,$content);
547 $content=str_replace('{dbx:profile_link}',$ProfileLink,$content);
548 $content=str_replace('{dbx:design}' ,$design ,$content);
549 $content=str_replace('{dbx:page}' ,$page ,$content);
550 $content=str_replace('{dbx:lng}' ,$lng ,$content);
551
552
553 return $content;
554 }
555
556}
557
558?>
$config['version']
Definition config.php:2
dbx_os_path_file($path_file)
Definition index.php:164
if( $syncRequest)
Definition index.php:520
dbx_get_base_dir($cut_Data=0)
Definition index.php:157
DBX schema administration.