summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--config.php.example30
-rw-r--r--inc/dashboard.inc.php38
-rw-r--r--inc/event.inc.php4
-rw-r--r--inc/module.inc.php12
-rw-r--r--inc/trigger.inc.php13
-rw-r--r--inc/util.inc.php64
-rw-r--r--modules-available/dozmod/api.inc.php250
-rw-r--r--modules-available/exams/inc/exams.inc.php4
-rw-r--r--modules-available/exams/install.inc.php9
-rw-r--r--modules-available/exams/lang/de/messages.json11
-rw-r--r--modules-available/exams/lang/de/module.json8
-rw-r--r--modules-available/exams/lang/de/template-tags.json18
-rw-r--r--modules-available/exams/lang/en/messages.json1
-rw-r--r--modules-available/exams/lang/en/template-tags.json12
-rw-r--r--modules-available/exams/page.inc.php213
-rw-r--r--modules-available/exams/style.css23
-rw-r--r--modules-available/exams/templates/page-add-edit-exam.html65
-rw-r--r--modules-available/exams/templates/page-exams-vis.html52
-rw-r--r--modules-available/exams/templates/page-exams.html128
-rw-r--r--modules-available/exams/templates/page-main-heading.html3
-rw-r--r--modules-available/exams/templates/page-upcoming-lectures.html34
-rw-r--r--modules-available/locations/page.inc.php2
-rw-r--r--modules-available/main/lang/de/module.json3
-rw-r--r--modules-available/main/lang/en/module.json3
-rw-r--r--modules-available/minilinux/hooks/main-warning.inc.php2
-rw-r--r--modules-available/serversetup-bwlp/hooks/main-warning.inc.php6
-rw-r--r--modules-available/serversetup-bwlp/lang/de/messages.json3
-rw-r--r--modules-available/serversetup-bwlp/lang/de/template-tags.json7
-rw-r--r--modules-available/serversetup-bwlp/lang/en/messages.json3
-rw-r--r--modules-available/serversetup-bwlp/lang/en/template-tags.json7
-rw-r--r--modules-available/serversetup-bwlp/page.inc.php2
-rw-r--r--modules-available/serversetup-bwlp/templates/ipxe.html38
-rw-r--r--modules-available/sysconfig/hooks/main-warning.inc.php4
-rw-r--r--modules-available/sysconfig/inc/configmodule.inc.php70
-rw-r--r--modules-available/sysconfig/inc/configmodule/adauth.inc.php73
-rw-r--r--modules-available/sysconfig/inc/configmodule/branding.inc.php5
-rw-r--r--modules-available/sysconfig/inc/configmodule/customodule.inc.php5
-rw-r--r--modules-available/sysconfig/inc/configmodule/ldapauth.inc.php78
-rw-r--r--modules-available/sysconfig/inc/configmodulebaseldap.inc.php85
-rw-r--r--modules-available/sysconfig/install.inc.php23
-rw-r--r--modules-available/sysconfig/page.inc.php88
-rw-r--r--modules-available/sysconfig/templates/list-modules.html26
-rw-r--r--style/default.css10
43 files changed, 970 insertions, 565 deletions
diff --git a/config.php.example b/config.php.example
index 14978897..82fd1b77 100644
--- a/config.php.example
+++ b/config.php.example
@@ -27,12 +27,24 @@ define('CONFIG_VMSTORE_DIR', '/srv/openslx/nfs');
define('CONFIG_PROXY_CONF', '/opt/openslx/proxy/config');
/* for the dozmod API proxy cache */
-define('CONFIG_DOZMOD_EXPIRE', 60*60); // 1 Minute
-
-// Sort order for menu - optional, if missing, order will be alphabetically
-$MENU_CAT_SORT_ORDER = array('main.content' => 0, 'main.settings-client' => 1, 'main.settings-server' => 2, 'main.status' => 3, 'main.users' => 4);
-$MENU_SETTING_SORT_ORDER = array(
- 'news' => 0, 'sysconfig' => 1, 'baseconfig' => 2, 'locations' => 3, // main.content
- 'serversetup' => 0, 'internetaccess' => 1, 'vmstore' => 2, 'webinterface' => 3, 'backup' => 4, // main.settings
- 'systemstatus' => 0, 'eventlog' => 1, 'syslog' => 2, 'statistics' => 3 // main.status
-);
+define('CONFIG_DOZMOD_URL', 'http://127.0.0.1:9080');
+define('CONFIG_DOZMOD_EXPIRE', 60);
+
+// Sort order for menu
+// Optional - if missing, will be sorted by module id (internal name)
+// Here it is also possible to assign a module to a different category,
+// overriding the config.json entry
+$MENU_CAT_OVERRIDE = array(
+ 'main.content' => array(
+ 'news', 'locations', 'exams', 'dozmod', 'translation'
+ ),
+ 'main.settings-server' => array(
+ 'serversetup', 'vmstore', 'webinterface', 'backup'
+ ),
+ 'main.settings-client' => array(
+ 'sysconfig', 'baseconfig', 'minilinux'
+ ),
+ 'main.status' => array(
+ 'systemstatus', 'eventlog', 'syslog', 'statistics'
+ )
+); \ No newline at end of file
diff --git a/inc/dashboard.inc.php b/inc/dashboard.inc.php
index 3c913b76..01572461 100644
--- a/inc/dashboard.inc.php
+++ b/inc/dashboard.inc.php
@@ -8,23 +8,37 @@ class Dashboard
public static function createMenu()
{
- global $MENU_SETTING_SORT_ORDER, $MENU_CAT_SORT_ORDER;
+ global $MENU_CAT_OVERRIDE;
$modByCategory = array();
- $all = Module::getEnabled();
+ $modById = array();
+ if (isset($MENU_CAT_OVERRIDE)) {
+ foreach ($MENU_CAT_OVERRIDE as $cat => $list) {
+ foreach ($list as $mod) {
+ $modByCategory[$cat][$mod] = false;
+ $modById[$mod] =& $modByCategory[$cat][$mod];
+ }
+ }
+ }
+ $all = Module::getEnabled(true);
foreach ($all as $module) {
$cat = $module->getCategory();
if ($cat === false)
continue;
- $modByCategory[$cat][] = $module;
+ $modId = $module->getIdentifier();
+ if (isset($modById[$modId])) {
+ $modById[$modId] = $module;
+ } else {
+ $modByCategory[$cat][$modId] = $module;
+ }
}
$currentPage = Page::getModule()->getIdentifier();
$categories = array();
$catSort = array();
foreach ($modByCategory as $catId => $modList) {
$modules = array();
- $sectionSort = array();
- foreach ($modList as $module) {
- $modId = $module->getIdentifier();
+ foreach ($modList as $modId => $module) {
+ if ($module === false)
+ continue; // Was set in $MENU_CAT_OVERRIDE, but is not enabled
$newEntry = array(
'displayName' => $module->getDisplayName(),
'identifier' => $module->getIdentifier()
@@ -34,25 +48,13 @@ class Dashboard
$newEntry['subMenu'] = self::$subMenu;
}
$modules[] = $newEntry;
- if (isset($MENU_SETTING_SORT_ORDER[$modId])) {
- $sectionSort[] = (string)($MENU_SETTING_SORT_ORDER[$modId] + 1000);
- } else {
- $sectionSort[] = '9999' . $modId;
- }
}
- array_multisort($sectionSort, SORT_ASC, $modules);
$categories[] = array(
'icon' => self::getCategoryIcon($catId),
'displayName' => Dictionary::getCategoryName($catId),
'modules' => $modules
);
- if (isset($MENU_CAT_SORT_ORDER[$catId])) {
- $catSort[] = (string)($MENU_CAT_SORT_ORDER[$catId] + 1000);
- } else {
- $catSort[] = '9999' . $catId;
- }
}
- array_multisort($catSort, SORT_ASC, $categories);
Render::setDashboard(array(
'categories' => $categories,
'url' => urlencode($_SERVER['REQUEST_URI']),
diff --git a/inc/event.inc.php b/inc/event.inc.php
index 01a148b0..dee435a8 100644
--- a/inc/event.inc.php
+++ b/inc/event.inc.php
@@ -88,7 +88,9 @@ class Event
error_log('Server ip changed');
global $tidIpxe;
$tidIpxe = Trigger::ipxe();
- ConfigModule::serverIpChanged();
+ if (Module::isAvailable('sysconfig')) { // TODO: Modularize events
+ ConfigModule::serverIpChanged();
+ }
}
/**
diff --git a/inc/module.inc.php b/inc/module.inc.php
index a7a767ef..960a0a87 100644
--- a/inc/module.inc.php
+++ b/inc/module.inc.php
@@ -68,12 +68,20 @@ class Module
/**
* @return \Module[] List of valid, enabled modules
*/
- public static function getEnabled()
+ public static function getEnabled($sortById = false)
{
$ret = array();
+ $sort = array();
foreach (self::$modules as $module) {
- if (self::resolveDeps($module))
+ if (self::resolveDeps($module)) {
$ret[] = $module;
+ }
+ if ($sortById) {
+ $sort[] = $module->name;
+ }
+ }
+ if ($sortById) {
+ array_multisort($sort, SORT_ASC, $ret);
}
return $ret;
}
diff --git a/inc/trigger.inc.php b/inc/trigger.inc.php
index 353d6d69..db4a2148 100644
--- a/inc/trigger.inc.php
+++ b/inc/trigger.inc.php
@@ -86,19 +86,16 @@ class Trigger
public static function ldadp($exclude = NULL, $parent = NULL)
{
// TODO: Fetch list from ConfigModule_AdAuth (call loadDb first)
- $res = Database::simpleQuery("SELECT moduleid, configtgz.filepath FROM configtgz_module"
+ $res = Database::simpleQuery("SELECT DISTINCT moduleid FROM configtgz_module"
. " INNER JOIN configtgz_x_module USING (moduleid)"
. " INNER JOIN configtgz USING (configid)"
+ . " INNER JOIN configtgz_location USING (configid)"
. " WHERE moduletype IN ('AdAuth', 'LdapAuth')");
- // TODO: Multiconfig support
$id = array();
while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
- if (readlink('/srv/openslx/www/boot/default/config.tgz') === $row['filepath']) {
- if (!is_null($exclude) && (int)$row['moduleid'] === (int)$exclude)
- continue;
- $id[] = (int)$row['moduleid'];
- break;
- }
+ if (!is_null($exclude) && (int)$row['moduleid'] === (int)$exclude)
+ continue;
+ $id[] = (int)$row['moduleid'];
}
$task = Taskmanager::submit('LdadpLauncher', array(
'ids' => $id,
diff --git a/inc/util.inc.php b/inc/util.inc.php
index 1b29aa39..14621a5a 100644
--- a/inc/util.inc.php
+++ b/inc/util.inc.php
@@ -12,32 +12,38 @@ class Util
*/
public static function traceError($message)
{
- if (defined('API')) {
+ if (defined('API') && API) {
error_log('API ERROR: ' . $message);
}
Header('HTTP/1.1 500 Internal Server Error');
- Header('Content-Type: text/plain; charset=utf-8');
- echo "--------------------\nFlagrant system error:\n$message\n--------------------\n\n";
+ Header('Content-Type: text/html; charset=utf-8');
+ echo '<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><style>', "\n",
+ ".arg { color: red; background: white; }\n",
+ "h1 a { color: inherit; text-decoration: inherit; font-weight: inherit; }\n",
+ '</style><title>Fatal Error</title></head><body>';
+ echo '<h1>Flagrant <a href="https://www.youtube.com/watch?v=7rrZ-sA4FQc&t=2m2s" target="_blank">S</a>ystem error</h1>';
+ echo "<h2>Message</h2><pre>$message</pre>";
+ if (strpos($message, 'Database') !== false) {
+ echo '<div><a href="install.php">Try running database setup</a></div>';
+ }
+ echo "<br><br>";
if (defined('CONFIG_DEBUG') && CONFIG_DEBUG) {
global $SLX_ERRORS;
-/*
- 'errno' => $errno,
- 'errstr' => $errstr,
- 'errfile' => $errfile,
- 'errline' => $errline,
- */
if (!empty($SLX_ERRORS)) {
+ echo '<h2>PHP Errors</h2><pre>';
foreach ($SLX_ERRORS as $error) {
- echo "{$error['errstr']} ({$error['errfile']}:{$error['errline']}\n";
+ echo htmlspecialchars("{$error['errstr']} ({$error['errfile']}:{$error['errline']}\n");
}
- echo "--------------------\n";
+ echo '</pre>';
}
- debug_print_backtrace();
- echo "\n\nSome variables for your entertainment:\n";
- print_r($GLOBALS);
+ echo "<h2>Stack Trace</h2>";
+ echo '<pre>', self::formatBacktrace(debug_backtrace()), '</pre>';
+ echo "<h2>Globals</h2><pre>";
+ echo print_r($GLOBALS, true);
+ echo '</pre>';
} else {
echo <<<SADFACE
-
+<pre>
________________________¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶________
____________________¶¶¶___________________¶¶¶¶_____
________________¶¶¶_________________________¶¶¶¶___
@@ -61,11 +67,39 @@ __¶¶¶________________________________¶¶____________
___¶¶¶____________________________¶¶_______________
____¶¶¶¶______________________¶¶¶__________________
_______¶¶¶¶¶_____________¶¶¶¶¶_____________________
+</pre>
SADFACE;
}
+ echo '</body></html>';
exit(0);
}
+ public static function formatBacktrace($trace, $escape = true)
+ {
+ $output = '';
+ foreach ($trace as $idx => $line) {
+ $args = array();
+ foreach ($line['args'] as $arg) {
+ if (is_string($arg)) {
+ $arg = "'$arg'";
+ } elseif (is_object($arg)) {
+ $arg = 'instanceof ' . get_class($arg);
+ } elseif (is_array($arg)) {
+ $arg = 'Array(' . count($arg) . ')';
+ }
+ $args[] = '<span class="arg">' . htmlspecialchars($arg) . '</span>';
+ }
+ $frame = str_pad('#' . $idx, 3, ' ', STR_PAD_LEFT);
+ $function = htmlspecialchars($line['function']);
+ $args = implode(', ', $args);
+ $file = preg_replace('~(/[^/]+)$~', '<b>$1</b>', htmlspecialchars($line['file']));
+ // Add line
+ $output .= $frame . ' ' . $function . '<b>(</b>'
+ . $args . '<b>)</b>' . ' @ <i>' . $file . '</i>:' . $line['line'] . "\n";
+ }
+ return $output;
+ }
+
/**
* Redirects the user via a '302 Moved' header.
* An active session will be saved, any messages that haven't
diff --git a/modules-available/dozmod/api.inc.php b/modules-available/dozmod/api.inc.php
index b37c668c..17ead3c1 100644
--- a/modules-available/dozmod/api.inc.php
+++ b/modules-available/dozmod/api.inc.php
@@ -5,70 +5,69 @@
*
* Required Configuration:
* CONFIG_DOZMOD_EXPIRE: Expiration time in seconds for the cache
-* CONFIG_DOZMOD: URL to the dozmod server
+* CONFIG_DOZMOD_URL: URL to the dozmod server
*
**/
if (!Module::isAvailable('locations')) {
- die('require locations module');
+ die('require locations module');
}
-define('LIST_URL', CONFIG_DOZMOD . '/vmchooser/list');
-define('VMX_URL', CONFIG_DOZMOD . '/vmchooser/lecture');
-$availableRessources = ['vmx', 'test', 'netrules'];
+define('LIST_URL', CONFIG_DOZMOD_URL . '/vmchooser/list');
+define('VMX_URL', CONFIG_DOZMOD_URL . '/vmchooser/lecture');
+$availableRessources = ['list', 'vmx', 'test', 'netrules'];
/* BEGIN: A simple caching mechanism ---------------------------- */
function cache_hash($obj)
{
- return md5(serialize($obj));
+ return md5(serialize($obj));
}
function cache_key_to_filename($key)
{
- return "/tmp/bwlp-slxadmin-cache-$key"; // TODO: hash
+ return "/tmp/bwlp-slxadmin-cache-$key";
}
function cache_put($key, $value)
{
- $filename = cache_key_to_filename($key);
- file_put_contents($filename, $value);
+ $filename = cache_key_to_filename($key);
+ file_put_contents($filename, $value);
}
function cache_has($key)
{
- $filename = cache_key_to_filename($key);
- $mtime = filemtime($filename);
+ $filename = cache_key_to_filename($key);
+ $mtime = @filemtime($filename);
- if (!$mtime) {
- return false; // cache miss
- }
- if (time() - $mtime > CONFIG_DOZMOD_EXPIRE) {
- return false;
- } else {
- return true;
- }
+ if ($mtime === false) {
+ return false; // cache miss
+ }
+ if (time() - $mtime > CONFIG_DOZMOD_EXPIRE) {
+ return false;
+ } else {
+ return true;
+ }
}
-
function cache_get($key)
{
- $filename = cache_key_to_filename($key);
- return file_get_contents($filename);
+ $filename = cache_key_to_filename($key);
+ return file_get_contents($filename);
}
/* good for large binary files */
function cache_get_passthru($key)
{
- $filename = cache_key_to_filename($key);
- $fp = fopen($filename, "r");
- if ($fp) {
- fpassthru($fp);
- } else {
- Util::traceError("cannot open file");
- }
+ $filename = cache_key_to_filename($key);
+ $fp = fopen($filename, "r");
+ if ($fp) {
+ fpassthru($fp);
+ exit;
+ }
+ error_log('Cannot passthrough cache file ' . $filename);
}
/* END: Cache ---------------------------------------------------- */
@@ -81,106 +80,157 @@ function cache_get_passthru($key)
**/
-function println($str) { echo "$str\n"; }
-
-/* return an array of lecutre uuids.
-* Parameter: an array with location Ids
-* */
-function _getLecturesForLocations($locationIds)
+/**
+ * Takes raw lecture list xml, returns array of uuids.
+ *
+ * @param string $responseXML XML from dozmod server
+ * @return array list of UUIDs, false on error
+ */
+function xmlToLectureIds($responseXML)
{
+ $xml = new SimpleXMLElement($responseXML);
+ if (!isset($xml->eintrag))
+ return [];
- /* if in any of the locations there is an exam active, consider the client
- to be in "exam-mode" and only offer him exams (no lectures) */
- $examMode = false;
-
- if (Module::isAvailable('exams')) {
- $examMode = Exams::isInExamMode($locationIds);
- }
- $ids = implode('%20', $locationIds);
- $url = LIST_URL . "?locations=$ids" . ($examMode ? '&exams' : '');
- $responseXML = Download::asString($url, 60, $code);
- $xml = new SimpleXMLElement($responseXML);
-
- $uuids = [];
- foreach ($xml->eintrag as $e) {
- $uuids[] = strval($e->uuid['param'][0]);
- }
- return $uuids;
+ $uuids = [];
+ foreach ($xml->eintrag as $e) {
+ $uuids[] = strval($e->uuid['param'][0]);
+ }
+ return $uuids;
}
/** Caching wrapper around _getLecturesForLocations() */
-function getLecturesForLocations($locationIds)
+function getListForLocations($locationIds, $raw)
+{
+ /* if in any of the locations there is an exam active, consider the client
+ to be in "exam-mode" and only offer him exams (no lectures) */
+ $key = 'lectures_' . cache_hash($locationIds);
+ $examMode = false;
+ if (Module::isAvailable('exams')) {
+ $examMode = Exams::isInExamMode($locationIds);
+ if ($examMode) {
+ $key .= '_exams';
+ }
+ }
+ $rawKey = $key . '_raw';
+ if ($raw) {
+ Header('Content-Type: text/xml; charset=utf-8');
+ if (cache_has($rawKey)) {
+ cache_get_passthru($rawKey);
+ }
+ } elseif (cache_has($key)) {
+ return unserialize(cache_get($key));
+ }
+ // Not in cache
+ $url = LIST_URL . "?locations=" . implode('%20', $locationIds);
+ if ($examMode) {
+ $url .= '&exams';
+ }
+ $value = Download::asString($url, 60, $code);
+ if ($value === false)
+ return false;
+ cache_put($rawKey, $value);
+ $list = xmlToLectureIds($value);
+ cache_put($key, serialize($list));
+ if ($raw) {
+ die($value);
+ }
+ return $list;
+}
+
+function getLectureUuidsForLocations($locationIds)
+{
+ return getListForLocations($locationIds, false);
+}
+
+function outputLectureXmlForLocation($locationIds)
{
- $key = 'lectures_' . cache_hash($locationIds);
- if (cache_has($key)) {
- return unserialize(cache_get($key));
- } else {
- $value = _getLecturesForLocations($locationIds);
- cache_put($key, serialize($value));
- return $value;
- }
+ return getListForLocations($locationIds, true);
}
function _getVMX($lecture_uuid)
{
- $url = VMX_URL . '/' . $lecture_uuid;
- $response = Download::asString($url, 60, $code);
- return $response;
+ $url = VMX_URL . '/' . $lecture_uuid;
+ $response = Download::asString($url, 60, $code);
+ return $response;
}
/** Caching wrapper around _getVMX() **/
-function getVMX($lecture_uuid)
+function outputVMX($lecture_uuid)
+{
+ $key = 'vmx_' . $lecture_uuid;
+ if (cache_has($key)) {
+ cache_get_passthru($key);
+ } else {
+ $value = _getVMX($lecture_uuid);
+ if ($value === false)
+ return false;
+ cache_put($key, $value);
+ die($value);
+ }
+}
+
+function fatalDozmodUnreachable()
{
- $key = 'vmx_' . $lecture_uuid;
- if (cache_has($key)) {
- cache_get_passthru($key);
- } else {
- $value = _getVMX($lecture_uuid);
- cache_put($key, $value);
- return $value;
- }
+ Header('HTTP/1.1 504 Gateway Timeout');
+ die('Resource not available');
}
-
-// -----------------------------------------------------------------------------//
-$ip = $_SERVER['REMOTE_ADDR'];
-if (substr($ip, 0, 7) === '::ffff:') {
- $ip = substr($ip, 7);
+function readLectureParam()
+{
+ global $location_ids;
+ $lecture = Request::get('lecture', false, 'string');
+ if ($lecture === false) {
+ Header('HTTP/1.1 400 Bad Request');
+ die('Missing lecture UUID');
+ }
+ $lectures = getLectureUuidsForLocations($location_ids);
+ if ($lectures === false) {
+ fatalDozmodUnreachable();
+ }
+ /* check that the user requests a lecture that he is allowed to have */
+ if (!in_array($lecture, $lectures)) {
+ Header('HTTP/1.1 403 Forbidden');
+ die("You don't have permission to access this lecture");
+ }
+ return $lecture;
}
+
+// -----------------------------------------------------------------------------//
/* request data, don't trust */
-$resource = Request::get('resource', false, 'string');
-$lecture = Request::get('lecture', false, 'string');
+$resource = Request::get('resource', false, 'string');
if ($resource === false) {
- Util::traceError("you have to specify the 'resource' parameter");
+ Util::traceError("you have to specify the 'resource' parameter");
}
-if ($lecture === false) {
- Util::traceError("you have to specify the 'lecture' parameter");
+
+if (!in_array($resource, $availableRessources)) {
+ Util::traceError("unknown resource: $resource");
+}
+
+$ip = $_SERVER['REMOTE_ADDR'];
+if (substr($ip, 0, 7) === '::ffff:') {
+ $ip = substr($ip, 7);
}
+
/* lookup location id(s) */
$location_ids = Location::getFromIp($ip);
$location_ids = Location::getLocationRootChain($location_ids);
-/* lookup lecture uuids */
-$lectures = getLecturesForLocations($location_ids);
-
-/* validate request -------------------------------------------- */
-/* check resources */
-if (!in_array($resource, $availableRessources)) {
- Util::traceError("unknown resource: $resource");
+if ($resource === 'vmx') {
+ $lecture = readLectureParam();
+ outputVMX($lecture);
+ // outputVMX does not return on success
+ fatalDozmodUnreachable();
}
-/* check that the user requests a lecture that he is allowed to have */
-if (!in_array($lecture, $lectures)) {
- Util::traceError("client is not allowed to access this lecture: $lecture");
+if ($resource === 'list') {
+ outputLectureXmlForLocation($location_ids);
+ // Won't return on success...
+ fatalDozmodUnreachable();
}
-if ($resource === 'vmx') {
- echo getVMX($lecture);
-} else if ($resource === 'test') {
- echo "Here's your special test data!";
-} else {
- echo "I don't know how to give you that resource";
-}
+Header('HTTP/1.1 400 Bad Request');
+die("I don't know how to give you that resource");
diff --git a/modules-available/exams/inc/exams.inc.php b/modules-available/exams/inc/exams.inc.php
index c01d4bad..f781fc1e 100644
--- a/modules-available/exams/inc/exams.inc.php
+++ b/modules-available/exams/inc/exams.inc.php
@@ -14,10 +14,10 @@ class Exams
} elseif (empty($locationIds)) {
return false;
}
- $l = str_repeat(',?', count($locationIds) - 1);
+ $l = str_repeat(',?', count($locationIds));
$res = Database::queryFirst("SELECT examid FROM exams"
. " INNER JOIN exams_x_location USING (examid)"
- . " WHERE UNIX_TIMESTAMP() BETWEEN starttime AND endtime AND locationid IN (?$l) LIMIT 1", $locationIds);
+ . " WHERE UNIX_TIMESTAMP() BETWEEN starttime AND endtime AND locationid IN (0$l) LIMIT 1", $locationIds);
return $res !== false;
}
diff --git a/modules-available/exams/install.inc.php b/modules-available/exams/install.inc.php
index aa8c9a0d..18be0be6 100644
--- a/modules-available/exams/install.inc.php
+++ b/modules-available/exams/install.inc.php
@@ -4,9 +4,10 @@ $res = array();
$res[] = tableCreate('exams', '
`examid` int(11) NOT NULL AUTO_INCREMENT,
+ `lectureid` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL
`starttime` int(11) NOT NULL,
`endtime` int(11) NOT NULL,
- `description` varchar(100) DEFAULT NULL,
+ `description` varchar(500) DEFAULT NULL,
PRIMARY KEY (`examid`)
');
@@ -24,6 +25,12 @@ if (Database::exec("ALTER TABLE exams ADD INDEX `idx_daterange` ( `starttime` ,
$res[] = UPDATE_DONE;
}
+if (!tableHasColumn('exams', 'lectureid')) {
+ Database::exec("ALTER TABLE `exams` ADD `lectureid` CHAR(36) CHARACTER SET ascii COLLATE ascii_bin NULL DEFAULT NULL AFTER `examid`");
+}
+
+Database::exec("ALTER TABLE `exams` CHANGE `description` `description` varchar(500) DEFAULT NULL");
+
if (in_array(UPDATE_DONE, $res)) {
finalResponse(UPDATE_DONE, 'Tables created successfully');
}
diff --git a/modules-available/exams/lang/de/messages.json b/modules-available/exams/lang/de/messages.json
index eda4257e..574cca93 100644
--- a/modules-available/exams/lang/de/messages.json
+++ b/modules-available/exams/lang/de/messages.json
@@ -3,10 +3,11 @@
"end-before-start": "Endzeitpunkt liegt vor Startzeitpunkt!",
"endtime-invalid": "Ung\u00fcltige Endzeit: {{0}}",
"error-while-saving-changes": "Fehler beim Speichern der \u00c4nderungen",
- "exam-added-success": "Klausurzeitraum erfolgreich hinzugef\u00fcgt",
- "exam-deleted-success": "Klausurzeitraum erfolgreich gel\u00f6scht",
- "exam-not-added": "Klausurzeitraum konnte nicht hinzugef\u00fcgt werden",
- "exam-not-deleted-error": "Klausurzeitraum konnte nicht gel\u00f6scht werden",
- "invalid-exam-id": "Ung\u00fcltige Klausur-ID: {{0}}",
+ "exam-added-success": "Pr\u00fcfungszeitraum erfolgreich hinzugef\u00fcgt",
+ "exam-deleted-success": "Pr\u00fcfungszeitraum erfolgreich gel\u00f6scht",
+ "exam-not-added": "Pr\u00fcfungszeitraum konnte nicht hinzugef\u00fcgt werden",
+ "exam-not-deleted-error": "Pr\u00fcfungszeitraum konnte nicht gel\u00f6scht werden",
+ "invalid-exam-id": "Ung\u00fcltige Pr\u00fcfungs-ID: {{0}}",
+ "no-upcoming-lecture-exams": "Keine anstehenden Veranstaltungen, die als Pr\u00fcfung markiert sind",
"starttime-invalid": "Ung\u00fcltige Startzeit: {{0}}"
} \ No newline at end of file
diff --git a/modules-available/exams/lang/de/module.json b/modules-available/exams/lang/de/module.json
index 226810fd..4d9fd954 100644
--- a/modules-available/exams/lang/de/module.json
+++ b/modules-available/exams/lang/de/module.json
@@ -1,6 +1,6 @@
{
- "module_name": "Klausurmodus",
- "title_add-exam": "Klausur hinzuf\u00fcgen",
- "title_edit-exam": "Klausur bearbeiten",
- "warning_lecture_is_not_enabled": "Warnung: Diese Vorlesung ist nicht vom Dozenten aktiviert"
+ "module_name": "Pr\u00fcfungsmodus",
+ "title_add-exam": "Pr\u00fcfungszeitraum hinzuf\u00fcgen",
+ "title_edit-exam": "Pr\u00fcfungszeitraum bearbeiten",
+ "warning_lecture_is_not_enabled": "Warnung: Diese Veranstaltung ist nicht vom Dozenten aktiviert worden"
} \ No newline at end of file
diff --git a/modules-available/exams/lang/de/template-tags.json b/modules-available/exams/lang/de/template-tags.json
index 9b9225de..797cc371 100644
--- a/modules-available/exams/lang/de/template-tags.json
+++ b/modules-available/exams/lang/de/template-tags.json
@@ -1,17 +1,27 @@
{
"lang_actions": "Aktionen",
- "lang_addExam": "Klausurzeitraum hinzuf\u00fcgen",
- "lang_allExamPeriods": "Alle Klausurzeitr\u00e4ume",
+ "lang_addExam": "Zeitraum hinzuf\u00fcgen",
+ "lang_addingBasedOnLecture": "F\u00fcge neuen Pr\u00fcfungszeitraum basierend auf vorhandener Veranstaltung an",
+ "lang_allExamPeriods": "Alle Pr\u00fcfungszeitr\u00e4ume",
"lang_begin": "Beginn",
"lang_begin_date": "Beginn Datum",
"lang_begin_time": "Uhrzeit",
"lang_deleteConfirmation": "Wirklich l\u00f6schen?",
"lang_description": "Beschreibung",
- "lang_editExam": "Klausurzeitraum bearbeiten",
+ "lang_duration": "Dauer",
+ "lang_editExam": "Zeitraum bearbeiten",
"lang_end": "Ende",
"lang_end_date": "Ende Datum",
"lang_end_time": "Uhrzeit",
+ "lang_examModeDescription": "Hier k\u00f6nnen Sie bwLehrpool-R\u00e4ume zeitgesteuert in den Pr\u00fcfungsmodus versetzen. Im Pr\u00fcfungsmodus ist das Client-System st\u00e4rker abgeriegelt, sodass es sich zum Schreiben von E-Pr\u00fcfungen eignet. Nach dem Ein- bzw. Ausschalten des Pr\u00fcfungsmodus ist es notwendig, die Rechner in den betroffenen R\u00e4umen neuzustarten.",
+ "lang_global": "Global",
+ "lang_headingAllExamLectures": "Liste ausstehender Pr\u00fcfungsveranstaltungen",
+ "lang_headingGraphicalOverview": "Grafische Darstellung",
+ "lang_headingMain": "bwLehrpool Pr\u00fcfungsmodus",
"lang_id": "ID",
+ "lang_lectureName": "Veranstaltungsname",
"lang_location": "Raum\/Ort",
- "lang_locations": "R\u00e4ume\/Orte"
+ "lang_locations": "R\u00e4ume\/Orte",
+ "lang_noDescription": "Keine Beschreibung",
+ "lang_timeFrame": "Zeitraum"
} \ No newline at end of file
diff --git a/modules-available/exams/lang/en/messages.json b/modules-available/exams/lang/en/messages.json
index a62cc489..f07ffba1 100644
--- a/modules-available/exams/lang/en/messages.json
+++ b/modules-available/exams/lang/en/messages.json
@@ -8,5 +8,6 @@
"exam-not-added": "Exam period was not added",
"exam-not-deleted-error": "Exam period was not deleted",
"invalid-exam-id": "Invalid exam id: {{0}}",
+ "no-upcoming-lecture-exams": "No upcoming lectures which are marked as exam",
"starttime-invalid": "Invalid start time: {{0}}"
} \ No newline at end of file
diff --git a/modules-available/exams/lang/en/template-tags.json b/modules-available/exams/lang/en/template-tags.json
index 9c49c8a3..2e5abeff 100644
--- a/modules-available/exams/lang/en/template-tags.json
+++ b/modules-available/exams/lang/en/template-tags.json
@@ -1,17 +1,27 @@
{
"lang_actions": "Actions",
"lang_addExam": "Add exam period",
+ "lang_addingBasedOnLecture": "Adding exam period based on lecture",
"lang_allExamPeriods": "All exam periods",
"lang_begin": "Begin",
"lang_begin_date": "Begin Date",
"lang_begin_time": "Time",
"lang_deleteConfirmation": "Are you sure?",
"lang_description": "Description",
+ "lang_duration": "Duration",
"lang_editExam": "Edit Exam Period",
"lang_end": "End",
"lang_end_date": "End Date",
"lang_end_time": "Time",
+ "lang_examModeDescription": "Here you can define time spans during which selected rooms will be set to exam mode. In exam mode, the client computers are more locked down than usual so it is suitable for writing electronic exams.",
+ "lang_global": "Global",
+ "lang_headingAllExamLectures": "Upcoming lectures marked as exams",
+ "lang_headingGraphicalOverview": "Graphical overview",
+ "lang_headingMain": "bwLehrpool exam mode",
"lang_id": "ID",
+ "lang_lectureName": "Lecture name",
"lang_location": "Room\/Location",
- "lang_locations": "Rooms\/Locations"
+ "lang_locations": "Rooms\/Locations",
+ "lang_noDescription": "No description",
+ "lang_timeFrame": "Time frame"
} \ No newline at end of file
diff --git a/modules-available/exams/page.inc.php b/modules-available/exams/page.inc.php
index 54fad5cf..d6b1ccea 100644
--- a/modules-available/exams/page.inc.php
+++ b/modules-available/exams/page.inc.php
@@ -7,15 +7,19 @@ class Page_Exams extends Page
var $locations;
var $lectures;
private $currentExam;
+ private $rangeMin;
+ private $rangeMax;
/** if examid is set, also add a column 'selected' **/
- protected function readLocations($examid = null)
+ protected function readLocations($examidOrLocations = null)
{
- if ($examid == null) {
+ if ($examidOrLocations == null) {
$active = 0;
+ } elseif (is_array($examidOrLocations)) {
+ $active = $examidOrLocations;
} else {
- $tmp = Database::simpleQuery("SELECT locationid FROM exams_x_location WHERE examid= :examid", compact('examid'));
+ $tmp = Database::simpleQuery("SELECT locationid FROM exams_x_location WHERE examid= :examid", array('examid' => $examidOrLocations));
$active = array();
while ($row = $tmp->fetch(PDO::FETCH_ASSOC)) {
$active[] = (int)$row['locationid'];
@@ -26,9 +30,9 @@ class Page_Exams extends Page
protected function readExams()
{
- $tmp = Database::simpleQuery("select examid, starttime, endtime, description, GROUP_CONCAT(locationid) AS locationids, "
- . "GROUP_CONCAT(locationname) AS locationnames FROM "
- . "exams NATURAL LEFT JOIN exams_x_location NATURAL LEFT JOIN location GROUP BY examid", []);
+ $tmp = Database::simpleQuery("SELECT examid, starttime, endtime, description, GROUP_CONCAT(locationid) AS locationids, "
+ . "GROUP_CONCAT(locationname SEPARATOR ', ') AS locationnames FROM exams "
+ . "NATURAL LEFT JOIN exams_x_location NATURAL LEFT JOIN location GROUP BY examid");
while ($exam = $tmp->fetch(PDO::FETCH_ASSOC)) {
$this->exams[] = $exam;
}
@@ -37,9 +41,13 @@ class Page_Exams extends Page
protected function readLectures()
{
$tmp = Database::simpleQuery(
- "SELECT lectureid, locationid, displayname, starttime, endtime, isenabled " .
- "FROM sat.lecture NATURAL JOIN sat.lecture_x_location " .
- "WHERE isexam <> 0");
+ "SELECT lectureid, Group_Concat(locationid) as lids, displayname, starttime, endtime, isenabled, firstname, lastname, email " .
+ "FROM sat.lecture " .
+ "INNER JOIN sat.user ON (user.userid = lecture.ownerid) " .
+ "NATURAL LEFT JOIN sat.lecture_x_location " .
+ "WHERE isexam <> 0 AND starttime < :rangeMax AND endtime > :rangeMin " .
+ "GROUP BY lectureid",
+ ['rangeMax' => $this->rangeMax, 'rangeMin' => $this->rangeMin]);
while ($lecture = $tmp->fetch(PDO::FETCH_ASSOC)) {
$this->lectures[] = $lecture;
}
@@ -48,7 +56,9 @@ class Page_Exams extends Page
protected function makeItemsForVis()
{
$out = [];
- /* foreach group also add an invisible item on top */
+ // foreach group also add an invisible item on top
+ // disabled for now - more of an annoyance if you have more than a few rooms
+ /*
foreach ($this->locations as $l) {
$out[] = ['id' => 'spacer_' . $l['locationid'],
'group' => $l['locationid'],
@@ -58,21 +68,25 @@ class Page_Exams extends Page
'end' => 99999999999999,
'subgroup' => 0
];
-
}
+ */
$unique_ids = 1;
/* add the red shadows */
foreach ($this->exams as $e) {
+ if ($e['starttime'] > $this->rangeMax || $e['endtime'] < $this->rangeMin)
+ continue;
$locationids = explode(',', $e['locationids']);
if ($locationids[0] == 0) {
$locationids = [];
- foreach($this->locations as $l) {
- $locationids[] = $l['locationid'];
+ foreach($this->locations as $location) {
+ $locationids[] = $location['locationid'];
}
}
foreach ($locationids as $locationid) {
- $out[] = ['id' => 'shadow_' . $unique_ids++,
- 'content' => '',
+ $out[] = [
+ 'id' => 'shadow_' . $unique_ids++,
+ 'content' => $e['description'],
+ 'title' => $e['description'],
'start' => intval($e['starttime']) * 1000,
'end' => intval($e['endtime']) * 1000,
'type' => 'background',
@@ -81,21 +95,28 @@ class Page_Exams extends Page
}
}
/* add the lectures */
+ $allLocationIds = array_map(function($loc) { return $loc['locationid']; }, $this->locations);
$i = 2;
- foreach ($this->lectures as $l) {
- $mark = '<span class="' . ($l['isenabled'] ? '' : 'glyphicon glyphicon-exclamation-sign') . '"></span>';
- $out[] = [
- 'id' => $l['lectureid'] . '/' . $l['locationid'],
- 'content' => htmlspecialchars($l['displayname']) . $mark,
- 'title' => $l['isenabled'] ? '' : Dictionary::translate('warning_lecture_is_not_enabled'),
- 'start' => intval($l['starttime']) * 1000,
- 'end' => intval($l['endtime']) * 1000,
- 'group' => $l['locationid'],
- 'className' => $l['isenabled'] ? '' : 'disabled',
- 'editable' => false,
- 'subgroup' => $i++
- ];
-
+ foreach ($this->lectures as $lecture) {
+ $mark = '<span class="' . ($lecture['isenabled'] ? '' : 'glyphicon glyphicon-exclamation-sign') . '"></span>';
+ if (empty($lecture['lids'])) {
+ $locations = $allLocationIds;
+ } else {
+ $locations = explode(',', $lecture['lids']);
+ }
+ foreach ($locations as $location) {
+ $out[] = [
+ 'id' => $lecture['lectureid'] . '/' . $location,
+ 'content' => htmlspecialchars($lecture['displayname']) . $mark,
+ 'title' => $lecture['isenabled'] ? '' : Dictionary::translate('warning_lecture_is_not_enabled'),
+ 'start' => intval($lecture['starttime']) * 1000,
+ 'end' => intval($lecture['endtime']) * 1000,
+ 'group' => $location,
+ 'className' => $lecture['isenabled'] ? '' : 'disabled',
+ 'editable' => false,
+ 'subgroup' => $i++,
+ ];
+ }
}
return json_encode($out);
@@ -120,25 +141,58 @@ class Page_Exams extends Page
$now = time();
foreach ($this->exams as $exam) {
if ($exam['endtime'] < $now) {
- $exam['rowClass'] = 'gray';
+ $exam['rowClass'] = 'text-muted';
$exam['btnClass'] = 'btn-success';
+ $exam['liesInPast'] = true;
} else {
$exam['btnClass'] = 'btn-default';
}
- $exam['starttime'] = date('Y-m-d H:i', $exam['starttime']);
- $exam['endtime'] = date('Y-m-d H:i', $exam['endtime']);
+ $exam['starttime_s'] = date('Y-m-d H:i', $exam['starttime']);
+ $exam['endtime_s'] = date('Y-m-d H:i', $exam['endtime']);
$out[] = $exam;
}
return $out;
}
- private function dateSane($time)
+ protected function makeLectureExamList()
+ {
+ $out = [];
+ $now = time();
+ $cutoff = strtotime('+ 5 day');
+ foreach ($this->lectures as $lecture) {
+ if ($lecture['endtime'] < $now || $lecture['starttime'] > $cutoff)
+ continue;
+ $entry = $lecture;
+ if (!$lecture['isenabled']) {
+ $entry['class'] = 'text-muted';
+ }
+ $entry['starttime_s'] = date('Y-m-d H:i', $lecture['starttime']);
+ $entry['endtime_s'] = date('Y-m-d H:i', $lecture['endtime']);
+ $duration = $lecture['endtime'] - $lecture['starttime'];
+ if ($duration < 86400) {
+ $entry['duration_s'] = gmdate('H:i', $duration);
+ }
+ $out[] = $entry;
+ }
+ return $out;
+ }
+
+ protected function makeEditFromArray($source)
+ {
+ if (!isset($source['description']) && isset($source['displayname'])) {
+ $source['description'] = $source['displayname'];
+ }
+ return [
+ 'starttime_date' => date('Y-m-d', $source['starttime']),
+ 'starttime_time' => date('H:i', $source['starttime']),
+ 'endtime_date' => date('Y-m-d', $source['endtime']),
+ 'endtime_time' => date('H:i', $source['endtime'])
+ ] + $source;
+ }
+
+ private function isDateSane($time)
{
- if ($time < strtotime('-1 day'))
- return false;
- if ($time > strtotime('+90 day'))
- return false;
- return true;
+ return ($time >= $this->rangeMin && $time <= $this->rangeMax);
}
private function saveExam()
@@ -158,11 +212,11 @@ class Page_Exams extends Page
$starttime = strtotime(Request::post('starttime_date') . " " . Request::post('starttime_time'));
$endtime = strtotime(Request::post('endtime_date') . " " . Request::post('endtime_time'));
$description = Request::post('description');
- if (!$this->dateSane($starttime)) {
+ if (!$this->isDateSane($starttime)) {
Message::addError('starttime-invalid', Request::post('starttime_date') . " " . Request::post('starttime_time'));
Util::redirect('?do=exams');
}
- if (!$this->dateSane($endtime)) {
+ if (!$this->isDateSane($endtime)) {
Message::addError('endtime-invalid', Request::post('endtime_date') . " " . Request::post('endtime_time'));
Util::redirect('?do=exams');
}
@@ -217,6 +271,14 @@ class Page_Exams extends Page
{
User::load();
+ if (!User::hasPermission('superadmin')) {
+ Message::addError('main.no-permission');
+ Util::redirect('?do=Main');
+ }
+
+ $this->rangeMin = strtotime('-1 day');
+ $this->rangeMax = strtotime('+3 month');
+
$req_action = Request::any('action', 'show');
if (in_array($req_action, ['show', 'add', 'delete', 'edit', 'save'])) {
$this->action = $req_action;
@@ -228,6 +290,10 @@ class Page_Exams extends Page
$this->readLocations();
$this->readLectures();
+ } elseif ($this->action === 'add') {
+
+ $this->readLectures();
+
} elseif ($this->action === 'edit') {
$examid = Request::get('examid', 0, 'int');
@@ -237,6 +303,7 @@ class Page_Exams extends Page
Util::redirect('?do=exams');
}
$this->readLocations($examid);
+ $this->readLectures();
} elseif ($this->action === 'save') {
@@ -267,31 +334,59 @@ class Page_Exams extends Page
protected function doRender()
{
if ($this->action === "show") {
- Render::setTitle("All Exams");
- Render::addTemplate('page-exams',
- ['exams' => $this->makeExamsForTemplate(),
- 'exams_json' => $this->makeItemsForVis(),
- 'rooms_json' => $this->makeGroupsForVis(),
- 'vis_begin' => strtotime('-5 minute') * 1000,
- 'vis_end' => strtotime('+2 day') * 1000,
- 'vis_min_date' => strtotime('-1 day') * 1000,
- 'vis_max_date' => strtotime('+3 month') * 1000
+
+ // General title and description
+ Render::addTemplate('page-main-heading');
+ // List of defined exam periods
+ Render::addTemplate('page-exams', [
+ 'exams' => $this->makeExamsForTemplate()
+ ]);
+ // List of upcoming lectures marked as exam
+ $upcoming = $this->makeLectureExamList();
+ if (empty($upcoming)) {
+ Message::addInfo('no-upcoming-lecture-exams');
+ } else {
+ Render::addTemplate('page-upcoming-lectures', [
+ 'pending_lectures' => $upcoming
]);
+ }
+ // Vis.js timeline
+ Render::addTemplate('page-exams-vis', [
+ 'exams_json' => $this->makeItemsForVis(),
+ 'rooms_json' => $this->makeGroupsForVis(),
+ 'vis_begin' => strtotime('-5 minute') * 1000,
+ 'vis_end' => strtotime('+2 day') * 1000,
+ 'vis_min_date' => $this->rangeMin * 1000,
+ 'vis_max_date' => $this->rangeMax * 1000,
+ 'axis_label' => (count($this->locations) > 5 ? 'both' : 'bottom'),
+ 'utc_offset' => date('P')
+ ]);
+
} elseif ($this->action === "add") {
+
Render::setTitle(Dictionary::translate('title_add-exam'));
- $this->readLocations();
- Render::addTemplate('page-add-edit-exam', ['locations' => $this->locations]);
+ $data = [];
+ $baseLecture = Request::any('lectureid', false, 'string');
+ $locations = null;
+ if ($baseLecture !== false) {
+ foreach ($this->lectures as $lecture) {
+ if ($lecture['lectureid'] === $baseLecture) {
+ $data['exam'] = $this->makeEditFromArray($lecture);
+ $locations = explode(',', $lecture['lids']);
+ break;
+ }
+ }
+ }
+ $this->readLocations($locations);
+ $data['locations'] = $this->locations;
+ Render::addTemplate('page-add-edit-exam', $data);
+
} elseif ($this->action === 'edit') {
+
Render::setTitle(Dictionary::translate('title_edit-exam'));
- $exam = [
- 'examid' => $this->currentExam['examid'],
- 'starttime_date' => date('Y-m-d', $this->currentExam['starttime']),
- 'starttime_time' => date('H:i', $this->currentExam['starttime']),
- 'endtime_date' => date('Y-m-d', $this->currentExam['endtime']),
- 'endtime_time' => date('H:i', $this->currentExam['endtime']),
- 'description' => $this->currentExam['description']
- ];
+ $exam = $this->makeEditFromArray($this->currentExam);
Render::addTemplate('page-add-edit-exam', ['exam' => $exam, 'locations' => $this->locations]);
+
}
}
diff --git a/modules-available/exams/style.css b/modules-available/exams/style.css
index 89f312fa..4a6cd7da 100644
--- a/modules-available/exams/style.css
+++ b/modules-available/exams/style.css
@@ -1,16 +1,17 @@
- .vis-item.vis-background {
+.vis-item.vis-background {
background-color: rgba(192, 57, 43, .5) !important;
}
- .vis-item.spacer {
+
+.vis-item.spacer {
visibility: hidden;
- }
+}
- .vis-selected,
- .vis-readonly {
- background-color: #80D6F2 !important;
- border: 1px solid grey !important;
- }
+.vis-selected,
+.vis-readonly {
+ background-color: #80D6F2 !important;
+ border: 1px solid grey !important;
+}
- .vis-item.disabled {
- background-color: rgba(189, 195, 199,1.0) !important;
- }
+.vis-item.disabled {
+ background-color: rgba(189, 195, 199, 1.0) !important;
+}
diff --git a/modules-available/exams/templates/page-add-edit-exam.html b/modules-available/exams/templates/page-add-edit-exam.html
index 7d74972d..3f0ef372 100644
--- a/modules-available/exams/templates/page-add-edit-exam.html
+++ b/modules-available/exams/templates/page-add-edit-exam.html
@@ -4,10 +4,15 @@
{{^exam.examid}}
<h1>{{lang_addExam}}</h1>
{{/exam.examid}}
+{{#exam.displayname}}
+<div class="alert alert-info">{{lang_addingBasedOnLecture}}:<br><b>{{exam.displayname}}</b></div>
+{{/exam.displayname}}
<form class="form" method="POST" action="?do=exams" id="tolleform">
<div class="form-group">
- <label for="locations">{{lang_location}}</label>
+ <div>
+ <label for="locations">{{lang_location}}</label>
+ </div>
<select id="locations" multiple name="locations[]">
{{#locations}}
<option value="{{locationid}}" {{#selected}}selected{{/selected}}>{{locationpad}} {{locationname}}</option>
@@ -56,12 +61,19 @@
<span class="input-group-addon">
<span class="glyphicon glyphicon-time"></span>
</span>
- <input required type="texxt" class="form-control timepicker2" name="endtime_time" id="endtime_time"
+ <input required type="text" class="form-control timepicker2" name="endtime_time" id="endtime_time"
value="{{exam.endtime_time}}"
pattern="[0-9]{1,2}:[0-9]{2}">
</div>
</div>
</div>
+
+ <div class="panel">
+ <div class="panel-body">
+ {{lang_duration}}: <span id="exam-duration">-</span>
+ </div>
+ </div>
+
<div class="row form-group">
<div class="form-group col-xs-12">
<label for="description">{{lang_description}}</label>
@@ -78,6 +90,8 @@
<script type="application/javascript"><!--
document.addEventListener("DOMContentLoaded", function () {
var filename = "modules/bootstrap_datepicker/lang/bootstrap-datepicker." + LANG + ".js";
+ moment.locale(LANG);
+ var slxMoment = moment;
$.getScript(filename)
.always(function () {
@@ -85,23 +99,58 @@ document.addEventListener("DOMContentLoaded", function () {
var dateSettings = {
format: 'yyyy-mm-dd',
weekStart: 1,
- startDate: 'today',
+ todayHighlight: true,
language: LANG
};
- var timeSettings = {
+ var timeSettings = {
showSeconds: false,
showMeridian: false,
minuteStep: 5,
appendWidgetTo: 'body'
};
- $('.datepicker').datepicker(dateSettings);
- $('.timepicker2').timepicker(timeSettings);
+ $('.datepicker').datepicker(dateSettings);
+ $('.timepicker2').timepicker(timeSettings);
+
+ showDuration();
+ });
+
+ $('#locations').multiselect({numberDisplayed: 1});
+
+ var start_date = $('#starttime_date');
+ var start_time = $('#starttime_time');
+ var end_date = $('#endtime_date');
+ var end_time = $('#endtime_time');
+ var rspan = $('#exam-duration');
- $('#locations').multiselect({numberDisplayed: 1});
+ start_date.focusout(function () {
+ var start = start_date.val();
+ var end = end_date.val();
+ var ok = end.length === 0;
+ if (!ok) {
+ var ms = slxMoment(start, 'YYYY-MM-DD');
+ var me = slxMoment(end, 'YYYY-MM-DD');
+ ok = !me.isValid() || me.isBefore(ms);
+ }
+ if (ok) {
+ end_date.val(start);
+ }
+ });
+ var showDuration = function () {
+ var sd = slxMoment(start_date.val() + ' ' + start_time.val(), 'YYYY-MM-DD H:mm');
+ var ed = slxMoment(end_date.val() + ' ' + end_time.val(), 'YYYY-MM-DD H:mm');
+ if (!sd.isValid() || !ed.isValid()) {
+ rspan.text('-');
+ return;
+ }
+ rspan.text(slxMoment.duration(ed.diff(sd)).humanize());
+ }
- });
+ start_date.change(showDuration);
+ start_time.change(showDuration);
+ end_date.change(showDuration);
+ end_time.change(showDuration);
}, false);
// --></script>
diff --git a/modules-available/exams/templates/page-exams-vis.html b/modules-available/exams/templates/page-exams-vis.html
new file mode 100644
index 00000000..e347900b
--- /dev/null
+++ b/modules-available/exams/templates/page-exams-vis.html
@@ -0,0 +1,52 @@
+<h2>{{lang_headingGraphicalOverview}}</h2>
+
+<div id="timeline" class="slx-space"></div>
+
+<script type="application/javascript"><!--
+
+
+function itemOrderFun(a, b) {
+ return a.content.localeCompare(b.content);
+}
+
+function groupOrderFun(a, b) {
+ var s = a.sortIndex - b.sortIndex;
+ if (s != 0) return s;
+ return itemOrderFun(a, b);
+}
+
+var slxTimeline;
+
+document.addEventListener("DOMContentLoaded", function () {
+ var container = document.getElementById('timeline');
+ var groups_plain = {{{rooms_json}}};
+ var items_plain = {{{exams_json}}};
+ var groups = new vis.DataSet(groups_plain);
+ var items = new vis.DataSet(items_plain);
+
+ var language = window.navigator.userLanguage || window.navigator.language;
+
+ var options = {
+ 'start' : {{vis_begin}},
+ 'end' : {{vis_end}},
+ 'stack' : false,
+ 'editable': false,
+ 'min' : {{vis_min_date}},
+ 'max' : {{vis_max_date}},
+ 'zoomMin': 6 * 3600 * 1000,
+ 'zoomMax': 2 * 86400 * 1000,
+ 'order' : itemOrderFun,
+ 'groupOrder': groupOrderFun,
+ 'locale' : language,
+ 'moment' : function(date) { return vis.moment(date).utcOffset('{{utc_offset}}'); },
+ 'orientation': { 'axis': '{{axis_label}}' }
+};
+
+ slxTimeline = new vis.Timeline(container, items, groups, options);
+}, false);
+
+function slxShow(st, et) {
+ slxTimeline.setWindow(st * 1000, et * 1000);
+}
+
+// --></script>
diff --git a/modules-available/exams/templates/page-exams.html b/modules-available/exams/templates/page-exams.html
index 45ec0b50..184a69e3 100644
--- a/modules-available/exams/templates/page-exams.html
+++ b/modules-available/exams/templates/page-exams.html
@@ -1,85 +1,47 @@
-<div class="container-fluid">
- <h1>{{lang_allExamPeriods}}</h1>
-
- <div class="row">
- <table class="table">
- <tr>
- <th>{{lang_id}}</th>
- <th>{{lang_locations}}</th>
- <th>{{lang_begin}}</th>
- <th>{{lang_end}}</th>
- <th>{{lang_actions}}</th>
- </tr>
- {{#exams}}
- <tr class="{{rowClass}}">
- <td>{{examid}}</td>
- <td>{{locationnames}} </td>
- <td>{{starttime}}</td>
- <td>{{endtime}}</td>
- <td>
- <form method="POST" action="?do=exams&action=delete" onsubmit="return confirm('{{lang_deleteConfirmation}}');">
- <a href="?do=exams&action=edit&examid={{examid}}" class="btn btn-default btn-sm" >{{lang_edit}}</a>
- <input type="hidden" name="token" value="{{token}}">
- <input type="hidden" name="examid" value="{{examid}}">
- <button class="btn {{btnClass}} btn-sm">{{lang_delete}}</button>
- </form>
- </td>
- </tr>
- {{/exams}}
-
- </table>
- <div class="btn-toolbar" role="toolbar">
- <div class="btn-group" role="group">
- <a href="?do=exams&action=add" class="btn btn-success">{{lang_addExam}}</a>
- </div>
- </div>
- </div>
-
-
- <div class="row" style="margin-top: 2em">
- <div id="timeline"></div>
- </div>
+<h2>{{lang_allExamPeriods}}</h2>
+
+<div class="slx-space">
+ <table class="table">
+ <tr>
+ <th>{{lang_id}}</th>
+ <th>{{lang_locations}}</th>
+ <th>{{lang_begin}}</th>
+ <th>{{lang_end}}</th>
+ <th>{{lang_actions}}</th>
+ </tr>
+ {{#exams}}
+ <tr class="{{rowClass}}">
+ <td>{{examid}}</td>
+ <td>
+ {{locationnames}}
+ {{^locationnames}}
+ <i>{{lang_global}}</i>
+ {{/locationnames}}
+ <div class="small">
+ {{description}}
+ {{^description}}
+ <i>{{lang_noDescription}}</i>
+ {{/description}}
+ </div>
+ </td>
+ <td class="slx-nowrap">{{starttime_s}}</td>
+ <td class="slx-nowrap">{{endtime_s}}</td>
+ <td class="slx-nowrap">
+ <form method="POST" action="?do=exams&action=delete" {{^liesInPast}}onsubmit="return confirm('{{lang_deleteConfirmation}}');"{{/liesInPast}} >
+ {{^liesInPast}}
+ <a onclick="slxShow({{starttime}}, {{endtime}})" class="btn btn-default btn-sm"><span class="glyphicon glyphicon-eye-open"></span></a>
+ {{/liesInPast}}
+ <a href="?do=exams&action=edit&examid={{examid}}" class="btn btn-default btn-sm" >{{lang_edit}}</a>
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="examid" value="{{examid}}">
+ <button class="btn {{btnClass}} btn-sm">{{lang_delete}}</button>
+ </form>
+ </td>
+ </tr>
+ {{/exams}}
+ </table>
</div>
-<script type="application/javascript"><!--
-
-
-function itemOrderFun(a, b) {
- return a.content.localeCompare(b.content);
-}
-function groupOrderFun(a, b) {
- var s = a.sortIndex - b.sortIndex;
- if (s != 0) return s;
- return itemOrderFun(a, b);
-}
-
-document.addEventListener("DOMContentLoaded", function () {
- var container = document.getElementById('timeline');
- var groups_plain = {{{rooms_json}}};
- var items_plain = {{{exams_json}}};
- console.log(groups_plain);
- console.log(items_plain);
- var groups = new vis.DataSet(groups_plain);
- var items = new vis.DataSet(items_plain);
-
- var language = window.navigator.userLanguage || window.navigator.language;
-
- var options = {
- 'start' : {{vis_begin}},
- 'end' : {{vis_end}},
- 'stack' : false,
- 'editable' : false,
- 'min' : {{vis_min_date}},
- 'max' : {{vis_max_date}},
- 'zoomMin': 6 * 3600 * 1000,
- 'zoomMax': 2 * 86400 * 1000,
- 'order' : itemOrderFun,
- 'groupOrder' : groupOrderFun,
- 'locale' : language,
- 'moment' : function(date) { return vis.moment(date).utc(); }
- };
-
- var timeline = new vis.Timeline(container, items, groups, options);
-}, false);
-
-// --></script>
+<div class="btn-group" role="group">
+ <a href="?do=exams&action=add" class="btn btn-success">{{lang_addExam}}</a>
+</div>
diff --git a/modules-available/exams/templates/page-main-heading.html b/modules-available/exams/templates/page-main-heading.html
new file mode 100644
index 00000000..87b92a20
--- /dev/null
+++ b/modules-available/exams/templates/page-main-heading.html
@@ -0,0 +1,3 @@
+<h1>{{lang_headingMain}}</h1>
+
+<p>{{lang_examModeDescription}}</p> \ No newline at end of file
diff --git a/modules-available/exams/templates/page-upcoming-lectures.html b/modules-available/exams/templates/page-upcoming-lectures.html
new file mode 100644
index 00000000..4a62bc29
--- /dev/null
+++ b/modules-available/exams/templates/page-upcoming-lectures.html
@@ -0,0 +1,34 @@
+<h2>{{lang_headingAllExamLectures}}</h2>
+
+<div class="slx-space">
+ <table class="table">
+ <tr>
+ <th>{{lang_lectureName}}</th>
+ <th>{{lang_timeFrame}}</th>
+ <th>{{lang_actions}}</th>
+ </tr>
+ {{#pending_lectures}}
+ <tr>
+ <td>
+ {{displayname}}
+ <div class="small">
+ <a href="mailto:{{email}}">{{lastname}}, {{firstname}}</a>
+ </div>
+ </td>
+ <td width="30%" class="text-nowrap">
+ {{starttime_s}} &ensp; {{endtime_s}}
+ <div class="small">{{duration_s}}</div>
+ </td>
+ <td width="20%">
+ <div class="pull-right text-nowrap">
+ <a class="btn btn-sm btn-default" role="button" onclick="slxShow({{starttime}}, {{endtime}})"><span class="glyphicon glyphicon-eye-open"></span></a>
+ <a href="?do=exams&amp;action=add&amp;lectureid={{lectureid}}" class="btn btn-sm btn-default" role="button">
+ <span class="glyphicon glyphicon-plus-sign"></span>
+ <span class="hidden-sm">{{lang_addExam}}</span>
+ </a>
+ </div>
+ </td>
+ </tr>
+ {{/pending_lectures}}
+ </table>
+</div> \ No newline at end of file
diff --git a/modules-available/locations/page.inc.php b/modules-available/locations/page.inc.php
index 02f33bb3..464c4c95 100644
--- a/modules-available/locations/page.inc.php
+++ b/modules-available/locations/page.inc.php
@@ -321,6 +321,8 @@ class Page_Locations extends Page
if (Module::isAvailable('sysconfig')) {
$confs = SysConfig::getAll();
foreach ($confs as $conf) {
+ if (strlen($conf['locs']) === 0)
+ continue;
$confLocs = explode(',', $conf['locs']);
foreach ($confLocs as $loc) {
settype($loc, 'int');
diff --git a/modules-available/main/lang/de/module.json b/modules-available/main/lang/de/module.json
new file mode 100644
index 00000000..9df03bba
--- /dev/null
+++ b/modules-available/main/lang/de/module.json
@@ -0,0 +1,3 @@
+{
+ "module_name": "Startseite"
+} \ No newline at end of file
diff --git a/modules-available/main/lang/en/module.json b/modules-available/main/lang/en/module.json
new file mode 100644
index 00000000..15c9658e
--- /dev/null
+++ b/modules-available/main/lang/en/module.json
@@ -0,0 +1,3 @@
+{
+ "module_name": "Front page"
+} \ No newline at end of file
diff --git a/modules-available/minilinux/hooks/main-warning.inc.php b/modules-available/minilinux/hooks/main-warning.inc.php
index 2056bbbf..31668e6c 100644
--- a/modules-available/minilinux/hooks/main-warning.inc.php
+++ b/modules-available/minilinux/hooks/main-warning.inc.php
@@ -1,6 +1,6 @@
<?php
if (!file_exists(CONFIG_HTTP_DIR . '/default/kernel') || !file_exists(CONFIG_HTTP_DIR . '/default/initramfs-stage31') || !file_exists(CONFIG_HTTP_DIR . '/default/stage32.sqfs')) {
- Message::addError('minilinux.please-download-minilinux');
+ Message::addError('minilinux.please-download-minilinux', true);
$needSetup = true;
} \ No newline at end of file
diff --git a/modules-available/serversetup-bwlp/hooks/main-warning.inc.php b/modules-available/serversetup-bwlp/hooks/main-warning.inc.php
new file mode 100644
index 00000000..a2eba6ff
--- /dev/null
+++ b/modules-available/serversetup-bwlp/hooks/main-warning.inc.php
@@ -0,0 +1,6 @@
+<?php
+
+if (!preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/', Property::getServerIp())) {
+ Message::addError('serversetup.no-ip-addr-set', true);
+ $needSetup = true;
+}
diff --git a/modules-available/serversetup-bwlp/lang/de/messages.json b/modules-available/serversetup-bwlp/lang/de/messages.json
index c39958f6..3e2cc834 100644
--- a/modules-available/serversetup-bwlp/lang/de/messages.json
+++ b/modules-available/serversetup-bwlp/lang/de/messages.json
@@ -1,4 +1,5 @@
{
"image-not-found": "USB-Image nicht gefunden. Generieren Sie das Bootmen\u00fc neu.",
- "invalid-ip": "Kein Interface ist auf die Adresse {{0}} konfiguriert"
+ "invalid-ip": "Kein Interface ist auf die Adresse {{0}} konfiguriert",
+ "no-ip-addr-set": "Bitte w\u00e4hlen Sie die prim\u00e4re IP-Adresse des Servers"
} \ No newline at end of file
diff --git a/modules-available/serversetup-bwlp/lang/de/template-tags.json b/modules-available/serversetup-bwlp/lang/de/template-tags.json
index 593eb9e8..4cbd13af 100644
--- a/modules-available/serversetup-bwlp/lang/de/template-tags.json
+++ b/modules-available/serversetup-bwlp/lang/de/template-tags.json
@@ -9,6 +9,7 @@
"lang_chooseIP": "Bitte w\u00e4hlen Sie die IP-Adresse, \u00fcber die der Server von den Clients zum Booten angesprochen werden soll.",
"lang_customEntry": "Eigener Eintrag",
"lang_downloadImage": "USB-Image herunterladen",
+ "lang_downloadRufus": "Rufus herunterladen",
"lang_example": "Beispiel",
"lang_generationFailed": "Erzeugen des Bootmen\u00fcs fehlgeschlagen. Der Netzwerkboot von bwLehrpool wird wahrscheinlich nicht funktionieren. Wenn Sie den Fehler nicht selbst beheben k\u00f6nnen, melden Sie bitte obenstehende Fehlermeldung an das bwLehrpool-Projekt.",
"lang_localHDD": "Lokale HDD",
@@ -21,5 +22,9 @@
"lang_menuDisplayTime": "Anzeigedauer des Men\u00fcs",
"lang_menuGeneration": "Erzeugen des Bootmen\u00fcs",
"lang_seconds": "Sekunden",
- "lang_set": "Setzen"
+ "lang_set": "Setzen",
+ "lang_usbImage": "USB-Image",
+ "lang_usbImgHelp": "Mit dem USB-Image k\u00f6nnen Sie einen bootbaren USB-Stick erstellen, \u00fcber den sich bwLehrpool an Rechnern starten l\u00e4sst, die keinen Netzwerkboot unterst\u00fctzen, bzw. f\u00fcr die keine entsprechende DHCP-Konfiguration vorhanden ist. Dies erfordert dann lediglich, dass in der BIOS-Konfiguration des Rechners USB-Boot zugelassen ist. Der Stick dient dabei lediglich als Einstiegspunkt; es ist nach wie vor ein bwLehrpool-Satellitenserver f\u00fcr den eigentlichen Bootvorgang von N\u00f6ten.",
+ "lang_usbImgHelpLinux": "Nutzen Sie dd, im das Image auf einen USB-Stick zu schreiben. Das Image enth\u00e4lt bereits eine Partitionstabelle, achten Sie daher darauf, dass Sie das Image z.B. nach \/dev\/sdx schreiben, und nicht nach \/dev\/sdx1",
+ "lang_usbImgHelpWindows": "Unter Windows muss zun\u00e4chst ein Programm besorgt werden, mit dem sich Images direkt auf einen USB-Stick schreiben lassen. Es gibt gleich mehrere kostenlose und quelloffene Programme, eines davon ist Rufus. Rufus wurde mit dem bwLehrpool-Image gestetet. Nach dem Starten des Programms ist lediglich das heruntergeladene Image zu \u00f6ffnen, sowie in der Liste der Laufwerke der richtige USB-Stick auszuw\u00e4hlen (damit Sie nicht versehentlich Daten auf dem falschen Laufwerk \u00fcberschreiben!)"
} \ No newline at end of file
diff --git a/modules-available/serversetup-bwlp/lang/en/messages.json b/modules-available/serversetup-bwlp/lang/en/messages.json
index 1e9fe612..d4ba6905 100644
--- a/modules-available/serversetup-bwlp/lang/en/messages.json
+++ b/modules-available/serversetup-bwlp/lang/en/messages.json
@@ -1,4 +1,5 @@
{
"image-not-found": "USB image not found. Try regenerating the boot menu first.",
- "invalid-ip": "No interface is configured with the address {{0}}"
+ "invalid-ip": "No interface is configured with the address {{0}}",
+ "no-ip-addr-set": "Please set the server's primary IP address"
} \ No newline at end of file
diff --git a/modules-available/serversetup-bwlp/lang/en/template-tags.json b/modules-available/serversetup-bwlp/lang/en/template-tags.json
index f2c8aa41..533dacba 100644
--- a/modules-available/serversetup-bwlp/lang/en/template-tags.json
+++ b/modules-available/serversetup-bwlp/lang/en/template-tags.json
@@ -9,6 +9,7 @@
"lang_chooseIP": "Please select the IP address that the client server will use to boot.",
"lang_customEntry": "Custom entry",
"lang_downloadImage": "Download USB image",
+ "lang_downloadRufus": "Download Rufus",
"lang_example": "Example",
"lang_generationFailed": "Could not generate boot menu. The bwLehrpool-System might not work properly. If you can't fix the problem, please report the error message above to the bwLehrpool project.",
"lang_localHDD": "Local HDD",
@@ -21,5 +22,9 @@
"lang_menuDisplayTime": "Menu Display Time",
"lang_menuGeneration": "Generating boot menu...",
"lang_seconds": "Seconds",
- "lang_set": "Set"
+ "lang_set": "Set",
+ "lang_usbImage": "USB image",
+ "lang_usbImgHelp": "The USB image can be used to create a bootable USB stick, which enables you to boot bwLehrpool without changing your DHCP settings or enabling network boot in the clients. The only requirement is that you enable USB boot in the client's BIOS. The USB stick is only used for bootstrapping, the actual bwLehrpool system is still loaded via network from your local bwLehrpool server.",
+ "lang_usbImgHelpLinux": "On Linux you can simply use dd to write the image to a usb stick. The image already contains a partition table, so make sure you write the image to the device itself and not to an already existing partition (e.g. to \/dev\/sdx not \/dev\/sdx1)",
+ "lang_usbImgHelpWindows": "On Windows you need to use a 3rd party tool that can directly write to usb sticks. There are several free and open source soltions, one of them being Rufus. Rufus has been tested with the bwLehrpool image and is very simple to use. After launching Rufus, just open the downloaded USB image, select the proper USB stick to write to (be careful not to overwrite the wrong drive!), and you're ready to go."
} \ No newline at end of file
diff --git a/modules-available/serversetup-bwlp/page.inc.php b/modules-available/serversetup-bwlp/page.inc.php
index 9deb6891..9bea4b50 100644
--- a/modules-available/serversetup-bwlp/page.inc.php
+++ b/modules-available/serversetup-bwlp/page.inc.php
@@ -153,7 +153,7 @@ class Page_ServerSetup extends Page
return;
}
Header('Content-Type: application/octet-stream');
- Header('Content-Disposition: attachment; filename="openslx-bootstick.raw"');
+ Header('Content-Disposition: attachment; filename="openslx-bootstick-' . Property::getServerIp() . '-raw.img"');
readfile($file);
exit;
}
diff --git a/modules-available/serversetup-bwlp/templates/ipxe.html b/modules-available/serversetup-bwlp/templates/ipxe.html
index 7f142066..e0eaa8cc 100644
--- a/modules-available/serversetup-bwlp/templates/ipxe.html
+++ b/modules-available/serversetup-bwlp/templates/ipxe.html
@@ -43,10 +43,15 @@
</div>
<div class="panel-footer">
- <a class="btn btn-default pull-right" href="?do=ServerSetup&amp;action=getimage">
- <span class="glyphicon glyphicon-download-alt"></span>
- {{lang_downloadImage}}
- </a>
+ <div class="pull-right">
+ <div class="btn-group" role="group">
+ <a class="btn btn-default" href="?do=ServerSetup&amp;action=getimage">
+ <span class="glyphicon glyphicon-download-alt"></span>
+ {{lang_downloadImage}}
+ </a>
+ <span class="btn btn-default" data-toggle="modal" data-target="#help-usbimg"><span class="glyphicon glyphicon-question-sign"></span></span>
+ </div>
+ </div>
<button class="btn btn-primary" name="action" value="ipxe">{{lang_bootMenuCreate}}</button>
</div>
</div>
@@ -72,3 +77,28 @@
</div>
</div>
</div>
+
+<div class="modal fade" id="help-usbimg" tabindex="-1" role="dialog">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <div class="modal-header">{{lang_usbImage}}</div>
+ <div class="modal-body">
+ <p>{{lang_usbImgHelp}}</p>
+ <p>
+ <b>Linux</b>
+ <br>
+ {{lang_usbImgHelpLinux}}
+ </p>
+ <p>
+ <b>Windows</b>
+ <br>
+ {{lang_usbImgHelpWindows}}
+ </p>
+ <p>
+ <a href="https://rufus.akeo.ie/#download">{{lang_downloadRufus}}</a>
+ </p>
+ </div>
+ <div class="modal-footer"><a class="btn btn-primary" data-dismiss="modal">{{lang_close}}</a></div>
+ </div>
+ </div>
+</div> \ No newline at end of file
diff --git a/modules-available/sysconfig/hooks/main-warning.inc.php b/modules-available/sysconfig/hooks/main-warning.inc.php
index e5bc592f..9c5a4f3f 100644
--- a/modules-available/sysconfig/hooks/main-warning.inc.php
+++ b/modules-available/sysconfig/hooks/main-warning.inc.php
@@ -1,6 +1,6 @@
<?php
-if (!file_exists(CONFIG_HTTP_DIR . '/default/config.tgz')) {
+if (false === Database::queryFirst("SELECT locationid FROM configtgz_location WHERE locationid = 0")) {
Message::addError('sysconfig.no-noconfig-active', true);
$needSetup = true;
-} \ No newline at end of file
+}
diff --git a/modules-available/sysconfig/inc/configmodule.inc.php b/modules-available/sysconfig/inc/configmodule.inc.php
index 9fc3db21..ca40094a 100644
--- a/modules-available/sysconfig/inc/configmodule.inc.php
+++ b/modules-available/sysconfig/inc/configmodule.inc.php
@@ -14,6 +14,7 @@ abstract class ConfigModule
private $moduleId = 0;
private $moduleArchive = false;
private $moduleTitle = false;
+ private $moduleStatus = false;
private $currentVersion = 0;
protected $moduleData = false;
@@ -28,6 +29,7 @@ abstract class ConfigModule
if (self::$moduleTypes !== false)
return;
self::$moduleTypes = array();
+ Module::isAvailable('sysconfig');
foreach (glob(dirname(__FILE__) . '/configmodule/*.inc.php', GLOB_NOSORT) as $file) {
require_once $file;
}
@@ -58,14 +60,17 @@ abstract class ConfigModule
*/
public static function registerModule($id, $title, $description, $group, $unique, $sortOrder = 0)
{
- if (isset(self::$moduleTypes[$id]))
+ if (isset(self::$moduleTypes[$id])) {
Util::traceError("Config Module $id already registered!");
+ }
$moduleClass = 'ConfigModule_' . $id;
$wizardClass = $id . '_Start';
- if (!class_exists($moduleClass))
+ if (!class_exists($moduleClass)) {
Util::traceError("Class $moduleClass does not exist!");
- if (get_parent_class($moduleClass) !== 'ConfigModule')
+ }
+ if (!is_subclass_of($moduleClass, 'ConfigModule')) {
Util::traceError("$moduleClass does not have ConfigModule as its parent!");
+ }
self::$moduleTypes[$id] = array(
'title' => $title,
'description' => $description,
@@ -86,11 +91,28 @@ abstract class ConfigModule
public static function getInstance($moduleType)
{
self::loadDb();
- if (!isset(self::$moduleTypes[$moduleType]))
+ if (!isset(self::$moduleTypes[$moduleType])) {
+ error_log('Unknown module type: ' . $moduleType);
return false;
+ }
return new self::$moduleTypes[$moduleType]['moduleClass'];
}
+ public static function instanceFromDbRow($dbRow)
+ {
+ $instance = self::getInstance($dbRow['moduletype']);
+ $instance->currentVersion = $dbRow['version'];
+ $instance->moduleArchive = $dbRow['filepath'];
+ $instance->moduleData = json_decode($dbRow['contents'], true);
+ $instance->moduleId = $dbRow['moduleid'];
+ $instance->moduleTitle = $dbRow['title'];
+ $instance->moduleStatus = $dbRow['status'];
+ if ($instance->moduleVersion() > $instance->currentVersion) {
+ $instance->markFailed();
+ }
+ return $instance;
+ }
+
/**
* Get module instance from id.
*
@@ -99,19 +121,11 @@ abstract class ConfigModule
*/
public static function get($moduleId)
{
- $ret = Database::queryFirst("SELECT title, moduletype, filepath, contents, version FROM configtgz_module "
+ $ret = Database::queryFirst("SELECT moduleid, title, moduletype, filepath, contents, version, status FROM configtgz_module "
. " WHERE moduleid = :moduleid LIMIT 1", array('moduleid' => $moduleId));
if ($ret === false)
return false;
- $instance = self::getInstance($ret['moduletype']);
- if ($instance === false)
- return false;
- $instance->currentVersion = $ret['version'];
- $instance->moduleArchive = $ret['filepath'];
- $instance->moduleData = json_decode($ret['contents'], true);
- $instance->moduleId = $moduleId;
- $instance->moduleTitle = $ret['title'];
- return $instance;
+ return self::instanceFromDbRow($ret);
}
/**
@@ -123,23 +137,18 @@ abstract class ConfigModule
public static function getAll($moduleType = false)
{
if ($moduleType === false) {
- $ret = Database::simpleQuery("SELECT moduleid, title, moduletype, filepath, contents, version FROM configtgz_module");
+ $ret = Database::simpleQuery("SELECT moduleid, title, moduletype, filepath, contents, version, status FROM configtgz_module");
} else {
- $ret = Database::simpleQuery("SELECT moduleid, title, moduletype, filepath, contents, version FROM configtgz_module "
+ $ret = Database::simpleQuery("SELECT moduleid, title, moduletype, filepath, contents, version, status FROM configtgz_module "
. " WHERE moduletype = :moduletype", array('moduletype' => $moduleType));
}
if ($ret === false)
return false;
$list = array();
while ($row = $ret->fetch(PDO::FETCH_ASSOC)) {
- $instance = self::getInstance($row['moduletype']);
+ $instance = self::instanceFromDbRow($row);
if ($instance === false)
- return false;
- $instance->currentVersion = $row['version'];
- $instance->moduleArchive = $row['filepath'];
- $instance->moduleData = json_decode($row['contents'], true);
- $instance->moduleId = $row['moduleid'];
- $instance->moduleTitle = $row['title'];
+ continue;
$list[] = $instance;
}
return $list;
@@ -200,6 +209,16 @@ abstract class ConfigModule
return CONFIG_TGZ_LIST_DIR . '/modules/'
. $this->moduleType() . '_id-' . $this->moduleId . '__' . mt_rand() . '-' . time() . '.tgz';
}
+
+ public function allowDownload()
+ {
+ return false;
+ }
+
+ public function needRebuild()
+ {
+ return $this->moduleStatus !== 'OK' || $this->currentVersion < $this->moduleVersion();
+ }
/**
* Get module id (in db)
@@ -230,6 +249,11 @@ abstract class ConfigModule
{
return $this->moduleArchive;
}
+
+ public final function status()
+ {
+ return $this->moduleStatus;
+ }
/**
* Get the module type.
diff --git a/modules-available/sysconfig/inc/configmodule/adauth.inc.php b/modules-available/sysconfig/inc/configmodule/adauth.inc.php
index 180ac717..db06a4a4 100644
--- a/modules-available/sysconfig/inc/configmodule/adauth.inc.php
+++ b/modules-available/sysconfig/inc/configmodule/adauth.inc.php
@@ -1,5 +1,12 @@
<?php
+class ConfigModule_AdAuth extends ConfigModuleBaseLdap
+{
+
+ const MODID = 'AdAuth';
+
+}
+
ConfigModule::registerModule(
ConfigModule_AdAuth::MODID, // ID
Dictionary::translateFileModule('sysconfig', 'config-module', 'adAuth_title'), // Title
@@ -7,69 +14,3 @@ ConfigModule::registerModule(
Dictionary::translateFileModule('sysconfig', 'config-module', 'group_authentication'), // Group
true // Only one per config?
);
-
-class ConfigModule_AdAuth extends ConfigModule
-{
-
- const MODID = 'AdAuth';
- const VERSION = 1;
-
- private static $REQUIRED_FIELDS = array('server', 'searchbase', 'binddn');
- private static $OPTIONAL_FIELDS = array('bindpw', 'home', 'ssl', 'fingerprint', 'certificate', 'homeattr');
-
- protected function generateInternal($tgz, $parent)
- {
- Trigger::ldadp($this->id(), $parent);
- $config = $this->moduleData;
- if (isset($config['certificate']) && !is_string($config['certificate'])) {
- unset($config['certificate']);
- }
- if (preg_match('/^([^\:]+)\:(\d+)$/', $config['server'], $out)) {
- $config['server'] = $out[1];
- $config['adport'] = $out[2];
- } else {
- if (isset($config['certificate'])) {
- $config['adport'] = 636;
- } else {
- $config['adport'] = 389;
- }
- }
- $config['parentTask'] = $parent;
- $config['failOnParentFail'] = false;
- $config['proxyip'] = Property::getServerIp();
- $config['proxyport'] = 3100 + $this->id();
- $config['filename'] = $tgz;
- $config['moduleid'] = $this->id();
- return Taskmanager::submit('CreateLdapConfig', $config);
- }
-
- protected function moduleVersion()
- {
- return self::VERSION;
- }
-
- protected function validateConfig()
- {
- // Check if required fields are filled
- return Util::hasAllKeys($this->moduleData, self::$REQUIRED_FIELDS);
- }
-
- public function setData($key, $value)
- {
- if (!in_array($key, self::$REQUIRED_FIELDS) && !in_array($key, self::$OPTIONAL_FIELDS))
- return false;
- $this->moduleData[$key] = $value;
- return true;
- }
-
- // ############## Callbacks #############################
-
- /**
- * Server IP changed - rebuild all AD modules.
- */
- public function event_serverIpChanged()
- {
- $this->generate(false);
- }
-
-}
diff --git a/modules-available/sysconfig/inc/configmodule/branding.inc.php b/modules-available/sysconfig/inc/configmodule/branding.inc.php
index 4a9718d6..fd11dade 100644
--- a/modules-available/sysconfig/inc/configmodule/branding.inc.php
+++ b/modules-available/sysconfig/inc/configmodule/branding.inc.php
@@ -53,4 +53,9 @@ class ConfigModule_Branding extends ConfigModule
return false;
}
+ public function allowDownload()
+ {
+ return true;
+ }
+
}
diff --git a/modules-available/sysconfig/inc/configmodule/customodule.inc.php b/modules-available/sysconfig/inc/configmodule/customodule.inc.php
index 8d1b6bf0..336d794f 100644
--- a/modules-available/sysconfig/inc/configmodule/customodule.inc.php
+++ b/modules-available/sysconfig/inc/configmodule/customodule.inc.php
@@ -53,4 +53,9 @@ class ConfigModule_CustomModule extends ConfigModule
return false;
}
+ public function allowDownload()
+ {
+ return true;
+ }
+
}
diff --git a/modules-available/sysconfig/inc/configmodule/ldapauth.inc.php b/modules-available/sysconfig/inc/configmodule/ldapauth.inc.php
index ed1a47c3..1a706234 100644
--- a/modules-available/sysconfig/inc/configmodule/ldapauth.inc.php
+++ b/modules-available/sysconfig/inc/configmodule/ldapauth.inc.php
@@ -1,77 +1,23 @@
<?php
-ConfigModule::registerModule(
- ConfigModule_LdapAuth::MODID, // ID
- Dictionary::translateFileModule('sysconfig', 'config-module', 'ldapAuth_title'), // Title
- Dictionary::translateFileModule('sysconfig', 'config-module', 'ldapAuth_description'), // Description
- Dictionary::translateFileModule('sysconfig', 'config-module', 'group_authentication'), // Group
- true // Only one per config?
-);
-
-class ConfigModule_LdapAuth extends ConfigModule
+class ConfigModule_LdapAuth extends ConfigModuleBaseLdap
{
const MODID = 'LdapAuth';
- const VERSION = 1;
- private static $REQUIRED_FIELDS = array('server', 'searchbase');
- private static $OPTIONAL_FIELDS = array('binddn', 'bindpw', 'home', 'ssl', 'fingerprint', 'certificate');
-
- protected function generateInternal($tgz, $parent)
+ protected function preTaskmanagerHook(&$config)
{
- Trigger::ldadp($this->id(), $parent);
- $config = $this->moduleData;
- if (isset($config['certificate']) && !is_string($config['certificate'])) {
- unset($config['certificate']);
- }
- if (preg_match('/^([^\:]+)\:(\d+)$/', $config['server'], $out)) {
- $config['server'] = $out[1];
- $config['adport'] = $out[2]; // sic!
- } else {
- if (isset($config['certificate'])) {
- $config['adport'] = 636;
- } else {
- $config['adport'] = 389;
- }
- }
- $config['parentTask'] = $parent;
- $config['failOnParentFail'] = false;
- $config['proxyip'] = Property::getServerIp();
- $config['proxyport'] = 3100 + $this->id();
- $config['filename'] = $tgz;
- $config['moduleid'] = $this->id();
+ // Just set the flag so the taskmanager job knows we're dealing with a normal ldap server,
+ // not AD scheme
$config['plainldap'] = true;
- return Taskmanager::submit('CreateLdapConfig', $config);
- }
-
- protected function moduleVersion()
- {
- return self::VERSION;
- }
-
- protected function validateConfig()
- {
- // Check if required fields are filled
- return Util::hasAllKeys($this->moduleData, self::$REQUIRED_FIELDS);
- }
-
- public function setData($key, $value)
- {
- if (!in_array($key, self::$REQUIRED_FIELDS) && !in_array($key, self::$OPTIONAL_FIELDS))
- return false;
- $this->moduleData[$key] = $value;
- return true;
- }
-
- // ############## Callbacks #############################
-
- /**
- * Server IP changed - rebuild all LDAP modules.
- */
- public function event_serverIpChanged()
- {
- error_log('Calling generate on ' . $this->title());
- $this->generate(false);
}
}
+
+ConfigModule::registerModule(
+ ConfigModule_LdapAuth::MODID, // ID
+ Dictionary::translateFileModule('sysconfig', 'config-module', 'ldapAuth_title'), // Title
+ Dictionary::translateFileModule('sysconfig', 'config-module', 'ldapAuth_description'), // Description
+ Dictionary::translateFileModule('sysconfig', 'config-module', 'group_authentication'), // Group
+ true // Only one per config?
+);
diff --git a/modules-available/sysconfig/inc/configmodulebaseldap.inc.php b/modules-available/sysconfig/inc/configmodulebaseldap.inc.php
new file mode 100644
index 00000000..760593e1
--- /dev/null
+++ b/modules-available/sysconfig/inc/configmodulebaseldap.inc.php
@@ -0,0 +1,85 @@
+<?php
+
+abstract class ConfigModuleBaseLdap extends ConfigModule
+{
+
+ const VERSION = 2;
+
+ private static $REQUIRED_FIELDS = array('server', 'searchbase');
+ private static $OPTIONAL_FIELDS = array('binddn', 'bindpw', 'home', 'ssl', 'fingerprint', 'certificate', 'homeattr',
+ 'shareRemapMode', 'shareRemapCreate', 'shareDocuments', 'shareDownloads', 'shareDesktop', 'shareMedia', 'shareOther', 'shareHomeDrive');
+
+ protected function generateInternal($tgz, $parent)
+ {
+ Trigger::ldadp($this->id(), $parent);
+ $config = $this->moduleData;
+ if (isset($config['certificate']) && !is_string($config['certificate'])) {
+ unset($config['certificate']);
+ }
+ if (preg_match('/^([^\:]+)\:(\d+)$/', $config['server'], $out)) {
+ $config['server'] = $out[1];
+ $config['adport'] = $out[2];
+ } else {
+ if (isset($config['certificate'])) {
+ $config['adport'] = 636;
+ } else {
+ $config['adport'] = 389;
+ }
+ }
+ $config['parentTask'] = $parent;
+ $config['failOnParentFail'] = false;
+ $config['proxyip'] = Property::getServerIp();
+ $config['proxyport'] = 3100 + $this->id();
+ $config['filename'] = $tgz;
+ $config['moduleid'] = $this->id();
+ if (!isset($config['shareRemapMode'])) {
+ $config['shareRemapMode'] = 3;
+ }
+ if (!isset($config['shareHomeDrive'])) {
+ $config['shareHomeDrive'] = 'H:';
+ }
+ $this->preTaskmanagerHook($config);
+ return Taskmanager::submit('CreateLdapConfig', $config);
+ }
+
+ /**
+ * Hook called before running CreateLdapConfig task with the
+ * configuration to be passed to the task. Passed by reference
+ * so it can be modified.
+ *
+ * @param array $config
+ */
+ protected function preTaskmanagerHook(&$config)
+ {
+ }
+
+ protected function moduleVersion()
+ {
+ return self::VERSION;
+ }
+
+ protected function validateConfig()
+ {
+ // Check if required fields are filled
+ return Util::hasAllKeys($this->moduleData, self::$REQUIRED_FIELDS);
+ }
+
+ public function setData($key, $value)
+ {
+ if (!in_array($key, self::$REQUIRED_FIELDS) && !in_array($key, self::$OPTIONAL_FIELDS))
+ return false;
+ $this->moduleData[$key] = $value;
+ return true;
+ }
+
+ // ############## Callbacks #############################
+
+ /**
+ * Server IP changed - rebuild all AD modules.
+ */
+ public function event_serverIpChanged()
+ {
+ $this->generate(false);
+ }
+
+}
diff --git a/modules-available/sysconfig/install.inc.php b/modules-available/sysconfig/install.inc.php
index 0cde39c2..91f282dd 100644
--- a/modules-available/sysconfig/install.inc.php
+++ b/modules-available/sysconfig/install.inc.php
@@ -39,10 +39,17 @@ $res[] = tableCreate('configtgz_location', "
// Constraints
if (in_array(UPDATE_DONE, $res)) {
- Database::exec("ALTER TABLE `configtgz_x_module`
- ADD CONSTRAINT `configtgz_x_module_ibfk_1` FOREIGN KEY (`configid`) REFERENCES `configtgz` (`configid`) ON DELETE CASCADE");
- Database::exec("ALTER TABLE `configtgz_x_module`
- ADD CONSTRAINT `configtgz_x_module_ibfk_2` FOREIGN KEY (`moduleid`) REFERENCES `configtgz_module` (`moduleid`)");
+ $ret = Database::exec("ALTER TABLE `configtgz_x_module`
+ ADD CONSTRAINT `configtgz_x_module_ibfk_1` FOREIGN KEY (`configid`) REFERENCES `configtgz` (`configid`)
+ ON DELETE CASCADE");
+ $ret = Database::exec("ALTER TABLE `configtgz_x_module`
+ ADD CONSTRAINT `configtgz_x_module_ibfk_2` FOREIGN KEY (`moduleid`) REFERENCES `configtgz_module` (`moduleid`)") || $ret;
+ $ret = Database::exec("ALTER TABLE `configtgz_location`
+ ADD CONSTRAINT `configtgz_location_fk_configid` FOREIGN KEY ( `configid` ) REFERENCES `openslx`.`configtgz` (`configid`)
+ ON DELETE CASCADE ON UPDATE CASCADE") || $ret;
+ if ($ret) {
+ $res[] = UPDATE_DONE;
+ }
}
// Update path
@@ -77,14 +84,16 @@ if (!tableHasColumn('configtgz', 'status')) {
}
// ----- rebuild AD configs ------
-// TEMPORARY HACK; Rebuild AD configs.. move somewhere else
+// TEMPORARY HACK; Rebuild configs.. move somewhere else?
Module::isAvailable('sysconfig');
-$list = array_merge(ConfigModule::getAll('AdAuth'), ConfigModule::getAll('LdapAuth'));
+$list = ConfigModule::getAll();
if ($list === false) {
EventLog::warning('Could not regenerate AD/LDAP configs - please do so manually');
} else {
foreach ($list as $ad) {
- $ad->generate(false);
+ if ($ad->needRebuild()) {
+ $ad->generate(false);
+ }
}
}
diff --git a/modules-available/sysconfig/page.inc.php b/modules-available/sysconfig/page.inc.php
index 9bf9cacd..a3c0c93f 100644
--- a/modules-available/sysconfig/page.inc.php
+++ b/modules-available/sysconfig/page.inc.php
@@ -5,6 +5,7 @@ class Page_SysConfig extends Page
/**
* Holds all the known configuration modules, with title, description, start class for their wizard, etc.
+ *
* @var array
*/
protected static $moduleTypes = array();
@@ -46,7 +47,7 @@ class Page_SysConfig extends Page
}
/**
- *
+ *
* @return array All registered module types
*/
public static function getModuleTypes()
@@ -136,35 +137,35 @@ class Page_SysConfig extends Page
{
$action = Request::any('action', 'list');
switch ($action) {
- case 'addmodule':
- AddModule_Base::render();
- return;
- case 'addconfig':
- AddConfig_Base::render();
+ case 'addmodule':
+ AddModule_Base::render();
+ return;
+ case 'addconfig':
+ AddConfig_Base::render();
+ return;
+ case 'list':
+ Render::openTag('div', array('class' => 'row'));
+ $this->listConfigs();
+ if ($this->currentLoc === 0) {
+ $this->listModules();
+ }
+ Render::closeTag('div');
+ Render::addTemplate('list-legend', array('showLocationBadge' => $this->haveOverriddenLocations));
+ return;
+ case 'module':
+ $listid = Request::post('list');
+ if ($listid !== false) {
+ $this->listModuleContents($listid);
return;
- case 'list':
- Render::openTag('div', array('class' => 'row'));
- $this->listConfigs();
- if ($this->currentLoc === 0) {
- $this->listModules();
- }
- Render::closeTag('div');
- Render::addTemplate('list-legend', array('showLocationBadge' => $this->haveOverriddenLocations));
+ }
+ break;
+ case 'config':
+ $listid = Request::post('list');
+ if ($listid !== false) {
+ $this->listConfigContents($listid);
return;
- case 'module':
- $listid = Request::post('list');
- if ($listid !== false) {
- $this->listModuleContents($listid);
- return;
- }
- break;
- case 'config':
- $listid = Request::post('list');
- if ($listid !== false) {
- $this->listConfigContents($listid);
- return;
- }
- break;
+ }
+ break;
}
Message::addError('invalid-action', $action, 'main');
}
@@ -243,17 +244,10 @@ class Page_SysConfig extends Page
private function listModules()
{
// Config modules
- $res = Database::simpleQuery("SELECT moduleid, title, moduletype, status FROM configtgz_module ORDER BY moduletype ASC, title ASC");
- $modules = array();
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
- $modules[] = array(
- 'moduleid' => $row['moduleid'],
- 'moduletype' => $row['moduletype'],
- 'module' => $row['title'],
- 'iscustom' => ($row['moduletype'] === 'CustomModule' || $row['moduletype'] === 'Branding'),
- 'needrebuild' => ($row['status'] !== 'OK')
- );
- }
+ $modules = ConfigModule::getAll();
+ $types = array_map(function ($mod) { return $mod->moduleType(); }, $modules);
+ $titles = array_map(function ($mod) { return $mod->title(); }, $modules);
+ array_multisort($types, SORT_ASC, $titles, SORT_ASC, $modules);
Render::addTemplate('list-modules', array(
'modules' => $modules,
'havemodules' => (count($modules) > 0)
@@ -271,7 +265,7 @@ class Page_SysConfig extends Page
// find files in that archive
$status = Taskmanager::submit('ListArchive', array(
- 'file' => $row['filepath']
+ 'file' => $row['filepath']
));
if (isset($status['id']))
$status = Taskmanager::waitComplete($status, 4000);
@@ -306,7 +300,7 @@ class Page_SysConfig extends Page
'files' => $list,
));
}
-
+
private function listConfigContents($configid)
{
// get config name
@@ -321,7 +315,7 @@ class Page_SysConfig extends Page
. " INNER JOIN configtgz_x_module USING (moduleid)"
. " WHERE configtgz_x_module.configid = :configid"
. " ORDER BY module.title ASC", array('configid' => $configid));
-
+
$modules = array();
while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
$modules[] = array(
@@ -329,7 +323,7 @@ class Page_SysConfig extends Page
'moduleid' => $row['moduleid']
);
}
-
+
// render the template
Render::addDialog(Dictionary::translate('lang_contentOf') . ' ' . $config['title'], false, 'config-module-list', array(
'modules' => $modules
@@ -364,7 +358,7 @@ class Page_SysConfig extends Page
Message::addError('config-invalid', $configid);
Util::redirect('?do=sysconfig&locationid=' . $this->currentLoc);
}
- $ret = $config->generate(false, 350); // TODO
+ $ret = $config->generate(false, 500); // TODO
if ($ret === true)
Message::addSuccess('module-rebuilt', $config->title());
elseif ($ret === false)
@@ -383,14 +377,14 @@ class Page_SysConfig extends Page
Util::redirect('?do=sysconfig');
}
$existing = Database::queryFirst("SELECT title FROM configtgz_x_module"
- . " INNER JOIN configtgz USING (configid)"
- . " WHERE moduleid = :moduleid LIMIT 1", array('moduleid' => $moduleid));
+ . " INNER JOIN configtgz USING (configid)"
+ . " WHERE moduleid = :moduleid LIMIT 1", array('moduleid' => $moduleid));
if ($existing !== false) {
Message::addError('module-in-use', $row['title'], $existing['title']);
Util::redirect('?do=sysconfig');
}
$task = Taskmanager::submit('DeleteFile', array(
- 'file' => $row['filepath']
+ 'file' => $row['filepath']
));
if (isset($task['statusCode']) && $task['statusCode'] === TASK_WAITING) {
$task = Taskmanager::waitComplete($task['id']);
diff --git a/modules-available/sysconfig/templates/list-modules.html b/modules-available/sysconfig/templates/list-modules.html
index c3e2d736..ba9ce425 100644
--- a/modules-available/sysconfig/templates/list-modules.html
+++ b/modules-available/sysconfig/templates/list-modules.html
@@ -11,25 +11,25 @@
<table id="modtable" class="slx-table" style="max-width:100px !important">
{{#modules}}
<tr>
- <td class="badge slx-nowrap">{{moduletype}}</td>
- <td data-id="{{moduleid}}" class="modrow slx-width-ignore slx-nowrap" width="100%"><div class="slx-dyn-ellipsis">{{module}}</div></td>
+ <td class="badge slx-nowrap">{{moduleType}}</td>
+ <td data-id="{{id}}" class="modrow slx-width-ignore slx-nowrap" width="100%"><div class="slx-dyn-ellipsis">{{title}}</div></td>
<td class="slx-nowrap">
- {{#iscustom}}
- <button class="btn btn-default btn-xs" name="list" value="{{moduleid}}" title="{{lang_show}}"><span class="glyphicon glyphicon-eye-open"></span></button>
- <button class="btn btn-default btn-xs" name="download" value="{{moduleid}}" title="{{lang_download}}"><span class="glyphicon glyphicon-download-alt"></span></button>
- {{/iscustom}}
+ {{#allowDownload}}
+ <button class="btn btn-default btn-xs" name="list" value="{{id}}" title="{{lang_show}}"><span class="glyphicon glyphicon-eye-open"></span></button>
+ <button class="btn btn-default btn-xs" name="download" value="{{id}}" title="{{lang_download}}"><span class="glyphicon glyphicon-download-alt"></span></button>
+ {{/allowDownload}}
</td>
<td class="slx-nowrap">
<button
- {{#needrebuild}}
+ {{#needRebuild}}
class="refmod btn btn-primary btn-xs"
- {{/needrebuild}}
- {{^needrebuild}}
+ {{/needRebuild}}
+ {{^needRebuild}}
class="refmod btn btn-default btn-xs"
- {{/needrebuild}}
- name="rebuild" value="{{moduleid}}" title="{{lang_rebuild}}"><span class="glyphicon glyphicon-refresh"></span></button>
- <a class="btn btn-success btn-xs" href="?do=SysConfig&amp;action=addmodule&amp;step={{moduletype}}_Start&amp;edit={{moduleid}}" title="{{lang_edit}}"><span class="glyphicon glyphicon-edit"></span></a>
- <button class="btn btn-danger btn-xs" name="del" value="{{moduleid}}" title="{{lang_delete}}"><span class="glyphicon glyphicon-trash"></span></button>
+ {{/needRebuild}}
+ name="rebuild" value="{{id}}" title="{{lang_rebuild}}"><span class="glyphicon glyphicon-refresh"></span></button>
+ <a class="btn btn-success btn-xs" href="?do=SysConfig&amp;action=addmodule&amp;step={{moduleType}}_Start&amp;edit={{id}}" title="{{lang_edit}}"><span class="glyphicon glyphicon-edit"></span></a>
+ <button class="btn btn-danger btn-xs" name="del" value="{{id}}" title="{{lang_delete}}"><span class="glyphicon glyphicon-trash"></span></button>
</td>
</tr>
{{/modules}}
diff --git a/style/default.css b/style/default.css
index 9d29c9fa..6ae03b6b 100644
--- a/style/default.css
+++ b/style/default.css
@@ -159,6 +159,16 @@ body {
white-space: nowrap;
}
+.slx-space {
+ margin-top: 1em;
+ margin-bottom: 1em;
+}
+
+.slx-smallspace {
+ margin-top: 4px;
+ margin-bottom: 4px;
+}
+
.slx-rotation {
animation-name: rotateThis;
animation-duration: .75s;