Move libraries from incubator to working tree for evaluation

Add all untested files from incubator's library/Icinga to working
tree library/Icinga

refs #4257
This commit is contained in:
Jannis Moßhammer 2013-06-07 11:44:37 +02:00 committed by Eric Lippmann
parent 1b600a8dd3
commit 54ccb9b12a
69 changed files with 6906 additions and 0 deletions

View File

@ -0,0 +1,268 @@
<?php
/**
* Icinga Application Bootstrap class
*
* @package Icinga\Application
*/
namespace Icinga\Application;
use Icinga\Application\Modules\Manager as ModuleManager;
use Icinga\Application\Platform;
use Zend_Loader_Autoloader as ZendLoader;
use Icinga\Exception\ConfigurationError;
/**
* This class bootstraps a thin Icinga application layer
*
* Usage example for CLI:
* <code>
* use Icinga\Application\Cli;
* Cli::start();
* </code>
*
* Usage example for Icinga Web application:
* <code>
* use Icinga\Application\Web;
* Web::start()->dispatch();
* </code>
*
* Usage example for Icinga-Web 1.x compatibility mode:
* <code>
* use Icinga\Application\LegacyWeb;
* LegacyWeb::start()->setIcingaWebBasedir(ICINGAWEB_BASEDIR)->dispatch();
* </code>
*
* @copyright Copyright (c) 2013 Icinga-Web Team <info@icinga.org>
* @author Icinga-Web Team <info@icinga.org>
* @package Icinga\Application
* @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
*/
abstract class ApplicationBootstrap
{
protected $loader;
protected $libdir;
protected $config;
protected $configFile;
protected $appdir;
protected $moduleManager;
protected $isCli = false;
protected $isWeb = false;
/**
* Constructor
*
* The constructor is protected to avoid incorrect usage
*
* @return void
*/
protected function __construct($configFile = null)
{
$this->checkPrerequisites();
$this->libdir = realpath(dirname(dirname(dirname(__FILE__))));
require $this->libdir . '/Icinga/Application/Loader.php';
if (! defined('ICINGA_LIBDIR')) {
define('ICINGA_LIBDIR', $this->libdir);
}
// TODO: Make appdir configurable for packagers
$this->appdir = realpath(dirname($this->libdir) . '/application');
if (! defined('ICINGA_APPDIR')) {
define('ICINGA_APPDIR', $this->appdir);
}
$this->loader = Loader::register();
$this->registerZendAutoloader();
Benchmark::measure('Bootstrap, autoloader registered');
Icinga::setApp($this);
// Unfortunately this is needed to get the Zend Plugin loader working:
set_include_path(
implode(
PATH_SEPARATOR,
array($this->libdir, get_include_path())
)
);
if ($configFile === null) {
$configFile = dirname($this->libdir) . '/config/icinga.ini';
}
$this->configFile = $configFile;
require_once dirname(__FILE__) . '/functions.php';
}
abstract protected function bootstrap();
public function moduleManager()
{
if ($this->moduleManager === null) {
$this->moduleManager = new ModuleManager($this);
}
return $this->moduleManager;
}
public function getLoader()
{
return $this->loader;
}
protected function loadEnabledModules()
{
$this->moduleManager()->loadEnabledModules();
return $this;
}
public function isCli()
{
return $this->isCli;
}
public function isWeb()
{
return $this->isWeb;
}
public function getApplicationDir($subdir = null)
{
$dir = $this->appdir;
if ($subdir !== null) {
$dir .= '/' . ltrim($subdir, '/');
}
return $dir;
}
public function hasModule($name)
{
return $this->moduleManager()->hasLoaded($name);
}
public function getModule($name)
{
return $this->moduleManager()->getModule($name);
}
public function loadModule($name)
{
return $this->moduleManager()->loadModule($name);
}
public function getConfig()
{
return $this->config;
}
public static function start($config = null)
{
$class = get_called_class();
$obj = new $class();
$obj->bootstrap();
return $obj;
}
/**
* Register the Zend Autoloader
*
* @return self
*/
protected function registerZendAutoloader()
{
require_once 'Zend/Loader/Autoloader.php';
ZendLoader::getInstance();
return $this;
}
/**
* Check whether we have all we need
*
* Pretty useless right now as a namespaces class would not work
* with PHP 5.3
*
* @return self
*/
protected function checkPrerequisites()
{
if (version_compare(phpversion(), '5.3.0', '<') === true) {
die('PHP > 5.3.0 required');
}
return $this;
}
/**
* Check whether a given PHP extension is available
*
* @return boolean
*/
protected function hasExtension($name)
{
if (!extension_loaded($name)) {
if (! @ dl($name)) {
throw new ConfigurationError(
sprintf(
'The PHP extension %s is not available',
$name
)
);
}
}
}
/**
* Load Configuration
*
* @return self
*/
protected function loadConfig()
{
// TODO: add an absolutely failsafe config loader
if (! @is_readable($this->configFile)) {
throw new \Exception('Cannot read config file: ' . $this->configFile);
}
$this->config = Config::getInstance($this->configFile);
return $this;
}
/**
* Configure cache settings
*
* TODO: Right now APC is hardcoded, make this configurable
*
* @return self
*/
protected function configureCache()
{
// TODO: Provide Zend_Cache_Frontend_File for statusdat
//$this->cache = \Zend_Cache::factory('Core', 'Apc');
return $this;
}
/**
* Error handling configuration
*
* @return self
*/
protected function configureErrorHandling()
{
if ($this->config->global->environment == 'development') {
error_reporting(E_ALL | E_NOTICE);
ini_set('display_startup_errors', 1);
ini_set('display_errors', 1);
}
Logger::create($this->config->logging);
return $this;
}
/**
* Set timezone settings
*
* @return self
*/
protected function setTimezone()
{
date_default_timezone_set(
$this->config->{'global'}->get('timezone', 'UTC')
);
return $this;
}
}

View File

