Merge branch 'master' into feature/display-documentation-4820

Conflicts:
	modules/doc/library/Doc/Controller.php
This commit is contained in:
Eric Lippmann 2014-07-01 12:42:55 +02:00
commit db5c6631d9
299 changed files with 7514 additions and 9158 deletions

4
.gitignore vendored
View File

@ -10,7 +10,6 @@
!.gitkeep !.gitkeep
build/ build/
test/js/npm-debug.log
# ./configure output # ./configure output
config.log config.log
@ -20,10 +19,9 @@ config.status
Makefile Makefile
# cmd tester # cmd tester
test/php/bin/extcmd_test modules/test/bin/extcmd_test
# misc test output # misc test output
test/frontend/static/public
test/php/library/Icinga/Protocol/Statusdat/.cache test/php/library/Icinga/Protocol/Statusdat/.cache
# Generated API documentation # Generated API documentation

View File

@ -43,7 +43,7 @@ priority = 50
[Overview.Servicematrix] [Overview.Servicematrix]
title = "Servicematrix" title = "Servicematrix"
url = "monitoring/list/servicematrix" url = "monitoring/list/servicematrix?service_problem=1"
priority = 51 priority = 51
[Overview.Servicegroups] [Overview.Servicegroups]
@ -85,7 +85,7 @@ url = "monitoring/list/notifications"
[History.Events] [History.Events]
title = "All Events" title = "All Events"
url = "monitoring/list/eventhistory?raw_timestamp>=-2+days" url = "monitoring/list/eventhistory?timestamp>=-7%20days"
[System.Process Info] [System.Process Info]
title = "Process Info" title = "Process Info"

View File

@ -6,6 +6,7 @@ PACKAGE_VERSION=@PACKAGE_VERSION@
prefix=@prefix@ prefix=@prefix@
exec_prefix=@exec_prefix@ exec_prefix=@exec_prefix@
bindir=@bindir@
HTTPD_CONFIG_PATH=@httpd_config_path@ HTTPD_CONFIG_PATH=@httpd_config_path@
ICINGAWEB_CONFIG_PATH=@icingaweb_config_path@ ICINGAWEB_CONFIG_PATH=@icingaweb_config_path@
@ -24,7 +25,15 @@ default:
# #
# Installs the whole application w\o httpd configurations # Installs the whole application w\o httpd configurations
# #
install: install-config install-basic ensure-writable-folders install: install-config install-basic ensure-writable-folders install-cli
#
# Install icingacli bin
#
install-cli:
$(INSTALL) -m 755 -d $(INSTALL_OPTS) $(bindir)
$(INSTALL) -m 755 $(INSTALL_OPTS) "./bin/icingacli" $(bindir)/icingacli;
# #
# Installs the whole application w\o configuration # Installs the whole application w\o configuration
@ -91,7 +100,7 @@ install-apache-config:
# #
# Installs the php files to the prefix # Installs the php files to the prefix
# #
install-application: copy-web-files-application copy-web-files-library install-application: copy-web-files-application copy-web-files-library install-cli
# #
# Rule for copying folders and containing files (arbitary types), hidden files are excluded # Rule for copying folders and containing files (arbitary types), hidden files are excluded

1
Vagrantfile vendored
View File

@ -73,6 +73,7 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
# information on available options. # information on available options.
config.vm.provider "virtualbox" do |vb| config.vm.provider "virtualbox" do |vb|
vb.customize ["setextradata", :id, "VBoxInternal2/SharedFoldersEnableSymlinksCreate//vagrant/config", "1"] vb.customize ["setextradata", :id, "VBoxInternal2/SharedFoldersEnableSymlinksCreate//vagrant/config", "1"]
vb.customize ["modifyvm", :id, "--memory", "1024"]
end end
# Enable provisioning with Puppet stand alone. Puppet manifests # Enable provisioning with Puppet stand alone. Puppet manifests

View File

@ -1,5 +1,4 @@
<?php <?php
// @codeCoverageIgnoreStart
namespace Icinga\Clicommands; namespace Icinga\Clicommands;
@ -118,4 +117,3 @@ class AutocompleteCommand extends Command
} }
} }
} }
// @codeCoverageIgnoreEnd

View File

@ -1,5 +1,4 @@
<?php <?php
// @codeCoverageIgnoreStart
namespace Icinga\Clicommands; namespace Icinga\Clicommands;
@ -41,4 +40,3 @@ class HelpCommand extends Command
); );
} }
} }
// @codeCoverageIgnoreEnd

View File

@ -1,5 +1,4 @@
<?php <?php
// @codeCoverageIgnoreStart
namespace Icinga\Clicommands; namespace Icinga\Clicommands;
@ -212,4 +211,3 @@ class ModuleCommand extends Command
$this->fail("Not implemented yet"); $this->fail("Not implemented yet");
} }
} }
// @codeCoverageIgnoreEnd

View File

@ -1,5 +1,4 @@
<?php <?php
// @codeCoverageIgnoreStart
namespace Icinga\Clicommands; namespace Icinga\Clicommands;
@ -81,4 +80,3 @@ class WebCommand extends Command
} }
} }
} }
// @codeCoverageIgnoreEnd

View File

@ -1,5 +1,4 @@
<?php <?php
// @codeCoverageIgnoreStart
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
/** /**
* This file is part of Icinga Web 2. * This file is part of Icinga Web 2.
@ -32,7 +31,6 @@
use Icinga\Authentication\Backend\AutoLoginBackend; use Icinga\Authentication\Backend\AutoLoginBackend;
use Icinga\Web\Controller\ActionController; use Icinga\Web\Controller\ActionController;
use Icinga\Authentication\Manager as AuthManager;
use Icinga\Form\Authentication\LoginForm; use Icinga\Form\Authentication\LoginForm;
use Icinga\Authentication\AuthChain; use Icinga\Authentication\AuthChain;
use Icinga\Application\Config; use Icinga\Application\Config;
@ -60,21 +58,16 @@ class AuthenticationController extends ActionController
*/ */
public function loginAction() public function loginAction()
{ {
$auth = $this->Auth();
$this->view->form = new LoginForm(); $this->view->form = new LoginForm();
$this->view->form->setRequest($this->_request); $this->view->form->setRequest($this->_request);
$this->view->title = $this->translate('Icingaweb Login'); $this->view->title = $this->translate('Icingaweb Login');
try { try {
$redirectUrl = Url::fromPath($this->_request->getParam('redirect', 'dashboard')); $redirectUrl = Url::fromPath($this->params->get('redirect', 'dashboard'));
if ($this->_request->isXmlHttpRequest()) {
$redirectUrl->setParam('_render', 'layout');
}
$auth = AuthManager::getInstance();
if ($auth->isAuthenticated()) { if ($auth->isAuthenticated()) {
$this->redirectNow($redirectUrl); $this->rerenderLayout()->redirectNow($redirectUrl);
} }
try { try {
@ -99,7 +92,7 @@ class AuthenticationController extends ActionController
$authenticated = $backend->authenticate($user); $authenticated = $backend->authenticate($user);
if ($authenticated === true) { if ($authenticated === true) {
$auth->setAuthenticated($user); $auth->setAuthenticated($user);
$this->redirectNow($redirectUrl); $this->rerenderLayout()->redirectNow($redirectUrl);
} }
} }
} }
@ -123,7 +116,7 @@ class AuthenticationController extends ActionController
} }
if ($authenticated === true) { if ($authenticated === true) {
$auth->setAuthenticated($user); $auth->setAuthenticated($user);
$this->redirectNow($redirectUrl); $this->rerenderLayout()->redirectNow($redirectUrl);
} }
} }
if ($backendsWithError === $backendsTried) { if ($backendsWithError === $backendsTried) {
@ -154,16 +147,14 @@ class AuthenticationController extends ActionController
*/ */
public function logoutAction() public function logoutAction()
{ {
$auth = AuthManager::getInstance(); $auth = $this->Auth();
$auth->removeAuthorization(); $auth->removeAuthorization();
if ($auth->isAuthenticatedFromRemoteUser()) { if ($auth->isAuthenticatedFromRemoteUser()) {
$this->_helper->layout->setLayout('login'); $this->_helper->layout->setLayout('login');
$this->_response->setHttpResponseCode(401); $this->_response->setHttpResponseCode(401);
} else { } else {
$this->_helper->layout->setLayout('inline'); $this->rerenderLayout()->redirectToLogin();
$this->redirectToLogin();
} }
} }
} }
// @codeCoverageIgnoreEnd

View File

@ -1,5 +1,4 @@
<?php <?php
// @codeCoverageIgnoreStart
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
/** /**
* This file is part of Icinga Web 2. * This file is part of Icinga Web 2.
@ -27,21 +26,25 @@
*/ */
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
use \Icinga\Web\Controller\BaseConfigController; use Icinga\Web\Controller\BaseConfigController;
use \Icinga\Web\Widget\Tab; use Icinga\Web\Widget\Tab;
use \Icinga\Web\Widget\AlertMessageBox; use Icinga\Web\Widget\AlertMessageBox;
use \Icinga\Web\Url; use Icinga\Web\Notification;
use \Icinga\Application\Icinga; use Icinga\Application\Modules\Module;
use \Icinga\Application\Config as IcingaConfig; use Icinga\Web\Url;
use \Icinga\Data\ResourceFactory; use Icinga\Web\Widget;
use \Icinga\Form\Config\GeneralForm; use Icinga\Application\Icinga;
use \Icinga\Form\Config\Authentication\ReorderForm; use Icinga\Application\Config as IcingaConfig;
use \Icinga\Form\Config\Authentication\LdapBackendForm; use Icinga\Data\ResourceFactory;
use \Icinga\Form\Config\Authentication\DbBackendForm; use Icinga\Form\Config\GeneralForm;
use \Icinga\Form\Config\ResourceForm; use Icinga\Form\Config\Authentication\ReorderForm;
use \Icinga\Form\Config\LoggingForm; use Icinga\Form\Config\Authentication\LdapBackendForm;
use \Icinga\Form\Config\ConfirmRemovalForm; use Icinga\Form\Config\Authentication\DbBackendForm;
use \Icinga\Config\PreservingIniWriter; use Icinga\Form\Config\ResourceForm;
use Icinga\Form\Config\LoggingForm;
use Icinga\Form\Config\ConfirmRemovalForm;
use Icinga\Config\PreservingIniWriter;
/** /**
* Application wide controller for application preferences * Application wide controller for application preferences
@ -55,52 +58,29 @@ class ConfigController extends BaseConfigController
*/ */
private $resourceTypes = array('livestatus', 'ido', 'statusdat', 'ldap'); private $resourceTypes = array('livestatus', 'ido', 'statusdat', 'ldap');
/** public function init()
* Create tabs for this configuration controller
*
* @return array
*
* @see BaseConfigController::createProvidedTabs()
*/
public static function createProvidedTabs()
{ {
return array( $this->view->tabs = Widget::create('tabs')->add('index', array(
'index' => new Tab( 'title' => 'Application',
array( 'url' => 'config'
'name' => 'index', ))->add('authentication', array(
'title' => 'Application', 'title' => 'Authentication',
'url' => Url::fromPath('/config') 'url' => 'config/authentication'
) ))->add('resources', array(
), 'title' => 'Resources',
'authentication' => new Tab( 'url' => 'config/resource'
array( ))->add('logging', array(
'name' => 'auth', 'title' => 'Logging',
'title' => 'Authentication', 'url' => 'config/logging'
'url' => Url::fromPath('/config/authentication') ))->add('modules', array(
) 'title' => 'Modules',
), 'url' => 'config/modules'
'resources' => new Tab( ));
array( }
'name' => 'resource',
'title' => 'Resources', public function devtoolsAction()
'url' => Url::fromPath('/config/resource') {
) $this->view->tabs = null;
),
'logging' => new Tab(
array(
'name' => 'logging',
'title' => 'Logging',
'url' => Url::fromPath('/config/logging')
)
),
'modules' => new Tab(
array(
'name' => 'modules',
'title' => 'Modules',
'url' => Url::fromPath('/config/moduleoverview')
)
)
);
} }
/** /**
@ -117,7 +97,7 @@ class ConfigController extends BaseConfigController
if (!$this->writeConfigFile($form->getConfig(), 'config')) { if (!$this->writeConfigFile($form->getConfig(), 'config')) {
return; return;
} }
$this->addSuccessMessage("Configuration Sucessfully Updated"); Notification::success('New configuration has successfully been stored');
$form->setConfiguration(IcingaConfig::app(), true); $form->setConfiguration(IcingaConfig::app(), true);
$this->redirectNow('config/index'); $this->redirectNow('config/index');
} }
@ -139,7 +119,7 @@ class ConfigController extends BaseConfigController
if (!$this->writeConfigFile($form->getConfig(), 'config')) { if (!$this->writeConfigFile($form->getConfig(), 'config')) {
return; return;
} }
$this->addSuccessMessage("Configuration Sucessfully Updated"); Notification::success('New configuration has sucessfully been stored');
$form->setConfiguration(IcingaConfig::app(), true); $form->setConfiguration(IcingaConfig::app(), true);
$this->redirectNow('config/logging'); $this->redirectNow('config/logging');
} }
@ -149,15 +129,29 @@ class ConfigController extends BaseConfigController
/** /**
* Display the list of all modules * Display the list of all modules
*/ */
public function moduleoverviewAction() public function modulesAction()
{ {
$this->view->messageBox = new AlertMessageBox(true);
$this->view->tabs->activate('modules'); $this->view->tabs->activate('modules');
$this->view->modules = Icinga::app()->getModuleManager()->select() $this->view->modules = Icinga::app()->getModuleManager()->select()
->from('modules') ->from('modules')
->order('name'); ->order('enabled', 'desc')
$this->render('module/overview'); ->order('name')->paginate();
}
public function moduleAction()
{
$name = $this->getParam('name');
$app = Icinga::app();
$manager = $app->getModuleManager();
if ($manager->hasInstalled($name)) {
$this->view->moduleData = Icinga::app()->getModuleManager()->select()
->from('modules')->where('name', $name)->fetchRow();
$module = new Module($app, $name, $manager->getModuleDir($name));
$this->view->module = $module;
} else {
$this->view->module = false;
}
$this->view->tabs = $module->getConfigTabs()->activate('info');
} }
/** /**
@ -170,9 +164,8 @@ class ConfigController extends BaseConfigController
try { try {
$manager->enableModule($module); $manager->enableModule($module);
$manager->loadModule($module); $manager->loadModule($module);
$this->addSuccessMessage('Module "' . $module . '" enabled'); Notification::success('Module "' . $module . '" enabled');
$this->redirectNow('config/moduleoverview?_render=layout&_reload=css'); $this->rerenderLayout()->reloadCss()->redirectNow('config/modules');
return;
} catch (Exception $e) { } catch (Exception $e) {
$this->view->exceptionMesssage = $e->getMessage(); $this->view->exceptionMesssage = $e->getMessage();
$this->view->moduleName = $module; $this->view->moduleName = $module;
@ -190,9 +183,8 @@ class ConfigController extends BaseConfigController
$manager = Icinga::app()->getModuleManager(); $manager = Icinga::app()->getModuleManager();
try { try {
$manager->disableModule($module); $manager->disableModule($module);
$this->addSuccessMessage('Module "' . $module . '" disabled'); Notification::success('Module "' . $module . '" disabled');
$this->redirectNow('config/moduleoverview?_render=layout&_reload=css'); $this->rerenderLayout()->reloadCss()->redirectNow('config/modules');
return;
} catch (Exception $e) { } catch (Exception $e) {
$this->view->exceptionMessage = $e->getMessage(); $this->view->exceptionMessage = $e->getMessage();
$this->view->moduleName = $module; $this->view->moduleName = $module;
@ -222,7 +214,7 @@ class ConfigController extends BaseConfigController
if ($form->isSubmittedAndValid()) { if ($form->isSubmittedAndValid()) {
if ($this->writeAuthenticationFile($form->getReorderedConfig($config))) { if ($this->writeAuthenticationFile($form->getReorderedConfig($config))) {
$this->addSuccessMessage('Authentication Order Updated'); Notification::success('Authentication Order Updated');
$this->redirectNow('config/authentication'); $this->redirectNow('config/authentication');
} }
@ -272,7 +264,7 @@ class ConfigController extends BaseConfigController
} }
if ($this->writeAuthenticationFile($backendCfg)) { if ($this->writeAuthenticationFile($backendCfg)) {
// redirect to overview with success message // redirect to overview with success message
$this->addSuccessMessage('Backend Modification Written.'); Notification::success('Backend Modification Written.');
$this->redirectNow("config/authentication"); $this->redirectNow("config/authentication");
} }
return; return;
@ -337,7 +329,7 @@ class ConfigController extends BaseConfigController
} }
if ($this->writeAuthenticationFile($backendCfg)) { if ($this->writeAuthenticationFile($backendCfg)) {
// redirect to overview with success message // redirect to overview with success message
$this->addSuccessMessage('Backend "' . $authBackend . '" created'); Notification::success('Backend "' . $authBackend . '" created');
$this->redirectNow("config/authentication"); $this->redirectNow("config/authentication");
} }
return; return;
@ -361,7 +353,7 @@ class ConfigController extends BaseConfigController
$configArray = IcingaConfig::app('authentication', true)->toArray(); $configArray = IcingaConfig::app('authentication', true)->toArray();
$authBackend = $this->getParam('auth_backend'); $authBackend = $this->getParam('auth_backend');
if (!isset($configArray[$authBackend])) { if (!isset($configArray[$authBackend])) {
$this->addSuccessMessage('Can\'t perform removal: Unknown Authentication Backend Provided'); Notification::error('Can\'t perform removal: Unknown Authentication Backend Provided');
$this->render('authentication/remove'); $this->render('authentication/remove');
return; return;
} }
@ -373,7 +365,7 @@ class ConfigController extends BaseConfigController
if ($form->isSubmittedAndValid()) { if ($form->isSubmittedAndValid()) {
unset($configArray[$authBackend]); unset($configArray[$authBackend]);
if ($this->writeAuthenticationFile($configArray)) { if ($this->writeAuthenticationFile($configArray)) {
$this->addSuccessMessage('Authentication Backend "' . $authBackend . '" Removed'); Notification::success('Authentication Backend "' . $authBackend . '" Removed');
$this->redirectNow("config/authentication"); $this->redirectNow("config/authentication");
} }
return; return;
@ -497,7 +489,6 @@ class ConfigController extends BaseConfigController
$this->render('resource/remove'); $this->render('resource/remove');
} }
/** /**
* Redirect target only for error-states * Redirect target only for error-states
* *
@ -507,7 +498,7 @@ class ConfigController extends BaseConfigController
public function configurationerrorAction() public function configurationerrorAction()
{ {
$this->view->messageBox = new AlertMessageBox(true); $this->view->messageBox = new AlertMessageBox(true);
$this->render('error/error'); $this->render('error/error', null, true);
} }
/** /**
@ -557,4 +548,3 @@ class ConfigController extends BaseConfigController
} }
} }
} }
// @codeCoverageIgnoreEnd

View File

@ -1,5 +1,4 @@
<?php <?php
// @codeCoverageIgnoreStart
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
/** /**
* This file is part of Icinga Web 2. * This file is part of Icinga Web 2.
@ -191,4 +190,3 @@ class DashboardController extends ActionController
return true; return true;
} }
} }
// @codeCoverageIgnoreEnd

View File

@ -1,5 +1,4 @@
<?php <?php
// @codeCoverageIgnoreStart
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
/** /**
* This file is part of Icinga Web 2. * This file is part of Icinga Web 2.
@ -78,4 +77,3 @@ class ErrorController extends ActionController
$this->view->request = $error->request; $this->view->request = $error->request;
} }
} }
// @codeCoverageIgnoreEnd

View File

@ -1,5 +1,4 @@
<?php <?php
// @codeCoverageIgnoreStart
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
/** /**
* This file is part of Icinga Web 2. * This file is part of Icinga Web 2.
@ -117,4 +116,3 @@ class FilterController extends ActionController
} }
} }
} }
// @codeCoverageIgnoreEnd

View File

@ -1,5 +1,4 @@
<?php <?php
// @codeCoverageIgnoreStart
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
/** /**
* This file is part of Icinga Web 2. * This file is part of Icinga Web 2.
@ -45,12 +44,7 @@ class IndexController extends ActionController
public function preDispatch() public function preDispatch()
{ {
if ($this->getRequest()->getActionName() !== 'welcome') { if ($this->getRequest()->getActionName() !== 'welcome') {
$url = Url::fromPath('dashboard'); $this->redirectNow('dashboard');
$render = $this->_request->getParam('_render');
if ($render) {
$url->setParam('_render', $render);
}
$this->redirectNow($url);
} }
} }
@ -61,4 +55,3 @@ class IndexController extends ActionController
{ {
} }
} }
// @codeCoverageIgnoreEnd

View File

@ -1,5 +1,4 @@
<?php <?php
// @codeCoverageIgnoreStart
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
@ -107,5 +106,3 @@ class InstallController extends ActionController
$session->write(); $session->write();
} }
} }
// @codeCoverageIgnoreEnd

View File

@ -1,5 +1,4 @@
<?php <?php
// @codeCoverageIgnoreStart
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
@ -43,4 +42,3 @@ class LayoutController extends ActionController
$this->renderScript('parts/topbar.phtml'); $this->renderScript('parts/topbar.phtml');
} }
} }
// @codeCoverageIgnoreEnd

View File

@ -1,5 +1,4 @@
<?php <?php
// @codeCoverageIgnoreStart
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
/** /**
* This file is part of Icinga Web 2. * This file is part of Icinga Web 2.
@ -79,4 +78,3 @@ class ListController extends Controller
} }
} }
} }
// @codeCoverageIgnoreEnd

View File

@ -1,5 +1,4 @@
<?php <?php
// @codeCoverageIgnoreStart
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
/** /**
* This file is part of Icinga Web 2. * This file is part of Icinga Web 2.
@ -83,4 +82,3 @@ class PreferenceController extends BasePreferenceController
$this->view->form = $form; $this->view->form = $form;
} }
} }
// @codeCoverageIgnoreEnd

View File

@ -1,5 +1,4 @@
<?php <?php
// @codeCoverageIgnoreStart
use Icinga\Web\Controller\ActionController; use Icinga\Web\Controller\ActionController;
use Icinga\Application\Icinga; use Icinga\Application\Icinga;
@ -13,7 +12,6 @@ class SearchController extends ActionController
{ {
public function indexAction() public function indexAction()
{ {
$this->setAutorefreshInterval(10);
$search = $this->_request->getParam('q'); $search = $this->_request->getParam('q');
if (! $search) { if (! $search) {
$this->view->tabs = Widget::create('tabs')->add( $this->view->tabs = Widget::create('tabs')->add(
@ -58,4 +56,3 @@ class SearchController extends ActionController
$this->view->tabs = $dashboard->getTabs(); $this->view->tabs = $dashboard->getTabs();
} }
} }
// @codeCoverageIgnoreEnd

View File

@ -1,5 +1,4 @@
<?php <?php
// @codeCoverageIgnoreStart
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
/** /**
* This file is part of Icinga Web 2. * This file is part of Icinga Web 2.
@ -85,11 +84,14 @@ class StaticController extends ActionController
$extension = 'fixme'; $extension = 'fixme';
} }
$s = stat($filePath);
header('Content-Type: image/' . $extension); header('Content-Type: image/' . $extension);
header('Cache-Control: max-age=3600'); header(sprintf('ETag: "%x-%x-%x"', $s['ino'], $s['size'], (float) str_pad($s['mtime'], 16, '0')));
header('Cache-Control: public, max-age=3600');
header('Pragma: cache');
header('Last-Modified: ' . gmdate( header('Last-Modified: ' . gmdate(
'D, d M Y H:i:s', 'D, d M Y H:i:s',
filemtime($filePath) $s['mtime']
) . ' GMT'); ) . ' GMT');
readfile($filePath); readfile($filePath);
@ -185,4 +187,3 @@ class StaticController extends ActionController
$lessCompiler->printStack(); $lessCompiler->printStack();
} }
} }
// @codeCoverageIgnoreEnd

View File

@ -1,32 +1,4 @@
<?php <?php
// @codeCoverageIgnoreStart
// {{{ICINGA_LICENSE_HEADER}}}
/**
* This file is part of Icinga Web 2.
*
* Icinga Web 2 - Head for multiple monitoring backends.
* Copyright (C) 2013 Icinga Development Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* @copyright 2013 Icinga Development Team <info@icinga.org>
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
* @author Icinga Development Team <info@icinga.org>
*
*/
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Form\Authentication; namespace Icinga\Form\Authentication;
@ -49,32 +21,23 @@ class LoginForm extends Form
protected function create() protected function create()
{ {
$this->setName('form_login'); $this->setName('form_login');
$this->addElement( $this->addElement('text', 'username', array(
'text', 'label' => t('Username'),
'username', 'placeholder' => t('Please enter your username...'),
array( 'required' => true,
'required' => true, ));
'placeholder' => t('Username'),
)
);
$this->addElement( $this->addElement('password', 'password', array(
'password', 'label' => t('Password'),
'password', 'placeholder' => t('...and your password'),
array( 'required' => true
'placeholder' => t('Password'), ));
'required' => true
)
);
$user = $this->getElement('username');
// TODO: We need a place to intercept filled forms before rendering // TODO: We need a place to intercept filled forms before rendering
if (isset($_POST['username'])) { if (isset($_POST['username'])) {
$this->getElement('password')->setAttrib('class', 'autofocus'); $this->getElement('password')->setAttrib('class', 'autofocus');
} else { } else {
$user->setAttrib('class', 'autofocus'); $this->getElement('username')->setAttrib('class', 'autofocus');
} }
$this->setSubmitLabel('Login'); $this->setSubmitLabel('Login');
} }
} }
// @codeCoverageIgnoreEnd

