summaryrefslogtreecommitdiffstats
path: root/library/Icinga/Application/Web.php
diff options
context:
space:
mode:
Diffstat (limited to 'library/Icinga/Application/Web.php')
-rw-r--r--library/Icinga/Application/Web.php505
1 files changed, 505 insertions, 0 deletions
diff --git a/library/Icinga/Application/Web.php b/library/Icinga/Application/Web.php
new file mode 100644
index 0000000..4ce9b45
--- /dev/null
+++ b/library/Icinga/Application/Web.php
@@ -0,0 +1,505 @@
+<?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;
+
+ /**
+ * 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();
+ }
+
+ /**
+ * 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)) {
+ // 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();
+ }
+}