<commit_before><?php
namespace wcf\system;
use wcf\data\application\Application;
use wcf\data\option\OptionEditor;
use wcf\data\package\Package;
use wcf\data\package\PackageCache;
use wcf\data\package\PackageEditor;
use wcf\data\page\Page;
use wcf\data\page\PageCache;
use wcf\page\CmsPage;
use wcf\system\application\ApplicationHandler;
use wcf\system\application\IApplication;
use wcf\system\box\BoxHandler;
use wcf\system\cache\builder\CoreObjectCacheBuilder;
use wcf\system\cache\builder\PackageUpdateCacheBuilder;
use wcf\system\cronjob\CronjobScheduler;
use wcf\system\database\MySQLDatabase;
use wcf\system\event\EventHandler;
use wcf\system\exception\AJAXException;
use wcf\system\exception\ErrorException;
use wcf\system\exception\IPrintableException;
use wcf\system\exception\NamedUserException;
use wcf\system\exception\ParentClassException;
use wcf\system\exception\PermissionDeniedException;
use wcf\system\exception\SystemException;
use wcf\system\language\LanguageFactory;
use wcf\system\package\PackageInstallationDispatcher;
use wcf\system\registry\RegistryHandler;
use wcf\system\request\Request;
use wcf\system\request\RequestHandler;
use wcf\system\session\SessionFactory;
use wcf\system\session\SessionHandler;
use wcf\system\style\StyleHandler;
use wcf\system\template\EmailTemplateEngine;
use wcf\system\template\TemplateEngine;
use wcf\system\user\storage\UserStorageHandler;
use wcf\util\FileUtil;
use wcf\util\HeaderUtil;
use wcf\util\StringUtil;
use wcf\util\UserUtil;

// try to set a time-limit to infinite
@set_time_limit(0);

// fix timezone warning issue
if (!@ini_get('date.timezone')) {
	@date_default_timezone_set('Europe/London');
}

// define current woltlab suite version
define('WCF_VERSION', '3.1.2 pl 2');

// define current API version
define('WSC_API_VERSION', 2018);

// define current unix timestamp
define('TIME_NOW', time());

// wcf imports
if (!defined('NO_IMPORTS')) {
	require_once(WCF_DIR.'lib/core.functions.php');
	require_once(WCF_DIR.'lib/system/api/autoload.php');
}

/**
 * WCF is the central class for the WoltLab Suite Core.
 * It holds the database connection, access to template and language engine.
 * 
 * @author	<NAME>
 * @copyright	2001-2018 WoltLab GmbH
 * @license	GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
 * @package	WoltLabSuite\Core\System
 */
class WCF {
	/**
	 * list of supported legacy API versions
	 * @var integer[]
	 */
	private static $supportedLegacyApiVersions = [2017];
	
	/**
	 * list of currently loaded applications
	 * @var	Application[]
	 */
	protected static $applications = [];
	
	/**
	 * list of currently loaded application objects
	 * @var	IApplication[]
	 */
	protected static $applicationObjects = [];
	
	/**
	 * list of autoload directories
	 * @var	array
	 */
	protected static $autoloadDirectories = [];
	
	/**
	 * list of unique instances of each core object
	 * @var	SingletonFactory[]
	 */
	protected static $coreObject = [];
	
	/**
	 * list of cached core objects
	 * @var	string[]
	 */
	protected static $coreObjectCache = [];
	
	/**
	 * database object
	 * @var	MySQLDatabase
	 */
	protected static $dbObj = null;
	
	/**
	 * language object
	 * @var	\wcf\data\language\Language
	 */
	protected static $languageObj = null;
	
	/**
	 * overrides disabled debug mode
	 * @var	boolean
	 */
	protected static $overrideDebugMode = false;
	
	/**
	 * session object
	 * @var	SessionHandler
	 */
	protected static $sessionObj = null;
	
	/**
	 * template object
	 * @var	TemplateEngine
	 */
	protected static $tplObj = null;
	
	/**
	 * true if Zend Opcache is loaded and enabled
	 * @var	boolean
	 */
	protected static $zendOpcacheEnabled = null;
	
	/**
	 * force logout during destructor call
	 * @var boolean
	 */
	protected static $forceLogout = false;
	
	/**
	 * Calls all init functions of the WCF class.
	 */
	public function __construct() {
		// add autoload directory
		self::$autoloadDirectories['wcf'] = WCF_DIR . 'lib/';
		
		// define tmp directory
		if (!defined('TMP_DIR')) define('TMP_DIR', FileUtil::getTempFolder());
		
		// start initialization
		$this->initDB();
		$this->loadOptions();
		$this->initSession();
		$this->initLanguage();
		$this->initTPL();
		$this->initCronjobs();
		$this->initCoreObjects();
		$this->initApplications();
		$this->initBlacklist();
		
		EventHandler::getInstance()->fireAction($this, 'initialized');
	}
	
	/**
	 * Flushes the output, closes the session, performs background tasks and more.
	 * 
	 * You *must* not create output in here under normal circumstances, as it might get eaten
	 * when gzip is enabled.
	 */
	public static function destruct() {
		try {
			// database has to be initialized
			if (!is_object(self::$dbObj)) return;
			
			$debug = self::debugModeIsEnabled(true);
			if (!$debug) {
				// flush output
				if (ob_get_level()) ob_end_flush();
				flush();
				
				// close connection if using FPM
				if (function_exists('fastcgi_finish_request')) fastcgi_finish_request();
			}
			
			// update session
			if (is_object(self::getSession())) {
				if (self::$forceLogout) {
					// do logout
					self::getSession()->delete();
				}
				else {
					self::getSession()->update();
				}
			}
			
			// execute shutdown actions of storage handlers
			RegistryHandler::getInstance()->shutdown();
			UserStorageHandler::getInstance()->shutdown();
		}
		catch (\Exception $exception) {
			die("<pre>WCF::destruct() Unhandled exception: ".$exception->getMessage()."\n\n".$exception->getTraceAsString());
		}
	}
	