View File

@ -30,10 +30,10 @@
namespace Icinga\Form\Config\Authentication; namespace Icinga\Form\Config\Authentication;
use \Exception; use \Exception;
use \Icinga\Authentication\Backend\DbUserBackend;
use \Zend_Config; use \Zend_Config;
use Icinga\Data\ResourceFactory; use Icinga\Data\ResourceFactory;
use Icinga\Authentication\UserBackend; use Icinga\Authentication\DbConnection;
use Icinga\Authentication\Backend\DbUserBackend;
/** /**
* Form class for adding/modifying database authentication backends * Form class for adding/modifying database authentication backends
@ -148,7 +148,6 @@ class DbBackendForm extends BaseBackendForm
$this->addErrorMessage(sprintf(t('Using the specified backend failed: %s'), $e->getMessage())); $this->addErrorMessage(sprintf(t('Using the specified backend failed: %s'), $e->getMessage()));
return false; return false;
} }
return true; return true;
} }
} }

View File

@ -1,5 +1,4 @@
<?php <?php
// @codeCoverageIgnoreStart
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
/** /**
* This file is part of Icinga Web 2. * This file is part of Icinga Web 2.
@ -93,4 +92,3 @@ class ConfirmRemovalForm extends Form
); );
} }
} }
// @codeCoverageIgnoreEnd

View File

@ -1,5 +1,4 @@
<?php <?php
// @codeCoverageIgnoreStart
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
/** /**
* This file is part of Icinga Web 2. * This file is part of Icinga Web 2.
@ -30,19 +29,15 @@
namespace Icinga\Form\Config; namespace Icinga\Form\Config;
use \DateTimeZone; use Icinga\Application\Config as IcingaConfig;
use \Zend_Config; use Icinga\Data\ResourceFactory;
use \Zend_Form_Element_Text; use Icinga\Web\Form;
use \Zend_Form_Element_Select; use Icinga\Util\Translator;
use \Zend_View_Helper_DateFormat; use Icinga\Web\Form\Validator\WritablePathValidator;
use \Icinga\Application\Config as IcingaConfig; use Icinga\Web\Form\Decorator\ConditionalHidden;
use \Icinga\Data\ResourceFactory; use DateTimeZone;
use \Icinga\Web\Form; use Zend_Form_Element_Select;
use \Icinga\Util\Translator; use Zend_Config;
use \Icinga\Web\Form\Validator\WritablePathValidator;
use \Icinga\Web\Form\Validator\TimeFormatValidator;
use \Icinga\Web\Form\Validator\DateFormatValidator;
use \Icinga\Web\Form\Decorator\ConditionalHidden;
/** /**
* Configuration form for general, application-wide settings * Configuration form for general, application-wide settings
@ -63,13 +58,6 @@ class GeneralForm extends Form
*/ */
private $resources; private $resources;
/**
* The view helper to format date/time strings
*
* @var Zend_View_Helper_DateFormat
*/
private $dateHelper;
/** /**
* Set a specific configuration directory to use for configuration specific default paths * Set a specific configuration directory to use for configuration specific default paths
* *
@ -92,29 +80,6 @@ class GeneralForm extends Form
return $this->configDir === null ? IcingaConfig::$configDir : $this->configDir; return $this->configDir === null ? IcingaConfig::$configDir : $this->configDir;
} }
/**
* Return the view helper to format date/time strings
*
* @return Zend_View_Helper_DateFormat
*/
public function getDateFormatter()
{
if ($this->dateHelper === null) {
return $this->getView()->dateFormat();
}
return $this->dateHelper;
}
/**
* Set the view helper that is used to format date/time strings (used for testing)
*
* @param Zend_View_Helper_DateFormat $dateHelper
*/
public function setDateFormatter(Zend_View_Helper_DateFormat $dateHelper)
{
$this->dateHelper = $dateHelper;
}
/** /**
* Set an alternative array of resources that should be used instead of the DBFactory resource set * Set an alternative array of resources that should be used instead of the DBFactory resource set
* (used for testing) * (used for testing)
@ -140,29 +105,6 @@ class GeneralForm extends Form
} }
} }
/**
* Add the checkbox for using the development environment to this form
*
* @param Zend_Config $cfg The "global" section of the config.ini
*/
private function addDevelopmentCheckbox(Zend_Config $cfg)
{
$env = $cfg->get('environment', 'development');
$this->addElement(
'checkbox',
'environment',
array(
'label' => 'Development Mode',
'required' => true,
'helptext' => 'Set true to show more detailed errors and disable certain optimizations in order to '
. 'make debugging easier.',
'tooltip' => 'More verbose output',
'value' => $env === 'development'
)
);
}
/** /**
* Add a select field for setting the default language * Add a select field for setting the default language
* *
@ -229,16 +171,6 @@ class GeneralForm extends Form
*/ */
private function addModuleSettings(Zend_Config $cfg) private function addModuleSettings(Zend_Config $cfg)
{ {
$this->addElement(
'text',
'module_folder',
array(
'label' => 'Module Folder',
'required' => true,
'helptext' => 'The directory that contains the symlink to all enabled directories.',
'value' => $cfg->get('moduleFolder', $this->getConfigDir() . '/config/enabledModules')
)
);
$this->addElement( $this->addElement(
'text', 'text',
'module_path', 'module_path',
@ -253,51 +185,6 @@ class GeneralForm extends Form
); );
} }
/**
* Add text fields for the date and time format used in the application
*
* @param Zend_Config $cfg The "global" section of the config.ini
*/
private function addDateFormatSettings(Zend_Config $cfg)
{
$phpUrl = '<a href="http://php.net/manual/en/function.date.php" target="_new">'
. 'the official PHP documentation</a>';
$dateFormatValue = $this->getRequest()->getParam('date_format', '');
if (empty($dateFormatValue)) {
$dateFormatValue = $cfg->get('dateFormat', 'd/m/Y');
}
$txtDefaultDateFormat = new Zend_Form_Element_Text(
array(
'name' => 'date_format',
'label' => 'Date Format',
'helptext' => 'Display dates according to this format. (See ' . $phpUrl . ' for possible values.) '
. 'Example result: ' . $this->getDateFormatter()->format(time(), $dateFormatValue),
'required' => true,
'value' => $dateFormatValue
)
);
$this->addElement($txtDefaultDateFormat);
$txtDefaultDateFormat->addValidator(new DateFormatValidator());
$timeFormatValue = $this->getRequest()->getParam('time_format', '');
if (empty($timeFormatValue)) {
$timeFormatValue = $cfg->get('timeFormat', 'g:i A');
}
$txtDefaultTimeFormat = new Zend_Form_Element_Text(
array(
'name' => 'time_format',
'label' => 'Time Format',
'required' => true,
'helptext' => 'Display times according to this format. (See ' . $phpUrl . ' for possible values.) '
. 'Example result: ' . $this->getDateFormatter()->format(time(), $timeFormatValue),
'value' => $timeFormatValue
)
);
$txtDefaultTimeFormat->addValidator(new TimeFormatValidator());
$this->addElement($txtDefaultTimeFormat);
}
/** /**
* Add form elements for setting the user preference storage backend * Add form elements for setting the user preference storage backend
* *
@ -324,16 +211,6 @@ class GeneralForm extends Form
) )
); );
$txtPreferencesIniPath = new Zend_Form_Element_Text(
array(
'name' => 'preferences_ini_path',
'label' => 'User Preference Filepath',
'required' => $backend === 'ini',
'condition' => $backend === 'ini',
'value' => $cfg->get('config_path')
)
);
$backends = array(); $backends = array();
foreach ($this->getResources() as $name => $resource) { foreach ($this->getResources() as $name => $resource) {
if ($resource['type'] !== 'db') { if ($resource['type'] !== 'db') {
@ -354,11 +231,8 @@ class GeneralForm extends Form
); );
$validator = new WritablePathValidator(); $validator = new WritablePathValidator();
$validator->setRequireExistence(); $validator->setRequireExistence();
$txtPreferencesIniPath->addValidator($validator);
$this->addElement($txtPreferencesIniPath);
$this->addElement($txtPreferencesDbResource); $this->addElement($txtPreferencesDbResource);
$txtPreferencesIniPath->addDecorator(new ConditionalHidden());
$txtPreferencesDbResource->addDecorator(new ConditionalHidden()); $txtPreferencesDbResource->addDecorator(new ConditionalHidden());
$this->enableAutoSubmit( $this->enableAutoSubmit(
array( array(
@ -384,24 +258,12 @@ class GeneralForm extends Form
$preferences = new Zend_Config(array()); $preferences = new Zend_Config(array());
} }
$this->setName('form_config_general'); $this->setName('form_config_general');
$this->addDevelopmentCheckbox($global);
$this->addLanguageSelection($global); $this->addLanguageSelection($global);
$this->addTimezoneSelection($global); $this->addTimezoneSelection($global);
$this->addModuleSettings($global); $this->addModuleSettings($global);
$this->addDateFormatSettings($global);
$this->addUserPreferencesDialog($preferences); $this->addUserPreferencesDialog($preferences);
$this->addElement( $this->setSubmitLabel('Save Changes');
'button',
'btn_submit',
array(
'type' => 'submit',
'escape' => false,
'value' => '1',
'label' => $this->getView()->icon('save.png', 'Save Changes')
. ' Save changes',
)
);
} }
/** /**
@ -421,23 +283,14 @@ class GeneralForm extends Form
$values = $this->getValues(); $values = $this->getValues();
$cfg = clone $config; $cfg = clone $config;
$cfg->global->environment = ($values['environment'] == 1) ? 'development' : 'production';
$cfg->global->language = $values['language']; $cfg->global->language = $values['language'];
$cfg->global->timezone = $values['timezone']; $cfg->global->timezone = $values['timezone'];
$cfg->global->moduleFolder = $values['module_folder'];
$cfg->global->modulePath = $values['module_path']; $cfg->global->modulePath = $values['module_path'];
$cfg->global->dateFormat = $values['date_format'];
$cfg->global->timeFormat = $values['time_format'];
$cfg->preferences->type = $values['preferences_type']; $cfg->preferences->type = $values['preferences_type'];
if ($cfg->preferences->type === 'ini') { if ($cfg->preferences->type === 'db') {
$cfg->preferences->config_path = $values['preferences_ini_path'];
} elseif ($cfg->preferences->type === 'db') {
$cfg->preferences->resource = $values['preferences_db_resource']; $cfg->preferences->resource = $values['preferences_db_resource'];
} }
return $cfg; return $cfg;
} }
} }
// @codeCoverageIgnoreEnd

View File

@ -1,5 +1,4 @@
<?php <?php
// @codeCoverageIgnoreStart
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
/** /**
* This file is part of Icinga Web 2. * This file is part of Icinga Web 2.
@ -202,4 +201,3 @@ class LoggingForm extends Form
return new Zend_Config($cfg); return new Zend_Config($cfg);
} }
} }
// @codeCoverageIgnoreEnd

View File

@ -1,5 +1,4 @@
<?php <?php
// @codeCoverageIgnoreStart
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
/** /**
* This file is part of Icinga Web 2. * This file is part of Icinga Web 2.
@ -165,4 +164,3 @@ class AddUrlForm extends Form
$this->setSubmitLabel("Add To Dashboard"); $this->setSubmitLabel("Add To Dashboard");
} }
} }
// @codeCoverageIgnoreEnd

View File

@ -1,5 +1,4 @@
<?php <?php
// @codeCoverageIgnoreStart
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
@ -64,4 +63,3 @@ class LoggingPage extends Page
return $this->createForm()->getConfig(); return $this->createForm()->getConfig();
} }
} }
// @codeCoverageIgnoreEnd

View File

@ -1,5 +1,4 @@
<?php <?php
// @codeCoverageIgnoreStart
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
/** /**
* This file is part of Icinga Web 2. * This file is part of Icinga Web 2.
@ -45,36 +44,6 @@ use \Icinga\Util\Translator;
*/ */
class GeneralForm extends Form class GeneralForm extends Form
{ {
/**
* The view helper to format date/time strings
*
* @var Zend_View_Helper_DateFormat
*/
private $dateHelper;
/**
* Return the view helper to format date/time strings
*
* @return Zend_View_Helper_DateFormat
*/
public function getDateFormatter()
{
if ($this->dateHelper === null) {
return $this->getView()->dateFormat();
}
return $this->dateHelper;
}
/**
* Set the view helper that is used to format date/time strings (used for testing)
*
* @param Zend_View_Helper_DateFormat $dateHelper
*/
public function setDateFormatter(Zend_View_Helper_DateFormat $dateHelper)
{
$this->dateHelper = $dateHelper;
}
/** /**
* Add a select field for setting the user's language * Add a select field for setting the user's language
* *
@ -160,84 +129,6 @@ class GeneralForm extends Form
$this->enableAutoSubmit(array('default_timezone')); $this->enableAutoSubmit(array('default_timezone'));
} }
/**
* Add text fields for the date and time format used for this user
*
* Also, a 'use default format' checkbox is added in order to allow a user to discard his overwritten setting
*
* @param Zend_Config $cfg The "global" section of the config.ini to be used as default values
*/
private function addDateFormatSettings(Zend_Config $cfg)
{
$prefs = $this->getUserPreferences();
$useGlobalDateFormat = $this->getRequest()->getParam('default_date_format', !$prefs->has('app.dateFormat'));
$useGlobalTimeFormat = $this->getRequest()->getParam('default_time_format', !$prefs->has('app.timeFormat'));
$phpUrl = '<a href="http://php.net/manual/en/function.date.php" target="_new">'
. 'the official PHP documentation</a>';
$this->addElement(
'checkbox',
'default_date_format',
array(
'label' => 'Use Default Date Format',
'value' => $useGlobalDateFormat,
'required' => true
)
);
$dateFormatValue = $this->getRequest()->getParam('date_format', '');
if (empty($dateFormatValue)) {
$dateFormatValue = $prefs->get('app.dateFormat', $cfg->get('dateFormat', 'd/m/Y'));
}
$txtDefaultDateFormat = new Zend_Form_Element_Text(
array(
'name' => 'date_format',
'label' => 'Preferred Date Format',
'helptext' => 'Display dates according to this format. (See ' . $phpUrl . ' for possible values.) '
. 'Example result: ' . $this->getDateFormatter()->format(time(), $dateFormatValue),
'required' => !$useGlobalDateFormat,
'value' => $dateFormatValue
)
);
$this->addElement($txtDefaultDateFormat);
$txtDefaultDateFormat->addValidator(new DateFormatValidator());
if ($useGlobalDateFormat) {
$txtDefaultDateFormat->setAttrib('disabled', '1');
}
$this->addElement(
'checkbox',
'default_time_format',
array(
'label' => 'Use Default Time Format',
'value' => $useGlobalTimeFormat,
'required' => !$useGlobalTimeFormat
)
);
$timeFormatValue = $this->getRequest()->getParam('time_format', '');
if (empty($timeFormatValue)) {
$timeFormatValue = $prefs->get('app.timeFormat', $cfg->get('timeFormat', 'g:i A'));
}
$txtDefaultTimeFormat = new Zend_Form_Element_Text(
array(
'name' => 'time_format',
'label' => 'Preferred Time Format',
'required' => !$useGlobalTimeFormat,
'helptext' => 'Display times according to this format. (See ' . $phpUrl . ' for possible values.) '
. 'Example result: ' . $this->getDateFormatter()->format(time(), $timeFormatValue),
'value' => $timeFormatValue
)
);
$txtDefaultTimeFormat->addValidator(new TimeFormatValidator());
$this->addElement($txtDefaultTimeFormat);
if ($useGlobalTimeFormat) {
$txtDefaultTimeFormat->setAttrib('disabled', '1');
}
$this->enableAutoSubmit(array('default_time_format', 'default_date_format'));
}
/** /**
* Create the general form, using the global configuration as fallback values for preferences * Create the general form, using the global configuration as fallback values for preferences
* *
@ -255,7 +146,6 @@ class GeneralForm extends Form
$this->addLanguageSelection($global); $this->addLanguageSelection($global);
$this->addTimezoneSelection($global); $this->addTimezoneSelection($global);
$this->addDateFormatSettings($global);
$this->setSubmitLabel('Save Changes'); $this->setSubmitLabel('Save Changes');
@ -280,10 +170,7 @@ class GeneralForm extends Form
return array( return array(
'app.language' => $values['default_language'] ? null : $values['language'], 'app.language' => $values['default_language'] ? null : $values['language'],
'app.timezone' => $values['default_timezone'] ? null : $values['timezone'], 'app.timezone' => $values['default_timezone'] ? null : $values['timezone'],
'app.dateFormat' => $values['default_date_format'] ? null : $values['date_format'],
'app.timeFormat' => $values['default_time_format'] ? null : $values['time_format'],
'app.show_benchmark' => $values['show_benchmark'] === '1' ? true : false 'app.show_benchmark' => $values['show_benchmark'] === '1' ? true : false
); );
} }
} }
// @codeCoverageIgnoreEnd

View File

@ -16,6 +16,11 @@ if ($moduleName) {
$moduleClass = ''; $moduleClass = '';
} }
$refresh = '';
if ($this->layout()->autorefreshInterval) {
$refresh = ' data-icinga-refresh="' . $this->layout()->autorefreshInterval . '"';
}
$notifications = Notification::getInstance(); $notifications = Notification::getInstance();
if ($notifications->hasMessages()) { if ($notifications->hasMessages()) {
foreach ($notifications->getMessages() as $m) { foreach ($notifications->getMessages() as $m) {
@ -23,15 +28,17 @@ if ($notifications->hasMessages()) {
} }
} }
?></ul> ?></ul>
<div id="logo" data-base-target="_main"><a href="<?= $this->href('/dashboard') ?>"><img src="<?= $this->href('img/logo_icinga-inv.png') ?>" class="logo" /></a> <div id="logo" data-base-target="_main"><a href="<?= $this->href('/dashboard') ?>"><img src="<?= $this->href('img/logo_icinga-inv.png') ?>" class="logo" alt="<?= t('Dashboard') ?>" /></a>
</div> </div>
</div> </div>
<?php if (!$this->layout()->isIframe): ?>
<div id="sidebar"> <div id="sidebar">
<?php echo $this->render('parts/navigation.phtml') ?> <?php echo $this->render('parts/navigation.phtml') ?>
</div> </div>
<div id="main"> <?php endif ?>
<div id="col1" class="container<?= $moduleClass ?>"<?php if ($moduleName): ?> data-icinga-module="<?= $moduleName ?>" <?php endif ?> data-icinga-url="<?= Url::fromRequest() ?>" style="display: block"> <div id="main" role="main">
<div id="col1" class="container<?= $moduleClass ?>"<?php if ($moduleName): ?> data-icinga-module="<?= $moduleName ?>" <?php endif ?> data-icinga-url="<?= Url::fromRequest() ?>"<?= $refresh ?> style="display: block">
<?= $this->render('inline.phtml') ?> <?= $this->render('inline.phtml') ?>
</div> </div>
<div id="col2" class="container"> <div id="col2" class="container">

View File

@ -1,6 +1,7 @@
<?php <?php
use Icinga\Web\JavaScript; use Icinga\Web\JavaScript;
use Icinga\Util\Translator;
if (array_key_exists('_dev', $_GET)) { if (array_key_exists('_dev', $_GET)) {
$jsfile = 'js/icinga.dev.js'; $jsfile = 'js/icinga.dev.js';
@ -11,15 +12,15 @@ if (array_key_exists('_dev', $_GET)) {
} }
$ie8jsfile = 'js/icinga.ie8.js'; $ie8jsfile = 'js/icinga.ie8.js';
$lang = Translator::splitLocaleCode()->language;
$isIframe = isset($_GET['_render']) && $_GET['_render'] === 'iframe'; $isIframe = $this->layout()->isIframe;
$iframeClass = $isIframe ? ' iframe' : ''; $iframeClass = $isIframe ? ' iframe' : '';
?><!DOCTYPE html> ?><!DOCTYPE html>
<!--[if IE 8]> <!--[if IE 8]>
<html class="no-js ie8<?= $iframeClass ?>"> <![endif]--> <html class="no-js ie8<?= $iframeClass ?>" lang="<?= $lang ?>"> <![endif]-->
<!--[if gt IE 8]><!--> <!--[if gt IE 8]><!-->
<html class="no-js<?= $iframeClass ?>"> <!--<![endif]--> <html class="no-js<?= $iframeClass ?>" lang="<?= $lang ?>"> <!--<![endif]-->
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta content="width=320; initial-scale=1.0; maximum-scale=1.0; user-scalable=0;"/> <meta content="width=320; initial-scale=1.0; maximum-scale=1.0; user-scalable=0;"/>
@ -29,15 +30,16 @@ $iframeClass = $isIframe ? ' iframe' : '';
<!-- TODO: viewport and scale settings make no sense for us, fix this --> <!-- TODO: viewport and scale settings make no sense for us, fix this -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="stylesheet" href="<?= $this->href($cssfile) ?>" media="screen" type="text/css" /> <link rel="stylesheet" href="<?= $this->href($cssfile) ?>" media="screen" type="text/css" />
<? if ($isIframe): ?>
<base target="_parent"/>
<?php else: ?>
<script type="text/javascript"> <script type="text/javascript">
(function() { (function() {
var html = document.getElementsByTagName('html')[0]; var html = document.getElementsByTagName('html')[0];
html.className = html.className.replace(/no-js/, 'js'); html.className = html.className.replace(/no-js/, 'js');
}()); }());
</script> </script>
<? if ($isIframe): ?> <?php endif ?>
<base target="_parent"/>
<? endif ?>
<!-- Respond.js IE8 support of media queries --> <!-- Respond.js IE8 support of media queries -->
<!--[if lt IE 9]> <!--[if lt IE 9]>
<script src="<?= $this->baseUrl('js/vendor/respond.min.js');?>"></script> <script src="<?= $this->baseUrl('js/vendor/respond.min.js');?>"></script>
@ -49,6 +51,7 @@ $iframeClass = $isIframe ? ' iframe' : '';
<div id="layout" class="default-layout"> <div id="layout" class="default-layout">
<?= $this->render('body.phtml') ?> <?= $this->render('body.phtml') ?>
</div> </div>
<?php if (! $isIframe): ?>
<!--[if IE 8]> <!--[if IE 8]>
<script type="text/javascript" src="<?= $this->href($ie8jsfile) ?>"></script> <script type="text/javascript" src="<?= $this->href($ie8jsfile) ?>"></script>
<![endif]--> <![endif]-->
@ -60,5 +63,6 @@ var icinga = new Icinga({
baseUrl: '<?= $this->href('/') ?>' baseUrl: '<?= $this->href('/') ?>'
}); });
</script> </script>
<?php endif ?>
</body> </body>
</html> </html>

View File

@ -1,4 +1,9 @@
<ul> <?php
if (! $this->level) {
$this->level = 0;
}
?>
<ul<?= $this->level === 0 ? ' role="navigation"' : '' ?>>
<?php <?php
foreach ($this->items as $item) { foreach ($this->items as $item) {
@ -17,7 +22,7 @@ foreach ($this->items as $item) {
if ($item->hasChildren()) { if ($item->hasChildren()) {
echo $this->partial( echo $this->partial(
'parts/menu.phtml', 'parts/menu.phtml',
array('items' => $item->getChildren(), 'url' => $this->url) array('items' => $item->getChildren(), 'url' => $this->url, 'level' => $this->level + 1)
); );
} }

View File

@ -9,11 +9,11 @@ if (! $this->auth()->isAuthenticated()) {
} }
// Current url // Current url
$url = Url::fromRequest()->remove('_render')->getRelativeUrl(); $url = Url::fromRequest()->getRelativeUrl();
$menu = Menu::fromConfig(); $menu = Menu::fromConfig();
?> ?>
<div id="menu" data-base-target="_main"> <div id="menu" data-base-target="_main">
<form action="<?= $this->href('search') ?>" method="get"> <form action="<?= $this->href('search') ?>" method="get" role="search">
<input type="text" name="q" class="search autofocus" placeholder="<?= $this->translate('Search...') ?>" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" /> <input type="text" name="q" class="search autofocus" placeholder="<?= $this->translate('Search...') ?>" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" />
</form> </form>
<?= $this->partial('parts/menu.phtml', array( <?= $this->partial('parts/menu.phtml', array(

View File

@ -1,5 +1,4 @@
<?php <?php
// @codeCoverageIgnoreStart
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
/** /**
* This file is part of Icinga Web 2. * This file is part of Icinga Web 2.
@ -94,4 +93,3 @@ class Zend_View_Helper_FormDateTime extends Zend_View_Helper_FormElement
return $xhtml; return $xhtml;
} }
} }
// @codeCoverageIgnoreEnd

View File

@ -1,5 +1,4 @@
<?php <?php
// @codeCoverageIgnoreStart
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
/** /**
* This file is part of Icinga Web 2. * This file is part of Icinga Web 2.
@ -54,4 +53,3 @@ class Zend_View_Helper_FormNumber extends \Zend_View_Helper_FormText
. $this->getClosingBracket(); . $this->getClosingBracket();
} }
} }
// @codeCoverageIgnoreEnd

View File

@ -1,5 +1,4 @@
<?php <?php
// @codeCoverageIgnoreStart
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
/** /**
* This file is part of Icinga Web 2. * This file is part of Icinga Web 2.
@ -63,4 +62,3 @@ class Zend_View_Helper_FormTriStateCheckbox extends Zend_View_Helper_FormElement
return $xhtml . '</div>'; return $xhtml . '</div>';
} }
} }
// @codeCoverageIgnoreEnd

View File

@ -1,6 +1,4 @@
<?php <?php
// @codingStandardsIgnoreStart
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
/** /**
* This file is part of Icinga Web 2. * This file is part of Icinga Web 2.
@ -142,5 +140,3 @@ class Zend_View_Helper_Util extends Zend_View_Helper_Abstract
return sprintf('OUT OF BOUND (%d)' . $state, (int) $state); return sprintf('OUT OF BOUND (%d)' . $state, (int) $state);
} }
} }
// @codingStandardsIgnoreStop

View File

@ -1,7 +1,7 @@
<div id="login"> <div id="login">
<div class="logo"> <div class="logo">
<div class="image"> <div class="image">
<img src="<?= $this->baseUrl('img/logo_icinga_big.png') ?>" > <img src="<?= $this->baseUrl('img/logo_icinga_big.png') ?>" alt="<?= t('The Icinga logo') ?>" >
</div> </div>
</div> </div>
<div class="form" data-base-target="layout"> <div class="form" data-base-target="layout">

View File

@ -0,0 +1,6 @@
<div class="controls">
<?= $this->tabs ?>
</div>
<table class="avp">
<tr><th><?= $this->translate('UI Debug') ?></th><td><a href="javascript:void(0);" onclick="icinga.ui.toggleDebug();"><?= $this->translate('toggle') ?></td></tr>
</table>

View File

@ -9,6 +9,7 @@
$dependencies = $module->getDependencies(); $dependencies = $module->getDependencies();
$restrictions = $module->getProvidedRestrictions(); $restrictions = $module->getProvidedRestrictions();
$permissions = $module->getProvidedPermissions(); $permissions = $module->getProvidedPermissions();
$state = $moduleData->enabled ? ($moduleData->loaded ? 'enabled' : 'failed') : 'disabled'
?> ?>
<h1><?= $this->escape($module->getTitle()) ?></h1> <h1><?= $this->escape($module->getTitle()) ?></h1>
@ -17,6 +18,19 @@ $permissions = $module->getProvidedPermissions();
<th><?= $this->escape('Name') ?></th> <th><?= $this->escape('Name') ?></th>
<td><?= $this->escape($module->getName()) ?></td> <td><?= $this->escape($module->getName()) ?></td>
</tr> </tr>
<tr>
<th><?= $this->translate('State') ?></th>
<td><?= $state ?><?php if ($state === 'enabled'): ?>
<?= $this->qlink('disable', 'config/moduledisable', array(
'name' => $module->getName()
)) ?>
<?php endif ?>
<?php if ($state === 'disabled'): ?>
<?= $this->qlink('enable', 'config/moduleenable', array(
'name' => $module->getName()
)) ?>
<?php endif ?>
</td>
<tr> <tr>
<th><?= $this->escape('Version') ?></th> <th><?= $this->escape('Version') ?></th>
<td><?= $this->escape($module->getVersion()) ?></td></tr> <td><?= $this->escape($module->getVersion()) ?></td></tr>

View File

@ -1,46 +0,0 @@
<?php
use Icinga\Web\Url;
$this->modules->limit(10);
$modules = $this->modules->paginate();
?>
<div class="controls">
<?= $this->tabs->render($this); ?>
</div>
<div class="content">
<h3>Installed Modules</h3>
<?php if (isset($this->messageBox)): ?>
<?= $this->messageBox->render() ?>
<?php endif ?>
<?= $this->paginationControl($modules, null, null, array(
'preserve' => $this->preserve
));
?>
<table class="action">
<tbody>
<? foreach ($modules as $module): ?>
<?php
$enableUrl = Url::fromPath('config/moduleenable/',array('name' => $module->name))->getAbsoluteUrl();
$disableUrl = Url::fromPath('config/moduledisable/',array('name' => $module->name))->getAbsoluteUrl();
?>
<tr>
<td>
<? if ($module->enabled): ?>
<i class="icinga-icon-success"></i>
<a href="<?= $disableUrl ?>" data-icinga-target="main"><?= $this->escape($module->name); ?></a>
<? else: ?>
<i class="icinga-icon-remove"></i>
<a href="<?= $enableUrl ?>" data-icinga-target="main"><?= $this->escape($module->name); ?></a>
<? endif ?>
(<?=
$module->enabled ? ($module->loaded ? 'enabled' : 'failed') : 'disabled' ?>)
</td>
</tr>
<? endforeach ?>
</tbody>
</table>
</div>

View File

@ -1,25 +1,10 @@
<?php
use Icinga\Web\Url;
$this->modules->limit(10);
$modules = $this->modules->paginate();
?>
<div class="controls"> <div class="controls">
<?= $this->tabs->render($this); ?> <?= $this->tabs ?>
<h1>Installed Modules</h1>
<?= $this->paginationControl($modules) ?>
</div> </div>
<div class="content"> <div class="content">
<h3>Installed Modules</h3>
<?php if (isset($this->messageBox)): ?>
<?= $this->messageBox->render() ?>
<?php endif ?>
<?= $this->paginationControl($modules, null, null, array(
'preserve' => $this->preserve
));
?>
<table class="action" data-base-target="_next"> <table class="action" data-base-target="_next">
<tbody> <tbody>
<?php foreach ($modules as $module): ?> <?php foreach ($modules as $module): ?>
@ -30,9 +15,12 @@ $modules = $this->modules->paginate();
<?php else: ?> <?php else: ?>
<?= $this->icon('remove.png', 'Module is disabled') ?> <?= $this->icon('remove.png', 'Module is disabled') ?>
<? endif ?> <? endif ?>
<a href="<?= $this->url('config/module/', array('name' => $module->name)) ?>"><?= $this->escape($module->name); ?></a> <a href="<?= $this->url(
(<?= 'config/module/',
$module->enabled ? ($module->loaded ? 'enabled' : 'failed') : 'disabled' ?>) array('name' => $module->name)
) ?>"><?= $this->escape($module->name); ?></a> (<?=
$module->enabled ? ($module->loaded ? 'enabled' : 'failed') : 'disabled'
?>)
</td> </td>
</tr> </tr>
<? endforeach ?> <? endforeach ?>

View File

@ -4,7 +4,9 @@
</div> </div>
<?php endif ?> <?php endif ?>
<div class="content"> <div class="content">
<p><strong><?= $this->escape($message) ?></strong></p> <?php if ($this->message): ?>
<p><strong><?= nl2br($this->escape($message)) ?></strong></p>
<?php endif ?>
<?php if (isset($stackTrace)) : ?> <?php if (isset($stackTrace)) : ?>
<hr /> <hr />
<pre><?= $this->escape($stackTrace) ?></pre> <pre><?= $this->escape($stackTrace) ?></pre>

View File

@ -1,2 +1,2 @@
<h1>Welcome to Icinga!</h1> <h1>Welcome to Icinga!</h1>
You should install/configure some <a href="<?= $this->href('config/moduleoverview');?>">modules</a> now! You should install/configure some <a href="<?= $this->href('config/modules');?>">modules</a> now!

View File

@ -9,10 +9,11 @@ use Icinga\Web\Url;
if ($this->pageCount <= 1) return; if ($this->pageCount <= 1) return;
?><ul class="pagination" ?><p id="paginationlabel" class="audible"><?= t('Pagination') ?></p>
<ul class="pagination" aria-labelledby="paginationlabel" role="navigation"
<?php <?php
$fromto = t('%d to %d of %d'); $fromto = t('Show rows %d to %d of %d');
$total = $this->totalItemCount; $total = $this->totalItemCount;
$limit = $this->itemCountPerPage; $limit = $this->itemCountPerPage;
$title_prev = sprintf( $title_prev = sprintf(
@ -28,7 +29,9 @@ $title_next = sprintf(
($this->current + 1) * $limit, ($this->current + 1) * $limit,
$total $total
); );
$li = ' ><li%s><a href="%s" title="%s">%s</a></li $li = ' ><li%s><span class="audible">'
. t('Page')
. ' </span><a href="%s" title="%s">%s</a></li
'; ';
?> ?>
@ -65,7 +68,7 @@ foreach ($this->pagesInRange as $page) {
$class, $class,
Url::fromRequest()->overwriteParams( Url::fromRequest()->overwriteParams(
array('page' => $page) array('page' => $page)
)->getAbsoluteUrl(), ),
$title, $title,
$page $page
); );

View File

@ -1,8 +1,5 @@
[global] [global]
environment = "development"
timezone = "Europe/Berlin" timezone = "Europe/Berlin"
indexModule = "monitoring"
indexController = "dashboard"
dateFormat = "d/m/Y" dateFormat = "d/m/Y"
timeFormat = "g:i A" timeFormat = "g:i A"
@ -40,7 +37,6 @@ level = 1
[preferences] [preferences]
; Use INI file storage to save preferences to a local disk ; Use INI file storage to save preferences to a local disk
type = "ini" type = "ini"
config_path = "@icingaweb_config_path@/preferences"
; Use database storage to save preferences in either a MySQL or PostgreSQL database ; Use database storage to save preferences in either a MySQL or PostgreSQL database
;type = db ;type = db

View File

@ -43,7 +43,7 @@ priority = 50
[Overview.Servicematrix] [Overview.Servicematrix]
title = "Servicematrix" title = "Servicematrix"
url = "monitoring/list/servicematrix" url = "monitoring/list/servicematrix?service_problem=1"
priority = 51 priority = 51
[Overview.Servicegroups] [Overview.Servicegroups]
@ -85,7 +85,7 @@ url = "monitoring/list/notifications"
[History.Events] [History.Events]
title = "All Events" title = "All Events"
url = "monitoring/list/eventhistory?raw_timestamp>=-2+days" url = "monitoring/list/eventhistory?timestamp>=-7%20days"
[History.Timeline] [History.Timeline]
title = "Timeline" title = "Timeline"

View File

@ -1,30 +0,0 @@
README
======
This directory should be used to place project specfic documentation including
but not limited to project notes, generated API/phpdoc documentation, or
manual files generated or hand written. Ideally, this directory would remain
in your development environment only and should not be deployed with your
application to it's final production location.
Setting Up Your VHOST
=====================
The following is a sample VHOST you might want to consider for your project.
<VirtualHost *:80>
DocumentRoot "/var/www/net-test-icinga-vm1.adm.netways.de/public"
ServerName net-test-icinga-vm1.adm.netways.de.local
# This should be omitted in the production environment
SetEnv APPLICATION_ENV development
<Directory "/var/www/net-test-icinga-vm1.adm.netways.de/public">
Options Indexes MultiViews FollowSymLinks
AllowOverride All
Order allow,deny
Allow from all
</Directory>
</VirtualHost>

View File

@ -57,7 +57,7 @@ database test looks like this:
/** /**
* @dataProvider mysqlDb * @dataProvider mysqlDb
* @param Icinga\Data\Db\Connection $mysqlDb * @param Icinga\Data\Db\DbConnection $mysqlDb
*/ */
public function testSomethingWithMySql($mysqlDb) public function testSomethingWithMySql($mysqlDb)
{ {

View File

@ -1,5 +1,4 @@
<?php <?php
// @codeCoverageIgnoreStart
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
/** /**
* This file is part of Icinga Web 2. * This file is part of Icinga Web 2.
@ -151,7 +150,8 @@ abstract class ApplicationBootstrap
$configDir = '/etc/icingaweb'; $configDir = '/etc/icingaweb';
} }
} }
$this->configDir = realpath($configDir); $canonical = realpath($configDir);
$this->configDir = $canonical ? $canonical : $configDir;
$this->setupAutoloader(); $this->setupAutoloader();
$this->setupZendAutoloader(); $this->setupZendAutoloader();
@ -457,7 +457,11 @@ abstract class ApplicationBootstrap
*/ */
protected function setupTimezone() protected function setupTimezone()
{ {
$timeZoneString = $this->config->global !== null ? $this->config->global->get('timezone', 'UTC') : 'UTC'; $default = @date_default_timezone_get();
if (! $default) {
$default = 'UTC';
}
$timeZoneString = $this->config->global !== null ? $this->config->global->get('timezone', $default) : $default;
date_default_timezone_set($timeZoneString); date_default_timezone_set($timeZoneString);
DateTimeFactory::setConfig(array('timezone' => $timeZoneString)); DateTimeFactory::setConfig(array('timezone' => $timeZoneString));
return $this; return $this;
@ -466,27 +470,27 @@ abstract class ApplicationBootstrap
/** /**
* Setup internationalization using gettext * Setup internationalization using gettext
* *
* Uses the language defined in the global config or the default one * Uses the preferred language sent by the browser or the default one
* *
* @return self * @return self
*/ */
protected function setupInternationalization() protected function setupInternationalization()
{ {
$localeDir = $this->getApplicationDir('locale');
if (file_exists($localeDir) && is_dir($localeDir)) {
Translator::registerDomain(Translator::DEFAULT_DOMAIN, $localeDir);
}
try { try {
Translator::setupLocale( Translator::setupLocale(
$this->config->global !== null ? $this->config->global->get('language', Translator::DEFAULT_LOCALE) isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])
? Translator::getPreferredLocaleCode($_SERVER['HTTP_ACCEPT_LANGUAGE'])
: Translator::DEFAULT_LOCALE : Translator::DEFAULT_LOCALE
); );
} catch (Exception $error) { } catch (Exception $error) {
Logger::error($error); Logger::error($error);
} }
$localeDir = $this->getApplicationDir('locale');
if (file_exists($localeDir) && is_dir($localeDir)) {
Translator::registerDomain(Translator::DEFAULT_DOMAIN, $localeDir);
}
return $this; return $this;
} }
} }
// @codeCoverageIgnoreEnd

View File

@ -1,5 +1,4 @@
<?php <?php
// @codeCoverageIgnoreStart
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
/** /**
* This file is part of Icinga Web 2. * This file is part of Icinga Web 2.
@ -318,4 +317,3 @@ class Benchmark
{ {
} }
} }
// @codeCoverageIgnoreEnd

View File

@ -1,5 +1,4 @@
<?php <?php
// @codeCoverageIgnoreStart
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
/** /**
* This file is part of Icinga Web 2. * This file is part of Icinga Web 2.
@ -191,4 +190,3 @@ class Cli extends ApplicationBootstrap
throw new ProgrammingError('Icinga is not running on CLI'); throw new ProgrammingError('Icinga is not running on CLI');
} }
} }
// @codeCoverageIgnoreEnd

View File

@ -165,10 +165,6 @@ class Config extends Zend_Config
*/ */
public static function resolvePath($path) public static function resolvePath($path)
{ {
if (strpos($path, DIRECTORY_SEPARATOR) === 0) { return self::$configDir . DIRECTORY_SEPARATOR . ltrim($path, DIRECTORY_SEPARATOR);
return $path;
}
return self::$configDir . DIRECTORY_SEPARATOR . $path;
} }
} }