@ -0,0 +1,293 @@
<?php
/**
* Icinga\Application\Benchmark class
*/
namespace Icinga\Application;
use Icinga\Util\Format;
/**
* This class provides a simple and lightweight benchmark class
*
* <code>
* Benchmark::measure('Program started');
* // ...do something...
* Benchmark::measure('Task finieshed');
* Benchmark::dump();
* </code>
*/
class Benchmark
{
const TIME = 0x01;
const MEMORY = 0x02;
protected static $instance;
protected $start;
protected $measures = array();
/**
* Add a measurement to your benchmark
*
* The same identifier can also be used multiple times
*
* @param string A comment identifying the current measurement
* @return void
*/
public static function measure($message)
{
self::getInstance()->measures[] = (object) array(
'timestamp' => microtime(true),
'memory_real' => memory_get_usage(true),
'memory' => memory_get_usage(),
'message' => $message
);
}
/**
* Throws all measurements away
*
* This empties your measurement table and allows you to restart your
* benchmark from scratch
*
* @return void
*/
public static function reset()
{
self::$instance = null;
}
/**
* Rerieve benchmark start time
*
* This will give you the timestamp of your first measurement
*
* @return float
*/
public static function getStartTime()
{
return self::getInstance()->start;
}
/**
* Dump benchmark data
*
* Will dump a text table if running on CLI and a simple HTML table
* otherwise. Use Benchmark::TIME and Benchmark::MEMORY to choose whether
* you prefer to show either time or memory or both in your output
*
* @param int Whether to get time and/or memory summary
* @return string
*/
public static function dump($what = null)
{
if (Icinga::app()->isCli()) {
echo self::renderToText($what);
} else {
echo self::renderToHtml($what);
}
}
/**
* Render benchmark data to a simple text table
*
* Use Benchmark::TIME and Icinga::MEMORY to choose whether you prefer to
* show either time or memory or both in your output
*
* @param int Whether to get time and/or memory summary
* @return string
*/
public static function renderToText($what = null)
{
$data = self::prepareDataForRendering($what);
$sep = '+';
$title = '|';
foreach ($data->columns as & $col) {
$col->format = ' %'
. ($col->align === 'right' ? '' : '-')
. $col->maxlen . 's |';
$sep .= str_repeat('-', $col->maxlen) . '--+';
$title .= sprintf($col->format, $col->title);
}
$out = $sep . "\n" . $title . "\n" . $sep . "\n";
foreach ($data->rows as & $row) {
$r = '|';
foreach ($data->columns as $key => & $col) {
$r .= sprintf($col->format, $row[$key]);
}
$out .= $r . "\n";
}
$out .= $sep . "\n";
return $out;
}
/**
* Render benchmark data to a simple HTML table
*
* Use Benchmark::TIME and Benchmark::MEMORY to choose whether you prefer
* to show either time or memory or both in your output
*
* @param int Whether to get time and/or memory summary
* @return string
*/
public static function renderToHtml($what = null)
{
$data = self::prepareDataForRendering($what);
// TODO: Move formatting to CSS file
$style = 'font-family: monospace; font-size: 1.5em; width: 100%';
$html = '<table style="' . $style . '">' . "\n" . '<tr>';
foreach ($data->columns as & $col) {
$html .= sprintf(
'<td align="%s">%s</td>',
$col->align,
htmlspecialchars($col->title)
);
}
$html .= "</tr>\n";
foreach ($data->rows as & $row) {
$html .= '<tr>';
foreach ($data->columns as $key => & $col) {
$html .= sprintf(
'<td align="%s">%s</td>',
$col->align,
$row[$key]
);
}
$html .= "</tr>\n";
}
$html .= "</table>\n";
return $html;
}
/**
* Prepares benchmark data for output
*
* Use Benchmark::TIME and Benchmark::MEMORY to choose whether you prefer
* to have either time or memory or both in your output
*
* @param int Whether to get time and/or memory summary
* @return array
*/
protected static function prepareDataForRendering($what = null)
{
if ($what === null) {
$what = self::TIME | self::MEMORY;
}
$columns = array(
(object) array(
'title' => 'Time',
'align' => 'left',
'maxlen' => 4
),
(object) array(
'title' => 'Description',
'align' => 'left',
'maxlen' => 11
)
);
if ($what & self::TIME) {
$columns[] = (object) array(
'title' => 'Off (ms)',
'align' => 'right',
'maxlen' => 11
);
$columns[] = (object) array(
'title' => 'Dur (ms)',
'align' => 'right',
'maxlen' => 13
);
}
if ($what & self::MEMORY) {
$columns[] = (object) array(
'title' => 'Mem (diff)',
'align' => 'right',
'maxlen' => 10
);
$columns[] = (object) array(
'title' => 'Mem (total)',
'align' => 'right',
'maxlen' => 11
);
}
$bench = self::getInstance();
$last = $bench->start;
$rows = array();
$lastmem = 0;
foreach ($bench->measures as $m) {
$micro = sprintf(
'%03d',
round(($m->timestamp - floor($m->timestamp)) * 1000)
);
$vals = array(
date('H:i:s', $m->timestamp) . '.' . $micro,
$m->message
);
if ($what & self::TIME) {
$m->relative = $m->timestamp - $bench->start;
$m->offset = $m->timestamp - $last;
$last = $m->timestamp;
$vals[] = sprintf('%0.3f', $m->relative * 1000);
$vals[] = sprintf('%0.3f', $m->offset * 1000);
}
if ($what & self::MEMORY) {
$mem = $m->memory - $lastmem;
$lastmem = $m->memory;
$vals[] = Format::bytes($mem);
$vals[] = Format::bytes($m->memory);
}
$row = & $rows[];
foreach ($vals as $col => $val) {
$row[$col] = $val;
$columns[$col]->maxlen = max(
strlen($val),
$columns[$col]->maxlen
);
}
}
return (object) array(
'columns' => $columns,
'rows' => $rows
);
}
/**
* Singleton
*
* Benchmark is run only once, but you are not allowed to directly access
* the getInstance() method
*
* @return self
*/
protected static function getInstance()
{
if (self::$instance === null) {
self::$instance = new Benchmark();
self::$instance->start = microtime(true);
}
return self::$instance;
}
/**
* Constructor
*
* Singleton usage is enforced, the only way to instantiate Benchmark is by
* starting your measurements
*
* @return void
*/
protected function __construct()
{
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace Icinga\Application;
require_once dirname(__FILE__) . '/ApplicationBootstrap.php';
class Cli extends ApplicationBootstrap
{
protected $isCli = true;
protected function bootstrap()
{
$this->assertRunningOnCli();
return $this->loadConfig()
->configureErrorHandling()
->setTimezone();
}
/**
* Fail if Icinga has not been called on CLI
*
* @throws Exception
* @return void
*/
private static function assertRunningOnCli()
{
if (Platform::isCli()) {
return;
}
throw new Exception('Icinga is not running on CLI');
}
}

View File

@ -0,0 +1,76 @@
<?php
namespace Icinga\Application;
use Zend_Config_Ini;
use Zend_Config;
class Config extends Zend_Config_Ini
{
protected $data;
protected static $instance;
protected $configDir;
public function listAll($what)
{
if ($this->$what === null) {
return array();
} else {
return array_keys($this->$what->toArray());
}
}
public function getConfigDir()
{
return $this->configDir;
}
public function __construct($filename, $section = null, $options = false)
{
$options['allowModifications'] = true;
$this->configDir = dirname($filename);
return parent::__construct($filename, $section, $options);
}
public function getModuleConfig($key, $module)
{
$manager = Icinga::app()->moduleManager();
$res = null;
if ($manager->hasInstalled($module)) {
$filename = $manager->getModuleConfigDir($module) . "/$key.ini";
if (file_exists($filename)) {
return $this->$key = new Config($filename);
}
}
return $res;
}
public function __get($key)
{
$res = parent::__get($key);
if ($res === null) {
$app = Icinga::app();
if ($app->hasModule($key)) {
$filename = $app->getModule($key)->getConfigDir() . "/$key.ini";
} else {
$filename = $this->configDir . '/' . $key . '.ini';
}
if (file_exists($filename)) {
$res = $this->$key = new Config($filename);
}
}
return $res;
}
public static function getInstance($configFile = null)
{
if (self::$instance === null) {
if ($configFile === null) {
$configFile = dirname(dirname(dirname(dirname(__FILE__))))
. '/config/icinga.ini';
}
self::$instance = new Config($configFile);
}
return self::$instance;
}
}

View File

@ -0,0 +1,36 @@
<?php
/**
* Run embedded in other web applications
*
* @package Icinga\Application
*/
namespace Icinga\Application;
require_once dirname(__FILE__) . '/ApplicationBootstrap.php';
use Icinga\Exception\ProgrammingError;
/**
* Use this if you want to make use of Icinga funtionality in other web projects
*
* Usage example:
* <code>
* use Icinga\Application\EmbeddedWeb;
* EmbeddedWeb::start();
* </code>
*
* @copyright Copyright (c) 2013 Icinga-Web Team <info@icinga.org>
* @author Icinga-Web Team <info@icinga.org>
* @package Icinga\Application
* @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
*/
class EmbeddedWeb extends ApplicationBootstrap
{
protected function bootstrap()
{
return $this->loadConfig()
->configureErrorHandling()
->setTimezone()
->loadEnabledModules();
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace Icinga\Application;
use Icinga\Exception\ProgrammingError;
class Icinga
{
protected static $app;
public static function app()
{
if (null === self::$app) {
throw new ProgrammingError('Icinga has never been started');
}
return self::$app;
}
public static function setApp($app)
{
if (null !== self::$app) {
throw new ProgrammingError('Cannot start Icinga twice');
}
self::$app = $app;
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace Icinga\Application;
require_once dirname(__FILE__) . '/Web.php';
use Icinga\Exception\ProgrammingError;
class LegacyWeb extends Web
{
// IcingaWeb 1.x base dir
protected $legacyBasedir;
protected function bootstrap()
{
parent::bootstrap();
throw new ProgrammingError('Not yet');
// $this->setupIcingaLegacyWrapper();
}
/**
* Get the Icinga-Web 1.x base path
*
* @throws Exception
* @return self
*/
public function getLecacyBasedir()
{
return $this->legacyBasedir;
}
}

View File

@ -0,0 +1,131 @@
<?php
/**
* Icinga\Application\Loader class
*
* @category Icinga\Application
*/
namespace Icinga\Application;
use Icinga\Application\Log;
/**
* This class provides a simple Autoloader
*
* It takes care of loading classes in the Icinga namespace. You shouldn't need
* to manually instantiate this class, as bootstrapping code will do so for you.
*
* Usage example:
*
* <code>
* Icinga\Application\Loader::register();
* </code>
*
* @copyright Copyright (c) 2013 Icinga-Web Team <info@icinga.org>
* @author Icinga-Web Team <info@icinga.org>
* @package Icinga\Application
* @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
*/
class Loader
{
const NS = '\\';
protected $moduleDirs = array();
private static $instance;
/**
* Register the Icinga autoloader
*
* You could also call getInstance(), this alias function is here to make
* code look better
*
* @return self
*/
public static function register()
{
return self::getInstance();
}
/**
* Singleton
*
* Registers the Icinga autoloader if not already been done
*
* @return self
*/
public static function getInstance()
{
if (self::$instance === null) {
self::$instance = new Loader();
self::$instance->registerAutoloader();
}
return self::$instance;
}
public function addModule($name, $dir)
{
$this->moduleDirs[ucfirst($name)] = $dir;
return $this;
}
/**
* Class loader
*
* Ignores all but classes in the Icinga namespace.
*
* @return boolean
*/
public function loadClass($class)
{
if (strpos($class, 'Icinga' . self::NS) === false) {
return false;
}
$file = str_replace(self::NS, '/', $class) . '.php';
$file = ICINGA_LIBDIR . '/' . $file;
if (! @is_file($file)) {
$parts = preg_split('~\\\~', $class);
array_shift($parts);
$module = $parts[0];
if (array_key_exists($module, $this->moduleDirs)) {
$file = $this->moduleDirs[$module]
. '/'
. implode('/', $parts) . '.php';
if (@is_file($file)) {
require_once $file;
return true;
}
}
// Log::debug('File ' . $file . ' not found');
return false;
}
require_once $file;
return true;
}
/**
* Effectively registers the autoloader the PHP/SPL way
*
* @return void
*/
protected function registerAutoloader()
{
// Not adding ourselves to include_path right now, MAY be faster
/*set_include_path(implode(PATH_SEPARATOR, array(
realpath(dirname(dirname(__FILE__))),
get_include_path(),
)));*/
spl_autoload_register(array($this, 'loadClass'));
}
/**
* Constructor
*
* Singleton usage is enforced, you are also not allowed to overwrite this
* function
*
* @return void
*/
final private function __construct()
{
}
}

View File

@ -0,0 +1,249 @@
<?php
namespace Icinga\Application\Modules;
use Icinga\Application\ApplicationBootstrap;
use Icinga\Data\ArrayDatasource;
use Icinga\Web\Notification;
use Icinga\Exception\ConfigurationError;
// TODO: show whether enabling/disabling modules is allowed by checking enableDir
// perms
class Manager
{
protected $installedBaseDirs;
protected $enabledDirs = array();
protected $loadedModules = array();
protected $index;
protected $app;
protected $enableDir;
public function __construct(ApplicationBootstrap $app)
{
$this->app = $app;
$this->prepareEssentials();
$this->detectEnabledModules();
}
protected function prepareEssentials()
{
$this->enableDir = $this->app->getConfig()->getConfigDir()
. '/enabledModules';
if (! file_exists($this->enableDir) || ! is_dir($this->enableDir)) {
throw new ProgrammingError(
sprintf(
'Missing module directory: %s',
$this->enableDir
)
);
}
}
protected function detectEnabledModules()
{
$fh = opendir($this->enableDir);
while (false !== ($file = readdir($fh))) {
if ($file[0] === '.') {
continue;
}
$link = $this->enableDir . '/' . $file;
if (! is_link($link)) {
continue;
}
$dir = realpath($link);
if (! file_exists($dir) || ! is_dir($dir)) {
continue;
}
$this->enabledDirs[$file] = $dir;
}
}
public function loadEnabledModules()
{
foreach ($this->listEnabledModules() as $name) {
$this->loadModule($name);
}
return $this;
}
public function loadModule($name)
{
if ($this->hasLoaded($name)) {
return $this;
}
$module = new Module($this->app, $name, $this->getModuleDir($name));
$module->register();
$this->loadedModules[$name] = $module;
return $this;
}
public function enableModule($name)
{
if (! $this->hasInstalled($name)) {
throw new ConfigurationError(
sprintf(
"Cannot enable module '%s' as it isn't installed",
$name
)
);
return $this;
}
$target = $this->installedBaseDirs[$name];
$link = $this->enableDir . '/' . $name;
if (! is_writable($this->enableDir)) {
Notification::error("I do not have permissions to enable modules");
return $this;
}
if (@symlink($target, $link)) {
Notification::success("The module $name has been enabled");
} else {
Notification::error("Enabling module $name failed");
}
return $this;
}
public function disableModule($name)
{
if (! $this->hasEnabled($name)) {
return $this;
}
if (! is_writable($this->enableDir)) {
Notification::error("I do not have permissions to disable modules");
return $this;
}
$link = $this->enableDir . '/' . $name;
if (file_exists($link) && is_link($link)) {
if (@unlink($link)) {
Notification::success("The module $name has been disabled");
} else {
Notification::error("Disabling module $name failed");
}
}
return $this;
}
public function getModuleConfigDir($name)
{
return $this->getModuleDir($name, '/config');
}
public function getModuleDir($name, $subdir = '')
{
if ($this->hasEnabled($name)) {
return $this->enabledDirs[$name]. $subdir;
}
if ($this->hasInstalled($name)) {
return $this->installedBaseDirs[$name] . $subdir;
}
throw new ProgrammingError(
sprintf(
'Trying to access uninstalled module dir: %s',
$name
)
);
}
public function hasInstalled($name)
{
if ($this->installedBaseDirs === null) {
$this->detectInstalledModules();
}
return array_key_exists($name, $this->installedBaseDirs);
}
public function hasEnabled($name)
{
return array_key_exists($name, $this->enabledDirs);
}
public function hasLoaded($name)
{
return array_key_exists($name, $this->loadedModules);
}
public function getLoadedModules()
{
return $this->loadedModules;
}
public function getModule($name)
{
if (! $this->hasLoaded($name)) {
throw new ProgrammingError(
sprintf(
'Cannot access module %s as it hasn\'t been loaded',
$name
)
);
}
return $this->loadedModules[$name];
}
public function getModuleInfo()
{
$installed = $this->listInstalledModules();
$info = array();
foreach ($installed as $name) {
$info[] = (object) array(
'name' => $name,
'path' => $this->installedBaseDirs[$name],
'enabled' => $this->hasEnabled($name),
'loaded' => $this->hasLoaded($name)
);
}
return $info;
}
public function select()
{
$ds = new ArrayDatasource($this->getModuleInfo());
return $ds->select();
}
public function listEnabledModules()
{
return array_keys($this->enabledDirs);
}
public function listLoadedModules()
{
return array_keys($this->loadedModules);
}
public function listInstalledModules()
{
if ($this->installedBaseDirs === null) {
$this->detectInstalledModules();
}
return array_keys($this->installedBaseDirs);
}
public function detectInstalledModules()
{
// TODO: Allow multiple paths for installed modules (e.g. web vs pkg)
$basedir = realpath(ICINGA_APPDIR . '/../modules');
$fh = @opendir($basedir);
if ($fh === false) {
return $this;
}
while ($name = readdir($fh)) {
if ($name[0] === '.') {
continue;
}
if (is_dir($basedir . '/' . $name)) {
$this->installedBaseDirs[$name] = $basedir . '/' . $name;
}
}
}
}

View File

@ -0,0 +1,139 @@
<?php
namespace Icinga\Application\Modules;
use Icinga\Application\ApplicationBootstrap;
use Icinga\Web\Hook;
use Zend_Controller_Router_Route as Route;
class Module
{
protected $name;
protected $basedir;
protected $cssdir;
protected $libdir;
protected $localedir;
protected $controllerdir;
protected $registerscript;
protected $app;
public function __construct(ApplicationBootstrap $app, $name, $basedir)
{
$this->app = $app;
$this->name = $name;
$this->basedir = $basedir;
$this->cssdir = $basedir . '/public/css';
$this->libdir = $basedir . '/library';
$this->configdir = $basedir . '/config';
$this->localedir = $basedir . '/application/locale';
$this->controllerdir = $basedir . '/application/controllers';
$this->registerscript = $basedir . '/register.php';
}
public function register()
{
$this->registerLibrary()
->registerWebIntegration()
->runRegisterScript();
return true;
}
public function hasCss()
{
return file_exists($this->getCssFilename());
}
public function getCssFilename()
{
return $this->cssdir . '/module.less';
}
public function getBaseDir()
{
return $this->basedir;
}
public function getConfigDir()
{
return $this->configdir;
}
protected function registerLibrary()
{
if (file_exists($this->libdir) && is_dir($this->libdir)) {
$this->app->getLoader()->addModule($this->name, $this->libdir);
}
return $this;
}
protected function registerLocales()
{
if (file_exists($this->localedir) && is_dir($this->localedir)) {
bindtextdomain($this->name, $this->localedir);
}
return $this;
}
protected function registerWebIntegration()
{
if (! $this->app->isWeb()) {
return $this;
}
if (file_exists($this->controllerdir) && is_dir($this->controllerdir)) {
$this->app->frontController()->addControllerDirectory(
$this->controllerdir,
$this->name
);
}
$this->registerLocales()
->registerRoutes()
->registerMenuEntries();
return $this;
}
protected function registerMenuEntries()
{
$cfg = $this->app
->getConfig()
->getModuleConfig('menu', $this->name);
$view = $this->app->getView();
if ($cfg) {
$view->view->navigation = $cfg->merge($view->view->navigation);
}
return $this;
}
protected function registerRoutes()
{
$this->app->frontController()->getRouter()->addRoute(
$this->name . '_jsprovider',
new Route(
'js/' . $this->name . '/:file',
array(
'controller' => 'static',
'action' =>'javascript',
'moduleName' => $this->name
)
)
);
return $this;
}
protected function runRegisterScript()
{
if (file_exists($this->registerscript)
&& is_readable($this->registerscript)) {
include($this->registerscript);
}
return $this;
}
protected function registerHook($name, $class)
{
Hook::register($name, $class);
return $this;
}
}

View File

@ -0,0 +1,67 @@
<?php
namespace Icinga\Application;
class Platform
{
protected static $domain;
protected static $hostname;
protected static $fqdn;
public static function isWindows()
{
return strtoupper(substr(PHP_OS, 0, 3)) === 'WIN';
}
public static function isLinux()
{
return strtoupper(substr(PHP_OS, 0, 5)) === 'LINUX';
}
public static function isCli()
{
if (PHP_SAPI == 'cli') {
return true;
} elseif ((PHP_SAPI == 'cgi' || PHP_SAPI == 'cgi-fcgi')
&& empty($_SERVER['SERVER_NAME'])) {
return true;
}
return false;
}
public static function getHostname()
{
if (self::$hostname === null) {
self::discoverHostname();
}
return self::$hostname;
}
public static function getDomain()
{
if (self::$domain === null) {
self::discoverHostname();
}
return self::$domain;
}
public static function getFqdn()
{
if (self::$fqdn === null) {
self::discoverHostname();
}
return self::$fqdn;
}
protected static function discoverHostname()
{
self::$hostname = gethostname();
self::$fqdn = gethostbyaddr(gethostbyname(self::$hostname));
if (substr(self::$fqdn, 0, strlen(self::$hostname)) === self::$hostname) {
self::$domain = substr(self::$fqdn, strlen(self::$hostname) + 1);
} else {
self::$domain = array_shift(preg_split('~\.~', self::$hostname, 2));
}
}
}

View File

@ -0,0 +1,147 @@
<?php
namespace Icinga\Application;
class TranslationHelper
{
protected $basedir;
protected $moduledir;
protected $tmpfile;
protected $potfile;
protected $locale;
protected $module;
public function __construct(ApplicationBootstrap $bootstrap, $locale, $module = null)
{
$this->moduledir = $bootstrap->getModuleDir();
if ($module) {
$this->basedir = $bootstrap->getModuleDir($module) . '/application';
} else {
$this->basedir = $bootstrap->getApplicationDir();
}
$this->locale = $locale;
$this->module = $module;
$this->targetfile = $this->basedir
. '/locale/'
. $this->locale
. '/LC_MESSAGES/'
. ($module ? $module : 'icinga')
. '.po';
$target_dir = dirname($this->targetfile);
if (! is_dir($target_dir)) {
mkdir($target_dir, 0755, true);
}
}
public function __destruct()
{
if ($this->tmpfile !== null) {
unlink($this->tmpfile);
}
if ($this->potfile !== null) {
unlink($this->potfile);
}
}
public function extractTexts()
{
$tmpdir = sys_get_temp_dir();
$this->potfile = tempnam($tmpdir, 'IcingaPot_');
$cmd = '/usr/bin/xgettext'
. ' --language=PHP'
. ' --from-code=iso-8859-15'
. ' --keyword='
. ($this->module ? '_mt:2' : '_t')
. ' --sort-output'
. ' --force-po'
. ' --package-name=Icinga'
. ' --package-version=0.1'
. ' --copyright-holder="Icinga Team"'
. ' --msgid-bugs-address="dev@icinga.org"'
. ' --files-from=' . $this->tmpfile
. ' --output=' . $this->potfile
;
`$cmd`;
$this->fixPotfile();
$this->mergeOldTranslations();
return $this;
}
protected function fixPotfile()
{
$content = file_get_contents($this->potfile);
$fh = fopen($this->potfile, 'w');
foreach (preg_split('~\n~', $content) as $line) {
// if (preg_match('~^"Language:~', $line)) continue;
if (preg_match('~^"Content-Type:~', $line)) {
$line = '"Content-Type: text/plain; charset=utf-8\n"';
}
fwrite($fh, $line . "\n");
}
fclose($fh);
}
protected function mergeOldTranslations()
{
if (is_file($this->targetfile)) {
$cmd = sprintf(
'/usr/bin/msgmerge %s %s -o %s 2>&1',
$this->targetfile,
$this->potfile,
$this->targetfile . '.new'
);
`$cmd`;
rename($this->targetfile . '.new', $this->targetfile);
} else {
file_put_contents($this->targetfile, file_get_contents($this->potfile));
}
}
public function createTemporaryFileList()
{
$tmpdir = sys_get_temp_dir();
$this->tmpfile = tempnam($tmpdir, 'IcingaTranslation_');
$tmp_fh = fopen($this->tmpfile, 'w');
if (! $tmp_fh) {
throw new \Exception('Unable to create ' . $this->tmpfile);
}
if ($this->module) {
$blacklist = array();
} else {
$blacklist = array(
$this->moduledir
);
}
$this->getSourceFileNames($this->basedir, $tmp_fh, $blacklist);
$this->getSourceFileNames(ICINGA_LIBDIR, $tmp_fh, $blacklist);
fclose($tmp_fh);
return $this;
}
protected function getSourceFileNames($dir, & $fh, $blacklist = array())
{
$dh = opendir($dir);
if (! $dh) {
throw new \Exception("Unable to read files from $dir");
}
$subdirs = array();
while ($filename = readdir($dh)) {
if ($filename[0] === '.') {
continue;
}
$fullname = $dir . '/' . $filename;
if (preg_match('~\.(?:php|phtml)$~', $filename)) {
fwrite($fh, "$fullname\n");
} elseif (is_dir($fullname)) {
if (in_array($fullname, $blacklist)) {
continue;
}
$subdirs[] = $fullname;
}
}
closedir($dh);
foreach ($subdirs as $dir) {
$this->getSourceFileNames($dir, $fh, $blacklist);
}
}
}

View File

@ -0,0 +1,199 @@
<?php
namespace Icinga\Application;
require_once dirname(__FILE__) . '/ApplicationBootstrap.php';
use Icinga\Web\Session;
use Zend_Controller_Front as FrontController;
use Zend_Layout as Layout;
use Zend_Paginator as Paginator;
use Zend_View_Helper_PaginationControl as PaginationControl;
use Zend_Controller_Action_HelperBroker as ActionHelper;
use Zend_Controller_Router_Route as Route;
/**
* Use this if you want to make use of Icinga funtionality in other web projects
*
* Usage example:
* <code>
* use Icinga\Application\EmbeddedWeb;
* EmbeddedWeb::start();
* </code>
*
* @copyright Copyright (c) 2013 Icinga-Web Team <info@icinga.org>
* @author Icinga-Web Team <info@icinga.org>
* @package Icinga\Application
* @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
*/
class Web extends ApplicationBootstrap
{
protected $view;
protected $frontController;
protected $isWeb = true;
protected function bootstrap()
{
return $this->loadConfig()
->configureErrorHandling()
->setTimezone()
->configureSession()
->configureCache()
->prepareZendMvc()
->loadTranslations()
->loadEnabledModules()
->setupSpecialRoutes()
->configurePagination();
}
protected function setupSpecialRoutes()
{
// TODO: Find a better solution
$this->frontController->getRouter()->addRoute(
'module_overview',
new Route(
'js/modules/list.js',
array(
'controller' =>'static',
'action' =>'modulelist',
)
)
);
return $this;
}
public function frontController()
{
// TODO: ProgrammingError if null
return $this->frontController;
}
public function getView()
{
// TODO: ProgrammingError if null
return $this->view;
}
public function dispatch()
{
$this->dispatchFrontController();
}
/**
* Configure web session settings
*
* @return self
*/
protected function configureSession()
{
Session::setOptions(
array(
// strict requires Zend_Session::start()
'strict' => true,
'cookie_secure' => false,
'name' => $this->config->{'global'}->get(
'session_cookie',
'ICINGA_SID'
),
// Obsolete once moved to Icinga\Web\Session:
'cookie_httponly' => true,
'use_only_cookies' => true,
'hash_function' => true,
'hash_bits_per_character' => 5,
)
);
return $this;
}
protected function loadTranslations()
{
$locale = Session::getInstance()->language;
if (! $locale) {
$locale = 'en_US';
}
putenv('LC_ALL=' . $locale . '.UTF-8');
setlocale(LC_ALL, $locale . '.UTF-8');
bindtextdomain('icinga', ICINGA_APPDIR . '/locale');
textdomain('icinga');
return $this;
}
protected function dispatchFrontController()
{
Session::getInstance();
$this->frontController->dispatch();
return $this;
}
/**
* Prepare Zend MVC Base
*
* @return self
*/
protected function prepareZendMvc()
{
// TODO: Replace Zend_Application:
Layout::startMvc(
array(
'layout' => 'layout',
'layoutPath' => $this->appdir . '/layouts/scripts'
)
);
return $this->prepareFrontController()
->prepareView();
}
protected function prepareFrontController()
{
$this->frontController = FrontController::getInstance()
->setControllerDirectory($this->appdir . '/controllers')
// TODO: Create config option for Load balancers etc:
// ->setBaseurl()
->setParams(
array(
'displayExceptions' => 1
)
);
return $this;
}
protected function prepareView()
{
$view = ActionHelper::getStaticHelper('viewRenderer');
$view->initView();
$view->view->addHelperPath($this->appdir . '/views/helpers');
// TODO: find out how to avoid this additional helper path:
$view->view->addHelperPath($this->appdir . '/views/helpers/layout');
$view->view->setEncoding('UTF-8');
$view->view->headTitle()->prepend(
$this->config->{'global'}->get('project', 'Icinga')
);
$view->view->headTitle()->setSeparator(' :: ');
$view->view->navigation = $this->config->menu;
$this->view = $view;
return $this;
}
/**
* Configure pagination settings
*
* @return self
*/
protected function configurePagination()
{
Paginator::addScrollingStylePrefixPath(
'Icinga_Web_Paginator_ScrollingStyle',
'Icinga/Web/Paginator/ScrollingStyle'
);
Paginator::setDefaultScrollingStyle('SlidingWithBorder');
PaginationControl::setDefaultViewPartial(
array('mixedPagination.phtml','default')
);
return $this;
}
}

View File

@ -0,0 +1,31 @@
<?php
if (function_exists('_')) {
function t($messageId = null)
{
$msg = _($messageId);
if (! $msg) {
return $messageId;
}
return $msg;
}
function mt($domain, $messageId = null)
{
$msg = dgettext($domain, $messageId);
if (! $msg) {
return $messageId;
}
return $msg;
}
} else {
function t($messageId = null)
{
return $messageId;
}
function mt($domain, $messageId = null)
{
return $messageId;
}
}

View File

@ -0,0 +1,83 @@
<?php
namespace Icinga\Authentication;
use Icinga\Exception;
use Zend_Session_Namespace as SessionNamespace;
class Auth
{
protected static $instance;
protected $userInfo;
protected $session;
final private function __construct()
{
$this->session = new SessionNamespace('IcingaAuth');
}
public static function getInstance()
{
if (self::$instance === null) {
self::$instance = new Auth();
}
return self::$instance;
}
public function isAuthenticated()
{
if ($this->userInfo === null) {
if ($sessionInfo = $this->session->userInfo) {
$this->userInfo = $sessionInfo;
}
}
return is_object($this->userInfo) && ! empty($this->userInfo->username);
}
public function getUsername()
{
$this->assertIsAuthenticated();
return $this->userInfo->username;
}
public function getEmail()
{
$this->assertIsAuthenticated();
return $this->userInfo->email;
}
public function setAuthenticatedUser(User $user)
{
$this->userInfo = (object) array(
'username' => $user->username,
'permissions' => $user->getPermissionList(),
'email' => $user->email,
);
$this->session->userInfo = $this->userInfo;
}
public function forgetAuthentication()
{
unset($this->session->userInfo);
$this->userInfo = null;
}
public function hasPermission($route, $flags = 0x01)
{
$this->assertBeingAuthenticated();
if (! array_key_exists($route, $this->userInfo->permissions)) {
return false;
}
return $this->userInfo->permissions[$route] & $flags === $flags;
}
protected function assertIsAuthenticated()
{
if (! $this->isAuthenticated()) {
throw new Exception\ProgrammingError(
'Cannot fetch properties of a non-authenticated user'
);
}
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace Icinga\Authentication;
class Backend
{
protected $userBackend;
public function __construct($config)
{
$this->config = $config;
$userbackend = ucwords(strtolower($config->users->backend));
$class = '\\Icinga\\Authentication\\' . $userbackend . 'UserBackend';
$this->userBackend = new $class($config->users);
}
public function hasUsername($username)
{
return $this->userBackend->hasUsername($username);
}
public function authenticate($username, $password = null)
{
return $this->userBackend->authenticate($username, $password);
}
}

View File

View File

@ -0,0 +1,57 @@
<?php
namespace Icinga\Authentication;
use Icinga\Protocol\Ldap;
class LdapUserBackend extends UserBackend
{
protected $connection;
protected function init()
{
$this->connection = new Ldap\Connection($this->config);
}
public function hasUsername($username)
{
if (! $username) {
return false;
}
return $this->connection->fetchOne(
$this->selectUsername($username)
) === $username;
}
protected function stripAsterisks($string)
{
return str_replace('*', '', $string);
}
protected function selectUsername($username)
{
return $this->connection->select()
->from('user', array('sAMAccountName'))
->where('sAMAccountName', $this->stripAsterisks($username));
}
public function authenticate($username, $password = null)
{
if (empty($username) || empty($password)) {
return false;
}
if (! $this->connection->testCredentials(
$this->connection->fetchDN($this->selectUsername($username)),
$password
)) {
return false;
}
$user = User::create(
$this,
array(
'username' => $username,
)
);
return $user;
}
}

View File

@ -0,0 +1,203 @@
<?php
/**
* Icinga Authentication Storable class
*
* @package Icinga\Authentication
*/
namespace Icinga\Authentication;
/**
* This class represents an abstract storable object
*
* Use this only for objects with unique identifiers. Do not persist such
* objects, they shall be loaded at each request. Storable doesn't care about
* race conditions and doesn't care about the current data in your backend.
*
* @copyright Copyright (c) 2013 Icinga-Web Team <info@icinga.org>
* @author Icinga-Web Team <info@icinga.org>
* @package Icinga\Application
* @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
*/
abstract class Storable
{
protected $key;
/**
* Current Storable properties
*/
protected $props;
/**
* Default property values for this Storable
*
* All allowed properties have to be defined here, otherwise they will be
* rejected
*/
protected $defaultProps = array();
/**
* Properties as they have been once loaded from backend
*/
protected $storedProps = array();
/**
* Whether this storable has been stored in the current state
*/
protected $stored = false;
/**
* Create a new Storable instance, with data loaded from backend
*
* You should NEVER directly use this function unless you are absolutely
* sure on what you are doing.
*
* @param Backend The backend used to load this object from
* @param Array Property array
* @return Storable
*/
public static function create(UserBackend $backend, $props = array())
{
$class = get_called_class();
$object = new $class($props);
return $object;
}
/**
* Override this function for custom cross-value checks before storing it
*
* @return boolean Whether the Storable is valid
*/
public function isValid()
{
return true;
}
/**
* The constructor is protected, you should never override it
*
* Use the available hooks for all the things you need to do at construction
* time
*
* @param Array Property array
* @return void
*/
final protected function __construct($properties = array())
{
$this->assertKeyHasBeenDefined();
$this->props = $this->defaultProps;
foreach ($properties as $key => $val) {
$this->set($key, $val);
}
$this->assertKeyExists();
}
/**
* Get property value, fail unless it exists
*
* @param string Property name
* @return mixed
*/
public function get($key)
{
$this->assertPropertyExists($key);
return $this->props[$key];
return $this;
}
/**
* Set property value, fail unless it exists
*
* @param string Property name
* @param mixed New property value
* @return Storable
*/
protected function set($key, $val)
{
$this->assertPropertyExists($key);
$this->props[$key] = $val;
return $this;
}
/**
* Getter
*
* @param string Property name
* @return mixed
*/
public function __get($key)
{
return $this->get($key);
}
/**
* Setter
*
* @param string Property name
* @param mixed New property value
* @return void
*/
public function __set($key, $val)
{
$this->set($key, $val);
}
/**
* Whether the given property name exist
*
* @param string Property name
* @return boolean
*/
public function __isset($key)
{
return array_key_exists($key, $this->props);
}
/**
* Makes sure that the Storable got it's unique key
*
* @throws \Exception
* @return Storable
*/
protected function assertKeyExists()
{
return $this->assertPropertyExists($this->key);
}
/**
* Makes sure the given property is allowed
*
* @throws \Exception
* @return Storable
*/
protected function assertPropertyExists($key)
{
if (! array_key_exists($key, $this->props)) {
throw new \Exception(
sprintf(
'Storable (%s) has no "%s" property',
get_class($this),
$key
)
);
}
return $this;
}
/**
* Makes sure that the class inheriting Storable defined it's key column
*
* @throws \Exception
* @return Storable
*/
protected function assertKeyHasBeenDefined()
{
if ($this->key === null) {
throw new \Exception(
'Implementation error, Storable needs a valid key'
);
}
return $this;
}
}

View File

@ -0,0 +1,70 @@
<?php
/**
* Icinga Authentication User class
*
* @package Icinga\Authentication
*/
namespace Icinga\Authentication;
/**
* This class represents a user object
*
* TODO: Show some use cases
*
* @copyright Copyright (c) 2013 Icinga-Web Team <info@icinga.org>
* @author Icinga-Web Team <info@icinga.org>
* @package Icinga\Application
* @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
*/
class User extends Storable
{
protected $defaultProps = array(
'username' => null,
'password' => null,
'first_name' => null,
'last_name' => null,
'email' => null,
);
protected $permissions = array();
protected $backend;
protected $groups;
protected $key = 'username';
public function listGroups()
{
if ($this->groups === null) {
$this->loadGroups();
}
}
protected function loadGroups()
{
// Whatever
}
public function isMemberOf(Group $group)
{
}
public function getPermissionList()
{
return $this->permissions;
}
public function hasPermission($uri, $permission)
{
}
public function grantPermission($uri, $permission)
{
}
public function revokePermission($uri, $permission)
{
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace Icinga\Authentication;
class UserBackend
{
protected $config;
public function __construct($config)
{
$this->config = $config;
$this->init();
}
protected function init()
{
}
public function hasUsername($username)
{
return false;
}
public function authenticate($username, $password = null)
{
return false;
}
}

38
library/Icinga/Backend.php Executable file
View File

@ -0,0 +1,38 @@
<?php
namespace Icinga;
use Icinga\Application\Config;
use Icinga\Web\Session;
class Backend
{
protected static $instances = array();
protected function __construct() {}
public static function getInstance($name = null)
{
if (! array_key_exists($name, self::$instances)) {
$config = Config::getInstance()->backends;
if ($name === null) {
$name = Session::getInstance()->backend;
}
if ($name === null) {
$name = array_shift(array_keys($config->toArray()));
}
if (isset($config->backends->$name)) {
$config = $config->backends->$name;
$type = $config->type;
$type[0] = strtoupper($type[0]);
$class = '\\Icinga\\Backend\\' . $type;
self::$instances[$name] = new $class($config);
} else {
throw new \Exception(sprintf(
'Got no config for backend %s',
$name
));
}
}
return self::$instances[$name];
}
}

View File

@ -0,0 +1,53 @@
<?php
namespace Icinga\Backend;
use Icinga\Backend;
class Combo extends AbstractBackend
{
protected $db;
protected $backends;
// TODO: Create a dummy query object to also catch errors with lazy connections
public function from($view, $fields = array())
{
$backends = $this->listMyBackends();
$query = null;
$msg = '';
while ($query === null) {
try {
$backend_name = array_shift($backends);
$msg .= "Trying $backend_name";
$backend = Backend::getInstance($backend_name);
if ($backend->hasView($view)) {
$query = $backend->from($view, $fields);
}
} catch (\Exception $e) {
$msg .= ' Failed: ' . $e->getMessage() . "\n";
}
if ($query !== null) $msg .= " Succeeded.\n";
if ($query === null && empty($backends)) {
throw new \Exception('All backends failed: ' . nl2br($msg));
}
}
return $query;
}
public function hasView($virtual_table)
{
$backends = $this->listMyBackends();
while ($backend_name = array_shift($backends)) {
if (Backend::getInstance($backend_name)->hasView($virtual_table)) {
return true;
}
}
return false;
}
protected function listMyBackends()
{
return preg_split('~,\s*~', $this->config->backends, -1, PREG_SPLIT_NO_EMPTY);
}
}

292
library/Icinga/Backend/Ido.php Executable file
View File

@ -0,0 +1,292 @@
<?php
/**
* Icinga IDO Backend
*
* @package Icinga\Backend
*/
namespace Icinga\Backend;
/**
* This class provides an easy-to-use interface to the IDO database
*
* You should usually not directly use this class but go through Icinga\Backend.
*
* New MySQL indexes:
* <code>
* CREATE INDEX web2_index ON icinga_scheduleddowntime (object_id, is_in_effect);
* CREATE INDEX web2_index ON icinga_comments (object_id);
* CREATE INDEX web2_index ON icinga_objects (object_id, is_active); -- (not sure yet)
* </code>
*
* Other possible (history-related) indexes, still subject to tests:
* CREATE INDEX web2_index ON icinga_statehistory (object_id, state_time DESC);
* CREATE INDEX web2_index ON icinga_notifications (object_id, instance_id, start_time DESC);
* CREATE INDEX web2_index ON icinga_downtimehistory (object_id, actual_start_time, actual_end_time);
*
* @copyright Copyright (c) 2013 Icinga-Web Team <info@icinga.org>
* @author Icinga-Web Team <info@icinga.org>
* @package Icinga\Application
* @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
*/
class Ido extends AbstractBackend
{
protected $db;
protected $dbtype;
protected $prefix = 'icinga_';
/**
* Backend initialization starts here
*
* return void
*/
protected function init()
{
$this->connect();
}
/**
* Get our Zend_Db connection
*
* return \Zend_Db_Adapter_Abstract
*/
public function getAdapter()
{
return $this->db;
}
public function getDbType()
{
return $this->dbtype;
}
/**
* Get our IDO table prefix
*
* return string
*/
public function getPrefix()
{
return $this->prefix;
}
// TODO: Move elsewhere. Really? Reasons may be: other backends need IDO
// access, even in environments running state details without IDO
protected function connect()
{
$this->dbtype = $this->config->get('dbtype', 'mysql');
$options = array(
\Zend_Db::AUTO_QUOTE_IDENTIFIERS => false,
\Zend_Db::CASE_FOLDING => \Zend_Db::CASE_LOWER
);
$drv_options = array(
\PDO::ATTR_TIMEOUT => 2,
// TODO: Check whether LC is useful. Zend_Db does fetchNum for Oci:
\PDO::ATTR_CASE => \PDO::CASE_LOWER
// TODO: ATTR_ERRMODE => ERRMODE_EXCEPTION vs ERRMODE_SILENT
);
switch ($this->dbtype) {
case 'mysql':
$adapter = 'Pdo_Mysql';
$drv_options[\PDO::MYSQL_ATTR_INIT_COMMAND] =
"SET SESSION SQL_MODE='STRICT_ALL_TABLES,NO_ZERO_IN_DATE,"
. "NO_ZERO_DATE,NO_ENGINE_SUBSTITUTION';";
// Not using ONLY_FULL_GROUP_BY as of performance impact
// TODO: NO_ZERO_IN_DATE as been added with 5.1.11. Is it
// ignored by other versions?
$port = $this->config->get('port', 3306);
break;
case 'pgsql':
$adapter = 'Pdo_Pgsql';
$port = $this->config->get('port', 5432);
break;
case 'oracle':
$adapter = 'Pdo_Oci';
// $adapter = 'Oracle';
$port = $this->config->get('port', 1521);
// $drv_options[\PDO::ATTR_STRINGIFY_FETCHES] = true;
if ($adapter === 'Oracle') {
putenv('ORACLE_SID=XE');
putenv('ORACLE_HOME=/u01/app/oracle/product/11.2.0/xe');
putenv('PATH=$PATH:$ORACLE_HOME/bin');
putenv('ORACLE_BASE=/u01/app/oracle');
putenv('NLS_LANG=AMERICAN_AMERICA.UTF8');
}
$this->prefix = '';
break;
default:
throw new \Exception(sprintf(
'Backend "%s" is not supported', $type
));
}
$attributes = array(
'host' => $this->config->host,
'port' => $port,
'username' => $this->config->user,
'password' => $this->config->pass,
'dbname' => $this->config->db,
'options' => $options,
'driver_options' => $drv_options
);
if ($this->dbtype === 'oracle') {
$attributes['persistent'] = true;
}
$this->db = \Zend_Db::factory($adapter, $attributes);
if ($adapter === 'Oracle') {
$this->db->setLobAsString(false);
}
$this->db->setFetchMode(\Zend_Db::FETCH_OBJ);
// $this->db->setFetchMode(\Zend_Db::FETCH_ASSOC);
}
/// *** TODO: EVERYTHING BELOW THIS LINE WILL BE MOVED AWAY *** ///
// UGLY temporary host fetch
public function fetchHost($host)
{
$object = \Icinga\Objects\Service::fromBackend(
$this->select()
->from('servicelist')
->where('so.name1 = ?', $host)
->fetchRow()
);
$object->customvars = $this->fetchCustomvars($host);
return $object;
}
// UGLY temporary service fetch
public function fetchService($host, $service)
{
$object = \Icinga\Objects\Service::fromBackend(
$this->select()
->from('servicelist')
->where('so.name1 = ?', $host)
->where('so.name2 = ?', $service)
->fetchRow()
);
$object->customvars = $this->fetchCustomvars($host, $service);
return $object;
}
public function fetchCustomvars($host, $service = null)
{
if ($this->dbtype === 'oracle') return (object) array();
$select = $this->db->select()->from(
array('cv' => $this->prefix . 'customvariablestatus'),
array(
// 'host_name' => 'cvo.name1',
// 'service_description' => 'cvo.name2',
'name' => 'cv.varname',
'value' => 'cv.varvalue',
)
)->join(
array('cvo' => $this->prefix . 'objects'),
'cvo.object_id = cv.object_id',
array()
);
$select->where('name1 = ?', $host);
if ($service === null) {
$select->where('objecttype_id = 1');
} else {
$select->where('objecttype_id = 1');
$select->where('name2 = ?', $service);
}
$select->where('is_active = 1')->order('cv.varname');
return (object) $this->db->fetchPairs($select);
}
// TODO: Move to module!
public function fetchHardStatesForBpHosts($hosts)
{
return $this->fetchStatesForBp($hosts, 'last_hard_state');
}
public function fetchSoftStatesForBpHosts($hosts)
{
return $this->fetchStatesForBp($hosts, 'current_state');
}
public function fetchStatesForBp($hosts, $state_column = 'last_hard_state')
{
$select_hosts = $this->db->select()->from(
array('hs' => $this->prefix . 'hoststatus'),
array(
'state' => 'hs.' . $state_column,
'ack' => 'hs.problem_has_been_acknowledged',
'in_downtime' => 'CASE WHEN (d.object_id IS NULL) THEN 0 ELSE 1 END',
'combined' => 'hs.current_state << 2 + hs.problem_has_been_acknowledged << 1 + CASE WHEN (d.object_id IS NULL) THEN 0 ELSE 1 END'
)
)->joinRight(
array('o' => $this->prefix . 'objects'),
'hs.host_object_id = o.object_id',
array(
'object_id' => 'o.object_id',
'hostname' => 'o.name1',
'service' => '(NULL)'
)
)->joinLeft(
array('d' => $this->prefix . 'scheduleddowntime'),
'o.object_id = d.object_id'
. ' AND d.was_started = 1'
. ' AND d.scheduled_end_time > NOW()'
. ' AND d.actual_start_time < NOW()',
array()
)->where('o.name1 IN (?)', $hosts)
->where('o.objecttype_id = 1')
->where('o.is_active = 1');
$select_services = $this->db->select()->from(
array('ss' => $this->prefix . 'servicestatus'),
array(
'state' => 'ss.' . $state_column,
'ack' => 'ss.problem_has_been_acknowledged',
'in_downtime' => 'CASE WHEN (d.object_id IS NULL) THEN 0 ELSE 1 END',
'combined' => 'ss.current_state << 2 + ss.problem_has_been_acknowledged << 1 + CASE WHEN (d.object_id IS NULL) THEN 0 ELSE 1 END'
)
)->joinRight(
array('o' => $this->prefix . 'objects'),
'ss.service_object_id = o.object_id',
array(
'object_id' => 'o.object_id',
'hostname' => 'o.name1',
'service' => 'o.name2'
)
)->joinLeft(
array('d' => $this->prefix . 'scheduleddowntime'),
'o.object_id = d.object_id'
. ' AND d.was_started = 1'
. ' AND d.scheduled_end_time > NOW()'
. ' AND d.actual_start_time < NOW()',
array()
)->where('o.name1 IN (?)', $hosts)
->where('o.is_active = 1')
->where('o.objecttype_id = 2');
$union = $this->db->select()->union(
array(
'(' . $select_hosts . ')', // ZF-4338 :-(
'(' . $select_services . ')',
),
// At least on MySQL UNION ALL seems to be faster than UNION in
// most situations, as it doesn't care about duplicates
\Zend_Db_Select::SQL_UNION_ALL
)->order('hostname')->order('service');
return $this->db->fetchAll($union);
}
}

View File

@ -0,0 +1,191 @@
<?php
namespace Icinga\Backend\Ido;
abstract class GroupsummaryQuery extends Query
{
protected $name_alias;
protected $sub_group_column;
protected $sub_query;
protected $sub_count_query;
protected $available_columns = array(
'ok' => 'SUM(CASE WHEN state = 0 THEN 1 ELSE 0 END)',
'critical' => 'SUM(CASE WHEN state = 2 AND downtime = 0 AND ack = 0 THEN 1 ELSE 0 END)',
'critical_dt' => 'SUM(CASE WHEN state = 2 AND downtime = 1 THEN 1 ELSE 0 END)',
'critical_ack' => 'SUM(CASE WHEN state = 2 AND downtime = 0 AND ack = 1 THEN 1 ELSE 0 END)',
'unknown' => 'SUM(CASE WHEN state = 3 AND downtime = 0 AND ack = 0 THEN 1 ELSE 0 END)',
'unknown_dt' => 'SUM(CASE WHEN state = 3 AND downtime = 1 THEN 1 ELSE 0 END)',
'unknown_ack' => 'SUM(CASE WHEN state = 3 AND downtime = 0 AND ack = 1 THEN 1 ELSE 0 END)',
'warning' => 'SUM(CASE WHEN state = 1 AND downtime = 0 AND ack = 0 THEN 1 ELSE 0 END)',
'warning_dt' => 'SUM(CASE WHEN state = 1 AND downtime = 1 THEN 1 ELSE 0 END)',
'warning_ack' => 'SUM(CASE WHEN state = 1 AND downtime = 0 AND ack = 1 THEN 1 ELSE 0 END)',
'last_state_change' => 'UNIX_TIMESTAMP(MAX(last_state_change))',
);
protected $order_columns = array(
'state' => array(
'ASC' => array(
'ok ASC',
'warning_dt ASC',
'warning_ack ASC',
'warning ASC',
'unknown_dt ASC',
'unknown_ack ASC',
'unknown ASC',
'critical_dt ASC',
'critical_ack ASC',
'critical ASC',
),
'DESC' => array(
'critical DESC',
'unknown DESC',
'warning DESC',
'critical_ack DESC',
'critical_dt DESC',
'unknown_ack DESC',
'unknown_dt DESC',
'warning_ack DESC',
'warning_dt DESC',
'ok DESC',
),
'default' => 'DESC'
)
);
abstract protected function addSummaryJoins($query);
protected function init()
{
parent::init();
if ($this->dbtype === 'oracle') {
$this->columns['last_state_change'] = 'localts2unixts(MAX(last_state_change))';
}
}
protected function createQuery()
{
$this->columns[$this->name_alias] = $this->name_alias;
$this->order_columns['state']['ASC'][] = $this->name_alias . ' ASC';
$this->order_columns['state']['DESC'][] = $this->name_alias . ' DESC';
$this->order_columns['name'] = array(
'ASC' => array( $this->name_alias . ' ASC'),
'DESC' => array( $this->name_alias . ' DESC'),
'default' => 'ASC'
);
$sub_query = $this->createSubQuery();
// $sub_query->group($this->sub_group_column);
// $sub_query->columns(array($this->name_alias => 'MAX(' . $this->sub_group_column . ')'));
$sub_query->columns(array($this->name_alias => $this->sub_group_column ));
$this->addSummaryJoins($sub_query);
$query = $this->db->select()->from(
array('sub' => $sub_query),
array()
);
$query->group($this->name_alias);
$this->sub_query = $sub_query;
return $query;
}
protected function createCountQuery()
{
$this->sub_count_query = $this->createCountSubQuery();
$this->sub_count_query->group($this->sub_group_column);
$this->addSummaryJoins($this->sub_count_query);
$count = $this->db->select()->from(
array('cnt' => $this->sub_count_query),
array()
);
return $count;
}
protected function createSubQuery()
{
$query = $this->db->select()
->from(
array('so' => $this->prefix . 'objects'),
array(
// MAX seems to be useless, but is required as of the GROUP below
// 'state' => 'MAX(ss.current_state)',
'state' => 'ss.current_state',
// 'ack' => 'MAX(ss.problem_has_been_acknowledged)',
'ack' => 'ss.problem_has_been_acknowledged',
// 'downtime' => 'MAX(CASE WHEN (dt.object_id IS NULL) THEN 0 ELSE 1 END)',
// 'downtime' => 'MAX(CASE WHEN (scheduled_downtime_depth = 0) THEN 0 ELSE 1 END)',
'downtime' => 'CASE WHEN (scheduled_downtime_depth = 0) THEN 0 ELSE 1 END',
// 'last_state_change' => 'MAX(ss.last_state_change)',
'last_state_change' => 'ss.last_state_change',
)
)->joinLeft(
array('ss' => $this->prefix . 'servicestatus'),
"so.$this->object_id = ss.service_object_id",
array()
)->join(
array('s' => $this->prefix . 'services'),
's.service_object_id = ss.service_object_id',
array()
)/*->joinLeft(
array('dt' => $this->prefix . 'scheduleddowntime'),
"so.$this->object_id = dt.object_id"
. ' AND dt.is_in_effect = 1',
array()
)->joinLeft(
array('co' => $this->prefix . 'comments'),
"so.$this->object_id = co.object_id",
array()
)*/
->where('so.is_active = 1')
->where('so.objecttype_id = 2')
// Group is required as there could be multiple comments:
// ->group('so.' . $this->object_id)
;
return $query;
}
protected function createCountSubQuery()
{
return $this->db->select()
->from(
array('so' => $this->prefix . 'objects'),
array('state' => 'MAX(ss.current_state)')
)->joinLeft(
array('ss' => $this->prefix . 'servicestatus'),
"so.$this->object_id = ss.service_object_id",
array()
)->join(
array('s' => $this->prefix . 'services'),
's.service_object_id = ss.service_object_id',
array()
);
}
public function where($column, $value = null)
{
if ($column === 'problems') {
if ($value === 'true') {
$this->query->having('(SUM(CASE WHEN state = 2 AND downtime = 0 AND ack = 0 THEN 1 ELSE 0 END) +
SUM(CASE WHEN state = 2 AND downtime = 1 THEN 1 ELSE 0 END) +
SUM(CASE WHEN state = 2 AND downtime = 0 AND ack = 1 THEN 1 ELSE 0 END) +
SUM(CASE WHEN state = 3 AND downtime = 0 AND ack = 0 THEN 1 ELSE 0 END) +
SUM(CASE WHEN state = 3 AND downtime = 1 THEN 1 ELSE 0 END) +
SUM(CASE WHEN state = 3 AND downtime = 0 AND ack = 1 THEN 1 ELSE 0 END) +
SUM(CASE WHEN state = 1 AND downtime = 0 AND ack = 0 THEN 1 ELSE 0 END) +
SUM(CASE WHEN state = 1 AND downtime = 1 THEN 1 ELSE 0 END) +
SUM(CASE WHEN state = 1 AND downtime = 0 AND ack = 1 THEN 1 ELSE 0 END)) > 0');
$this->sub_count_query->where('ss.current_state > 0');
$this->sub_query->where('ss.current_state > 0');
}
} elseif ($column === 'search') {
if ($value) {
// $this->sub_query->where($this->name_alias . ' LIKE ?', '%' . $value . '%');
$this->sub_query->where($this->sub_group_column . ' LIKE ?', '%' . $value . '%');
$this->sub_count_query->where($this->sub_group_column . ' LIKE ?', '%' . $value . '%');
}
} else {
parent::where($column, $value);
}
return $this;
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace Icinga\Backend\Ido;
class HostgroupsummaryQuery extends GroupsummaryQuery
{
protected $name_alias = 'hostgroup_name';
protected $sub_group_column = 'hg.alias';
protected function addSummaryJoins($query)
{
$this->joinServiceHostgroups($query);
}
}

View File

@ -0,0 +1,521 @@
<?php
namespace Icinga\Backend\Ido;
abstract class Query extends \Icinga\Backend\Query
{
protected $db;
protected $prefix;
protected $query;
protected $count_query;
protected $count_columns;
protected $ordered = false;
protected $finalized = false;
protected $object_id = 'object_id';
protected $hostgroup_id = 'hostgroup_id';
protected $servicegroup_id = 'servicegroup_id';
protected $custom_cols = array();
/**
* Available sort combinations
*/
protected $order_columns = array(
'host' => array(
'ASC' => array(
'host_name ASC',
'service_description ASC'
),
'DESC' => array(
'host_name DESC',
'service_description ASC'
),
'default' => 'ASC'
),
'host_address' => array(
'ASC' => array(
'host_ipv4 ASC',
'service_description ASC'
),
'DESC' => array(
'host_ipv4 ASC',
'service_description ASC'
),
'default' => 'ASC'
),
'service' => array(
'ASC' => array(
'service_description ASC'
),
'DESC' => array(
'service_description DESC'
),
'default' => 'ASC'
),
'service_state_change' => array(
'ASC' => array(
'ss.last_state_change ASC'
),
'DESC' => array(
'ss.last_state_change DESC'
),
'default' => 'DESC'
),
'service_state' => array(
'ASC' => array(
'CASE WHEN (ss.current_state = 3) THEN 2 WHEN (ss.current_state = 2) THEN 3 ELSE ss.current_state END DESC', // TODO: distinct severity in a better way
'ss.problem_has_been_acknowledged ASC',
// 'CASE WHEN (ss.scheduled_downtime_depth = 0) THEN 0 ELSE 1 END ASC',
'service_in_downtime ASC', // TODO: Check if all dbs allow sorting by alias
'ss.last_state_change DESC',
'so.name1 ASC',
'so.name2 ASC'
),
'DESC' => array(
'CASE WHEN (ss.current_state = 3) THEN 2 WHEN (ss.current_state = 2) THEN 3 ELSE ss.current_state END ASC',
'ss.problem_has_been_acknowledged ASC',
// 'CASE WHEN (ss.scheduled_downtime_depth = 0) THEN 0 ELSE 1 END ASC',
'service_in_downtime ASC',
'ss.last_state_change DESC'
),
'default' => 'ASC'
)
);
abstract protected function createQuery();
public function dump()
{
$this->finalize();
return "QUERY\n=====\n"
. $this->query
. "\n\nCOUNT\n=====\n"
. $this->count_query
. "\n\n";
}
public function getCountQueryObject()
{
return $this->finalize()->count_query;
}
public function getQueryObject()
{
return $this->finalize()->query;
}
protected function createCountQuery()
{
return clone($this->query);
}
protected function init()
{
$this->db = $this->backend->getAdapter();
$this->dbtype = $this->backend->getDbType();
if ($this->dbtype === 'oracle') {
$this->object_id = $this->hostgroup_id = $this->servicegroup_id = 'id';
}
$this->prefix = $this->backend->getPrefix();
$this->query = $this->createQuery();
$this->count_query = $this->createCountQuery();
}
protected function finalize()
{
if ($this->finalized) return $this;
$this->finalized = true;
$this->query->columns($this->columns);
if ($this->count_columns === null) {
$this->count_columns = array('cnt' => 'COUNT(*)');
}
if (! $this->ordered) {
$this->order();
}
$this->count_query->columns($this->count_columns);
return $this;
}
protected function prepareServiceStatesQuery()
{
$query = $this->db->select()
->from(
array('hs' => $this->prefix . 'hoststatus'),
array()
)->join(
array('h' => $this->prefix . 'hosts'),
'hs.host_object_id = h.host_object_id',
array()
)->join(
array('s' => $this->prefix . 'services'),
's.host_object_id = h.host_object_id',
array()
)->join(
array('so' => $this->prefix . 'objects'),
"so.$this->object_id = s.service_object_id AND so.is_active = 1",
array()
)->joinLeft(
array('ss' => $this->prefix . 'servicestatus'),
"so.$this->object_id = ss.service_object_id",
array()
);
// $this->joinServiceDowntimes($query);
// $query->group('so.object_id');
return $query;
}
protected function prepareServicesCount()
{
// TODO: Depends on filter, some cols could be avoided
$query = $this->db->select()->from(
array('hs' => $this->prefix . 'hoststatus'),
array()
)->join(
array('h' => $this->prefix . 'hosts'),
'hs.host_object_id = h.host_object_id',
array()
)->join(
array('s' => $this->prefix . 'services'),
's.host_object_id = h.host_object_id',
array()
)->join(
array('so' => $this->prefix . 'objects'),
"so.$this->object_id = s.service_object_id AND so.is_active = 1",
"COUNT(so.$this->object_id)"
)->joinLeft(
array('ss' => $this->prefix . 'servicestatus'),
"so.$this->object_id = ss.service_object_id",
array()
);
// $this->joinServiceDowntimes($query);
return $query;
}
protected function joinHostgroups($query = null)
{
if ($query === null) $query = $this->query;
$query->join(
array('hgm' => $this->prefix . 'hostgroup_members'),
'hgm.host_object_id = h.host_object_id',
array()
)->join(
array('hg' => $this->prefix . 'hostgroups'),
"hgm.hostgroup_id = hg.$this->hostgroup_id",
array()
);
return $this;
}
protected function joinServiceHostgroups($query)
{
if ($query === null) $query = $this->query;
$query->join(
array('hgm' => $this->prefix . 'hostgroup_members'),
'hgm.host_object_id = s.host_object_id',
array()
)->join(
array('hg' => $this->prefix . 'hostgroups'),
"hgm.hostgroup_id = hg.$this->hostgroup_id",
array()
);
return $this;
}
protected function joinServicegroups($query)
{
if ($query === null) $query = $this->query;
$query->join(
array('sgm' => $this->prefix . 'servicegroup_members'),
'sgm.service_object_id = s.service_object_id',
array()
)->join(
array('sg' => $this->prefix . 'servicegroups'),
"sgm.servicegroup_id = sg.$this->servicegroup_id",
array()
);
return $this;
}
protected function joinServiceDowntimes($query)
{
$query->joinLeft(
array('dt' => $this->prefix . 'scheduleddowntime'),
"so.$this->object_id = dt.object_id"
. ' AND dt.is_in_effect = 1',
array()
);
// NDO compat (doesn't work correctly like this):
// $now = "'" . date('Y-m-d H:i:s') . "'";
// . ' AND dt.was_started = 1'
// . ' AND dt.scheduled_end_time > ' . $now
// . ' AND dt.actual_start_time < ' . $now,
return $query;
}
public function where($column, $value = null)
{
// Ugly temporary hack:
foreach (array($this->query, $this->count_query) as $query) {
if ($column === 'search') {
if ($this->dbtype === 'mysql') {
$query->where($this->db->quoteInto(
'so.name2 COLLATE latin1_general_ci LIKE ?'
. ' OR so.name1 COLLATE latin1_general_ci LIKE ?',
'%' . $value . '%',
'%' . $value . '%'
));
} else {
$query->where($this->db->quoteInto(
'LOWER(so.name2) LIKE ?'
. ' OR LOWER(so.name1) LIKE ?',
'%' . strtolower($value) . '%',
'%' . strtolower($value) . '%'
));
}
continue;
}
// TODO: Check if this also works based on column:
if ($column === 'hostgroups') {
$this->appendHostgroupLimit($query, $value);
continue;
}
if (preg_match('~^_([^_]+)_(.+)$~', $column, $m)) {
switch($m[1]) {
case 'host':
$this->appendHostCustomVarLimit($query, $m[2], $value);
break;
case 'service':
$this->appendServiceCustomVarLimit($query, $m[2], $value);
break;
}
continue;
}
//$column = preg_replace('~^current_state~', 'ss.current_state', $column);
if (array_key_exists($column, $this->available_columns)) {
$column = $this->available_columns[$column];
}
$query->where($this->prepareFilterStringForColumn($column, $value));
}
/*->orWhere('last_state_change > ?', $new)*/
return $this;
}
public function order($column = '', $dir = null)
{
$this->ordered = true;
return $this->applyOrder($column, $dir);
}
protected function applyOrder($order = '', $order_dir = null)
{
if (! array_key_exists($order, $this->order_columns)) {
$order = key($this->order_columns);
}
if ($order_dir === null) {
$order_dir = $this->order_columns[$order]['default'];
}
foreach ($this->order_columns[$order][$order_dir] as $col) {
$this->query->order($col);
}
return $this;
}
protected function addServiceComments($query = null)
{
if ($query === null) {
$query = $this->query;
}
$query->joinLeft(
array('co' => $this->prefix . 'comments'),
"so.$this->object_id = co.object_id",
array()
)
->group('so.object_id')
;
return $this;
}
/**
* $column = col
* $value = abc,cde,cd*,!egh,!*hh*
* -> (col IN ('abc', 'cde') OR col LIKE 'cd%') AND (col != 'egh' AND col NOT LIKE '%hh%')
*/
protected function prepareFilterStringForColumn($column, $value)
{
$filter = '';
$filters = array();
$or = array();
$and = array();
if (strpos($value, ',') !== false) {
$value = preg_split('~,~', $value, -1, PREG_SPLIT_NO_EMPTY);
}
if (! is_array($value)) {
$value = array($value);
}
// Go through all given values
foreach ($value as $val) {
// Value starting with - means negation
if ($val[0] === '-') {
$val = substr($val, 1);
if (strpos($val, '*') === false) {
$and[] = $this->db->quoteInto($column . ' != ?', $val);
} else {
$and[] = $this->db->quoteInto(
$column . ' NOT LIKE ?',
str_replace('*', '%', $val)
);
}
// Starting with + enforces AND
} elseif ($val[0] === '+') {
$val = substr($val, 1);
if (strpos($val, '*') === false) {
$and[] = $this->db->quoteInto($column . ' = ?', $val);
} else {
$and[] = $this->db->quoteInto(
$column . ' LIKE ?',
str_replace('*', '%', $val)
);
}
// All others ar ORs:
} else {
if (strpos($val, '*') === false) {
$or[] = $this->db->quoteInto($column . ' = ?', $val);
} else {
$or[] = $this->db->quoteInto(
$column . ' LIKE ?',
str_replace('*', '%', $val)
);
}
}
}
if (! empty($or)) { $filters[] = implode(' OR ', $or); }
if (! empty($and)) { $filters[] = implode(' AND ', $and); }
if (! empty($filters)) {
$filter = '(' . implode(') AND (', $filters) . ')';
}
return $filter;
}
protected function addCustomVarColumn($query, $alias, $name, $filter = null)
{
// TODO: Improve this:
if (! preg_match('~^[a-zA-Z0-9_]+$~', $name)) {
throw new \Exception(sprintf(
'Got invalid custom var: "%s"',
$name
));
}
$qobj = spl_object_hash($query);
if (! array_key_exists($qobj, $this->custom_cols)) {
$this->custom_cols[$qobj] = array();
}
if (array_key_exists($alias, $this->custom_cols[$qobj])) {
if ($name !== $this->custom_cols[$qobj][$alias]) {
throw new \Exception(sprintf(
'Cannot add CV alias "%s" twice with different target',
$alias
));
}
return $this;
}
$query->join(
// TODO: Allow multiple limits with different aliases
array($alias => $this->prefix . 'customvariablestatus'),
's.host_object_id = ' . $alias . '.object_id'
. ' AND ' . $alias . '.varname = '
. $this->db->quote(strtoupper($name))
//. ($filter === null ? '' : ' AND ' . $filter),
,
array()
);
$this->custom_cols[$qobj][$alias] = $name;
return $this;
}
protected function appendHostCustomVarLimit($query, $key, $value)
{
$alias = 'hcv_' . strtolower($key);
$filter = $this->prepareFilterStringForColumn($alias . '.varvalue', $value);
$this->addCustomVarColumn($query, $alias, $key);
$query->where($filter);
return $query;
}
protected function appendHostgroupLimit($query, $hostgroups)
{
return $query->join(
array('hgm' => $this->prefix . 'hostgroup_members'),
'hgm.host_object_id = s.host_object_id',
array()
)->join(
array('hg' => $this->prefix . 'hostgroups'),
"hgm.hostgroup_id = hg.$this->hostgroup_id",
array()
)
->where('hg.alias IN (?)', $hostgroups);
}
public function count()
{
return $this->db->fetchOne(
$this->finalize()->count_query
);
}
public function fetchAll()
{
return $this->db->fetchAll($this->finalize()->query);
}
public function fetchRow()
{
return $this->db->fetchRow($this->finalize()->query);
}
public function fetchOne()
{
return $this->db->fetchOne($this->finalize()->query);
}
public function fetchPairs()
{
return $this->db->fetchPairs($this->finalize()->query);
}
/**
* Sets a limit count and offset to the query
*
* @param int $count Number of rows to return
* @param int $offset Row offset to start from
* @return \Icinga\Backend\Query This Query object
*/
public function limit($count = null, $offset = null)
{
$this->query->limit($count, $offset);
return $this;
}
public function __toString()
{
$this->finalize();
return (string) $this->query;
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace Icinga\Backend\Ido;
class ServicegroupsummaryQuery extends GroupsummaryQuery
{
protected $name_alias = 'servicegroup_name';
protected $sub_group_column = 'sg.alias';
protected function addSummaryJoins($query)
{
$this->joinServicegroups($query);
}
}

View File

@ -0,0 +1,95 @@
<?php
namespace Icinga\Backend\Ido;
class ServicelistQuery extends Query
{
protected $available_columns = array(
// Host config
'host_name' => 'so.name1',
'host_display_name' => 'h.display_name',
'host_alias' => 'h.alias',
'host_address' => 'h.address',
'host_ipv4' => 'INET_ATON(h.address)',
'host_icon_image' => 'h.icon_image',
// Host state
'host_state' => 'hs.current_state',
'host_output' => 'hs.output',
'host_perfdata' => 'hs.perfdata',
'host_acknowledged' => 'hs.problem_has_been_acknowledged',
'host_does_active_checks' => 'hs.active_checks_enabled',
'host_accepts_passive_checks' => 'hs.passive_checks_enabled',
'host_last_state_change' => 'UNIX_TIMESTAMP(hs.last_state_change)',
// Service config
'service_description' => 'so.name2',
'service_display_name' => 's.display_name',
// Service state
'current_state' => 'ss.current_state',
'service_state' => 'ss.current_state',
'service_output' => 'ss.output',
'service_perfdata' => 'ss.perfdata',
'service_acknowledged' => 'ss.problem_has_been_acknowledged',
'service_in_downtime' => 'CASE WHEN (ss.scheduled_downtime_depth = 0) THEN 0 ELSE 1 END',
'service_handled' => 'CASE WHEN ss.problem_has_been_acknowledged + ss.scheduled_downtime_depth > 0 THEN 1 ELSE 0 END',
'service_does_active_checks' => 'ss.active_checks_enabled',
'service_accepts_passive_checks' => 'ss.passive_checks_enabled',
'service_last_state_change' => 'UNIX_TIMESTAMP(ss.last_state_change)',
// Service comments
//'service_downtimes_with_info' => "IF(dt.object_id IS NULL, NULL, GROUP_CONCAT(CONCAT('[', dt.author_name, '] ', dt.comment_data) ORDER BY dt.entry_time DESC SEPARATOR '|'))",
//'service_comments_with_info' => "IF(co.object_id IS NULL, NULL, GROUP_CONCAT(CONCAT('[', co.author_name, '] ', co.comment_data) ORDER BY co.entry_time DESC SEPARATOR '|'))",
// SLA Example:
// 'sla' => "icinga_availability(so.object_id,"
// . " '2012-12-01 00:00:00', '2012-12-31 23:59:59')",
);
protected function init()
{
parent::init();
if ($this->dbtype === 'oracle') {
$this->columns['host_last_state_change'] =
'localts2unixts(ss.last_state_change)';
$this->columns['service_last_state_change'] =
'localts2unixts(ss.last_state_change)';
}
}
public function where($column, $value = null)
{
// Ugly temporary hack:
if ($column === 'problems') {
if ($value === true || $value === 'true') {
parent::where('current_state', '-0');
} elseif ($value === false || $value === 'false') {
parent::where('current_state', '0');
}
return $this;
}
if ($column === 'handled') $column = 'service_handled';
parent::where($column, $value);
return $this;
}
protected function createQuery()
{
$query = $this->prepareServiceStatesQuery();
if ($this->dbtype === 'mysql') {
// $this->addServiceComments($query);
} else {
$this->columns['host_ipv4'] = 'h.address';
$this->columns['service_downtimes_with_info'] = '(NULL)';
$this->columns['service_comments_with_info'] = '(NULL)';
}
return $query;
}
protected function createCountQuery()
{
return $this->prepareServicesCount();
}
}

View File

@ -0,0 +1,86 @@
<?php
namespace Icinga\Backend\Ido;
class StatehistoryQuery extends Query
{
protected $available_columns = array(
// Host config
'host_name' => 'sho.name1',
'service_description' => 'sho.name2',
'object_type' => "CASE WHEN sho.objecttype_id = 1 THEN 'host' ELSE 'service' END",
'timestamp' => 'UNIX_TIMESTAMP(sh.state_time)',
'state' => 'sh.state',
'last_state' => 'sh.last_state',
'last_hard_state' => 'sh.last_hard_state',
'attempt' => 'sh.current_check_attempt',
'max_attempts' => 'sh.max_check_attempts',
'output' => 'sh.output', // no long_output in browse
);
protected $order_columns = array(
'timestamp' => array(
'ASC' => array(
'state_time ASC',
),
'DESC' => array(
'state_time DESC',
),
'default' => 'DESC'
)
);
protected function init()
{
parent::init();
if ($this->dbtype === 'oracle') {
$this->columns['timestamp'] =
'localts2unixts(sh.state_time)';
}
}
public function where($column, $value = null)
{
if ($column === 'problems') {
if ($value === 'true') {
foreach (array($this->query, $this->count_query) as $query) {
$query->where('sh.state > 0');
}
}
return $this;
}
if ($column === 'host') {
foreach (array($this->query, $this->count_query) as $query) {
$query->where('sho.name1 = ?', $value);
}
return $this;
}
if ($column === 'service') {
foreach (array($this->query, $this->count_query) as $query) {
$query->where('sho.name2 = ?', $value);
}
return $this;
}
parent::where($column, $value);
return $this;
}
protected function createQuery()
{
$query = $this->db->select()->from(
array('sh' => $this->prefix . 'statehistory'),
array()
// )->join(
)->joinLeft( // LEFT is bullshit but greatly helps MySQL
// Problem -> has to be INNER once permissions are in effect
// Therefore this should probably be "flexible" or handled in another
// way
array('sho' => $this->prefix . 'objects'),
'sho.' . $this->object_id . ' = sh.object_id AND sho.is_active = 1',
array()
);
return $query;
}
}

View File

@ -0,0 +1,45 @@
<?php
/**
* Icinga Livestatus Backend
*
* @package Icinga\Backend
*/
namespace Icinga\Backend;
use Icinga\Protocol\Livestatus\Connection;
/**
* This class provides an easy-to-use interface to the Livestatus socket library
*
* You should usually not directly use this class but go through Icinga\Backend.
*
* @copyright Copyright (c) 2013 Icinga-Web Team <info@icinga.org>
* @author Icinga-Web Team <info@icinga.org>
* @package Icinga\Application
* @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
*/
class Livestatus extends AbstractBackend
{
protected $connection;
/**
* Backend initialization starts here
*
* return void
*/
protected function init()
{
$this->connection = new Connection($this->config->socket);
}
/**
* Get our Livestatus connection
*
* return \Icinga\Protocol\Livestatus\Connection
*/
public function getConnection()
{
return $this->connection;
}
}

View File

@ -0,0 +1,213 @@
<?php
namespace Icinga\Backend\Livestatus;
abstract class Query extends \Icinga\Backend\Query
{
protected $connection;
protected $query;
protected $ordered = false;
protected $finalized = false;
/**
* Available sort combinations
*/
protected $order_columns = array(
'host' => array(
'ASC' => array(
'host_name ASC',
'service_description ASC'
),
'DESC' => array(
'host_name DESC',
'service_description ASC'
),
'default' => 'ASC'
),
'host_address' => array(
'ASC' => array(
'host_ipv4 ASC',
'service_description ASC'
),
'DESC' => array(
'host_ipv4 ASC',
'service_description ASC'
),
'default' => 'ASC'
),
'service' => array(
'ASC' => array(
'service_description ASC'
),
'DESC' => array(
'service_description DESC'
),
'default' => 'ASC'
),
'service_state_change' => array(
'ASC' => array(
'ss.last_state_change ASC'
),
'DESC' => array(
'ss.last_state_change DESC'
),
'default' => 'DESC'
),
'service_state' => array(
'ASC' => array(
'IF (ss.current_state = 3, 2, IF(ss.current_state = 2, 3, ss.current_state)) DESC',
'ss.problem_has_been_acknowledged ASC',
'IF(dt.object_id IS NULL, 0, 1) ASC',
'ss.last_state_change DESC'
),
'DESC' => array(
'IF (ss.current_state = 3, 2, IF(ss.current_state = 3, 2, ss.current_state)) DESC',
'ss.problem_has_been_acknowledged ASC',
'IF(dt.object_id IS NULL, 0, 1) ASC',
'ss.last_state_change DESC'
),
'default' => 'ASC'
)
);
abstract protected function createQuery();
protected function init()
{
$this->connection = $this->backend->getConnection();
$this->query = $this->createQuery();
}
public function where($column, $value = null)
{
if ($column === 'problems') {
if ($value === 'true') {
$this->query->where('state > 0');
} elseif ($value === 'false') {
$this->query->where('state = 0');
}
return $this;
}
if ($column === 'handled') {
if ($value === 'true') {
// TODO: Not yet
} elseif ($value === 'false') {
// TODO: Not yet
}
return $this;
}
// Ugly temporary hack:
$colcheck = preg_replace('~[\s=><].+$~', '', $column);
if (array_key_exists($colcheck, $this->available_columns)) {
$query->where(preg_replace(
'~' . $colcheck . '~',
$this->available_columns[$colcheck],
$column
), $value);
} else {
$this->query->where($column, $value);
}
return $this;
}
protected function finalize()
{
return $this;
if ($this->finalized) return $this;
$this->finalized = true;
$this->query->columns($this->columns);
if ($this->count_columns === null) {
$this->count_columns = array('cnt' => 'COUNT(*)');
}
if (! $this->ordered) {
$this->order();
}
$this->count_query->columns($this->count_columns);
return $this;
}
public function applyFilters($filters = array())
{
foreach ($filters as $key => $val) {
$this->where($key, $val);
}
return $this;
}
public function order($column = '', $dir = null)
{
$this->ordered = true;
return $this->applyOrder($column, $dir);
}
protected function applyOrder($order = '', $order_dir = null)
{
return $this;
if (! array_key_exists($order, $this->order_columns)) {
$order = key($this->order_columns);
}
if ($order_dir === null) {
$order_dir = $this->order_columns[$order]['default'];
}
foreach ($this->order_columns[$order][$order_dir] as $col) {
$this->query->order($col);
}
return $this;
}
public function count()
{
return $this->connection->count(
$this->finalize()->query
);
}
public function fetchAll()
{
return $this->connection->fetchAll($this->finalize()->query);
}
public function fetchRow()
{
return $this->db->fetchRow($this->finalize()->query);
}
public function fetchOne()
{
return $this->db->fetchOne($this->finalize()->query);
}
public function fetchPairs()
{
return $this->db->fetchPairs($this->finalize()->query);
}
/**
* Sets a limit count and offset to the query
*
* @param int $count Number of rows to return
* @param int $offset Row offset to start from
* @return \Icinga\Backend\Query This Query object
*/
public function limit($count = null, $offset = null)
{
$this->query->limit($count, $offset);
return $this;
}
// For debugging and testing only:
public function __toString()
{
$this->finalize();
return (string) $this->query;
}
}

View File

@ -0,0 +1,46 @@
<?php
namespace Icinga\Backend\Livestatus;
class ServicelistQuery extends Query
{
protected $available_columns = array(
'host_name',
'host_display_name',
'host_alias',
'host_address',
'host_ipv4' => 'host_address', // TODO
'host_icon_image',
// Host state
'host_state',
'host_output' => 'host_plugin_output',
'host_perfdata' => 'host_perf_data',
'host_acknowledged',
'host_does_active_checks' => 'host_active_checks_enabled',
'host_accepts_passive_checks' => 'host_accept_passive_checks',
'host_last_state_change',
// Service config
'service_description' => 'description',
'service_display_name' => 'display_name',
// Service state
'service_state' => 'state',
'service_output' => 'plugin_output',
'service_perfdata' => 'perf_data',
'service_acknowledged' => 'acknowledged',
'service_does_active_checks' => 'active_checks_enabled',
'service_accepts_passive_checks' => 'accept_passive_checks',
'service_last_state_change' => 'last_state_change',
// Service comments
'comments_with_info',
'downtimes_with_info',
);
protected function createQuery()
{
return $this->connection->select()->from('services', $this->available_columns);
}
}

View File

@ -0,0 +1,110 @@
<?php
namespace Icinga\Backend;
/**
* Wrapper around an array of monitoring objects that can be enhanced with an optional
* object that extends AbstractAccessorStrategy. This will act as a dataview and provide
* normalized access to the underlying data (mapping properties, retrieving additional data)
*
* If not Accessor is set, this class just behaves like a normal Iterator and returns
* the underlying objects.
*
* If the dataset contains arrays instead of objects, they will be cast to objects.
*
*/
use Icinga\Backend\DataView as DataView;
class MonitoringObjectList implements \Iterator, \Countable
{
private $dataSet = array();
private $position = 0;
private $dataView = null;
function __construct(array &$dataset, DataView\AbstractAccessorStrategy $dataView = null)
{
$this->dataSet = $dataset;
$this->position = 0;
$this->dataView = $dataView;
}
public function count()
{
return count($this->dataSet);
}
/**
* (PHP 5 &gt;= 5.0.0)<br/>
* Return the current element
* @link http://php.net/manual/en/iterator.current.php
* @return mixed Can return any type.
*/
public function current()
{
if ($this->dataView)
return $this;
return $this->dataSet[$this->position];
}
/**
* (PHP 5 &gt;= 5.0.0)<br/>
* Move forward to next element
* @link http://php.net/manual/en/iterator.next.php
* @return void Any returned value is ignored.
*/
public function next()
{
$this->position++;
}
/**
* (PHP 5 &gt;= 5.0.0)<br/>
* Return the key of the current element
* @link http://php.net/manual/en/iterator.key.php
* @return mixed scalar on success, or null on failure.
*/
public function key()
{
return $this->position;
}
/**
* (PHP 5 &gt;= 5.0.0)<br/>
* Checks if current position is valid
* @link http://php.net/manual/en/iterator.valid.php
* @return boolean The return value will be casted to boolean and then evaluated.
* Returns true on success or false on failure.
*/
public function valid()
{
return $this->position < count($this->dataSet);
}
/**
* (PHP 5 &gt;= 5.0.0)<br/>
* Rewind the Iterator to the first element
* @link http://php.net/manual/en/iterator.rewind.php
* @return void Any returned value is ignored.
*/
public function rewind()
{
$this->position = 0;
}
public function __isset($name)
{
return $this->dataView->exists($this->dataSet[$this->position],$name);
}
function __get($name)
{
return $this->dataView->get($this->dataSet[$this->position],$name);
}
function __set($name, $value)
{
throw new \Exception("Setting is currently not available for objects");
}
}

View File

@ -0,0 +1,376 @@
<?php
namespace Icinga\Data;
use Icinga\Exception;
abstract class AbstractQuery
{
/**
* Sort ascending
*/
const SORT_ASC = 1;
/**
* Sort descending
*/
const SORT_DESC = -1;
/**
* Query data source
*
* @var DatasourceInterface
*/
protected $ds;
/**
* The table you are going to query
*/
protected $table;
/**
* The columns you are interested in. All columns if empty
*/
protected $columns = array();
/**
* A list of filters
*/
protected $filters = array();
/**
* The columns you're using to sort the query result
*/
protected $order_columns = array();
/**
* Return not more than that many rows
*/
protected $limit_count;
/**
* Result starts with this row
*/
protected $limit_offset;
/**
* Constructor
*
* @param DatasourceInterface $ds Your data source
*/
public function __construct(DatasourceInterface $ds, $columns = null)
{
$this->ds = $ds;
if ($columns === null) {
$columns = $this->getDefaultColumns();
}
if ($columns !== null) {
$this->columns($columns);
}
$this->init();
}
protected function getDefaultColumns()
{
return null;
}
/**
* 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($table, $columns = null)
{
$this->table = $table;
if ($columns !== null) {
$this->columns($columns);
}
return $this;
}
public function columns($columns)
{
if (is_array($columns)) {
$this->columns = $columns;
} else {
$this->columns = array($columns);
}
return $this;
}
/**
* Use once or multiple times to filter result set
*
* Multiple where calls will be combined by a logical AND operation
*
* @param string $key Column or backend-specific search expression
* @param string $val Search value, must be escaped automagically
*
* @return self
*/
public function where($key, $val = null)
{
$this->filters[] = array($key, $val);
return $this;
}
/**
* 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 $col Column, may contain direction separated by space
* @param int $dir Sort direction
*
* @return self
*/
public function order($col, $dir = null)
{
if ($dir === null) {
if (($pos = strpos($col, ' ')) === false) {
$dir = $this->getDefaultSortDir($col);
} else {
$dir = strtoupper(substr($col, $pos + 1));
if ($dir === 'DESC') {
$dir = self::SORT_DESC;
} else {
$dir = self::SORT_ASC;
}
$col = substr($col, 0, $pos);
}
} else {
if (strtoupper($dir) === 'DESC') {
$dir = self::SORT_DESC;
} else {
$dir = self::SORT_ASC;
}
}
$this->order_columns[] = array($col, $dir);
return $this;
}
protected function getDefaultSortDir($col)
{
return self::SORT_ASC;
}
/**
* Limit the result set
*
* @param int $count Return not more than that many rows
* @param int $offset Result starts with this row
*
* @return self
*/
// Nur wenn keine stats, sonst im RAM!!
// Offset gibt es nicht, muss simuliert werden
public function limit($count = null, $offset = null)
{
if (! preg_match('~^\d+~', $count . $offset)) {
throw new Exception\ProgrammingError(
sprintf(
'Got invalid limit: %s, %s',
$count,
$offset
)
);
}
$this->limit_count = (int) $count;
$this->limit_offset = (int) $offset;
return $this;
}
/**
* Wheter at least one order column has been applied to this Query
*
* @return bool
*/
public function hasOrder()
{
return ! empty($this->order_columns);
}
/**
* Wheter a limit has been applied to this Query
*
* @return bool
*/
public function hasLimit()
{
return $this->limit_count !== null;
}
/**
* Wheter a starting offset been applied to this Query
*
* @return bool
*/
public function hasOffset()
{
return $this->limit_offset > 0;
}
/**
* Get the query limit
*
* @return int|null
*/
public function getLimit()
{
return $this->limit_count;
}
/**
* Get the query starting offset
*
* @return int|null
*/
public function getOffset()
{
return $this->limit_offset;
}
/**
* Get the columns that have been asked for with this query
*
* @return array
*/
public function listColumns()
{
return $this->columns;
}
/**
* Get the filters that have been applied to this query
*
* @return array
*/
public function listFilters()
{
return $this->filters;
}
/**
* Extend this function for things that should happen at construction time
*/
protected function init()
{
}
/**
* Extend this function for things that should happen before query execution
*/
protected function finish()
{
}
/**
* 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);
}
/**
* Return a pagination adapter for this query
*
* @return \Zend_Paginator
*/
public function paginate($limit = null, $page = null)
{
$this->finish();
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);
}
}
$paginator = new \Zend_Paginator(
new \Icinga\Web\Paginator\Adapter\QueryAdapter($this)
);
$paginator->setItemCountPerPage($limit);
$paginator->setCurrentPageNumber($page);
return $paginator;
}
/**
* Destructor. Remove $ds, just to be on the safe side
*/
public function __destruct()
{
unset($this->ds);
}
}

View File

@ -0,0 +1,100 @@
<?php
namespace Icinga\Data;
class ArrayDatasource implements DatasourceInterface
{
protected $data;
/**
* Constructor, create a new Datasource for the given Array
*
* @param array $array The array you're going to use as a data source
*/
public function __construct(array $array)
{
$this->data = (array) $array;
}
/**
* Instantiate an ArrayQuery object
*
* @return ArrayQuery
*/
public function select()
{
return new ArrayQuery($this);
}
public function fetchColumn(ArrayQuery $query)
{
$result = array();
foreach ($this->getResult($query) as $row) {
$arr = (array) $row;
$result[] = array_shift($arr);
}
return $result;
}
public function fetchAll(ArrayQuery $query)
{
$result = $this->getResult($query);
return $result;
}
public function count(ArrayQuery $query)
{
$this->createResult($query);
return $query->getCount();
}
protected function createResult(ArrayQuery $query)
{
if ($query->hasResult()) {
return $this;
}
$result = array();
$filters = $query->listFilters();
$columns = $query->listColumns();
foreach ($this->data as & $row) {
// Skip rows that do not match applied filters
foreach ($filters as $f) {
if ($row->{$f[0]} !== $f[1]) {
continue 2;
}
}
// Get only desired columns if asked so
if (empty($columns)) {
$result[] = $row;
} else {
$c_row = (object) array();
foreach ($columns as $key) {
if (isset($row->$key)) {
$c_row->$key = $row->$key;
} else {
$c_row->$key = null;
}
}
$result[] = $c_row;
}
}
// Sort the result
if ($query->hasOrder()) {
usort($result, array($query, 'compare'));
}
$query->setResult($result);
return $this;
}
protected function getResult(ArrayQuery $query)
{
if (! $query->hasResult()) {
$this->createResult($query);
}
return $query->getLimitedResult();
}
}

View File

@ -0,0 +1,85 @@
<?php
namespace Icinga\Data;
class ArrayQuery extends AbstractQuery
{
/**
* 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)
{
if (! array_key_exists($col_num, $this->order_columns)) {
return 0;
}
$col = $this->order_columns[$col_num][0];
$dir = $this->order_columns[$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, $this->order_columns)) {
return $this->compare($a, $b, $col_num);
} else {
return 0;
}
}
if ($dir === self::SORT_ASC) {
return $res;
} else {
return $res * -1;
}
}
}

View File

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

View File

@ -0,0 +1,113 @@
<?php
namespace Icinga\Data\Db;
use Icinga\Data\DatasourceInterface;
class Connection implements DatasourceInterface
{
protected $db;
protected $config;
protected $dbtype;
public function __construct(\Zend_Config $config = null)
{
$this->config = $config;
$this->connect();
$this->init();
}
public function select()
{
return new Query($this);
}
public function getDbType()
{
return $this->dbtype;
}
public function getDb()
{
return $this->db;
}
protected function init()
{
}
protected function connect()
{
$this->dbtype = $this->config->get('dbtype', 'mysql');
$options = array(
\Zend_Db::AUTO_QUOTE_IDENTIFIERS => false,
\Zend_Db::CASE_FOLDING => \Zend_Db::CASE_LOWER
);
$drv_options = array(
\PDO::ATTR_TIMEOUT => 2,
// TODO: Check whether LC is useful. Zend_Db does fetchNum for Oci:
\PDO::ATTR_CASE => \PDO::CASE_LOWER
// TODO: ATTR_ERRMODE => ERRMODE_EXCEPTION vs ERRMODE_SILENT
);
switch ($this->dbtype) {
case 'mysql':
$adapter = 'Pdo_Mysql';
$drv_options[\PDO::MYSQL_ATTR_INIT_COMMAND] =
"SET SESSION SQL_MODE='STRICT_ALL_TABLES,NO_ZERO_IN_DATE,"
. "NO_ZERO_DATE,NO_ENGINE_SUBSTITUTION';";
// Not using ONLY_FULL_GROUP_BY as of performance impact
// TODO: NO_ZERO_IN_DATE as been added with 5.1.11. Is it
// ignored by other versions?
$port = $this->config->get('port', 3306);
break;
case 'pgsql':
$adapter = 'Pdo_Pgsql';
$port = $this->config->get('port', 5432);
break;
case 'oracle':
$adapter = 'Pdo_Oci';
// $adapter = 'Oracle';
$port = $this->config->get('port', 1521);
// $drv_options[\PDO::ATTR_STRINGIFY_FETCHES] = true;
if ($adapter === 'Oracle') {
// Unused right now
putenv('ORACLE_SID=XE');
putenv('ORACLE_HOME=/u01/app/oracle/product/11.2.0/xe');
putenv('PATH=$PATH:$ORACLE_HOME/bin');
putenv('ORACLE_BASE=/u01/app/oracle');
putenv('NLS_LANG=AMERICAN_AMERICA.UTF8');
}
break;
default:
throw new \Exception(sprintf(
'Backend "%s" is not supported', $type
));
}
$attributes = array(
'host' => $this->config->host,
'port' => $port,
'username' => $this->config->user,
'password' => $this->config->pass,
'dbname' => $this->config->db,
'options' => $options,
'driver_options' => $drv_options
);
if ($this->dbtype === 'oracle') {
$attributes['persistent'] = true;
}
$this->db = \Zend_Db::factory($adapter, $attributes);
if ($adapter === 'Oracle') {
$this->db->setLobAsString(false);
}
// TODO: Zend_Db::FETCH_ASSOC for Oracle?
$this->db->setFetchMode(\Zend_Db::FETCH_OBJ);
}
}

View File

@ -0,0 +1,130 @@
<?php
namespace Icinga\Data\Db;
use Icinga\Data\AbstractQuery;
class Query extends AbstractQuery
{
/**
* 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
*/
protected $selectQuery;
/**
* Select object used for count query
*/
protected $countQuery;
/**
* Allow to override COUNT(*)
*/
protected $countColumns;
protected function init()
{
$this->db = $this->ds->getConnection()->getDb();
$this->baseQuery = $this->db->select();
}
protected function getSelectQuery()
{
if ($this->selectQuery === null) {
$this->createQueryObjects();
}
if ($this->hasLimit()) {
$this->selectQuery->limit($this->getLimit(), $this->getOffset());
}
return $this->selectQuery;
}
protected function getCountQuery()
{
if ($this->countQuery === null) {
$this->createQueryObjects();
}
return $this->countQuery;
}
protected function createQueryObjects()
{
$this->beforeCreatingCountQuery();
$this->countQuery = clone($this->baseQuery);
if ($this->countColumns === null) {
$this->countColumns = array('cnt' => 'COUNT(*)');
}
$this->countQuery->columns($this->countColumns);
$this->beforeCreatingSelectQuery();
$this->selectQuery = clone($this->baseQuery);
$this->selectQuery->columns($this->columns);
if ($this->hasOrder()) {
foreach ($this->order_columns as $col) {
$this->selectQuery->order(
$col[0]
. ' '
. ( $col[1] === self::SORT_DESC ? 'DESC' : 'ASC')
);
}
}
}
protected function beforeCreatingCountQuery()
{
}
protected function beforeCreatingSelectQuery()
{
}
public function count()
{
return $this->db->fetchOne($this->getCountQuery());
}
public function fetchAll()
{
return $this->db->fetchAll($this->getSelectQuery());
}
public function fetchRow()
{
return $this->db->fetchRow($this->getSelectQuery());
}
public function fetchOne()
{
return $this->db->fetchOne($this->getSelectQuery());
}
public function fetchPairs()
{
return $this->db->fetchPairs($this->getSelectQuery());
}
public function dump()
{
return "QUERY\n=====\n"
. $this->getSelectQuery()
. "\n\nCOUNT\n=====\n"
. $this->getCountQuery()
. "\n\n";
}
public function __toString()
{
return (string) $this->getSelectQuery();
}
}

View File

@ -0,0 +1,54 @@
<?php
/**
* Data Filter
*/
namespace Icinga\Data;
use ArrayIterator;
/**
* This class contains an array of filters
*
* @package Icinga\Data
* @author Icinga-Web Team <info@icinga.org>
* @copyright 2013 Icinga-Web Team <info@icinga.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
*/
class Filter extends ArrayIterator
{
public function without($keys)
{
$filter = new Filter();
$params = $this->toParams();
if (! is_array($keys)) {
$keys = array($keys);
}
foreach ($keys as $key) {
if (array_key_exists($key, $params)) {
unset($params[$key]);
}
}
foreach ($params as $k => $v) {
$filter[] = array($k, $v);
}
return $filter;
}
/**
* Get filtere as key-value array
*
* @return array
*/
public function toParams()
{
$params = array();
foreach ($this as $filter) {
$params[$filter[0]] = $filter[1];
}
return $params;
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace Icinga\Objects;
class Host extends Object
{
// Nothing here
}

View File

@ -0,0 +1,61 @@
<?php
namespace Icinga\Objects;
class Object
{
protected $id;
protected $name;
protected $props;
protected $defaults = array();
protected $fromBackend = false;
protected $hasBeenChanged = false;
protected function __construct($props = array())
{
$this->props = $this->defaults;
if (! empty($props)) {
$this->setProperties($props);
}
}
public function setProperties($props)
{
foreach ($props as $key => $val) {
$this->props[$key] = $val;
}
}
protected function set($key, $val)
{
$this->props[$key] = $val;
return $this;
}
public function __set($key, $val)
{
$this->set($key, $val);
}
public function __get($key)
{
if (array_key_exists($key, $this->props)) {
return $this->props[$key];
}
return null;
}
protected function setLoadedFromBackend($loaded = true)
{
$this->fromBackend = $loaded;
return $this;
}
public static function fromBackend($row)
{
$class = get_called_class();
$object = new $class($row);
$object->setLoadedFromBackend();
return $object;
}
}

View File

@ -0,0 +1,7 @@
<?php
namespace Icinga\Objects;
class Service extends Object
{
}

View File

@ -0,0 +1,84 @@
<?php
namespace Icinga\Pdf;
use TCPDF;
define('K_TCPDF_EXTERNAL_CONFIG', true);
//define('K_PATH_URL', 'http://net-test-icinga-vm1.adm.netways.de/develop/'); // ???
// define('K_PATH_URL', '/var/www/net-test-icinga-vm1.adm.netways.de/develop/public'); // ???
define('K_PATH_URL', '/develop'); // ???
define('K_PATH_MAIN', dirname(ICINGA_LIBDIR) . '/public');
define('K_PATH_FONTS', ICINGA_LIBDIR . '/vendor/tcpdf/fonts/');
define('K_PATH_CACHE', ICINGA_LIBDIR . '/vendor/tcpdf/cache/');
define('K_PATH_URL_CACHE', ICINGA_LIBDIR . '/vendor/tcpdf/cache/');
//define('K_PATH_IMAGES', K_PATH_MAIN . 'images/'); // ???
define('K_PATH_IMAGES', dirname(ICINGA_LIBDIR) . '/public'); // ???
define('K_BLANK_IMAGE', K_PATH_IMAGES.'_blank.png'); // COULD be anything?
// define('K_CELL_HEIGHT_RATIO', 1.25);
define('K_SMALL_RATIO', 2/3);
define('K_TCPDF_CALLS_IN_HTML', true); // SECURITY: is false better?
define('K_TCPDF_THROW_EXCEPTION_ERROR', true);
require_once 'vendor/tcpdf/tcpdf.php';
class File extends TCPDF
{
protected $cell_height_ratio = 1.25;
public function __construct(
$orientation = 'P',
$unit = 'mm',
$format = 'A4',
$unicode = true,
$encoding = 'UTF-8',
$diskcache = false,
$pdfa = false
) {
parent::__construct(
$orientation,
$unit,
$format,
$unicode,
$encoding,
$diskcache,
$pdfa
);
$this->SetCreator('IcingaWeb');
$this->SetAuthor('IcingaWeb Team');
$this->SetTitle('IcingaWeb Sample PDF - Title');
$this->SetSubject('IcingaWeb Sample PDF - Subject');
$this->SetKeywords('IcingaWeb, Monitoring');
// set default header data
// $pdf->SetHeaderData('tcpdf_logo.jpg', 30, 'Header title',
// 'Header string', array(0,64,255), array(0,64,128));
// $pdf->setFooterData($tc=array(0,64,0), $lc=array(0,64,128));
$this->setHeaderFont(array('helvetica', '', 10));
$this->setFooterFont(array('helvetica', '', 8));
$this->SetDefaultMonospacedFont('courier');
$this->SetMargins(15, 27, 15); // left, top, right
$this->SetHeaderMargin(5);
$this->SetFooterMargin(10);
$this->SetAutoPageBreak(true, 25); // margin bottom
$this->setImageScale(1.75);
$lang = array(
'a_meta_charset' => 'UTF-8',
'a_meta_dir' => 'ltr',
'a_meta_language' => 'de',
'w_page' => 'Seite',
);
$this->setLanguageArray($lang);
$this->setFontSubsetting(true);
$this->SetFont('dejavusans', '', 16, '', true);
}
}

View File

@ -27,6 +27,7 @@ class LdapUtils
public static function explodeDN($dn, $with_type = true)
{
$res = ldap_explode_dn($dn, $with_type ? 0 : 1);
foreach ($res as $k => $v) {
$res[$k] = preg_replace(
'/\\\([0-9a-f]{2})/ei',

View File

@ -0,0 +1,249 @@
<?php
namespace Icinga\Protocol\Livestatus;
class Connection
{
const TYPE_UNIX = 1;
const TYPE_TCP = 2;
protected $available_tables = array(
'hosts', // hosts
'services', // services, joined with all data from hosts
'hostgroups', // hostgroups
'servicegroups', // servicegroups
'contactgroups', // contact groups
'servicesbygroup', // all services grouped by service groups
'servicesbyhostgroup', // all services grouped by host groups
'hostsbygroup', // all hosts grouped by host groups
'contacts', // contacts
'commands', // defined commands
'timeperiods', // time period definitions (currently only name
// and alias)
'downtimes', // all scheduled host and service downtimes,
// joined with data from hosts and services.
'comments', // all host and service comments
'log', // a transparent access to the nagios logfiles
// (include archived ones)ones
'status', // general performance and status information.
// This table contains exactly one dataset.
'columns', // a complete list of all tables and columns
// available via Livestatus, including
// descriptions!
'statehist', // 1.2.1i2 sla statistics for hosts and services,
// joined with data from hosts, services and log.
);
protected $socket_path;
protected $socket_host;
protected $socket_port;
protected $socket_type;
protected $connection;
public function hasTable($name)
{
return in_array($name, $this->available_tables);
}
public function __construct($socket = '/var/lib/icinga/rw/live')
{
$this->assertPhpExtensionLoaded('sockets');
if ($socket[0] === '/') {
if (! is_writable($socket)) {
throw new \Exception(sprintf(
'Cannot write to livestatus socket "%s"',
$socket
));
}
$this->socket_type = self::TYPE_UNIX;
$this->socket_path = $socket;
} else {
if (! preg_match('~^tcp://([^:]+):(\d+)~', $socket, $m)) {
throw new \Exception(sprintf(
'Invalid TCP socket syntax: "%s"',
$socket
));
}
// TODO: Better syntax checks
$this->socket_host = $m[1];
$this->socket_port = (int) $m[2];
$this->socket_type = self::TYPE_TCP;
}
}
public function select()
{
$select = new Query($this);
return $select;
}
public function count(Query $query)
{
$count = clone($query);
$count->count();
\Icinga\Benchmark::measure('Sending Livestatus Count Query');
$data = $this->_fetch((string) $count);
\Icinga\Benchmark::measure('Got Livestatus count result');
return $data[0][0];
}
public function fetchAll(Query $query)
{
\Icinga\Benchmark::measure('Sending Livestatus Query');
$data = $this->_fetch((string) $query);
\Icinga\Benchmark::measure('Got Livestatus Data');
if ($query->hasColumns()) {
$headers = $query->getColumnAliases();
} else {
$headers = array_shift($data);
}
$result = array();
foreach ($data as $row) {
$result_row = & $result[];
$result_row = (object) array();
foreach ($row as $key => $val) {
$result_row->{$headers[$key]} = $val;
}
}
if ($query->hasOrder()) {
usort($result, array($query, 'compare'));
}
if ($query->hasLimit()) {
$result = array_slice(
$result,
$query->getOffset(),
$query->getLimit()
);
}
\Icinga\Benchmark::measure('Data sorted, limits applied');
return $result;
}
protected function _fetch($raw_query)
{
$conn = $this->getConnection();
$this->writeToSocket($raw_query);
$header = $this->readFromSocket(16);
$status = (int) substr($header, 0, 3);
$length = (int) trim(substr($header, 4));
$body = $this->readFromSocket($length);
if ($status !== 200) {
throw new \Exception(sprintf(
'Problem while reading %d bytes from livestatus: %s',
$length,
$body
));
}
$result = json_decode($body);
if ($result === null) {
throw new \Exception('Got invalid response body from livestatus');
}
return $result;
}
protected function readFromSocket($length)
{
$offset = 0;
$buffer = '';
while($offset < $length) {
$data = socket_read($this->connection, $length - $offset);
if ($data === false) {
throw new \Exception(sprintf(
'Failed to read from livestatus socket: %s',
socket_strerror(socket_last_error($this->connection))
));
}
$size = strlen($data);
$offset += $size;
$buffer .= $data;
if ($size === 0) {
break;
}
}
if ($offset !== $length) {
throw new \Exception(sprintf(
'Got only %d instead of %d bytes from livestatus socket',
$offset, $length
));
}
return $buffer;
}
protected function writeToSocket($data)
{
$res = socket_write($this->connection, $data);
if ($res === false) {
throw new \Exception('Writing to livestatus socket failed');
}
return true;
}
protected function assertPhpExtensionLoaded($name)
{
if (! extension_loaded($name)) {
throw new \Exception(sprintf(
'The extension "%s" is not loaded',
$name
));
}
}
protected function getConnection()
{
if ($this->connection === null) {
if ($this->socket_type === self::TYPE_TCP) {
$this->establishTcpConnection();
} else {
$this->establishSocketConnection();
}
}
return $this->connection;
}
protected function establishTcpConnection()
{
// TODO: find a bedder place for this
if (! defined('TCP_NODELAY')) {
define('TCP_NODELAY', 1);
}
$this->connection = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if (! @socket_connect($this->connection, $this->socket_host, $this->socket_port)) {
throw new \Exception(sprintf(
'Cannot connect to livestatus TCP socket "%s:%d": %s',
$this->socket_host,
$this->socket_port,
socket_strerror(socket_last_error($this->connection))
));
}
socket_set_option($this->connection, SOL_TCP, TCP_NODELAY, 1);
}
protected function establishSocketConnection()
{
$this->connection = socket_create(AF_UNIX, SOCK_STREAM, 0);
if (! socket_connect($this->connection, $this->socket_path)) {
throw new \Exception(sprintf(
'Cannot connect to livestatus local socket "%s"',
$this->socket_path
));
}
}
public function disconnect()
{
if ($this->connection) {
socket_close($this->connection);
}
}
public function __destruct()
{
$this->disconnect();
}
}

View File

@ -0,0 +1,210 @@
<?php
namespace Icinga\Protocol\Livestatus;
use Icinga\Protocol;
class Query extends Protocol\AbstractQuery
{
protected $connection;
protected $table;
protected $filters = array();
protected $limit_count;
protected $limit_offset;
protected $columns;
protected $order_columns = array();
protected $count = false;
public function __construct(Connection $connection)
{
$this->connection = $connection;
}
public function getAdapter()
{
return $this->connection;
}
public function compare(& $a, & $b, $col_num = 0)
{
if (! array_key_exists($col_num, $this->order_columns)) {
return 0;
}
$col = $this->order_columns[$col_num][0];
$dir = $this->order_columns[$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, $this->order_columns)) {
return $this->compare($a, $b, $col_num);
} else {
return 0;
}
}
if ($dir === self::SORT_ASC) {
return $res;
} else {
return $res * -1;
}
}
public function hasOrder()
{
return ! empty($this->order_columns);
}
public function where($key, $val = null)
{
$this->filters[$key] = $val;
return $this;
}
public function order($col)
{
if (($pos = strpos($col, ' ')) !== false) {
$dir = strtoupper(substr($col, $pos + 1));
if ($dir === 'DESC') {
$dir = self::SORT_DESC;
} else {
$dir = self::SORT_ASC;
}
$col = substr($col, 0, $pos);
} else {
$col = $col;
}
$this->order_columns[] = array($col, $dir);
return $this;
}
// Nur wenn keine stats, sonst im RAM!!
// Offset gibt es nicht, muss simuliert werden
public function limit($count = null, $offset = null)
{
if (! preg_match('~^\d+~', $count . $offset)) {
throw new Exception(sprintf(
'Got invalid limit: %s, %s',
$count,
$offset
));
}
$this->limit_count = (int) $count;
$this->limit_offset = (int) $offset;
return $this;
}
public function hasLimit()
{
return $this->limit_count !== null;
}
public function hasOffset()
{
return $this->limit_offset > 0;
}
public function getLimit()
{
return $this->limit_count;
}
public function getOffset()
{
return $this->limit_offset;
}
public function from($table, $columns = null)
{
if (! $this->connection->hasTable($table)) {
throw new Exception(sprintf(
'This livestatus connection does not provide "%s"',
$table
));
}
$this->table = $table;
if (is_array($columns)) {
// TODO: check for valid names?
$this->columns = $columns;
}
return $this;
}
public function hasColumns()
{
return $this->columns !== null;
}
public function getColumns()
{
return $this->columns;
}
public function getColumnAliases()
{
$aliases = array();
foreach ($this->getColumns() as $key => $val) {
if (is_int($key)) {
$aliases[] = $val;
} else {
$aliases[] = $key;
}
}
return $aliases;
}
public function count()
{
$this->count = true;
return $this;
}
public function __toString()
{
if ($this->table === null) {
throw new Exception('Table is required');
}
$default_headers = array(
'OutputFormat: json',
'ResponseHeader: fixed16',
'KeepAlive: on'
);
$parts = array(
sprintf('GET %s', $this->table)
);
if ($this->count === false && $this->columns !== null) {
$parts[] = 'Columns: ' . implode(' ', $this->columns);
}
foreach ($this->filters as $key => $val) {
if ($key === 'search') {
$parts[] = 'Filter: host_name ~~ ' . $val;
$parts[] = 'Filter: description ~~ ' . $val;
$parts[] = 'Or: 2';
continue;
}
if ($val === null) {
$parts[] = 'Filter: ' . $key;
} elseif (strpos($key, '?') === false) {
$parts[] = sprintf('Filter: %s = %s', $key, $val);
} else {
$parts[] = sprintf('Filter: %s', str_replace('?', $val, $key));
}
}
if ($this->count === true) {
$parts[] = 'Stats: state >= 0';
}
if (! $this->count && $this->hasLimit() && ! $this->hasOrder()) {
$parts[] = 'Limit: ' . ($this->limit_count + $this->limit_offset);
}
$lql = implode("\n", $parts)
. "\n"
. implode("\n", $default_headers)
. "\n\n";
return $lql;
}
public function __destruct()
{
unset($this->connection);
}
}

View File

@ -0,0 +1,106 @@
<?php
namespace Icinga\Protocol\Nrpe;
class Connection
{
protected $host;
protected $port;
protected $connection;
protected $use_ssl = false;
protected $lastReturnCode = null;
public function __construct($host, $port = 5666)
{
$this->host = $host;
$this->port = $port;
}
public function useSsl($use_ssl = true)
{
$this->use_ssl = $use_ssl;
return $this;
}
public function sendCommand($command, $args = null)
{
if (! empty($args)) {
$command .= '!' . implode('!', $args);
}
$packet = Packet::createQuery($command);
return $this->send($packet);
}
public function getLastReturnCode()
{
return $this->lastReturnCode;
}
public function send(Packet $packet)
{
$conn = $this->connection();
$bytes = $packet->getBinary();
fputs($conn, $bytes, strlen($bytes));
// TODO: Check result checksum!
$result = fread($conn, 8192);
if ($result === false) {
throw new \Exception('CHECK_NRPE: Error receiving data from daemon.');
} elseif (strlen($result) === 0) {
throw new \Exception(
'CHECK_NRPE: Received 0 bytes from daemon.'
. ' Check the remote server logs for error messages'
);
}
// TODO: CHECK_NRPE: Receive underflow - only %d bytes received (%d expected)
$code = unpack('n', substr($result, 8, 2));
$this->lastReturnCode = $code[1];
$this->disconnect();
return rtrim(substr($result, 10, -2));
}
protected function connect()
{
$ctx = stream_context_create();
if ($this->use_ssl) {
// TODO: fail if not ok:
$res = stream_context_set_option($ctx, 'ssl', 'ciphers', 'ADH');
$uri = sprintf('ssl://%s:%d', $this->host, $this->port);
} else {
$uri = sprintf('tcp://%s:%d', $this->host, $this->port);
}
$this->connection = stream_socket_client(
$uri,
$errno,
$errstr,
60,
STREAM_CLIENT_CONNECT,
$ctx
);
if (! $this->connection) {
throw new \Exception(sprintf('NRPE Connection failed: ' . $errstr));
}
}
protected function connection()
{
if ($this->connection === null) {
$this->connect();
}
return $this->connection;
}
protected function disconnect()
{
if ($this->connection !== null) {
fclose($this->connection);
$this->connection = null;
}
return $this;
}
public function __destruct()
{
$this->disconnect();
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace Icinga\Protocol\Nrpe;
class Packet
{
const QUERY = 0x01;
const RESPONSE = 0x02;
protected $version = 0x02;
protected $type;
protected $body;
protected static $randomBytes;
public function __construct($type, $body)
{
$this->type = $type;
$this->body = $body;
$this->regenerateRandomBytes();
}
// TODO: renew "from time to time" to allow long-running daemons
protected function regenerateRandomBytes()
{
self::$randomBytes = '';
for ($i = 0; $i < 4096; $i++) {
self::$randomBytes .= pack('N', mt_rand());
}
}
public static function createQuery($body)
{
$packet = new Packet(self::QUERY, $body);
return $packet;
}
protected function getFillString($length)
{
$max = strlen(self::$randomBytes) - $length;
return substr(self::$randomBytes, rand(0, $max), $length);
}
// TODO: WTF is SR? And 2324?
public function getBinary()
{
$version = pack('n', $this->version);
$type = pack('n', $this->type);
$dummycrc = "\x00\x00\x00\x00";
$result = "\x00\x00";
$result = pack('n', 2324);
$body = $this->body
. "\x00"
. $this->getFillString(1023 - strlen($this->body))
. 'SR';
$crc = pack(
'N',
crc32($version . $type . $dummycrc . $result . $body)
);
$bytes = $version . $type . $crc . $result . $body;
return $bytes;
}
public function __toString()
{
return $this->body;
}
}

View File

@ -0,0 +1,71 @@
<?php
namespace Icinga\Util;
class Format
{
const STANDARD_IEC = 0;
const STANDARD_SI = 1;
protected static $instance;
protected static $bitPrefix = array(
array('bit', 'Kibit', 'Mibit', 'Gibit', 'Tibit', 'Pibit', 'Eibit', 'Zibit', 'Yibit'),
array('bit', 'kbit', 'Mbit', 'Gbit', 'Tbit', 'Pbit', 'Ebit', 'Zbit', 'Ybit'),
);
protected static $bitBase = array(1024, 1000);
protected static $bytePrefix = array(
array('B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'),
array('B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'),
);
protected static $byteBase = array(1024, 1000);
public static function getInstance()
{
if (self::$instance === null) {
self::$instance = new Format;
}
return self::$instance;
}
public static function bits($value, $standard = self::STANDARD_SI)
{
return self::formatForUnits(
$value,
self::$bitPrefix[$standard],
self::$bitBase[$standard]
);
}
public static function bytes($value, $standard = self::STANDARD_IEC)
{
return self::formatForUnits(
$value,
self::$bytePrefix[$standard],
self::$byteBase[$standard]
);
}
protected static function formatForUnits($value, & $units, $base)
{
$sign = '';
if ($value < 0) {
$value = abs($value);
$sign = '-';
}
$pow = floor(log($value, $base));
$result = $value / pow($base, $pow);
// 1034.23 looks better than 1.03, but 2.03 is fine:
if ($pow > 0 && $result < 2) {
$pow--;
$result = $value / pow($base, $pow);
}
return sprintf(
'%s%0.2f %s',
$sign,
$result,
$units[$pow]
);
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace Icinga\Web;
class Cookie
{
// TBD
}

View File

@ -0,0 +1,79 @@
<?php
namespace Icinga\Web;
use Zend_Form;
use Zend_Controller_Front as Front; // TODO: Get from App
use Zend_Controller_Action_HelperBroker as ZfActionHelper;
class Form extends Zend_Form
{
protected $request;
/**
* @param Zend_Controller_Request_Abstract $request
* @param array $options[optional]
*/
public function __construct($options = null)
{
/*
if (isset($options['prefill'])) {
$this->_prefill = $options['prefill'];
unset($options['prefill']);
}
*/
$this->request = Front::getInstance()->getRequest();
// $this->handleRequest();
foreach ($this->elements() as $key => $values) {
$this->addElement($values[0], $key, $values[1]); // do it better!
}
// Should be replaced with button check:
$this->addElement('hidden', '__submitted');
$this->setDefaults(array('__submitted' => 'true'));
parent::__construct($options);
if ($this->getAttrib('action') === null) {
$this->setAction($this->request->getRequestUri());
}
if ($this->getAttrib('method') === null) {
$this->setMethod('post');
}
if ($this->hasBeenSubmitted()) {
$this->handleRequest();
}
}
public function redirectNow($url)
{
ZfActionHelper::getStaticHelper('redirector')
->gotoUrlAndExit($url);
}
public function handleRequest()
{
if ($this->isValid($this->request->getPost())) {
$this->onSuccess();
} else {
$this->onFailure();
}
}
public function onSuccess()
{
}
public function onFailure()
{
}
public function hasBeenSubmitted()
{
return $this->request->getPost('__submitted', 'false') === 'true';
}
public function elements()
{
return array();
}
}

View File

@ -0,0 +1,90 @@
<?php
/**
* Icinga Web Grapher Hook
*/
namespace Icinga\Web\Hook;
/**
* Icinga Web Grapher Hook base class
*
* Extend this class if you want to integrate your graphing solution nicely into
* Icinga Web
*
* @copyright Copyright (c) 2013 Icinga-Web Team <info@icinga.org>
* @author Icinga-Web Team <info@icinga.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
*/
class Grapher
{
/**
* Whether this grapher provides preview images
*
* @var bool
*/
protected $hasPreviews = false;
/**
* Constructor must live without arguments right now
*
* Therefore the constructor is final, we might change our opinion about
* this one far day
*/
final public function __construct()
{
$this->init();
}
/**
* Whether this grapher provides preview images
*
* @return bool
*/
public function hasPreviews()
{
return $this->hasPreviews;
}
/**
* Overwrite this function if you want to do some initialization stuff
*
* @return void
*/
protected function init()
{
}
/**
* Whether a graph for the given host[, service [, plot]] exists
*
* @return bool
*/
public function hasGraph($host, $service = null, $plot = null)
{
return false;
}
/**
* Get a preview image for the given host[, service [, plot]] exists
*
* WARNING: We are not sure yet whether this will remain as is
*
* @return string
*/
public function getPreviewImage($host, $service = null, $plot = null)
{
throw new Exception('This backend has no preview images');
}
/**
* Get URL pointing to the grapher
*
* WARNING: We are not sure yet whether this will remain as is
*
* @return string
*/
public function getGraphUrl($host, $service = null, $plot = null)
{
throw new Exception('This backend has no images');
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace Icinga\Web\Hook;
use Icinga\Application\Logger as Logger;
abstract class Toptray {
const ALIGN_LEFT = "pull-left";
const ALIGN_NONE = "";
const ALIGN_RIGHT = "pull-right";
protected $align = self::ALIGN_NONE;
public function setAlignment($align)
{
$this->align = $align;
}
final public function getWidgetDOM()
{
try {
return '<ul class="nav '.$this->align.'" >'.$this->buildDOM().'</ul>';
} catch (\Exception $e) {
Logger::error("Could not create tray widget : %s",$e->getMessage());
return '';
}
}
abstract protected function buildDOM();
}

View File

@ -0,0 +1,87 @@
<?php
/**
* Module action controller
*/
namespace Icinga\Web;
use Icinga\Application\Config;
use Icinga\Application\Icinga;
/**
* Base class for all module action controllers
*
* All Icinga Web module controllers should extend this class
*
* @copyright Copyright (c) 2013 Icinga-Web Team <info@icinga.org>
* @author Icinga-Web Team <info@icinga.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
*/
class ModuleActionController extends ActionController
{
protected $module;
protected $module_dir;
/**
* Gives you this modules base directory
*
* @return string
*/
public function getModuleDir()
{
if ($this->module_dir === null) {
$this->module_dir = $this->getModule()->getBaseDir();
}
return $this->module_dir;
}
public function getModule()
{
if ($this->module === null) {
$this->module = Icinga::app()->getModule(
$this->module_name
);
}
return $this->module;
}
/**
* Translates the given string with the modules translation catalog
*
* @param string $string The string that should be translated
*
* @return string
*/
public function translate($string)
{
return mt($this->module_name, $string);
}
/**
* This is where the module configuration is going to be loaded
*
* @return void
*/
protected function loadConfig()
{
$this->config = Config::getInstance()->{$this->module_name};
}
/**
* Once dispatched we are going to place each modules output in a div
* container having the icinga-module and the icinga-$module-name classes
*
* @return void
*/
public function postDispatch()
{
parent::postDispatch();
$this->_helper->layout()->moduleStart =
'<div class="icinga-module module-'
. $this->module_name
. '">'
. "\n"
;
$this->_helper->layout()->moduleEnd = "</div>\n";
}
}

View File

@ -0,0 +1,113 @@
<?php
namespace Icinga\Web;
use Icinga\Exception\ProgrammingError;
use Icinga\Application\Platform;
use Icinga\Application\Logger as Log;
use Zend_Session_Namespace as SessionNamespace;
class Notification
{
protected static $instance;
protected $is_cli = false;
public static function info($msg)
{
self::getInstance()->addMessage($msg, 'info');
}
public static function success($msg)
{
self::getInstance()->addMessage($msg, 'success');
}
public static function warning($msg)
{
self::getInstance()->addMessage($msg, 'warning');
}
public static function error($msg)
{
self::getInstance()->addMessage($msg, 'error');
}
protected function addMessage($message, $type = 'info')
{
if (! in_array(
$type,
array(
'info',
'error',
'warning',
'success'
)
)) {
throw new ProgrammingError(
sprintf(
'"%s" is not a valid notification type',
$type
)
);
}
if ($this->is_cli) {
$msg = sprintf('[%s] %s', $type, $message);
switch ($type) {
case 'info':
case 'success':
Log::info($msg);
break;
case 'warning':
Log::warn($msg);
break;
case 'error':
Log::error($msg);
break;
}
return;
}
$mo = (object) array(
'type' => $type,
'message' => $message,
);
// Get, change, set - just to be on the safe side:
$msgs = $this->session->messages;
$msgs[] = $mo;
$this->session->messages = $msgs;
}
public function hasMessages()
{
return ! empty($this->session->messages);
}
public function getMessages()
{
$msgs = $this->session->messages;
$this->session->messages = array();
return $msgs;
}
final private function __construct()
{
$this->session = new SessionNamespace('IcingaNotification');
if (! is_array($this->session->messages)) {
$this->session->messages = array();
}
if (Platform::isCli()) {
$this->is_cli = true;
}
}
public static function getInstance()
{
if (self::$instance === null) {
self::$instance = new Notification();
}
return self::$instance;
}
}

View File

@ -0,0 +1,62 @@
<?php
namespace Icinga\Web\Paginator\Adapter;
/**
* @see Zend_Paginator_Adapter_Interface
*/
class QueryAdapter implements \Zend_Paginator_Adapter_Interface
{
/**
* Array
*
* @var array
*/
protected $query = null;
/**
* Item count
*
* @var integer
*/
protected $count = null;
/**
* Constructor.
*
* @param array $query Query to paginate
*/
// TODO: Re-add abstract Query type as soon as a more generic one
// is available. Should fit Protocol-Queries too.
// public function __construct(\Icinga\Backend\Query $query)
public function __construct($query)
{
$this->query = $query;
}
/**
* Returns an array of items for a page.
*
* @param integer $offset Page offset
* @param integer $itemCountPerPage Number of items per page
* @return array
*/
public function getItems($offset, $itemCountPerPage)
{
return $this->query->limit($itemCountPerPage, $offset)->fetchAll();
}
/**
* Returns the total number of items in the query result.
*
* @return integer
*/
public function count()
{
if ($this->count === null) {
$this->count = $this->query->count();
}
return $this->count;
}
}

View File

@ -0,0 +1,63 @@
<?php
/**
* @see Zend_Paginator_ScrollingStyle_Interface
*/
class Icinga_Web_Paginator_ScrollingStyle_SlidingWithBorder implements Zend_Paginator_ScrollingStyle_Interface
{
/**
* Returns an array of "local" pages given a page number and range.
*
* @param Zend_Paginator $paginator
* @param integer $pageRange (Optional) Page range
* @return array
*/
public function getPages(Zend_Paginator $paginator, $pageRange = null)
{
if ($pageRange === null) {
$pageRange = $paginator->getPageRange();
}
$pageNumber = $paginator->getCurrentPageNumber();
$pageCount = count($paginator);
$range = array();
if ($pageCount < 15) {
for ($i = 1; $i < 15; $i++) {
if ($i > $pageCount) break;
$range[$i] = $i;
}
} else {
foreach (array(1, 2) as $i) {
$range[$i] = $i;
}
if ($pageNumber > 8) {
$range[] = '...';
$start = 5;
if ($pageCount - $pageNumber < 8) {
$start = 9 - ($pageCount - $pageNumber);
}
for ($i = $pageNumber - $start; $i < $pageNumber + (10 - $start); $i++) {
if ($i > $pageCount) break;
$range[$i] = $i;
}
} else {
for ($i = 3; $i <= 10; $i++) {
$range[$i] = $i;
}
}
if ($pageNumber < ($pageCount - 7)) {
$range[] = '...';
foreach (array($pageCount - 1, $pageCount) as $i) {
$range[$i] = $i;
}
}
}
if (empty($range)) $range[] = 1;
return $range;
}
}

156
library/Icinga/Web/Session.php Executable file
View File

@ -0,0 +1,156 @@
<?php
/**
* Session handling
*/
namespace Icinga\Web;
use Icinga\Authentication\Auth\User;
use Zend_Session;
use Zend_Session_Namespace;
use Icinga\Exception\ProgrammingError;
/**
* Session handling happens here
*
* This is mainly a facade for Zend_Session_Namespace but provides some farther
* functionality for authentication
*
* @copyright Copyright (c) 2013 Icinga-Web Team <info@icinga.org>
* @author Icinga-Web Team <info@icinga.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
*/
class Session
{
/**
* Session is a Singleton stored in $instance
*
* @var Session
*/
protected static $instance;
protected $defaultOptions = array(
'use_trans_sid' => false,
'use_cookies' => true,
'use_only_cooies' => true,
'cookie_httponly' => true,
'use_only_cookies' => true,
'hash_function' => true,
'hash_bits_per_character' => 5,
);
/**
* The ZF session namespace
*
* @var \Zend_Session_Namespace
*/
protected $session;
protected $started = false;
protected $closed = true;
/**
* Constructor is protected to enforce singleton usage
*/
protected function __construct()
{
Zend_Session::start();
$this->session = new Zend_Session_Namespace('Icinga');
}
/*
// Not yet
public function start()
{
if ($this->started) {
return $this;
}
if ($this->closed) {
ini_set('session.cache_limiter', null);
ini_set('session.use_only_cookies', false);
ini_set('session.use_cookies', false);
ini_set('session.use_trans_sid', false);
}
$this->applyOptions();
session_start();
return $this;
}
protected function applyOptions()
{
foreach ($this->defaultOptions as $key => $val) {
ini_set('session.' . $key => $val);
}
return $this;
}
*/
public static function setOptions($options)
{
return Zend_Session::setOptions($options);
}
/**
* Once authenticated we store the given user(name) to our session
*
* @param Auth\User $user The user object
* // TODO: Useless
* @return self
*/
public function setAuthenticatedUser(User $user)
{
$this->session->userInfo = (string) $user;
$this->session->realname = (string) $user; // TODO: getRealName()
return $this;
}
/**
* Get the user object for the authenticated user
*
* // TODO: This has not been done yet. Useless?
*
* @return User $user The user object
*/
public function getUser()
{
throw new ProgrammingError('Not implemented yet');
}
/**
* Whether this session has an authenticated user
*
* // TODO: remove
* @return bool
*/
public function isAuthenticated()
{
return isset($this->session->username);
}
/**
* Forget everything we know about the authenticated user
*
* // TODO: Remove
* @return self
*/
public function discardAuthentication()
{
unset($this->session->username);
unset($this->session->realname);
return $this;
}
/**
* Get a Singleton instance
*
* TODO: This doesn't work so yet, it gives you a Zend_Session_Namespace
* instance. Facade has to be completed before we can fix this.
*
* @return Icinga\Web\Session -> not yet
*/
public static function getInstance()
{
if (self::$instance === null) {
self::$instance = new Session();
}
return self::$instance->session;
}
}

View File

@ -0,0 +1,53 @@
<?php
/**
* Web widget class
*/
namespace Icinga\Web;
use Icinga\Exception\ProgrammingError;
/**
* Web widgets make things easier for you!
*
* This class provides nothing but a static factory method for widget creation.
* Usually it will not be used directly as there are widget()-helpers available
* in your action controllers and view scripts.
*
* Usage example:
* <code>
* $tabs = Widget::create('tabs');
* </code>
*
* @copyright Copyright (c) 2013 Icinga-Web Team <info@icinga.org>
* @author Icinga-Web Team <info@icinga.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
*/
class Widget
{
/**
* Create a new widget
*
* @param string $name Widget name
* @param array $options Widget constructor options
*
* @return Icinga\Web\Widget\AbstractWidget
*/
public static function create($name, $options = array())
{
$class = 'Icinga\\Web\\Widget\\' . ucfirst($name);
if (! class_exists($class)) {
throw new ProgrammingError(
sprintf(
'There is no such widget: %s',
$name
)
);
}
$widget = new $class($options);
return $widget;
}
}

View File

@ -0,0 +1,162 @@
<?php
/**
* Web Widget abstract class
*/
namespace Icinga\Web\Widget;
use Icinga\Exception\ProgrammingError;
use Zend_Controller_Action_HelperBroker as ZfActionHelper;
/**
* Web widgets MUST extend this class
*
* AbstractWidget implements getters and setters for widget options stored in
* the protected options array. If you want to allow options for your own
* widget, you have to set a default value (may be null) for each single option
* in this array.
*
* Please have a look at the available widgets in this folder to get a better
* idea on what they should look like.
*
* @copyright Copyright (c) 2013 Icinga-Web Team <info@icinga.org>
* @author Icinga-Web Team <info@icinga.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
*/
abstract class AbstractWidget
{
/**
* If you are going to access the current view with the view() function,
* it's instance is stored here for performance reasons.
*
* @var Zend_View_Abstract
*/
protected static $view;
/**
* Fill $properties with default values for all your valid widget properties
*
* @var array
*/
protected $properties = array();
/**
* You MUST extend this function. This is where all your HTML voodoo happens
*
* @return string
*/
abstract public function renderAsHtml();
/**
* You are not allowed to override the constructor, but you can put
* initialization stuff in your init() function
*
* @return void
*/
protected function init()
{
}
/**
* We are not allowing you to override the constructor unless someone
* presents a very good reason for doing so
*
* @param array $properties An optional properties array
*/
final public function __construct($properties = array())
{
foreach ($properties as $key => $val) {
$this->$key = $val;
}
$this->init();
}
/**
* Getter for widget properties
*
* @param string $key The option you're interested in
*
* @throws ProgrammingError for unknown property name
*
* @return mixed
*/
public function __get($key)
{
if (array_key_exists($key, $this->properties)) {
return $this->properties[$key];
}
throw new ProgrammingError(
sprintf(
'Trying to get invalid "%s" property for %s',
$key,
get_class($this)
)
);
}
/**
* Setter for widget properties
*
* @param string $key The option you want to set
* @param string $val The new value going to be assigned to this option
*
* @throws ProgrammingError for unknown property name
*
* @return mixed
*/
public function __set($key, $val)
{
if (array_key_exists($key, $this->properties)) {
$this->properties[$key] = $val;
return;
}
throw new ProgrammingError(
sprintf(
'Trying to set invalid "%s" property in %s. Allowed are: %s',
$key,
get_class($this),
empty($this->properties)
? 'none'
: implode(', ', array_keys($this->properties))
)
);
}
/**
* Access the current view
*
* Will instantiate a new one if none exists
* // TODO: App->getView
*
* @return Zend_View_Abstract
*/
protected function view()
{
if (self::$view === null) {
$renderer = ZfActionHelper::getStaticHelper(
'viewRenderer'
);
if (null === $renderer->view) {
$renderer->initView();
}
self::$view = $renderer->view;
}
return self::$view;
}
/**
* Cast this widget to a string. Will call your renderAsHtml() function
*
* @return string
*/
public function __toString()
{
return $this->renderAsHtml();
}
}

View File

@ -0,0 +1,43 @@
<?php
/**
* Form
*/
namespace Icinga\Web\Widget;
/**
* A form loader...
*
* @copyright Copyright (c) 2013 Icinga-Web Team <info@icinga.org>
* @author Icinga-Web Team <info@icinga.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
*/
class Form extends AbstractWidget
{
protected $form;
protected $properties = array(
'name' => null
);
public function __call($func, $args)
{
return call_user_func_array(array($this->form, $func), $args);
}
protected function init()
{
// Load form by name given in props?
$class = 'Icinga\\Web\\Form\\' . ucfirst($this->name) . 'Form';
$file = ICINGA_APPDIR
. '/forms/authentication/'
. ucfirst($this->name)
. 'Form.php';
require_once($file);
$this->form = new $class;
}
public function renderAsHtml()
{
return (string) $this->form;
}
}

View File

@ -0,0 +1,119 @@
<?php
/**
* Single tab
*/
namespace Icinga\Web\Widget;
use Icinga\Exception\ProgrammingError;
/**
* A single tab, usually used through the tabs widget
*
* Will generate an &lt;li&gt; list item, with an optional link and icon
*
* @property string $name Tab identifier
* @property string $title Tab title
* @property string $icon Icon URL, preferrably relative to the Icinga
* base URL
* @property string $url Action URL, preferrably relative to the Icinga
* base URL
* @property string $urlParams Action URL Parameters
*
* @copyright Copyright (c) 2013 Icinga-Web Team <info@icinga.org>
* @author Icinga-Web Team <info@icinga.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
*/
class Tab extends AbstractWidget
{
/**
* Whether this tab is currently active
*
* @var bool
*/
protected $active = false;
/**
* Default values for widget properties
*
* @var array
*/
protected $properties = array(
'name' => null,
'title' => '',
'url' => null,
'urlParams' => array(),
'icon' => null,
);
/**
* Health check at initialization time
*
* @throws Icinga\Exception\ProgrammingError if tab name is missing
*
* @return void
*/
protected function init()
{
if ($this->name === null) {
throw new ProgrammingError(
'Cannot create a nameless tab'
);
}
}
/**
* Set this tab active (default) or inactive
*
* This is usually done through the tabs container widget, therefore it
* is not a good idea to directly call this function
*
* @param bool $active Whether the tab should be active
*
* @return self
*/
public function setActive($active = true)
{
$this->active = (bool) $active;
return $this;
}
/**
* Whether this tab is currently active
*
* @return bool
*/
public function isActive()
{
return $this->active;
}
/**
* This is where the list item HTML is created
*
* @return string
*/
public function renderAsHtml()
{
$view = $this->view();
$class = $this->isActive() ? ' class="active"' : '';
$caption = $this->title;
if ($this->icon !== null) {
$caption = $view->img($this->icon, array(
'width' => 16,
'height' => 16
)) . ' ' . $caption;
}
if ($this->url !== null) {
$tab = $view->qlink(
$caption,
$this->url,
$this->urlParams,
array('quote' => false)
);
} else {
$tab = $caption;
}
return "<li $class>$tab</li>\n";
}
}

View File

@ -0,0 +1,195 @@
<?php
/**
* Navigation tabs
*/
namespace Icinga\Web\Widget;
use Icinga\Exception\ProgrammingError;
/**
* Navigation tab widget
*
* Useful if you want to create navigation tabs
*
* @copyright Copyright (c) 2013 Icinga-Web Team <info@icinga.org>
* @author Icinga-Web Team <info@icinga.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
*/
class Tabs extends AbstractWidget
{
/**
* This is where single tabs added to this container will be stored
*
* @var array
*/
protected $tabs = array();
/**
* The name of the currently activated tab
*
* @var string
*/
protected $active;
/**
* Class name(s) going to be assigned to the &lt;ul&gt; element
*
* @var string
*/
protected $tab_class = 'nav-tabs';
/**
* Activate the tab with the given name
*
* If another tab is currently active it will be deactivated
*
* @param string $name Name of the tab going to be activated
*
* @throws ProgrammingError if given tab name doesn't exist
*
* @return self
*/
public function activate($name)
{
if ($this->has($name)) {
if ($this->active !== null) {
$this->tabs[$this->active]->setActive(false);
}
$this->get($name)->setActive();
$this->active = $name;
return $this;
}
throw new ProgrammingError(
sprintf(
"Cannot activate a tab that doesn't exist: %s. Available: %s",
$name,
empty($this->tabs)
? 'none'
: implode(', ', array_keys($this->tabs))
)
);
}
public function getActiveName()
{
return $this->active;
}
/**
* Set the CSS class name(s) for the &lt;ul&gt; element
*
* @param string $name CSS class name(s)
*
* @return self
*/
public function setClass($name)
{
$this->tab_class = $name;
return $this;
}
/**
* Whether the given tab name exists
*
* @param string $name Tab name
*
* @return bool
*/
public function has($name)
{
return array_key_exists($name, $this->tabs);
}
/**
* Whether the given tab name exists
*
* @param string $name The tab you're interested in
*
* @throws ProgrammingError if given tab name doesn't exist
*
* @return Tab
*/
public function get($name)
{
if (! $this->has($name)) {
throw new ProgrammingError(
sprintf(
'There is no such tab: %s',
$name
)
);
}
return $this->tabs[$name];
}
/**
* Add a new tab
*
* A unique tab name is required, the Tab itself can either be an array
* with tab properties or an instance of an existing Tab
*
* @param string $name The new tab name
* @param array|Tab The tab itself of it's properties
*
* @throws ProgrammingError if tab name already exists
*
* @return self
*/
public function add($name, $tab)
{
if ($this->has($name)) {
throw new ProgrammingError(
sprintf(
'Cannot add a tab named "%s" twice"',
$name
)
);
}
return $this->set($name, $tab);
}
/**
* Set a tab
*
* A unique tab name is required, will be replaced in case it already
* exists. The tab can either be an array with tab properties or an instance
* of an existing Tab
*
* @param string $name The new tab name
* @param array|Tab The tab itself of it's properties
*
* @return self
*/
public function set($name, $tab)
{
if ($tab instanceof Tab) {
$this->tabs[$name] = $tab;
} else {
$this->tabs[$name] = new Tab($tab + array('name' => $name));
}
return $this;
}
/**
* This is where the tabs are going to be rendered
*
* @return string
*/
public function renderAsHtml()
{
$view = $this->view();
if (empty($this->tabs)) {
return '';
}
$html = '<ul class="nav ' . $this->tab_class . '">' . "\n";
foreach ($this->tabs as $tab) {
$html .= $tab;
}
$html .= "</ul>\n";
return $html;
}
}