	/**
	 * Returns the database object.
	 * 
	 * @return	\wcf\system\database\Database
	 */
	public static final function getDB() {
		return self::$dbObj;
	}
	
	/**
	 * Returns the session object.
	 * 
	 * @return	SessionHandler
	 */
	public static final function getSession() {
		return self::$sessionObj;
	}
	
	/**
	 * Returns the user object.
	 * 
	 * @return	\wcf\data\user\User
	 */
	public static final function getUser() {
		return self::getSession()->getUser();
	}
	
	/**
	 * Returns the language object.
	 * 
	 * @return	\wcf\data\language\Language
	 */
	public static final function getLanguage() {
		return self::$languageObj;
	}
	
	/**
	 * Returns the template object.
	 * 
	 * @return	TemplateEngine
	 */
	public static final function getTPL() {
		return self::$tplObj;
	}
	
	/**
	 * Calls the show method on the given exception.
	 * 
	 * @param	\Exception	$e
	 */
	public static final function handleException($e) {
		// backwards compatibility
		if ($e instanceof IPrintableException) {
			$e->show();
			exit;
		}
		
		if (ob_get_level()) {
			// discard any output generated before the exception occurred, prevents exception
			// being hidden inside HTML elements and therefore not visible in browser output
			// 
			// ob_get_level() can return values > 1, if the PHP setting `output_buffering` is on
			while (ob_get_level()) ob_end_clean();
			
			// Some webservers are broken and will apply gzip encoding at all cost, but they fail
			// to set a proper `Content-Encoding` HTTP header and mess things up even more.
			// Especially the `identity` value appears to be unrecognized by some of them, hence
			// we'll just gzip the output of the exception to prevent them from tampering.
			// This part is copied from `HeaderUtil` in order to isolate the exception handler!
			if (HTTP_ENABLE_GZIP && !defined('HTTP_DISABLE_GZIP')) {
				if (function_exists('gzcompress') && !@ini_get('zlib.output_compression') && !@ini_get('output_handler') && isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strstr($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip')) {
					if (strstr($_SERVER['HTTP_ACCEPT_ENCODING'], 'x-gzip')) {
						@header('Content-Encoding: x-gzip');
					}
					else {
						@header('Content-Encoding: gzip');
					}
					
					ob_start(function($output) {
						$size = strlen($output);
						$crc = crc32($output);
						
						$newOutput = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff";
						$newOutput .= substr(gzcompress($output, 1), 2, -4);
						$newOutput .= pack('V', $crc);
						$newOutput .= pack('V', $size);
						
						return $newOutput;
					});
				}
			}
		}
		
		@header('HTTP/1.1 503 Service Unavailable');
		try {
			\wcf\functions\exception\printThrowable($e);
		}
		catch (\Throwable $e2) {
			echo "<pre>An Exception was thrown while handling an Exception:\n\n";
			echo preg_replace('/Database->__construct\(.*\)/', 'Database->__construct(...)', $e2);
			echo "\n\nwas thrown while:\n\n";
			echo preg_replace('/Database->__construct\(.*\)/', 'Database->__construct(...)', $e);
			echo "\n\nwas handled.</pre>";
			exit;
		}
		catch (\Exception $e2) {
			echo "<pre>An Exception was thrown while handling an Exception:\n\n";
			echo preg_replace('/Database->__construct\(.*\)/', 'Database->__construct(...)', $e2);
			echo "\n\nwas thrown while:\n\n";
			echo preg_replace('/Database->__construct\(.*\)/', 'Database->__construct(...)', $e);
			echo "\n\nwas handled.</pre>";
			exit;
		}
	}
	
	/**
	 * Turns PHP errors into an ErrorException.
	 * 
	 * @param	integer		$severity
	 * @param	string		$message
	 * @param	string		$file
	 * @param	integer		$line
	 * @throws	ErrorException
	 */
	public static final function handleError($severity, $message, $file, $line) {
		// this is necessary for the shut-up operator
		if (error_reporting() == 0) return;
		
		throw new ErrorException($message, 0, $severity, $file, $line);
	}
	
	/**
	 * Loads the database configuration and creates a new connection to the database.
	 */
	protected function initDB() {
		// get configuration
		$dbHost = $dbUser = $dbPassword = $dbName = '';
		$dbPort = 0;
		require(WCF_DIR.'config.inc.php');
		
		// create database connection
		self::$dbObj = new MySQLDatabase($dbHost, $dbUser, $dbPassword, $dbName, $dbPort);
	}
	
	/**
	 * Loads the options file, automatically created if not exists.
	 */
	protected function loadOptions() {
		$filename = WCF_DIR.'options.inc.php';
		
		// create options file if doesn't exist
		if (!file_exists($filename) || filemtime($filename) <= 1) {
			OptionEditor::rebuild();
		}
		require_once($filename);
		
		// check if option file is complete and writable
		if (PACKAGE_ID) {
			if (!is_writable($filename)) {
				FileUtil::makeWritable($filename);
				
				if (!is_writable($filename)) {
					throw new SystemException("The option file '" . $filename . "' is not writable.");
				}
			}
			
			// check if a previous write operation was incomplete and force rebuilding
			if (!defined('WCF_OPTION_INC_PHP_SUCCESS')) {
				OptionEditor::rebuild();
				
				require_once($filename);
			}
		}
	}
	
	/**
	 * Starts the session system.
	 */
	protected function initSession() {
		$factory = new SessionFactory();
		$factory->load();
		
		self::$sessionObj = SessionHandler::getInstance();
		self::$sessionObj->setHasValidCookie($factory->hasValidCookie());
	}
	
	/**
	 * Initialises the language engine.
	 */
	protected function initLanguage() {
		if (isset($_GET['l']) && !self::getUser()->userID) {
			self::getSession()->setLanguageID(intval($_GET['l']));
		}
		
		// set mb settings
		mb_internal_encoding('UTF-8');
		if (function_exists('mb_regex_encoding')) mb_regex_encoding('UTF-8');
		mb_language('uni');
		
		// get language
		self::$languageObj = LanguageFactory::getInstance()->getUserLanguage(self::getSession()->getLanguageID());
	}
	
	/**
	 * Initialises the template engine.
	 */
	protected function initTPL() {
		self::$tplObj = TemplateEngine::getInstance();
		self::getTPL()->setLanguageID(self::getLanguage()->languageID);
		$this->assignDefaultTemplateVariables();
		
		$this->initStyle();
	}
	
	/**
	 * Initializes the user's style.
	 */
	protected function initStyle() {
		if (isset($_REQUEST['styleID'])) {
			self::getSession()->setStyleID(intval($_REQUEST['styleID']));
		}
		
		$styleHandler = StyleHandler::getInstance();
		$styleHandler->changeStyle(self::getSession()->getStyleID());
	}
	
	/**
	 * Executes the blacklist.
	 */
	protected function initBlacklist() {
		$isAjax = isset($_SERVER['HTTP_X_REQUESTED_WITH']) && ($_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest');
		
		if (defined('BLACKLIST_IP_ADDRESSES') && BLACKLIST_IP_ADDRESSES != '') {
			if (!StringUtil::executeWordFilter(UserUtil::convertIPv6To4(self::getSession()->ipAddress), BLACKLIST_IP_ADDRESSES)) {
				if ($isAjax) {
					throw new AJAXException(self::getLanguage()->getDynamicVariable('wcf.ajax.error.permissionDenied'), AJAXException::INSUFFICIENT_PERMISSIONS);
				}
				else {
					throw new PermissionDeniedException();
				}
			}
			else if (!StringUtil::executeWordFilter(self::getSession()->ipAddress, BLACKLIST_IP_ADDRESSES)) {
				if ($isAjax) {
					throw new AJAXException(self::getLanguage()->getDynamicVariable('wcf.ajax.error.permissionDenied'), AJAXException::INSUFFICIENT_PERMISSIONS);
				}
				else {
					throw new PermissionDeniedException();
				}
			}
		}
		if (defined('BLACKLIST_USER_AGENTS') && BLACKLIST_USER_AGENTS != '') {
			if (!StringUtil::executeWordFilter(self::getSession()->userAgent, BLACKLIST_USER_AGENTS)) {
				if ($isAjax) {
					throw new AJAXException(self::getLanguage()->getDynamicVariable('wcf.ajax.error.permissionDenied'), AJAXException::INSUFFICIENT_PERMISSIONS);
				}
				else {
					throw new PermissionDeniedException();
				}
			}
		}
		if (defined('BLACKLIST_HOSTNAMES') && BLACKLIST_HOSTNAMES != '') {
			if (!StringUtil::executeWordFilter(@gethostbyaddr(self::getSession()->ipAddress), BLACKLIST_HOSTNAMES)) {
				if ($isAjax) {
					throw new AJAXException(self::getLanguage()->getDynamicVariable('wcf.ajax.error.permissionDenied'), AJAXException::INSUFFICIENT_PERMISSIONS);
				}
				else {
					throw new PermissionDeniedException();
				}
			}
		}
		
		// handle banned users
		if (self::getUser()->userID && self::getUser()->banned) {
			if ($isAjax) {
				throw new AJAXException(self::getLanguage()->getDynamicVariable('wcf.user.error.isBanned'), AJAXException::INSUFFICIENT_PERMISSIONS);
			}
			else {
				self::$forceLogout = true;
				
				// remove cookies
				if (isset($_COOKIE[COOKIE_PREFIX.'userID'])) {
					HeaderUtil::setCookie('userID', 0);
				}
				if (isset($_COOKIE[COOKIE_PREFIX.'password'])) {
					HeaderUtil::setCookie('password', '');
				}
				
				throw new NamedUserException(self::getLanguage()->getDynamicVariable('wcf.user.error.isBanned'));
			}
		}
	}
	
	/**
	 * Initializes applications.
	 */
	protected function initApplications() {
		// step 1) load all applications
		$loadedApplications = [];
		
		// register WCF as application
		self::$applications['wcf'] = ApplicationHandler::getInstance()->getApplicationByID(1);
		
		if (!class_exists(WCFACP::class, false)) {
			static::getTPL()->assign('baseHref', self::$applications['wcf']->getPageURL());
		}
		
		// start main application
		$application = ApplicationHandler::getInstance()->getActiveApplication();
		if ($application->packageID != 1) {
			$loadedApplications[] = $this->loadApplication($application);
			
			// register primary application
			$abbreviation = ApplicationHandler::getInstance()->getAbbreviation($application->packageID);
			self::$applications[$abbreviation] = $application;
		}
		
		// start dependent applications
		$applications = ApplicationHandler::getInstance()->getDependentApplications();
		foreach ($applications as $application) {
			if ($application->packageID == 1) {
				// ignore WCF
				continue;
			}
			else if ($application->isTainted) {
				// ignore apps flagged for uninstallation
				continue;
			}
			
			$loadedApplications[] = $this->loadApplication($application, true);
		}
		
		// step 2) run each application
		if (!class_exists('wcf\system\WCFACP', false)) {
			/** @var IApplication $application */
			foreach ($loadedApplications as $application) {
				$application->__run();
			}
			
			// refresh the session 1 minute before it expires
			self::getTPL()->assign('__sessionKeepAlive', SESSION_TIMEOUT - 60);
		}
	}
	
	/**
	 * Loads an application.
	 * 
	 * @param	Application		$application
	 * @param	boolean			$isDependentApplication
	 * @return	IApplication
	 * @throws	SystemException
	 */
	protected function loadApplication(Application $application, $isDependentApplication = false) {
		$package = PackageCache::getInstance()->getPackage($application->packageID);
		// package cache might be outdated
		if ($package === null) {
			$package = new Package($application->packageID);
			
			// package cache is outdated, discard cache
			if ($package->packageID) {
				PackageEditor::resetCache();
			}
			else {
				// package id is invalid
				throw new SystemException("application identified by package id '".$application->packageID."' is unknown");
			}
		}
		
		$abbreviation = ApplicationHandler::getInstance()->getAbbreviation($application->packageID);
		$packageDir = FileUtil::getRealPath(WCF_DIR.$package->packageDir);
		self::$autoloadDirectories[$abbreviation] = $packageDir . 'lib/';
		
		$className = $abbreviation.'\system\\'.strtoupper($abbreviation).'Core';
		
		// class was not found, possibly the app was moved, but `packageDir` has not been adjusted
		if (!class_exists($className)) {
			// check if both the Core and the app are on the same domain
			$coreApp = ApplicationHandler::getInstance()->getApplicationByID(1);
			if ($coreApp->domainName === $application->domainName) {
				// resolve the relative path and use it to construct the autoload directory
				$relativePath = FileUtil::getRelativePath($coreApp->domainPath, $application->domainPath);
				if ($relativePath !== './') {
					$packageDir = FileUtil::getRealPath(WCF_DIR.$relativePath);
					self::$autoloadDirectories[$abbreviation] = $packageDir . 'lib/';
					
					if (class_exists($className)) {
						// the class can now be found, update the `packageDir` value
						(new PackageEditor($package))->update(['packageDir' => $relativePath]);
					}
				}
			}
		}
		
		if (class_exists($className) && is_subclass_of($className, IApplication::class)) {
			// include config file
			$configPath = $packageDir . PackageInstallationDispatcher::CONFIG_FILE;
			if (!file_exists($configPath)) {
				Package::writeConfigFile($package->packageID);
			}
			
			if (file_exists($configPath)) {
				require_once($configPath);
			}
			else {
				throw new SystemException('Unable to load configuration for '.$package->package);
			}
			
			// register template path if not within ACP
			if (!class_exists('wcf\system\WCFACP', false)) {
				// add template path and abbreviation
				static::getTPL()->addApplication($abbreviation, $packageDir . 'templates/');
			}
			EmailTemplateEngine::getInstance()->addApplication($abbreviation, $packageDir . 'templates/');
			
			// init application and assign it as template variable
			self::$applicationObjects[$application->packageID] = call_user_func([$className, 'getInstance']);
			static::getTPL()->assign('__'.$abbreviation, self::$applicationObjects[$application->packageID]);
			EmailTemplateEngine::getInstance()->assign('__'.$abbreviation, self::$applicationObjects[$application->packageID]);
		}
		else {
			unset(self::$autoloadDirectories[$abbreviation]);
			throw new SystemException("Unable to run '".$package->package."', '".$className."' is missing or does not implement '".IApplication::class."'.");
		}
		
		// register template path in ACP
		if (class_exists('wcf\system\WCFACP', false)) {
			static::getTPL()->addApplication($abbreviation, $packageDir . 'acp/templates/');
		}
		else if (!$isDependentApplication) {
			// assign base tag
			static::getTPL()->assign('baseHref', $application->getPageURL());
		}
		
		// register application
		self::$applications[$abbreviation] = $application;
		
		return self::$applicationObjects[$application->packageID];
	}
	
	/**
	 * Returns the corresponding application object. Does not support the 'wcf' pseudo application.
	 * 
	 * @param	Application	$application
	 * @return	IApplication
	 */
	public static function getApplicationObject(Application $application) {
		if (isset(self::$applicationObjects[$application->packageID])) {
			return self::$applicationObjects[$application->packageID];
		}
		
		return null;
	}
	
	/**
	 * Returns the invoked application.
	 * 
	 * @return      Application
	 */
	public static function getActiveApplication() {
		return ApplicationHandler::getInstance()->getActiveApplication();
	}
	
	/**
	 * Loads an application on runtime, do not use this outside the package installation.
	 * 
	 * @param	integer		$packageID
	 */
	public static function loadRuntimeApplication($packageID) {
		$package = new Package($packageID);
		$application = new Application($packageID);
		
		$abbreviation = Package::getAbbreviation($package->package);
		$packageDir = FileUtil::getRealPath(WCF_DIR.$package->packageDir);
		self::$autoloadDirectories[$abbreviation] = $packageDir . 'lib/';
		self::$applications[$abbreviation] = $application;
		self::getTPL()->addApplication($abbreviation, $packageDir . 'acp/templates/');
	}
	<commit_msg>missing since-documentation of WCF::getActiveApplication()<commit_after><?php
namespace wcf\system;
use wcf\data\application\Application;
use wcf\data\option\OptionEditor;
use wcf\data\package\Package;
use wcf\data\package\PackageCache;
use wcf\data\package\PackageEditor;
use wcf\data\page\Page;
use wcf\data\page\PageCache;
use wcf\page\CmsPage;
use wcf\system\application\ApplicationHandler;
use wcf\system\application\IApplication;
use wcf\system\box\BoxHandler;
use wcf\system\cache\builder\CoreObjectCacheBuilder;
use wcf\system\cache\builder\PackageUpdateCacheBuilder;
use wcf\system\cronjob\CronjobScheduler;
use wcf\system\database\MySQLDatabase;
use wcf\system\event\EventHandler;
use wcf\system\exception\AJAXException;
use wcf\system\exception\ErrorException;
use wcf\system\exception\IPrintableException;
use wcf\system\exception\NamedUserException;
use wcf\system\exception\ParentClassException;
use wcf\system\exception\PermissionDeniedException;
use wcf\system\exception\SystemException;
use wcf\system\language\LanguageFactory;
use wcf\system\package\PackageInstallationDispatcher;
use wcf\system\registry\RegistryHandler;
use wcf\system\request\Request;
use wcf\system\request\RequestHandler;
use wcf\system\session\SessionFactory;
use wcf\system\session\SessionHandler;
use wcf\system\style\StyleHandler;
use wcf\system\template\EmailTemplateEngine;
use wcf\system\template\TemplateEngine;
use wcf\system\user\storage\UserStorageHandler;
use wcf\util\FileUtil;
use wcf\util\HeaderUtil;
use wcf\util\StringUtil;
use wcf\util\UserUtil;

// try to set a time-limit to infinite
@set_time_limit(0);

// fix timezone warning issue
if (!@ini_get('date.timezone')) {
	@date_default_timezone_set('Europe/London');
}

// define current woltlab suite version
define('WCF_VERSION', '3.1.2 pl 2');

// define current API version
define('WSC_API_VERSION', 2018);

// define current unix timestamp
define('TIME_NOW', time());

// wcf imports
if (!defined('NO_IMPORTS')) {
	require_once(WCF_DIR.'lib/core.functions.php');
	require_once(WCF_DIR.'lib/system/api/autoload.php');
}

/**
 * WCF is the central class for the WoltLab Suite Core.
 * It holds the database connection, access to template and language engine.
 * 
 * @author	<NAME>
 * @copyright	2001-2018 WoltLab GmbH
 * @license	GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
 * @package	WoltLabSuite\Core\System
 */
class WCF {
	/**
	 * list of supported legacy API versions
	 * @var integer[]
	 */
	private static $supportedLegacyApiVersions = [2017];
	
	/**
	 * list of currently loaded applications
	 * @var	Application[]
	 */
	protected static $applications = [];
	
	/**
	 * list of currently loaded application objects
	 * @var	IApplication[]
	 */
	protected static $applicationObjects = [];
	
	/**
	 * list of autoload directories
	 * @var	array
	 */
	protected static $autoloadDirectories = [];
	
	/**
	 * list of unique instances of each core object
	 * @var	SingletonFactory[]
	 */
	protected static $coreObject = [];
	
	/**
	 * list of cached core objects
	 * @var	string[]
	 */
	protected static $coreObjectCache = [];
	
	/**
	 * database object
	 * @var	MySQLDatabase
	 */
	protected static $dbObj = null;
	
	/**
	 * language object
	 * @var	\wcf\data\language\Language
	 */
	protected static $languageObj = null;
	
	/**
	 * overrides disabled debug mode
	 * @var	boolean
	 */
	protected static $overrideDebugMode = false;
	
	/**
	 * session object
	 * @var	SessionHandler
	 */
	protected static $sessionObj = null;
	
	/**
	 * template object
	 * @var	TemplateEngine
	 */
	protected static $tplObj = null;
	
	/**
	 * true if Zend Opcache is loaded and enabled
	 * @var	boolean
	 */
	protected static $zendOpcacheEnabled = null;
	
	/**
	 * force logout during destructor call
	 * @var boolean
	 */
	protected static $forceLogout = false;
	
	/**
	 * Calls all init functions of the WCF class.
	 */
	public function __construct() {
		// add autoload directory
		self::$autoloadDirectories['wcf'] = WCF_DIR . 'lib/';
		
		// define tmp directory
		if (!defined('TMP_DIR')) define('TMP_DIR', FileUtil::getTempFolder());
		
		// start initialization
		$this->initDB();
		$this->loadOptions();
		$this->initSession();
		$this->initLanguage();
		$this->initTPL();
		$this->initCronjobs();
		$this->initCoreObjects();
		$this->initApplications();
		$this->initBlacklist();
		
		EventHandler::getInstance()->fireAction($this, 'initialized');
	}
	
	/**
	 * Flushes the output, closes the session, performs background tasks and more.
	 * 
	 * You *must* not create output in here under normal circumstances, as it might get eaten
	 * when gzip is enabled.
	 */
	public static function destruct() {
		try {
			// database has to be initialized
			if (!is_object(self::$dbObj)) return;
			
			$debug = self::debugModeIsEnabled(true);
			if (!$debug) {
				// flush output
				if (ob_get_level()) ob_end_flush();
				flush();
				
				// close connection if using FPM
				if (function_exists('fastcgi_finish_request')) fastcgi_finish_request();
			}
			
			// update session
			if (is_object(self::getSession())) {
				if (self::$forceLogout) {
					// do logout
					self::getSession()->delete();
				}
				else {
					self::getSession()->update();
				}
			}
			
			// execute shutdown actions of storage handlers
			RegistryHandler::getInstance()->shutdown();
			UserStorageHandler::getInstance()->shutdown();
		}
		catch (\Exception $exception) {
			die("<pre>WCF::destruct() Unhandled exception: ".$exception->getMessage()."\n\n".$exception->getTraceAsString());
		}
	}
	
	/**
	 * Returns the database object.
	 * 
	 * @return	\wcf\system\database\Database
	 */
	public static final function getDB() {
		return self::$dbObj;
	}
	
	/**
	 * Returns the session object.
	 * 
	 * @return	SessionHandler
	 */
	public static final function getSession() {
		return self::$sessionObj;
	}
	
	/**
	 * Returns the user object.
	 * 
	 * @return	\wcf\data\user\User
	 */
	public static final function getUser() {
		return self::getSession()->getUser();
	}
	
	/**
	 * Returns the language object.
	 * 
	 * @return	\wcf\data\language\Language
	 */
	public static final function getLanguage() {
		return self::$languageObj;
	}
	
	/**
	 * Returns the template object.
	 * 
	 * @return	TemplateEngine
	 */
	public static final function getTPL() {
		return self::$tplObj;
	}
	
	/**
	 * Calls the show method on the given exception.
	 * 
	 * @param	\Exception	$e
	 */
	public static final function handleException($e) {
		// backwards compatibility
		if ($e instanceof IPrintableException) {
			$e->show();
			exit;
		}
		
		if (ob_get_level()) {
			// discard any output generated before the exception occurred, prevents exception
			// being hidden inside HTML elements and therefore not visible in browser output
			// 
			// ob_get_level() can return values > 1, if the PHP setting `output_buffering` is on
			while (ob_get_level()) ob_end_clean();
			
			// Some webservers are broken and will apply gzip encoding at all cost, but they fail
			// to set a proper `Content-Encoding` HTTP header and mess things up even more.
			// Especially the `identity` value appears to be unrecognized by some of them, hence
			// we'll just gzip the output of the exception to prevent them from tampering.
			// This part is copied from `HeaderUtil` in order to isolate the exception handler!
			if (HTTP_ENABLE_GZIP && !defined('HTTP_DISABLE_GZIP')) {
				if (function_exists('gzcompress') && !@ini_get('zlib.output_compression') && !@ini_get('output_handler') && isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strstr($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip')) {
					if (strstr($_SERVER['HTTP_ACCEPT_ENCODING'], 'x-gzip')) {
						@header('Content-Encoding: x-gzip');
					}
					else {
						@header('Content-Encoding: gzip');
					}
					
					ob_start(function($output) {
						$size = strlen($output);
						$crc = crc32($output);
						
						$newOutput = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff";
						$newOutput .= substr(gzcompress($output, 1), 2, -4);
						$newOutput .= pack('V', $crc);
						$newOutput .= pack('V', $size);
						
						return $newOutput;
					});
				}
			}
		}
		
		@header('HTTP/1.1 503 Service Unavailable');
		try {
			\wcf\functions\exception\printThrowable($e);
		}
		catch (\Throwable $e2) {
			echo "<pre>An Exception was thrown while handling an Exception:\n\n";
			echo preg_replace('/Database->__construct\(.*\)/', 'Database->__construct(...)', $e2);
			echo "\n\nwas thrown while:\n\n";
			echo preg_replace('/Database->__construct\(.*\)/', 'Database->__construct(...)', $e);
			echo "\n\nwas handled.</pre>";
			exit;
		}
		catch (\Exception $e2) {
			echo "<pre>An Exception was thrown while handling an Exception:\n\n";
			echo preg_replace('/Database->__construct\(.*\)/', 'Database->__construct(...)', $e2);
			echo "\n\nwas thrown while:\n\n";
			echo preg_replace('/Database->__construct\(.*\)/', 'Database->__construct(...)', $e);
			echo "\n\nwas handled.</pre>";
			exit;
		}
	}
	
	/**
	 * Turns PHP errors into an ErrorException.
	 * 
	 * @param	integer		$severity
	 * @param	string		$message
	 * @param	string		$file
	 * @param	integer		$line
	 * @throws	ErrorException
	 */
	public static final function handleError($severity, $message, $file, $line) {
		// this is necessary for the shut-up operator
		if (error_reporting() == 0) return;
		
		throw new ErrorException($message, 0, $severity, $file, $line);
	}
	
	/**
	 * Loads the database configuration and creates a new connection to the database.
	 */
	protected function initDB() {
		// get configuration
		$dbHost = $dbUser = $dbPassword = $dbName = '';
		$dbPort = 0;
		require(WCF_DIR.'config.inc.php');
		
		// create database connection
		self::$dbObj = new MySQLDatabase($dbHost, $dbUser, $dbPassword, $dbName, $dbPort);
	}
	
	/**
	 * Loads the options file, automatically created if not exists.
	 */
	protected function loadOptions() {
		$filename = WCF_DIR.'options.inc.php';
		
		// create options file if doesn't exist
		if (!file_exists($filename) || filemtime($filename) <= 1) {
			OptionEditor::rebuild();
		}
		require_once($filename);
		
		// check if option file is complete and writable
		if (PACKAGE_ID) {
			if (!is_writable($filename)) {
				FileUtil::makeWritable($filename);
				
				if (!is_writable($filename)) {
					throw new SystemException("The option file '" . $filename . "' is not writable.");
				}
			}
			
			// check if a previous write operation was incomplete and force rebuilding
			if (!defined('WCF_OPTION_INC_PHP_SUCCESS')) {
				OptionEditor::rebuild();
				
				require_once($filename);
			}
		}
	}
	
	/**
	 * Starts the session system.
	 */
	protected function initSession() {
		$factory = new SessionFactory();
		$factory->load();
		
		self::$sessionObj = SessionHandler::getInstance();
		self::$sessionObj->setHasValidCookie($factory->hasValidCookie());
	}
	
	/**
	 * Initialises the language engine.
	 */
	protected function initLanguage() {
		if (isset($_GET['l']) && !self::getUser()->userID) {
			self::getSession()->setLanguageID(intval($_GET['l']));
		}
		
		// set mb settings
		mb_internal_encoding('UTF-8');
		if (function_exists('mb_regex_encoding')) mb_regex_encoding('UTF-8');
		mb_language('uni');
		
		// get language
		self::$languageObj = LanguageFactory::getInstance()->getUserLanguage(self::getSession()->getLanguageID());
	}
	
	/**
	 * Initialises the template engine.
	 */
	protected function initTPL() {
		self::$tplObj = TemplateEngine::getInstance();
		self::getTPL()->setLanguageID(self::getLanguage()->languageID);
		$this->assignDefaultTemplateVariables();
		
		$this->initStyle();
	}
	
	/**
	 * Initializes the user's style.
	 */
	protected function initStyle() {
		if (isset($_REQUEST['styleID'])) {
			self::getSession()->setStyleID(intval($_REQUEST['styleID']));
		}
		
		$styleHandler = StyleHandler::getInstance();
		$styleHandler->changeStyle(self::getSession()->getStyleID());
	}
	
	/**
	 * Executes the blacklist.
	 */
	protected function initBlacklist() {
		$isAjax = isset($_SERVER['HTTP_X_REQUESTED_WITH']) && ($_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest');
		
		if (defined('BLACKLIST_IP_ADDRESSES') && BLACKLIST_IP_ADDRESSES != '') {
			if (!StringUtil::executeWordFilter(UserUtil::convertIPv6To4(self::getSession()->ipAddress), BLACKLIST_IP_ADDRESSES)) {
				if ($isAjax) {
					throw new AJAXException(self::getLanguage()->getDynamicVariable('wcf.ajax.error.permissionDenied'), AJAXException::INSUFFICIENT_PERMISSIONS);
				}
				else {
					throw new PermissionDeniedException();
				}
			}
			else if (!StringUtil::executeWordFilter(self::getSession()->ipAddress, BLACKLIST_IP_ADDRESSES)) {
				if ($isAjax) {
					throw new AJAXException(self::getLanguage()->getDynamicVariable('wcf.ajax.error.permissionDenied'), AJAXException::INSUFFICIENT_PERMISSIONS);
				}
				else {
					throw new PermissionDeniedException();
				}
			}
		}
		if (defined('BLACKLIST_USER_AGENTS') && BLACKLIST_USER_AGENTS != '') {
			if (!StringUtil::executeWordFilter(self::getSession()->userAgent, BLACKLIST_USER_AGENTS)) {
				if ($isAjax) {
					throw new AJAXException(self::getLanguage()->getDynamicVariable('wcf.ajax.error.permissionDenied'), AJAXException::INSUFFICIENT_PERMISSIONS);
				}
				else {
					throw new PermissionDeniedException();
				}
			}
		}
		if (defined('BLACKLIST_HOSTNAMES') && BLACKLIST_HOSTNAMES != '') {
			if (!StringUtil::executeWordFilter(@gethostbyaddr(self::getSession()->ipAddress), BLACKLIST_HOSTNAMES)) {
				if ($isAjax) {
					throw new AJAXException(self::getLanguage()->getDynamicVariable('wcf.ajax.error.permissionDenied'), AJAXException::INSUFFICIENT_PERMISSIONS);
				}
				else {
					throw new PermissionDeniedException();
				}
			}
		}
		
		// handle banned users
		if (self::getUser()->userID && self::getUser()->banned) {
			if ($isAjax) {
				throw new AJAXException(self::getLanguage()->getDynamicVariable('wcf.user.error.isBanned'), AJAXException::INSUFFICIENT_PERMISSIONS);
			}
			else {
				self::$forceLogout = true;
				
				// remove cookies
				if (isset($_COOKIE[COOKIE_PREFIX.'userID'])) {
					HeaderUtil::setCookie('userID', 0);
				}
				if (isset($_COOKIE[COOKIE_PREFIX.'password'])) {
					HeaderUtil::setCookie('password', '');
				}
				
				throw new NamedUserException(self::getLanguage()->getDynamicVariable('wcf.user.error.isBanned'));
			}
		}
	}
	
	/**
	 * Initializes applications.
	 */
	protected function initApplications() {
		// step 1) load all applications
		$loadedApplications = [];
		
		// register WCF as application
		self::$applications['wcf'] = ApplicationHandler::getInstance()->getApplicationByID(1);
		
		if (!class_exists(WCFACP::class, false)) {
			static::getTPL()->assign('baseHref', self::$applications['wcf']->getPageURL());
		}
		
		// start main application
		$application = ApplicationHandler::getInstance()->getActiveApplication();
		if ($application->packageID != 1) {
			$loadedApplications[] = $this->loadApplication($application);
			
			// register primary application
			$abbreviation = ApplicationHandler::getInstance()->getAbbreviation($application->packageID);
			self::$applications[$abbreviation] = $application;
		}
		
		// start dependent applications
		$applications = ApplicationHandler::getInstance()->getDependentApplications();
		foreach ($applications as $application) {
			if ($application->packageID == 1) {
				// ignore WCF
				continue;
			}
			else if ($application->isTainted) {
				// ignore apps flagged for uninstallation
				continue;
			}
			
			$loadedApplications[] = $this->loadApplication($application, true);
		}
		
		// step 2) run each application
		if (!class_exists('wcf\system\WCFACP', false)) {
			/** @var IApplication $application */
			foreach ($loadedApplications as $application) {
				$application->__run();
			}
			
			// refresh the session 1 minute before it expires
			self::getTPL()->assign('__sessionKeepAlive', SESSION_TIMEOUT - 60);
		}
	}
	
	/**
	 * Loads an application.
	 * 
	 * @param	Application		$application
	 * @param	boolean			$isDependentApplication
	 * @return	IApplication
	 * @throws	SystemException
	 */
	protected function loadApplication(Application $application, $isDependentApplication = false) {
		$package = PackageCache::getInstance()->getPackage($application->packageID);
		// package cache might be outdated
		if ($package === null) {
			$package = new Package($application->packageID);
			
			// package cache is outdated, discard cache
			if ($package->packageID) {
				PackageEditor::resetCache();
			}
			else {
				// package id is invalid
				throw new SystemException("application identified by package id '".$application->packageID."' is unknown");
			}
		}
		
		$abbreviation = ApplicationHandler::getInstance()->getAbbreviation($application->packageID);
		$packageDir = FileUtil::getRealPath(WCF_DIR.$package->packageDir);
		self::$autoloadDirectories[$abbreviation] = $packageDir . 'lib/';
		
		$className = $abbreviation.'\system\\'.strtoupper($abbreviation).'Core';
		
		// class was not found, possibly the app was moved, but `packageDir` has not been adjusted
		if (!class_exists($className)) {
			// check if both the Core and the app are on the same domain
			$coreApp = ApplicationHandler::getInstance()->getApplicationByID(1);
			if ($coreApp->domainName === $application->domainName) {
				// resolve the relative path and use it to construct the autoload directory
				$relativePath = FileUtil::getRelativePath($coreApp->domainPath, $application->domainPath);
				if ($relativePath !== './') {
					$packageDir = FileUtil::getRealPath(WCF_DIR.$relativePath);
					self::$autoloadDirectories[$abbreviation] = $packageDir . 'lib/';
					
					if (class_exists($className)) {
						// the class can now be found, update the `packageDir` value
						(new PackageEditor($package))->update(['packageDir' => $relativePath]);
					}
				}
			}
		}
		
		if (class_exists($className) && is_subclass_of($className, IApplication::class)) {
			// include config file
			$configPath = $packageDir . PackageInstallationDispatcher::CONFIG_FILE;
			if (!file_exists($configPath)) {
				Package::writeConfigFile($package->packageID);
			}
			
			if (file_exists($configPath)) {
				require_once($configPath);
			}
			else {
				throw new SystemException('Unable to load configuration for '.$package->package);
			}
			
			// register template path if not within ACP
			if (!class_exists('wcf\system\WCFACP', false)) {
				// add template path and abbreviation
				static::getTPL()->addApplication($abbreviation, $packageDir . 'templates/');
			}
			EmailTemplateEngine::getInstance()->addApplication($abbreviation, $packageDir . 'templates/');
			
			// init application and assign it as template variable
			self::$applicationObjects[$application->packageID] = call_user_func([$className, 'getInstance']);
			static::getTPL()->assign('__'.$abbreviation, self::$applicationObjects[$application->packageID]);
			EmailTemplateEngine::getInstance()->assign('__'.$abbreviation, self::$applicationObjects[$application->packageID]);
		}
		else {
			unset(self::$autoloadDirectories[$abbreviation]);
			throw new SystemException("Unable to run '".$package->package."', '".$className."' is missing or does not implement '".IApplication::class."'.");
		}
		
		// register template path in ACP
		if (class_exists('wcf\system\WCFACP', false)) {
			static::getTPL()->addApplication($abbreviation, $packageDir . 'acp/templates/');
		}
		else if (!$isDependentApplication) {
			// assign base tag
			static::getTPL()->assign('baseHref', $application->getPageURL());
		}
		
		// register application
		self::$applications[$abbreviation] = $application;
		
		return self::$applicationObjects[$application->packageID];
	}
	
	/**
	 * Returns the corresponding application object. Does not support the 'wcf' pseudo application.
	 * 
	 * @param	Application	$application
	 * @return	IApplication
	 */
	public static function getApplicationObject(Application $application) {
		if (isset(self::$applicationObjects[$application->packageID])) {
			return self::$applicationObjects[$application->packageID];
		}
		
		return null;
	}
	
	/**
	 * Returns the invoked application.
	 * 
	 * @return      Application
	 * @since	3.1
	 */
	public static function getActiveApplication() {
		return ApplicationHandler::getInstance()->getActiveApplication();
	}
	
	/**
	 * Loads an application on runtime, do not use this outside the package installation.
	 * 
	 * @param	integer		$packageID
	 */
	public static function loadRuntimeApplication($packageID) {
		$package = new Package($packageID);
		$application = new Application($packageID);
		
		$abbreviation = Package::getAbbreviation($package->package);
		$packageDir = FileUtil::getRealPath(WCF_DIR.$package->packageDir);
		self::$autoloadDirectories[$abbreviation] = $packageDir . 'lib/';
		self::$applications[$abbreviation] = $application;
		self::getTPL()->addApplication($abbreviation, $packageDir . 'acp/templates/');
	}
	
{'fraction_non_alphanumeric': 0.14549444258932404, 'fraction_numerical': 0.00338691981109574, 'mean_word_length': 3.9954724174907663, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 221, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6194763', 'n_tokens_mistral': 14738, 'n_tokens_neox': 12460, 'n_words': 2857}