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