View File

@ -1,5 +1,4 @@
<?php <?php
// @codeCoverageIgnoreStart
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
/** /**
* This file is part of Icinga Web 2. * This file is part of Icinga Web 2.
@ -30,9 +29,7 @@
namespace Icinga\Application; namespace Icinga\Application;
// @codingStandardsIgnoreStart
require_once dirname(__FILE__) . '/ApplicationBootstrap.php'; require_once dirname(__FILE__) . '/ApplicationBootstrap.php';
// @codingStandardsIgnoreStop
use Icinga\Exception\ProgrammingError; use Icinga\Exception\ProgrammingError;
@ -62,4 +59,3 @@ class EmbeddedWeb extends ApplicationBootstrap
->loadEnabledModules(); ->loadEnabledModules();
} }
} }
// @codeCoverageIgnoreEnd

View File

@ -1,5 +1,4 @@
<?php <?php
// @codeCoverageIgnoreStart
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
/** /**
* This file is part of Icinga Web 2. * This file is part of Icinga Web 2.
@ -56,4 +55,3 @@ class LegacyWeb extends Web
return $this->legacyBasedir; return $this->legacyBasedir;
} }
} }
// @codeCoverageIgnoreEnd

View File

@ -32,8 +32,8 @@ namespace Icinga\Application\Modules;
use Icinga\Application\ApplicationBootstrap; use Icinga\Application\ApplicationBootstrap;
use Icinga\Application\Icinga; use Icinga\Application\Icinga;
use Icinga\Logger\Logger; use Icinga\Logger\Logger;
use Icinga\Data\DataArray\Datasource as ArrayDatasource; use Icinga\Data\DataArray\ArrayDatasource;
use Icinga\Data\DataArray\Query as ArrayQuery; use Icinga\Data\SimpleQuery;
use Icinga\Exception\ConfigurationError; use Icinga\Exception\ConfigurationError;
use Icinga\Exception\SystemPermissionException; use Icinga\Exception\SystemPermissionException;
use Icinga\Exception\ProgrammingError; use Icinga\Exception\ProgrammingError;
@ -110,7 +110,7 @@ class Manager
/** /**
* Query interface for the module manager * Query interface for the module manager
* *
* @return ArrayQuery * @return SimpleQuery
*/ */
public function select() public function select()
{ {

View File

@ -18,6 +18,9 @@ use Icinga\Application\Icinga;
use Icinga\Logger\Logger; use Icinga\Logger\Logger;
use Icinga\Util\Translator; use Icinga\Util\Translator;
use Icinga\Web\Hook; use Icinga\Web\Hook;
use Icinga\Web\Widget;
use Icinga\Util\File;
use Icinga\Exception\ProgrammingError;
/** /**
* Module handling * Module handling
@ -124,6 +127,13 @@ class Module
*/ */
private $restrictionList = array(); private $restrictionList = array();
/**
* Provided config tabs
*
* @var array
*/
private $configTabs = array();
/** /**
* Icinga application * Icinga application
* *
@ -330,10 +340,9 @@ class Module
if (file_exists($this->metadataFile)) { if (file_exists($this->metadataFile)) {
$fh = fopen($this->metadataFile, 'r');
$key = null; $key = null;
$file = new File($this->metadataFile, 'r');
while (false !== ($line = fgets($fh))) { foreach ($file as $line) {
$line = rtrim($line); $line = rtrim($line);
if ($key === 'description') { if ($key === 'description') {
@ -523,6 +532,27 @@ class Module
return array_key_exists($name, $this->restrictionList); return array_key_exists($name, $this->restrictionList);
} }
/**
* Retrieve this modules configuration tabs
*
* @return Icinga\Web\Widget\Tabs
*/
public function getConfigTabs()
{
$this->launchConfigScript();
$tabs = Widget::create('tabs');
$tabs->add('info', array(
'url' => 'config/module',
'urlParams' => array('name' => $this->getName()),
'title' => 'Module: ' . $this->getName()
));
foreach ($this->configTabs as $name => $config) {
$tabs->add($name, $config);
}
return $tabs;
}
/** /**
* Provide a named permission * Provide a named permission
* *
@ -565,6 +595,24 @@ class Module
); );
} }
/**
* Provide a module config tab
*
* @param string $name Unique tab name
* @param string $config Tab config
*
* @return self
*/
protected function provideConfigTab($name, $config = array())
{
if (! array_key_exists('url', $config)) {
throw new ProgrammingError('A module config tab MUST provide and "url"');
}
$config['url'] = $this->getName() . '/' . ltrim($config['url'], '/');
$this->configTabs[$name] = $config;
return $this;
}
/** /**
* Register new namespaces on the autoloader * Register new namespaces on the autoloader
* *

View File

@ -1,5 +1,4 @@
<?php <?php
// @codeCoverageIgnoreStart
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
/** /**
* This file is part of Icinga Web 2. * This file is part of Icinga Web 2.
@ -93,4 +92,3 @@ class Platform
} }
} }
} }
// @codeCoverageIgnoreEnd

View File

@ -1,5 +1,4 @@
<?php <?php
// @codeCoverageIgnoreStart
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
/** /**
* This file is part of Icinga Web 2. * This file is part of Icinga Web 2.
@ -37,6 +36,7 @@ use Icinga\Exception\ConfigurationError;
use Icinga\Exception\NotReadableError; use Icinga\Exception\NotReadableError;
use Icinga\Logger\Logger; use Icinga\Logger\Logger;
use Icinga\Web\Request; use Icinga\Web\Request;
use Icinga\Web\Response;
use Icinga\Web\View; use Icinga\Web\View;
use Icinga\Web\Session\Session as BaseSession; use Icinga\Web\Session\Session as BaseSession;
use Icinga\Web\Session; use Icinga\Web\Session;
@ -48,7 +48,7 @@ use Exception;
use Zend_Layout; use Zend_Layout;
use Zend_Paginator; use Zend_Paginator;
use Zend_View_Helper_PaginationControl; use Zend_View_Helper_PaginationControl;
use Zend_Controller_Action_HelperBroker; use Zend_Controller_Action_HelperBroker as ActionHelperBroker;
use Zend_Controller_Router_Route; use Zend_Controller_Router_Route;
use Zend_Controller_Front; use Zend_Controller_Front;
@ -124,7 +124,7 @@ class Web extends ApplicationBootstrap
->setupInternationalization() ->setupInternationalization()
->setupRequest() ->setupRequest()
->setupZendMvc() ->setupZendMvc()
->setupFormNamespace() ->setupFormNamespace()
->setupModuleManager() ->setupModuleManager()
->loadEnabledModules() ->loadEnabledModules()
->setupRoute() ->setupRoute()
@ -177,7 +177,7 @@ class Web extends ApplicationBootstrap
*/ */
public function dispatch() public function dispatch()
{ {
$this->frontController->dispatch(); $this->frontController->dispatch($this->request, new Response());
} }
/** /**
@ -274,7 +274,7 @@ class Web extends ApplicationBootstrap
private function setupViewRenderer() private function setupViewRenderer()
{ {
/** @var \Zend_Controller_Action_Helper_ViewRenderer $view */ /** @var \Zend_Controller_Action_Helper_ViewRenderer $view */
$view = Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer'); $view = ActionHelperBroker::getStaticHelper('viewRenderer');
$view->setView(new View()); $view->setView(new View());
$view->view->addHelperPath($this->getApplicationDir('/views/helpers')); $view->view->addHelperPath($this->getApplicationDir('/views/helpers'));
@ -375,4 +375,3 @@ class Web extends ApplicationBootstrap
return $this; return $this;
} }
} }
// @codeCoverageIgnoreEnd

