summaryrefslogtreecommitdiffstats
path: root/inc
diff options
context:
space:
mode:
authorUdo Walter2017-09-01 15:27:06 +0200
committerUdo Walter2017-09-01 15:27:06 +0200
commit599c259845cd94a0bb64233b9935ea8c38be65b0 (patch)
treef6b2601d774aa5a0ca8785015d997545ef9952f7 /inc
parent[js_stupidtable] changed one-line code to formatted code; (diff)
parent[locationinfo] Add backend for Microsoft Exchange Server (diff)
downloadslx-admin-599c259845cd94a0bb64233b9935ea8c38be65b0.tar.gz
slx-admin-599c259845cd94a0bb64233b9935ea8c38be65b0.tar.xz
slx-admin-599c259845cd94a0bb64233b9935ea8c38be65b0.zip
Merge remote-tracking branch 'origin/master' into permission-manager
Diffstat (limited to 'inc')
-rw-r--r--inc/database.inc.php116
-rw-r--r--inc/dictionary.inc.php4
-rw-r--r--inc/download.inc.php1
-rw-r--r--inc/event.inc.php30
-rw-r--r--inc/module.inc.php36
-rw-r--r--inc/property.inc.php11
-rw-r--r--inc/render.inc.php52
-rw-r--r--inc/session.inc.php9
-rw-r--r--inc/util.inc.php30
9 files changed, 263 insertions, 26 deletions
diff --git a/inc/database.inc.php b/inc/database.inc.php
index ff98f5ee..150f828a 100644
--- a/inc/database.inc.php
+++ b/inc/database.inc.php
@@ -45,7 +45,7 @@ class Database
*
* @return array|boolean Associative array representing row, or false if no row matches the query
*/
- public static function queryFirst($query, $args = array(), $ignoreError = false)
+ public static function queryFirst($query, $args = array(), $ignoreError = null)
{
$res = self::simpleQuery($query, $args, $ignoreError);
if ($res === false)
@@ -53,6 +53,20 @@ class Database
return $res->fetch(PDO::FETCH_ASSOC);
}
+ /**
+ * If you need all rows for a query as plain array you can use this.
+ * Don't use this if you want to do further processing of the data, to save some
+ * memory.
+ *
+ * @return array|bool List of associative arrays representing rows, or false on error
+ */
+ public static function queryAll($query, $args = array(), $ignoreError = null)
+ {
+ $res = self::simpleQuery($query, $args, $ignoreError);
+ if ($res === false)
+ return false;
+ return $res->fetchAll(PDO::FETCH_ASSOC);
+ }
/**
* Execute the given query and return the number of rows affected.
@@ -63,7 +77,7 @@ class Database
* @param boolean $ignoreError Ignore query errors and just return false
* @return int|boolean Number of rows affected, or false on error
*/
- public static function exec($query, $args = array(), $ignoreError = false)
+ public static function exec($query, $args = array(), $ignoreError = null)
{
$res = self::simpleQuery($query, $args, $ignoreError);
if ($res === false)
@@ -95,11 +109,16 @@ class Database
* query again with different params, do not rely on the first PDOStatement
* still being valid. If you need to do something fancy, use Database::prepare
*
- * @return \PDOStatement The query result object
+ * @return \PDOStatement|false The query result object
*/
- public static function simpleQuery($query, $args = array(), $ignoreError = false)
+ public static function simpleQuery($query, $args = array(), $ignoreError = null)
{
self::init();
+ if (CONFIG_DEBUG && preg_match('/^\s*SELECT/is', $query)) {
+ self::explainQuery($query, $args);
+ }
+ // Support passing nested arrays for IN statements, automagically refactor
+ self::handleArrayArgument($query, $args);
try {
if (!isset(self::$statements[$query])) {
self::$statements[$query] = self::$dbh->prepare($query);
@@ -108,20 +127,105 @@ class Database
}
if (self::$statements[$query]->execute($args) === false) {
self::$lastError = implode("\n", self::$statements[$query]->errorInfo());
- if ($ignoreError || self::$returnErrors)
+ if ($ignoreError === true || ($ignoreError === null && self::$returnErrors))
return false;
Util::traceError("Database Error: \n" . self::$lastError);
}
return self::$statements[$query];
} catch (Exception $e) {
self::$lastError = '(' . $e->getCode() . ') ' . $e->getMessage();
- if ($ignoreError || self::$returnErrors)
+ if ($ignoreError === true || ($ignoreError === null && self::$returnErrors))
return false;
Util::traceError("Database Error: \n" . self::$lastError);
}
return false;
}
+ private static function explainQuery($query, $args)
+ {
+ $res = self::simpleQuery('EXPLAIN ' . $query, $args, true);
+ if ($res === false)
+ return;
+ $rows = $res->fetchAll(PDO::FETCH_ASSOC);
+ if (empty($rows))
+ return;
+ $log = false;
+ $lens = array();
+ foreach (array_keys($rows[0]) as $key) {
+ $lens[$key] = strlen($key);
+ }
+ foreach ($rows as $row) {
+ if (!$log && preg_match('/filesort|temporary/i', $row['Extra'])) {
+ $log = true;
+ }
+ foreach ($row as $key => $col) {
+ $l = strlen($col);
+ if ($l > $lens[$key]) {
+ $lens[$key] = $l;
+ }
+ }
+ }
+ if (!$log)
+ return;
+ error_log('Possible slow query: ' . $query);
+ $border = $head = '';
+ foreach ($lens as $key => $len) {
+ $border .= '+' . str_repeat('-', $len + 2);
+ $head .= '| ' . str_pad($key, $len) . ' ';
+ }
+ $border .= '+';
+ $head .= '|';
+ error_log("\n" . $border . "\n" . $head . "\n" . $border);
+ foreach ($rows as $row) {
+ $line = '';
+ foreach ($lens as $key => $len) {
+ $line .= '| '. str_pad($row[$key], $len) . ' ';
+ }
+ error_log($line . "|");
+ }
+ error_log($border);
+ }
+
+ /**
+ * Convert nested array argument to multiple arguments.
+ * If you have:
+ * $query = 'SELECT * FROM tbl WHERE bcol = :bool AND col IN (:list)
+ * $args = ( 'bool' => 1, 'list' => ('foo', 'bar') )
+ * it results in:
+ * $query = '...WHERE bcol = :bool AND col IN (:list_0, :list_1)
+ * $args = ( 'bool' => 1, 'list_0' => 'foo', 'list_1' => 'bar' )
+ *
+ * @param string $query sql query string
+ * @param array $args query arguments
+ */
+ private static function handleArrayArgument(&$query, &$args)
+ {
+ foreach (array_keys($args) as $key) {
+ if (is_numeric($key) || $key === '?')
+ continue;
+ if (is_array($args[$key])) {
+ if (empty($args[$key])) {
+ // Empty list - what to do? We try to generate a query string that will not yield any result
+ $args[$key] = 'asdf' . mt_rand(0,PHP_INT_MAX) . mt_rand(0,PHP_INT_MAX)
+ . mt_rand(0,PHP_INT_MAX) . '@' . microtime(true);
+ continue;
+ }
+ $newkey = $key;
+ if ($newkey{0} !== ':') {
+ $newkey = ":$newkey";
+ }
+ $new = array();
+ foreach ($args[$key] as $subIndex => $sub) {
+ $new[] = $newkey . '_' . $subIndex;
+ $args[$newkey . '_' . $subIndex] = $sub;
+ }
+ unset($args[$key]);
+ $new = implode(',', $new);
+ $query = preg_replace('/' . $newkey . '\b/', $new, $query);
+ }
+ }
+ }
+
/**
* Simply calls PDO::prepare and returns the PDOStatement.
* You must call PDOStatement::execute manually on it.
diff --git a/inc/dictionary.inc.php b/inc/dictionary.inc.php
index 634b1c3c..ca2811ff 100644
--- a/inc/dictionary.inc.php
+++ b/inc/dictionary.inc.php
@@ -81,11 +81,11 @@ class Dictionary
return $strings[$tag];
}
- public static function translateFile($path, $tag)
+ public static function translateFile($path, $tag, $returnTagOnMissing = false)
{
if (!class_exists('Page') || Page::getModule() === false)
return false; // We have no page - return false for now, as we're most likely running in api or install mode
- return self::translateFileModule(Page::getModule()->getIdentifier(), $path, $tag);
+ return self::translateFileModule(Page::getModule()->getIdentifier(), $path, $tag, $returnTagOnMissing);
}
public static function translate($tag, $returnTagOnMissing = false)
diff --git a/inc/download.inc.php b/inc/download.inc.php
index a2054f78..b3496e85 100644
--- a/inc/download.inc.php
+++ b/inc/download.inc.php
@@ -21,6 +21,7 @@ class Download
curl_setopt($ch, CURLOPT_MAXREDIRS, 6);
$tmpfile = tempnam('/tmp/', 'bwlp-');
$head = fopen($tmpfile, 'w+b');
+ unlink($tmpfile);
if ($head === false)
Util::traceError("Could not open temporary head file $tmpfile for writing.");
curl_setopt($ch, CURLOPT_WRITEHEADER, $head);
diff --git a/inc/event.inc.php b/inc/event.inc.php
index 7a7c48b0..a16be97b 100644
--- a/inc/event.inc.php
+++ b/inc/event.inc.php
@@ -20,12 +20,17 @@ class Event
EventLog::info('System boot...');
$everythingFine = true;
+ // Delete job entries that might have been running when system rebooted
+ Property::clearList('cron.key.status');
+ Property::clearList('cron.key.blocked');
+
// Tasks: fire away
+ $mountStatus = false;
$mountId = Trigger::mount();
$autoIp = Trigger::autoUpdateServerIp();
$ldadpId = Trigger::ldadp();
$ipxeId = Trigger::ipxe();
-
+
Taskmanager::submit('DozmodLauncher', array(
'operation' => 'start'
));
@@ -36,11 +41,8 @@ class Event
EventLog::info('No VM store type defined.');
$everythingFine = false;
} else {
- $res = Taskmanager::waitComplete($mountId, 5000);
- if (Taskmanager::isFailed($res)) {
- EventLog::failure('Mounting VM store failed', $res['data']['messages']);
- $everythingFine = false;
- }
+ $mountStatus = Taskmanager::waitComplete($mountId, 5000);
+
}
// LDAP AD Proxy
if ($ldadpId === false) {
@@ -70,6 +72,22 @@ class Event
}
}
+ if ($mountStatus !== false && !Taskmanager::isFinished($mountStatus)) {
+ $mountStatus = Taskmanager::waitComplete($mountStatus, 5000);
+ }
+ if (Taskmanager::isFailed($mountStatus)) {
+ // One more time, network could've been down before
+ sleep(10);
+ $mountId = Trigger::mount();
+ $mountStatus = Taskmanager::waitComplete($mountId, 10000);
+ }
+ if ($mountId !== false && Taskmanager::isFailed($mountStatus)) {
+ EventLog::failure('Mounting VM store failed', $mountStatus['data']['messages']);
+ $everythingFine = false;
+ } elseif ($mountId !== false && !Taskmanager::isFinished($mountStatus)) {
+ // TODO: Still running - create callback
+ }
+
// Just so we know booting is done (and we don't expect any more errors from booting up)
if ($everythingFine) {
EventLog::info('Bootup finished without errors.');
diff --git a/inc/module.inc.php b/inc/module.inc.php
index 597cfb57..7211c68c 100644
--- a/inc/module.inc.php
+++ b/inc/module.inc.php
@@ -142,13 +142,23 @@ class Module
private $activated = false;
private $dependencies = array();
private $name;
+ /**
+ * @var array assoc list of 'filename.css' => true|false (true = always load, false = only if module is main module)
+ */
+ private $css = array();
+ /**
+ * @var array assoc list of 'filename.js' => true|false (true = always load, false = only if module is main module)
+ */
+ private $scripts = array();
private function __construct($name)
{
$file = 'modules/' . $name . '/config.json';
$json = @json_decode(@file_get_contents($file), true);
- if (isset($json['dependencies']) && is_array($json['dependencies'])) {
- $this->dependencies = $json['dependencies'];
+ foreach (['dependencies', 'css', 'scripts'] as $key) {
+ if (isset($json[$key]) && is_array($json[$key])) {
+ $this->$key = $json[$key];
+ }
}
if (isset($json['category']) && is_string($json['category'])) {
$this->category = $json['category'];
@@ -251,4 +261,26 @@ class Module
return 'modules/' . $this->name;
}
+ public function getScripts($externalOnly)
+ {
+ if (!$externalOnly) {
+ if (!isset($this->scripts['clientscript.js']) && file_exists($this->getDir() . '/clientscript.js')) {
+ $this->scripts['clientscript.js'] = false;
+ }
+ return array_keys($this->scripts);
+ }
+ return array_keys(array_filter($this->scripts));
+ }
+
+ public function getCss($externalOnly)
+ {
+ if (!$externalOnly) {
+ if (!isset($this->css['style.css']) && file_exists($this->getDir() . '/style.css')) {
+ $this->css['style.css'] = false;
+ }
+ return array_keys($this->css);
+ }
+ return array_keys(array_filter($this->css));
+ }
+
}
diff --git a/inc/property.inc.php b/inc/property.inc.php
index b33e1bff..0b4ea7b3 100644
--- a/inc/property.inc.php
+++ b/inc/property.inc.php
@@ -105,6 +105,17 @@ class Property
));
}
+ /**
+ * Delete entire list with given key.
+ *
+ * @param string $key Key of list
+ * @return int number of items removed
+ */
+ public static function clearList($key)
+ {
+ return Database::exec("DELETE FROM property_list WHERE name = :key", compact('key'));
+ }
+
/*
* Legacy getters/setters
*/
diff --git a/inc/render.inc.php b/inc/render.inc.php
index 5515c659..53e2f314 100644
--- a/inc/render.inc.php
+++ b/inc/render.inc.php
@@ -40,19 +40,39 @@ class Render
self::$mustache = new Mustache_Engine($options);
}
+ private static function cssEsc($str)
+ {
+ return str_replace(array('"', '&', '<', '>'), array('\\000022', '\\000026', '\\00003c', '\\00003e'), $str);
+ }
+
/**
* Output the buffered, generated page
*/
public static function output()
{
Header('Content-Type: text/html; charset=utf-8');
+ /* @var $modules Module[] */
$modules = array_reverse(Module::getActivated());
+ $pageModule = Page::getModule();
+ $title = Property::get('page-title-prefix', '');
+ $bgcolor = Property::get('logo-background', '');
+ if (!empty($bgcolor) || !empty($title)) {
+ self::$header .= '<style type="text/css">' . "\n";
+ if (!empty($bgcolor)) {
+ $fgcolor = self::readableColor($bgcolor);
+ self::$header .= ".navbar-header{background-color:$bgcolor}a.navbar-brand{color:$fgcolor!important}";
+ }
+ if (!empty($title)) {
+ self::$header .= '#navbar-sub:after{content:"' . self::cssEsc($title) . '";margin:0}';
+ }
+ self::$header .= "\n</style>";
+ }
ob_start('ob_gzhandler');
echo
'<!DOCTYPE html>
<html>
<head>
- <title>', self::$title, RENDER_DEFAULT_TITLE, '</title>
+ <title>', $title, self::$title, RENDER_DEFAULT_TITLE, '</title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -61,9 +81,9 @@ class Render
';
// Include any module specific styles
foreach ($modules as $module) {
- $file = $module->getDir() . '/style.css';
- if (file_exists($file)) {
- echo '<link href="', $file, '" rel="stylesheet" media="screen">';
+ $files = $module->getCss($module != $pageModule);
+ foreach ($files as $file) {
+ echo '<link href="', $module->getDir(), '/', $file, '" rel="stylesheet" media="screen">';
}
}
echo '
@@ -91,9 +111,9 @@ class Render
<script src="script/collapse.js"></script>
';
foreach ($modules as $module) {
- $file = $module->getDir() . '/clientscript.js';
- if (file_exists($file)) {
- echo '<script src="', $file, '"></script>';
+ $files = $module->getScripts($module != $pageModule);
+ foreach ($files as $file) {
+ echo '<script src="', $module->getDir(), '/', $file, '"></script>';
}
}
echo
@@ -285,4 +305,22 @@ class Render
self::$dashboard = $params;
}
+ public static function readableColor($hex) {
+ if (strlen($hex) <= 4) {
+ $cnt = 1;
+ } else {
+ $cnt = 2;
+ }
+ if (preg_match('/^#?([a-f0-9]{'.$cnt.'})([a-f0-9]{'.$cnt.'})([a-f0-9]{'.$cnt.'})$/i', $hex, $out) != 1)
+ return '#000';
+ $chans = array();
+ $f = ($cnt === 1 ? 17 : 1);
+ for ($i = 1; $i <= 3; ++$i) {
+ $out[$i] = (hexdec($out[$i]) * $f);
+ $chans[] = $out[$i] ^ 0x80;
+ }
+ $b = (255 - (0.299 * $out[1] + 0.587 * $out[2] + 0.114 * $out[3])) * 2;
+ return sprintf("#%02x%02x%02x", ($chans[0] + $b) / 3, ($chans[1] + $b) / 3, ($chans[2] + $b) / 3);
+ }
+
}
diff --git a/inc/session.inc.php b/inc/session.inc.php
index 26effa3f..24bf6ac0 100644
--- a/inc/session.inc.php
+++ b/inc/session.inc.php
@@ -74,10 +74,15 @@ class Session
{
if (self::$sid === false) return;
@unlink(self::getSessionFile());
- @setcookie('sid', '', time() - 8640000, null, null, !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off', true);
+ self::deleteCookie();
self::$sid = false;
self::$data = false;
}
+
+ public static function deleteCookie()
+ {
+ setcookie('sid', '', time() - 8640000, null, null, !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off', true);
+ }
private static function getSessionFile()
{
@@ -104,7 +109,7 @@ class Session
$sessionfile = self::getSessionFile();
$ret = @file_put_contents($sessionfile, @serialize(self::$data));
if (!$ret) Util::traceError('Storing session data in ' . $sessionfile . ' failed.');
- $ret = @setcookie('sid', self::$sid, time() + CONFIG_SESSION_TIMEOUT, null, null, !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off', true);
+ $ret = setcookie('sid', self::$sid, time() + CONFIG_SESSION_TIMEOUT, null, null, !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off', true);
if (!$ret) Util::traceError('Error: Could not set Cookie for Client (headers already sent)');
}
}
diff --git a/inc/util.inc.php b/inc/util.inc.php
index 5d1a4563..9bcfdf13 100644
--- a/inc/util.inc.php
+++ b/inc/util.inc.php
@@ -12,7 +12,7 @@ class Util
*/
public static function traceError($message)
{
- if ((defined('API') && API) || (defined('AJAX') && AJAX)) {
+ if ((defined('API') && API) || (defined('AJAX') && AJAX) || php_sapi_name() === 'cli') {
error_log('API ERROR: ' . $message);
error_log(self::formatBacktracePlain(debug_backtrace()));
}
@@ -434,4 +434,32 @@ SADFACE;
return $bytes;
}
+ /**
+ * @return string a random UUID, v4.
+ */
+ public static function randomUuid()
+ {
+ $b = unpack('h8a/h4b/h12c', self::randomBytes(12));
+ return sprintf('%08s-%04s-%04x-%04x-%012s',
+
+ // 32 bits for "time_low"
+ $b['a'],
+
+ // 16 bits for "time_mid"
+ $b['b'],
+
+ // 16 bits for "time_hi_and_version",
+ // four most significant bits holds version number 4
+ mt_rand(0, 0x0fff) | 0x4000,
+
+ // 16 bits, 8 bits for "clk_seq_hi_res",
+ // 8 bits for "clk_seq_low",
+ // two most significant bits holds zero and one for variant DCE1.1
+ mt_rand(0, 0x3fff) | 0x8000,
+
+ // 48 bits for "node"
+ $b['c']
+ );
+ }
+
}