Commit | Line | Data |
---|---|---|
d335fa16 | 1 | <?php |
308c880f | 2 | declare(strict_types=1); |
d335fa16 AE |
3 | namespace wcf\system; |
4 | use wcf\data\application\Application; | |
5 | use wcf\data\option\OptionEditor; | |
6 | use wcf\data\package\Package; | |
7 | use wcf\data\package\PackageCache; | |
8 | use wcf\data\package\PackageEditor; | |
12a2ff01 AE |
9 | use wcf\data\page\Page; |
10 | use wcf\data\page\PageCache; | |
5346b183 | 11 | use wcf\page\CmsPage; |
d335fa16 | 12 | use wcf\system\application\ApplicationHandler; |
f341086b | 13 | use wcf\system\application\IApplication; |
55b402a0 | 14 | use wcf\system\box\BoxHandler; |
d335fa16 AE |
15 | use wcf\system\cache\builder\CoreObjectCacheBuilder; |
16 | use wcf\system\cache\builder\PackageUpdateCacheBuilder; | |
17 | use wcf\system\cronjob\CronjobScheduler; | |
157054c9 | 18 | use wcf\system\database\MySQLDatabase; |
d335fa16 AE |
19 | use wcf\system\event\EventHandler; |
20 | use wcf\system\exception\AJAXException; | |
b25ad8f3 | 21 | use wcf\system\exception\ErrorException; |
d335fa16 AE |
22 | use wcf\system\exception\IPrintableException; |
23 | use wcf\system\exception\NamedUserException; | |
7b9ff46b | 24 | use wcf\system\exception\ParentClassException; |
d335fa16 AE |
25 | use wcf\system\exception\PermissionDeniedException; |
26 | use wcf\system\exception\SystemException; | |
27 | use wcf\system\language\LanguageFactory; | |
28 | use wcf\system\package\PackageInstallationDispatcher; | |
11117cd5 | 29 | use wcf\system\registry\RegistryHandler; |
12a2ff01 | 30 | use wcf\system\request\Request; |
a80873d5 | 31 | use wcf\system\request\RequestHandler; |
d335fa16 AE |
32 | use wcf\system\session\SessionFactory; |
33 | use wcf\system\session\SessionHandler; | |
34 | use wcf\system\style\StyleHandler; | |
15621401 | 35 | use wcf\system\template\EmailTemplateEngine; |
d335fa16 AE |
36 | use wcf\system\template\TemplateEngine; |
37 | use wcf\system\user\storage\UserStorageHandler; | |
d335fa16 | 38 | use wcf\util\FileUtil; |
8418bed7 | 39 | use wcf\util\HeaderUtil; |
d335fa16 | 40 | use wcf\util\StringUtil; |
dc0015ab | 41 | use wcf\util\UserUtil; |
d335fa16 AE |
42 | |
43 | // try to set a time-limit to infinite | |
44 | @set_time_limit(0); | |
45 | ||
46 | // fix timezone warning issue | |
47 | if (!@ini_get('date.timezone')) { | |
48 | @date_default_timezone_set('Europe/London'); | |
49 | } | |
50 | ||
359841c3 | 51 | // define current woltlab suite version |
39993659 | 52 | define('WCF_VERSION', '3.1.4'); |
d335fa16 | 53 | |
89484ba0 AE |
54 | // define current API version |
55 | define('WSC_API_VERSION', 2018); | |
56 | ||
d335fa16 AE |
57 | // define current unix timestamp |
58 | define('TIME_NOW', time()); | |
59 | ||
60 | // wcf imports | |
61 | if (!defined('NO_IMPORTS')) { | |
62 | require_once(WCF_DIR.'lib/core.functions.php'); | |
d11a8c9e | 63 | require_once(WCF_DIR.'lib/system/api/autoload.php'); |
d335fa16 AE |
64 | } |
65 | ||
66 | /** | |
e71525e4 | 67 | * WCF is the central class for the WoltLab Suite Core. |
d335fa16 AE |
68 | * It holds the database connection, access to template and language engine. |
69 | * | |
70 | * @author Marcel Werk | |
21987a12 | 71 | * @copyright 2001-2018 WoltLab GmbH |
d335fa16 | 72 | * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> |
e71525e4 | 73 | * @package WoltLabSuite\Core\System |
d335fa16 AE |
74 | */ |
75 | class WCF { | |
89484ba0 AE |
76 | /** |
77 | * list of supported legacy API versions | |
78 | * @var integer[] | |
79 | */ | |
80 | private static $supportedLegacyApiVersions = [2017]; | |
81 | ||
d335fa16 AE |
82 | /** |
83 | * list of currently loaded applications | |
893aace3 | 84 | * @var Application[] |
d335fa16 | 85 | */ |
058cbd6a | 86 | protected static $applications = []; |
d335fa16 AE |
87 | |
88 | /** | |
89 | * list of currently loaded application objects | |
7a23a706 | 90 | * @var IApplication[] |
d335fa16 | 91 | */ |
058cbd6a | 92 | protected static $applicationObjects = []; |
d335fa16 AE |
93 | |
94 | /** | |
95 | * list of autoload directories | |
96 | * @var array | |
97 | */ | |
058cbd6a | 98 | protected static $autoloadDirectories = []; |
d335fa16 AE |
99 | |
100 | /** | |
101 | * list of unique instances of each core object | |
7a23a706 | 102 | * @var SingletonFactory[] |
d335fa16 | 103 | */ |
058cbd6a | 104 | protected static $coreObject = []; |
d335fa16 AE |
105 | |
106 | /** | |
107 | * list of cached core objects | |
7a23a706 | 108 | * @var string[] |
d335fa16 | 109 | */ |
058cbd6a | 110 | protected static $coreObjectCache = []; |
d335fa16 AE |
111 | |
112 | /** | |
113 | * database object | |
4e25add7 | 114 | * @var MySQLDatabase |
d335fa16 | 115 | */ |
85099417 | 116 | protected static $dbObj; |
d335fa16 AE |
117 | |
118 | /** | |
119 | * language object | |
a59497a6 | 120 | * @var \wcf\data\language\Language |
d335fa16 | 121 | */ |
85099417 | 122 | protected static $languageObj; |
d335fa16 AE |
123 | |
124 | /** | |
125 | * overrides disabled debug mode | |
126 | * @var boolean | |
127 | */ | |
128 | protected static $overrideDebugMode = false; | |
129 | ||
130 | /** | |
131 | * session object | |
4e25add7 | 132 | * @var SessionHandler |
d335fa16 | 133 | */ |
85099417 | 134 | protected static $sessionObj; |
d335fa16 AE |
135 | |
136 | /** | |
137 | * template object | |
4e25add7 | 138 | * @var TemplateEngine |
d335fa16 | 139 | */ |
85099417 | 140 | protected static $tplObj; |
d335fa16 AE |
141 | |
142 | /** | |
143 | * true if Zend Opcache is loaded and enabled | |
144 | * @var boolean | |
145 | */ | |
85099417 | 146 | protected static $zendOpcacheEnabled; |
d335fa16 | 147 | |
ab84d9ca AE |
148 | /** |
149 | * force logout during destructor call | |
150 | * @var boolean | |
151 | */ | |
152 | protected static $forceLogout = false; | |
153 | ||
d335fa16 AE |
154 | /** |
155 | * Calls all init functions of the WCF class. | |
156 | */ | |
157 | public function __construct() { | |
158 | // add autoload directory | |
159 | self::$autoloadDirectories['wcf'] = WCF_DIR . 'lib/'; | |
160 | ||
161 | // define tmp directory | |
162 | if (!defined('TMP_DIR')) define('TMP_DIR', FileUtil::getTempFolder()); | |
163 | ||
164 | // start initialization | |
d335fa16 AE |
165 | $this->initDB(); |
166 | $this->loadOptions(); | |
167 | $this->initSession(); | |
168 | $this->initLanguage(); | |
169 | $this->initTPL(); | |
170 | $this->initCronjobs(); | |
171 | $this->initCoreObjects(); | |
172 | $this->initApplications(); | |
173 | $this->initBlacklist(); | |
174 | ||
175 | EventHandler::getInstance()->fireAction($this, 'initialized'); | |
176 | } | |
177 | ||
178 | /** | |
3ba197fa TD |
179 | * Flushes the output, closes the session, performs background tasks and more. |
180 | * | |
181 | * You *must* not create output in here under normal circumstances, as it might get eaten | |
182 | * when gzip is enabled. | |
d335fa16 AE |
183 | */ |
184 | public static function destruct() { | |
185 | try { | |
186 | // database has to be initialized | |
187 | if (!is_object(self::$dbObj)) return; | |
188 | ||
3ba197fa TD |
189 | $debug = self::debugModeIsEnabled(true); |
190 | if (!$debug) { | |
191 | // flush output | |
192 | if (ob_get_level()) ob_end_flush(); | |
d335fa16 | 193 | flush(); |
3ba197fa TD |
194 | |
195 | // close connection if using FPM | |
196 | if (function_exists('fastcgi_finish_request')) fastcgi_finish_request(); | |
d335fa16 AE |
197 | } |
198 | ||
199 | // update session | |
200 | if (is_object(self::getSession())) { | |
ab84d9ca AE |
201 | if (self::$forceLogout) { |
202 | // do logout | |
1eb97b3a | 203 | self::getSession()->delete(); |
ab84d9ca AE |
204 | } |
205 | else { | |
206 | self::getSession()->update(); | |
207 | } | |
d335fa16 AE |
208 | } |
209 | ||
11117cd5 AE |
210 | // execute shutdown actions of storage handlers |
211 | RegistryHandler::getInstance()->shutdown(); | |
d335fa16 AE |
212 | UserStorageHandler::getInstance()->shutdown(); |
213 | } | |
214 | catch (\Exception $exception) { | |
215 | die("<pre>WCF::destruct() Unhandled exception: ".$exception->getMessage()."\n\n".$exception->getTraceAsString()); | |
216 | } | |
217 | } | |
218 | ||
d335fa16 AE |
219 | /** |
220 | * Returns the database object. | |
221 | * | |
222 | * @return \wcf\system\database\Database | |
223 | */ | |
224 | public static final function getDB() { | |
225 | return self::$dbObj; | |
226 | } | |
227 | ||
228 | /** | |
229 | * Returns the session object. | |
230 | * | |
4e25add7 | 231 | * @return SessionHandler |
d335fa16 AE |
232 | */ |
233 | public static final function getSession() { | |
234 | return self::$sessionObj; | |
235 | } | |
236 | ||
237 | /** | |
238 | * Returns the user object. | |
239 | * | |
240 | * @return \wcf\data\user\User | |
241 | */ | |
242 | public static final function getUser() { | |
243 | return self::getSession()->getUser(); | |
244 | } | |
245 | ||
246 | /** | |
247 | * Returns the language object. | |
248 | * | |
249 | * @return \wcf\data\language\Language | |
250 | */ | |
251 | public static final function getLanguage() { | |
252 | return self::$languageObj; | |
253 | } | |
254 | ||
255 | /** | |
256 | * Returns the template object. | |
257 | * | |
4e25add7 | 258 | * @return TemplateEngine |
d335fa16 AE |
259 | */ |
260 | public static final function getTPL() { | |
261 | return self::$tplObj; | |
262 | } | |
263 | ||
264 | /** | |
265 | * Calls the show method on the given exception. | |
266 | * | |
267 | * @param \Exception $e | |
268 | */ | |
e09c7044 | 269 | public static final function handleException($e) { |
cbf81afa AE |
270 | // backwards compatibility |
271 | if ($e instanceof IPrintableException) { | |
272 | $e->show(); | |
273 | exit; | |
274 | } | |
275 | ||
f8434d25 | 276 | if (ob_get_level()) { |
1615fc2e | 277 | // discard any output generated before the exception occurred, prevents exception |
f8434d25 | 278 | // being hidden inside HTML elements and therefore not visible in browser output |
f46ac60e AE |
279 | // |
280 | // ob_get_level() can return values > 1, if the PHP setting `output_buffering` is on | |
281 | while (ob_get_level()) ob_end_clean(); | |
5f2c3ab4 | 282 | |
fef5bd1f AE |
283 | // Some webservers are broken and will apply gzip encoding at all cost, but they fail |
284 | // to set a proper `Content-Encoding` HTTP header and mess things up even more. | |
285 | // Especially the `identity` value appears to be unrecognized by some of them, hence | |
286 | // we'll just gzip the output of the exception to prevent them from tampering. | |
287 | // This part is copied from `HeaderUtil` in order to isolate the exception handler! | |
4d7a0b73 | 288 | if (defined('HTTP_ENABLE_GZIP') && HTTP_ENABLE_GZIP && !defined('HTTP_DISABLE_GZIP')) { |
ac8615e4 LK |
289 | if (function_exists('gzcompress') && !@ini_get('zlib.output_compression') && !@ini_get('output_handler') && isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== false) { |
290 | if (strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'x-gzip') !== false) { | |
fef5bd1f AE |
291 | @header('Content-Encoding: x-gzip'); |
292 | } | |
293 | else { | |
294 | @header('Content-Encoding: gzip'); | |
295 | } | |
296 | ||
297 | ob_start(function($output) { | |
298 | $size = strlen($output); | |
299 | $crc = crc32($output); | |
300 | ||
301 | $newOutput = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff"; | |
302 | $newOutput .= substr(gzcompress($output, 1), 2, -4); | |
303 | $newOutput .= pack('V', $crc); | |
304 | $newOutput .= pack('V', $size); | |
305 | ||
306 | return $newOutput; | |
307 | }); | |
308 | } | |
309 | } | |
f8434d25 AE |
310 | } |
311 | ||
5bc5a7e6 | 312 | @header('HTTP/1.1 503 Service Unavailable'); |
d335fa16 | 313 | try { |
5bc5a7e6 | 314 | \wcf\functions\exception\printThrowable($e); |
d335fa16 | 315 | } |
5bc5a7e6 TD |
316 | catch (\Throwable $e2) { |
317 | echo "<pre>An Exception was thrown while handling an Exception:\n\n"; | |
6b7d6653 | 318 | echo preg_replace('/Database->__construct\(.*\)/', 'Database->__construct(...)', $e2); |
5bc5a7e6 | 319 | echo "\n\nwas thrown while:\n\n"; |
6b7d6653 | 320 | echo preg_replace('/Database->__construct\(.*\)/', 'Database->__construct(...)', $e); |
5bc5a7e6 TD |
321 | echo "\n\nwas handled.</pre>"; |
322 | exit; | |
e09c7044 | 323 | } |
d335fa16 AE |
324 | } |
325 | ||
326 | /** | |
b25ad8f3 | 327 | * Turns PHP errors into an ErrorException. |
d335fa16 | 328 | * |
ac52543a | 329 | * @param integer $severity |
d335fa16 | 330 | * @param string $message |
ac52543a MS |
331 | * @param string $file |
332 | * @param integer $line | |
2b770bdd | 333 | * @throws ErrorException |
d335fa16 | 334 | */ |
b25ad8f3 | 335 | public static final function handleError($severity, $message, $file, $line) { |
b2aa772d | 336 | // this is necessary for the shut-up operator |
b25ad8f3 TD |
337 | if (error_reporting() == 0) return; |
338 | ||
339 | throw new ErrorException($message, 0, $severity, $file, $line); | |
d335fa16 AE |
340 | } |
341 | ||
342 | /** | |
343 | * Loads the database configuration and creates a new connection to the database. | |
344 | */ | |
345 | protected function initDB() { | |
346 | // get configuration | |
347 | $dbHost = $dbUser = $dbPassword = $dbName = ''; | |
348 | $dbPort = 0; | |
d335fa16 AE |
349 | require(WCF_DIR.'config.inc.php'); |
350 | ||
351 | // create database connection | |
a2d822ad | 352 | self::$dbObj = new MySQLDatabase($dbHost, $dbUser, $dbPassword, $dbName, $dbPort); |
d335fa16 AE |
353 | } |
354 | ||
355 | /** | |
356 | * Loads the options file, automatically created if not exists. | |
357 | */ | |
358 | protected function loadOptions() { | |
85099417 AE |
359 | // the attachment module is always enabled since 3.2 |
360 | // https://github.com/WoltLab/WCF/issues/2531 | |
361 | define('MODULE_ATTACHMENT', 1); | |
362 | ||
d335fa16 AE |
363 | $filename = WCF_DIR.'options.inc.php'; |
364 | ||
365 | // create options file if doesn't exist | |
366 | if (!file_exists($filename) || filemtime($filename) <= 1) { | |
367 | OptionEditor::rebuild(); | |
368 | } | |
11beb0a0 | 369 | require($filename); |
b842faa1 AE |
370 | |
371 | // check if option file is complete and writable | |
372 | if (PACKAGE_ID) { | |
373 | if (!is_writable($filename)) { | |
374 | FileUtil::makeWritable($filename); | |
375 | ||
376 | if (!is_writable($filename)) { | |
377 | throw new SystemException("The option file '" . $filename . "' is not writable."); | |
378 | } | |
379 | } | |
380 | ||
381 | // check if a previous write operation was incomplete and force rebuilding | |
382 | if (!defined('WCF_OPTION_INC_PHP_SUCCESS')) { | |
383 | OptionEditor::rebuild(); | |
384 | ||
11beb0a0 | 385 | require($filename); |
b842faa1 AE |
386 | } |
387 | } | |
d335fa16 AE |
388 | } |
389 | ||
390 | /** | |
391 | * Starts the session system. | |
392 | */ | |
393 | protected function initSession() { | |
f341086b | 394 | $factory = new SessionFactory(); |
d335fa16 AE |
395 | $factory->load(); |
396 | ||
f341086b | 397 | self::$sessionObj = SessionHandler::getInstance(); |
91082aee | 398 | self::$sessionObj->setHasValidCookie($factory->hasValidCookie()); |
d335fa16 AE |
399 | } |
400 | ||
401 | /** | |
402 | * Initialises the language engine. | |
403 | */ | |
404 | protected function initLanguage() { | |
405 | if (isset($_GET['l']) && !self::getUser()->userID) { | |
406 | self::getSession()->setLanguageID(intval($_GET['l'])); | |
407 | } | |
408 | ||
409 | // set mb settings | |
410 | mb_internal_encoding('UTF-8'); | |
411 | if (function_exists('mb_regex_encoding')) mb_regex_encoding('UTF-8'); | |
412 | mb_language('uni'); | |
413 | ||
414 | // get language | |
f341086b | 415 | self::$languageObj = LanguageFactory::getInstance()->getUserLanguage(self::getSession()->getLanguageID()); |
d335fa16 AE |
416 | } |
417 | ||
418 | /** | |
419 | * Initialises the template engine. | |
420 | */ | |
421 | protected function initTPL() { | |
f341086b | 422 | self::$tplObj = TemplateEngine::getInstance(); |
d335fa16 AE |
423 | self::getTPL()->setLanguageID(self::getLanguage()->languageID); |
424 | $this->assignDefaultTemplateVariables(); | |
425 | ||
426 | $this->initStyle(); | |
427 | } | |
428 | ||
429 | /** | |
430 | * Initializes the user's style. | |
431 | */ | |
432 | protected function initStyle() { | |
433 | if (isset($_REQUEST['styleID'])) { | |
434 | self::getSession()->setStyleID(intval($_REQUEST['styleID'])); | |
435 | } | |
436 | ||
f341086b | 437 | $styleHandler = StyleHandler::getInstance(); |
d11a8c9e | 438 | $styleHandler->changeStyle(self::getSession()->getStyleID()); |
d335fa16 AE |
439 | } |
440 | ||
441 | /** | |
442 | * Executes the blacklist. | |
443 | */ | |
444 | protected function initBlacklist() { | |
fa75ccfb AE |
445 | $isAjax = isset($_SERVER['HTTP_X_REQUESTED_WITH']) && ($_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest'); |
446 | ||
d335fa16 | 447 | if (defined('BLACKLIST_IP_ADDRESSES') && BLACKLIST_IP_ADDRESSES != '') { |
dc0015ab | 448 | if (!StringUtil::executeWordFilter(UserUtil::convertIPv6To4(self::getSession()->ipAddress), BLACKLIST_IP_ADDRESSES)) { |
fa75ccfb | 449 | if ($isAjax) { |
a1721f86 | 450 | throw new AJAXException(self::getLanguage()->getDynamicVariable('wcf.ajax.error.permissionDenied'), AJAXException::INSUFFICIENT_PERMISSIONS); |
fa75ccfb AE |
451 | } |
452 | else { | |
453 | throw new PermissionDeniedException(); | |
454 | } | |
dc0015ab AE |
455 | } |
456 | else if (!StringUtil::executeWordFilter(self::getSession()->ipAddress, BLACKLIST_IP_ADDRESSES)) { | |
fa75ccfb | 457 | if ($isAjax) { |
a1721f86 | 458 | throw new AJAXException(self::getLanguage()->getDynamicVariable('wcf.ajax.error.permissionDenied'), AJAXException::INSUFFICIENT_PERMISSIONS); |
fa75ccfb AE |
459 | } |
460 | else { | |
461 | throw new PermissionDeniedException(); | |
462 | } | |
d335fa16 AE |
463 | } |
464 | } | |
465 | if (defined('BLACKLIST_USER_AGENTS') && BLACKLIST_USER_AGENTS != '') { | |
466 | if (!StringUtil::executeWordFilter(self::getSession()->userAgent, BLACKLIST_USER_AGENTS)) { | |
fa75ccfb | 467 | if ($isAjax) { |
a1721f86 | 468 | throw new AJAXException(self::getLanguage()->getDynamicVariable('wcf.ajax.error.permissionDenied'), AJAXException::INSUFFICIENT_PERMISSIONS); |
fa75ccfb AE |
469 | } |
470 | else { | |
471 | throw new PermissionDeniedException(); | |
472 | } | |
d335fa16 AE |
473 | } |
474 | } | |
475 | if (defined('BLACKLIST_HOSTNAMES') && BLACKLIST_HOSTNAMES != '') { | |
476 | if (!StringUtil::executeWordFilter(@gethostbyaddr(self::getSession()->ipAddress), BLACKLIST_HOSTNAMES)) { | |
fa75ccfb | 477 | if ($isAjax) { |
a1721f86 | 478 | throw new AJAXException(self::getLanguage()->getDynamicVariable('wcf.ajax.error.permissionDenied'), AJAXException::INSUFFICIENT_PERMISSIONS); |
fa75ccfb AE |
479 | } |
480 | else { | |
481 | throw new PermissionDeniedException(); | |
482 | } | |
d335fa16 AE |
483 | } |
484 | } | |
485 | ||
486 | // handle banned users | |
487 | if (self::getUser()->userID && self::getUser()->banned) { | |
fa75ccfb | 488 | if ($isAjax) { |
d335fa16 AE |
489 | throw new AJAXException(self::getLanguage()->getDynamicVariable('wcf.user.error.isBanned'), AJAXException::INSUFFICIENT_PERMISSIONS); |
490 | } | |
491 | else { | |
ab84d9ca AE |
492 | self::$forceLogout = true; |
493 | ||
494 | // remove cookies | |
495 | if (isset($_COOKIE[COOKIE_PREFIX.'userID'])) { | |
496 | HeaderUtil::setCookie('userID', 0); | |
497 | } | |
498 | if (isset($_COOKIE[COOKIE_PREFIX.'password'])) { | |
499 | HeaderUtil::setCookie('password', ''); | |
500 | } | |
501 | ||
d335fa16 AE |
502 | throw new NamedUserException(self::getLanguage()->getDynamicVariable('wcf.user.error.isBanned')); |
503 | } | |
504 | } | |
505 | } | |
506 | ||
507 | /** | |
508 | * Initializes applications. | |
509 | */ | |
510 | protected function initApplications() { | |
511 | // step 1) load all applications | |
058cbd6a | 512 | $loadedApplications = []; |
d335fa16 AE |
513 | |
514 | // register WCF as application | |
39abe192 | 515 | self::$applications['wcf'] = ApplicationHandler::getInstance()->getApplicationByID(1); |
d335fa16 | 516 | |
39abe192 | 517 | if (!class_exists(WCFACP::class, false)) { |
b2b2c26b | 518 | static::getTPL()->assign('baseHref', self::$applications['wcf']->getPageURL()); |
39abe192 AE |
519 | } |
520 | ||
d335fa16 AE |
521 | // start main application |
522 | $application = ApplicationHandler::getInstance()->getActiveApplication(); | |
39abe192 AE |
523 | if ($application->packageID != 1) { |
524 | $loadedApplications[] = $this->loadApplication($application); | |
525 | ||
526 | // register primary application | |
527 | $abbreviation = ApplicationHandler::getInstance()->getAbbreviation($application->packageID); | |
528 | self::$applications[$abbreviation] = $application; | |
529 | } | |
d335fa16 AE |
530 | |
531 | // start dependent applications | |
532 | $applications = ApplicationHandler::getInstance()->getDependentApplications(); | |
533 | foreach ($applications as $application) { | |
39abe192 AE |
534 | if ($application->packageID == 1) { |
535 | // ignore WCF | |
536 | continue; | |
537 | } | |
f4eafe37 AE |
538 | else if ($application->isTainted) { |
539 | // ignore apps flagged for uninstallation | |
540 | continue; | |
541 | } | |
39abe192 | 542 | |
d335fa16 AE |
543 | $loadedApplications[] = $this->loadApplication($application, true); |
544 | } | |
545 | ||
546 | // step 2) run each application | |
547 | if (!class_exists('wcf\system\WCFACP', false)) { | |
f341086b | 548 | /** @var IApplication $application */ |
d335fa16 AE |
549 | foreach ($loadedApplications as $application) { |
550 | $application->__run(); | |
551 | } | |
552 | ||
553 | // refresh the session 1 minute before it expires | |
63b9817b | 554 | self::getTPL()->assign('__sessionKeepAlive', SESSION_TIMEOUT - 60); |
d335fa16 AE |
555 | } |
556 | } | |
557 | ||
558 | /** | |
559 | * Loads an application. | |
560 | * | |
2b770bdd MS |
561 | * @param Application $application |
562 | * @param boolean $isDependentApplication | |
563 | * @return IApplication | |
564 | * @throws SystemException | |
d335fa16 AE |
565 | */ |
566 | protected function loadApplication(Application $application, $isDependentApplication = false) { | |
d335fa16 AE |
567 | $package = PackageCache::getInstance()->getPackage($application->packageID); |
568 | // package cache might be outdated | |
569 | if ($package === null) { | |
570 | $package = new Package($application->packageID); | |
571 | ||
572 | // package cache is outdated, discard cache | |
573 | if ($package->packageID) { | |
574 | PackageEditor::resetCache(); | |
575 | } | |
576 | else { | |
577 | // package id is invalid | |
578 | throw new SystemException("application identified by package id '".$application->packageID."' is unknown"); | |
579 | } | |
580 | } | |
e54191ea | 581 | |
d335fa16 AE |
582 | $abbreviation = ApplicationHandler::getInstance()->getAbbreviation($application->packageID); |
583 | $packageDir = FileUtil::getRealPath(WCF_DIR.$package->packageDir); | |
584 | self::$autoloadDirectories[$abbreviation] = $packageDir . 'lib/'; | |
585 | ||
586 | $className = $abbreviation.'\system\\'.strtoupper($abbreviation).'Core'; | |
59162cf3 AE |
587 | |
588 | // class was not found, possibly the app was moved, but `packageDir` has not been adjusted | |
589 | if (!class_exists($className)) { | |
590 | // check if both the Core and the app are on the same domain | |
591 | $coreApp = ApplicationHandler::getInstance()->getApplicationByID(1); | |
592 | if ($coreApp->domainName === $application->domainName) { | |
593 | // resolve the relative path and use it to construct the autoload directory | |
594 | $relativePath = FileUtil::getRelativePath($coreApp->domainPath, $application->domainPath); | |
595 | if ($relativePath !== './') { | |
596 | $packageDir = FileUtil::getRealPath(WCF_DIR.$relativePath); | |
597 | self::$autoloadDirectories[$abbreviation] = $packageDir . 'lib/'; | |
598 | ||
599 | if (class_exists($className)) { | |
600 | // the class can now be found, update the `packageDir` value | |
601 | (new PackageEditor($package))->update(['packageDir' => $relativePath]); | |
602 | } | |
603 | } | |
604 | } | |
605 | } | |
606 | ||
157054c9 | 607 | if (class_exists($className) && is_subclass_of($className, IApplication::class)) { |
d335fa16 AE |
608 | // include config file |
609 | $configPath = $packageDir . PackageInstallationDispatcher::CONFIG_FILE; | |
b842faa1 AE |
610 | if (!file_exists($configPath)) { |
611 | Package::writeConfigFile($package->packageID); | |
612 | } | |
613 | ||
d335fa16 AE |
614 | if (file_exists($configPath)) { |
615 | require_once($configPath); | |
616 | } | |
617 | else { | |
618 | throw new SystemException('Unable to load configuration for '.$package->package); | |
619 | } | |
620 | ||
621 | // register template path if not within ACP | |
622 | if (!class_exists('wcf\system\WCFACP', false)) { | |
623 | // add template path and abbreviation | |
b2b2c26b | 624 | static::getTPL()->addApplication($abbreviation, $packageDir . 'templates/'); |
d335fa16 | 625 | } |
15621401 | 626 | EmailTemplateEngine::getInstance()->addApplication($abbreviation, $packageDir . 'templates/'); |
d335fa16 AE |
627 | |
628 | // init application and assign it as template variable | |
058cbd6a | 629 | self::$applicationObjects[$application->packageID] = call_user_func([$className, 'getInstance']); |
b2b2c26b | 630 | static::getTPL()->assign('__'.$abbreviation, self::$applicationObjects[$application->packageID]); |
15621401 | 631 | EmailTemplateEngine::getInstance()->assign('__'.$abbreviation, self::$applicationObjects[$application->packageID]); |
d335fa16 AE |
632 | } |
633 | else { | |
634 | unset(self::$autoloadDirectories[$abbreviation]); | |
157054c9 | 635 | throw new SystemException("Unable to run '".$package->package."', '".$className."' is missing or does not implement '".IApplication::class."'."); |
d335fa16 AE |
636 | } |
637 | ||
638 | // register template path in ACP | |
639 | if (class_exists('wcf\system\WCFACP', false)) { | |
b2b2c26b | 640 | static::getTPL()->addApplication($abbreviation, $packageDir . 'acp/templates/'); |
d335fa16 AE |
641 | } |
642 | else if (!$isDependentApplication) { | |
643 | // assign base tag | |
b2b2c26b | 644 | static::getTPL()->assign('baseHref', $application->getPageURL()); |
d335fa16 AE |
645 | } |
646 | ||
647 | // register application | |
648 | self::$applications[$abbreviation] = $application; | |
649 | ||
650 | return self::$applicationObjects[$application->packageID]; | |
651 | } | |
652 | ||
653 | /** | |
654 | * Returns the corresponding application object. Does not support the 'wcf' pseudo application. | |
655 | * | |
4e25add7 MS |
656 | * @param Application $application |
657 | * @return IApplication | |
d335fa16 AE |
658 | */ |
659 | public static function getApplicationObject(Application $application) { | |
660 | if (isset(self::$applicationObjects[$application->packageID])) { | |
661 | return self::$applicationObjects[$application->packageID]; | |
662 | } | |
663 | ||
664 | return null; | |
665 | } | |
666 | ||
dbf41d8a AE |
667 | /** |
668 | * Returns the invoked application. | |
669 | * | |
670 | * @return Application | |
8e152e07 | 671 | * @since 3.1 |
dbf41d8a AE |
672 | */ |
673 | public static function getActiveApplication() { | |
674 | return ApplicationHandler::getInstance()->getActiveApplication(); | |
675 | } | |
676 | ||
d335fa16 AE |
677 | /** |
678 | * Loads an application on runtime, do not use this outside the package installation. | |
679 | * | |
680 | * @param integer $packageID | |
681 | */ | |
682 | public static function loadRuntimeApplication($packageID) { | |
683 | $package = new Package($packageID); | |
684 | $application = new Application($packageID); | |
685 | ||
686 | $abbreviation = Package::getAbbreviation($package->package); | |
687 | $packageDir = FileUtil::getRealPath(WCF_DIR.$package->packageDir); | |
688 | self::$autoloadDirectories[$abbreviation] = $packageDir . 'lib/'; | |
689 | self::$applications[$abbreviation] = $application; | |
690 | self::getTPL()->addApplication($abbreviation, $packageDir . 'acp/templates/'); | |
691 | } | |
692 | ||
693 | /** | |
694 | * Initializes core object cache. | |
695 | */ | |
696 | protected function initCoreObjects() { | |
697 | // ignore core objects if installing WCF | |
698 | if (PACKAGE_ID == 0) { | |
699 | return; | |
700 | } | |
701 | ||
702 | self::$coreObjectCache = CoreObjectCacheBuilder::getInstance()->getData(); | |
703 | } | |
704 | ||
705 | /** | |
706 | * Assigns some default variables to the template engine. | |
707 | */ | |
708 | protected function assignDefaultTemplateVariables() { | |
058cbd6a MS |
709 | self::getTPL()->registerPrefilter(['event', 'hascontent', 'lang']); |
710 | self::getTPL()->assign([ | |
d335fa16 | 711 | '__wcf' => $this, |
e71525e4 | 712 | '__wcfVersion' => LAST_UPDATE_TIME // @deprecated 2.1, use LAST_UPDATE_TIME directly |
058cbd6a | 713 | ]); |
54de7a5f TD |
714 | |
715 | $isAjax = isset($_SERVER['HTTP_X_REQUESTED_WITH']) && ($_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest'); | |
716 | // Execute background queue in this request, if it was requested and AJAX isn't used. | |
717 | if (!$isAjax) { | |
718 | if (self::getSession()->getVar('forceBackgroundQueuePerform')) { | |
719 | self::getTPL()->assign([ | |
720 | 'forceBackgroundQueuePerform' => true | |
721 | ]); | |
722 | ||
723 | self::getSession()->unregister('forceBackgroundQueuePerform'); | |
724 | } | |
725 | } | |
726 | ||
15621401 TD |
727 | EmailTemplateEngine::getInstance()->registerPrefilter(['event', 'hascontent', 'lang']); |
728 | EmailTemplateEngine::getInstance()->assign([ | |
729 | '__wcf' => $this | |
730 | ]); | |
d335fa16 AE |
731 | } |
732 | ||
733 | /** | |
734 | * Wrapper for the getter methods of this class. | |
735 | * | |
736 | * @param string $name | |
737 | * @return mixed value | |
2b770bdd | 738 | * @throws SystemException |
d335fa16 AE |
739 | */ |
740 | public function __get($name) { | |
741 | $method = 'get'.ucfirst($name); | |
742 | if (method_exists($this, $method)) { | |
743 | return $this->$method(); | |
744 | } | |
745 | ||
746 | throw new SystemException("method '".$method."' does not exist in class WCF"); | |
747 | } | |
748 | ||
487db634 MW |
749 | /** |
750 | * Returns true if current application (WCF) is treated as active and was invoked directly. | |
751 | * | |
752 | * @return boolean | |
753 | */ | |
754 | public function isActiveApplication() { | |
755 | return (ApplicationHandler::getInstance()->getActiveApplication()->packageID == 1); | |
756 | } | |
757 | ||
d335fa16 AE |
758 | /** |
759 | * Changes the active language. | |
760 | * | |
761 | * @param integer $languageID | |
762 | */ | |
763 | public static final function setLanguage($languageID) { | |
5b8eae2c MW |
764 | if (!$languageID || LanguageFactory::getInstance()->getLanguage($languageID) === null) { |
765 | $languageID = LanguageFactory::getInstance()->getDefaultLanguageID(); | |
766 | } | |
2d90d60f | 767 | |
d335fa16 | 768 | self::$languageObj = LanguageFactory::getInstance()->getLanguage($languageID); |
b372bd53 AE |
769 | |
770 | // the template engine may not be available yet, usually happens when | |
771 | // changing the user (and thus the language id) during session init | |
772 | if (self::$tplObj !== null) { | |
773 | self::getTPL()->setLanguageID(self::getLanguage()->languageID); | |
774 | EmailTemplateEngine::getInstance()->setLanguageID(self::getLanguage()->languageID); | |
775 | } | |
d335fa16 AE |
776 | } |
777 | ||
778 | /** | |
779 | * Includes the required util or exception classes automatically. | |
780 | * | |
781 | * @param string $className | |
782 | * @see spl_autoload_register() | |
783 | */ | |
784 | public static final function autoload($className) { | |
785 | $namespaces = explode('\\', $className); | |
786 | if (count($namespaces) > 1) { | |
787 | $applicationPrefix = array_shift($namespaces); | |
788 | if ($applicationPrefix === '') { | |
789 | $applicationPrefix = array_shift($namespaces); | |
790 | } | |
791 | if (isset(self::$autoloadDirectories[$applicationPrefix])) { | |
792 | $classPath = self::$autoloadDirectories[$applicationPrefix] . implode('/', $namespaces) . '.class.php'; | |
793 | if (file_exists($classPath)) { | |
794 | require_once($classPath); | |
795 | } | |
796 | } | |
797 | } | |
798 | } | |
799 | ||
800 | /** | |
0fcfe5f6 | 801 | * @inheritDoc |
d335fa16 AE |
802 | */ |
803 | public final function __call($name, array $arguments) { | |
804 | // bug fix to avoid php crash, see http://bugs.php.net/bug.php?id=55020 | |
805 | if (!method_exists($this, $name)) { | |
806 | return self::__callStatic($name, $arguments); | |
807 | } | |
808 | ||
809 | return $this->$name($arguments); | |
810 | } | |
811 | ||
812 | /** | |
813 | * Returns dynamically loaded core objects. | |
814 | * | |
815 | * @param string $name | |
816 | * @param array $arguments | |
71952a87 | 817 | * @return object |
2b770bdd | 818 | * @throws SystemException |
d335fa16 AE |
819 | */ |
820 | public static final function __callStatic($name, array $arguments) { | |
821 | $className = preg_replace('~^get~', '', $name); | |
822 | ||
823 | if (isset(self::$coreObject[$className])) { | |
824 | return self::$coreObject[$className]; | |
825 | } | |
826 | ||
827 | $objectName = self::getCoreObject($className); | |
828 | if ($objectName === null) { | |
829 | throw new SystemException("Core object '".$className."' is unknown."); | |
830 | } | |
831 | ||
832 | if (class_exists($objectName)) { | |
63b9817b | 833 | if (!is_subclass_of($objectName, SingletonFactory::class)) { |
7b9ff46b | 834 | throw new ParentClassException($objectName, SingletonFactory::class); |
d335fa16 AE |
835 | } |
836 | ||
058cbd6a | 837 | self::$coreObject[$className] = call_user_func([$objectName, 'getInstance']); |
d335fa16 AE |
838 | return self::$coreObject[$className]; |
839 | } | |
840 | } | |
841 | ||
842 | /** | |
843 | * Searches for cached core object definition. | |
844 | * | |
845 | * @param string $className | |
846 | * @return string | |
847 | */ | |
848 | protected static final function getCoreObject($className) { | |
849 | if (isset(self::$coreObjectCache[$className])) { | |
850 | return self::$coreObjectCache[$className]; | |
851 | } | |
852 | ||
853 | return null; | |
854 | } | |
855 | ||
856 | /** | |
857 | * Returns true if the debug mode is enabled, otherwise false. | |
858 | * | |
859 | * @param boolean $ignoreACP | |
860 | * @return boolean | |
861 | */ | |
862 | public static function debugModeIsEnabled($ignoreACP = false) { | |
863 | // ACP override | |
864 | if (!$ignoreACP && self::$overrideDebugMode) { | |
865 | return true; | |
866 | } | |
867 | else if (defined('ENABLE_DEBUG_MODE') && ENABLE_DEBUG_MODE) { | |
868 | return true; | |
869 | } | |
870 | ||
871 | return false; | |
872 | } | |
873 | ||
874 | /** | |
875 | * Returns true if benchmarking is enabled, otherwise false. | |
876 | * | |
877 | * @return boolean | |
878 | */ | |
879 | public static function benchmarkIsEnabled() { | |
880 | // benchmarking is enabled by default | |
881 | if (!defined('ENABLE_BENCHMARK') || ENABLE_BENCHMARK) return true; | |
882 | return false; | |
883 | } | |
884 | ||
885 | /** | |
886 | * Returns domain path for given application. | |
887 | * | |
888 | * @param string $abbreviation | |
889 | * @return string | |
890 | */ | |
891 | public static function getPath($abbreviation = 'wcf') { | |
892 | // workaround during WCFSetup | |
893 | if (!PACKAGE_ID) { | |
894 | return '../'; | |
895 | } | |
896 | ||
897 | if (!isset(self::$applications[$abbreviation])) { | |
898 | $abbreviation = 'wcf'; | |
899 | } | |
900 | ||
901 | return self::$applications[$abbreviation]->getPageURL(); | |
902 | } | |
903 | ||
5d1c96bc AE |
904 | /** |
905 | * Returns the domain path for the currently active application, | |
906 | * used to avoid CORS requests. | |
907 | * | |
908 | * @return string | |
909 | */ | |
910 | public static function getActivePath() { | |
911 | if (!PACKAGE_ID) { | |
912 | return self::getPath(); | |
913 | } | |
914 | ||
915 | return self::getPath(ApplicationHandler::getInstance()->getAbbreviation(ApplicationHandler::getInstance()->getActiveApplication()->packageID)); | |
916 | } | |
917 | ||
d335fa16 AE |
918 | /** |
919 | * Returns a fully qualified anchor for current page. | |
920 | * | |
921 | * @param string $fragment | |
922 | * @return string | |
923 | */ | |
924 | public function getAnchor($fragment) { | |
694f6545 | 925 | return StringUtil::encodeHTML(self::getRequestURI() . '#' . $fragment); |
d335fa16 AE |
926 | } |
927 | ||
12a2ff01 AE |
928 | /** |
929 | * Returns the currently active page or null if unknown. | |
930 | * | |
b212ba66 | 931 | * @return Page|null |
12a2ff01 AE |
932 | */ |
933 | public static function getActivePage() { | |
a039ebd2 AE |
934 | if (self::getActiveRequest() === null) { |
935 | return null; | |
936 | } | |
937 | ||
5346b183 AE |
938 | if (self::getActiveRequest()->getClassName() === CmsPage::class) { |
939 | $metaData = self::getActiveRequest()->getMetaData(); | |
b212ba66 MS |
940 | if (isset($metaData['cms'])) { |
941 | return PageCache::getInstance()->getPage($metaData['cms']['pageID']); | |
942 | } | |
943 | ||
944 | return null; | |
5346b183 AE |
945 | } |
946 | ||
12a2ff01 AE |
947 | return PageCache::getInstance()->getPageByController(self::getActiveRequest()->getClassName()); |
948 | } | |
949 | ||
950 | /** | |
951 | * Returns the currently active request. | |
952 | * | |
953 | * @return Request | |
954 | */ | |
955 | public static function getActiveRequest() { | |
956 | return RequestHandler::getInstance()->getActiveRequest(); | |
957 | } | |
958 | ||
d335fa16 AE |
959 | /** |
960 | * Returns the URI of the current page. | |
961 | * | |
962 | * @return string | |
963 | */ | |
964 | public static function getRequestURI() { | |
c68f6161 | 965 | return preg_replace('~^(https?://[^/]+)(?:/.*)?$~', '$1', self::getTPL()->get('baseHref')) . $_SERVER['REQUEST_URI']; |
d335fa16 AE |
966 | } |
967 | ||
968 | /** | |
969 | * Resets Zend Opcache cache if installed and enabled. | |
970 | * | |
971 | * @param string $script | |
972 | */ | |
973 | public static function resetZendOpcache($script = '') { | |
974 | if (self::$zendOpcacheEnabled === null) { | |
975 | self::$zendOpcacheEnabled = false; | |
976 | ||
977 | if (extension_loaded('Zend Opcache') && @ini_get('opcache.enable')) { | |
978 | self::$zendOpcacheEnabled = true; | |
979 | } | |
980 | ||
981 | } | |
982 | ||
983 | if (self::$zendOpcacheEnabled) { | |
984 | if (empty($script)) { | |
985 | opcache_reset(); | |
986 | } | |
987 | else { | |
988 | opcache_invalidate($script, true); | |
989 | } | |
990 | } | |
991 | } | |
992 | ||
993 | /** | |
994 | * Returns style handler. | |
995 | * | |
4e25add7 | 996 | * @return StyleHandler |
d335fa16 AE |
997 | */ |
998 | public function getStyleHandler() { | |
999 | return StyleHandler::getInstance(); | |
1000 | } | |
1001 | ||
55b402a0 MW |
1002 | /** |
1003 | * Returns box handler. | |
1004 | * | |
1005 | * @return BoxHandler | |
e71525e4 | 1006 | * @since 3.0 |
55b402a0 MW |
1007 | */ |
1008 | public function getBoxHandler() { | |
1009 | return BoxHandler::getInstance(); | |
1010 | } | |
1011 | ||
d335fa16 AE |
1012 | /** |
1013 | * Returns number of available updates. | |
1014 | * | |
1015 | * @return integer | |
1016 | */ | |
1017 | public function getAvailableUpdates() { | |
1018 | $data = PackageUpdateCacheBuilder::getInstance()->getData(); | |
1019 | return $data['updates']; | |
1020 | } | |
1021 | ||
1022 | /** | |
1023 | * Returns a 8 character prefix for editor autosave. | |
1024 | * | |
1025 | * @return string | |
1026 | */ | |
1027 | public function getAutosavePrefix() { | |
1028 | return substr(sha1(preg_replace('~^https~', 'http', self::getPath())), 0, 8); | |
1029 | } | |
1030 | ||
1031 | /** | |
1032 | * Returns the favicon URL or a base64 encoded image. | |
1033 | * | |
1034 | * @return string | |
1035 | */ | |
1036 | public function getFavicon() { | |
1037 | $activeApplication = ApplicationHandler::getInstance()->getActiveApplication(); | |
39abe192 | 1038 | $wcf = ApplicationHandler::getInstance()->getWCF(); |
aef117b5 | 1039 | $favicon = StyleHandler::getInstance()->getStyle()->getRelativeFavicon(); |
d335fa16 | 1040 | |
39abe192 | 1041 | if ($activeApplication->domainName !== $wcf->domainName) { |
aef117b5 AE |
1042 | if (file_exists(WCF_DIR.$favicon)) { |
1043 | $favicon = file_get_contents(WCF_DIR.$favicon); | |
d335fa16 AE |
1044 | |
1045 | return 'data:image/x-icon;base64,' . base64_encode($favicon); | |
1046 | } | |
1047 | } | |
1048 | ||
aef117b5 | 1049 | return self::getPath() . $favicon; |
d335fa16 AE |
1050 | } |
1051 | ||
6e4aa12f AE |
1052 | /** |
1053 | * Returns true if the desktop notifications should be enabled. | |
1054 | * | |
1055 | * @return boolean | |
1056 | */ | |
1057 | public function useDesktopNotifications() { | |
1058 | if (!ENABLE_DESKTOP_NOTIFICATIONS) { | |
1059 | return false; | |
1060 | } | |
1061 | else if (ApplicationHandler::getInstance()->isMultiDomainSetup()) { | |
1062 | $application = ApplicationHandler::getInstance()->getApplicationByID(DESKTOP_NOTIFICATION_PACKAGE_ID); | |
1063 | // mismatch, default to Core | |
1064 | if ($application === null) $application = ApplicationHandler::getInstance()->getApplicationByID(1); | |
1065 | ||
1066 | $currentApplication = ApplicationHandler::getInstance()->getActiveApplication(); | |
1067 | if ($currentApplication->domainName != $application->domainName) { | |
1068 | // different domain | |
1069 | return false; | |
1070 | } | |
1071 | } | |
1072 | ||
1073 | return true; | |
1074 | } | |
1075 | ||
a80873d5 AE |
1076 | /** |
1077 | * Returns true if currently active request represents the landing page. | |
1078 | * | |
8ff2cd79 | 1079 | * @return boolean |
a80873d5 | 1080 | */ |
0fdd7f96 | 1081 | public static function isLandingPage() { |
a039ebd2 AE |
1082 | if (self::getActiveRequest() === null) { |
1083 | return false; | |
1084 | } | |
1085 | ||
1086 | return self::getActiveRequest()->isLandingPage(); | |
a80873d5 AE |
1087 | } |
1088 | ||
89484ba0 AE |
1089 | /** |
1090 | * Returns true if the given API version is currently supported. | |
1091 | * | |
1092 | * @param integer $apiVersion | |
1093 | * @return boolean | |
1094 | */ | |
1095 | public static function isSupportedApiVersion($apiVersion) { | |
1096 | return ($apiVersion == WSC_API_VERSION) || in_array($apiVersion, self::$supportedLegacyApiVersions); | |
1097 | } | |
1098 | ||
1099 | /** | |
1100 | * Returns the list of supported legacy API versions. | |
1101 | * | |
1102 | * @return integer[] | |
1103 | */ | |
1104 | public static function getSupportedLegacyApiVersions() { | |
1105 | return self::$supportedLegacyApiVersions; | |
1106 | } | |
1107 | ||
d335fa16 AE |
1108 | /** |
1109 | * Initialises the cronjobs. | |
1110 | */ | |
1111 | protected function initCronjobs() { | |
1112 | if (PACKAGE_ID) { | |
63b9817b | 1113 | self::getTPL()->assign('executeCronjobs', CronjobScheduler::getInstance()->getNextExec() < TIME_NOW && defined('OFFLINE') && !OFFLINE); |
d335fa16 AE |
1114 | } |
1115 | } | |
1116 | } |