* use Icinga\Application\Cli; * Cli::start(); * * * Usage example for Icinga Web application: * * use Icinga\Application\Web; * Web::start()->dispatch(); * * * Usage example for Icinga-Web 1.x compatibility mode: * * use Icinga\Application\LegacyWeb; * LegacyWeb::start()->setIcingaWebBasedir(ICINGAWEB_BASEDIR)->dispatch(); * */ abstract class ApplicationBootstrap { /** * Base directory * * Parent folder for at least application, bin, modules and public * * @var string */ protected $baseDir; /** * Application directory * * @var string */ protected $appDir; /** * Icinga library directory * * @var string */ protected $libDir; /** * Configuration directory * * @var string */ protected $configDir; /** * Locale directory * * @var string */ protected $localeDir; /** * Common storage directory * * @var string */ protected $storageDir; /** * External library paths * * @var string[] */ protected $libraryPaths; /** * Loaded external libraries * * @var Libraries */ protected $libraries; /** * Icinga class loader * * @var ClassLoader */ private $loader; /** * Config object * * @var Config */ protected $config; /** * Module manager * * @var ModuleManager */ private $moduleManager; /** * Flag indicates we're on cli environment * * @var bool */ protected $isCli = false; /** * Flag indicates we're on web environment * * @var bool */ protected $isWeb = false; /** * Whether Icinga Web 2 requires setup * * @var bool */ protected $requiresSetup = false; /** * Constructor * * @param string $baseDir Icinga Web 2 base directory * @param string $configDir Path to Icinga Web 2's configuration files * @param string $storageDir Path to Icinga Web 2's stored files */ protected function __construct($baseDir = null, $configDir = null, $storageDir = null) { if ($baseDir === null) { $baseDir = dirname($this->getBootstrapDirectory()); } $this->baseDir = $baseDir; $this->appDir = $baseDir . '/application'; if (substr(__DIR__, 0, 8) === 'phar:///') { $this->libDir = dirname(dirname(__DIR__)); } else { $this->libDir = realpath(__DIR__ . '/../..'); } $this->setupAutoloader(); if ($configDir === null) { $configDir = getenv('ICINGAWEB_CONFIGDIR'); if ($configDir === false) { $configDir = Platform::isWindows() ? $baseDir . '/config' : '/etc/icingaweb2'; } } $canonical = realpath($configDir); $this->configDir = $canonical ? $canonical : $configDir; if ($storageDir === null) { $storageDir = getenv('ICINGAWEB_STORAGEDIR'); if ($storageDir === false) { $storageDir = Platform::isWindows() ? $baseDir . '/storage' : '/var/lib/icingaweb2'; } } $canonical = realpath($storageDir); $this->storageDir = $canonical ? $canonical : $storageDir; if ($this->libraryPaths === null) { $libraryPaths = getenv('ICINGAWEB_LIBDIR'); if ($libraryPaths !== false) { $this->libraryPaths = array_filter(array_map( 'realpath', explode(':', $libraryPaths) ), 'is_dir'); } else { $this->libraryPaths = is_dir('/usr/share/icinga-php') ? ['/usr/share/icinga-php'] : []; } } Icinga::setApp($this); require_once dirname(__FILE__) . '/functions.php'; } /** * Bootstrap interface method for concrete bootstrap objects * * @return mixed */ abstract protected function bootstrap(); /** * Get loaded external libraries * * @return Libraries */ public function getLibraries() { return $this->libraries; } /** * Getter for module manager * * @return ModuleManager */ public function getModuleManager() { return $this->moduleManager; } /** * Getter for class loader * * @return ClassLoader */ public function getLoader() { return $this->loader; } /** * Getter for configuration object * * @return Config */ public function getConfig() { return $this->config; } /** * Flag indicates we're on cli environment * * @return bool */ public function isCli() { return $this->isCli; } /** * Flag indicates we're on web environment * * @return bool */ public function isWeb() { return $this->isWeb; } /** * Helper to glue directories together * * @param string $dir * @param string $subdir * * @return string */ private function getDirWithSubDir($dir, $subdir = null) { if ($subdir !== null) { $dir .= '/' . ltrim($subdir, '/'); } return $dir; } /** * Get the base directory * * @param string $subDir Optional sub directory to get * * @return string */ public function getBaseDir($subDir = null) { return $this->getDirWithSubDir($this->baseDir, $subDir); } /** * Get the application directory * * @param string $subDir Optional sub directory to get * * @return string */ public function getApplicationDir($subDir = null) { return $this->getDirWithSubDir($this->appDir, $subDir); } /** * Get the configuration directory * * @param string $subDir Optional sub directory to get * * @return string */ public function getConfigDir($subDir = null) { return $this->getDirWithSubDir($this->configDir, $subDir); } /** * Get the common storage directory * * @param string $subDir Optional sub directory to get * * @return string */ public function getStorageDir($subDir = null) { return $this->getDirWithSubDir($this->storageDir, $subDir); } /** * Get the Icinga library directory * * @param string $subDir Optional sub directory to get * * @return string */ public function getLibraryDir($subDir = null) { return $this->getDirWithSubDir($this->libDir, $subDir); } /** * Get the path to the bootstrapping directory * * This is usually /public for Web and EmbeddedWeb and /bin for the CLI * * @return string * * @throws LogicException If the base directory can not be detected */ public function getBootstrapDirectory() { $script = $_SERVER['SCRIPT_FILENAME']; $canonical = realpath($script); if ($canonical !== false) { $dir = dirname($canonical); } elseif (substr($script, -14) === '/webrouter.php') { // If Icinga Web 2 is served using PHP's built-in webserver with our webrouter.php script, the $_SERVER // variable SCRIPT_FILENAME is set to DOCUMENT_ROOT/webrouter.php which is not a valid path to // realpath but DOCUMENT_ROOT here still is the bootstrapping directory $dir = dirname($script); } else { throw new LogicException('Can\'t detected base directory'); } return $dir; } /** * Start the bootstrap * * @param string $baseDir Icinga Web 2 base directory * @param string $configDir Path to Icinga Web 2's configuration files * * @return static */ public static function start($baseDir = null, $configDir = null) { $application = new static($baseDir, $configDir); $application->bootstrap(); return $application; } /** * Setup Icinga class loader * * @return $this */ public function setupAutoloader() { require_once $this->libDir . '/Icinga/Application/ClassLoader.php'; $this->loader = new ClassLoader(); $this->loader->registerNamespace('Icinga', $this->libDir . '/Icinga'); $this->loader->registerNamespace('Icinga', $this->libDir . '/Icinga', $this->appDir); $this->loader->register(); return $this; } /** * Setup module manager * * @return $this */ protected function setupModuleManager() { $paths = $this->getAvailableModulePaths(); $this->moduleManager = new ModuleManager( $this, $this->configDir . '/enabledModules', $paths ); return $this; } protected function getAvailableModulePaths() { $paths = []; $configured = getenv('ICINGAWEB_MODULES_DIR'); if (! $configured) { $configured = $this->config->get('global', 'module_path', $this->baseDir . '/modules'); } $nextIsPhar = false; foreach (explode(PATH_SEPARATOR, $configured) as $path) { if ($path === 'phar') { $nextIsPhar = true; continue; } if ($nextIsPhar) { $nextIsPhar = false; $paths[] = 'phar:' . $path; } else { $paths[] = $path; } } return $paths; } /** * Load all enabled modules * * @return $this */ protected function loadEnabledModules() { try { $this->moduleManager->loadEnabledModules(); } catch (NotReadableError $e) { Logger::error(new IcingaException('Cannot load enabled modules. An exception was thrown:', $e)); } return $this; } /** * Load the setup module if Icinga Web 2 requires setup or the setup token exists * * @return $this */ protected function loadSetupModuleIfNecessary() { if (! @file_exists($this->config->resolvePath('authentication.ini'))) { $this->requiresSetup = true; if ($this->moduleManager->hasInstalled('setup')) { $this->moduleManager->loadModule('setup'); } } elseif ($this->setupTokenExists()) { // Load setup module but do not require setup if ($this->moduleManager->hasInstalled('setup')) { $this->moduleManager->loadModule('setup'); } } return $this; } /** * Get whether Icinga Web 2 requires setup * * @return bool */ public function requiresSetup() { return $this->requiresSetup; } /** * Get whether the setup token exists * * @return bool */ public function setupTokenExists() { return @file_exists($this->config->resolvePath('setup.token')); } /** * Load external libraries * * @return $this */ protected function loadLibraries() { $this->libraries = new Libraries(); foreach ($this->libraryPaths as $libraryPath) { foreach (new DirectoryIterator($libraryPath) as $path) { if (! $path->isDot() && is_dir($path->getRealPath())) { $this->libraries->registerPath($path->getPathname()) ->registerAutoloader(); } } } return $this; } /** * Setup default logging * * @return $this */ protected function setupLogging() { Logger::create( new ConfigObject( array( 'log' => 'syslog' ) ) ); return $this; } /** * Load Configuration * * @return $this */ protected function loadConfig() { Config::$configDir = $this->configDir; try { $this->config = Config::app(); } catch (NotReadableError $e) { Logger::error(new IcingaException('Cannot load application configuration. An exception was thrown:', $e)); $this->config = new Config(); } return $this; } /** * Error handling configuration * * @return $this */ protected function setupErrorHandling() { error_reporting(E_ALL | E_STRICT); ini_set('display_startup_errors', 1); ini_set('display_errors', 1); set_error_handler(function ($errno, $errstr, $errfile, $errline) { if (! (error_reporting() & $errno)) { // Error was suppressed with the @-operator return false; // Continue with the normal error handler } switch ($errno) { case E_NOTICE: case E_WARNING: case E_STRICT: case E_RECOVERABLE_ERROR: throw new ErrorException($errstr, 0, $errno, $errfile, $errline); } return false; // Continue with the normal error handler }); return $this; } /** * Set up logger * * @return $this */ protected function setupLogger() { if ($this->config->hasSection('logging')) { $loggingConfig = $this->config->getSection('logging'); try { Logger::create($loggingConfig); } catch (ConfigurationError $e) { Logger::getInstance()->registerConfigError($e->getMessage()); try { Logger::getInstance()->setLevel($loggingConfig->get('level', Logger::ERROR)); } catch (ConfigurationError $e) { Logger::getInstance()->registerConfigError($e->getMessage()); } } } return $this; } /** * Set up the user backend factory * * @return $this */ protected function setupUserBackendFactory() { try { UserBackend::setConfig(Config::app('authentication')); } catch (NotReadableError $e) { Logger::error( new IcingaException('Cannot load user backend configuration. An exception was thrown:', $e) ); } return $this; } /** * Detect the timezone * * @return null|string */ protected function detectTimezone() { return null; } /** * Set up the timezone * * @return $this */ final protected function setupTimezone() { $timezone = $this->detectTimezone(); if ($timezone === null || @date_default_timezone_set($timezone) === false) { date_default_timezone_set(@date_default_timezone_get()); } return $this; } /** * Detect the locale * * @return null|string */ protected function detectLocale() { return null; } /** * Prepare internationalization using gettext * * @return $this */ protected function prepareInternationalization() { StaticTranslator::$instance = (new GettextTranslator()) ->setDefaultDomain('icinga'); return $this; } /** * Set up internationalization using gettext * * @return $this */ final protected function setupInternationalization() { /** @var GettextTranslator $translator */ $translator = StaticTranslator::$instance; if ($this->hasLocales()) { $translator->addTranslationDirectory($this->getLocaleDir(), 'icinga'); } $locale = $this->detectLocale(); if ($locale === null) { $locale = $translator->getDefaultLocale(); } try { $translator->setLocale($locale); } catch (Exception $error) { Logger::error($error); } return $this; } /** * @return string Our locale directory */ public function getLocaleDir() { if ($this->localeDir === null) { $L10nLocales = getenv('ICINGAWEB_LOCALEDIR') ?: '/usr/share/icinga-L10n/locale'; if (file_exists($L10nLocales) && is_dir($L10nLocales)) { $this->localeDir = $L10nLocales; } else { $this->localeDir = false; } } return $this->localeDir; } /** * return bool Whether Icinga Web has translations */ public function hasLocales() { $localedir = $this->getLocaleDir(); return $localedir !== false && file_exists($localedir) && is_dir($localedir); } /** * Register all hooks provided by the main application * * @return $this */ protected function registerApplicationHooks(): self { Hook::register('DbMigration', DbMigration::class, DbMigration::class); return $this; } }