Bearsampp 2025.8.29
Loading...
Searching...
No Matches
class.winbinder.php
Go to the documentation of this file.
1<?php
2/*
3 *
4 * * Copyright (c) 2022-2025 Bearsampp
5 * * License: GNU General Public License version 3 or later; see LICENSE.txt
6 * * Website: https://bearsampp.com
7 * * Github: https://github.com/Bearsampp
8 *
9 */
10
19{
20 // Constants for control IDs and objects
21 const CTRL_ID = 0;
22 const CTRL_OBJ = 1;
23
24 // Constants for progress bar increment and new line
25 const INCR_PROGRESS_BAR = '++';
26 const NEW_LINE = '@nl@';
27
28 // Constants for message box types
29 const BOX_INFO = WBC_INFO;
30 const BOX_OK = WBC_OK;
31 const BOX_OKCANCEL = WBC_OKCANCEL;
32 const BOX_QUESTION = WBC_QUESTION;
33 const BOX_ERROR = WBC_STOP;
34 const BOX_WARNING = WBC_WARNING;
35 const BOX_YESNO = WBC_YESNO;
36 const BOX_YESNOCANCEL = WBC_YESNOCANCEL;
37
38 // Constants for cursor types
39 const CURSOR_ARROW = 'arrow';
40 const CURSOR_CROSS = 'cross';
41 const CURSOR_FINGER = 'finger';
42 const CURSOR_FORBIDDEN = 'forbidden';
43 const CURSOR_HELP = 'help';
44 const CURSOR_IBEAM = 'ibeam';
45 const CURSOR_NONE = null;
46 const CURSOR_SIZEALL = 'sizeall';
47 const CURSOR_SIZENESW = 'sizenesw';
48 const CURSOR_SIZENS = 'sizens';
49 const CURSOR_SIZENWSE = 'sizenwse';
50 const CURSOR_SIZEWE = 'sizewe';
51 const CURSOR_UPARROW = 'uparrow';
52 const CURSOR_WAIT = 'wait';
53 const CURSOR_WAITARROW = 'waitarrow';
54
55 // Constants for system information types
56 const SYSINFO_SCREENAREA = 'screenarea';
57 const SYSINFO_WORKAREA = 'workarea';
58 public $callback;
59 public $gauge;
61 private $countCtrls;
62
68 public function __construct()
69 {
70 global $bearsamppCore;
71 Util::logInitClass($this);
72
73 $this->defaultTitle = APP_TITLE . ' ' . $bearsamppCore->getAppVersion();
74 $this->reset();
75 }
76
80 public function reset(): void
81 {
82 $this->countCtrls = 1000;
83 $this->callback = array();
84 }
85
97 public function createAppWindow($caption, $width, $height, $style = null, $params = null): mixed
98 {
99 return $this->createWindow(null, AppWindow, $caption, WBC_CENTER, WBC_CENTER, $width, $height, $style, $params);
100 }
101
117 public function createWindow($parent, $wclass, $caption, $xPos, $yPos, $width, $height, $style = null, $params = null): mixed
118 {
119 global $bearsamppCore;
120
121 // Fix for PHP 8.2: Convert null to 0 for parent parameter
122 $parent = $parent === null ? 0 : $parent;
123
124 $caption = empty($caption) ? $this->defaultTitle : $this->defaultTitle . ' - ' . $caption;
125 $window = $this->callWinBinder('wb_create_window', array($parent, $wclass, $caption, $xPos, $yPos, $width, $height, $style, $params));
126
127 // Set tiny window icon
128 $this->setImage($window, $bearsamppCore->getIconsPath() . '/app.ico');
129
130 return $window;
131 }
132
142 private function callWinBinder($function, $params = array(), $removeErrorHandler = false): mixed
143 {
144 $result = false;
145 if (function_exists($function)) {
146 if ($removeErrorHandler) {
147 // Suppress all errors for this call
148 $oldErrorLevel = error_reporting(0);
149 $result = @call_user_func_array($function, $params);
150 error_reporting($oldErrorLevel);
151 } else {
152 $result = call_user_func_array($function, $params);
153 }
154 }
155
156 return $result;
157 }
158
167 public function setImage($wbobject, $path): mixed
168 {
169 if ($wbobject === null) {
170 error_log('Error: $wbobject is null.');
171
172 return false;
173 }
174
175 if (!file_exists($path)) {
176 error_log('Error: Image file does not exist at path: ' . $path);
177
178 return false;
179 }
180
181 return $this->callWinBinder('wb_set_image', array($wbobject, $path));
182 }
183
195 public function createNakedWindow($caption, $width, $height, $style = null, $params = null): mixed
196 {
197 $window = $this->createWindow(null, NakedWindow, $caption, WBC_CENTER, WBC_CENTER, $width, $height, $style, $params);
198 $this->setArea($window, $width, $height);
199
200 return $window;
201 }
202
212 public function setArea($wbobject, $width, $height): mixed
213 {
214 return $this->callWinBinder('wb_set_area', array($wbobject, WBC_TITLE, 0, 0, $width, $height));
215 }
216
223 public function destroyWindow($window): bool
224 {
225 // Check if window exists and is valid
226 if (!$window || !$this->windowIsValid($window)) {
227 return true; // Already closed or invalid window
228 }
229
230 // Get window title before destruction for fallback
231 $windowTitle = $this->getText($window);
232 $currentPid = Win32Ps::getCurrentPid();
233
234 // Attempt standard destruction
235 $this->callWinBinder('wb_destroy_window', array($window));
236
237 // Verify closure with retries
238 $maxAttempts = 3;
239 $attempt = 0;
240 $destroyed = false;
241
242 while ($attempt < $maxAttempts && !$destroyed) {
243 $this->processMessages();
244 usleep(100000); // 100ms delay
245 $destroyed = !$this->windowIsValid($window);
246 $attempt++;
247 }
248
249 // Fallback to process termination if window still exists
250 if (!$destroyed) {
251 // 1. Try to close using window title
252 $this->exec('taskkill', '/FI "WINDOWTITLE eq ' . $windowTitle . '" /F', true);
253
254 // 2. Try to kill process directly using Winbinder's PID method
255 $currentPid = Win32Ps::getCurrentPid();
256 if (!empty($currentPid)) {
257 $this->exec('taskkill', '/PID ' . $currentPid . ' /T /F', true);
258 $this->writeLog('Force-killed PID: ' . $currentPid . ' for window: ' . $window);
259 }
260
261 // 3. Final sanity check
262 if ($this->windowIsValid($window)) {
263 $this->callWinBinder('wb_destroy_window', array($window), true); // Force native call
264 }
265
266 // 4. Reset internal state to prevent memory leaks
267 $this->reset();
268 }
269
270 // Final verification
271 return !$this->windowIsValid($window);
272 }
273
281 public function getText($wbobject): mixed
282 {
283 return $this->callWinBinder('wb_get_text', array($wbobject));
284 }
285
292 private function windowIsValid($window): bool
293 {
294 if (!$window) {
295 return false;
296 }
297
298 // Try to get window text - if window is invalid, this will fail
299 $text = $this->callWinBinder('wb_get_text', array($window), true);
300 return ($text !== false);
301 }
302
308 private function processMessages(): void
309 {
310 $this->callWinBinder('wb_wait', array(null, 1), true);
311 }
312
322 public function exec($cmd, $params = null, $silent = false): mixed
323 {
324 global $bearsamppCore;
325
326 if ($silent) {
327 $silent = '"' . $bearsamppCore->getScript(Core::SCRIPT_EXEC_SILENT) . '" "' . $cmd . '"';
328 $cmd = 'wscript.exe';
329 $params = !empty($params) ? $silent . ' "' . $params . '"' : $silent;
330 }
331
332 $this->writeLog('exec: ' . $cmd . ' ' . $params);
333
334 return $this->callWinBinder('wb_exec', array($cmd, $params));
335 }
336
342 private static function writeLog($log): void
343 {
344 global $bearsamppRoot;
345 Util::logDebug($log, $bearsamppRoot->getWinbinderLogFilePath());
346 }
347
353 public function mainLoop(): mixed
354 {
355 return $this->callWinBinder('wb_main_loop');
356 }
357
365 public function refresh($wbobject): mixed
366 {
367 return $this->callWinBinder('wb_refresh', array($wbobject, true));
368 }
369
377 public function getSystemInfo($info): mixed
378 {
379 return $this->callWinBinder('wb_get_system_info', array($info));
380 }
381
394 public function drawImage($wbobject, $path, $xPos = 0, $yPos = 0, $width = 0, $height = 0): mixed
395 {
396 $image = $this->callWinBinder('wb_load_image', array($path));
397
398 return $this->callWinBinder('wb_draw_image', array($wbobject, $image, $xPos, $yPos, $width, $height));
399 }
400
414 public function drawText($parent, $caption, $xPos, $yPos, $width = null, $height = null, $font = null)
415 {
416 $caption = str_replace(self::NEW_LINE, PHP_EOL, $caption);
417 $width = $width == null ? 120 : $width;
418 $height = $height == null ? 25 : $height;
419
420 return $this->callWinBinder('wb_draw_text', array($parent, $caption, $xPos, $yPos, $width, $height, $font));
421 }
422
436 public function drawRect($parent, $xPos, $yPos, $width, $height, $color = 15790320, $filled = true)
437 {
438 return $this->callWinBinder('wb_draw_rect', array($parent, $xPos, $yPos, $width, $height, $color, $filled));
439 }
440
454 public function drawLine($wbobject, $xStartPos, $yStartPos, $xEndPos, $yEndPos, $color, $height = 1)
455 {
456 return $this->callWinBinder('wb_draw_line', array($wbobject, $xStartPos, $yStartPos, $xEndPos, $yEndPos, $color, $height));
457 }
458
469 public function createFont($fontName, $size = null, $color = null, $style = null)
470 {
471 return $this->callWinBinder('wb_create_font', array($fontName, $size, $color, $style));
472 }
473
481 public function wait($wbobject = null)
482 {
483 return $this->callWinBinder('wb_wait', array($wbobject), true);
484 }
485
494 public function destroyTimer($wbobject, $timerobject)
495 {
496 return $this->callWinBinder('wb_destroy_timer', array($wbobject, $timerobject));
497 }
498
506 public function findFile($filename)
507 {
508 $result = $this->callWinBinder('wb_find_file', array($filename));
509 $this->writeLog('findFile ' . $filename . ': ' . $result);
510
511 return $result != $filename ? $result : false;
512 }
513
524 public function setHandler($wbobject, $classCallback, $methodCallback, $launchTimer = null)
525 {
526 if ($launchTimer != null) {
527 $launchTimer = $this->createTimer($wbobject, $launchTimer);
528 }
529
530 $this->callback[$wbobject] = array($classCallback, $methodCallback, $launchTimer);
531
532 return $this->callWinBinder('wb_set_handler', array($wbobject, '__winbinderEventHandler'));
533 }
534
543 public function createTimer($wbobject, $wait = 1000)
544 {
545 $this->countCtrls++;
546
547 return array(
548 self::CTRL_ID => $this->countCtrls,
549 self::CTRL_OBJ => $this->callWinBinder('wb_create_timer', array($wbobject, $this->countCtrls, $wait))
550 );
551 }
552
561 public function setText($wbobject, $content)
562 {
563 $content = str_replace(self::NEW_LINE, PHP_EOL, $content);
564
565 return $this->callWinBinder('wb_set_text', array($wbobject, $content));
566 }
567
573 public function getFocus()
574 {
575 return $this->callWinBinder('wb_get_focus');
576 }
577
585 public function setFocus($wbobject)
586 {
587 return $this->callWinBinder('wb_set_focus', array($wbobject));
588 }
589
597 public function isEnabled($wbobject)
598 {
599 return $this->callWinBinder('wb_get_enabled', array($wbobject));
600 }
601
609 public function setDisabled($wbobject)
610 {
611 return $this->setEnabled($wbobject, false);
612 }
613
622 public function setEnabled($wbobject, $enabled = true)
623 {
624 return $this->callWinBinder('wb_set_enabled', array($wbobject, $enabled));
625 }
626
635 public function setStyle($wbobject, $style)
636 {
637 return $this->callWinBinder('wb_set_style', array($wbobject, $style));
638 }
639
649 public function sysDlgPath($parent, $title, $path = null)
650 {
651 return $this->callWinBinder('wb_sys_dlg_path', array($parent, $title, $path));
652 }
653
664 public function sysDlgOpen($parent, $title, $filter = null, $path = null)
665 {
666 return $this->callWinBinder('wb_sys_dlg_open', array($parent, $title, $filter, $path));
667 }
668
683 public function createLabel($parent, $caption, $xPos, $yPos, $width = null, $height = null, $style = null, $params = null)
684 {
685 $caption = str_replace(self::NEW_LINE, PHP_EOL, $caption);
686 $width = $width == null ? 120 : $width;
687 $height = $height == null ? 25 : $height;
688
689 return $this->createControl($parent, Label, $caption, $xPos, $yPos, $width, $height, $style, $params);
690 }
691
707 public function createControl($parent, $ctlClass, $caption, $xPos, $yPos, $width, $height, $style = null, $params = null)
708 {
709 $this->countCtrls++;
710
711 return array(
712 self::CTRL_ID => $this->countCtrls,
713 self::CTRL_OBJ => $this->callWinBinder('wb_create_control', array(
714 $parent,
715 $ctlClass,
716 $caption,
717 $xPos,
718 $yPos,
719 $width,
720 $height,
721 $this->countCtrls,
722 $style,
723 $params
724 )),
725 );
726 }
727
743 public function createInputText($parent, $value, $xPos, $yPos, $width = null, $height = null, $maxLength = null, $style = null, $params = null)
744 {
745 $value = str_replace(self::NEW_LINE, PHP_EOL, $value);
746 $width = $width == null ? 120 : $width;
747 $height = $height == null ? 25 : $height;
748 $inputText = $this->createControl($parent, EditBox, (string)$value, $xPos, $yPos, $width, $height, $style, $params);
749 if (is_numeric($maxLength) && $maxLength > 0) {
750 $this->setMaxLength($inputText[self::CTRL_OBJ], $maxLength);
751 }
752
753 return $inputText;
754 }
755
764 public function setMaxLength($wbobject, $length)
765 {
766 return $this->callWinBinder('wb_send_message', array($wbobject, 0x00c5, $length, 0));
767 }
768
783 public function createEditBox($parent, $value, $xPos, $yPos, $width = null, $height = null, $style = null, $params = null)
784 {
785 $value = str_replace(self::NEW_LINE, PHP_EOL, $value);
786 $width = $width == null ? 540 : $width;
787 $height = $height == null ? 340 : $height;
788 $editBox = $this->createControl($parent, RTFEditBox, (string)$value, $xPos, $yPos, $width, $height, $style, $params);
789
790 return $editBox;
791 }
792
807 public function createHyperLink($parent, $caption, $xPos, $yPos, $width = null, $height = null, $style = null, $params = null)
808 {
809 $caption = str_replace(self::NEW_LINE, PHP_EOL, $caption);
810 $width = $width == null ? 120 : $width;
811 $height = $height == null ? 15 : $height;
812 $hyperLink = $this->createControl($parent, HyperLink, (string)$caption, $xPos, $yPos, $width, $height, $style, $params);
813 $this->setCursor($hyperLink[self::CTRL_OBJ], self::CURSOR_FINGER);
814
815 return $hyperLink;
816 }
817
826 public function setCursor($wbobject, $type = self::CURSOR_ARROW)
827 {
828 return $this->callWinBinder('wb_set_cursor', array($wbobject, $type));
829 }
830
845 public function createRadioButton($parent, $caption, $checked, $xPos, $yPos, $width = null, $height = null, $startGroup = false)
846 {
847 $caption = str_replace(self::NEW_LINE, PHP_EOL, $caption);
848 $width = $width == null ? 120 : $width;
849 $height = $height == null ? 25 : $height;
850
851 return $this->createControl($parent, RadioButton, (string)$caption, $xPos, $yPos, $width, $height, $startGroup ? WBC_GROUP : null, $checked ? 1 : 0);
852 }
853
868 public function createButton($parent, $caption, $xPos, $yPos, $width = null, $height = null, $style = null, $params = null)
869 {
870 $width = $width == null ? 80 : $width;
871 $height = $height == null ? 25 : $height;
872
873 return $this->createControl($parent, PushButton, $caption, $xPos, $yPos, $width, $height, $style, $params);
874 }
875
890 public function createProgressBar($parent, $max, $xPos, $yPos, $width = null, $height = null, $style = null, $params = null)
891 {
892 global $bearsamppLang;
893
894 $width = $width == null ? 200 : $width;
895 $height = $height == null ? 15 : $height;
896 $progressBar = $this->createControl($parent, Gauge, $bearsamppLang->getValue(Lang::LOADING), $xPos, $yPos, $width, $height, $style, $params);
897
898 $this->setRange($progressBar[self::CTRL_OBJ], 0, $max);
899 $this->gauge[$progressBar[self::CTRL_OBJ]] = 0;
900
901 return $progressBar;
902 }
903
911 public function getValue($wbobject)
912 {
913 return $this->callWinBinder('wb_get_value', array($wbobject));
914 }
915
925 public function setRange($wbobject, $min, $max)
926 {
927 return $this->callWinBinder('wb_set_range', array($wbobject, $min, $max));
928 }
929
935 public function incrProgressBar($progressBar)
936 {
937 $this->setProgressBarValue($progressBar, self::INCR_PROGRESS_BAR);
938 }
939
946 public function setProgressBarValue($progressBar, $value)
947 {
948 if ($progressBar != null && isset($progressBar[self::CTRL_OBJ]) && isset($this->gauge[$progressBar[self::CTRL_OBJ]])) {
949 if (strval($value) == self::INCR_PROGRESS_BAR) {
950 $value = $this->gauge[$progressBar[self::CTRL_OBJ]] + 1;
951 }
952 if (is_numeric($value)) {
953 $this->gauge[$progressBar[self::CTRL_OBJ]] = $value;
954
955 // Check if the control is still valid before setting the value
956 // This prevents errors when the parent window has been destroyed
957 $this->callWinBinder('wb_set_value', array($progressBar[self::CTRL_OBJ], $value), true);
958 }
959 }
960 }
961
970 public function setValue($wbobject, $content)
971 {
972 return $this->callWinBinder('wb_set_value', array($wbobject, $content));
973 }
974
980 public function resetProgressBar($progressBar)
981 {
982 $this->setProgressBarValue($progressBar, 0);
983 }
984
991 public function setProgressBarMax($progressBar, $max)
992 {
993 $this->setRange($progressBar[self::CTRL_OBJ], 0, $max);
994 }
995
1004 public function messageBoxInfo($message, $title = null)
1005 {
1006 return $this->messageBox($message, self::BOX_INFO, $title);
1007 }
1008
1018 public function messageBox($message, $type, $title = null)
1019 {
1020 global $bearsamppCore;
1021
1022 $message = str_replace(self::NEW_LINE, PHP_EOL, $message);
1023 $messageBox = $this->callWinBinder('wb_message_box', array(
1024 0, // Use 0 instead of null for the window handle parameter
1025 strlen($message) < 64 ? str_pad($message, 64) : $message, // Pad message to display entire title
1026 $title == null ? $this->defaultTitle : $this->defaultTitle . ' - ' . $title,
1027 $type
1028 ));
1029
1030 return $messageBox;
1031 }
1032
1041 public function messageBoxOk($message, $title = null)
1042 {
1043 return $this->messageBox($message, self::BOX_OK, $title);
1044 }
1045
1054 public function messageBoxOkCancel($message, $title = null)
1055 {
1056 return $this->messageBox($message, self::BOX_OKCANCEL, $title);
1057 }
1058
1067 public function messageBoxQuestion($message, $title = null)
1068 {
1069 return $this->messageBox($message, self::BOX_QUESTION, $title);
1070 }
1071
1080 public function messageBoxError($message, $title = null)
1081 {
1082 return $this->messageBox($message, self::BOX_ERROR, $title);
1083 }
1084
1093 public function messageBoxWarning($message, $title = null)
1094 {
1095 return $this->messageBox($message, self::BOX_WARNING, $title);
1096 }
1097
1106 public function messageBoxYesNo($message, $title = null)
1107 {
1108 return $this->messageBox($message, self::BOX_YESNO, $title);
1109 }
1110
1119 public function messageBoxYesNoCancel($message, $title = null)
1120 {
1121 return $this->messageBox($message, self::BOX_YESNOCANCEL, $title);
1122 }
1123
1124}
1125
1139function __winbinderEventHandler($window, $id, $ctrl, $param1, $param2)
1140{
1141 global $bearsamppWinbinder;
1142
1143 if ($bearsamppWinbinder->callback[$window][2] != null) {
1144 $bearsamppWinbinder->destroyTimer($window, $bearsamppWinbinder->callback[$window][2][0]);
1145 }
1146
1147 call_user_func_array(
1148 array($bearsamppWinbinder->callback[$window][0], $bearsamppWinbinder->callback[$window][1]),
1149 array($window, $id, $ctrl, $param1, $param2)
1150 );
1151}
$result
global $bearsamppLang
global $bearsamppRoot
global $bearsamppCore
__winbinderEventHandler($window, $id, $ctrl, $param1, $param2)
const SCRIPT_EXEC_SILENT
const LOADING
static logInitClass($classInstance)
static logDebug($data, $file=null)
static getCurrentPid()
setCursor($wbobject, $type=self::CURSOR_ARROW)
messageBoxYesNoCancel($message, $title=null)
getValue($wbobject)
setHandler($wbobject, $classCallback, $methodCallback, $launchTimer=null)
createFont($fontName, $size=null, $color=null, $style=null)
drawLine($wbobject, $xStartPos, $yStartPos, $xEndPos, $yEndPos, $color, $height=1)
findFile($filename)
setDisabled($wbobject)
messageBox($message, $type, $title=null)
const CURSOR_SIZEALL
createProgressBar($parent, $max, $xPos, $yPos, $width=null, $height=null, $style=null, $params=null)
const CURSOR_FINGER
static writeLog($log)
createButton($parent, $caption, $xPos, $yPos, $width=null, $height=null, $style=null, $params=null)
messageBoxError($message, $title=null)
destroyTimer($wbobject, $timerobject)
const CURSOR_SIZEWE
callWinBinder($function, $params=array(), $removeErrorHandler=false)
const CURSOR_FORBIDDEN
incrProgressBar($progressBar)
setImage($wbobject, $path)
setValue($wbobject, $content)
createAppWindow($caption, $width, $height, $style=null, $params=null)
windowIsValid($window)
createInputText($parent, $value, $xPos, $yPos, $width=null, $height=null, $maxLength=null, $style=null, $params=null)
getSystemInfo($info)
getText($wbobject)
const CURSOR_SIZENESW
setArea($wbobject, $width, $height)
wait($wbobject=null)
setStyle($wbobject, $style)
setProgressBarValue($progressBar, $value)
createControl($parent, $ctlClass, $caption, $xPos, $yPos, $width, $height, $style=null, $params=null)
refresh($wbobject)
setRange($wbobject, $min, $max)
const SYSINFO_SCREENAREA
const BOX_YESNOCANCEL
drawText($parent, $caption, $xPos, $yPos, $width=null, $height=null, $font=null)
const CURSOR_SIZENWSE
setFocus($wbobject)
createNakedWindow($caption, $width, $height, $style=null, $params=null)
messageBoxWarning($message, $title=null)
const INCR_PROGRESS_BAR
messageBoxOk($message, $title=null)
createHyperLink($parent, $caption, $xPos, $yPos, $width=null, $height=null, $style=null, $params=null)
drawRect($parent, $xPos, $yPos, $width, $height, $color=15790320, $filled=true)
isEnabled($wbobject)
resetProgressBar($progressBar)
messageBoxInfo($message, $title=null)
setText($wbobject, $content)
createRadioButton($parent, $caption, $checked, $xPos, $yPos, $width=null, $height=null, $startGroup=false)
exec($cmd, $params=null, $silent=false)
createEditBox($parent, $value, $xPos, $yPos, $width=null, $height=null, $style=null, $params=null)
const SYSINFO_WORKAREA
destroyWindow($window)
const CURSOR_UPARROW
sysDlgPath($parent, $title, $path=null)
const CURSOR_SIZENS
messageBoxOkCancel($message, $title=null)
messageBoxQuestion($message, $title=null)
sysDlgOpen($parent, $title, $filter=null, $path=null)
setProgressBarMax($progressBar, $max)
const CURSOR_WAITARROW
createLabel($parent, $caption, $xPos, $yPos, $width=null, $height=null, $style=null, $params=null)
setMaxLength($wbobject, $length)
createTimer($wbobject, $wait=1000)
createWindow($parent, $wclass, $caption, $xPos, $yPos, $width, $height, $style=null, $params=null)
drawImage($wbobject, $path, $xPos=0, $yPos=0, $width=0, $height=0)
setEnabled($wbobject, $enabled=true)
messageBoxYesNo($message, $title=null)
const APP_TITLE
Definition root.php:13