diff options
Diffstat (limited to '')
-rw-r--r-- | library/Icinga/Application/Web.php | 509 |
1 files changed, 509 insertions, 0 deletions
diff --git a/library/Icinga/Application/Web.php b/library/Icinga/Application/Web.php new file mode 100644 index 0000000..934af07 --- /dev/null +++ b/library/Icinga/Application/Web.php @@ -0,0 +1,509 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Application; + +require_once __DIR__ . '/EmbeddedWeb.php'; + +use ErrorException; +use ipl\I18n\GettextTranslator; +use ipl\I18n\Locale; +use ipl\I18n\StaticTranslator; +use Zend_Controller_Action_HelperBroker; +use Zend_Controller_Front; +use Zend_Controller_Router_Route; +use Zend_Layout; +use Zend_Paginator; +use Zend_View_Helper_PaginationControl; +use Icinga\Authentication\Auth; +use Icinga\User; +use Icinga\Util\DirectoryIterator; +use Icinga\Util\TimezoneDetect; +use Icinga\Web\Controller\Dispatcher; +use Icinga\Web\Navigation\Navigation; +use Icinga\Web\Notification; +use Icinga\Web\Session; +use Icinga\Web\Session\Session as BaseSession; +use Icinga\Web\StyleSheet; +use Icinga\Web\View; + +/** + * Use this if you want to make use of Icinga functionality in other web projects + * + * Usage example: + * <code> + * use Icinga\Application\Web; + * Web::start(); + * </code> + */ +class Web extends EmbeddedWeb +{ + /** + * View object + * + * @var View + */ + private $viewRenderer; + + /** + * Zend front controller instance + * + * @var Zend_Controller_Front + */ + private $frontController; + + /** + * Session object + * + * @var BaseSession + */ + private $session; + + /** + * User object + * + * @var User + */ + private $user; + + /** @var array */ + protected $accessibleMenuItems; + + /** + * Identify web bootstrap + * + * @var bool + */ + protected $isWeb = true; + + /** + * Initialize all together + * + * @return $this + */ + protected function bootstrap() + { + return $this + ->setupLogging() + ->setupErrorHandling() + ->loadLibraries() + ->loadConfig() + ->setupLogger() + ->setupRequest() + ->setupSession() + ->setupNotifications() + ->setupResponse() + ->setupZendMvc() + ->prepareInternationalization() + ->setupModuleManager() + ->loadSetupModuleIfNecessary() + ->loadEnabledModules() + ->setupRoute() + ->setupPagination() + ->setupUserBackendFactory() + ->setupUser() + ->setupTimezone() + ->setupInternationalization() + ->setupFatalErrorHandling() + ->registerApplicationHooks(); + } + + /** + * Get themes provided by Web 2 and all enabled modules + * + * @return string[] Array of theme names as keys and values + */ + public function getThemes() + { + $themes = array(StyleSheet::DEFAULT_THEME); + $applicationThemePath = $this->getBaseDir('public/css/themes'); + if (DirectoryIterator::isReadable($applicationThemePath)) { + foreach (new DirectoryIterator($applicationThemePath, 'less') as $name => $theme) { + $themes[] = substr($name, 0, -5); + } + } + $mm = $this->getModuleManager(); + foreach ($mm->listEnabledModules() as $moduleName) { + $moduleThemePath = $mm->getModule($moduleName)->getCssDir() . '/themes'; + if (! DirectoryIterator::isReadable($moduleThemePath)) { + continue; + } + foreach (new DirectoryIterator($moduleThemePath, 'less') as $name => $theme) { + $themes[] = $moduleName . '/' . substr($name, 0, -5); + } + } + return array_combine($themes, $themes); + } + + /** + * Prepare routing + * + * @return $this + */ + private function setupRoute() + { + $this->frontController->getRouter()->addRoute( + 'module_javascript', + new Zend_Controller_Router_Route( + 'js/components/:module_name/:file', + array( + 'controller' => 'static', + 'action' => 'javascript' + ) + ) + ); + + return $this; + } + + /** + * Getter for frontController + * + * @return Zend_Controller_Front + */ + public function getFrontController() + { + return $this->frontController; + } + + /** + * Getter for view + * + * @return View + */ + public function getViewRenderer() + { + return $this->viewRenderer; + } + + private function hasAccessToSharedNavigationItem(&$config, Config $navConfig) + { + // TODO: Provide a more sophisticated solution + + if (isset($config['owner']) && strtolower($config['owner']) === strtolower($this->user->getUsername())) { + unset($config['owner']); + unset($config['users']); + unset($config['groups']); + return true; + } + + if (isset($config['parent']) && $navConfig->hasSection($config['parent'])) { + unset($config['owner']); + if (isset($this->accessibleMenuItems[$config['parent']])) { + return $this->accessibleMenuItems[$config['parent']]; + } + + $parentConfig = $navConfig->getSection($config['parent']); + $this->accessibleMenuItems[$config['parent']] = $this->hasAccessToSharedNavigationItem( + $parentConfig, + $navConfig + ); + return $this->accessibleMenuItems[$config['parent']]; + } + + if (isset($config['users'])) { + $users = array_map('trim', explode(',', strtolower($config['users']))); + if (in_array('*', $users, true) || in_array(strtolower($this->user->getUsername()), $users, true)) { + unset($config['owner']); + unset($config['users']); + unset($config['groups']); + return true; + } + } + + if (isset($config['groups'])) { + $groups = array_map('trim', explode(',', strtolower($config['groups']))); + if (in_array('*', $groups, true)) { + unset($config['owner']); + unset($config['users']); + unset($config['groups']); + return true; + } + + $userGroups = array_map('strtolower', $this->user->getGroups()); + $matches = array_intersect($userGroups, $groups); + if (! empty($matches)) { + unset($config['owner']); + unset($config['users']); + unset($config['groups']); + return true; + } + } + + return false; + } + + /** + * Load and return the shared navigation of the given type + * + * @param string $type + * + * @return Navigation + */ + public function getSharedNavigation($type) + { + $config = Config::navigation($type === 'dashboard-pane' ? 'dashlet' : $type); + + if ($type === 'dashboard-pane') { + $panes = array(); + foreach ($config as $dashletName => $dashletConfig) { + if ($this->hasAccessToSharedNavigationItem($dashletConfig, $config)) { + // TODO: Throw ConfigurationError if pane or url is missing + $panes[$dashletConfig->pane][$dashletName] = $dashletConfig->url; + } + } + + $navigation = new Navigation(); + foreach ($panes as $paneName => $dashlets) { + $navigation->addItem( + $paneName, + array( + 'type' => 'dashboard-pane', + 'dashlets' => $dashlets + ) + ); + } + } else { + $items = array(); + foreach ($config as $name => $typeConfig) { + if (isset($this->accessibleMenuItems[$name])) { + if ($this->accessibleMenuItems[$name]) { + $items[$name] = $typeConfig; + } + } else { + if ($this->hasAccessToSharedNavigationItem($typeConfig, $config)) { + $this->accessibleMenuItems[$name] = true; + $items[$name] = $typeConfig; + } else { + $this->accessibleMenuItems[$name] = false; + } + } + } + + $navigation = Navigation::fromConfig($items); + } + + return $navigation; + } + + /** + * Dispatch public interface + */ + public function dispatch() + { + $this->frontController->dispatch($this->getRequest(), $this->getResponse()); + } + + /** + * Prepare Zend MVC Base + * + * @return $this + */ + private function setupZendMvc() + { + Zend_Layout::startMvc( + array( + 'layout' => 'layout', + 'layoutPath' => $this->getApplicationDir('/layouts/scripts') + ) + ); + + $this->setupFrontController(); + $this->setupViewRenderer(); + return $this; + } + + /** + * Create user object + * + * @return $this + */ + private function setupUser() + { + $auth = Auth::getInstance(); + if (! $this->request->isXmlHttpRequest() && $this->request->isApiRequest() && ! $auth->isAuthenticated()) { + $auth->authHttp(); + } + if ($auth->isAuthenticated()) { + $user = $auth->getUser(); + $this->getRequest()->setUser($user); + $this->user = $user; + + if ($user->can('user/application/stacktraces')) { + $displayExceptions = $this->user->getPreferences()->getValue( + 'icingaweb', + 'show_stacktraces' + ); + + if ($displayExceptions !== null) { + $this->frontController->setParams( + array( + 'displayExceptions' => $displayExceptions + ) + ); + } + } + } + return $this; + } + + /** + * Initialize a session provider + * + * @return $this + */ + private function setupSession() + { + $this->session = Session::create(); + return $this; + } + + /** + * Initialize notifications to remove them immediately from session + * + * @return $this + */ + private function setupNotifications() + { + Notification::getInstance(); + return $this; + } + + /** + * Instantiate front controller + * + * @return $this + */ + private function setupFrontController() + { + $this->frontController = Zend_Controller_Front::getInstance(); + $this->frontController->setDispatcher(new Dispatcher()); + $this->frontController->setRequest($this->getRequest()); + $this->frontController->setControllerDirectory($this->getApplicationDir('/controllers')); + + $displayExceptions = $this->config->get('global', 'show_stacktraces', true); + + $this->frontController->setParams( + array( + 'displayExceptions' => $displayExceptions + ) + ); + return $this; + } + + /** + * Register helper paths and views for renderer + * + * @return $this + */ + private function setupViewRenderer() + { + $view = Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer'); + /** @var \Zend_Controller_Action_Helper_ViewRenderer $view */ + $view->setView(new View()); + $view->view->addHelperPath($this->getApplicationDir('/views/helpers')); + $view->view->setEncoding('UTF-8'); + $view->view->headTitle()->prepend($this->config->get('global', 'project', 'Icinga')); + $view->view->headTitle()->setSeparator(' :: '); + $this->viewRenderer = $view; + return $this; + } + + /** + * Configure pagination settings + * + * @return $this + */ + private function setupPagination() + { + // TODO: document what we need for whatever reason?! + Zend_Paginator::addScrollingStylePrefixPath( + 'Icinga_Web_Paginator_ScrollingStyle_', + $this->getLibraryDir('Icinga/Web/Paginator/ScrollingStyle') + ); + + Zend_Paginator::addScrollingStylePrefixPath( + 'Icinga_Web_Paginator_ScrollingStyle', + 'Icinga/Web/Paginator/ScrollingStyle' + ); + + Zend_Paginator::setDefaultScrollingStyle('SlidingWithBorder'); + Zend_View_Helper_PaginationControl::setDefaultViewPartial( + array('mixedPagination.phtml', 'default') + ); + return $this; + } + + /** + * Fatal error handling configuration + * + * @return $this + */ + protected function setupFatalErrorHandling() + { + register_shutdown_function(function () { + $error = error_get_last(); + + if ($error !== null && $error['type'] === E_ERROR) { + $frontController = Icinga::app()->getFrontController(); + $response = $frontController->getResponse(); + + $response->setException(new ErrorException( + $error['message'], + 0, + $error['type'], + $error['file'], + $error['line'] + )); + + // Clean PHP's fatal error stack trace and replace it with ours + ob_end_clean(); + $frontController->dispatch($frontController->getRequest(), $response); + } + }); + + return $this; + } + + /** + * (non-PHPDoc) + * @see ApplicationBootstrap::detectTimezone() For the method documentation. + */ + protected function detectTimezone() + { + $auth = Auth::getInstance(); + if (! $auth->isAuthenticated() + || ($timezone = $auth->getUser()->getPreferences()->getValue('icingaweb', 'timezone')) === null + ) { + $detect = new TimezoneDetect(); + $timezone = $detect->getTimezoneName(); + } + return $timezone; + } + + /** + * Setup internationalization using gettext + * + * Uses the preferred user language or the browser suggested language or our default. + * + * @return string Detected locale code + */ + protected function detectLocale() + { + $auth = Auth::getInstance(); + if ($auth->isAuthenticated() + && ($locale = $auth->getUser()->getPreferences()->getValue('icingaweb', 'language')) !== null + ) { + return $locale; + } + + /** @var GettextTranslator $translator */ + $translator = StaticTranslator::$instance; + + if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { + return (new Locale())->getPreferred($_SERVER['HTTP_ACCEPT_LANGUAGE'], $translator->listLocales()); + } + + return $translator->getDefaultLocale(); + } +} |