View File

@ -29,24 +29,24 @@
namespace Icinga\Authentication\Backend; namespace Icinga\Authentication\Backend;
use \Exception;
use \Zend_Db_Expr;
use \Zend_Db_Select;
use Icinga\Authentication\UserBackend; use Icinga\Authentication\UserBackend;
use Icinga\Data\Db\Connection; use Icinga\Data\Db\DbConnection;
use Icinga\User; use Icinga\User;
use Icinga\Exception\AuthenticationException; use Icinga\Exception\AuthenticationException;
use Exception;
use Zend_Db_Expr;
use Zend_Db_Select;
class DbUserBackend extends UserBackend class DbUserBackend extends UserBackend
{ {
/** /**
* Connection to the database * Connection to the database
* *
* @var Connection * @var DbConnection
*/ */
private $conn; private $conn;
public function __construct(Connection $conn) public function __construct(DbConnection $conn)
{ {
$this->conn = $conn; $this->conn = $conn;
} }
@ -150,4 +150,4 @@ class DbUserBackend extends UserBackend
return ($row !== false) ? $row->count : 0; return ($row !== false) ? $row->count : 0;
} }
} }

View File

@ -29,11 +29,11 @@
namespace Icinga\Authentication\Backend; namespace Icinga\Authentication\Backend;
use \Exception;
use Icinga\User; use Icinga\User;
use Icinga\Authentication\UserBackend; use Icinga\Authentication\UserBackend;
use Icinga\Protocol\Ldap\Connection; use Icinga\Protocol\Ldap\Connection;
use Icinga\Exception\AuthenticationException; use Icinga\Exception\AuthenticationException;
use Icinga\Protocol\Ldap\Exception as LdapException;
class LdapUserBackend extends UserBackend class LdapUserBackend extends UserBackend
{ {
@ -127,10 +127,11 @@ class LdapUserBackend extends UserBackend
* *
* @param User $user * @param User $user
* @param string $password * @param string $password
* @param boolean $healthCheck Perform additional health checks to generate more useful * @param boolean $healthCheck Perform additional health checks to generate more useful exceptions in case
* exceptions in case of a configuration or backend error * of a configuration or backend error
* *
* @return bool True when the authentication was successful, false when the username or password was invalid * @return bool True when the authentication was successful, false when the username
* or password was invalid
* @throws AuthenticationException When an error occurred during authentication and authentication is not possible * @throws AuthenticationException When an error occurred during authentication and authentication is not possible
*/ */
public function authenticate(User $user, $password, $healthCheck = true) public function authenticate(User $user, $password, $healthCheck = true)
@ -150,14 +151,15 @@ class LdapUserBackend extends UserBackend
); );
} }
} }
if (! $this->hasUser($user)) {
return false;
}
try { try {
$userDn = $this->conn->fetchDN($this->createQuery($user->getUsername())); return $this->conn->testCredentials(
if (!$userDn) { $this->conn->fetchDN($this->createQuery($user->getUsername())),
// User does not exist $password
return false; );
} } catch (LdapException $e) {
return $this->conn->testCredentials($userDn, $password);
} catch (Exception $e) {
// Error during authentication of this specific user // Error during authentication of this specific user
throw new AuthenticationException( throw new AuthenticationException(
sprintf( sprintf(

View File

@ -237,7 +237,9 @@ class Manager
*/ */
public function authenticateFromRemoteUser() public function authenticateFromRemoteUser()
{ {
$this->fromRemoteUser = true; if (array_key_exists('REMOTE_USER', $_SERVER)) {
$this->fromRemoteUser = true;
}
$this->authenticateFromSession(); $this->authenticateFromSession();
if ($this->user !== null) { if ($this->user !== null) {
if (array_key_exists('REMOTE_USER', $_SERVER) && $this->user->getUsername() !== $_SERVER["REMOTE_USER"]) { if (array_key_exists('REMOTE_USER', $_SERVER) && $this->user->getUsername() !== $_SERVER["REMOTE_USER"]) {

View File

@ -1,5 +1,4 @@
<?php <?php
// @codingStandardsIgnoreStart
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
/** /**
* This file is part of Icinga Web 2. * This file is part of Icinga Web 2.
@ -101,14 +100,14 @@ class Inline {
foreach ($this->data as $key => $value) { foreach ($this->data as $key => $value) {
$this->data[$key] = (int)$value; $this->data[$key] = (int)$value;
} }
for ($i = 0; $i < sizeof($this->data); $i++) { for ($i = 0; $i < count($this->data); $i++) {
$this->labels[] = ''; $this->labels[] = '';
} }
if (array_key_exists('colors', $_GET)) { if (array_key_exists('colors', $_GET)) {
$this->colors = $this->sanitizeStringArray(explode(',', $_GET['colors'])); $this->colors = $this->sanitizeStringArray(explode(',', $_GET['colors']));
} }
while (sizeof($this->colors) < sizeof($this->data)) { while (count($this->colors) < count($this->data)) {
$this->colors[] = '#FEFEFE'; $this->colors[] = '#FEFEFE';
} }
@ -120,4 +119,4 @@ class Inline {
} }
} }
} }

View File

@ -1,5 +1,4 @@
<?php <?php
// @codingStandardsIgnoreStart
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
/** /**
* This file is part of Icinga Web 2. * This file is part of Icinga Web 2.

View File

@ -1,543 +0,0 @@
<?php
namespace Icinga\Data;
use Icinga\Logger\Logger;
use Icinga\Exception;
use Icinga\Filter\Filterable;
use Icinga\Filter\Query\Node;
use Icinga\Filter\Query\Tree;
use Zend_Paginator;
use Icinga\Web\Paginator\Adapter\QueryAdapter;
abstract class BaseQuery implements Filterable
{
/**
* Sort ascending
*/
const SORT_ASC = 1;
/**
* Sort descending
*/
const SORT_DESC = -1;
/**
* Query data source
*
* @var DatasourceInterface
*/
protected $ds;
/**
* The target of this query
* @var string
*/
protected $table;
/**
* The columns of the target that should be returned
* @var array
*/
private $columns;
/**
* The columns you're using to sort the query result
* @var array
*/
private $orderColumns = array();
/**
* Return not more than that many rows
* @var int
*/
private $limitCount;
/**
* Result starts with this row
* @var int
*/
private $limitOffset;
/**
* Whether its a distinct query or not
*
* @var bool
*/
private $distinct = false;
/**
* The backend independent filter to use for this query
*
* @var Tree
*/
private $filter;
/**
* Constructor
*
* @param DatasourceInterface $ds Your data source
*/
public function __construct(DatasourceInterface $ds, array $columns = null)
{
$this->ds = $ds;
$this->columns = $columns;
$this->clearFilter();
$this->init();
}
public function getDatasource()
{
return $this->ds;
}
public function setColumns(array $columns)
{
$this->columns = $columns;
}
/**
* Define the target and attributes for this query
*
* The Query will return the default attribute the attributes parameter is omitted
*
* @param String $target The target of this query (tablename, objectname, depends on the concrete implementation)
* @param array $columns An optional array of columns to select, if none are given the default
* columnset is returned
*
* @return self Fluent interface
*/
public function from($target, array $attributes = null)
{
$this->table = $target;
if ($attributes !== null) {
$this->columns = $attributes;
}
return $this;
}
/**
* Add a filter expression to be applied on this query.
*
* This is an alias to andWhere()
* The syntax of the expression and valid parameters are to be defined by the concrete
* backend-specific query implementation.
*
* @param string $expression Implementation specific search expression
* @param mixed $parameters Implementation specific search value to use for query placeholders
*
* @return self Fluent Interface
* @see BaseQuery::andWhere() This is an alias to andWhere()
*/
public function where($expression, $parameters = null)
{
return $this->andWhere($expression, $parameters);
}
/**
* Add an mandatory filter expression to be applied on this query
*
* The syntax of the expression and valid parameters are to be defined by the concrete
* backend-specific query implementation.
*
* @param string $expression Implementation specific search expression
* @param mixed $parameters Implementation specific search value to use for query placeholders
* @return self Fluent interface
*/
public function andWhere($expression, $parameters = null)
{
$node = $this->parseFilterExpression($expression, $parameters);
if ($node === null) {
Logger::debug('Ignoring invalid filter expression: %s (params: %s)', $expression, $parameters);
return $this;
}
$this->filter->insert(Node::createAndNode());
$this->filter->insert($node);
return $this;
}
/**
* Add an lower priority filter expression to be applied on this query
*
* The syntax of the expression and valid parameters are to be defined by the concrete
* backend-specific query implementation.
*
* @param string $expression Implementation specific search expression
* @param mixed $parameters Implementation specific search value to use for query placeholders
* @return self Fluent interface
*/
public function orWhere($expression, $parameters = null)
{
$node = $this->parseFilterExpression($expression, $parameters);
if ($node === null) {
Logger::debug('Ignoring invalid filter expression: %s (params: %s)', $expression, $parameters);
return $this;
}
$this->filter->insert(Node::createOrNode());
$this->filter->insert($node);
return $this;
}
/**
* Determine whether the given field is a valid filter target
*
* The base implementation always returns true, overwrite it in concrete backend-specific
* implementations
*
* @param String $field The field to test for being filterable
* @return bool True if the field can be filtered, otherwise false
*/
public function isValidFilterTarget($field)
{
return true;
}
/**
* Return the internally used field name for the given alias
*
* The base implementation just returns the given field, overwrite it in concrete backend-specific
* implementations
*
* @param String $field The field to test for being filterable
* @return bool True if the field can be filtered, otherwise false
*/
public function getMappedField($field)
{
return $field;
}
/**
* Add a filter to this query
*
* This is the implementation for the Filterable, use where instead
*
* @param $filter
*/
public function addFilter($filter)
{
if (is_string($filter)) {
$this->addFilter(call_user_func_array(array($this, 'parseFilterExpression'), func_get_args()));
} elseif ($filter instanceof Node) {
$this->filter->insert($filter);
}
}
public function setFilter(Tree $filter)
{
$this->filter = $filter;
}
/**
* Return all default columns
*
* @return array An array of default columns to use when none are selected
*/
public function getDefaultColumns()
{
return array();
}
/**
* Sort query result by the given column name
*
* Sort direction can be ascending (self::SORT_ASC, being the default)
* or descending (self::SORT_DESC).
*
* Preferred usage:
* <code>
* $query->sort('column_name ASC')
* </code>
*
* @param string $columnOrAlias Column, may contain direction separated by space
* @param int $dir Sort direction
*
* @return BaseQuery
*/
public function order($columnOrAlias, $dir = null)
{
if ($dir === null) {
$colDirPair = explode(' ', $columnOrAlias, 2);
if (count($colDirPair) === 1) {
$dir = $this->getDefaultSortDir($columnOrAlias);
} else {
$dir = $colDirPair[1];
$columnOrAlias = $colDirPair[0];
}
}
$dir = (strtoupper(trim($dir)) === 'DESC') ? self::SORT_DESC : self::SORT_ASC;
$this->orderColumns[] = array($columnOrAlias, $dir);
return $this;
}
/**
* Set the columns used for ordering
*
* @param array $orderColumns
*/
public function setOrderColumns(array $orderColumns)
{
$this->orderColumns = $orderColumns;
}
/**
* Determine the default sort direction constant for the given column
*
* @param String $col The column to get the sort direction for
* @return int Either SORT_ASC or SORT_DESC
*/
protected function getDefaultSortDir($col)
{
return self::SORT_ASC;
}
/**
* Limit the result set
*
* @param int $count The numeric maximum limit to apply on the query result
* @param int $offset The offset to use for the result set
*
* @return BaseQuery
*/
public function limit($count = null, $offset = null)
{
$this->limitCount = $count !== null ? intval($count) : null;
$this->limitOffset = intval($offset);
return $this;
}
/**
* Return only distinct results
*
* @param bool $distinct Whether the query should be distinct or not
*
* @return BaseQuery
*/
public function distinct($distinct = true)
{
$this->distinct = $distinct;
return $this;
}
/**
* Determine whether this query returns only distinct results
*
* @return bool True in case its a distinct query otherwise false
*/
public function isDistinct()
{
return $this->distinct;
}
/**
* Determine whether this query will be ordered explicitly
*
* @return bool True when an order column has been set
*/
public function hasOrder()
{
return !empty($this->orderColumns);
}
/**
* Determine whether this query will be limited explicitly
*
* @return bool True when an limit count has been set, otherwise false
*/
public function hasLimit()
{
return $this->limitCount !== null;
}
/**
* Determine whether an offset is set or not
*
* @return bool True when an offset > 0 is set
*/
public function hasOffset()
{
return $this->limitOffset > 0;
}
/**
* Get the query limit
*
* @return int The query limit or null if none is set
*/
public function getLimit()
{
return $this->limitCount;
}
/**
* Get the query starting offset
*
* @return int The query offset or null if none is set
*/
public function getOffset()
{
return $this->limitOffset;
}
/**
* Implementation specific initialization
*
* Overwrite this instead of __construct (it's called at the end of the construct) to
* implement custom initialization logic on construction time
*/
protected function init()
{
}
/**
* Return all columns set in this query or the default columns if none are set
*
* @return array An array of columns
*/
public function getColumns()
{
return ($this->columns !== null) ? $this->columns : $this->getDefaultColumns();
}
/**
* Return all columns used for ordering
*
* @return array
*/
public function getOrderColumns()
{
return $this->orderColumns;
}
public function getFilter()
{
return $this->filter;
}
public function clearFilter()
{
$this->filter = new Tree();
}
/**
* Return a pagination adapter for this query
*
* @return \Zend_Paginator
*/
public function paginate($limit = null, $page = null)
{
if ($page === null || $limit === null) {
$request = \Zend_Controller_Front::getInstance()->getRequest();
if ($page === null) {
$page = $request->getParam('page', 0);
}
if ($limit === null) {
$limit = $request->getParam('limit', 20);
}
}
$this->limit($limit, $page * $limit);
$paginator = new Zend_Paginator(new QueryAdapter($this));
$paginator->setItemCountPerPage($limit);
$paginator->setCurrentPageNumber($page);
return $paginator;
}
/**
* Parse a backend specific filter expression and return a Query\Node object
*
* @param $expression The expression to parse
* @param $parameters Optional parameters for the expression
*
* @return Node A query node or null if it's an invalid expression
*/
protected function parseFilterExpression($expression, $parameter = null)
{
$splitted = explode(' ', $expression, 3);
if (count($splitted) === 1 && $parameter) {
return Node::createOperatorNode(Node::OPERATOR_EQUALS, $splitted[0], $parameter);
} elseif (count($splitted) === 2 && $parameter) {
Node::createOperatorNode($splitted[0], $splitted[1], is_string($parameter));
return Node::createOperatorNode(Node::OPERATOR_EQUALS, $splitted[0], $parameter);
} elseif (count($splitted) === 3) {
if (trim($splitted[2]) === '?') {
return Node::createOperatorNode($splitted[1], $splitted[0], $parameter);
} else {
return Node::createOperatorNode($splitted[1], $splitted[0], $splitted[2]);
}
}
return null;
}
/**
* Total result size regardless of limit and offset
*
* @return int
*/
public function count()
{
return $this->ds->count($this);
}
/**
* Fetch result as an array of objects
*
* @return array
*/
public function fetchAll()
{
return $this->ds->fetchAll($this);
}
/**
* Fetch first result row
*
* @return object
*/
public function fetchRow()
{
return $this->ds->fetchRow($this);
}
/**
* Fetch first result column
*
* @return array
*/
public function fetchColumn()
{
return $this->ds->fetchColumn($this);
}
/**
* Fetch first column value from first result row
*
* @return mixed
*/
public function fetchOne()
{
return $this->ds->fetchOne($this);
}
/**
* Fetch result as a key/value pair array
*
* @return array
*/
public function fetchPairs()
{
return $this->ds->fetchPairs($this);
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace Icinga\Data;
/**
* Interface for browsing data
*/
interface Browsable
{
/**
* Paginate data
*
* @param int $itemsPerPage Number of items per page
* @param int $pageNumber Current page number
*
* @return Zend_Paginator
*/
public function paginate($itemsPerPage = null, $pageNumber = null);
}

View File

@ -0,0 +1,5 @@
<?php
namespace Icinga\Data;
interface ConnectionInterface extends Selectable, Queryable {};

View File

@ -2,11 +2,14 @@
namespace Icinga\Data\DataArray; namespace Icinga\Data\DataArray;
use Icinga\Data\DatasourceInterface; use Icinga\Data\Selectable;
use Icinga\Data\SimpleQuery;
class Datasource implements DatasourceInterface class ArrayDatasource implements Selectable
{ {
protected $data; protected $data;
protected $result;
/** /**
* Constructor, create a new Datasource for the given Array * Constructor, create a new Datasource for the given Array
@ -21,14 +24,14 @@ class Datasource implements DatasourceInterface
/** /**
* Instantiate a Query object * Instantiate a Query object
* *
* @return Query * @return SimpleQuery
*/ */
public function select() public function select()
{ {
return new Query($this); return new SimpleQuery($this);
} }
public function fetchColumn(Query $query) public function fetchColumn(SimpleQuery $query)
{ {
$result = array(); $result = array();
foreach ($this->getResult($query) as $row) { foreach ($this->getResult($query) as $row) {
@ -38,7 +41,7 @@ class Datasource implements DatasourceInterface
return $result; return $result;
} }
public function fetchPairs(Query $query) public function fetchPairs(SimpleQuery $query)
{ {
$result = array(); $result = array();
$keys = null; $keys = null;
@ -54,27 +57,40 @@ class Datasource implements DatasourceInterface
return $result; return $result;
} }
public function fetchAll(Query $query) public function fetchRow(SimpleQuery $query)
{
$result = $this->getResult($query);
if (empty($result)) {
return false;
}
return $result[0];
}
public function fetchAll(SimpleQuery $query)
{ {
return $this->getResult($query); return $this->getResult($query);
} }
public function count(Query $query) public function count(SimpleQuery $query)
{ {
$this->createResult($query); $this->createResult($query);
return $query->getCount(); return count($this->result);
} }
protected function createResult(Query $query) protected function createResult(SimpleQuery $query)
{ {
if ($query->hasResult()) { if ($this->hasResult()) {
return $this; return $this;
} }
$result = array(); $result = array();
$columns = $query->getColumns(); $columns = $query->getColumns();
$filter = $query->getFilter();
foreach ($this->data as & $row) { foreach ($this->data as & $row) {
if (! $filter->matches($row)) {
continue;
}
// Get only desired columns if asked so // Get only desired columns if asked so
if (empty($columns)) { if (empty($columns)) {
@ -96,19 +112,44 @@ class Datasource implements DatasourceInterface
} }
// Sort the result // Sort the result
if ($query->hasOrder()) { if ($query->hasOrder()) {
usort($result, array($query, 'compare')); usort($result, array($query, 'compare'));
} }
$query->setResult($result); $this->setResult($result);
return $this; return $this;
} }
protected function getResult(Query $query) protected function getLimitedResult($query)
{ {
if (! $query->hasResult()) { if ($query->hasLimit()) {
if ($query->hasOffset()) {
$offset = $query->getOffset();
} else {
$offset = 0;
}
return array_slice($this->result, $offset, $query->getLimit());
} else {
return $this->result;
}
}
protected function hasResult()
{
return $this->result !== null;
}
protected function setResult($result)
{
return $this->result = $result;
}
protected function getResult(SimpleQuery $query)
{
if (! $this->hasResult()) {
$this->createResult($query); $this->createResult($query);
} }
return $query->getLimitedResult(); return $this->getLimitedResult($query);
} }
} }

View File

@ -1,99 +0,0 @@
<?php
namespace Icinga\Data\DataArray;
use Icinga\Data\BaseQuery;
class Query extends BaseQuery
{
/**
* Remember the last count
*/
protected $count;
/**
* Remember the last result without applied limits
*/
protected $result;
public function getCount()
{
return $this->count;
}
public function hasResult()
{
return $this->result !== null;
}
public function getFullResult()
{
return $this->result;
}
public function getLimitedResult()
{
if ($this->hasLimit()) {
if ($this->hasOffset()) {
$offset = $this->getOffset();
} else {
$offset = 0;
}
return array_slice($this->result, $offset, $this->getLimit());
} else {
return $this->result;
}
}
public function setResult($result)
{
$this->result = $result;
$this->count = count($result);
return $this;
}
/**
* ArrayDatasource will apply this function to sort the array
*
* @param mixed $a Left side comparsion value
* @param mixed $b Right side comparsion value
* @param int $col_num Current position in order_columns array
*
* @return int
*/
public function compare(& $a, & $b, $col_num = 0)
{
$orderColumns = $this->getOrderColumns();
if (! array_key_exists($col_num, $orderColumns)) {
return 0;
}
$col = $orderColumns[$col_num][0];
$dir = $orderColumns[$col_num][1];
//$res = strnatcmp(strtolower($a->$col), strtolower($b->$col));
$res = strcmp(strtolower($a->$col), strtolower($b->$col));
if ($res === 0) {
if (array_key_exists(++$col_num, $orderColumns)) {
return $this->compare($a, $b, $col_num);
} else {
return 0;
}
}
if ($dir === self::SORT_ASC) {
return $res;
} else {
return $res * -1;
}
}
public function parseFilterExpression($expression, $parameters = null)
{
return null;
}
public function applyFilter()
{
return null;
}
}

View File

@ -1,14 +0,0 @@
<?php
// TODO: create interface instead of abstract class
namespace Icinga\Data;
interface DatasourceInterface
{
/**
* Instantiate a Query object
*
* @return BaseQuery
*/
public function select();
}

View File

@ -29,16 +29,19 @@
namespace Icinga\Data\Db; namespace Icinga\Data\Db;
use Icinga\Application\Benchmark;
use Icinga\Data\Db\DbQuery;
use Icinga\Data\ResourceFactory;
use Icinga\Data\Selectable;
use Icinga\Exception\ConfigurationError;
use PDO; use PDO;
use Zend_Config; use Zend_Config;
use Zend_Db; use Zend_Db;
use Icinga\Data\DatasourceInterface;
use Icinga\Exception\ConfigurationError;
/** /**
* Encapsulate database connections and query creation * Encapsulate database connections and query creation
*/ */
class Connection implements DatasourceInterface class DbConnection implements Selectable
{ {
/** /**
* Connection config * Connection config
@ -54,8 +57,16 @@ class Connection implements DatasourceInterface
*/ */
private $dbType; private $dbType;
private $conn; /**
* @var Zend_Db_Adapter_Abstract
*/
private $dbAdapter;
/**
* Table prefix
*
* @var string
*/
private $tablePrefix = ''; private $tablePrefix = '';
private static $genericAdapterOptions = array( private static $genericAdapterOptions = array(
@ -64,9 +75,10 @@ class Connection implements DatasourceInterface
); );
private static $driverOptions = array( private static $driverOptions = array(
PDO::ATTR_TIMEOUT => 5, PDO::ATTR_TIMEOUT => 10,
PDO::ATTR_CASE => PDO::CASE_LOWER, PDO::ATTR_CASE => PDO::CASE_LOWER,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
// TODO: allow configurable PDO::ATTR_PERSISTENT => true
); );
/** /**
@ -81,13 +93,13 @@ class Connection implements DatasourceInterface
} }
/** /**
* Prepare query object * Provide a query on this connection
* *
* @return Query * @return Query
*/ */
public function select() public function select()
{ {
return new Query($this); return new DbQuery($this);
} }
/** /**
@ -101,13 +113,13 @@ class Connection implements DatasourceInterface
} }
/** /**
* Getter for database object * Getter for the Zend_Db_Adapter
* *
* @return Zend_Db_Adapter_Abstract * @return Zend_Db_Adapter_Abstract
*/ */
public function getDb() public function getDbAdapter()
{ {
return $this->db; return $this->dbAdapter;
} }
/** /**
@ -157,24 +169,111 @@ class Connection implements DatasourceInterface
default: default:
throw new ConfigurationError(sprintf('Backend "%s" is not supported', $this->dbType)); throw new ConfigurationError(sprintf('Backend "%s" is not supported', $this->dbType));
} }
$this->conn = Zend_Db::factory($adapter, $adapterParamaters); $this->dbAdapter = Zend_Db::factory($adapter, $adapterParamaters);
$this->conn->setFetchMode(Zend_Db::FETCH_OBJ); $this->dbAdapter->setFetchMode(Zend_Db::FETCH_OBJ);
$this->conn->getProfiler()->setEnabled(false); // TODO(el/tg): The profiler is disabled per default, why do we disable the profiler explicitly?
$this->dbAdapter->getProfiler()->setEnabled(false);
} }
public static function fromResourceName($name)
{
return new static(ResourceFactory::getResourceConfig($name));
}
/**
* @deprecated Use Connection::getDbAdapter() instead
*/
public function getConnection() public function getConnection()
{ {
return $this->conn; return $this->dbAdapter;
} }
/**
* Getter for the table prefix
*
* @return string
*/
public function getTablePrefix() public function getTablePrefix()
{ {
return $this->tablePrefix; return $this->tablePrefix;
} }
/**
* Setter for the table prefix
*
* @param string $prefix
*
* @return self
*/
public function setTablePrefix($prefix) public function setTablePrefix($prefix)
{ {
$this->tablePrefix = $prefix; $this->tablePrefix = $prefix;
return $this; return $this;
} }
/**
* Retrieve an array containing all rows of the result set
*
* @param DbQuery $query
*
* @return array
*/
public function fetchAll(DbQuery $query)
{
Benchmark::measure('DB is fetching All');
$result = $this->dbAdapter->fetchAll($query->getSelectQuery());
Benchmark::measure('DB fetch done');
return $result;
}
/**
* Fetch the first row of the result set
*
* @param DbQuery $query
*
* @return mixed
*/
public function fetchRow(DbQuery $query)
{
return $this->dbAdapter->fetchRow($query->getSelectQuery());
}
/**
* Fetch a column of all rows of the result set as an array
*
* @param DbQuery $query
* @param int $columnIndex Index of the column to fetch
*
* @return array
*/
public function fetchColumn(DbQuery $query, $columnIndex = 0)
{
return $this->dbAdapter->fetchCol($query->getSelectQuery());
}
/**
* Fetch the first column of the first row of the result set
*
* @param DbQuery $query
*
* @return string
*/
public function fetchOne(DbQuery $query)
{
return $this->dbAdapter->fetchOne($query->getSelectQuery());
}
/**
* Fetch all rows of the result set as an array of key-value pairs
*
* The first column is the key, the second column is the value.
*
* @param DbQuery $query
*
* @return array
*/
public function fetchPairs(DbQuery $query)
{
return $this->dbAdapter->fetchPairs($query->getSelectQuery());
}
} }

View File

@ -0,0 +1,267 @@
<?php
namespace Icinga\Data\Db;
use Icinga\Data\SimpleQuery;
use Icinga\Application\Benchmark;
use Icinga\Data\Filter\FilterChain;
use Icinga\Data\Filter\FilterExpression;
use Icinga\Data\Filter\FilterOr;
use Icinga\Data\Filter\FilterAnd;
use Icinga\Data\Filter\FilterNot;
use Zend_Db_Select;
/**
* Database query class
*/
class DbQuery extends SimpleQuery
{
/**
* @var Zend_Db_Adapter_Abstract
*/
protected $db;
/**
* Select query
*
* @var Zend_Db_Select
*/
protected $select;
/**
* Whether to use a subquery for counting
*
* When the query is distinct or has a HAVING or GROUP BY clause this must be set to true
*
* @var bool
*/
protected $useSubqueryCount = false;
/**
* Set the count maximum
*
* If the count maximum is set, count queries will not count more than that many rows. You should set this
* property only for really heavy queries.
*
* @var int
*/
protected $maxCount;
/**
* Count query result
*
* Count queries are only executed once
*
* @var int
*/
protected $count;
protected function init()
{
$this->db = $this->ds->getDbAdapter();
parent::init();
}
public function where($condition, $value = null)
{
// $this->count = $this->select = null;
return parent::where($condition, $value);
}
protected function dbSelect()
{
if ($this->select === null) {
$this->select = $this->db->select()->from($this->target, array());
}
return clone $this->select;
}
/**
* Get the select query
*
* Applies order and limit if any
*
* @return Zend_Db_Select
*/
public function getSelectQuery()
{
$select = $this->dbSelect();
$select->columns($this->columns);
$this->applyFilterSql($select);
if ($this->hasLimit() || $this->hasOffset()) {
$select->limit($this->getLimit(), $this->getOffset());
}
if ($this->hasOrder()) {
foreach ($this->getOrder() as $fieldAndDirection) {
$select->order(
$fieldAndDirection[0] . ' ' . $fieldAndDirection[1]
);
}
}
return $select;
}
protected function applyFilterSql($query)
{
$where = $this->renderFilter($this->filter);
if ($where !== '') {
$query->where($where);
}
}
protected function renderFilter($filter, $level = 0)
{
$str = '';
if ($filter instanceof FilterChain) {
if ($filter instanceof FilterAnd) {
$op = ' AND ';
} elseif ($filter instanceof FilterOr) {
$op = ' OR ';
} elseif ($filter instanceof FilterNot) {
$op = ' AND ';
$str .= ' NOT ';
} else {
throw new \Exception('Cannot render filter: ' . $filter);
}
$parts = array();
if (! $filter->isEmpty()) {
foreach ($filter->filters() as $f) {
$parts[] = $this->renderFilter($f, $level + 1);
}
if ($level > 0) {
$str .= ' (' . implode($op, $parts) . ') ';
} else {
$str .= implode($op, $parts);
}
}
} else {
$str .= $this->whereToSql($filter->getColumn(), $filter->getSign(), $filter->getExpression());
}
return $str;
}
protected function escapeForSql($value)
{
// bindParam? bindValue?
if (is_array($value)) {
$ret = array();
foreach ($value as $val) {
$ret[] = $this->escapeForSql($val);
}
return implode(', ', $ret);
} else {
//if (preg_match('/^\d+$/', $value)) {
// return $value;
//} else {
return $this->db->quote($value);
//}
}
}
protected function escapeWildcards($value)
{
return preg_replace('/\*/', '%', $value);
}
protected function valueToTimestamp($value)
{
// We consider integers as valid timestamps. Does not work for URL params
if (ctype_digit($value)) {
return $value;
}
$value = strtotime($value);
if (! $value) {
/*
NOTE: It's too late to throw exceptions, we might finish in __toString
throw new \Exception(sprintf(
'"%s" is not a valid time expression',
$value
));
*/
}
return $value;
}
protected function timestampForSql($value)
{
// TODO: do this db-aware
return $this->escapeForSql(date('Y-m-d H:i:s', $value));
}
public function whereToSql($col, $sign, $expression)
{
if ($this->isTimestamp($col)) {
$expression = $this->valueToTimestamp($expression);
}
if (is_array($expression) && $sign === '=') {
// TODO: Should we support this? Doesn't work for blub*
return $col . ' IN (' . $this->escapeForSql($expression) . ')';
} elseif (strpos($expression, '*') === false) {
return $col . ' ' . $sign . ' ' . $this->escapeForSql($expression);
} else {
return $col . ' LIKE ' . $this->escapeForSql($this->escapeWildcards($expression));
}
}
/**
* Get the count query
*
* @return Zend_Db_Select
*/
public function getCountQuery()
{
// TODO: there may be situations where we should clone the "select"
$count = $this->dbSelect();
$this->applyFilterSql($count);
if ($this->useSubqueryCount) {
$columns = array('cnt' => 'COUNT(*)');
return $this->db->select()->from($count, $columns);
}
if ($this->maxCount !== null) {
return $this->db->select()->from($count->limit($this->maxCount));
}
$count->columns(array('cnt' => 'COUNT(*)'));
return $count;
}
/**
* Count all rows of the result set
*
* @return int
*/
public function count()
{
if ($this->count === null) {
Benchmark::measure('DB is counting');
$this->count = $this->db->fetchOne($this->getCountQuery());
Benchmark::measure('DB finished count');
}
return $this->count;
}
/**
* Return the select and count query as a textual representation
*
* @return string A string containing the select and count query, using unix style newlines as linebreaks
*/
public function dump()
{
return "QUERY\n=====\n"
. $this->getSelectQuery()
. "\n\nCOUNT\n=====\n"
. $this->getCountQuery()
. "\n\n";
}
/**
* @return string
*/
public function __toString()
{
return (string) $this->getSelectQuery();
}
}

View File

@ -1,343 +0,0 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
/**
* This file is part of Icinga Web 2.
*
* Icinga Web 2 - Head for multiple monitoring backends.
* Copyright (C) 2013 Icinga Development Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* @copyright 2013 Icinga Development Team <info@icinga.org>
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
* @author Icinga Development Team <info@icinga.org>
*
*/
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Data\Db;
use Icinga\Filter\Query\Node;
use Icinga\Filter\Query\Tree;
use Zend_Db_Select;
use Icinga\Data\BaseQuery;
use Icinga\Application\Benchmark;
/**
* Db/Query class for implementing database queries
*/
class Query extends BaseQuery
{
/**
* Zend_Db_Adapter_Abstract
*
*
*/
protected $db;
/**
* Base Query will be prepared here, has tables and cols
* shared by full & count query
*/
protected $baseQuery;
/**
* Select object
*/
private $selectQuery;
/**
* Select object used for count query
*/
private $countQuery;
/**
* Allow to override COUNT(*)
*/
protected $countColumns;
protected $useSubqueryCount = false;
protected $countCache;
protected $maxCount;
protected function init()
{
$this->db = $this->ds->getConnection();
$this->baseQuery = $this->db->select();
}
public function __clone()
{
if ($this->baseQuery !== null) {
$this->baseQuery = clone $this->baseQuery;
}
if ($this->selectQuery !== null) {
$this->selectQuery = clone $this->selectQuery;
}
if ($this->countQuery !== null) {
$this->countQuery = clone $this->countQuery;
}
}
/**
* Return the raw base query
*
* Modifications on this requires a call to Query::refreshQueryObjects()
*
* @return Zend_Db_Select
*
*/
public function getRawBaseQuery()
{
return $this->baseQuery;
}
/**
* Recreate the select and count queries
*
* Required when external modifications are made in the baseQuery
*/
public function refreshQueryObjects()
{
$this->createQueryObjects();
}
/**
* Return the select query and initialize it if not done yet
*
* @return Zend_Db_Select
*/
public function getSelectQuery()
{
if ($this->selectQuery === null) {
$this->createQueryObjects();
}
if ($this->hasLimit()) {
$this->selectQuery->limit($this->getLimit(), $this->getOffset());
}
return $this->selectQuery;
}
/**
* Return the current count query and initialize it if not done yet
*
* @return Zend_Db_Select
*/
public function getCountQuery()
{
if ($this->countQuery === null) {
$this->createQueryObjects();
}
return $this->countQuery;
}
/**
* Create the Zend_Db select query for this query
*/
private function createSelectQuery()
{
$this->selectQuery = clone($this->baseQuery);
$this->selectQuery->columns($this->getColumns());
$this->selectQuery->distinct($this->isDistinct());
if ($this->hasOrder()) {
foreach ($this->getOrderColumns() as $col) {
$this->selectQuery->order(
$col[0] . ' ' . (($col[1] === self::SORT_DESC) ? 'DESC' : 'ASC')
);
}
}
}
/**
* Create a countquery by using the select query as a subselect and count it's result
*
* This is a rather naive approach and not suitable for complex queries or queries with many results
*
* @return Zend_Db_Select The query object representing the count
*/
private function createCountAsSubQuery()
{
$query = clone($this->selectQuery);
if ($this->maxCount === null) {
return $this->db->select()->from($query, 'COUNT(*)');
} else {
return $this->db->select()->from(
$query->reset('order')->limit($this->maxCount),
'COUNT(*)'
);
}
}
/**
* Create a custom count query based on the columns set in countColumns
*
* @return Zend_Db_Select The query object representing the count
*/
private function createCustomCountQuery()
{
$query = clone($this->baseQuery);
if ($this->countColumns === null) {
$this->countColumns = array('cnt' => 'COUNT(*)');
}
$query->columns($this->countColumns);
return $query;
}
/**
* Create a query using the selected operation
*
* @see Query::createCountAsSubQuery() Used when useSubqueryCount is true
* @see Query::createCustomCountQuery() Called when useSubqueryCount is false
*/
private function createCountQuery()
{
if ($this->isDistinct() || $this->useSubqueryCount) {
$this->countQuery = $this->createCountAsSubQuery();
} else {
$this->countQuery = $this->createCustomCountQuery();
}
}
protected function beforeQueryCreation()
{
}
protected function afterQueryCreation()
{
}
/**
* Create the Zend_Db select and count query objects for this instance
*/
private function createQueryObjects()
{
$this->beforeQueryCreation();
$this->applyFilter();
$this->createSelectQuery();
$this->createCountQuery();
$this->afterQueryCreation();
}
/**
* Query the database and fetch the result count of this query
*
* @return int The result count of this query as returned by the database
*/
public function count()
{
if ($this->countCache === null) {
Benchmark::measure('DB is counting');
$this->countCache = $this->db->fetchOne($this->getCountQuery());
Benchmark::measure('DB finished count');
}
return $this->countCache;
}
/**
* Query the database and return all results
*
* @return array An array containing subarrays with all results contained in the database
*/
public function fetchAll()
{
Benchmark::measure('DB is fetching All');
$result = $this->db->fetchAll($this->getSelectQuery());
Benchmark::measure('DB fetch done');
return $result;
}
/**
* Query the database and return the next result row
*
* @return array An array containing the next row of the database result
*/
public function fetchRow()
{
return $this->db->fetchRow($this->getSelectQuery());
}
/**
* Query the database and return a single column of the result
*
* @return array An array containing the first column of the result
*/
public function fetchColumn()
{
return $this->db->fetchCol($this->getSelectQuery());
}
/**
* Query the database and return a single result
*
* @return array An associative array containing the first result
*/
public function fetchOne()
{
return $this->db->fetchOne($this->getSelectQuery());
}
/**
* Query the database and return key=>value pairs using hte first two columns
*
* @return array An array containing key=>value pairs
*/
public function fetchPairs()
{
return $this->db->fetchPairs($this->getSelectQuery());
}
/**
* Return the select and count query as a textual representation
*
* @return string An String containing the select and count query, using unix style newlines
* as linebreaks
*/
public function dump()
{
return "QUERY\n=====\n"
. $this->getSelectQuery()
. "\n\nCOUNT\n=====\n"
. $this->getCountQuery()
. "\n\n";
}
/**
* Return the select query
*
* The paginator expects this, so we can't use debug output here
*
* @return Zend_Db_Select
*/
public function __toString()
{
return strval($this->getSelectQuery());
}
public function applyFilter()
{
$parser = new TreeToSqlParser($this);
$parser->treeToSql($this->getFilter(), $this->baseQuery);
$this->clearFilter();
}
}

View File

@ -1,222 +0,0 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
/**
* This file is part of Icinga Web 2.
*
* Icinga Web 2 - Head for multiple monitoring backends.
* Copyright (C) 2013 Icinga Development Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* @copyright 2013 Icinga Development Team <info@icinga.org>
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
* @author Icinga Development Team <info@icinga.org>
*
*/
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Data\Db;
use Icinga\Data\BaseQuery;
use Icinga\Filter\Query\Tree;
use Icinga\Filter\Query\Node;
/**
* Converter class that takes a query tree and creates an SQL Query from it's state
*/
class TreeToSqlParser
{
/**
* The query class to use as the base for converting
*
* @var BaseQuery
*/
private $query;
/**
* The type of the filter (WHERE or HAVING, depending whether it's an aggregate query)
* @var string
*/
private $type = 'WHERE';
/**
* Create a new converter from this query
*
* @param BaseQuery $query The query to use for conversion
*/
public function __construct(BaseQuery $query)
{
$this->query = $query;
}
/**
* Return the SQL equivalent fo the given text operator
*
* @param String $operator The operator from the query node
* @return string The operator for the sql query part
*/
private function getSqlOperator($operator, array $right)
{
switch($operator) {
case Node::OPERATOR_EQUALS:
if (count($right) > 1) {
return 'IN';
} else {
foreach ($right as $r) {
if (strpos($r, '*') !== false) {
return 'LIKE';
}
}
return '=';
}
case Node::OPERATOR_EQUALS_NOT:
if (count($right) > 1) {
return 'NOT IN';
} else {
return 'NOT LIKE';
}
default:
return $operator;
}
}
/**
* Convert a Query Tree node to an sql string
*
* @param Node $node The node to convert
* @return string The sql string representing the node's state
*/
private function nodeToSqlQuery(Node $node)
{
if ($node->type !== Node::TYPE_OPERATOR) {
return $this->parseConjunctionNode($node);
} else {
return $this->parseOperatorNode($node);
}
}
/**
* Parse an AND or OR node to an sql string
*
* @param Node $node The AND/OR node to parse
* @return string The sql string representing this node
*/
private function parseConjunctionNode(Node $node)
{
$queryString = '';
$leftQuery = $node->left !== null ? $this->nodeToSqlQuery($node->left) : '';
$rightQuery = $node->right !== null ? $this->nodeToSqlQuery($node->right) : '';
if ($leftQuery != '') {
$queryString .= $leftQuery . ' ';
}
if ($rightQuery != '') {
$queryString .= (($queryString !== '') ? $node->type . ' ' : ' ') . $rightQuery;
}
return $queryString;
}
/**
* Parse an operator node to an sql string
*
* @param Node $node The operator node to parse
* @return string The sql string representing this node
*/
private function parseOperatorNode(Node $node)
{
if (!$this->query->isValidFilterTarget($node->left) && $this->query->getMappedField($node->left)) {
return '';
}
$this->query->requireColumn($node->left);
$queryString = '(' . $this->query->getMappedField($node->left) . ')';
if ($this->query->isAggregateColumn($node->left)) {
$this->type = 'HAVING';
}
$queryString .= ' ' . (is_integer($node->right) ?
$node->operator : $this->getSqlOperator($node->operator, $node->right)) . ' ';
$queryString = $this->addValueToQuery($node, $queryString);
return $queryString;
}
/**
* Convert a node value to it's sql equivalent
*
* This currently only detects if the node is in the timestring context and calls strtotime if so and it replaces
* '*' with '%'
*
* @param Node $node The node to retrieve the sql string value from
* @return String|int The converted and quoted value
*/
private function addValueToQuery(Node $node, $query) {
$values = array();
foreach ($node->right as $value) {
if ($node->operator === Node::OPERATOR_EQUALS || $node->operator === Node::OPERATOR_EQUALS_NOT) {
$value = str_replace('*', '%', $value);
}
if ($this->query->isTimestamp($node->left)) {
$node->context = Node::CONTEXT_TIMESTRING;
}
if ($node->context === Node::CONTEXT_TIMESTRING && !is_numeric($value)) {
$value = strtotime($value);
}
if (preg_match('/^\d+$/', $value)) {
$values[] = $value;
} else {
$values[] = $this->query->getDatasource()->getConnection()->quote($value);
}
}
$valueString = join(',', $values);
if (count($values) > 1) {
return $query . '( '. $valueString . ')';
}
return $query . $valueString;
}
/**
* Apply the given tree to the query, either as where or as having clause
*
* @param Tree $tree The tree representing the filter
* @param \Zend_Db_Select $baseQuery The query to apply the filter on
*/
public function treeToSql(Tree $tree, $baseQuery)
{
if ($tree->root == null) {
return;
}
$sql = $this->nodeToSqlQuery($tree->normalizeTree($tree->root));
if ($this->filtersAggregate()) {
$baseQuery->having($sql);
} else {
$baseQuery->where($sql);
}
}
/**
* Return true if this is an filter that should be applied after aggregation
*
* @return bool True when having should be used, otherwise false
*/
private function filtersAggregate()
{
return $this->type === 'HAVING';
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace Icinga\Data;
/**
* Interface for retrieving data
*/
interface Fetchable
{
/**
* Retrieve an array containing all rows of the result set
*
* @return array
*/
public function fetchAll();
/**
* Fetch the first row of the result set
*
* @return mixed
*/
public function fetchRow();
/**
* Fetch a column of all rows of the result set as an array
*
* @param int $columnIndex Index of the column to fetch
*
* @return array
*/
public function fetchColumn($columnIndex = 0);
/**
* Fetch the first column of the first row of the result set
*
* @return string
*/
public function fetchOne();
/**
* Fetch all rows of the result set as an array of key-value pairs
*
* The first column is the key, the second column is the value.
*
* @return array
*/
public function fetchPairs();
}

View File

@ -0,0 +1,217 @@
<?php
namespace Icinga\Data\Filter;
use Icinga\Web\UrlParams;
use Icinga\Exception\ProgrammingError;
/**
* Filter
*
* Base class for filters (why?) and factory for the different FilterOperators
*/
abstract class Filter
{
protected $id = '1';
public function setId($id)
{
$this->id = (string) $id;
return $this;
}
abstract function toQueryString();
public function getUrlParams()
{
return UrlParams::fromQueryString($this->toQueryString());
}
public function getById($id)
{
if ((string) $id === $this->getId()) {
return $this;
}
throw new ProgrammingError(sprintf(
'Trying to get invalid filter index "%s" from "%s" ("%s")', $id, $this, $this->id
));
}
public function getId()
{
return $this->id;
}
public function isRootNode()
{
return false === strpos($this->id, '-');
}
public function applyChanges($changes)
{
$filter = $this;
$pairs = array();
foreach ($changes as $k => $v) {
if (preg_match('/^(column|value|sign|operator)_([\d-]+)$/', $k, $m)) {
$pairs[$m[2]][$m[1]] = $v;
}
}
$operators = array();
foreach ($pairs as $id => $fs) {
if (array_key_exists('operator', $fs)) {
$operators[$id] = $fs['operator'];
} else {
$f = $filter->getById($id);
$f->setColumn($fs['column']);
if ($f->getSign() !== $fs['sign']) {
if ($f->isRootNode()) {
$filter = $f->setSign($fs['sign']);
} else {
$filter->replaceById($id, $f->setSign($fs['sign']));
}
}
$f->setExpression($fs['value']);
}
}
krsort($operators, SORT_NATURAL);
foreach ($operators as $id => $operator) {
$f = $filter->getById($id);
if ($f->getOperatorName() !== $operator) {
if ($f->isRootNode()) {
$filter = $f->setOperatorName($operator);
} else {
$filter->replaceById($id, $f->setOperatorName($operator));
}
}
}
return $filter;
}
public function getParentId()
{
if ($self->isRootNode()) {
throw new ProgrammingError('Filter root nodes have no parent');
}
return substr($this->id, 0, strrpos($this->id, '-'));
}
public function getParent()
{
return $this->getById($this->getParentId());
}
public function hasId($id)
{
if ($id === $this->getId()) {
return true;
}
return false;
}
/**
* Where Filter factory
*
* @param string $col Column to be filtered
* @param string $filter Filter expression
*
* @throws FilterException
* @return FilterWhere
*/
public static function where($col, $filter)
{
return new FilterExpression($col, '=', $filter);
}
public static function expression($col, $op, $expression)
{
switch ($op) {
case '=': return new FilterEqual($col, $op, $expression);
case '<': return new FilterLessThan($col, $op, $expression);
case '>': return new FilterGreaterThan($col, $op, $expression);
case '>=': return new FilterEqualOrGreaterThan($col, $op, $expression);
case '<=': return new FilterEqualOrLessThan($col, $op, $expression);
case '!=': return new FilterNotEqual($col, $op, $expression);
default: throw new ProgrammingError(
sprintf('There is no such filter sign: %s', $op)
);
}
}
/**
* Or FilterOperator factory
*
* @param Filter $filter,... Unlimited optional list of Filters
*
* @return FilterOr
*/
public static function matchAny()
{
$args = func_get_args();
if (count($args) === 1 && is_array($args[0])) {
$args = $args[0];
}
return new FilterOr($args);
}
/**
* Or FilterOperator factory
*
* @param Filter $filter,... Unlimited optional list of Filters
*
* @return FilterAnd
*/
public static function matchAll()
{
$args = func_get_args();
if (count($args) === 1 && is_array($args[0])) {
$args = $args[0];
}
return new FilterAnd($args);
}
/**
* FilterNot factory, negates the given filter
*
* @param Filter $filter Filter to be negated
*
* @return FilterNot
*/
public static function not()
{
$args = func_get_args();
if (count($args) === 1) {
if (is_array($args[0])) {
$args = $args[0];
}
}
if (count($args) > 1) {
return new FilterNot(array(new FilterAnd($args)));
} else {
return new FilterNot($args);
}
}
public static function chain($operator, $filters = array())
{
switch ($operator) {
case 'AND': return self::matchAll($filters);
case 'OR' : return self::matchAny($filters);
case 'NOT': return self::not($filters);
}
throw new ProgrammingError(
sprintf('"%s" is not a valid filter chain operator', $operator)
);
}
/**
* Create filter from queryString
*
* This is still pretty basic, need improvement
*/
public static function fromQueryString($query)
{
return FilterQueryString::parse($query);
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace Icinga\Data\Filter;
/**
* Filter list AND
*
* Binary AND, all contained filters must succeed
*/
class FilterAnd extends FilterChain
{
protected $operatorName = 'AND';
protected $operatorSymbol = '&';
/**
* Whether the given row object matches this filter
*
* @object $row
* @return boolean
*/
public function matches($row)
{
foreach ($this->filters as $filter) {
if (! $filter->matches($row)) {
return false;
}
}
return true;
}
}

View File

@ -0,0 +1,189 @@
<?php
namespace Icinga\Data\Filter;
use Icinga\Exception\ProgrammingError;
/**
* FilterChain
*
* A FilterChain contains a list ...
*/
abstract class FilterChain extends Filter
{
protected $filters = array();
protected $operatorName;
protected $operatorSymbol;
public function hasId($id)
{
foreach ($this->filters() as $filter) {
if ($filter->hasId($id)) {
return true;
}
}
return parent::hasId($id);
}
public function getById($id)
{
foreach ($this->filters() as $filter) {
if ($filter->hasId($id)) {
return $filter->getById($id);
}
}
return parent::getById($id);
}
public function removeId($id)
{
if ($id === $this->getId()) {
$this->filters = array();
return $this;
}
$remove = null;
foreach ($this->filters as $key => $filter) {
if ($filter->getId() === $id) {
$remove = $key;
} elseif ($filter instanceof FilterChain) {
$filter->removeId($id);
}
}
if ($remove !== null) {
unset($this->filters[$remove]);
$this->filters = array_values($this->filters);
}
$this->refreshChildIds();
return $this;
}
public function replaceById($id, $filter)
{
$found = false;
foreach ($this->filters as $k => $child) {
if ($child->getId() == $id) {
$this->filters[$k] = $filter;
$found = true;
break;
}
if ($child->hasId($id)) {
$child->replaceById($id, $filter);
$found = true;
break;
}
}
if (! $found) {
throw new ProgrammingError('You tried to replace an unexistant child filter');
}
$this->refreshChildIds();
return $this;
}
protected function refreshChildIds()
{
$i = 0;
$id = $this->getId();
foreach ($this->filters as $filter) {
$i++;
$filter->setId($id . '-' . $i);
}
return $this;
}
public function setId($id)
{
return parent::setId($id)->refreshChildIds();
}
public function getOperatorName()
{
return $this->operatorName;
}
public function setOperatorName($name)
{
if ($name !== $this->operatorName) {
return Filter::chain($name, $this->filters);
}
return $this;
}
public function getOperatorSymbol()
{
return $this->operatorSymbol;
}
public function toQueryString()
{
$parts = array();
if (empty($this->filters)) {
return '';
}
foreach ($this->filters() as $filter) {
$parts[] = $filter->toQueryString();
}
// TODO: getLevel??
if (strpos($this->getId(), '-')) {
return '(' . implode($this->getOperatorSymbol(), $parts) . ')';
} else {
return implode($this->getOperatorSymbol(), $parts);
}
}
/**
* Get simple string representation
*
* Useful for debugging only
*
* @return string
*/
public function __toString()
{
if (empty($this->filters)) {
return '';
}
$parts = array();
foreach ($this->filters as $filter) {
if ($filter instanceof FilterChain) {
$parts[] = '(' . $filter . ')';
} else {
$parts[] = (string) $filter;
}
}
$op = ' ' . $this->getOperatorSymbol() . ' ';
return implode($op, $parts);
}
public function __construct($filters = array())
{
foreach ($filters as $filter) {
$this->addFilter($filter);
}
}
public function isEmpty()
{
return empty($this->filters);
}
public function addFilter(Filter $filter)
{
$this->filters[] = $filter;
$filter->setId($this->getId() . '-' . (count($this->filters)));
}
public function &filters()
{
return $this->filters;
}
public function __clone()
{
foreach ($this->filters as & $filter) {
$filter = clone $filter;
}
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace Icinga\Data\Filter;
class FilterEqual extends FilterExpression
{
public function matches($row)
{
return (string) $row->{$this->column} === (string) $this->expression;
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace Icinga\Data\Filter;
class FilterEqualOrGreaterThan extends FilterExpression
{
public function matches($row)
{
return (string) $row->{$this->column} >= (string) $this->expression;
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace Icinga\Data\Filter;
class FilterEqualOrLessThan extends FilterExpression
{
public function __toString()
{
return $this->column . ' <= ' . $this->expression;
}
public function toQueryString()
{
return $this->column . '<=' . $this->expression;
}
public function matches($row)
{
return (string) $row->{$this->column} <= (string) $this->expression;
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace Icinga\Data\Filter;
use Exception;
/**
* Filter Exception Class
*
* Filter Exceptions should be thrown on filter parse errors or similar
*/
class FilterException extends Exception {}

View File

@ -0,0 +1,96 @@
<?php
namespace Icinga\Data\Filter;
class FilterExpression extends Filter
{
protected $column;
protected $sign;
protected $expression;
public function __construct($column, $sign, $expression)
{
$this->column = $column;
$this->sign = $sign;
$this->expression = $expression;
}
public function isEmpty()
{
return false;
}
public function getColumn()
{
return $this->column;
}
public function getSign()
{
return $this->sign;
}
public function setColumn($column)
{
$this->column = $column;
return $this;
}
public function getExpression()
{
return $this->expression;
}
public function setExpression($expression)
{
$this->expression = $expression;
return $this;
}
public function setSign($sign)
{
if ($sign !== $this->sign) {
return Filter::expression($this->column, $sign, $this->expression);
}
return $this;
}
public function __toString()
{
$expression = is_array($this->expression) ?
'( ' . implode(' | ', $this->expression) . ' )' :
$this->expression;
return sprintf(
'%s %s %s',
$this->column,
$this->sign,
$expression
);
}
public function toQueryString()
{
$expression = is_array($this->expression) ?
'(' . implode('|', $this->expression) . ')' :
$this->expression;
return $this->column . $this->sign . $expression;
}
public function matches($row)
{
if (is_array($this->expression)) {
return in_array($row->{$this->column}, $this->expression);
} elseif (strpos($this->expression, '*') === false) {
return (string) $row->{$this->column} === (string) $this->expression;
} else {
$parts = preg_split('~\*~', $this->expression);
foreach ($parts as & $part) {
$part = preg_quote($part);
}
$pattern = '/^' . implode('.*', $parts) . '$/';
return (bool) preg_match($pattern, $row->{$this->column});
}
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace Icinga\Data\Filter;
class FilterGreaterThan extends FilterExpression
{
public function matches($row)
{
return (string) $row->{$this->column} > (string) $this->expression;
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace Icinga\Data\Filter;
class FilterLessThan extends FilterExpression
{
public function __toString()
{
return $this->column . ' < ' . $this->expression;
}
public function toQueryString()
{
return $this->column . '<' . $this->expression;
}
public function matches($row)
{
return (string) $row->{$this->column} < (string) $this->expression;
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace Icinga\Data\Filter;
class FilterNot extends FilterChain
{
protected $operatorName = 'NOT';
protected $operatorSymbol = '!'; // BULLSHIT
// TODO: Max count 1 or autocreate sub-and?
public function matches($row)
{
foreach ($this->filters() as $filter) {
if ($filter->matches($row)) {
return false;
}
}
return true;
}
public function toQueryString()
{
$parts = array();
if (empty($this->filters)) {
return '';
}
foreach ($this->filters() as $filter) {
$parts[] = $filter->toQueryString();
}
if (count($parts) === 1) {
return '!' . $parts[0];
} else {
return '!(' . implode('&', $parts) . ')';
}
}
public function __toString()
{
if (count($this->filters) === 1) {
return '! ' . $this->filters[0];
}
return '! (' . implode('&', $this->filters) . ')';
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace Icinga\Data\Filter;
class FilterOr extends FilterChain
{
protected $operatorName = 'OR';
protected $operatorSymbol = '|';
public function matches($row)
{
foreach ($this->filters as $filter) {
if ($filter->matches($row)) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,9 @@
<?php
namespace Icinga\Data\Filter;
use Exception;
class FilterParseException extends Exception
{
}

View File

@ -0,0 +1,295 @@
<?php
namespace Icinga\Data\Filter;
class FilterQueryString
{
protected $string;
protected $pos;
protected $debug = array();
protected $reportDebug = false;
protected $length;
protected function __construct()
{
}
protected function debug($msg, $level = 0, $op = null)
{
if ($op === null) $op = 'NULL';
$this->debug[] = sprintf('%s[%d=%s] (%s): %s', str_repeat('* ', $level), $this->pos, $this->string[$this->pos - 1], $op, $msg);
}
public static function parse($string)
{
$parser = new static();
return $parser->parseQueryString($string);
}
protected function readNextKey()
{
$str = $this->readUnlessSpecialChar();
if ($str === false) {
return $str;
}
return rawurldecode($str);
}
protected function readNextValue()
{
if ($this->nextChar() === '(') {
$this->readChar();
$var = preg_split('~\|~', $this->readUnless(')'));
if ($this->readChar() !== ')') {
$this->parseError(null, 'Expected ")"');
}
} else {
$var = rawurldecode($this->readUnless(array(')', '&', '|', '>', '<')));
}
return $var;
}
protected function readNextExpression()
{
if ('' === ($key = $this->readNextKey())) {
return false;
}
foreach (array('<', '>') as $sign) {
if (false !== ($pos = strpos($key, $sign))) {
if ($this->nextChar() === '=') break;
$var = substr($key, $pos + 1);
$key = substr($key, 0, $pos);
return Filter::expression($key, $sign, $var);
}
}
if (in_array($this->nextChar(), array('=', '>', '<', '!'))) {
$sign = $this->readChar();
} else {
$sign = false;
}
if ($sign === false) {
return Filter::expression($key, '=', true);
}
if ($sign === '=') {
$last = substr($key, -1);
if ($last === '>' || $last === '<') {
$sign = $last . $sign;
$key = substr($key, 0, -1);
}
// TODO: Same as above for unescaped <> - do we really need this?
} elseif ($sign === '>' || $sign === '<' || $sign === '!') {
if ($this->nextChar() === '=') {
$sign .= $this->readChar();
}
}
$var = $this->readNextValue();
return Filter::expression($key, $sign, $var);
}
protected function parseError($char = null, $extraMsg = null)
{
if ($extraMsg === null) {
$extra = '';
} else {
$extra = ': ' . $extraMsg;
}
if ($char === null) {
$char = $this->string[$this->pos];
}
if ($this->reportDebug) {
$extra .= "\n" . implode("\n", $this->debug);
}
throw new FilterParseException(sprintf(
'Invalid filter "%s", unexpected %s at pos %d%s',
$this->string,
$char,
$this->pos,
$extra
));
}
protected function readFilters($nestingLevel = 0, $op = null)
{
$filters = array();
while ($this->pos < $this->length) {
if ($op === '!' && count($filters) === 1) {
break;
}
$filter = $this->readNextExpression();
$next = $this->readChar();
if ($filter === false) {
$this->debug('Got no next expression, next is ' . $next, $nestingLevel, $op);
if ($next === '!') {
$not = $this->readFilters($nestingLevel + 1, '!');
$filters[] = $not;
if (in_array($this->nextChar(), array('|', '&', ')'))) {
$next = $this->readChar();
$this->debug('Got NOT, next is now: ' . $next, $nestingLevel, $op);
} else {
$this->debug('Breaking after NOT: ' . $not, $nestingLevel, $op);
break;
}
}
if ($op === null && count($filters > 0) && ($next === '&' || $next === '|')) {
$op = $next;
continue;
}
if ($next === false) {
// Nothing more to read
break;
}
if ($next === ')') {
if ($nestingLevel > 0) {
$this->debug('Closing without filter: ' . $next, $nestingLevel, $op);
break;
}
$this->parseError($next);
}
if ($next === '(') {
$filters[] = $this->readFilters($nestingLevel + 1, null);
continue;
}
if ($next === $op) {
continue;
}
$this->parseError($next, "$op level $nestingLevel");
} else {
$this->debug('Got new expression: ' . $filter, $nestingLevel, $op);
$filters[] = $filter;
if ($next === false) {
$this->debug('Next is false, nothing to read but got filter', $nestingLevel, $op);
// Got filter, nothing more to read
break;
}
if ($op === '!') {
$this->pos--;
break;
}
if ($next === $op) {
$this->debug('Next matches operator', $nestingLevel, $op);
continue; // Break??
}
if ($next === ')') {
if ($nestingLevel > 0) {
$this->debug('Closing with filter: ' . $next, $nestingLevel, $op);
break;
}
$this->parseError($next);
}
if ($op === null && in_array($next, array('&', '|'))) {
$this->debug('Setting op to ' . $next, $nestingLevel, $op);
$op = $next;
continue;
}
$this->parseError($next);
}
}
if ($nestingLevel === 0 && $this->pos < $this->length) {
$this->parseError($op, 'Did not read full filter');
}
if ($nestingLevel === 0 && count($filters) === 1 && $op !== '!') {
// There is only one filter expression, no chain
$this->debug('Returning first filter only: ' . $filters[0], $nestingLevel, $op);
return $filters[0];
}
if ($op === null && count($filters) === 1) {
$this->debug('No op, single filter, setting AND', $nestingLevel, $op);
$op = '&';
}
$this->debug(sprintf('Got %d filters, returning', count($filters)), $nestingLevel, $op);
switch ($op) {
case '&': return Filter::matchAll($filters);
case '|': return Filter::matchAny($filters);
case '!': return Filter::not($filters);
case null: return Filter::matchAll();
default: $this->parseError($op);
}
}
protected function parseQueryString($string)
{
$this->pos = 0;
$this->string = $string;
$this->length = strlen($string);
if ($this->length === 0) {
return Filter::matchAll();
}
return $this->readFilters();
}
protected function readUnless($char)
{
$buffer = '';
while (false !== ($c = $this->readChar())) {
if (is_array($char)) {
if (in_array($c, $char)) {
$this->pos--;
break;
}
} else {
if ($c === $char) {
$this->pos--;
break;
}
}
$buffer .= $c;
}
return $buffer;
}
protected function readUnlessSpecialChar()
{
return $this->readUnless(array('=', '(', ')', '&', '|', '>', '<', '!'));
}
protected function readExpressionOperator()
{
return $this->readUnless(array('=', '>', '<', '!'));
}
protected function readChar()
{
if ($this->length > $this->pos) {
return $this->string[$this->pos++];
}
return false;
}
protected function nextChar()
{
if ($this->length > $this->pos) {
return $this->string[$this->pos];
}
return false;
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace Icinga\Data;
use Icinga\Data\Filter\Filter;
/**
* Interface for filtering a result set
*/
interface Filterable
{
public function applyFilter(Filter $filter);
public function setFilter(Filter $filter);
public function getFilter();
public function addFilter(Filter $filter);
public function where($condition, $value = null);
}

View File

@ -0,0 +1,47 @@
<?php
namespace Icinga\Data;
/**
* Interface for retrieving just a portion of a result set
*/
interface Limitable
{
/**
* Set a limit count and offset
*
* @param int $count Number of rows to return
* @param int $offset Start returning after this many rows
*
* @return self
*/
public function limit($count = null, $offset = null);
/**
* Whether a limit is set
*
* @return bool
*/
public function hasLimit();
/**
* Get the limit if any
*
* @return int|null
*/
public function getLimit();
/**
* Whether an offset is set
*
* @return bool
*/
public function hasOffset();
/**
* Get the offset if any
*
* @return int|null
*/
public function getOffset();
}

View File

@ -4,31 +4,31 @@
namespace Icinga\Data; namespace Icinga\Data;
use \Zend_Paginator; use Icinga\Data\SimpleQuery;
use Icinga\Data\BaseQuery;
use Icinga\Application\Icinga; use Icinga\Application\Icinga;
use Icinga\Web\Paginator\Adapter\QueryAdapter; use Icinga\Web\Paginator\Adapter\QueryAdapter;
use Zend_Paginator;
class PivotTable class PivotTable
{ {
/** /**
* The query to fetch as pivot table * The query to fetch as pivot table
* *
* @var BaseQuery * @var SimpleQuery
*/ */
protected $baseQuery; protected $baseQuery;
/** /**
* The query to fetch the x axis labels * The query to fetch the x axis labels
* *
* @var BaseQuery * @var SimpleQuery
*/ */
protected $xAxisQuery; protected $xAxisQuery;
/** /**
* The query to fetch the y axis labels * The query to fetch the y axis labels
* *
* @var BaseQuery * @var SimpleQuery
*/ */
protected $yAxisQuery; protected $yAxisQuery;
@ -49,11 +49,11 @@ class PivotTable
/** /**
* Create a new pivot table * Create a new pivot table
* *
* @param BaseQuery $query The query to fetch as pivot table * @param SimpleQuery $query The query to fetch as pivot table
* @param string $xAxisColumn The column that contains the labels for the x axis * @param string $xAxisColumn The column that contains the labels for the x axis
* @param string $yAxisColumn The column that contains the labels for the y axis * @param string $yAxisColumn The column that contains the labels for the y axis
*/ */
public function __construct(BaseQuery $query, $xAxisColumn, $yAxisColumn) public function __construct(SimpleQuery $query, $xAxisColumn, $yAxisColumn)
{ {
$this->baseQuery = $query; $this->baseQuery = $query;
$this->xAxisColumn = $xAxisColumn; $this->xAxisColumn = $xAxisColumn;
@ -70,10 +70,10 @@ class PivotTable
{ {
$this->xAxisQuery = clone $this->baseQuery; $this->xAxisQuery = clone $this->baseQuery;
$this->xAxisQuery->distinct(); $this->xAxisQuery->distinct();
$this->xAxisQuery->setColumns(array($this->xAxisColumn)); $this->xAxisQuery->columns(array($this->xAxisColumn));
$this->yAxisQuery = clone $this->baseQuery; $this->yAxisQuery = clone $this->baseQuery;
$this->yAxisQuery->distinct(); $this->yAxisQuery->distinct();
$this->yAxisQuery->setColumns(array($this->yAxisColumn)); $this->yAxisQuery->columns(array($this->yAxisColumn));
return $this; return $this;
} }
@ -85,9 +85,9 @@ class PivotTable
*/ */
protected function adjustSorting() protected function adjustSorting()
{ {
$currentOrderColumns = $this->baseQuery->getOrderColumns(); $currentOrderColumns = $this->baseQuery->getOrder();
$xAxisOrderColumns = array(array($this->baseQuery->getMappedField($this->xAxisColumn), BaseQuery::SORT_ASC)); $xAxisOrderColumns = array(array($this->baseQuery->getMappedField($this->xAxisColumn), SimpleQuery::SORT_ASC));
$yAxisOrderColumns = array(array($this->baseQuery->getMappedField($this->yAxisColumn), BaseQuery::SORT_ASC)); $yAxisOrderColumns = array(array($this->baseQuery->getMappedField($this->yAxisColumn), SimpleQuery::SORT_ASC));
foreach ($currentOrderColumns as $orderInfo) { foreach ($currentOrderColumns as $orderInfo) {
if ($orderInfo[0] === $xAxisOrderColumns[0][0]) { if ($orderInfo[0] === $xAxisOrderColumns[0][0]) {
@ -99,9 +99,10 @@ class PivotTable
$yAxisOrderColumns[] = $orderInfo; $yAxisOrderColumns[] = $orderInfo;
} }
} }
//TODO: simplify this whole function. No need to care about mapping
$this->xAxisQuery->setOrderColumns($xAxisOrderColumns); // foreach ($xAxisOrderColumns as
$this->yAxisQuery->setOrderColumns($yAxisOrderColumns); // $this->xAxisQuery->setOrder($xAxisOrderColumns);
// $this->yAxisQuery->setOrder($yAxisOrderColumns);
return $this; return $this;
} }

View File

@ -0,0 +1,7 @@
<?php
namespace Icinga\Data;
use Countable;
interface QueryInterface extends Browsable, Fetchable, Filterable, Limitable, Sortable, Countable {};

View File

@ -0,0 +1,19 @@
<?php
namespace Icinga\Data;
/**
* Interface for specifying data sources
*/
interface Queryable
{
/**
* Set the target and fields to query
*
* @param string $target
* @param array $fields
*
* @return Fetchable
*/
public function from($target, array $fields = null);
}

View File

@ -33,7 +33,7 @@ use Icinga\Exception\ProgrammingError;
use Zend_Config; use Zend_Config;
use Icinga\Util\ConfigAwareFactory; use Icinga\Util\ConfigAwareFactory;
use Icinga\Exception\ConfigurationError; use Icinga\Exception\ConfigurationError;
use Icinga\Data\Db\Connection as DbConnection; use Icinga\Data\Db\DbConnection;
use Icinga\Protocol\Livestatus\Connection as LivestatusConnection; use Icinga\Protocol\Livestatus\Connection as LivestatusConnection;
use Icinga\Protocol\Statusdat\Reader as StatusdatReader; use Icinga\Protocol\Statusdat\Reader as StatusdatReader;
use Icinga\Protocol\Ldap\Connection as LdapConnection; use Icinga\Protocol\Ldap\Connection as LdapConnection;

View File

@ -0,0 +1,16 @@
<?php
namespace Icinga\Data;
/**
* Interface for classes providing a data source to fetch data from
*/
interface Selectable
{
/**
* Provide a data source to fetch data from
*
* @return Queryable
*/
public function select();
}

View File

@ -0,0 +1,414 @@
<?php
namespace Icinga\Data;
use Icinga\Application\Icinga;
use Icinga\Data\Filter\Filter;
use Icinga\Web\Paginator\Adapter\QueryAdapter;
use Zend_Paginator;
use Exception;
class SimpleQuery implements QueryInterface, Queryable
{
/**
* Query data source
*
* @var mixed
*/
protected $ds;
/**
* The table you are going to query
*/
protected $table;
/**
* The columns you asked for
*
* All columns if null, no column if empty??? Alias handling goes here!
*
* @var array
*/
protected $desiredColumns = array();
/**
* The columns you are interested in
*
* All columns if null, no column if empty??? Alias handling goes here!
*
* @var array
*/
protected $columns = array();
/**
* The columns you're using to sort the query result
*
* @var array
*/
protected $order = array();
/**
* Number of rows to return
*
* @var int
*/
protected $limitCount;
/**
* Result starts with this row
*
* @var int
*/
protected $limitOffset;
protected $filter;
/**
* Constructor
*
* @param mixed $ds
*/
public function __construct($ds, $columns = null)
{
$this->ds = $ds;
$this->filter = Filter::matchAll();
if ($columns !== null) {
$this->desiredColumns = $columns;
}
$this->init();
if ($this->desiredColumns !== null) {
$this->columns($this->desiredColumns);
}
}
/**
* Initialize query
*
* Overwrite this instead of __construct (it's called at the end of the construct) to
* implement custom initialization logic on construction time
*/
protected function init() {}
/**
* Get the data source
*
* @return mixed
*/
public function getDatasource()
{
return $this->ds;
}
/**
* Choose a table and the colums you are interested in
*
* Query will return all available columns if none are given here
*
* @return self
*/
public function from($target, array $fields = null)
{
$this->target = $target;
if ($fields !== null) {
$this->columns($fields);
}
return $this;
}
/**
* Add a where condition to the query by and
*
* The syntax of the condition and valid values are defined by the concrete backend-specific query implementation.
*
* @param string $condition
* @param mixed $value
*
* @return self
*/
public function where($condition, $value = null)
{
// TODO: more intelligence please
$this->filter->addFilter(Filter::expression($condition, '=', $value));
return $this;
}
public function getFilter()
{
return $this->filter;
}
public function applyFilter(Filter $filter)
{
return $this->addFilter($filter);
}
public function addFilter(Filter $filter)
{
$this->filter->addFilter($filter);
return $this;
}
public function setFilter(Filter $filter)
{
$this->filter = $filter;
return $this;
}
public function setOrderColumns(array $orderColumns)
{
throw new Exception('This function does nothing and will be removed');
}
/**
* Sort result set by the given field (and direction)
*
* Preferred usage:
* <code>
* $query->order('field, 'ASC')
* </code>
*
* @param string $field
* @param string $direction
*
* @return self
*/
public function order($field, $direction = null)
{
if ($direction === null) {
$fieldAndDirection = explode(' ', $field, 2);
if (count($fieldAndDirection) === 1) {
$direction = self::SORT_ASC;
} else {
$field = $fieldAndDirection[0];
$direction = (strtoupper(trim($fieldAndDirection[1])) === 'DESC') ?
Sortable::SORT_DESC : Sortable::SORT_ASC;
}
} else {
switch (($direction = strtoupper($direction))) {
case Sortable::SORT_ASC:
case Sortable::SORT_DESC:
break;
default:
$direction = Sortable::SORT_ASC;
break;
}
}
$this->order[] = array($field, $direction);
return $this;
}
public function compare($a, $b, $col_num = 0)
{
// Last column to sort reached, rows are considered being equal
if (! array_key_exists($col_num, $this->order)) {
return 0;
}
$col = $this->order[$col_num][0];
$dir = $this->order[$col_num][1];
// TODO: throw Exception if column is missing
//$res = strnatcmp(strtolower($a->$col), strtolower($b->$col));
$res = strcmp(strtolower($a->$col), strtolower($b->$col));
if ($res === 0) {
// return $this->compare($a, $b, $col_num++);
if (array_key_exists(++$col_num, $this->order)) {
return $this->compare($a, $b, $col_num);
} else {
return 0;
}
}
if ($dir === self::SORT_ASC) {
return $res;
} else {
return $res * -1;
}
}
/**
* Whether an order is set
*
* @return bool
*/
public function hasOrder()
{
return !empty($this->order);
}
/**
* Get the order if any
*
* @return array|null
*/
public function getOrder()
{
return $this->order;
}
/**
* Set a limit count and offset to the query
*
* @param int $count Number of rows to return
* @param int $offset Start returning after this many rows
*
* @return self
*/
public function limit($count = null, $offset = null)
{
$this->limitCount = $count !== null ? (int) $count : null;
$this->limitOffset = (int) $offset;
return $this;
}
/**
* Whether a limit is set
*
* @return bool
*/
public function hasLimit()
{
return $this->limitCount !== null;
}
/**
* Get the limit if any
*
* @return int|null
*/
public function getLimit()
{
return $this->limitCount;
}
/**
* Whether an offset is set
*
* @return bool
*/
public function hasOffset()
{
return $this->limitOffset > 0;
}
/**
* Get the offset if any
*
* @return int|null
*/
public function getOffset()
{
return $this->limitOffset;
}
/**
* Paginate data
*
* Auto-detects pagination parameters from request when unset
*
* @param int $itemsPerPage Number of items per page
* @param int $pageNumber Current page number
*
* @return Zend_Paginator
*/
public function paginate($itemsPerPage = null, $pageNumber = null)
{
if ($itemsPerPage === null || $pageNumber === null) {
// Detect parameters from request
$request = Icinga::app()->getFrontController()->getRequest();
if ($itemsPerPage === null) {
$itemsPerPage = $request->getParam('limit', 20);
}
if ($pageNumber === null) {
$pageNumber = $request->getParam('page', 0);
}
}
$this->limit($itemsPerPage, $pageNumber * $itemsPerPage);
$paginator = new Zend_Paginator(new QueryAdapter($this));
$paginator->setItemCountPerPage($itemsPerPage);
$paginator->setCurrentPageNumber($pageNumber);
return $paginator;
}
/**
* Retrieve an array containing all rows of the result set
*
* @return array
*/
public function fetchAll()
{
return $this->ds->fetchAll($this);
}
/**
* Fetch the first row of the result set
*
* @return mixed
*/
public function fetchRow()
{
return $this->ds->fetchRow($this);
}
/**
* Fetch a column of all rows of the result set as an array
*
* @param int $columnIndex Index of the column to fetch
*
* @return array
*/
public function fetchColumn($columnIndex = 0)
{
return $this->ds->fetchColumn($this, $columnIndex);
}
/**
* Fetch the first column of the first row of the result set
*
* @return string
*/
public function fetchOne()
{
return $this->ds->fetchOne($this);
}
/**
* Fetch all rows of the result set as an array of key-value pairs
*
* The first column is the key, the second column is the value.
*
* @return array
*/
public function fetchPairs()
{
return $this->ds->fetchPairs($this);
}
/**
* Count all rows of the result set
*
* @return int
*/
public function count()
{
return $this->ds->count($this);
}
/**
* Set columns
*
* @param array $columns
*
* @return self
*/
public function columns(array $columns)
{
$this->columns = $columns;
return $this;
}
public function getColumns()
{
return $this->columns;
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace Icinga\Data;
/**
* Interface for sorting a result set
*/
interface Sortable
{
/**
* Sort ascending
*/
const SORT_ASC = 'ASC';
/**
* Sort descending
*/
const SORT_DESC = 'DESC';
/**
* Sort result set by the given field (and direction)
*
* Preferred usage:
* <code>
* $query->order('field, 'ASC')
* </code>
*
* @param string $field
* @param string $direction
*
* @return self
*/
public function order($field, $direction = null);
/**
* Whether an order is set
*
* @return bool
*/
public function hasOrder();
/**
* Get the order if any
*
* @return array|null
*/
public function getOrder();
}

View File

@ -4,18 +4,15 @@
namespace Icinga\File; namespace Icinga\File;
use Icinga\Data\BaseQuery; use Icinga\Data\Browsable;
class Csv class Csv
{ {
protected $query; protected $query;
protected function __construct() protected function __construct() {}
{
} public static function fromQuery(Browsable $query)
public static function fromQuery(BaseQuery $query)
{ {
$csv = new Csv(); $csv = new Csv();
$csv->query = $query; $csv->query = $query;
@ -32,7 +29,7 @@ class Csv
{ {
$first = true; $first = true;
$csv = ''; $csv = '';
foreach ($this->query->fetchAll() as $row) { foreach ($this->query->getQuery()->fetchAll() as $row) {
if ($first) { if ($first) {
$csv .= implode(',', array_keys((array) $row)) . "\r\n"; $csv .= implode(',', array_keys((array) $row)) . "\r\n";
$first = false; $first = false;

View File

@ -1,142 +0,0 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
/**
* This file is part of Icinga Web 2.
*
* Icinga Web 2 - Head for multiple monitoring backends.
* Copyright (C) 2013 Icinga Development Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* @copyright 2013 Icinga Development Team <info@icinga.org>
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
* @author Icinga Development Team <info@icinga.org>
*
*/
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Filter;
use Icinga\Filter\Query\Node;
/**
* A Filter domain represents an object that supports filter operations and is basically a
* container for filter attribute
*
*/
class Domain extends QueryProposer
{
/**
* The label to filter for
*
* @var string
*/
private $label;
/**
* @var array
*/
private $attributes = array();
/**
* Create a new domain identified by the given label
*
* @param $label
*/
public function __construct($label)
{
$this->label = trim($label);
}
/**
* Return true when this domain handles a given query (even if it's incomplete)
*
* @param String $query The query to test this domain with
* @return bool True if this domain can handle the query
*/
public function handlesQuery($query)
{
$query = trim($query);
return stripos($query, $this->label) === 0;
}
/**
* Register an attribute to be handled for this filter domain
*
* @param FilterAttribute $attr The attribute object to add to the filter
* @return self Fluent interface
*/
public function registerAttribute(FilterAttribute $attr)
{
$this->attributes[] = $attr;
return $this;
}
/**
* Return proposals for the given query part
*
* @param String $query The part of the query that this specifier should parse
* @return array An array containing 0..* proposal text tokens
*/
public function getProposalsForQuery($query)
{
$query = trim($query);
if ($this->handlesQuery($query)) {
// remove domain portion of the query
$query = trim(substr($query, strlen($this->label)));
}
$proposals = array();
foreach ($this->attributes as $attributeHandler) {
$proposals = array_merge($proposals, $attributeHandler->getProposalsForQuery($query));
}
return $proposals;
}
/**
* Return the label identifying this domain
*
* @return string the label for this domain
*/
public function getLabel()
{
return $this->label;
}
/**
* Create a query tree node representing the given query and using the field given as
* $leftOperand as the attribute (left leaf of the tree)
*
* @param String $query The query to create the node from
* @param String $leftOperand The attribute use for the node
* @return Node|null
*/
public function convertToTreeNode($query)
{
if ($this->handlesQuery($query)) {
// remove domain portion of the query
$query = trim(substr($query, strlen($this->label)));
}
foreach ($this->attributes as $attributeHandler) {
if ($attributeHandler->isValidQuery($query)) {
$node = $attributeHandler->convertToTreeNode($query);
return $node;
}
}
return null;
}
}

View File

@ -1,296 +0,0 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
/**
* This file is part of Icinga Web 2.
*
* Icinga Web 2 - Head for multiple monitoring backends.
* Copyright (C) 2013 Icinga Development Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* @copyright 2013 Icinga Development Team <info@icinga.org>
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
* @author Icinga Development Team <info@icinga.org>
*
*/
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Filter;
use Icinga\Filter\Query\Tree;
use Icinga\Filter\Query\Node;
/**
* Class for filter input and query parsing
*
* This class handles the top level parsing of queries, i.e.
* - Splitting queries at conjunctions and parsing them part by part
* - Delegating the query parts to specific filter domains handling this filters
* - Building a query tree that allows to convert a filter representation into others
* (url to string, string to url, sql..)
*
* Filters are split in Filter Domains, Attributes and Types:
*
* Attribute
* Domain | FilterType
* _|__ _|_ ______|____
* / \/ \/ \
* Host name is not 'test'
*
*/
class Filter extends QueryProposer
{
/**
* The default domain to use, if not set the first added domain
*
* @var null
*/
private $defaultDomain = null;
/**
* An array containing all query parts that couldn't be parsed
*
* @var array
*/
private $ignoredQueryParts = array();
/**
* An array containing all domains of this filter
*
* @var array
*/
private $domains = array();
/**
* Create a new domain and return it
*
* @param String $name The field to be handled by this domain
*
* @return Domain The created domain object
*/
public function createFilterDomain($name)
{
$domain = new Domain(trim($name));
$this->domains[] = $domain;
return $domain;
}
/**
* Set the default domain (used if no domain identifier is given to the query) to the given one
*
* @param Domain $domain The domain to use as the default. Will be added to the domain list if not present yet
*/
public function setDefaultDomain(Domain $domain)
{
if (!in_array($domain, $this->domains)) {
$this->domains[] = $domain;
}
$this->defaultDomain = $domain;
}
/**
* Return the default domaon
*
* @return Domain Return either the domain that has been explicitly set as the default domain or the first
* added. If no domain has been added yet null is returned
*/
public function getDefaultDomain()
{
if ($this->defaultDomain !== null) {
return $this->defaultDomain;
} elseif (count($this->domains) > 0) {
return $this->domains[0];
}
return null;
}
/**
* Add a domain to this filter
*
* @param Domain $domain The domain to add
* @return self Fluent interface
*/
public function addDomain(Domain $domain)
{
$this->domains[] = $domain;
return $this;
}
/**
* Return all domains that could match the given query
*
* @param String $query The query to search matching domains for
*
* @return array An array containing 0..* domains that could handle the query
*/
public function getDomainsForQuery($query)
{
$domains = array();
foreach ($this->domains as $domain) {
if ($domain->handlesQuery($query)) {
$domains[] = $domain;
}
}
return $domains;
}
/**
* Return the first domain matching for this query (or the default domain)
*
* @param String $query The query to search for a domain
* @return Domain A matching domain or the default domain if no domain is matching
*/
public function getFirstDomainForQuery($query)
{
$domains = $this->getDomainsForQuery($query);
if (empty($domains)) {
$domain = $this->getDefaultDomain();
} else {
$domain = $domains[0];
}
return $domain;
}
/**
* Return proposals for the given query part
*
* @param String $query The part of the query that this specifier should parse
*
* @return array An array containing 0..* proposal text tokens
*/
public function getProposalsForQuery($query)
{
$query = $this->getLastQueryPart($query);
$proposals = array();
$domains = $this->getDomainsForQuery($query);
foreach ($domains as $domain) {
$proposals = array_merge($proposals, $domain->getProposalsForQuery($query));
}
if (empty($proposals) && $this->getDefaultDomain()) {
foreach ($this->domains as $domain) {
if (stripos($domain->getLabel(), $query) === 0 || $query == '') {
$proposals[] = self::markDifference($domain->getLabel(), $query);
}
}
$proposals = array_merge($proposals, $this->getDefaultDomain()->getProposalsForQuery($query));
}
return array_unique($proposals);
}
/**
* Split the query at the next conjunction and return a 3 element array containing (left, conjunction, right)
*
* @param $query The query to split
* @return array An three element tupel in the form array($left, $conjunction, $right)
*/
private function splitQueryAtNextConjunction($query)
{
$delimiter = array('AND'/*, 'OR'*/); // or is not supported currently
$inStr = false;
for ($i = 0; $i < strlen($query); $i++) {
// Skip strings
$char = $query[$i];
if ($inStr) {
if ($char == $inStr) {
$inStr = false;
}
continue;
}
if ($char === '\'' || $char === '"') {
$inStr = $char;
continue;
}
foreach ($delimiter as $delimiterString) {
$delimiterLength = strlen($delimiterString);
if (strtoupper(substr($query, $i, $delimiterLength)) === $delimiterString) {
// Delimiter, split into left, middle, right part
$nextPartOffset = $i + $delimiterLength;
$left = substr($query, 0, $i);
$conjunction = $delimiterString;
$right = substr($query, $nextPartOffset);
return array(trim($left), $conjunction, trim($right));
}
}
}
return array($query, null, null);
}
/**
* Return the last part of the query
*
* Mostly required for generating query proposals
*
* @param $query The query to scan for the last part
* @return mixed An string containing the rightmost query
*/
private function getLastQueryPart($query)
{
$right = $query;
do {
list($left, $conjuction, $right) = $this->splitQueryAtNextConjunction($right);
} while ($conjuction !== null);
return $left;
}
/**
* Create a query tree containing this filter
*
* Query parts that couldn't be parsed can be retrieved with Filter::getIgnoredQueryParts
*
* @param String $query The query string to parse into a query tree
* @return Tree The resulting query tree (empty for invalid queries)
*/
public function createQueryTreeForFilter($query)
{
$this->ignoredQueryParts = array();
$right = $query;
$domain = null;
$tree = new Tree();
do {
list($left, $conjunction, $right) = $this->splitQueryAtNextConjunction($right);
$domain = $this->getFirstDomainForQuery($left);
if ($domain === null) {
$this->ignoredQueryParts[] = $left;
continue;
}
$node = $domain->convertToTreeNode($left);
if (!$node) {
$this->ignoredQueryParts[] = $left;
continue;
}
$tree->insert($node);
if ($conjunction === 'AND') {
$tree->insert(Node::createAndNode());
} elseif ($conjunction === 'OR') {
$tree->insert(Node::createOrNode());
}
} while ($right !== null);
return $tree;
}
/**
* Return all parts that couldn't be parsed in the last createQueryTreeForFilter run
*
* @return array An array containing invalid/non-parseable query strings
*/
public function getIgnoredQueryParts()
{
return $this->ignoredQueryParts;
}
}

Some files were not shown because too many files have changed in this diff Show More