summaryrefslogtreecommitdiffstats
path: root/modules-available/sysconfig
diff options
context:
space:
mode:
Diffstat (limited to 'modules-available/sysconfig')
-rw-r--r--modules-available/sysconfig/addconfig.inc.php96
-rw-r--r--modules-available/sysconfig/addmodule.inc.php163
-rw-r--r--modules-available/sysconfig/addmodule_adauth.inc.php67
-rw-r--r--modules-available/sysconfig/addmodule_branding.inc.php103
-rw-r--r--modules-available/sysconfig/addmodule_custommodule.inc.php100
-rw-r--r--modules-available/sysconfig/addmodule_ldapauth.inc.php47
-rw-r--r--modules-available/sysconfig/addmodule_loginscreen.inc.php143
-rw-r--r--modules-available/sysconfig/addmodule_screensaver.inc.php252
-rw-r--r--modules-available/sysconfig/addmodule_shibauth.inc.php185
-rw-r--r--modules-available/sysconfig/addmodule_sshconfig.inc.php42
-rw-r--r--modules-available/sysconfig/addmodule_sshkey.inc.php72
-rw-r--r--modules-available/sysconfig/api.inc.php88
-rw-r--r--modules-available/sysconfig/clientscript.js195
-rw-r--r--modules-available/sysconfig/hooks/bootup.inc.php3
-rw-r--r--modules-available/sysconfig/hooks/cron.inc.php7
-rw-r--r--modules-available/sysconfig/hooks/locations-column.inc.php57
-rw-r--r--modules-available/sysconfig/inc/configmodule.inc.php245
-rw-r--r--modules-available/sysconfig/inc/configmodule/adauth.inc.php9
-rw-r--r--modules-available/sysconfig/inc/configmodule/branding.inc.php29
-rw-r--r--modules-available/sysconfig/inc/configmodule/customodule.inc.php44
-rw-r--r--modules-available/sysconfig/inc/configmodule/ldapauth.inc.php11
-rw-r--r--modules-available/sysconfig/inc/configmodule/loginscreen.inc.php100
-rw-r--r--modules-available/sysconfig/inc/configmodule/screensaver.inc.php99
-rw-r--r--modules-available/sysconfig/inc/configmodule/shibauth.inc.php150
-rw-r--r--modules-available/sysconfig/inc/configmodule/sshconfig.inc.php49
-rw-r--r--modules-available/sysconfig/inc/configmodule/sshkey.inc.php55
-rw-r--r--modules-available/sysconfig/inc/configmodulebaseldap.inc.php68
-rw-r--r--modules-available/sysconfig/inc/configtgz.inc.php296
-rw-r--r--modules-available/sysconfig/inc/ldap.inc.php2
-rw-r--r--modules-available/sysconfig/inc/ppd.inc.php255
-rw-r--r--modules-available/sysconfig/inc/shib.inc.php150
-rw-r--r--modules-available/sysconfig/inc/sysconfig.inc.php40
-rw-r--r--modules-available/sysconfig/install.inc.php87
-rw-r--r--modules-available/sysconfig/lang/de/config-module.json17
-rw-r--r--modules-available/sysconfig/lang/de/messages.json9
-rw-r--r--modules-available/sysconfig/lang/de/module.json34
-rw-r--r--modules-available/sysconfig/lang/de/template-tags.json54
-rw-r--r--modules-available/sysconfig/lang/en/config-module.json17
-rw-r--r--modules-available/sysconfig/lang/en/messages.json7
-rw-r--r--modules-available/sysconfig/lang/en/module.json34
-rw-r--r--modules-available/sysconfig/lang/en/template-tags.json56
-rw-r--r--modules-available/sysconfig/page.inc.php233
-rw-r--r--modules-available/sysconfig/templates/ad-selfsearch.html2
-rw-r--r--modules-available/sysconfig/templates/ad-start.html18
-rw-r--r--modules-available/sysconfig/templates/ad_ldap-checkconnection.html2
-rw-r--r--modules-available/sysconfig/templates/ad_ldap-checkcredentials.html3
-rw-r--r--modules-available/sysconfig/templates/ad_ldap-homedir.html1
-rw-r--r--modules-available/sysconfig/templates/assign.html33
-rw-r--r--modules-available/sysconfig/templates/branding-check.html2
-rw-r--r--modules-available/sysconfig/templates/branding-start.html2
-rw-r--r--modules-available/sysconfig/templates/cfg-finish.html5
-rw-r--r--modules-available/sysconfig/templates/cfg-start.html2
-rw-r--r--modules-available/sysconfig/templates/custom-filelist.html18
-rw-r--r--modules-available/sysconfig/templates/custom-fileselect.html36
-rw-r--r--modules-available/sysconfig/templates/js.html9
-rw-r--r--modules-available/sysconfig/templates/ldap-finish.html1
-rw-r--r--modules-available/sysconfig/templates/ldap-start.html16
-rw-r--r--modules-available/sysconfig/templates/list-configs.html18
-rw-r--r--modules-available/sysconfig/templates/list-legend.html9
-rw-r--r--modules-available/sysconfig/templates/list-modules.html26
-rw-r--r--modules-available/sysconfig/templates/loginscreen-start.html38
-rw-r--r--modules-available/sysconfig/templates/screensaver-start.html131
-rw-r--r--modules-available/sysconfig/templates/screensaver-text.html121
-rw-r--r--modules-available/sysconfig/templates/shibauth-orgs.html64
-rw-r--r--modules-available/sysconfig/templates/shibauth-start.html53
-rw-r--r--modules-available/sysconfig/templates/sshconfig-start.html38
-rw-r--r--modules-available/sysconfig/templates/sshkey-start.html21
67 files changed, 3293 insertions, 1146 deletions
diff --git a/modules-available/sysconfig/addconfig.inc.php b/modules-available/sysconfig/addconfig.inc.php
index 55944cfa..2b6d7624 100644
--- a/modules-available/sysconfig/addconfig.inc.php
+++ b/modules-available/sysconfig/addconfig.inc.php
@@ -9,57 +9,34 @@ abstract class AddConfig_Base
/**
* Holds the instance for the currently executing step
- * @var \AddConfig_Base
+ * @var AddConfig_Base
*/
- private static $instance = false;
+ private static $instance = null;
/**
* Config being edited (if any)
- * @var \ConfigTgz
+ * @var ?ConfigTgz
*/
- protected $edit = false;
+ protected $edit = null;
- /**
- *
- * @param string $step
- */
- public static function setStep($step)
+ public static function setStep(string $step)
{
if (empty($step) || !class_exists($step) || get_parent_class($step) !== 'AddConfig_Base') {
Message::addError('invalid-action', $step);
- Util::redirect('?do=SysConfig');
+ Util::redirect('?do=SysConfig', 400);
}
self::$instance = new $step();
- if (Request::any('edit')) {
- self::$instance->edit = ConfigTgz::get(Request::any('edit'));
- if (self::$instance->edit === false)
- Util::traceError('Invalid config id for editing');
+ if (($editId = Request::any('edit', 0, 'int')) !== 0) {
+ self::$instance->edit = ConfigTgz::get($editId);
+ if (self::$instance->edit === null)
+ ErrorHandler::traceError('Invalid config id for editing: ' . $editId);
Util::addRedirectParam('edit', self::$instance->edit->id());
}
}
- protected function tmError()
- {
- Message::addError('main.taskmanager-error');
- Util::redirect('?do=SysConfig');
- }
-
- protected function taskError($status)
- {
- if (isset($status['data']['error'])) {
- $error = $status['data']['error'];
- } elseif (isset($status['statusCode'])) {
- $error = $status['statusCode'];
- } else {
- $error = Dictionary::translate('lang_unknwonTaskManager'); // TODO: No text
- }
- Message::addError('main.task-error', $error);
- Util::redirect('?do=SysConfig');
- }
-
/**
* Called before any HTML rendering happens, so you can
- * pepare stuff, validate input, and optionally redirect
+ * prepare stuff, validate input, and optionally redirect
* early if something is wrong, or you received post
* data etc.
*/
@@ -86,25 +63,25 @@ abstract class AddConfig_Base
public static function preprocess()
{
- if (self::$instance === false) {
- Util::traceError('No step instance yet');
+ if (self::$instance === null) {
+ ErrorHandler::traceError('No step instance yet');
}
self::$instance->preprocessInternal();
}
public static function render()
{
- if (self::$instance === false)
- Util::traceError('No step instance yet');
- if (self::$instance->edit !== false)
+ if (self::$instance === null)
+ ErrorHandler::traceError('No step instance yet');
+ if (self::$instance->edit !== null)
Message::addInfo('replacing-config', self::$instance->edit->title());
self::$instance->renderInternal();
}
public static function ajax()
{
- if (self::$instance === false) {
- Util::traceError('No step instance yet');
+ if (self::$instance === null) {
+ ErrorHandler::traceError('No step instance yet');
}
self::$instance->ajaxInternal();
}
@@ -123,7 +100,7 @@ class AddConfig_Start extends AddConfig_Base
$mods = ConfigModule::getList();
$res = Database::simpleQuery("SELECT moduleid, title, moduletype, filepath FROM configtgz_module"
. " ORDER BY title ASC"); // Move to ConfigModule
- if ($this->edit === false) {
+ if ($this->edit === null) {
$active = array();
} else {
$active = $this->edit->getModuleIds();
@@ -135,7 +112,7 @@ class AddConfig_Start extends AddConfig_Base
$modGroups[$mod['group']] =& $mod;
}
unset($mod);
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ foreach ($res as $row) {
if (!isset($mods[$row['moduletype']])) {
$mods[$row['moduletype']] = array(
'unique' => false,
@@ -153,15 +130,15 @@ class AddConfig_Start extends AddConfig_Base
$row['active'] = in_array($row['moduleid'], $active);
$group['modules'][] = $row;
}
- if ($this->edit !== false) {
+ if ($this->edit !== null) {
$title = $this->edit->title();
- } elseif (Request::any('title')) {
- $title = Request::any('title');
} else {
- $title = '';
+ $title = Request::any('title', '', 'string');
}
$dummy = 0;
+ $sort = [];
foreach ($modGroups as &$mod) {
+ $sort[] = $mod['sortOrder'];
if (!empty($mod['modules']) && $mod['unique']) {
array_unshift($mod['modules'], array(
'moduleid' => 'x' . (++$dummy),
@@ -169,12 +146,13 @@ class AddConfig_Start extends AddConfig_Base
));
}
}
+ array_multisort($sort, SORT_ASC | SORT_NUMERIC, $modGroups);
unset($mod);
Render::addDialog(Dictionary::translate("lang_configurationCompilation"), false, 'cfg-start', array(
'step' => 'AddConfig_Finish',
'groups' => array_values($modGroups),
'title' => $title,
- 'edit' => ($this->edit !== false ? $this->edit->id() : false)
+ 'edit' => $this->edit === null ? null : $this->edit->id(),
));
}
@@ -186,31 +164,23 @@ class AddConfig_Start extends AddConfig_Base
class AddConfig_Finish extends AddConfig_Base
{
/**
- * @var ConfigTgz
+ * @var ?ConfigTgz
*/
- private $config = false;
+ private $config = null;
protected function preprocessInternal()
{
- $modules = Request::post('module');
- $title = Request::post('title');
- if (!is_array($modules)) {
- Message::addError('missing-file');
- Util::redirect('?do=SysConfig&action=addconfig');
- }
- if (empty($title)) {
- Message::addError('missing-title');
- Util::redirect('?do=SysConfig&action=addconfig');
- }
- if ($this->edit === false) {
+ $modules = Request::post('module', Request::REQUIRED_EMPTY, 'array');
+ $title = Request::post('title', Request::REQUIRED, 'string');
+ if ($this->edit === null) {
$this->config = ConfigTgz::insert($title, $modules);
} else {
$this->edit->update($title, $modules);
$this->config = $this->edit;
}
- if ($this->config === false || $this->config->generate(true, 150) === false) {
+ if ($this->config->generate($this->edit === null, 150) === false) {
Message::addError('unsuccessful-action');
- Util::redirect('?do=SysConfig&action=addconfig');
+ Util::redirect('?do=SysConfig&action=addconfig', 400);
}
}
diff --git a/modules-available/sysconfig/addmodule.inc.php b/modules-available/sysconfig/addmodule.inc.php
index 1f78de81..95f97ad7 100644
--- a/modules-available/sysconfig/addmodule.inc.php
+++ b/modules-available/sysconfig/addmodule.inc.php
@@ -9,33 +9,39 @@ abstract class AddModule_Base
/**
* Holds the instance for the currently executing step
- * @var \AddModule_Base
+ *
+ * @var AddModule_Base
*/
private static $instance = false;
/**
* Instance of ConfigModule we're editing. False if not editing but creating.
- * @var \ConfigModule
+ *
+ * @var ?ConfigModule
*/
- protected $edit = false;
+ protected $edit = null;
/**
*
* @param string $step name of class representing the current step
+ * @param int $editId (optional) overwrite for the request parameter 'edit'
*/
- public static function setStep($step)
+ public static function setStep(string $step, ?int $editId = null): void
{
if (empty($step) || !class_exists($step) || get_parent_class($step) !== 'AddModule_Base') {
Message::addError('invalid-action', $step);
- Util::redirect('?do=SysConfig');
+ Util::redirect('?do=SysConfig', 400);
}
self::$instance = new $step();
- if (Request::any('edit')) {
- self::$instance->edit = ConfigModule::get(Request::any('edit'));
- if (self::$instance->edit === false)
- Util::traceError('Invalid module id for editing');
- if (!preg_match('/^' . self::$instance->edit->moduleType() . '_/', $step))
- Util::traceError('Module to edit is of different type!');
+ if ($editId === null) {
+ $editId = Request::any('edit', 0, 'int');
+ }
+ if ($editId !== 0) {
+ self::$instance->edit = ConfigModule::get($editId);
+ if (self::$instance->edit === null)
+ ErrorHandler::traceError('Invalid module id for editing');
+ if ($step !== 'AddModule_Assign' && !preg_match('/^' . self::$instance->edit->moduleType() . '_/', $step))
+ ErrorHandler::traceError('Module to edit is of different type!');
Util::addRedirectParam('edit', self::$instance->edit->id());
}
}
@@ -43,7 +49,7 @@ abstract class AddModule_Base
protected function tmError()
{
Message::addError('main.taskmanager-error');
- Util::redirect('?do=SysConfig');
+ Util::redirect('?do=SysConfig', 500);
}
protected function taskError($status)
@@ -56,12 +62,12 @@ abstract class AddModule_Base
$error = Dictionary::translate('lang_unknwonTaskManager');
}
Message::addError('main.task-error', $error);
- Util::redirect('?do=SysConfig');
+ Util::redirect('?do=SysConfig', 500);
}
/**
* Called before any HTML rendering happens, so you can
- * pepare stuff, validate input, and optionally redirect
+ * prepare stuff, validate input, and optionally redirect
* early if something is wrong, or you received post
* data etc.
*/
@@ -89,7 +95,7 @@ abstract class AddModule_Base
public static function preprocess()
{
if (self::$instance === false) {
- Util::traceError('No step instance yet');
+ ErrorHandler::traceError('No step instance yet');
}
self::$instance->preprocessInternal();
}
@@ -97,9 +103,9 @@ abstract class AddModule_Base
public static function render()
{
if (self::$instance === false) {
- Util::traceError('No step instance yet');
+ ErrorHandler::traceError('No step instance yet');
}
- if (self::$instance->edit !== false) {
+ if (get_class(self::$instance) !== 'AddModule_Assign' && self::$instance->edit !== null) {
Message::addInfo('replacing-module', self::$instance->edit->title());
}
self::$instance->renderInternal();
@@ -108,7 +114,7 @@ abstract class AddModule_Base
public static function ajax()
{
if (self::$instance === false) {
- Util::traceError('No step instance yet');
+ ErrorHandler::traceError('No step instance yet');
}
self::$instance->ajaxInternal();
}
@@ -136,45 +142,124 @@ class AddModule_Start extends AddModule_Base
}
+/**
+ * End dialog for adding module. Here the user
+ * can assign the module to configs.
+ */
+class AddModule_Assign extends AddModule_Base
+{
+
+ protected function preprocessInternal()
+ {
+ User::assertPermission('config.edit');
+
+ $assign = Request::any('assign', false, 'boolean');
+
+ if ($assign) {
+ $configIds = Request::any('configs', [], 'array');
+ $moduleId = $this->edit->id();
+ $moduleType = $this->edit->moduleType();
+
+ if (ConfigModule::getList()[$moduleType]['unique']) {
+ // Is a unique type module
+ $moduleIds = [];
+ foreach (ConfigModule::getAll($moduleType) ?? [] as $module) {
+ $moduleIds[] = $module->id();
+ }
+ // First, delete all modules of given type from all involved configs
+ Database::exec("DELETE FROM configtgz_x_module
+ WHERE configid IN (:configids)
+ AND moduleid IN (:moduleids)
+ AND moduleid <> :moduleid",
+ ['configids' => $configIds, 'moduleids' => $moduleIds, 'moduleid' => $moduleId]);
+ }
+ // Not delete module from all configs that are not selected
+ Database::exec("DELETE FROM configtgz_x_module WHERE moduleid = :moduleid AND configid NOT IN (:configids)",
+ ['configids' => $configIds, 'moduleid' => $moduleId]);
+
+ // Finally, (re)insert for all configs selected
+ foreach ($configIds as $configId) {
+ Database::exec("INSERT IGNORE INTO configtgz_x_module (configid, moduleid) VALUES (:configid, :moduleid)", array(
+ 'configid' => $configId,
+ 'moduleid' => $moduleId
+ ));
+ $config = ConfigTgz::get($configId);
+ $config->markOutdated();
+ $config->generate();
+ }
+
+ Util::redirect('?do=SysConfig', null, false, true);
+ }
+ }
+
+ protected function renderInternal()
+ {
+ $data = ['configs' => SysConfig::getAll()];
+ if (count($data['configs']) === 0) // Nothing to do
+ Util::redirect('?do=SysConfig', null, false, true);
+
+ $moduleType = $this->edit->moduleType();
+ // If this is a module of type unique, mark all configs that already contain a module of that type
+ if (ConfigModule::getList()[$moduleType]['unique']) {
+ $modules = Database::queryAll("SELECT cm.moduleid as moduleid, cm.title as title, cxm.configid as configid
+ FROM configtgz_module cm
+ INNER JOIN configtgz_x_module cxm ON (cm.moduleid = cxm.moduleid)
+ WHERE cm.moduletype = :moduletype AND cxm.moduleid <> :moduleid",
+ ['moduletype' => $moduleType, 'moduleid' => $this->edit->id()]);
+
+ $modulesByConfigId = [];
+ foreach ($modules as $module) {
+ $modulesByConfigId[$module['configid']] = $module;
+ }
+
+ foreach ($data['configs'] as &$config) {
+ if (!isset($modulesByConfigId[$config['configid']]))
+ continue;
+ $config['replaces'] = $modulesByConfigId[$config['configid']]['title'];
+ }
+ }
+ // Pre-check those that are already included
+ $tgzs = [];
+ foreach (ConfigTgz::getAllForModule($this->edit->id()) as $tgz) {
+ $tgzs[$tgz->id()] = 1;
+ }
+ foreach ($data['configs'] as &$config) {
+ if (!isset($tgzs[$config['configid']]))
+ continue;
+ $config['checked'] = 'checked';
+ }
+
+ $data['edit'] = $this->edit->id();
+ $data['name'] = $this->edit->title();
+ Render::addDialog(Dictionary::translate('lang_moduleAssign'), false, 'assign', $data);
+ }
+
+}
+
/*
* Helper functions to set/get a batch of vars from/to post variables or a module
*/
-/**
- *
- * @param \ConfigModule $module
- * @param array $array
- * @param array $keys
- */
-function moduleToArray($module, &$array, $keys)
+function moduleToArray(ConfigModule $module, array &$array, array $keys): void
{
foreach ($keys as $key) {
$array[$key] = $module->getData($key);
}
}
-/**
- *
- * @param \ConfigModule $module
- * @param array $array
- * @param array $keys
- */
-function arrayToModule($module, $array, $keys)
+function arrayToModule(ConfigModule $module, array $array, array $keys): void
{
foreach ($keys as $key) {
$module->setData($key, $array[$key]);
}
}
-/**
- *
- * @param array $array
- * @param array $keys
- */
-function postToArray(&$array, $keys, $ignoreMissing = false)
+
+function postToArray(array &$array, array $keys, $ignoreMissing = false): void
{
foreach ($keys as $key) {
$val = Request::post($key, '--not-in-post');
- if ($ignoreMissing && $val === '--not-in-post') continue;
+ if ($ignoreMissing && $val === '--not-in-post')
+ continue;
$array[$key] = $val;
}
}
diff --git a/modules-available/sysconfig/addmodule_adauth.inc.php b/modules-available/sysconfig/addmodule_adauth.inc.php
index 80b7cff1..54771487 100644
--- a/modules-available/sysconfig/addmodule_adauth.inc.php
+++ b/modules-available/sysconfig/addmodule_adauth.inc.php
@@ -13,15 +13,14 @@ class AdAuth_Start extends AddModule_Base
protected function renderInternal()
{
- $ADAUTH_COMMON_FIELDS = array('title', 'server', 'searchbase', 'binddn', 'bindpw', 'home', 'homeattr', 'ssl', 'fixnumeric', 'genuid', 'certificate', 'mapping', 'nohomewarn');
+ $ADAUTH_COMMON_FIELDS = array('title', 'server', 'searchbase', 'binddn', 'bindpw', 'home', 'homeattr', 'ssl', 'genuid', 'certificate', 'mapping', 'nohomewarn');
$data = array();
- if ($this->edit !== false) {
+ if ($this->edit !== null) {
moduleToArray($this->edit, $data, $ADAUTH_COMMON_FIELDS);
$data['title'] = $this->edit->title();
$data['edit'] = $this->edit->id();
- }
- if ($data['fixnumeric'] === false) {
- $data['fixnumeric'] = 's';
+ } else {
+ $data['ssl'] = true;
}
postToArray($data, $ADAUTH_COMMON_FIELDS, true);
$obdn = Request::post('originalbinddn');
@@ -36,7 +35,7 @@ class AdAuth_Start extends AddModule_Base
}
$data['step'] = 'AdAuth_CheckConnection';
$data['map_empty'] = true;
- $data['mapping'] = ConfigModuleBaseLdap::getMapping(isset($data['mapping']) ? $data['mapping'] : false, $data['map_empty']);
+ $data['mapping'] = ConfigModuleBaseLdap::getMapping($data['mapping'] ?? null, $data['map_empty']);
Render::addDialog(Dictionary::translateFile('config-module', 'adAuth_title'), false, 'ad-start', $data);
}
@@ -90,13 +89,12 @@ class AdAuth_CheckConnection extends AddModule_Base
));
if (!isset($this->scanTask['id'])) {
AddModule_Base::setStep('AdAuth_Start'); // Continues with AdAuth_Start for render()
- return;
}
}
protected function renderInternal()
{
- $mapping = Request::post('mapping', false, 'array');
+ $mapping = Request::post('mapping', null, 'array');
$data = array(
'edit' => Request::post('edit'),
'title' => Request::post('title'),
@@ -106,7 +104,6 @@ class AdAuth_CheckConnection extends AddModule_Base
'bindpw' => Request::post('bindpw'),
'home' => Request::post('home'),
'ssl' => Request::post('ssl'),
- 'fixnumeric' => Request::post('fixnumeric'),
'genuid' => Request::post('genuid'),
'certificate' => Request::post('certificate', ''),
'taskid' => $this->scanTask['id'],
@@ -133,23 +130,17 @@ class AdAuth_SelfSearch extends AddModule_Base
protected function preprocessInternal()
{
- $server = $binddn = $port = null;
+ $server = $binddn = null;
$searchbase = Request::post('searchbase', '');
$bindpw = Request::post('bindpw');
$ssl = Request::post('ssl', 'off') === 'on';
if ($ssl && !Request::post('fingerprint')) {
- Message::addError('main.error-read', 'fingerprint');
+ Message::addError('main.parameter-empty', 'fingerprint');
AddModule_Base::setStep('AdAuth_Start'); // Continues with AdAuth_Start for render()
return;
}
- foreach (['server', 'binddn', 'port'] as $var) {
- $$var = Request::post($var, null);
- if (empty($$var)) {
- Message::addError('main.parameter-empty', $var);
- AddModule_Base::setStep('AdAuth_Start'); // Continues with AdAuth_Start for render()
- return;
- }
- }
+ $server = Request::post('server', Request::REQUIRED, 'string');
+ $binddn = Request::post('binddn', Request::REQUIRED, 'string');
$this->originalBindDn = '';
// Fix bindDN if short name given
//
@@ -197,7 +188,7 @@ class AdAuth_SelfSearch extends AddModule_Base
protected function renderInternal()
{
- $mapping = Request::post('mapping', false, 'array');
+ $mapping = Request::post('mapping', null, 'array');
$data = array(
'edit' => Request::post('edit'),
'title' => Request::post('title'),
@@ -208,7 +199,6 @@ class AdAuth_SelfSearch extends AddModule_Base
'bindpw' => Request::post('bindpw'),
'home' => Request::post('home'),
'ssl' => Request::post('ssl') === 'on',
- 'fixnumeric' => Request::post('fixnumeric'),
'genuid' => Request::post('genuid'),
'fingerprint' => Request::post('fingerprint'),
'certificate' => Request::post('certificate', ''),
@@ -239,7 +229,7 @@ class AdAuth_HomeAttrCheck extends AddModule_Base
$bindpw = Request::post('bindpw');
$ssl = Request::post('ssl', 'off') === 'on';
if ($ssl && !Request::post('fingerprint')) {
- Message::addError('main.error-read', 'fingerprint');
+ Message::addError('main.parameter-empty', 'fingerprint');
AddModule_Base::setStep('AdAuth_Start'); // Continues with AdAuth_Start for render()
return;
}
@@ -286,13 +276,12 @@ class AdAuth_HomeAttrCheck extends AddModule_Base
'bindpw' => Request::post('bindpw'),
'home' => Request::post('home'),
'ssl' => Request::post('ssl') === 'on',
- 'fixnumeric' => Request::post('fixnumeric'),
'genuid' => Request::post('genuid'),
'fingerprint' => Request::post('fingerprint'),
'certificate' => Request::post('certificate', ''),
'originalbinddn' => Request::post('originalbinddn'),
'tryHomeAttr' => true,
- 'mapping' => ConfigModuleBaseLdap::getMapping(Request::post('mapping', false, 'array')),
+ 'mapping' => ConfigModuleBaseLdap::getMapping(Request::post('mapping', null, 'array')),
'prev' => 'AdAuth_Start',
'next' => 'AdAuth_CheckCredentials'
))
@@ -313,7 +302,7 @@ class AdAuth_CheckCredentials extends AddModule_Base
$bindpw = Request::post('bindpw');
$ssl = Request::post('ssl', 'off') === 'on';
if ($ssl && !Request::post('fingerprint')) {
- Message::addError('main.error-read', 'fingerprint');
+ Message::addError('main.parameter-empty', 'fingerprint');
AddModule_Base::setStep('AdAuth_Start'); // Continues with AdAuth_Start for render()
return;
}
@@ -359,12 +348,11 @@ class AdAuth_CheckCredentials extends AddModule_Base
'home' => Request::post('home'),
'homeattr' => Request::post('homeattr'),
'ssl' => Request::post('ssl') === 'on',
- 'fixnumeric' => Request::post('fixnumeric'),
'genuid' => Request::post('genuid'),
'fingerprint' => Request::post('fingerprint'),
'certificate' => Request::post('certificate', ''),
'originalbinddn' => Request::post('originalbinddn'),
- 'mapping' => ConfigModuleBaseLdap::getMapping(Request::post('mapping', false, 'array')),
+ 'mapping' => ConfigModuleBaseLdap::getMapping(Request::post('mapping', null, 'array')),
'prev' => 'AdAuth_Start',
'next' => 'AdAuth_HomeDir'
))
@@ -424,16 +412,15 @@ class AdAuth_HomeDir extends AddModule_Base
'home' => Request::post('home'),
'homeattr' => Request::post('homeattr'),
'ssl' => Request::post('ssl') === 'on',
- 'fixnumeric' => Request::post('fixnumeric'),
'genuid' => Request::post('genuid'),
'fingerprint' => Request::post('fingerprint'),
'certificate' => Request::post('certificate', ''),
'originalbinddn' => Request::post('originalbinddn'),
- 'mapping' => ConfigModuleBaseLdap::getMapping(Request::post('mapping', false, 'array')),
+ 'mapping' => ConfigModuleBaseLdap::getMapping(Request::post('mapping', null, 'array')),
'prev' => 'AdAuth_Start',
'next' => 'AdAuth_Finish'
);
- if ($this->edit !== false) {
+ if ($this->edit !== null) {
foreach (self::getAttributes() as $key) {
if ($this->edit->getData($key)) {
$data[$key . '_c'] = 'checked="checked"';
@@ -454,13 +441,13 @@ class AdAuth_HomeDir extends AddModule_Base
foreach (range('D', 'Z') as $l) {
$data['drives'][] = array(
'drive' => $l . ':',
- 'selected' => (strtoupper($letter{0}) === $l) ? 'selected="selected"' : ''
+ 'selected' => (strtoupper($letter[0]) === $l) ? 'selected="selected"' : ''
);
}
Render::addDialog(Dictionary::translateFile('config-module', 'adAuth_title'), false, 'ad_ldap-homedir', $data);
}
- public static function getAttributes()
+ public static function getAttributes(): array
{
return array('shareRemapMode', 'shareRemapCreate', 'shareDocuments', 'shareDownloads', 'shareDesktop',
'shareMedia', 'shareOther', 'shareHomeDrive', 'shareDomain', 'credentialPassthrough');
@@ -478,12 +465,13 @@ class AdAuth_Finish extends AddModule_Base
$title = Request::post('title');
if (empty($title))
$title = 'AD: ' . Request::post('server');
- if ($this->edit === false)
+ if ($this->edit === null) {
$module = ConfigModule::getInstance('AdAuth');
- else
+ } else {
$module = $this->edit;
+ }
$ssl = Request::post('ssl', 'off') === 'on';
- foreach (['searchbase', 'binddn', 'server', 'bindpw', 'home', 'nohomewarn', 'homeattr', 'certificate', 'fixnumeric', 'genuid',
+ foreach (['searchbase', 'binddn', 'server', 'bindpw', 'home', 'nohomewarn', 'homeattr', 'certificate', 'genuid',
'ldapAttrMountOpts', 'shareHomeMountOpts'] as $key) {
$module->setData($key, Request::post($key, '', 'string'));
}
@@ -505,7 +493,7 @@ class AdAuth_Finish extends AddModule_Base
} else {
$module->setData('fingerprint', '');
}
- if ($this->edit !== false)
+ if ($this->edit !== null)
$ret = $module->update($title);
else
$ret = $module->insert($title);
@@ -513,7 +501,7 @@ class AdAuth_Finish extends AddModule_Base
Message::addError('main.value-invalid', 'any', 'any');
$tgz = false;
} else {
- $tgz = $module->generate($this->edit === false);
+ $tgz = $module->generate($this->edit === null);
}
if ($tgz === false) {
AddModule_Base::setStep('AdAuth_Start'); // Continues with AdAuth_Start for render()
@@ -522,6 +510,11 @@ class AdAuth_Finish extends AddModule_Base
$this->taskIds = array(
'tm-config' => $tgz,
);
+
+ if ($this->edit === null) {
+ AddModule_Base::setStep('AddModule_Assign', $module->id());
+ }
+
}
protected function renderInternal()
diff --git a/modules-available/sysconfig/addmodule_branding.inc.php b/modules-available/sysconfig/addmodule_branding.inc.php
index 6e628926..4f35c83b 100644
--- a/modules-available/sysconfig/addmodule_branding.inc.php
+++ b/modules-available/sysconfig/addmodule_branding.inc.php
@@ -11,7 +11,7 @@ class Branding_Start extends AddModule_Base
{
Render::addDialog(Dictionary::translateFile('config-module', 'branding_title'), false, 'branding-start', array(
'step' => 'Branding_ProcessFile',
- 'edit' => $this->edit ? $this->edit->id() : false
+ 'edit' => $this->edit == null ? null : $this->edit->id(),
));
}
@@ -22,14 +22,13 @@ class Branding_ProcessFile extends AddModule_Base
private $task;
private $svgFile;
- private $tarFile;
protected function preprocessInternal()
{
$url = Request::post('url');
if ((!isset($_FILES['file']['error']) || $_FILES['file']['error'] === UPLOAD_ERR_NO_FILE) && empty($url)) {
Message::addError('main.empty-field');
- Util::redirect('?do=SysConfig&action=addmodule&step=Branding_Start');
+ Util::redirect('?do=SysConfig&action=addmodule&step=Branding_Start', 400);
}
$this->svgFile = tempnam(sys_get_temp_dir(), 'bwlp-');
@@ -37,35 +36,36 @@ class Branding_ProcessFile extends AddModule_Base
// Prefer uploaded image over URL (in case both are given)
if ($_FILES['file']['error'] !== UPLOAD_ERR_OK) {
Message::addError('upload-failed', Util::uploadErrorString($_FILES['file']['error']));
- Util::redirect('?do=SysConfig&action=addmodule&step=Branding_Start');
+ Util::redirect('?do=SysConfig&action=addmodule&step=Branding_Start', 400);
}
if (!move_uploaded_file($_FILES["file"]["tmp_name"], $this->svgFile)) {
Message::addError('upload-failed', 'Moving temp file failed');
- Util::redirect('?do=SysConfig&action=addmodule&step=Branding_Start');
+ Util::redirect('?do=SysConfig&action=addmodule&step=Branding_Start', 500);
}
} else {
// URL - launch task that fetches the SVG file from it
if (strpos($url, '://') === false)
$url = "http://$url";
$title = false;
- if (!$this->downloadSvg($this->svgFile, $url, $title))
+ if (!Branding_ProcessFile::downloadSvg($this->svgFile, $url, $title)) {
+ @unlink($this->svgFile);
Util::redirect('?do=SysConfig&action=addmodule&step=Branding_Start');
+ }
Session::set('logo_name', $title);
}
chmod($this->svgFile, 0644);
- $this->tarFile = '/tmp/bwlp-' . time() . '-' . mt_rand() . '.tgz';
+ $tarFile = '/tmp/bwlp-' . time() . '-' . mt_rand() . '.tgz';
$this->task = Taskmanager::submit('BrandingGenerator', array(
- 'tarFile' => $this->tarFile,
+ 'tarFile' => $tarFile,
'svgFile' => $this->svgFile
));
$this->task = Taskmanager::waitComplete($this->task, 5000);
if (Taskmanager::isFailed($this->task)) {
@unlink($this->svgFile);
Taskmanager::addErrorMessage($this->task);
- Util::redirect('?do=SysConfig&action=addmodule&step=Branding_Start');
+ Util::redirect('?do=SysConfig&action=addmodule&step=Branding_Start', 500);
}
- Session::set('logo_tgz', $this->tarFile);
- Session::save();
+ Session::set('logo_tgz', $tarFile);
}
protected function renderInternal()
@@ -75,13 +75,13 @@ class Branding_ProcessFile extends AddModule_Base
$png = base64_encode(file_get_contents($this->task['data']['pngFile']));
if (filesize($this->svgFile) < 1000000)
$svg = base64_encode(file_get_contents($this->svgFile));
- Render::addDialog(Dictionary::translate('config-module', 'branding_title'), false, 'branding-check', array(
+ Render::addDialog(Dictionary::translateFile('config-module', 'branding_title'), false, 'branding-check', array(
'png' => $png,
'svg' => $svg,
- 'error' => $this->task['data']['error'],
+ 'error' => $this->task['data']['error'] ?? $this->task['statusCode'],
'step' => 'Branding_Finish',
- 'edit' => $this->edit ? $this->edit->id() : false,
- 'title' => $this->edit ? $this->edit->title() : false
+ 'edit' => $this->edit === null ? null : $this->edit->id(),
+ 'title' => $this->edit === null ? null : $this->edit->title(),
)
);
@unlink($this->svgFile);
@@ -93,42 +93,59 @@ class Branding_ProcessFile extends AddModule_Base
*
* @param string $svgName file to download to
* @param string $url url to download from
- * @return boolean true of download succeded, false on download error (also returns true if downloaded file doesn't
+ * @return boolean true of download succeeded, false on download error (also returns true if downloaded file doesn't
* seem to be svg!)
*/
- private function downloadSvg($svgName, $url, &$title)
+ private static function downloadSvg(string $svgName, string $url, &$title): bool
{
$title = false;
- // [wikipedia] Did someone paste a link to a thumbnail of the svg? Let's fix that...
- if (preg_match('#^(.*)/thumb/(.*\.svg)/.*\.svg#', $url, $out)) {
- $url = $out[1] . '/' . $out[2];
- }
for ($i = 0; $i < 5; ++$i) {
+ // [wikipedia] Did someone paste a link to a thumbnail of the svg? Let's fix that...
+ if (preg_match('#^(.*)/thumb/(.*\.svg)/.*\.svg#', $url, $out)) {
+ $url = $out[1] . '/' . $out[2];
+ }
$code = 400;
if (!Download::toFile($svgName, $url, 3, $code) || $code < 200 || $code > 299) {
Message::addError('remote-timeout', $url, $code);
return false;
}
- $content = FileUtil::readFile($svgName, 25000);
+ $content = FileUtil::readFile($svgName, 250000);
// Is svg file?
if (strpos($content, '<svg') !== false)
return true; // Found an svg tag - don't try to find links to the actual image
// [wikipedia] Try to be nice and detect links that might give a hint where the svg can be found
- if (preg_match_all('#href="([^"]*upload.wikimedia.org/[^"]*/[^"]*/[^"]*\.svg|[^"]+/[^"]+:[^"]+\.svg[^"]*)"#', $content, $out, PREG_PATTERN_ORDER)) {
+ $out1 = $out2 = $out3 = null;
+ if (preg_match_all('#href="([^"]*upload.wikimedia.org/[^"]*/[^"]*/[^"]*\.svg)"#', $content, $out1, PREG_PATTERN_ORDER)
+ || preg_match_all('#src="([^"]*upload.wikimedia.org/[^"]*/thumb/[^"]*\.svg/[^"]+\.svg[^"]*)"#', $content, $out2, PREG_PATTERN_ORDER)
+ || preg_match_all('#href="([^"]+/[^"]+:[^"]+\.svg)"#', $content, $out3, PREG_PATTERN_ORDER)) {
if ($title === false && preg_match('#<title>([^<]*)</title>#i', $content, $tout)) {
$title = trim(preg_replace('/\W*Wikipedia.*/', '', $tout[1]));
}
$new = false;
- foreach ($out[1] as $res) {
+ $out = [];
+ if (isset($out1[1])) {
+ $out += $out1[1];
+ }
+ if (isset($out2[1])) {
+ $out += $out2[1];
+ }
+ if (isset($out3[1])) {
+ $out += $out3[1];
+ }
+ foreach ($out as $res) {
+ error_log("Match '$res'");
+ if (!preg_match('/hochschule|univers|logo|siegel/i', $res))
+ continue;
if (strpos($res, 'action=edit') !== false)
continue;
- $new = $this->internetCombineUrl($url, html_entity_decode($res, ENT_COMPAT, 'UTF-8'));
+ $new = Branding_ProcessFile::internetCombineUrl($url, html_entity_decode($res, ENT_COMPAT, 'UTF-8'));
if ($new !== $url)
break;
}
if ($new === $url || $new === false)
break;
+ error_log("New: '$new'");
$url = $new;
continue;
}
@@ -145,7 +162,7 @@ class Branding_ProcessFile extends AddModule_Base
* @param string $relative relative url that will be converted to an absolute url
* @return string combined absolute url
*/
- private function internetCombineUrl($absolute, $relative)
+ private static function internetCombineUrl(string $absolute, string $relative): string
{
$p = parse_url($relative);
if (!empty($p["scheme"]))
@@ -154,8 +171,8 @@ class Branding_ProcessFile extends AddModule_Base
$parsed = parse_url($absolute);
$path = dirname($parsed['path']);
- if ($relative{0} === '/') {
- if ($relative{1} === '/')
+ if ($relative[0] === '/') {
+ if ($relative[1] === '/')
return "{$parsed['scheme']}:$relative";
$cparts = array_filter(explode("/", $relative));
} else {
@@ -196,10 +213,11 @@ class Branding_Finish extends AddModule_Base
protected function preprocessInternal()
{
- $title = Request::post('title');
- if ($title === false || empty($title))
+ $title = Request::post('title', '', 'string');
+ if (empty($title)) {
$title = Session::get('logo_name');
- if ($title === false || empty($title)) {
+ }
+ if (empty($title)) {
Message::addError('missing-title'); // TODO: Ask for title again instead of starting over
Util::redirect('?do=SysConfig&action=addmodule&step=Branding_Start');
}
@@ -208,31 +226,30 @@ class Branding_Finish extends AddModule_Base
Message::addError('main.error-read', $tgz);
Util::redirect('?do=SysConfig&action=addmodule&step=Branding_Start');
}
- if ($this->edit === false)
+ if ($this->edit === null) {
$module = ConfigModule::getInstance('Branding');
- else
+ } else {
$module = $this->edit;
- if ($module === false) {
- Message::addError('main.error-read', 'branding.inc.php');
- Util::redirect('?do=SysConfig&action=addmodule&step=Branding_Start');
}
$module->setData('tmpFile', $tgz);
- if ($this->edit !== false)
+ if ($this->edit !== null)
$ret = $module->update($title);
else
$ret = $module->insert($title);
if (!$ret)
- Util::redirect('?do=SysConfig&action=addmodule&step=Branding_Start');
- elseif ($module->generate($this->edit === false, NULL, 200) === false)
- Util::redirect('?do=SysConfig&action=addmodule&step=Branding_Start');
+ Util::redirect('?do=SysConfig&action=addmodule&step=Branding_Start', 500);
+ elseif ($module->generate($this->edit === null, NULL, 200) === false)
+ Util::redirect('?do=SysConfig&action=addmodule&step=Branding_Start', 500);
Session::set('logo_tgz', false);
Session::set('logo_name', false);
- Session::save();
// Yay
- if ($this->edit !== false)
+ if ($this->edit !== null) {
Message::addSuccess('module-edited');
- else
+ } else {
Message::addSuccess('module-added');
+ AddModule_Base::setStep('AddModule_Assign', $module->id());
+ return;
+ }
Util::redirect('?do=SysConfig');
}
diff --git a/modules-available/sysconfig/addmodule_custommodule.inc.php b/modules-available/sysconfig/addmodule_custommodule.inc.php
index c234f765..bd94ebe9 100644
--- a/modules-available/sysconfig/addmodule_custommodule.inc.php
+++ b/modules-available/sysconfig/addmodule_custommodule.inc.php
@@ -14,7 +14,7 @@ class CustomModule_Start extends AddModule_Base
Session::set('mod_temp', false);
Render::addDialog(Dictionary::translateFile('config-module', 'custom_title'), false, 'custom-upload', array(
'step' => 'CustomModule_ProcessUpload',
- 'edit' => $this->edit ? $this->edit->id() : false
+ 'edit' => $this->edit === null ? null : $this->edit->id(),
));
}
@@ -34,11 +34,11 @@ class CustomModule_ProcessUpload extends AddModule_Base
{
if (!isset($_FILES['modulefile'])) {
Message::addError('missing-file');
- Util::redirect('?do=SysConfig');
+ Util::redirect('?do=SysConfig', 400);
}
if ($_FILES['modulefile']['error'] != UPLOAD_ERR_OK) {
Message::addError('upload-failed', Util::uploadErrorString($_FILES['modulefile']['error']));
- Util::redirect('?do=SysConfig');
+ Util::redirect('?do=SysConfig', 400);
}
$tempfile = '/tmp/bwlp-' . mt_rand(1, 100000) . '-' . crc32($_SERVER['REMOTE_ADDR']) . '.tmp';
if (!move_uploaded_file($_FILES['modulefile']['tmp_name'], $tempfile)) {
@@ -55,8 +55,9 @@ class CustomModule_ProcessUpload extends AddModule_Base
protected function renderInternal()
{
- $status = Taskmanager::waitComplete($this->taskId);
+ $status = Taskmanager::waitComplete($this->taskId, 7500);
Taskmanager::release($this->taskId);
+ $userGroupWarn = false;
$tempfile = Session::get('mod_temp');
if (!isset($status['statusCode'])) {
unlink($tempfile);
@@ -66,64 +67,51 @@ class CustomModule_ProcessUpload extends AddModule_Base
unlink($tempfile);
$this->taskError($status);
}
- // Sort files for better display
- $dirs = array();
- foreach ($status['data']['entries'] as $file) {
- if ($file['isdir']) continue;
- $dirs[dirname($file['name'])][] = $file;
- }
- ksort($dirs);
- $list = array();
- foreach ($dirs as $dir => $files) {
- $list[] = array(
- 'name' => $dir,
- 'isdir' => true
- );
- sort($files);
- foreach ($files as $file) {
- $file['size'] = Util::readableFileSize($file['size']);
- $list[] = $file;
- }
- }
- if ($this->edit !== false)
+ $list = SysConfig::archiveContentsFromTask($status, $userGroupWarn);
+
+ if ($this->edit !== null) {
$title = $this->edit->title();
- elseif (isset($_FILES['modulefile']['name']))
+ } else if (isset($_FILES['modulefile']['name'])) {
$title = basename($_FILES['modulefile']['name']);
- else
+ } else {
$title = '';
- Render::addDialog(Dictionary::translate('config-module', 'custom_title'), false, 'custom-fileselect', array(
+ }
+ Render::addDialog(Dictionary::translateFile('config-module', 'custom_title'), false, 'custom-fileselect', array(
'step' => 'CustomModule_CompressModule',
'files' => $list,
- 'edit' => $this->edit ? $this->edit->id() : false,
- 'title' => $title
+ 'edit' => $this->edit === null ? null : $this->edit->id(),
+ 'title' => $title,
+ 'userGroupWarn' => $userGroupWarn,
));
- Session::save();
}
}
class CustomModule_CompressModule extends AddModule_Base
{
-
- private $taskId = false;
-
+
protected function preprocessInternal()
{
- $title = Request::post('title');
+ $title = Request::post('title', Request::REQUIRED, 'string');
$tempfile = Session::get('mod_temp');
- if (empty($title) || empty($tempfile) || !file_exists($tempfile)) {
+ if (empty($tempfile)) {
Message::addError('main.empty-field');
- Util::redirect('?do=SysConfig&action=addmodule&step=CustomModule_Start');
+ Util::redirect('?do=SysConfig&action=addmodule&step=CustomModule_Start', 400);
+ }
+ if (!file_exists($tempfile)) {
+ Message::addError('main.error-read', $tempfile);
+ Util::redirect('?do=SysConfig&action=addmodule&step=CustomModule_Start', 500);
}
// Recompress using task manager
- $this->taskId = 'tgzmod' . mt_rand() . '-' . microtime(true);
+ $taskId = 'tgzmod' . mt_rand() . '-' . microtime(true);
$destFile = tempnam(sys_get_temp_dir(), 'bwlp-') . '.tgz';
Taskmanager::submit('RecompressArchive', array(
- 'id' => $this->taskId,
- 'inputFiles' => array($tempfile),
- 'outputFile' => $destFile
+ 'id' => $taskId,
+ 'inputFiles' => [$tempfile => false],
+ 'outputFile' => $destFile,
+ 'forceRoot' => Request::post('force-owner', 0, 'int') !== 0,
), true);
- $status = Taskmanager::waitComplete($this->taskId, 5000);
+ $status = Taskmanager::waitComplete($taskId, 10000);
unlink($tempfile);
if (!isset($status['statusCode'])) {
$this->tmError();
@@ -132,30 +120,32 @@ class CustomModule_CompressModule extends AddModule_Base
$this->taskError($status);
}
// Seems ok, create entry
- if ($this->edit === false)
+ if ($this->edit === null) {
$module = ConfigModule::getInstance('CustomModule');
- else
+ } else {
$module = $this->edit;
- if ($module === false) {
- Message::addError('main.error-read', 'custommodule.inc.php');
- Util::redirect('?do=SysConfig&action=addmodule&step=CustomModule_Start');
}
$module->setData('tmpFile', $destFile);
- if ($this->edit !== false)
+ if ($this->edit !== null) {
$ret = $module->update($title);
- else
+ } else {
$ret = $module->insert($title);
- if (!$ret)
- Util::redirect('?do=SysConfig&action=addmodule&step=CustomModule_Start');
- elseif (!$module->generate($this->edit === false, NULL, 200))
- Util::redirect('?do=SysConfig&action=addmodule&step=CustomModule_Start');
+ }
+ if (!$ret) {
+ Util::redirect('?do=SysConfig&action=addmodule&step=CustomModule_Start', 500);
+ } elseif (!$module->generate($this->edit === null, NULL, 200)) {
+ Util::redirect('?do=SysConfig&action=addmodule&step=CustomModule_Start', 500);
+ }
Session::set('mod_temp', false);
- Session::save();
// Yay
- if ($this->edit !== false)
+ Audit::overrideResponseCode(200);
+ if ($this->edit !== null) {
Message::addSuccess('module-edited');
- else
+ } else {
Message::addSuccess('module-added');
+ AddModule_Base::setStep('AddModule_Assign', $module->id());
+ return;
+ }
Util::redirect('?do=SysConfig');
}
diff --git a/modules-available/sysconfig/addmodule_ldapauth.inc.php b/modules-available/sysconfig/addmodule_ldapauth.inc.php
index 6d612e9e..c40452c7 100644
--- a/modules-available/sysconfig/addmodule_ldapauth.inc.php
+++ b/modules-available/sysconfig/addmodule_ldapauth.inc.php
@@ -9,15 +9,14 @@ class LdapAuth_Start extends AddModule_Base
protected function renderInternal()
{
- $LDAPAUTH_COMMON_FIELDS = array('title', 'server', 'searchbase', 'binddn', 'bindpw', 'home', 'homeattr', 'ssl', 'fixnumeric', 'genuid', 'certificate', 'mapping', 'nohomewarn');
+ $LDAPAUTH_COMMON_FIELDS = array('title', 'server', 'searchbase', 'binddn', 'bindpw', 'home', 'homeattr', 'ssl', 'genuid', 'certificate', 'mapping', 'nohomewarn');
$data = array();
- if ($this->edit !== false) {
+ if ($this->edit !== null) {
moduleToArray($this->edit, $data, $LDAPAUTH_COMMON_FIELDS);
$data['title'] = $this->edit->title();
$data['edit'] = $this->edit->id();
- }
- if (!isset($data['fixnumeric']) || $data['fixnumeric'] === false) {
- $data['fixnumeric'] = 's';
+ } else {
+ $data['ssl'] = true;
}
postToArray($data, $LDAPAUTH_COMMON_FIELDS, true);
if (isset($data['server']) && preg_match('/^(.*)\:(636|389)$/', $data['server'], $out)) {
@@ -28,7 +27,7 @@ class LdapAuth_Start extends AddModule_Base
}
$data['step'] = 'LdapAuth_CheckConnection';
$data['map_empty'] = true;
- $data['mapping'] = ConfigModuleBaseLdap::getMapping(isset($data['mapping']) ? $data['mapping'] : false, $data['map_empty']);
+ $data['mapping'] = ConfigModuleBaseLdap::getMapping($data['mapping'] ?? null, $data['map_empty']);
Render::addDialog(Dictionary::translateFile('config-module', 'ldapAuth_title'), false, 'ldap-start', $data);
}
@@ -65,7 +64,6 @@ class LdapAuth_CheckConnection extends AddModule_Base
));
if (!isset($this->scanTask['id'])) {
AddModule_Base::setStep('LdapAuth_Start'); // Continues with LdapAuth_Start for render()
- return;
}
}
@@ -80,11 +78,10 @@ class LdapAuth_CheckConnection extends AddModule_Base
'bindpw' => Request::post('bindpw'),
'home' => Request::post('home'),
'ssl' => Request::post('ssl'),
- 'fixnumeric' => Request::post('fixnumeric'),
'genuid' => Request::post('genuid'),
'certificate' => Request::post('certificate', ''),
'taskid' => $this->scanTask['id'],
- 'mapping' => ConfigModuleBaseLdap::getMapping(Request::post('mapping', false, 'array')),
+ 'mapping' => ConfigModuleBaseLdap::getMapping(Request::post('mapping', null, 'array')),
);
$data['prev'] = 'LdapAuth_Start';
$data['next'] = 'LdapAuth_CheckCredentials';
@@ -107,7 +104,7 @@ class LdapAuth_CheckCredentials extends AddModule_Base
$bindpw = Request::post('bindpw');
$ssl = Request::post('ssl', 'off') === 'on';
if ($ssl && !Request::post('fingerprint')) {
- Message::addError('main.error-read', 'fingerprint');
+ Message::addError('main.parameter-empty', 'fingerprint');
AddModule_Base::setStep('LdapAuth_Start'); // Continues with LdapAuth_Start for render()
return;
}
@@ -152,11 +149,10 @@ class LdapAuth_CheckCredentials extends AddModule_Base
'bindpw' => Request::post('bindpw'),
'home' => Request::post('home'),
'ssl' => Request::post('ssl') === 'on',
- 'fixnumeric' => Request::post('fixnumeric'),
'genuid' => Request::post('genuid'),
'fingerprint' => Request::post('fingerprint'),
'certificate' => Request::post('certificate', ''),
- 'mapping' => ConfigModuleBaseLdap::getMapping(Request::post('mapping', false, 'array')),
+ 'mapping' => ConfigModuleBaseLdap::getMapping(Request::post('mapping', null, 'array')),
'prev' => 'LdapAuth_Start',
'next' => 'LdapAuth_HomeDir',
))
@@ -193,16 +189,15 @@ class LdapAuth_HomeDir extends AddModule_Base
'bindpw' => Request::post('bindpw'),
'home' => Request::post('home'),
'ssl' => Request::post('ssl') === 'on',
- 'fixnumeric' => Request::post('fixnumeric'),
'genuid' => Request::post('genuid'),
'fingerprint' => Request::post('fingerprint'),
'certificate' => Request::post('certificate', ''),
'originalbinddn' => Request::post('originalbinddn'),
- 'mapping' => ConfigModuleBaseLdap::getMapping(Request::post('mapping', false, 'array')),
+ 'mapping' => ConfigModuleBaseLdap::getMapping(Request::post('mapping', null, 'array')),
'prev' => 'LdapAuth_Start',
'next' => 'LdapAuth_Finish',
);
- if ($this->edit !== false) {
+ if ($this->edit !== null) {
foreach (self::getAttributes() as $key) {
if ($this->edit->getData($key)) {
$data[$key . '_c'] = 'checked="checked"';
@@ -223,13 +218,13 @@ class LdapAuth_HomeDir extends AddModule_Base
foreach (range('D', 'Z') as $l) {
$data['drives'][] = array(
'drive' => $l . ':',
- 'selected' => (strtoupper($letter{0}) === $l) ? 'selected="selected"' : ''
+ 'selected' => (strtoupper($letter[0]) === $l) ? 'selected="selected"' : ''
);
}
Render::addDialog(Dictionary::translateFile('config-module', 'ldapAuth_title'), false, 'ad_ldap-homedir', $data);
}
- public static function getAttributes()
+ public static function getAttributes(): array
{
return array('shareRemapMode', 'shareRemapCreate', 'shareDocuments', 'shareDownloads', 'shareDesktop',
'shareMedia', 'shareOther', 'shareHomeDrive', 'shareDomain', 'credentialPassthrough');
@@ -247,12 +242,13 @@ class LdapAuth_Finish extends AddModule_Base
$title = Request::post('title');
if (empty($title))
$title = 'LDAP: ' . Request::post('server');
- if ($this->edit === false)
+ if ($this->edit === null) {
$module = ConfigModule::getInstance('LdapAuth');
- else
+ } else {
$module = $this->edit;
+ }
$ssl = Request::post('ssl', 'off') === 'on';
- foreach (['searchbase', 'binddn', 'server', 'bindpw', 'home', 'nohomewarn', 'certificate', 'fixnumeric', 'genuid',
+ foreach (['searchbase', 'binddn', 'server', 'bindpw', 'home', 'nohomewarn', 'certificate', 'genuid',
'ldapAttrMountOpts', 'shareHomeMountOpts'] as $key) {
$module->setData($key, Request::post($key, '', 'string'));
}
@@ -274,15 +270,16 @@ class LdapAuth_Finish extends AddModule_Base
} else {
$module->setData('fingerprint', '');
}
- if ($this->edit !== false)
+ if ($this->edit !== null) {
$ret = $module->update($title);
- else
+ } else {
$ret = $module->insert($title);
+ }
if (!$ret) {
Message::addError('main.value-invalid', 'any', 'any');
$tgz = false;
} else {
- $tgz = $module->generate($this->edit === false);
+ $tgz = $module->generate($this->edit === null);
}
if ($tgz === false) {
AddModule_Base::setStep('LdapAuth_Start'); // Continues with LdapAuth_Start for render()
@@ -291,6 +288,10 @@ class LdapAuth_Finish extends AddModule_Base
$this->taskIds = array(
'tm-config' => $tgz,
);
+
+ if ($this->edit === null) {
+ AddModule_Base::setStep('AddModule_Assign', $module->id());
+ }
}
protected function renderInternal()
diff --git a/modules-available/sysconfig/addmodule_loginscreen.inc.php b/modules-available/sysconfig/addmodule_loginscreen.inc.php
new file mode 100644
index 00000000..5b4305f7
--- /dev/null
+++ b/modules-available/sysconfig/addmodule_loginscreen.inc.php
@@ -0,0 +1,143 @@
+<?php
+
+/*
+ * Wizard for configuring login screen (lightdm greeter)
+ */
+
+const SCRN_SESSION_DATA = 'lgnscrndata';
+
+const FIELDS = [
+ 'clock-text-style' => ['default' => 'border:none; color:#fff; font-size:14pt; qproperty-alignment:AlignRight'],
+ 'clock-background-style' => ['default' => 'border:none; background:#888687'],
+ 'clock-shadow' => ['default' => '1 1 #555 1', 'regex' => '\s*([0-9-]+(\s+[0-9-]+(\s+[#0-9a-zA-Z]+(\s+[0-9-]+)?)?)?)?\s*'],
+ 'username-placeholder' => ['default' => 'user id'],
+ 'password-placeholder' => ['default' => 'password'],
+ 'shib-session-button-text' => ['default' => 'Shibboleth-Login'],
+ 'qr-session-button-text' => ['default' => 'QR-Code Login'],
+ 'user-session-button-text' => ['default' => 'Campus-Login'],
+ 'guest-session-button-text' => ['default' => 'Gastsitzung'],
+ 'guest-session-start-text' => ['default' => 'Gast-Sitzungen sind möglicherweise auf einzelne' .
+ ' (z.B. ausschließlich interne) Webseiten beschränkt.'],
+ 'guest-session-start-button-text' => ['default' => 'Starten einer Gastsitzung'],
+ 'reset-timeout' => ['default' => 30, 'regex' => '[0-9]*'],
+];
+
+/* For translation scanner
+ Dictionary::translate('ls_clock-text-style'),
+ Dictionary::translate('ls_clock-background-style'),
+ Dictionary::translate('ls_clock-shadow'),
+ Dictionary::translate('ls_username-placeholder'),
+ Dictionary::translate('ls_password-placeholder'),
+ Dictionary::translate('ls_shib-session-button-text'),
+ Dictionary::translate('ls_qr-session-button-text'),
+ Dictionary::translate('ls_user-session-button-text'),
+ Dictionary::translate('ls_guest-session-button-text'),
+ Dictionary::translate('ls_guest-session-start-text'),
+ Dictionary::translate('ls_guest-session-start-button-text'),
+ Dictionary::translate('ls_reset-timeout'),
+*/
+
+class LoginScreen_Start extends AddModule_Base
+{
+
+ protected function renderInternal()
+ {
+ /* Load or initialise session data */
+ if (Request::get('back', 'false', 'string') !== 'false') {
+ /* If coming via the back button, load the session data */
+ $session_data = Session::get(SCRN_SESSION_DATA);
+ if (!is_array($session_data)) {
+ $session_data = [];
+ }
+ } elseif ($this->edit !== null) {
+ $session_data = $this->edit->getData(null);
+ Session::set(SCRN_SESSION_DATA, $session_data);
+ } else {
+ $session_data = [];
+ Session::set(SCRN_SESSION_DATA, false);
+ }
+ if (empty($session_data)) {
+ $session_data = [];
+ foreach (FIELDS as $key => $value) {
+ $session_data[$key] = $value['default'];
+ }
+ }
+ $data = [];
+ foreach (FIELDS as $key => $opts) {
+ $data[] = [
+ 'field' => $key,
+ 'value' => $session_data[$key] ?? '',
+ 'caption' => Dictionary::translate('ls_' . $key),
+ 'regex' => $opts['regex'] ?? null,
+ ];
+ }
+ Render::addDialog(Dictionary::translateFile('config-module', 'loginscreen_title'),
+ false, 'loginscreen-start', [
+ 'next' => 'LoginScreen_Finish',
+ 'edit' => $this->edit !== null ? $this->edit->id() : 0,
+ 'fields' => $data,
+ 'title' => $this->edit !== null ? $this->edit->title() : '',
+ ]);
+ }
+
+}
+
+class LoginScreen_Finish extends AddModule_Base
+{
+
+ protected function preprocessInternal()
+ {
+ $title = trim(Request::post('title', '', 'string'));
+ if (empty($title)) {
+ Util::redirect('?do=sysconfig&action=addmodule&step=LoginScreen_Start&back=true');
+ }
+ $session_data = Session::get(SCRN_SESSION_DATA);
+ if (!is_array($session_data)) {
+ $session_data = [];
+ }
+ $session_data['title'] = $title;
+
+ /* Only create an instance if it's a new one */
+ if ($this->edit !== null) {
+ $module = $this->edit;
+ } else {
+ $module = ConfigModule::getInstance('LoginScreen');
+ }
+
+ $err = false;
+ foreach (FIELDS as $key => $field) {
+ $val = Request::post($key, '', 'string');
+ if (isset($field['regex']) && !preg_match("/{$field['regex']}/", $val)) {
+ Message::addError('regex-mismatch', $val, $field['regex']);
+ $err = true;
+ }
+ $session_data[$key] = $val;
+ $module->setData($key, $val);
+ }
+ if ($err) {
+ Session::set(SCRN_SESSION_DATA, $session_data);
+ Util::redirect('?do=sysconfig&action=addmodule&step=LoginScreen_Start&back=true');
+ }
+
+ /* Insert or update database entries */
+ if ($this->edit !== null) {
+ $module->update($session_data['title']);
+ } else {
+ $module->insert($session_data['title']);
+ }
+
+ $task = $module->generate($this->edit === null);
+
+ // Yay
+ Session::set(SCRN_SESSION_DATA, false);
+ if ($task !== false && $this->edit !== null) {
+ Message::addSuccess('module-edited');
+ } elseif ($task !== false) {
+ Message::addSuccess('module-added');
+ AddModule_Base::setStep('AddModule_Assign', $module->id());
+ return;
+ }
+ Util::redirect('?do=SysConfig');
+ }
+
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/addmodule_screensaver.inc.php b/modules-available/sysconfig/addmodule_screensaver.inc.php
new file mode 100644
index 00000000..43024592
--- /dev/null
+++ b/modules-available/sysconfig/addmodule_screensaver.inc.php
@@ -0,0 +1,252 @@
+<?php
+
+/*
+ * Wizard for configuring the xscreensaver (client side).
+ */
+
+class Screensaver_Start extends AddModule_Base
+{
+ private $session_data;
+
+ protected function preprocessInternal()
+ {
+ /* Load or initialise session data */
+ if (Request::get('back', 'false', 'string') !== 'false') {
+ /* If coming via the back button, load the session data */
+ $this->session_data = Session::get(Screensaver_Helper::SESSION_KEY);
+ } elseif ($this->edit !== null) {
+ $this->session_data = array(
+ 'title' => $this->edit->title(),
+ 'qss' => $this->edit->getData('qss'),
+ 'messages' => $this->edit->getData('messages'),
+ 'texts' => $this->edit->getData('texts'),
+ );
+ } else {
+ $this->session_data = array(
+ 'title' => '',
+ 'qss' => Dictionary::translate('saver_QssDefault'),
+ 'messages' => array(
+ 'General' => array(
+ 'shutdown' => Dictionary::translate('saver_MessageDefaultShutdown'),
+ 'shutdown-locked' => Dictionary::translate('saver_MessageDefaultShutdownLocked'),
+ 'idle-kill' => Dictionary::translate('saver_MessageDefaultIdleKill'),
+ 'idle-kill-locked' => Dictionary::translate('saver_MessageDefaultIdleKillLocked'),
+ 'no-timeout' => Dictionary::translate('saver_MessageDefaultNoTimeout'),
+ 'no-timeout-locked' => Dictionary::translate('saver_MessageDefaultNoTimeoutLocked'),
+ )
+ ),
+ 'texts' => array(
+ 'text-shutdown' => Dictionary::translate('saver_TextDefaultShutdown'),
+ 'text-shutdown-locked' => '',
+ 'text-idle-kill' => Dictionary::translate('saver_TextDefaultIdleKill'),
+ 'text-idle-kill-locked' => Dictionary::translate('saver_TextDefaultIdleKillLocked'),
+ 'text-no-timeout' => '',
+ 'text-no-timeout-locked' => '',
+ ),
+ );
+ }
+ $this->session_data['next'] = 'idle-kill';
+ Session::set(Screensaver_Helper::SESSION_KEY, $this->session_data);
+ }
+
+ protected function renderInternal()
+ {
+ /* Load summernote module if available */
+ Module::isAvailable('summernote');
+ Render::addDialog(Dictionary::translateFile('config-module', 'screensaver_title'), false, 'screensaver-start', array(
+ 'step' => 'Screensaver_Text',
+ 'next' => 'idle-kill',
+ 'edit' => $this->edit !== null ? $this->edit->id() : 0,
+ 'id' => 'start',
+ 'title' => $this->session_data['title'],
+ 'qss' => $this->session_data['qss'],
+ ));
+ }
+}
+
+class Screensaver_Text extends AddModule_Base
+{
+ private $session_data;
+
+ protected function preprocessInternal()
+ {
+ /* Load session data */
+ $this->session_data = Session::get(Screensaver_Helper::SESSION_KEY);
+ $id = Request::post('id', '', 'string');
+
+ if ($id === 'start') {
+ Screensaver_Helper::processQssData($this->session_data);
+ } elseif ($id !== '') {
+ Screensaver_Helper::processScreensaverText($this->session_data, $id);
+ }
+
+ $next = Request::post('next', $this->session_data['next'], 'string');
+ $this->session_data['next'] = $next;
+ Session::set(Screensaver_Helper::SESSION_KEY, $this->session_data);
+
+
+ if ($next === 'finish')
+ Util::redirect('?do=SysConfig&action=addmodule&step=Screensaver_Finish');
+ elseif ($next === 'start')
+ Util::redirect('?do=SysConfig&action=addmodule&step=Screensaver_Start&back=true');
+ }
+
+ protected function renderInternal()
+ {
+ /* Load summernote module if available */
+ Module::isAvailable('summernote');
+ $next = $this->session_data['next'];
+
+ $data = array(
+ 'edit' => $this->edit !== null ? $this->edit->id() : 0,
+ );
+
+ /* Prepare and translate labels for the frontend */
+ $data['id'] = $next;
+ /* Convert the id to a language tag (camelCase) styled string */
+ $tag = implode(array_map('ucwords', explode('-', $next)));
+
+ /* For translate module:
+ * Dictionary::translate('saver_TitleNoTimeout');
+ * Dictionary::translate('saver_DescriptionNoTimeout');
+ * Dictionary::translate('saver_TitleIdleKill');
+ * Dictionary::translate('saver_DescriptionIdleKill');
+ * Dictionary::translate('saver_TitleShutdown');
+ * Dictionary::translate('saver_DescriptionShutdown');
+ */
+ $data['title'] = Dictionary::translate('saver_Title' . $tag);
+ $data['description'] = Dictionary::translate('saver_Description' . $tag);
+ $data['msg_value'] = $this->session_data['messages']['General'][$next];
+ $data['msg_locked_value'] = $this->session_data['messages']['General'][$next . '-locked'];
+ $data['text_value'] = $this->session_data['texts']['text-' . $next];
+ $data['text_locked_value'] = $this->session_data['texts']['text-' . $next . '-locked'];
+ $data['inherit_locked'] = $this->session_data['texts'][$next . '-inherit'];
+ $data['step'] = 'Screensaver_Text';
+
+ /* Set next and prev pages */
+ if ($next === 'idle-kill') {
+ $data['next'] = 'no-timeout';
+ $data['prev'] = 'start';
+ } elseif ($next === 'no-timeout') {
+ $data['next'] = 'shutdown';
+ $data['prev'] = 'idle-kill';
+ } elseif ($next === 'shutdown') {
+ $data['next'] = 'finish';
+ $data['prev'] = 'no-timeout';
+ $data['lastStep'] = true;
+ }
+
+ Render::addDialog(Dictionary::translateFile('config-module', 'screensaver_title'), false, 'screensaver-text', $data);
+ }
+}
+
+class Screensaver_Finish extends AddModule_Base
+{
+ protected function preprocessInternal()
+ {
+ /* Get session data */
+ $session_data = Session::get(Screensaver_Helper::SESSION_KEY);
+
+ if (empty($session_data['title'])) {
+ Message::addError('missing-title');
+ Util::redirect('?do=SysConfig', 400);
+ }
+
+ /* Only create an instance, if it's a new one */
+ if ($this->edit !== null) {
+ $module = $this->edit;
+ } else {
+ $module = ConfigModule::getInstance('Screensaver');
+ }
+
+ /* Set all the data to the module instance */
+ $module->setData('qss', $session_data['qss']);
+ $module->setData('messages', $session_data['messages']);
+ $module->setData('texts', $session_data['texts']);
+
+ /* Insert or update database entries */
+ if ($this->edit !== null) {
+ $module->update($session_data['title']);
+ } else {
+ $module->insert($session_data['title']);
+ }
+
+ $task = $module->generate($this->edit === null);
+
+ // Yay
+ Session::set(Screensaver_Helper::SESSION_KEY, false);
+ if ($task !== false && $this->edit !== null)
+ Message::addSuccess('module-edited');
+ elseif ($task !== false) {
+ Message::addSuccess('module-added');
+ AddModule_Base::setStep('AddModule_Assign', $module->id());
+ return;
+ }
+ Util::redirect('?do=SysConfig');
+ }
+}
+
+class Screensaver_Helper
+{
+ const SESSION_KEY = 'scrsvrmod';
+
+ public static function processQssData(&$session_data)
+ {
+ /* Process post data from the Screensaver_Start */
+ $session_data['title'] = Request::post('title', $session_data['title'], 'string');
+ if (empty($session_data['title'])) {
+ Message::addError('missing-title');
+ Util::redirect('?do=SysConfig', 400);
+ }
+ $session_data['qss'] = Request::post('qss', $session_data['qss'], 'string');
+ $helperMode = Request::post('helper_mode', 'false', 'string');
+ if ($helperMode !== 'false') {
+ // Get all the helper variables and build the qss
+ $bg_color_1 = Request::post('bg_color_1', '', 'string');
+ self::fixColor($bg_color_1, '#443');
+ $bg_color_2 = Request::post('bg_color_2', '', 'string');
+ self::fixColor($bg_color_2, '#000');
+ $label_color = Request::post('label_color', '', 'string');
+ self::fixColor($label_color, '#f64');
+ $label_size = Request::post('label_size', 10, 'int') . 'pt';
+ $clock_color = Request::post('clock_color', '', 'string');
+ self::fixColor($clock_color, '#999');
+ $clock_size = Request::post('clock_size', 20, 'int') . 'pt';
+ $header_color = Request::post('header_color', '', 'string');
+ self::fixColor($header_color, $label_color);
+ $header_size = Request::post('header_size', 20, 'int') . 'pt';
+
+ $session_data['qss'] = "#Saver {\n background: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 " .
+ $bg_color_1 . ", stop:1 " . $bg_color_2 . ")\n}\n\n" .
+ "QLabel {\n color: " . $label_color . ";\n font-size: " . $label_size . ";\n}\n\n" .
+ "#lblClock {\n color: " . $clock_color . ";\n font-size: " . $clock_size . ";\n}\n\n" .
+ "#lblHeader {\n color: " . $header_color . ";\n font-size: " . $header_size . ";\n}";
+ }
+ }
+
+ private static function fixColor(&$color, $fix)
+ {
+ if (!preg_match('/^#([0-9a-f]{3}|[0-6a-f]{6})$/i', $color)) {
+ $color = $fix;
+ }
+ }
+
+ public static function processScreensaverText(&$session_data, $name)
+ {
+ /* Process post data from the Screensaver_Text */
+ $session_data['messages']['General'][$name] = Request::post('msg_value', '', 'string');
+ $session_data['texts']['text-' . $name] = Request::post('text_value', '', 'string');
+ $inherit_locked = Request::post('inherit_locked', 'false', 'string');
+ $session_data['texts'][$name . '-inherit'] = $inherit_locked;
+
+ if ($inherit_locked !== 'false') {
+ $session_data['messages']['General'][$name . '-locked'] = $session_data['messages']['General'][$name];
+ $session_data['texts']['text-' . $name . '-locked'] = $session_data['texts']['text-' . $name];
+ } else {
+ $session_data['messages']['General'][$name . '-locked'] = Request::post('msg_locked_value', '', 'string');
+ $session_data['texts']['text-' . $name . '-locked'] = Request::post('text_locked_value', '', 'string');
+ }
+ }
+
+}
+
diff --git a/modules-available/sysconfig/addmodule_shibauth.inc.php b/modules-available/sysconfig/addmodule_shibauth.inc.php
new file mode 100644
index 00000000..17c9d5b3
--- /dev/null
+++ b/modules-available/sysconfig/addmodule_shibauth.inc.php
@@ -0,0 +1,185 @@
+<?php
+
+/*
+ * Wizard for configuring shibboleth login (lightdm greeter)
+ */
+
+const SHIB_SESSION_DATA = 'shibiddata';
+
+class ShibAuth_Start extends AddModule_Base
+{
+
+ protected function renderInternal()
+ {
+ /* Load or initialise session data */
+ if (Request::get('back', 'false', 'string') !== 'false') {
+ /* If coming via the back button, load the session data */
+ $session_data = Session::get(SHIB_SESSION_DATA);
+ if (!is_array($session_data)) {
+ $session_data = ['userlogin' => true, 'browser' => true];
+ }
+ } elseif ($this->edit !== null) {
+ $session_data = $this->edit->getData(null)
+ + ['title' => $this->edit->title()];
+ Session::set(SHIB_SESSION_DATA, $session_data);
+ } else {
+ $session_data = ['userlogin' => true, 'browser' => true];
+ Session::set(SHIB_SESSION_DATA, $session_data);
+ }
+ Render::addDialog(Dictionary::translateFile('config-module', 'shibauth_title'), false, 'shibauth-start', [
+ 'next' => 'ShibAuth_Orgs',
+ 'edit' => $this->edit !== null ? $this->edit->id() : 0,
+ ] + $session_data);
+ }
+
+ protected function ajaxInternal()
+ {
+ $ret = Shib::refreshIdpSuffixMap();
+ if (!$ret) {
+ Message::addError('shib-list-update-failed');
+ }
+ }
+
+}
+
+class Shibauth_Orgs extends AddModule_Base
+{
+
+ private $session_data;
+
+ protected function preprocessInternal()
+ {
+ $this->session_data = Session::get(SHIB_SESSION_DATA);
+ $title = trim(Request::post('title', null, 'string') ?? $this->session_data['title']);
+ if (empty($title) || !is_array($this->session_data)) {
+ Util::redirect('?do=sysconfig&action=addmodule&step=ShibAuth_Start&back=true');
+ }
+ $this->session_data['title'] = $title;
+ $this->session_data['browser'] = !empty(Request::post('browser', '', 'string'));
+ $this->session_data['qrcode'] = !empty(Request::post('qrcode', '', 'string'));
+ $this->session_data['userlogin'] = !empty(Request::post('userlogin', '', 'string'));
+ Session::set(SHIB_SESSION_DATA, $this->session_data);
+ }
+
+ protected function renderInternal()
+ {
+ $list = array_values(Shib::getListByRegistrar());
+ $selected = [];
+ if (is_array($this->session_data['regs'])) {
+ $selected += array_flip(Shib::explodeRegistrars($this->session_data['regs']));
+ }
+ if (is_array($this->session_data['idp'])) {
+ $selected += array_flip($this->session_data['idp']);
+ }
+ foreach ($list as &$reg) {
+ $reg['reghash'] = substr(md5($reg['registrar']), 0, 7);
+ foreach ($reg['list'] as &$idp) {
+ $idp['idphash'] = substr(md5($idp['id']), 0, 7);
+ if (isset($selected[$idp['id']])) {
+ $idp['checked'] = 'checked';
+ }
+ }
+ unset($idp);
+ ArrayUtil::sortByColumn($reg['list'], 'name');
+ }
+ Render::addDialog(Dictionary::translateFile('config-module', 'shibauth_title'), false, 'shibauth-orgs', [
+ 'next' => 'ShibAuth_Finish',
+ 'edit' => $this->edit !== null ? $this->edit->id() : 0,
+ 'list' => $list,
+ 'entitlements' => $this->session_data['entitlements'] ?? '',
+ ]);
+ }
+
+}
+
+class ShibAuth_Finish extends AddModule_Base
+{
+
+ protected function preprocessInternal()
+ {
+ $entitlements = Request::post('entitlements', '', 'string');
+ $userIdpList = Request::post('idp', [], 'array');
+ if (empty($userIdpList)) {
+ Message::addError('no-organization-selected');
+ Util::redirect('?do=sysconfig&action=addmodule&step=ShibAuth_Orgs', 400);
+ }
+ $session_data = Session::get(SHIB_SESSION_DATA);
+ if (!is_array($session_data)) {
+ $session_data = ['idp' => $userIdpList];
+ Session::set(SHIB_SESSION_DATA, $session_data);
+ Message::addError('no-session-data');
+ Util::redirect('?do=sysconfig&action=addmodule&step=ShibAuth_Start&back=true', 400);
+ }
+
+ /* Only create an instance if it's a new one */
+ if ($this->edit !== null) {
+ $module = $this->edit;
+ } else {
+ $module = ConfigModule::getInstance('ShibAuth');
+ }
+
+ // Now figure out which registrars were selected in its entirety.
+ // In those cases, add thr registrar instead.
+ $regLookup = Shib::getIdp2SuffixList();
+ $byReg = [];
+ $entireRegs = [];
+ // Collect by reg
+ foreach ($userIdpList as $idp) {
+ $reg = $regLookup[$idp]['regauth'] ?? 'none';
+ if (!isset($byReg[$reg])) {
+ $byReg[$reg] = [];
+ }
+ $byReg[$reg][] = $idp;
+ }
+ // Check which one matches all
+ $regList = Shib::getListByRegistrar();
+ foreach (array_keys($byReg) as $reg) {
+ if (!isset($regList[$reg]))
+ continue;
+ if (count ($regList[$reg]['list']) === count($byReg[$reg])) {
+ $entireRegs[] = $reg;
+ unset ($byReg[$reg]);
+ error_log("Entire registrar $reg");
+ }
+ }
+ // Build final IDP list by combining the remaining ones again
+ $userIdpList = [];
+ foreach ($byReg as $idps) {
+ $userIdpList = array_merge($userIdpList, $idps);
+ }
+
+ /* Set all the data to the module instance */
+ $module->setData('browser', $session_data['browser']);
+ $module->setData('qrcode', $session_data['qrcode']);
+ $module->setData('userlogin', $session_data['userlogin']);
+ $module->setData('idp', $userIdpList);
+ $module->setData('regs', $entireRegs);
+ $module->setData('entitlements', $entitlements);
+
+ /* Insert or update database entries */
+ if ($this->edit !== null) {
+ $module->update($session_data['title']);
+ } else {
+ $module->insert($session_data['title']);
+ }
+
+ $task = $module->generate($this->edit === null);
+
+ // Yay
+ Session::set(SHIB_SESSION_DATA, false);
+ if ($task !== false && $this->edit !== null) {
+ Message::addSuccess('module-edited');
+ } elseif ($task !== false) {
+ Message::addSuccess('module-added');
+ AddModule_Base::setStep('AddModule_Assign', $module->id());
+ return;
+ }
+ Util::redirect('?do=SysConfig');
+ }
+
+ protected function renderInternal()
+ {
+
+ }
+
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/addmodule_sshconfig.inc.php b/modules-available/sysconfig/addmodule_sshconfig.inc.php
index ec01f878..2447f9be 100644
--- a/modules-available/sysconfig/addmodule_sshconfig.inc.php
+++ b/modules-available/sysconfig/addmodule_sshconfig.inc.php
@@ -9,14 +9,18 @@ class SshConfig_Start extends AddModule_Base
protected function renderInternal()
{
- if ($this->edit !== false) {
- $data = $this->edit->getData(false) + array(
+ if ($this->edit !== null) {
+ $data = $this->edit->getData(null) + array(
'title' => $this->edit->title(),
'edit' => $this->edit->id(),
- 'apl' => $this->edit->getData('allowPasswordLogin') === 'yes'
+ 'PWD_' . strtoupper($this->edit->getData('allowPasswordLogin')) . '_selected' => 'selected',
+ 'USR_' . strtoupper($this->edit->getData('allowedUsersLogin')) . '_selected' => 'selected',
);
} else {
- $data = array();
+ $data = array(
+ 'PWD_NO_selected' => 'selected',
+ 'USR_ROOT_ONLY_selected' => 'selected',
+ );
}
Render::addDialog(Dictionary::translateFile('config-module', 'sshconfig_title'), false, 'sshconfig-start', $data + array(
'step' => 'SshConfig_Finish',
@@ -36,15 +40,17 @@ class SshConfig_Finish extends AddModule_Base
return;
}
// Seems ok, create entry
- if ($this->edit === false)
+ if ($this->edit === null) {
$module = ConfigModule::getInstance('SshConfig');
- else
+ } else {
$module = $this->edit;
+ }
if ($module === false) {
Message::addError('main.error-read', 'sshconfig.inc.php');
Util::redirect('?do=SysConfig&action=addmodule&step=SshConfig_Start');
}
- $module->setData('allowPasswordLogin', Request::post('allowPasswordLogin') === 'yes');
+ $module->setData('allowPasswordLogin', Request::post('allowPasswordLogin'));
+ $module->setData('allowedUsersLogin', Request::post('allowedUsersLogin'));
$port = Request::post('listenPort', '');
if ($port === '') {
$port = 22;
@@ -53,23 +59,25 @@ class SshConfig_Finish extends AddModule_Base
Message::addError('main.value-invalid', 'port', Request::post('listenPort'));
Util::redirect('?do=SysConfig&action=addmodule&step=SshConfig_Start');
}
- if (!$module->setData('publicKey', Request::post('publicKey'))) {
- Message::addError('main.value-invalid', 'pubkey', Request::post('publicKey'));
- Util::redirect('?do=SysConfig&action=addmodule&step=SshConfig_Start');
- }
- if ($this->edit !== false)
+ $module->setData('publicKey', false);
+ if ($this->edit !== null) {
$ret = $module->update($title);
- else
+ } else {
$ret = $module->insert($title);
- if (!$ret)
+ }
+ if (!$ret) {
Util::redirect('?do=SysConfig&action=addmodule&step=SshConfig_Start');
- elseif (!$module->generate($this->edit === false, NULL, 200))
+ } elseif (!$module->generate($this->edit === null, NULL, 200)) {
Util::redirect('?do=SysConfig&action=addmodule&step=SshConfig_Start');
+ }
// Yay
- if ($this->edit !== false)
+ if ($this->edit !== null) {
Message::addSuccess('module-edited');
- else
+ } else {
Message::addSuccess('module-added');
+ AddModule_Base::setStep('AddModule_Assign', $module->id());
+ return;
+ }
Util::redirect('?do=SysConfig');
}
diff --git a/modules-available/sysconfig/addmodule_sshkey.inc.php b/modules-available/sysconfig/addmodule_sshkey.inc.php
new file mode 100644
index 00000000..2337a898
--- /dev/null
+++ b/modules-available/sysconfig/addmodule_sshkey.inc.php
@@ -0,0 +1,72 @@
+<?php
+
+/*
+ * Wizard for configuring the sshd (client side).
+ */
+
+class SshKey_Start extends AddModule_Base
+{
+
+ protected function renderInternal()
+ {
+ if ($this->edit !== null) {
+ $data = $this->edit->getData(null) + array(
+ 'title' => $this->edit->title(),
+ 'edit' => $this->edit->id(),
+ );
+ } else {
+ $data = array();
+ }
+ Render::addDialog(Dictionary::translateFile('config-module', 'sshkey_title'), false, 'sshkey-start', $data + array(
+ 'step' => 'SshKey_Finish',
+ ));
+ }
+
+}
+
+class SshKey_Finish extends AddModule_Base
+{
+
+ protected function preprocessInternal()
+ {
+ $title = Request::post('title');
+ if (empty($title)) {
+ Message::addError('missing-title');
+ return;
+ }
+ // Seems ok, create entry
+ if ($this->edit === null) {
+ $module = ConfigModule::getInstance('SshKey');
+ } else {
+ $module = $this->edit;
+ }
+ if ($module === false) {
+ Message::addError('main.error-read', 'sshkey.inc.php');
+ Util::redirect('?do=SysConfig&action=addmodule&step=SshKey_Start');
+ }
+ if (!$module->setData('publicKey', Request::post('publicKey'))) {
+ Message::addError('main.value-invalid', 'pubkey', Request::post('publicKey'));
+ Util::redirect('?do=SysConfig&action=addmodule&step=SshKey_Start');
+ }
+ if ($this->edit !== null) {
+ $ret = $module->update($title);
+ } else {
+ $ret = $module->insert($title);
+ }
+ if (!$ret) {
+ Util::redirect('?do=SysConfig&action=addmodule&step=SshKey_Start', 500);
+ } elseif (!$module->generate($this->edit === null, NULL, 200)) {
+ Util::redirect('?do=SysConfig&action=addmodule&step=SshKey_Start', 500);
+ }
+ // Yay
+ if ($this->edit !== null) {
+ Message::addSuccess('module-edited');
+ } else {
+ Message::addSuccess('module-added');
+ AddModule_Base::setStep('AddModule_Assign', $module->id());
+ return;
+ }
+ Util::redirect('?do=SysConfig');
+ }
+
+}
diff --git a/modules-available/sysconfig/api.inc.php b/modules-available/sysconfig/api.inc.php
index bb2d9f5e..209bb758 100644
--- a/modules-available/sysconfig/api.inc.php
+++ b/modules-available/sysconfig/api.inc.php
@@ -11,74 +11,64 @@ if (substr($ip, 0, 7) === '::ffff:') {
$ip = substr($ip, 7);
}
-$uuid = Request::any('uuid', false, 'string');
-if ($uuid !== false && strlen($uuid) !== 36) {
- $uuid = false;
+$uuid = Request::any('uuid', null, 'string');
+if ($uuid !== null && strlen($uuid) !== 36) {
+ $uuid = null;
}
// What we do if we can't supply the requested config
function deliverEmpty($message)
{
EventLog::failure($message);
- Header('HTTP/1.1 404 Not found');
+ http_response_code(404);
die('Config file could not be found or read!');
}
-$runmode = false;
-if (Module::isAvailable('runmode')) {
- $runmode = RunMode::getRunMode($uuid);
- if ($runmode !== false) {
- $runmode = RunMode::getModuleConfig($runmode['module']);
+$locationId = null;
+if (Module::isAvailable('locations')) {
+ $locationId = Location::getFromIpAndUuid($ip, $uuid);
+ if ($locationId !== null) {
+ $locationChain = Location::getLocationRootChain($locationId);
+ $locationChain[] = 0;
}
}
-if ($runmode !== false && $runmode->noSysconfig && file_exists(SysConfig::GLOBAL_MINIMAL_CONFIG)) {
- $row = array('filepath' => SysConfig::GLOBAL_MINIMAL_CONFIG, 'title' => 'config');
-} else {
- $locationId = false;
- if (Module::isAvailable('locations')) {
- $locationId = Location::getFromIpAndUuid($ip, $uuid);
- if ($locationId !== false) {
- $locationChain = Location::getLocationRootChain($locationId);
- $locationChain[] = 0;
- }
- }
- if ($locationId === false) {
- $locationId = 0;
- $locationChain = array(0);
- }
+if ($locationId === null) {
+ $locationId = 0;
+ $locationChain = array(0);
+}
- // Get config module path
+// Get config module path
- // We get all the configs for the whole location chain up to root
- $res = Database::simpleQuery("SELECT c.title, c.filepath, c.status, cl.locationid FROM configtgz c"
- . " INNER JOIN configtgz_location cl USING (configid)"
- . " WHERE cl.locationid IN (" . implode(',', $locationChain) . ")");
+// We get all the configs for the whole location chain up to root
+$res = Database::simpleQuery("SELECT c.title, c.filepath, c.status, cl.locationid FROM configtgz c"
+ . " INNER JOIN configtgz_location cl USING (configid)"
+ . " WHERE cl.locationid IN (" . implode(',', $locationChain) . ")");
- $best = 1000;
- $row = false;
- while ($r = $res->fetch(PDO::FETCH_ASSOC)) {
- settype($r['locationid'], 'int');
- $index = array_search($r['locationid'], $locationChain);
- if ($index === false || $index > $best)
- continue;
- if (!file_exists($r['filepath'])) {
- if ($r['locationid'] === 0) {
- EventLog::failure("The global config.tgz '{$r['title']}' was not found at '{$r['filepath']}'. Please regenerate the system configuration");
- } else {
- EventLog::warning("config.tgz '{$r['title']}' for location $locationId not found at '{$r['filepath']}', trying fallback....");
- }
- continue;
+$best = 1000;
+$row = false;
+foreach ($res as $r) {
+ settype($r['locationid'], 'int');
+ $index = array_search($r['locationid'], $locationChain);
+ if ($index === false || $index > $best)
+ continue;
+ if (!file_exists($r['filepath'])) {
+ if ($r['locationid'] === 0) {
+ EventLog::failure("The global config.tgz '{$r['title']}' was not found at '{$r['filepath']}'. Please regenerate the system configuration");
+ } else {
+ EventLog::warning("config.tgz '{$r['title']}' for location $locationId not found at '{$r['filepath']}', trying fallback....");
}
- $best = $index;
- $row = $r;
+ continue;
}
+ $best = $index;
+ $row = $r;
+}
- if ($row === false) {
- // TODO Not found in DB
- deliverEmpty("No config.tgz for location $locationId found (src $ip)");
- }
+if ($row === false) {
+ // TODO Not found in DB
+ deliverEmpty("No config.tgz for location $locationId found (src $ip)");
}
+@ob_end_clean(); // Disable gzip output handler since this is already a compressed file
Header('Content-Type: application/gzip');
Header('Content-Disposition: attachment; filename=' . Util::sanitizeFilename($row['title']) . '.tgz');
$ret = readfile($row['filepath']);
diff --git a/modules-available/sysconfig/clientscript.js b/modules-available/sysconfig/clientscript.js
index 1553d678..9dbb0745 100644
--- a/modules-available/sysconfig/clientscript.js
+++ b/modules-available/sysconfig/clientscript.js
@@ -1,88 +1,113 @@
// Mouseover and clicking
-var $ct = $('#conftable').find('.confrow');
-$ct.click(function() {
- showmod(this, 'bold');
-}).mouseenter(function() {
- showmod(this, 'fade');
-}).mouseleave(function() {
- showmod(this, 'reset');
-});
-var $mt = $('#modtable').find('.modrow');
-$mt.click(function() {
- showconf(this, 'bold');
-}).mouseenter(function() {
- showconf(this, 'fade');
-}).mouseleave(function() {
- showconf(this, 'reset');
-});
+(function() {
+ var boldItem = false;
+ var modToConf = false;
-var boldItem = false;
-var revList = false;
-
-function showpre(e, action) {
- if (boldItem && action !== 'bold') return 'reset';
- if (boldItem) {
- if (e === boldItem) action = 'fade';
- boldItem = false;
- }
- $mt.removeClass("slx-bold slx-fade");
- $ct.removeClass("slx-bold slx-fade");
- return action;
-}
-
-function buildRevList() {
- revList = {};
- $ct.each(function() {
- var elem = $(this);
- var cid = elem.data('id')+'';
- var list = (elem.data('modlist')+'').split(',');
+ var $ct = $('#conftable').find('.confrow .title');
+ $ct.click(function () {
+ showmod(this, 'bold');
+ }).mouseenter(function () {
+ showmod(this, 'fade');
+ }).mouseleave(function () {
+ showmod(this, 'reset');
+ });
+ var $mt = $('#modtable').find('.modrow .title');
+ $mt.click(function () {
+ showconf(this, 'bold');
+ }).mouseenter(function () {
+ showconf(this, 'fade');
+ }).mouseleave(function () {
+ showconf(this, 'reset');
+ });
+ var $confirm = $('#delete-item-list');
+ $('.btn-del-module').click(function() {
+ var mid = $(this).val() + '';
+ var list = modToConf[mid];
+ if (!list || !list.length) {
+ $confirm.append($msgs).addClass('hidden');
+ return;
+ }
+ var $msgs = $confirm.find('ul').empty();
for (var i = 0; i < list.length; ++i) {
- if (!revList[list[i]]) revList[list[i]] = [];
- revList[list[i]].push(cid);
+ $msgs.append($('<li>').text(
+ $('.confrow[data-id="' + list[i] + '"] .title').text()
+ ));
}
+ $confirm.removeClass('hidden');
});
-}
+ $('.btn-del-config').click(function() {
+ $confirm.addClass('hidden');
+ });
+
+ buildRevList();
+ var mods = [];
+ $('#modtable .modrow').each(function() { mods.push($(this).data('id')) });
+ mods.forEach(function(e) { if (modToConf[e] === undefined) $('.modrow[data-id=' + e + '] .icon-unused').removeClass('hidden') });
+
+ function showpre(e, action) {
+ if (boldItem && action !== 'bold') return 'reset';
+ if (boldItem) {
+ if (e === boldItem) action = 'fade';
+ boldItem = false;
+ }
+ $mt.removeClass("slx-bold slx-fade");
+ $ct.removeClass("slx-bold slx-fade");
+ return action;
+ }
-function showconf(e, action) {
- action = showpre(e, action);
- if (action === 'reset') return;
- var $e = $(e);
- if (!revList) buildRevList();
- var mid = $e.data('id')+'';
- var list = revList[mid];
- if (list && list.length > 0) $ct.each(function() {
- var elem = $(this);
- var cid = elem.data('id')+'';
- if (list.indexOf(cid) === -1)
- elem.addClass('slx-fade');
- else if (action === 'bold')
- elem.addClass('slx-bold');
- }); else $ct.addClass('slx-fade');
- if (action === 'bold') {
- boldItem = e;
- $e.addClass("slx-bold");
+ function buildRevList() {
+ modToConf = {};
+ $ct.each(function () {
+ var elem = $(this).parent();
+ var cid = elem.data('id') + '';
+ var list = (elem.data('modlist') + '').split(',');
+ for (var i = 0; i < list.length; ++i) {
+ if (!modToConf[list[i]]) modToConf[list[i]] = [];
+ modToConf[list[i]].push(cid);
+ }
+ });
}
-}
-function showmod(e, action) {
- action = showpre(e, action);
- if (action === 'reset') return;
- var $e = $(e);
- var list = ($e.data('modlist')+'').split(',');
- $mt.each(function () {
- var elem = $(this);
- if (list.indexOf(elem.data('id')+'') === -1)
- elem.addClass("slx-fade");
- else if (action === 'bold')
- elem.addClass("slx-bold");
- });
- if (action === 'bold') {
- boldItem = e;
- $e.addClass("slx-bold");
+ function showconf(e, action) {
+ action = showpre(e, action);
+ if (action === 'reset') return;
+ var $e = $(e);
+ var mid = $e.parent().data('id') + '';
+ var list = modToConf[mid];
+ if (list && list.length > 0) $ct.each(function () {
+ var elem = $(this);
+ var cid = elem.parent().data('id') + '';
+ if (list.indexOf(cid) === -1)
+ elem.addClass('slx-fade');
+ else if (action === 'bold')
+ elem.addClass('slx-bold');
+ }); else $ct.addClass('slx-fade');
+ if (action === 'bold') {
+ boldItem = e;
+ $e.addClass("slx-bold");
+ }
}
-}
+
+ function showmod(e, action) {
+ action = showpre(e, action);
+ if (action === 'reset') return;
+ var $e = $(e);
+ var list = ($e.parent().data('modlist') + '').split(',');
+ $mt.each(function () {
+ var elem = $(this);
+ if (list.indexOf(elem.parent().data('id') + '') === -1)
+ elem.addClass("slx-fade");
+ else if (action === 'bold')
+ elem.addClass("slx-bold");
+ });
+ if (action === 'bold') {
+ boldItem = e;
+ $e.addClass("slx-bold");
+ }
+ }
+})();
// Polling for updated status (outdated, missing, ok)
@@ -91,23 +116,29 @@ var statusChecks = 0;
function checkBuildStatus() {
var mods = [];
var confs = [];
- $(".refmod.btn-primary").each(function (index) {
+ $(".modrow .btn-rebuild.btn-primary").each(function (index) {
mods.push($(this).val());
});
- $(".refconf.btn-primary").each(function (index) {
+ $(".confrow .btn-rebuild.btn-primary").each(function (index) {
confs.push($(this).val());
});
if (mods.length === 0 && confs.length === 0) return;
if (++statusChecks < 10) setTimeout(checkBuildStatus, 150 + 100 * statusChecks);
$.post('?do=SysConfig', { mods: mods.join(), confs: confs.join(), token: TOKEN, action: 'status' }, function (data) {
if (typeof data === 'undefined') return;
- if (typeof data.mods === 'object') updateButtonColor($(".refmod.btn-primary"), data.mods);
- if (typeof data.confs === 'object') updateButtonColor($(".refconf.btn-primary"), data.confs);
+ if (typeof data.mods === 'object') updateButtonColor('.modrow', data.mods);
+ if (typeof data.confs === 'object') updateButtonColor('.confrow', data.confs);
}, 'json');
}
-function updateButtonColor(list,ids) {
- list.each(function() {
- if (ids.indexOf($(this).val()) >= 0) $(this).removeClass('btn-primary').addClass('btn-default');
- });
+function updateButtonColor(rowclass,ids) {
+ for (var i = 0; i < ids.length; ++i) {
+ var e = ids[i];
+ var $row = $(rowclass + '[data-id=' + e.id + ']');
+ $row.find('.btn-rebuild').removeClass('btn-primary').addClass('btn-default');
+ if (e.warnings && (typeof e.warnings === 'string') && e.warnings.length > 0) {
+ $row.find('.row-warnings').text(e.warnings);
+ $row.find('.btn-warnings').removeClass('hidden');
+ }
+ }
}
diff --git a/modules-available/sysconfig/hooks/bootup.inc.php b/modules-available/sysconfig/hooks/bootup.inc.php
new file mode 100644
index 00000000..8e445dc4
--- /dev/null
+++ b/modules-available/sysconfig/hooks/bootup.inc.php
@@ -0,0 +1,3 @@
+<?php
+
+ConfigModuleBaseLdap::ldadp(); \ No newline at end of file
diff --git a/modules-available/sysconfig/hooks/cron.inc.php b/modules-available/sysconfig/hooks/cron.inc.php
index b518ca06..cedc0810 100644
--- a/modules-available/sysconfig/hooks/cron.inc.php
+++ b/modules-available/sysconfig/hooks/cron.inc.php
@@ -1,8 +1,11 @@
<?php
-Trigger::ldadp();
-
// Cleanup orphaned config<->location where the location has been deleted
Database::exec("DELETE c FROM configtgz_location c
LEFT JOIN location l USING (locationid)
WHERE l.locationid IS NULL AND c.locationid <> 0");
+
+// Refresh every once in a while (should roughly be once a day)
+if (mt_rand(0, 300) === 0) {
+ Shib::refreshIdpSuffixMap();
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/hooks/locations-column.inc.php b/modules-available/sysconfig/hooks/locations-column.inc.php
new file mode 100644
index 00000000..8042b51c
--- /dev/null
+++ b/modules-available/sysconfig/hooks/locations-column.inc.php
@@ -0,0 +1,57 @@
+<?php
+
+if (!User::hasPermission('.sysconfig.config.*') || !Module::isAvailable('sysconfig'))
+ return null;
+
+class SysconfigLocationColumn extends AbstractLocationColumn
+{
+
+ private $lookup = [];
+
+ public function __construct()
+ {
+ $confs = SysConfig::getAll();
+ foreach ($confs as $conf) {
+ if (!isset($conf['locs']) || strlen($conf['locs']) === 0)
+ continue;
+ $confLocs = explode(',', $conf['locs']);
+ foreach ($confLocs as $locId) {
+ $this->lookup[$locId] = $conf['title'];
+ }
+ }
+ }
+
+ public function getColumnHtml(int $locationId): string
+ {
+ return htmlspecialchars($this->lookup[$locationId] ?? '');
+ }
+
+ public function getEditUrl(int $locationId): string
+ {
+ if (!User::hasPermission('.sysconfig.config.assign', $locationId))
+ return '';
+ return '?do=sysconfig&locationid=' . $locationId;
+ }
+
+ public function header(): string
+ {
+ return Dictionary::translateFileModule('sysconfig', 'module', 'location-column-header');
+ }
+
+ public function priority(): int
+ {
+ return 2000;
+ }
+
+ public function propagateColumn(): bool
+ {
+ return true;
+ }
+
+ public function propagateDefaultHtml(): string
+ {
+ return htmlspecialchars($this->lookup[0] ?? '');
+ }
+}
+
+return new SysconfigLocationColumn(); \ 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 a9035d78..b5494866 100644
--- a/modules-available/sysconfig/inc/configmodule.inc.php
+++ b/modules-available/sysconfig/inc/configmodule.inc.php
@@ -7,18 +7,23 @@ abstract class ConfigModule
{
/**
- * @var array list of known module types
+ * @var ?array{'title': string,
+ * 'description': string,
+ * 'group': string,
+ * 'unique': bool,
+ * 'sortOrder': int,
+ * 'moduleClass': string,
+ * 'wizardClass': string}[] list of known module types
*/
- private static $moduleTypes = false;
+ private static $moduleTypes = null;
private $moduleId = 0;
- private $moduleArchive = false;
- private $moduleTitle = false;
- private $moduleStatus = false;
- /**
- * @var int
- */
+ private $moduleArchive = '';
+ private $moduleTitle = '';
+ private $moduleStatus = 'MISSING';
+ /** @var int */
private $dateline = 0;
+ /** @var int */
private $currentVersion = 0;
/**
* @var false|array Data of module, false if not initialized
@@ -33,9 +38,9 @@ abstract class ConfigModule
*/
public static function loadDb()
{
- if (self::$moduleTypes !== false)
+ if (self::$moduleTypes !== null)
return;
- self::$moduleTypes = array();
+ self::$moduleTypes = [];
Module::isAvailable('sysconfig');
foreach (glob(dirname(__FILE__) . '/configmodule/*.inc.php', GLOB_NOSORT) as $file) {
require_once $file;
@@ -44,10 +49,16 @@ abstract class ConfigModule
/**
* Get all known config module types.
- *
- * @return array list of modules
+ * @return array{'title': string,
+ * 'description': string,
+ * 'group': string,
+ * 'unique': bool,
+ * 'sortOrder': int,
+ * 'moduleClass': string,
+ * 'wizardClass': string}[] list of known module types
+ * /
*/
- public static function getList()
+ public static function getList(): array
{
self::loadDb();
return self::$moduleTypes;
@@ -63,20 +74,20 @@ abstract class ConfigModule
* @param string $description Description for this module type
* @param string $group Title for group this module type belongs to
* @param bool $unique Can only one such module be added to a config?
- * @param int $sortOrder Lower comes first, alphabetical ordering otherwiese
+ * @param int $sortOrder Lower comes first, alphabetical ordering otherwise
*/
- public static function registerModule($id, $title, $description, $group, $unique, $sortOrder = 0)
+ public static function registerModule(string $id, string $title, string $description, string $group, bool $unique, int $sortOrder = 0): void
{
if (isset(self::$moduleTypes[$id])) {
- Util::traceError("Config Module $id already registered!");
+ ErrorHandler::traceError("Config Module $id already registered!");
}
$moduleClass = 'ConfigModule_' . $id;
$wizardClass = $id . '_Start';
if (!class_exists($moduleClass)) {
- Util::traceError("Class $moduleClass does not exist!");
+ ErrorHandler::traceError("Class $moduleClass does not exist!");
}
if (!is_subclass_of($moduleClass, 'ConfigModule')) {
- Util::traceError("$moduleClass does not have ConfigModule as its parent!");
+ ErrorHandler::traceError("$moduleClass does not have ConfigModule as its parent!");
}
self::$moduleTypes[$id] = array(
'title' => $title,
@@ -93,21 +104,33 @@ abstract class ConfigModule
* Get fresh instance of ConfigModule subclass for given module type.
*
* @param string $moduleType name of module type
- * @return false|\ConfigModule module instance
+ * @return ConfigModule module instance
*/
- public static function getInstance($moduleType)
+ public static function getInstance(string $moduleType): ConfigModule
+ {
+ $ret = self::getInstanceOrNull($moduleType);
+ if ($ret === null) {
+ Message::addError('main.error-read', $moduleType . '.inc.php');
+ Util::redirect('?do=sysconfig');
+ }
+ return $ret;
+ }
+
+ public static function getInstanceOrNull(string $moduleType): ?ConfigModule
{
self::loadDb();
if (!isset(self::$moduleTypes[$moduleType])) {
error_log('Unknown module type: ' . $moduleType);
- return false;
+ return null;
}
return new self::$moduleTypes[$moduleType]['moduleClass'];
}
- public static function instanceFromDbRow($dbRow)
+ private static function instanceFromDbRow(array $dbRow): ?ConfigModule
{
- $instance = self::getInstance($dbRow['moduletype']);
+ $instance = self::getInstanceOrNull($dbRow['moduletype']);
+ if ($instance === null)
+ return null;
$instance->currentVersion = $dbRow['version'];
$instance->moduleArchive = $dbRow['filepath'];
$instance->moduleData = json_decode($dbRow['contents'], true);
@@ -125,37 +148,37 @@ abstract class ConfigModule
* Get module instance from id.
*
* @param int $moduleId module id to get
- * @return false|\ConfigModule The requested module from DB, or false on error
+ * @return ?ConfigModule The requested module from DB, or null on error
*/
- public static function get($moduleId)
+ public static function get(int $moduleId): ?ConfigModule
{
$ret = Database::queryFirst("SELECT moduleid, title, moduletype, filepath, contents, version, status, dateline FROM configtgz_module "
. " WHERE moduleid = :moduleid LIMIT 1", array('moduleid' => $moduleId));
if ($ret === false)
- return false;
+ return null;
return self::instanceFromDbRow($ret);
}
/**
* Get module instances from module type.
*
- * @param int $moduleType module type to get
- * @return \ConfigModule[]|false The requested modules from DB, or false on error
+ * @param string $moduleType module type to get
+ * @return ?ConfigModule[] The requested modules from DB, or null on error
*/
- public static function getAll($moduleType = false)
+ public static function getAll(?string $moduleType = null): ?array
{
- if ($moduleType === false) {
+ if ($moduleType === null) {
$ret = Database::simpleQuery("SELECT moduleid, title, moduletype, filepath, contents, version, status, dateline FROM configtgz_module");
} else {
$ret = Database::simpleQuery("SELECT moduleid, title, moduletype, filepath, contents, version, status, dateline FROM configtgz_module "
. " WHERE moduletype = :moduletype", array('moduletype' => $moduleType));
}
if ($ret === false)
- return false;
+ return null;
$list = array();
- while ($row = $ret->fetch(PDO::FETCH_ASSOC)) {
+ foreach ($ret as $row) {
$instance = self::instanceFromDbRow($row);
- if ($instance === false)
+ if ($instance === null)
continue;
$list[] = $instance;
}
@@ -167,37 +190,37 @@ abstract class ConfigModule
*
* @return int module version
*/
- protected abstract function moduleVersion();
+ protected abstract function moduleVersion(): int;
/**
* Validate the module's configuration.
*
- * @return boolean ok or not
+ * @return bool ok or not
*/
- protected abstract function validateConfig();
+ protected abstract function validateConfig(): bool;
/**
* Set module specific data.
*
* @param string $key key, name or id of data being set
* @param mixed $value Module specific data
- * @return boolean true if data was successfully set, false otherwise (i.e. invalid data being set)
+ * @return bool true if data was successfully set, false otherwise (i.e. invalid data being set)
*/
- public abstract function setData($key, $value);
+ public abstract function setData(string $key, $value): bool;
/**
* Get module specific data.
* Can be overridden by modules.
*
- * @param string $key key, name or id of data to get, or false to get the raw moduleData array
+ * @param ?string $key key, name or id of data to get, or null to get the raw moduleData array
* @return mixed Module specific data
*/
- public function getData($key)
+ public function getData(?string $key)
{
- if ($key === false)
+ if ($key === null)
return $this->moduleData;
if (!is_array($this->moduleData) || !isset($this->moduleData[$key]))
- return false;
+ return null;
return $this->moduleData[$key];
}
@@ -205,25 +228,25 @@ abstract class ConfigModule
* Module specific version of generate.
*
* @param string $tgz File name of tgz module to write final output to
- * @param string $parent Parent task of this task
+ * @param string|null $parent Parent task of this task
* @return array|boolean true if generation is completed immediately,
* a task struct if some task needs to be run for generation,
* false on error
*/
- protected abstract function generateInternal($tgz, $parent);
+ protected abstract function generateInternal(string $tgz, ?string $parent);
- private final function createFileName()
+ private function createFileName(): string
{
return CONFIG_TGZ_LIST_DIR . '/modules/'
. $this->moduleType() . '_id-' . $this->moduleId . '__' . mt_rand() . '-' . time() . '.tgz';
}
- public function allowDownload()
+ public function allowDownload(): bool
{
return false;
}
- public function needRebuild()
+ public function needRebuild(): bool
{
return $this->moduleStatus !== 'OK' || $this->currentVersion < $this->moduleVersion();
}
@@ -233,17 +256,15 @@ abstract class ConfigModule
*
* @return int id
*/
- public final function id()
+ public final function id(): int
{
return $this->moduleId;
}
/**
* Get module title.
- *
- * @return string
*/
- public final function title()
+ public final function title(): string
{
return $this->moduleTitle;
}
@@ -253,29 +274,33 @@ abstract class ConfigModule
*
* @return string tgz file absolute path
*/
- public final function archive()
+ public final function archive(): string
{
return $this->moduleArchive;
}
- public final function status()
+ public final function status(): string
{
return $this->moduleStatus;
}
+
+ public final function currentVersion(): int
+ {
+ return $this->currentVersion;
+ }
/**
* Get the module type.
*
* @return string module type
*/
- public final function moduleType()
+ public final function moduleType(): string
{
+ // Yes, need to pass $this, otherwise we get ConfigModule, the base class this function is part of
$name = get_class($this);
- if ($name === false)
- Util::traceError('ConfigModule::moduleType: get_class($this) returned false!');
// ConfigModule_*
if (!preg_match('/^ConfigModule_(\w+)$/', $name, $out))
- Util::traceError('ConfigModule::moduleType: get_class($this) returned "' . $name . '"');
+ ErrorHandler::traceError('ConfigModule::moduleType: get_class($this) returned "' . $name . '"');
return $out[1];
}
@@ -287,10 +312,10 @@ abstract class ConfigModule
* @param string $title display name of the module
* @return boolean true if inserted successfully, false if module config is invalid
*/
- public final function insert($title)
+ public final function insert(string $title): bool
{
if ($this->moduleId !== 0)
- Util::traceError('ConfigModule::insert called when moduleId != 0');
+ ErrorHandler::traceError('ConfigModule::insert called when moduleId != 0');
if (!$this->validateConfig())
return false;
$this->moduleTitle = $title;
@@ -306,7 +331,7 @@ abstract class ConfigModule
));
$this->moduleId = Database::lastInsertId();
if (!is_numeric($this->moduleId))
- Util::traceError('Inserting new config module into DB did not yield a numeric insert id');
+ ErrorHandler::traceError('Inserting new config module into DB did not yield a numeric insert id');
$this->moduleArchive = $this->createFileName();
Database::exec("UPDATE configtgz_module SET filepath = :path WHERE moduleid = :moduleid LIMIT 1", array(
'path' => $this->moduleArchive,
@@ -321,23 +346,25 @@ abstract class ConfigModule
*
* @return boolean true on success, false otherwise
*/
- public final function update($title)
+ public final function update(string $title = ''): bool
{
if ($this->moduleId === 0)
- Util::traceError('ConfigModule::update called when moduleId == 0');
- if (empty($title))
- $title = $this->moduleTitle;
+ ErrorHandler::traceError('ConfigModule::update called when moduleId == 0');
+ if (!empty($title)) {
+ $this->moduleTitle = $title;
+ }
if (!$this->validateConfig())
return false;
// Update
Database::exec("UPDATE configtgz_module SET title = :title, contents = :contents, status = :status, dateline = :now "
. " WHERE moduleid = :moduleid LIMIT 1", array(
'moduleid' => $this->moduleId,
- 'title' => $title,
+ 'title' => $this->moduleTitle,
'contents' => json_encode($this->moduleData),
'status' => 'OUTDATED',
'now' => time(),
));
+ $this->moduleStatus = 'OUTDATED';
return true;
}
@@ -346,16 +373,16 @@ abstract class ConfigModule
* Updating the database etc. will happen later through a callback.
*
* @param boolean $deleteOnError if true, the db entry will be deleted if generation failed
- * @param string $parent Parent task of this task
+ * @param string|null $parent Parent task of this task
* @param int $timeoutMs maximum time in milliseconds we wait for completion
* @return string|boolean task id if deferred generation was started,
* true if generation succeeded (without using a task or within $timeoutMs)
* false on error
*/
- public final function generate($deleteOnError, $parent = NULL, $timeoutMs = 0)
+ public final function generate(bool $deleteOnError, ?string $parent = null, int $timeoutMs = 0)
{
- if ($this->moduleId === 0 || $this->moduleTitle === false)
- Util::traceError('ConfigModule::generateAsync called on uninitialized/uninserted module!');
+ if ($this->moduleId === 0 || empty($this->moduleTitle))
+ ErrorHandler::traceError('ConfigModule::generateAsync called on uninitialized/uninserted module!');
$tmpTgz = '/tmp/bwlp-id-' . $this->moduleId . '_' . mt_rand() . '_' . time() . '.tgz';
$ret = $this->generateInternal($tmpTgz, $parent);
// Wait for generation if requested
@@ -388,10 +415,10 @@ abstract class ConfigModule
/**
* Delete the module.
*/
- public final function delete()
+ public final function delete(): void
{
if ($this->moduleId === 0)
- Util::traceError('ConfigModule::delete called with invalid module id!');
+ ErrorHandler::traceError('ConfigModule::delete called with invalid module id!');
$ret = Database::exec("DELETE FROM configtgz_module WHERE moduleid = :moduleid LIMIT 1", array(
'moduleid' => $this->moduleId
), true) !== false;
@@ -403,17 +430,19 @@ abstract class ConfigModule
$this->moduleTitle = false;
$this->moduleArchive = false;
}
- return $ret;
}
- private final function markUpdated($tmpTgz)
+ /**
+ * @param ?string $tmpTgz new tar archive to use for this module, or null if the old one is still valid
+ */
+ private function markUpdated(?string $tmpTgz): bool
{
if ($this->moduleId === 0)
- Util::traceError('ConfigModule::markUpdated called with invalid module id!');
- if ($this->moduleArchive === false)
+ ErrorHandler::traceError('ConfigModule::markUpdated called with invalid module id!');
+ if ($this->moduleArchive === null)
$this->moduleArchive = $this->createFileName();
// Move file
- if ($tmpTgz === false) {
+ if ($tmpTgz === null) {
if (!file_exists($this->moduleArchive)) {
EventLog::failure('ConfigModule::markUpdated for "' . $this->moduleTitle . '" called with no tmpTgz and no existing tgz!');
$this->markFailed();
@@ -453,24 +482,26 @@ abstract class ConfigModule
return $retval;
}
- private final function markFailed()
+ private function markFailed(): void
{
if ($this->moduleId === 0)
- Util::traceError('ConfigModule::markFailed called with invalid module id!');
- if ($this->moduleArchive === false)
+ ErrorHandler::traceError('ConfigModule::markFailed called with invalid module id!');
+ if ($this->moduleArchive === '') {
$this->moduleArchive = $this->createFileName();
- if (!file_exists($this->moduleArchive))
+ }
+ if (!file_exists($this->moduleArchive)) {
$status = 'MISSING';
- else
+ } else {
$status = 'OUTDATED';
- return Database::exec("UPDATE configtgz_module SET filepath = :filename, status = :status WHERE moduleid = :id LIMIT 1", array(
+ }
+ Database::exec("UPDATE configtgz_module SET filepath = :filename, status = :status WHERE moduleid = :id LIMIT 1", array(
'id' => $this->moduleId,
'filename' => $this->moduleArchive,
'status' => $status
- )) !== false;
+ ));
}
- public function dateline_s()
+ public function dateline_s(): string
{
return Util::prettyTime($this->dateline);
}
@@ -482,7 +513,7 @@ abstract class ConfigModule
* Override this if you need to handle this, otherwise
* the base implementation does nothing.
*/
- public function event_serverIpChanged()
+ public function event_serverIpChanged(): void
{
// Do::Nothing()
}
@@ -493,11 +524,10 @@ abstract class ConfigModule
* Will be called if the server's IP address changes. The event will be propagated
* to all config module classes so action can be taken if appropriate.
*/
- public static function serverIpChanged()
+ public static function serverIpChanged(): void
{
self::loadDb();
- $list = self::getAll();
- foreach ($list as $mod) {
+ foreach (self::getAll() ?? [] as $mod) {
$mod->event_serverIpChanged();
}
}
@@ -506,53 +536,54 @@ abstract class ConfigModule
* Called when (re)generating a config module failed, so we can
* update the status in the DB and add a server log entry.
*
- * @param array $task
- * @param array $args contains 'moduleid' and optionally 'deleteOnError' and 'tmpTgz'
+ * @param array $args contains 'moduleid' and optionally 'deleteOnError'
*/
- public static function generateFailed($task, $args)
+ public static function generateFailed(array $task, array $args): void
{
if (!isset($args['moduleid']) || !is_numeric($args['moduleid'])) {
EventLog::warning('Ignoring generateFailed event as it has no moduleid assigned.');
return;
}
$module = self::get($args['moduleid']);
- if ($module === false) {
+ if ($module === null) {
EventLog::warning('generateFailed callback for module id ' . $args['moduleid'] . ', but no instance could be generated.');
return;
}
- if (isset($task['data']['error']))
- $error = $task['data']['error'];
- elseif (isset($task['data']['messages']))
- $error = $task['data']['messages'];
- else
- $error = '';
+ $error = '';
+ if (!empty($task['data']['error'])) {
+ $error .= $task['data']['error'] . "\n\n";
+ }
+ if (!empty($task['data']['messages'])) {
+ $error .= $task['data']['messages'] . "\n\n";
+ }
+ if (empty($error)) {
+ $error = json_encode($task, JSON_PRETTY_PRINT);
+ }
EventLog::failure("Generating module '" . $module->moduleTitle . "' failed.", $error);
- if ($args['deleteOnError'])
+ if ($args['deleteOnError'] ?? false) {
$module->delete();
- else
+ } else {
$module->markFailed();
+ }
}
/**
* (Re)generating a config module succeeded. Update db entry.
*
- * @param array $args contains 'moduleid' and optionally 'deleteOnError' and 'tmpTgz'
+ * @param array $args contains 'moduleid' and optionally 'tmpTgz'
*/
- public static function generateSucceeded($args)
+ public static function generateSucceeded(array $args): void
{
if (!isset($args['moduleid']) || !is_numeric($args['moduleid'])) {
EventLog::warning('Ignoring generateSucceeded event as it has no moduleid assigned.');
return;
}
$module = self::get($args['moduleid']);
- if ($module === false) {
+ if ($module === null) {
EventLog::warning('generateSucceeded callback for module id ' . $args['moduleid'] . ', but no instance could be generated.');
return;
}
- if (isset($args['tmpTgz']))
- $module->markUpdated($args['tmpTgz']);
- else
- $module->markUpdated(false);
+ $module->markUpdated($args['tmpTgz'] ?? null);
}
}
diff --git a/modules-available/sysconfig/inc/configmodule/adauth.inc.php b/modules-available/sysconfig/inc/configmodule/adauth.inc.php
index ed7b318d..6d645cc2 100644
--- a/modules-available/sysconfig/inc/configmodule/adauth.inc.php
+++ b/modules-available/sysconfig/inc/configmodule/adauth.inc.php
@@ -9,8 +9,9 @@ class ConfigModule_AdAuth extends ConfigModuleBaseLdap
ConfigModule::registerModule(
ConfigModule_AdAuth::MODID, // ID
- Dictionary::translateFileModule('sysconfig', 'config-module', 'adAuth_title'), // Title
- Dictionary::translateFileModule('sysconfig', 'config-module', 'adAuth_description'), // Description
- Dictionary::translateFileModule('sysconfig', 'config-module', 'group_authentication'), // Group
- false // Only one per config?
+ Dictionary::translateFile('config-module', 'adAuth_title'), // Title
+ Dictionary::translateFile('config-module', 'adAuth_description'), // Description
+ Dictionary::translateFile('config-module', 'group_authentication'), // Group
+ false, // Only one per config?
+ 300
);
diff --git a/modules-available/sysconfig/inc/configmodule/branding.inc.php b/modules-available/sysconfig/inc/configmodule/branding.inc.php
index fd11dade..e1fe46d3 100644
--- a/modules-available/sysconfig/inc/configmodule/branding.inc.php
+++ b/modules-available/sysconfig/inc/configmodule/branding.inc.php
@@ -2,10 +2,11 @@
ConfigModule::registerModule(
ConfigModule_Branding::MODID, // ID
- Dictionary::translateFileModule('sysconfig', 'config-module', 'branding_title'), // Title
- Dictionary::translateFileModule('sysconfig', 'config-module', 'branding_description'), // Description
- Dictionary::translateFileModule('sysconfig', 'config-module', 'group_branding'), // Group
- true // Only one per config?
+ Dictionary::translateFile('config-module', 'branding_title'), // Title
+ Dictionary::translateFile('config-module', 'branding_description'), // Description
+ Dictionary::translateFile('config-module', 'group_branding'), // Group
+ true, // Only one per config?
+ 600
);
class ConfigModule_Branding extends ConfigModule
@@ -13,34 +14,34 @@ class ConfigModule_Branding extends ConfigModule
const MODID = 'Branding';
const VERSION = 1;
-
+
+ /** @var false|string */
private $tmpFile = false;
- protected function generateInternal($tgz, $parent)
+ protected function generateInternal(string $tgz, ?string $parent)
{
if (!$this->validateConfig()) {
- return $this->archive() !== false && file_exists($this->archive()); // No new temp file given, old archive still exists, pretend it worked...
+ return !empty($this->archive()) && file_exists($this->archive()); // No new temp file given, old archive still exists, pretend it worked...
}
- $task = Taskmanager::submit('MoveFile', array(
+ return Taskmanager::submit('MoveFile', array(
'source' => $this->tmpFile,
'destination' => $tgz,
'parentTask' => $parent,
'failOnParentFail' => false
));
- return $task;
}
- protected function moduleVersion()
+ protected function moduleVersion(): int
{
return self::VERSION;
}
- protected function validateConfig()
+ protected function validateConfig(): bool
{
return $this->tmpFile !== false && file_exists($this->tmpFile);
}
- public function setData($key, $value)
+ public function setData(string $key, $value): bool
{
if ($key !== 'tmpFile' || !is_string($value) || !file_exists($value))
return false;
@@ -48,12 +49,12 @@ class ConfigModule_Branding extends ConfigModule
return true;
}
- public function getData($key)
+ public function getData(?string $key): bool
{
return false;
}
- public function allowDownload()
+ public function allowDownload(): bool
{
return true;
}
diff --git a/modules-available/sysconfig/inc/configmodule/customodule.inc.php b/modules-available/sysconfig/inc/configmodule/customodule.inc.php
index 336d794f..d963ae29 100644
--- a/modules-available/sysconfig/inc/configmodule/customodule.inc.php
+++ b/modules-available/sysconfig/inc/configmodule/customodule.inc.php
@@ -2,58 +2,72 @@
ConfigModule::registerModule(
ConfigModule_CustomModule::MODID, // ID
- Dictionary::translateFileModule('sysconfig', 'config-module', 'custom_title'), // Title
- Dictionary::translateFileModule('sysconfig', 'config-module', 'custom_description'), // Description
- Dictionary::translateFileModule('sysconfig', 'config-module', 'group_generic'), // Group
+ Dictionary::translateFile('config-module', 'custom_title'), // Title
+ Dictionary::translateFile('config-module', 'custom_description'), // Description
+ Dictionary::translateFile('config-module', 'group_generic'), // Group
false, // Only one per config?
- 100 // Sort order
+ 900 // Sort order
);
class ConfigModule_CustomModule extends ConfigModule
{
const MODID = 'CustomModule';
- const VERSION = 1;
-
+ const VERSION = 2;
+
+ /** @var false|string */
private $tmpFile = false;
- protected function generateInternal($tgz, $parent)
+ protected function generateInternal(string $tgz, ?string $parent)
{
if (!$this->validateConfig()) {
- return $this->archive() !== false && file_exists($this->archive()); // No new temp file given, old archive still exists, pretend it worked...
+ // No temp file given from wizard
+ // Old archive still exists? pretend it worked...
+ if ($this->archive() === '' || !file_exists($this->archive()))
+ return false;
+ if ($this->currentVersion() == 1) {
+ // Need an upgrade
+ return Taskmanager::submit('RecompressArchive', array(
+ 'inputFiles' => [$this->archive() => false],
+ 'outputFile' => $tgz,
+ 'forceRoot' => true, // Force this for old modules for backward compat
+ ));
+ }
+ // Nothing to do
+ return true;
}
- $task = Taskmanager::submit('MoveFile', array(
+ return Taskmanager::submit('MoveFile', array(
'source' => $this->tmpFile,
'destination' => $tgz,
'parentTask' => $parent,
'failOnParentFail' => false
));
- return $task;
}
- protected function moduleVersion()
+ protected function moduleVersion(): int
{
return self::VERSION;
}
- protected function validateConfig()
+ protected function validateConfig(): bool
{
return $this->tmpFile !== false && file_exists($this->tmpFile);
}
- public function setData($key, $value)
+ public function setData(string $key, $value): bool
{
+ // Sets the temp file from the wizard, where it stored the processed archive
if ($key !== 'tmpFile' || !file_exists($value))
return false;
$this->tmpFile = $value;
return true;
}
- public function getData($key)
+ public function getData(?string $key): bool
{
return false;
}
- public function allowDownload()
+ public function allowDownload(): bool
{
return true;
}
diff --git a/modules-available/sysconfig/inc/configmodule/ldapauth.inc.php b/modules-available/sysconfig/inc/configmodule/ldapauth.inc.php
index e8df2877..aa2c8257 100644
--- a/modules-available/sysconfig/inc/configmodule/ldapauth.inc.php
+++ b/modules-available/sysconfig/inc/configmodule/ldapauth.inc.php
@@ -5,7 +5,7 @@ class ConfigModule_LdapAuth extends ConfigModuleBaseLdap
const MODID = 'LdapAuth';
- protected function preTaskmanagerHook(&$config)
+ protected function preTaskmanagerHook(array &$config)
{
// Just set the flag so the taskmanager job knows we're dealing with a normal ldap server,
// not AD scheme
@@ -16,8 +16,9 @@ class ConfigModule_LdapAuth extends ConfigModuleBaseLdap
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
- false // Only one per config?
+ Dictionary::translateFile('config-module', 'ldapAuth_title'), // Title
+ Dictionary::translateFile('config-module', 'ldapAuth_description'), // Description
+ Dictionary::translateFile('config-module', 'group_authentication'), // Group
+ false, // Only one per config?
+ 300
);
diff --git a/modules-available/sysconfig/inc/configmodule/loginscreen.inc.php b/modules-available/sysconfig/inc/configmodule/loginscreen.inc.php
new file mode 100644
index 00000000..ed99fcd5
--- /dev/null
+++ b/modules-available/sysconfig/inc/configmodule/loginscreen.inc.php
@@ -0,0 +1,100 @@
+<?php
+
+ConfigModule::registerModule(
+ ConfigModule_LoginScreen::MODID, // ID
+ Dictionary::translateFile('config-module', 'loginscreen_title'), // Title
+ Dictionary::translateFile('config-module', 'loginscreen_description'), // Description
+ Dictionary::translateFile('config-module', 'group_loginscreen'), // Group
+ true, // Only one per config?
+ 610 // Sort order
+);
+
+class ConfigModule_LoginScreen extends ConfigModule
+{
+ const MODID = 'LoginScreen';
+ const VERSION = 1;
+
+ const VALID_FIELDS = [
+ 'clock-text-style',
+ 'clock-background-style',
+ 'clock-shadow',
+ 'username-placeholder',
+ 'password-placeholder',
+ 'shib-session-button-text',
+ 'qr-session-button-text',
+ 'user-session-button-text',
+ 'guest-session-button-text',
+ 'guest-session-start-text',
+ 'guest-session-start-button-text',
+ 'reset-timeout',
+ ];
+
+ protected function generateInternal(string $tgz, ?string $parent)
+ {
+ /* Validate if all data are available */
+ if (!$this->validateConfig())
+ return false;
+
+ return Taskmanager::submit('MakeTarball', array(
+ 'files' => $this->getFileArray(),
+ 'destination' => $tgz,
+ 'parentTask' => $parent,
+ ), false);
+ }
+
+ protected function moduleVersion(): int
+ {
+ return self::VERSION;
+ }
+
+ protected function validateConfig(): bool
+ {
+ return true;
+ }
+
+ public function setData(string $key, $value): bool
+ {
+ if (!in_array($key, self::VALID_FIELDS))
+ return false;
+ $this->moduleData[$key] = $value;
+ return true;
+ }
+
+ public function allowDownload(): bool
+ {
+ return false;
+ }
+
+ /**
+ * Creates a map with filepath => file content
+ * @return array{"/etc/lightdm/qt-lightdm-greeter.conf.d/80-$id-customization.conf": string}
+ */
+ private function getFileArray(): array
+ {
+ $id = $this->id();
+ $content = "[General]\n";
+ foreach (self::VALID_FIELDS as $field) {
+ if (!isset($this->moduleData[$field]))
+ continue;
+ $content .= $field . '=' . $this->escapeString($this->moduleData[$field]) . "\n";
+ }
+ return [
+ "/etc/lightdm/qt-lightdm-greeter.conf.d/80-$id-customization.conf" => $content,
+ ];
+ }
+
+ private function escapeString(string $str)
+ {
+ if (strpos($str, '\\') !== false) {
+ $str = str_replace('\\', '\\\\', $str);
+ }
+ if (strpos($str, '"') !== false) {
+ $str = str_replace('"', '\\"', $str);
+ }
+ if (strpos($str, ';') !== false) {
+ $str = '"' . $str. '"';
+ }
+ return $str;
+ }
+
+}
diff --git a/modules-available/sysconfig/inc/configmodule/screensaver.inc.php b/modules-available/sysconfig/inc/configmodule/screensaver.inc.php
new file mode 100644
index 00000000..40736649
--- /dev/null
+++ b/modules-available/sysconfig/inc/configmodule/screensaver.inc.php
@@ -0,0 +1,99 @@
+<?php
+
+ConfigModule::registerModule(
+ ConfigModule_Screensaver::MODID, // ID
+ Dictionary::translateFile('config-module', 'screensaver_title'), // Title
+ Dictionary::translateFile('config-module', 'screensaver_description'), // Description
+ Dictionary::translateFile('config-module', 'group_screensaver'), // Group
+ true, // Only one per config?
+ 700 // Sort order
+);
+
+class ConfigModule_Screensaver extends ConfigModule
+{
+ const MODID = 'Screensaver';
+ const VERSION = 1;
+
+ protected function generateInternal(string $tgz, ?string $parent)
+ {
+ /* Validate if all data are available */
+ if (!$this->validateConfig())
+ return false;
+
+ return Taskmanager::submit('MakeTarball', array(
+ 'files' => $this->getFileArray(),
+ 'destination' => $tgz,
+ 'parentTask' => $parent,
+ ), false);
+ }
+
+ protected function moduleVersion(): int
+ {
+ return self::VERSION;
+ }
+
+ protected function validateConfig(): bool
+ {
+ return isset($this->moduleData['texts']['text-no-timeout'])
+ && isset($this->moduleData['texts']['text-idle-kill'])
+ && isset($this->moduleData['texts']['text-shutdown'])
+ && isset($this->moduleData['qss']);
+ }
+
+ public function setData(string $key, $value): bool
+ {
+ switch ($key) {
+ case 'qss':
+ case 'texts':
+ case 'messages':
+ break;
+ default:
+ return false;
+ }
+ $this->moduleData[$key] = $value;
+ return true;
+ }
+
+ public function allowDownload(): bool
+ {
+ return false;
+ }
+
+ /**
+ * Creates a map with filepath => file content
+ */
+ private function getFileArray(): array
+ {
+ $files = array(
+ '/opt/openslx/xscreensaver/style.qss' => $this->moduleData['qss'],
+ '/opt/openslx/xscreensaver/text-idle-kill' => $this->wrapHtmlTags('text-idle-kill'),
+ '/opt/openslx/xscreensaver/text-no-timeout' => $this->wrapHtmlTags('text-no-timeout'),
+ '/opt/openslx/xscreensaver/text-shutdown' => $this->wrapHtmlTags('text-shutdown'),
+ );
+
+ /* Create the message.ini from the messages array */
+ $messages = '';
+ foreach ($this->moduleData['messages'] as $category => $array) {
+ $messages .= '[' . $category . ']' . "\n";
+ foreach ($array as $key => $message) {
+ $messages .= $key . '="' . str_replace(['\\', '"', "\n", "\r"], '-', $message) . '"' . "\n";
+ }
+ }
+ $files['/opt/openslx/xscreensaver/messages.ini'] = $messages;
+
+ /* Add locked files if there are any */
+ if (isset($this->moduleData['texts']['text-idle-kill-locked']))
+ $files['/opt/openslx/xscreensaver/text-idle-kill-locked'] = $this->wrapHtmlTags('text-idle-kill-locked');
+ if (isset($this->moduleData['texts']['text-no-timeout-locked']))
+ $files['/opt/openslx/xscreensaver/text-no-timeout-locked'] = $this->wrapHtmlTags('text-no-timeout-locked');
+ if (isset($this->moduleData['texts']['text-shutdown-locked']))
+ $files['/opt/openslx/xscreensaver/text-shutdown-locked'] = $this->wrapHtmlTags('text-shutdown-locked');
+
+ return $files;
+ }
+
+ private function wrapHtmlTags(string $text_name): string
+ {
+ return '<html><body>' . $this->moduleData['texts'][$text_name] . '</body></html>';
+ }
+}
diff --git a/modules-available/sysconfig/inc/configmodule/shibauth.inc.php b/modules-available/sysconfig/inc/configmodule/shibauth.inc.php
new file mode 100644
index 00000000..801b30bf
--- /dev/null
+++ b/modules-available/sysconfig/inc/configmodule/shibauth.inc.php
@@ -0,0 +1,150 @@
+<?php
+
+ConfigModule::registerModule(
+ ConfigModule_ShibAuth::MODID, // ID
+ Dictionary::translateFile('config-module', 'shibauth_title'), // Title
+ Dictionary::translateFile('config-module', 'shibauth_description'), // Description
+ Dictionary::translateFile('config-module', 'group_authentication'), // Group
+ false, // Only one per config?
+ 310 // Sort order
+);
+
+class ConfigModule_ShibAuth extends ConfigModule
+{
+ const MODID = 'ShibAuth';
+ const VERSION = 1;
+
+ protected function generateInternal(string $tgz, ?string $parent)
+ {
+ /* Validate if all data are available */
+ if (!$this->validateConfig())
+ return false;
+
+ return Taskmanager::submit('MakeTarball', array(
+ 'files' => $this->getFileArray(),
+ 'destination' => $tgz,
+ 'parentTask' => $parent,
+ ), false);
+ }
+
+ protected function moduleVersion(): int
+ {
+ return self::VERSION;
+ }
+
+ protected function validateConfig(): bool
+ {
+ return isset($this->moduleData['browser']) || isset($this->moduleData['qrcode'])
+ || !empty($this->moduleData['idp']);
+ }
+
+ public function setData(string $key, $value): bool
+ {
+ switch ($key) {
+ case 'browser':
+ case 'qrcode':
+ case 'userlogin':
+ case 'idp':
+ case 'regs':
+ break;
+ case 'entitlements':
+ $value = str_replace(["\r", "\n", " ", "\t"], ';', $value);
+ break;
+ default:
+ return false;
+ }
+ $this->moduleData[$key] = $value;
+ return true;
+ }
+
+ public function allowDownload(): bool
+ {
+ return false;
+ }
+
+ /**
+ * Creates a map with filepath => file content
+ * @return array{"/opt/openslx/pam/shibboleth/whitelist/shib-$id.idp": string,
+ * "/etc/lightdm/qt-lightdm-greeter.conf.d/shib-$id.conf": string,
+ * "/opt/openslx/pam/shibboleth/whitelist/shib-$id.suffix": string}
+ */
+ private function getFileArray(): array
+ {
+ $id = $this->id();
+ $url = CONFIG_SHIB_CLIENT_URL;
+ $browser = '';
+ $qrcode = '';
+ $userlogin = '';
+ if ($this->moduleData['browser'] ?? false) {
+ $browser = "shib-session-enabled = true";
+ }
+ if ($this->moduleData['qrcode'] ?? false) {
+ $qrcode = "qr-session-enabled = true";
+ }
+ if (!($this->moduleData['userlogin'] ?? true)) {
+ $userlogin = "user-session-enabled = false";
+ }
+ return [
+ "/etc/lightdm/qt-lightdm-greeter.conf.d/shib-$id.conf" => <<<EOF
+[General]
+shib-url = $url
+$browser
+$qrcode
+$userlogin
+EOF,
+ "/opt/openslx/pam/shibboleth/whitelist/shib-$id.idp" => $this->generateIdpList(),
+ "/opt/openslx/pam/shibboleth/whitelist/shib-$id.suffix" => $this->generateSuffixList(),
+ ];
+ }
+
+ /**
+ * Generate plain-text file of suffixes belonging to all enabled entities.
+ * Used by pam-part on client to verify login.
+ */
+ private function generateSuffixList(): string
+ {
+ $idp2suffix = Shib::getIdp2SuffixList();
+ if ($idp2suffix === null)
+ return '';
+ // Explode registrar shortcuts
+ if (is_array($this->moduleData['regs'] ?? 0)) {
+ $idps = Shib::explodeRegistrars($this->moduleData['regs']);
+ } else {
+ $idps = [];
+ }
+ if (is_array($this->moduleData['idp'])) {
+ $idps = array_merge($idps, $this->moduleData['idp']);
+ }
+ // Build
+ $return = '';
+ foreach ($idps as $idp) {
+ if (empty($idp2suffix[$idp]))
+ continue;
+ $return .= implode("\n", $idp2suffix[$idp]['suffix']) . "\n";
+ }
+ return $return;
+ }
+
+ /**
+ * Generates a list of Identity Providers (IdPs) based on the module's configuration data.
+ * Expands registrar data and merges with IdP data into a single list.
+ * If one or more entitlements are required, they're put on the first line.
+ *
+ * @return string A newline-separated string containing the list of IdPs.
+ */
+ private function generateIdpList(): string
+ {
+ $idps = [];
+ if (!empty($this->moduleData['entitlements'])) {
+ $idps[] = '# entitlements=' . $this->moduleData['entitlements'];
+ }
+ if (is_array($this->moduleData['regs'] ?? 0)) {
+ $idps = array_merge($idps, Shib::explodeRegistrars($this->moduleData['regs']));
+ }
+ if (is_array($this->moduleData['idp'])) {
+ $idps = array_merge($idps, $this->moduleData['idp']);
+ }
+ return implode("\n", $idps) . "\n";
+ }
+
+}
diff --git a/modules-available/sysconfig/inc/configmodule/sshconfig.inc.php b/modules-available/sysconfig/inc/configmodule/sshconfig.inc.php
index 61f69581..57a2fb52 100644
--- a/modules-available/sysconfig/inc/configmodule/sshconfig.inc.php
+++ b/modules-available/sysconfig/inc/configmodule/sshconfig.inc.php
@@ -2,10 +2,11 @@
ConfigModule::registerModule(
ConfigModule_SshConfig::MODID, // ID
- Dictionary::translateFileModule('sysconfig', 'config-module', 'sshconfig_title'), // Title
- Dictionary::translateFileModule('sysconfig', 'config-module', 'sshconfig_description'), // Description
- Dictionary::translateFileModule('sysconfig', 'config-module', 'group_sshconfig'), // Group
- false // Only one per config?
+ Dictionary::translateFile('config-module', 'sshconfig_title'), // Title
+ Dictionary::translateFile('config-module', 'sshconfig_description'), // Description
+ Dictionary::translateFile('config-module', 'group_sshconfig'), // Group
+ true, // Only one per config?
+ 500
);
class ConfigModule_SshConfig extends ConfigModule
@@ -13,45 +14,59 @@ class ConfigModule_SshConfig extends ConfigModule
const MODID = 'SshConfig';
const VERSION = 1;
- protected function generateInternal($tgz, $parent)
+ protected function generateInternal(string $tgz, ?string $parent)
{
if (!$this->validateConfig())
return false;
$config = $this->moduleData + array(
'filename' => $tgz,
'failOnParentFail' => false,
- 'parent' => $parent
+ 'parentTask' => $parent,
);
- // Create config module, which will also check if the pubkey is valid
return Taskmanager::submit('SshdConfigGenerator', $config);
}
- protected function moduleVersion()
+ protected function moduleVersion(): int
{
return self::VERSION;
}
- protected function validateConfig()
+ protected function validateConfig(): bool
{
- return isset($this->moduleData['publicKey']) && isset($this->moduleData['allowPasswordLogin']) && isset($this->moduleData['listenPort']);
+ // UPGRADE
+ if (isset($this->moduleData['allowPasswordLogin']) && !isset($this->moduleData['allowedUsersLogin'])) {
+ $this->moduleData['allowPasswordLogin'] = strtoupper($this->moduleData['allowPasswordLogin']);
+ if (!in_array($this->moduleData['allowPasswordLogin'], ['NO', 'USER_ONLY', 'YES'])) {
+ $this->moduleData['allowPasswordLogin'] = 'NO';
+ }
+ $this->moduleData['allowedUsersLogin'] = 'ALL';
+ }
+ return isset($this->moduleData['allowPasswordLogin']) && isset($this->moduleData['allowedUsersLogin'])
+ && isset($this->moduleData['listenPort']);
}
- public function setData($key, $value)
+ public function setData(string $key, $value): bool
{
switch ($key) {
case 'publicKey':
- break;
+ if ($value === false) {
+ error_log('Unsetting publicKey');
+ unset($this->moduleData[$key]);
+ return true;
+ }
+ return false;
case 'allowPasswordLogin':
- if ($value === true || $value === 'yes')
- $value = 'yes';
- elseif ($value === false || $value === 'no')
- $value = 'no';
- else
+ if (!in_array($value, ['NO', 'USER_ONLY', 'YES']))
+ return false;
+ break;
+ case 'allowedUsersLogin';
+ if (!in_array($value, ['ROOT_ONLY', 'USER_ONLY', 'ALL']))
return false;
break;
case 'listenPort':
if (!is_numeric($value) || $value < 1 || $value > 65535)
return false;
+ $value = (int)$value;
break;
default:
return false;
diff --git a/modules-available/sysconfig/inc/configmodule/sshkey.inc.php b/modules-available/sysconfig/inc/configmodule/sshkey.inc.php
new file mode 100644
index 00000000..7ef546db
--- /dev/null
+++ b/modules-available/sysconfig/inc/configmodule/sshkey.inc.php
@@ -0,0 +1,55 @@
+<?php
+
+ConfigModule::registerModule(
+ ConfigModule_SshKey::MODID, // ID
+ Dictionary::translateFile('config-module', 'sshkey_title'), // Title
+ Dictionary::translateFile('config-module', 'sshkey_description'), // Description
+ Dictionary::translateFile('config-module', 'group_sshkey'), // Group
+ false, // Only one per config?
+ 510
+);
+
+class ConfigModule_SshKey extends ConfigModule
+{
+ const MODID = 'SshKey';
+ const VERSION = 1;
+
+ protected function generateInternal(string $tgz, ?string $parent)
+ {
+ if (!$this->validateConfig())
+ return false;
+ $config = array(
+ 'files' => [
+ '/root/.ssh/authorized_keys.d/sshkey_' . $this->id() . '_' . Util::sanitizeFilename($this->title()) . '.pub'
+ => $this->moduleData['publicKey']],
+ 'destination' => $tgz,
+ 'failOnParentFail' => false,
+ 'parentTask' => $parent,
+ );
+ // Create config module, which will also check if the pubkey is valid
+ return Taskmanager::submit('MakeTarball', $config);
+ }
+
+ protected function moduleVersion(): int
+ {
+ return self::VERSION;
+ }
+
+ protected function validateConfig(): bool
+ {
+ return isset($this->moduleData['publicKey']);
+ }
+
+ public function setData(string $key, $value): bool
+ {
+ switch ($key) {
+ case 'publicKey':
+ break;
+ default:
+ return false;
+ }
+ $this->moduleData[$key] = $value;
+ return true;
+ }
+
+}
diff --git a/modules-available/sysconfig/inc/configmodulebaseldap.inc.php b/modules-available/sysconfig/inc/configmodulebaseldap.inc.php
index ad3d32c5..e21c3904 100644
--- a/modules-available/sysconfig/inc/configmodulebaseldap.inc.php
+++ b/modules-available/sysconfig/inc/configmodulebaseldap.inc.php
@@ -3,15 +3,15 @@
abstract class ConfigModuleBaseLdap extends ConfigModule
{
- const VERSION = 3;
+ const VERSION = 5;
private static $REQUIRED_FIELDS = array('server', 'searchbase');
- private static $OPTIONAL_FIELDS = array('binddn', 'bindpw', 'home', 'ssl', 'fixnumeric', 'fingerprint', 'certificate', 'homeattr',
+ private static $OPTIONAL_FIELDS = array('binddn', 'bindpw', 'home', 'ssl', 'fingerprint', 'certificate', 'homeattr',
'shareRemapMode', 'shareRemapCreate', 'shareDocuments', 'shareDownloads', 'shareDesktop', 'shareMedia',
'shareOther', 'shareHomeDrive', 'shareDomain', 'credentialPassthrough', 'mapping', 'genuid',
'ldapAttrMountOpts', 'shareHomeMountOpts', 'nohomewarn');
- public static function getMapping($config = false, &$empty = true)
+ public static function getMapping(?array $config = null, ?bool &$empty = true): array
{
$list = array(
['name' => 'uid', 'field' => 'uid', 'ad' => 'sAMAccountName'],
@@ -32,12 +32,43 @@ abstract class ConfigModuleBaseLdap extends ConfigModule
return $list;
}
- protected function generateInternal($tgz, $parent)
+ public static function getActiveModuleIds()
{
- $np = Trigger::ldadp($this->id(), $parent);
- if ($np !== false) {
- $parent = $np;
+ return Database::queryColumnArray("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')");
+ }
+
+ /**
+ * Launch all ldadp instances that need to be running.
+ *
+ * @param string $command start, restart, check
+ * @param bool|int|int[] $ids list of IDs to run command on, or false meaning "all"
+ * @param string|null $parent if not NULL, this will be the parent task of the launch-task
+ * @return boolean|string false on error, id of task otherwise
+ */
+ public static function ldadp(string $command = 'start', $ids = false, ?string $parent = null)
+ {
+ if ($ids === false) {
+ $ids = self::getActiveModuleIds();
+ } elseif (!is_array($ids)) {
+ $ids = [$ids];
}
+ $task = Taskmanager::submit('LdadpLauncher', array(
+ 'ids' => $ids,
+ 'command' => $command,
+ 'parentTask' => $parent,
+ 'failOnParentFail' => false
+ ));
+ if (!isset($task['id']))
+ return false;
+ return $task['id'];
+ }
+
+ protected function generateInternal(string $tgz, ?string $parent)
+ {
$config = $this->moduleData;
if (isset($config['certificate']) && !is_string($config['certificate'])) {
unset($config['certificate']);
@@ -64,15 +95,14 @@ abstract class ConfigModuleBaseLdap extends ConfigModule
if (!isset($config['shareHomeDrive'])) {
$config['shareHomeDrive'] = 'H:';
}
- if (!isset($config['fixnumeric'])) {
- $config['fixnumeric'] = 's';
- }
- $config['genuid'] = isset($config['genuid']) && !empty($config['genuid']);
+ // This is now always on, as we mask it transparently in our lightdm greeter
+ $config['fixnumeric'] = 'true';
+ $config['genuid'] = !empty($config['genuid']);
$config['nohomewarn'] = isset($config['nohomewarn']) ? (int)$config['nohomewarn'] : 0;
$this->preTaskmanagerHook($config);
$task = Taskmanager::submit('CreateLdapConfig', $config);
if (is_array($task) && isset($task['id'])) {
- Trigger::ldadp(null, $task['id']);
+ self::ldadp('restart', $this->id(), $task['id']);
}
return $task;
}
@@ -81,25 +111,23 @@ abstract class ConfigModuleBaseLdap extends ConfigModule
* 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 preTaskmanagerHook(array &$config)
{
}
- protected function moduleVersion()
+ protected function moduleVersion(): int
{
return self::VERSION;
}
- protected function validateConfig()
+ protected function validateConfig(): bool
{
// Check if required fields are filled
- return Util::hasAllKeys($this->moduleData, self::$REQUIRED_FIELDS);
+ return ArrayUtil::hasAllKeys($this->moduleData, self::$REQUIRED_FIELDS);
}
- public function setData($key, $value)
+ public function setData(string $key, $value): bool
{
if (!in_array($key, self::$REQUIRED_FIELDS) && !in_array($key, self::$OPTIONAL_FIELDS))
return false;
@@ -112,7 +140,7 @@ abstract class ConfigModuleBaseLdap extends ConfigModule
/**
* Server IP changed - rebuild all AD modules.
*/
- public function event_serverIpChanged()
+ public function event_serverIpChanged(): void
{
$this->generate(false);
}
diff --git a/modules-available/sysconfig/inc/configtgz.inc.php b/modules-available/sysconfig/inc/configtgz.inc.php
index 374cb5e0..62017d14 100644
--- a/modules-available/sysconfig/inc/configtgz.inc.php
+++ b/modules-available/sysconfig/inc/configtgz.inc.php
@@ -4,29 +4,28 @@ class ConfigTgz
{
private $configId = 0;
- private $configTitle = false;
- private $file = false;
+ private $configTitle = '';
+ private $file = '';
private $modules = array();
private function __construct()
{
- ;
}
- public function id()
+ public function id(): int
{
return $this->configId;
}
- public function title()
+ public function title(): string
{
return $this->configTitle;
}
- public function areAllModulesUpToDate()
+ public function areAllModulesUpToDate(): bool
{
if (!$this->configId > 0)
- Util::traceError('ConfigTgz::areAllModulesUpToDate called on un-inserted config.tgz!');
+ ErrorHandler::traceError('ConfigTgz::areAllModulesUpToDate called on un-inserted config.tgz!');
foreach ($this->modules as $module) {
if (!empty($module['filepath']) && file_exists($module['filepath'])) {
if ($module['status'] !== 'OK')
@@ -38,25 +37,32 @@ class ConfigTgz
return true;
}
- public function isActive()
+ public function isActive(): bool
{
return readlink(CONFIG_HTTP_DIR . '/default/config.tgz') === $this->file;
}
-
- public function getModuleIds()
+
+ /**
+ * @return int[]
+ */
+ public function getModuleIds(): array
{
- $ret = array();
+ $ret = [];
foreach ($this->modules as $module) {
- $ret[] = $module['moduleid'];
+ $ret[] = (int)$module['moduleid'];
}
return $ret;
}
-
- public function update($title, $moduleIds)
+
+ /**
+ * @param string $title New title for module
+ * @param int[] $moduleIds List of modules to include in this config
+ */
+ public function update(string $title, array $moduleIds): void
{
- if (!is_array($moduleIds))
- return false;
- $this->configTitle = $title;
+ if (!empty($title)) {
+ $this->configTitle = $title;
+ }
$this->modules = array();
// Get all modules to put in config
$idstr = '0'; // Passed directly in query. Make sure no SQL injection is possible
@@ -67,7 +73,7 @@ class ConfigTgz
// Delete old connections
Database::exec("DELETE FROM configtgz_x_module WHERE configid = :configid", array('configid' => $this->configId));
// Make connection
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ foreach ($res as $row) {
Database::exec("INSERT INTO configtgz_x_module (configid, moduleid) VALUES (:configid, :moduleid)", array(
'configid' => $this->configId,
'moduleid' => $row['moduleid']
@@ -77,54 +83,51 @@ class ConfigTgz
// Update name
Database::exec("UPDATE configtgz SET title = :title, status = :status, dateline = :now WHERE configid = :configid LIMIT 1", array(
'configid' => $this->configId,
- 'title' => $title,
+ 'title' => $this->configTitle,
'status' => 'OUTDATED',
'now' => time(),
));
- return true;
}
/**
*
- * @param bool $deleteOnError
- * @param int $timeoutMs
- * @return string - OK (success)
- * - OUTDATED (updating failed, but old version still exists)
- * - MISSING (failed and no old version available)
+ * @param bool $deleteOnError Delete this config in case of error?
+ * @param int $timeoutMs max time to wait for completion
+ * @param string|null $parentTask parent task to order this (re)build after
+ * @return string|bool true=success, false=error, string=taskid, still running
*/
- public function generate($deleteOnError = false, $timeoutMs = 0)
+ public function generate(bool $deleteOnError = false, int $timeoutMs = 0, ?string $parentTask = null)
{
- if (!($this->configId > 0) || !is_array($this->modules) || $this->file === false)
- Util::traceError ('configId <= 0 or modules not array in ConfigTgz::rebuild()');
+ if (!($this->configId > 0) || empty($this->file))
+ ErrorHandler::traceError('configId <= 0 or no file in ConfigTgz::rebuild()');
$files = array();
// Get all config modules for system config
foreach ($this->modules as $module) {
if (!empty($module['filepath']) && file_exists($module['filepath'])) {
- $files[] = $module['filepath'];
- }
- if ($module['moduletype'] === 'SshConfig') {
- // HACK XXX TODO Global + SSH ugly
- self::rebuildEmptyGlobalConfig();
+ // Dupcheck only for custom modules for now
+ $files[$module['filepath']] = ($module['moduletype'] === 'CustomModule');
}
}
- $task = self::recompress($files, $this->file);
+ $task = self::recompress($files, $this->file, $parentTask);
// Wait for completion
- if ($timeoutMs > 0 && !Taskmanager::isFailed($task) && !Taskmanager::isFinished($task))
+ if ($timeoutMs > 0 && !Taskmanager::isFailed($task) && !Taskmanager::isFinished($task)) {
$task = Taskmanager::waitComplete($task, $timeoutMs);
- if ($task === true || (isset($task['statusCode']) && $task['statusCode'] === Taskmanager::TASK_FINISHED)) {
+ }
+ if (Taskmanager::isFinished($task)) {
// Success!
- $this->markUpdated();
+ $this->markUpdated($task);
return true;
}
if (!is_array($task) || !isset($task['id']) || Taskmanager::isFailed($task)) {
// Failed...
Taskmanager::addErrorMessage($task);
- if (!$deleteOnError)
+ if (!$deleteOnError) {
$this->markFailed();
- else
+ } else {
$this->delete();
+ }
return false;
}
// Still running, add callback
@@ -135,55 +138,81 @@ class ConfigTgz
return $task['id'];
}
- public function delete()
+ public function delete(): bool
{
if ($this->configId === 0)
- Util::traceError('ConfigTgz::delete called with invalid config id!');
- $ret = Database::exec("DELETE FROM configtgz WHERE configid = :configid LIMIT 1", array(
- 'configid' => $this->configId
- ), true) !== false;
+ ErrorHandler::traceError('ConfigTgz::delete called with invalid config id!');
+ $ret = Database::exec("DELETE FROM configtgz WHERE configid = :configid LIMIT 1",
+ ['configid' => $this->configId], true);
if ($ret !== false) {
- if ($this->file !== false)
+ if (!empty($this->file)) {
Taskmanager::submit('DeleteFile', array('file' => $this->file), true);
+ }
$this->configId = 0;
- $this->modules = false;
- $this->file = false;
+ $this->modules = [];
+ $this->file = '';
}
- return $ret;
+ return $ret !== false;
}
- public function markOutdated()
+ public function markOutdated(): void
{
if ($this->configId === 0)
- Util::traceError('ConfigTgz::markOutdated called with invalid config id!');
- return $this->mark('OUTDATED');
+ ErrorHandler::traceError('ConfigTgz::markOutdated called with invalid config id!');
+ $this->mark('OUTDATED');
}
- private function markUpdated()
+ private function markUpdated(array $task): void
{
if ($this->configId === 0)
- Util::traceError('ConfigTgz::markUpdated called with invalid config id!');
- if ($this->areAllModulesUpToDate())
- return $this->mark('OK');
- return $this->mark('OUTDATED');
+ ErrorHandler::traceError('ConfigTgz::markUpdated called with invalid config id!');
+ if ($this->areAllModulesUpToDate()) {
+ if (empty($task['data']['warnings'])) {
+ $warnings = '';
+ } else {
+ // There have been warnings while generating the combined archive
+ // Most likely duplicate file entries.
+ // Get mapping of moduleid to module name for prettier log display
+ $res = Database::simpleQuery('SELECT moduleid, title FROM configtgz_module');
+ $mods = [];
+ foreach ($res as $row) {
+ $mods[$row['moduleid']] = $row['title'];
+ }
+ // Now extract module id from filename and if applicable, replace filename by module name
+ $warnings = preg_replace_callback('#/opt/openslx/configs/modules/(\w+)_id-(\d+)__.*$#m', function ($m) use ($mods) {
+ if (!isset($mods[$m[2]]))
+ return $m[0];
+ return $mods[$m[2]] . ' (' . $m[1] . '#' . $m[2] . ')';
+ }, $task['data']['warnings']);
+ }
+ Database::exec("UPDATE configtgz SET status = :status, warnings = :warnings
+ WHERE configid = :configid LIMIT 1", [
+ 'configid' => $this->configId,
+ 'status' => 'OK',
+ 'warnings' => $warnings,
+ ]);
+ return;
+ }
+ $this->mark('OUTDATED');
}
- private function markFailed()
+ private function markFailed(): void
{
if ($this->configId === 0)
- Util::traceError('ConfigTgz::markFailed called with invalid config id!');
- if ($this->file === false || !file_exists($this->file))
- return $this->mark('MISSING');
- return $this->mark('OUTDATED');
+ ErrorHandler::traceError('ConfigTgz::markFailed called with invalid config id!');
+ if (empty($this->file) || !file_exists($this->file)) {
+ $this->mark('MISSING');
+ } else {
+ $this->mark('OUTDATED');
+ }
}
- private function mark($status)
+ private function mark($status): void
{
- Database::exec("UPDATE configtgz SET status = :status WHERE configid = :configid LIMIT 1", array(
+ Database::exec("UPDATE configtgz SET status = :status WHERE configid = :configid LIMIT 1", [
'configid' => $this->configId,
- 'status' => $status
- ));
- return $status;
+ 'status' => $status,
+ ]);
}
/*
@@ -191,28 +220,30 @@ class ConfigTgz
*/
/**
- * @param string[] $files source files to include
+ * @param bool[] $files source files to include key = file, value = dupCheck
* @param string $destFile where to store final result
* @return false|array taskmanager task
*/
- private static function recompress($files, $destFile)
+ private static function recompress(array $files, string $destFile, ?string $parentTask = null)
{
// Get stuff other modules want to inject
$handler = function($hook) {
include $hook->file;
- return isset($file) ? $file : false;
+ return $file ?? false;
};
foreach (Hook::load('config-tgz') as $hook) {
$file = $handler($hook);
if ($file !== false) {
- $files[] = $file;
+ $files[$file] = true;
}
}
// Hand over to tm
return Taskmanager::submit('RecompressArchive', array(
'inputFiles' => $files,
- 'outputFile' =>$destFile
+ 'outputFile' =>$destFile,
+ 'parentTask' => $parentTask,
+ 'failOnParentFail' => false,
));
}
@@ -221,57 +252,27 @@ class ConfigTgz
* on each one. This mostly makes sense to call if a global module
* that is injected via a hook has changed.
*/
- public static function rebuildAllConfigs()
+ public static function rebuildAllConfigs(): void
{
Database::exec("UPDATE configtgz SET status = :status", array(
'status' => 'OUTDATED'
));
$res = Database::simpleQuery("SELECT configid FROM configtgz");
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ foreach ($res as $row) {
$configTgz = self::get($row['configid']);
- if ($configTgz !== false) {
+ if ($configTgz !== null) {
$configTgz->generate();
}
}
- // Build the global "empty" config that just includes global hooks
- self::rebuildEmptyGlobalConfig();
- }
-
- /**
- * Rebuild the general "empty" config that only contains global hook modules
- * and forced ones.
- */
- private static function rebuildEmptyGlobalConfig()
- {
- static $onceOnly = false;
- if ($onceOnly)
- return;
- $onceOnly = true;
- // HACK TODO XXX -- just stuff (global) ssh config into this one for now, needs proper fix :-(
- $res = Database::simpleQuery("SELECT DISTINCT cm.filepath FROM configtgz_module cm
- INNER JOIN configtgz_x_module cxm USING (moduleid)
- INNER JOIN configtgz_location cl USING (configid)
- WHERE cm.moduletype = 'SshConfig' AND cm.status = 'OK'
- ORDER BY locationid ASC");
- $extra = [];
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
- if (file_exists($row['filepath'])) {
- $extra[] = $row['filepath'];
- break;
- }
- }
- self::recompress($extra, SysConfig::GLOBAL_MINIMAL_CONFIG);
}
/**
* @param string $title Title of config
* @param int[] $moduleIds Modules to include in config
- * @return false|ConfigTgz The module instance, false on error
+ * @return ConfigTgz The module instance
*/
- public static function insert($title, $moduleIds)
+ public static function insert(string $title, array $moduleIds): ConfigTgz
{
- if (!is_array($moduleIds))
- return false;
$instance = new ConfigTgz;
$instance->configTitle = $title;
// Create output file name (config.tgz)
@@ -293,7 +294,7 @@ class ConfigTgz
}
$res = Database::simpleQuery("SELECT moduleid, moduletype, filepath, status FROM configtgz_module WHERE moduleid IN ($idstr)");
// Make connection
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ foreach ($res as $row) {
Database::exec("INSERT INTO configtgz_x_module (configid, moduleid) VALUES (:configid, :moduleid)", array(
'configid' => $instance->configId,
'moduleid' => $row['moduleid']
@@ -303,53 +304,52 @@ class ConfigTgz
return $instance;
}
- public static function get($configId)
+ /**
+ * @param array{configid: int, title: string, filepath: string} $row Input data, fields mandatory
+ */
+ private static function instanceFromRow(array $row): ConfigTgz
{
- $ret = Database::queryFirst("SELECT configid, title, filepath FROM configtgz WHERE configid = :configid", array(
- 'configid' => $configId
- ));
- if ($ret === false)
- return false;
$instance = new ConfigTgz;
- $instance->configId = $ret['configid'];
- $instance->configTitle = $ret['title'];
- $instance->file = $ret['filepath'];
- $ret = Database::simpleQuery("SELECT moduleid, moduletype, filepath, status FROM configtgz_x_module "
+ $instance->configId = $row['configid'];
+ $instance->configTitle = $row['title'];
+ $instance->file = $row['filepath'];
+ $innerRes = Database::simpleQuery("SELECT moduleid, moduletype, filepath, status FROM configtgz_x_module "
. " INNER JOIN configtgz_module USING (moduleid) "
. " WHERE configid = :configid", array('configid' => $instance->configId));
$instance->modules = array();
- while ($row = $ret->fetch(PDO::FETCH_ASSOC)) {
- $instance->modules[] = $row;
+ foreach ($innerRes as $innerRow) {
+ $instance->modules[] = $innerRow;
}
return $instance;
}
+ public static function get(int $configId): ?ConfigTgz
+ {
+ $ret = Database::queryFirst("SELECT configid, title, filepath FROM configtgz WHERE configid = :configid",
+ ['configid' => $configId]);
+ if ($ret === false)
+ return null;
+ return self::instanceFromRow($ret);
+ }
+
/**
* @param int $moduleId ID of config module
- * @return ConfigTgz[]|false
+ * @return ConfigTgz[]
*/
- public static function getAllForModule($moduleId)
+ public static function getAllForModule(int $moduleId): array
{
$res = Database::simpleQuery("SELECT configid, title, filepath FROM configtgz_x_module "
. " INNER JOIN configtgz USING (configid) "
. " WHERE moduleid = :moduleid", array(
'moduleid' => $moduleId
));
- if ($res === false)
- return false;
+ if ($res === false) {
+ EventLog::warning('ConfigTgz::getAllForModule failed: ' . Database::lastError());
+ return [];
+ }
$list = array();
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
- $instance = new ConfigTgz;
- $instance->configId = $row['configid'];
- $instance->configTitle = $row['title'];
- $instance->file = $row['filepath'];
- $innerRes = Database::simpleQuery("SELECT moduleid, moduletype, filepath, status FROM configtgz_x_module "
- . " INNER JOIN configtgz_module USING (moduleid) "
- . " WHERE configid = :configid", array('configid' => $instance->configId));
- $instance->modules = array();
- while ($innerRow = $innerRes->fetch(PDO::FETCH_ASSOC)) {
- $instance->modules[] = $innerRow;
- }
+ foreach ($res as $row) {
+ $instance = self::instanceFromRow($row);
$list[] = $instance;
}
return $list;
@@ -359,50 +359,52 @@ class ConfigTgz
* Called when (re)generating a config tgz failed, so we can
* update the status in the DB and add a server log entry.
*
- * @param array $task
* @param array $args contains 'configid' and optionally 'deleteOnError'
*/
- public static function generateFailed($task, $args)
+ public static function generateFailed(array $task, array $args): void
{
if (!isset($args['configid']) || !is_numeric($args['configid'])) {
EventLog::warning('Ignoring generateFailed event as it has no configid assigned.');
return;
}
$config = self::get($args['configid']);
- if ($config === false) {
+ if ($config === null) {
EventLog::warning('generateFailed callback for config id ' . $args['configid'] . ', but no instance could be generated.');
return;
}
- if (isset($task['data']['error']))
+ if (isset($task['data']['error'])) {
$error = $task['data']['error'];
- elseif (isset($task['data']['messages']))
+ } elseif (isset($task['data']['messages'])) {
$error = $task['data']['messages'];
- else
+ } else {
$error = '';
+ }
EventLog::failure("Generating config.tgz '" . $config->configTitle . "' failed.", $error);
- if ($args['deleteOnError'])
+ if ($args['deleteOnError']) {
$config->delete();
- else
+ } else {
$config->markFailed();
+ }
}
/**
* (Re)generating a config tgz succeeded. Update db entry.
*
+ * @param array $task the task object
* @param array $args contains 'configid' and optionally 'deleteOnError'
*/
- public static function generateSucceeded($args)
+ public static function generateSucceeded(array $task, array $args): void
{
if (!isset($args['configid']) || !is_numeric($args['configid'])) {
EventLog::warning('Ignoring generateSucceeded event as it has no configid assigned.');
return;
}
$config = self::get($args['configid']);
- if ($config === false) {
+ if ($config === null) {
EventLog::warning('generateSucceeded callback for config id ' . $args['configid'] . ', but no instance could be generated.');
return;
}
- $config->markUpdated();
+ $config->markUpdated($task);
}
-
+
}
diff --git a/modules-available/sysconfig/inc/ldap.inc.php b/modules-available/sysconfig/inc/ldap.inc.php
index 349a662e..e974a8a3 100644
--- a/modules-available/sysconfig/inc/ldap.inc.php
+++ b/modules-available/sysconfig/inc/ldap.inc.php
@@ -3,7 +3,7 @@
class Ldap
{
- public static function normalizeDn($dn)
+ public static function normalizeDn(string $dn): string
{
return trim(preg_replace('/[,;]\s*/', ',', $dn));
}
diff --git a/modules-available/sysconfig/inc/ppd.inc.php b/modules-available/sysconfig/inc/ppd.inc.php
index 5ccdbd53..a7fc7acb 100644
--- a/modules-available/sysconfig/inc/ppd.inc.php
+++ b/modules-available/sysconfig/inc/ppd.inc.php
@@ -134,7 +134,7 @@ class Ppd
'JCLEnd' => '.*',
// TODO: The above three need to be either completely absent, or all three must be defined
/*
- * Resolution and Appearence Control, section 5.9
+ * Resolution and Appearance Control, section 5.9
*/
/*
* Gray Levels and Halftoning, section 5.10
@@ -247,8 +247,8 @@ class Ppd
private $data;
private $dataLen;
- private $error;
- private $warnings;
+ private $error = null;
+ private $warnings = [];
private $knownKeywordMalformed;
@@ -301,16 +301,16 @@ class Ppd
$this->dataLen = strlen($this->data);
$this->encoder = false;
$this->sourceEncoding = false;
- $this->error = false;
+ $this->error = null;
$this->warnings = array();
$this->knownKeywordMalformed = false;
$this->settings = array();
$this->requiredKeywords = array();
// Parse
- /* @var $rawOption \PpdOption */
- /* @var $currentBlock \PpdBlockInternal */
- $currentBlock = false;
+ /* @var PpdOption $rawOption */
+ /* @var ?PpdBlockInternal $currentBlock */
+ $currentBlock = null;
$inRawBlock = false; // True if in a multi-line InvocationValue or QuotedValue (3.6: Parsing Summary for Values)
$wantsEnd = false;
// For now we ignore values mostly while parsing. The spec says that InvocationValues must only contain printable
@@ -318,9 +318,9 @@ class Ppd
$lStart = -1;
$lEnd = -1;
$no = 0;
- while ($lStart < $this->dataLen && $lEnd !== false) {
+ while ($lStart < $this->dataLen) {
unset($mainKeyword, $optionKeyword, $optionTranslation, $option, $value, $valueTranslation);
- if ($no !== 0 && $this->data{$lEnd} === "\r" && $this->data{$lEnd + 1} === "\n") {
+ if ($no !== 0 && $this->data[$lEnd] === "\r" && $this->data[$lEnd + 1] === "\n") {
$lEnd++;
}
if ($no === 1) {
@@ -332,6 +332,8 @@ class Ppd
}
$lStart = $lEnd + 1;
$lEnd = $this->nextLineEnd($lStart);
+ if ($lEnd === null)
+ break;
$no++;
// Validate
$len = $lEnd - $lStart;
@@ -343,7 +345,7 @@ class Ppd
$this->warn($no, 'Exceeds length of 255');
}
if (!$inRawBlock && preg_match_all('/[^\x09\x0A\x0D\x20-\xFF]/', $line, $out)) {
- $chars = $this->escapeBinaryArray($out[0]);
+ $chars = self::escapeBinaryArray($out[0]);
$this->warn($no, 'Contains invalid character(s) ' . $chars);
}
// Handle
@@ -373,13 +375,13 @@ class Ppd
}
}
// 3) Handle "key [option]: value"
- if ($line{0} === '*') {
- if ($line{1} === '%') {
+ if ($line[0] === '*') {
+ if ($line[1] === '%') {
// Skip comment
continue;
}
$parts = preg_split('/\s*:\s*/', $line, 2); // TODO: UIConstrains
- if (count($parts) !== 2) {
+ if (!is_array($parts) || count($parts) !== 2) {
$this->warn($no, 'No colon found; not in "key [option]: value" format, ignoring line');
continue;
}
@@ -390,11 +392,12 @@ class Ppd
continue;
}
$mainKeyword = $out[1];
- $optionKeyword = isset($out[3]) ? $out[3] : false;
+ $optionKeyword = $out[3] ?? null;
$optionTranslation = isset($out[4]) ? $this->unhexTranslation($no, substr($out[4], 1)) : $optionKeyword; // If no translation given, fallback to option
// 3b) Handle value
+ /** @var string $value */
$value = $parts[1];
- if ($value{0} === '"') {
+ if ($value[0] === '"') {
// Start of InvocationValue or QuotedValue
if (preg_match(',^"([^"]*)"(/.*)?$,', $value, $vMatch)) {
// Single line
@@ -417,30 +420,25 @@ class Ppd
$valueTranslation = $value;
}
// Key-value-pair parsed, now the fun part
- // Special cases for openening closing certain groups
+ // Special cases for opening closing certain groups
if ($mainKeyword === 'OpenGroup') {
- if ($currentBlock !== false) {
+ if ($currentBlock !== null) {
$this->error = 'Line ' . $no . ': OpenGroup while other block (type=' . $currentBlock->type
. ', id=' . $currentBlock->id . ') was not closed yet';
return;
}
// TODO: Check unique
- $nb = new PpdBlockInternal($value, $valueTranslation, 'Group', $currentBlock, $lStart);
- if ($currentBlock !== false) {
- $currentBlock->childBlocks[] = $nb;
- }
+ $nb = new PpdBlockInternal($value, $valueTranslation, 'Group', null, $lStart);
$currentBlock = $nb;
continue;
} elseif ($mainKeyword === 'OpenSubGroup') {
- if ($currentBlock === false || $currentBlock->type !== 'Group') {
- $this->error = 'Line ' . $no . ': OpenSubGroup with no preceeding OpenGroup';
+ if ($currentBlock === null || $currentBlock->type !== 'Group') {
+ $this->error = 'Line ' . $no . ': OpenSubGroup with no preceding OpenGroup';
return;
}
// TODO: Check unique
$nb = new PpdBlockInternal($value, $valueTranslation, 'SubGroup', $currentBlock, $lStart);
- if ($currentBlock !== false) {
- $currentBlock->childBlocks[] = $nb;
- }
+ $currentBlock->childBlocks[] = $nb;
$currentBlock = $nb;
continue;
} elseif ($mainKeyword === 'OpenUI' || $mainKeyword === 'JCLOpenUI') {
@@ -450,23 +448,24 @@ class Ppd
} else {
$type = substr($type, 4);
}
- if ($currentBlock !== false && $currentBlock->isUi()) {
+ if ($currentBlock !== null && $currentBlock->isUi()) {
$this->error = 'Line ' . $no . ': ' . $mainKeyword . ' while previous ' . $type . ' "'
. $currentBlock->id . '" was not closed yet';
return;
}
- if ($optionKeyword === false) {
+ if ($optionKeyword === null) {
$this->error = 'Line ' . $no . ': ' . $mainKeyword . ' with no option keyword';
return;
}
- if ($optionKeyword{0} !== '*') {
+ if ($optionKeyword[0] !== '*') {
$this->error = 'Line ' . $no . ': ' . $mainKeyword . " with option keyword that doesn't start with asterisk (*).";
return;
}
// TODO: Check unique
- $nb = new PpdBlockInternal($optionKeyword, $optionTranslation, $type, $currentBlock, $lStart);
+ $nb = new PpdBlockInternal($optionKeyword, $optionTranslation ?? $optionKeyword,
+ $type, $currentBlock, $lStart);
$nb->value = $value;
- if ($currentBlock !== false) {
+ if ($currentBlock !== null) {
$currentBlock->childBlocks[] = $nb;
}
$currentBlock = $nb;
@@ -481,7 +480,7 @@ class Ppd
} else {
$type = substr($type, 5);
}
- if ($currentBlock === false) {
+ if ($currentBlock === null) {
$this->error = 'Line ' . $no . ': ' . $mainKeyword . ' with no Open' . $type;
return;
}
@@ -498,7 +497,7 @@ class Ppd
$currentBlock = $currentBlock->parent;
continue;
} elseif ($mainKeyword === 'OrderDependency') {
- if ($currentBlock === false || $currentBlock->isUi()) {
+ if ($currentBlock === null || $currentBlock->isUi()) {
$this->warn($no, 'OrderDependency outside OpenUI/CloseUI block');
}
continue;
@@ -521,9 +520,10 @@ class Ppd
$this->warn($no, 'Required keyword ' . $mainKeyword . ' declared twice, ignoring');
continue;
}
+ } else {
+ $this->requiredKeywords[$mainKeyword] = array($value);
}
- $this->requiredKeywords[$mainKeyword] = array($value);
- if (($err = $this->validateLine($this->REQUIRED_KEYWORDS[$mainKeyword], $optionKeyword, $value)) !== true) {
+ if (($err = Ppd::validateLine($this->REQUIRED_KEYWORDS[$mainKeyword], $optionKeyword, $value)) !== true) {
$this->warn($no, 'Required main keyword ' . $mainKeyword . ': ' . $err);
$this->knownKeywordMalformed = true;
}
@@ -531,7 +531,7 @@ class Ppd
}
// Other well known keywords
if (isset($this->KNOWN_KEYWORDS[$mainKeyword])) {
- if (($err = $this->validateLine($this->KNOWN_KEYWORDS[$mainKeyword], $optionKeyword, $value)) !== true) {
+ if (($err = Ppd::validateLine($this->KNOWN_KEYWORDS[$mainKeyword], $optionKeyword, $value)) !== true) {
$this->warn($no, 'Known main keyword ' . $mainKeyword . ': ' . $err);
$this->knownKeywordMalformed = true;
}
@@ -541,17 +541,18 @@ class Ppd
$option = $this->getOption(substr($mainKeyword, 7), $currentBlock);
$option->default = new PpdOption($lStart, $len, $value, $valueTranslation);
continue;
- } elseif (substr($mainKeyword, 0, 17) === 'FoomaticRIPOption') {
- if ($optionKeyword === false) {
+ }
+ if (substr($mainKeyword, 0, 17) === 'FoomaticRIPOption') {
+ if ($optionKeyword === null) {
$this->warn($no, "$mainKeyword with no option keyword");
- } elseif ($currentBlock !== false && isset($this->settings[$optionKeyword])) {
+ } elseif ($currentBlock !== null && isset($this->settings[$optionKeyword])) {
$option = $this->getOption($optionKeyword, $currentBlock);
$option->foomatic[substr($mainKeyword, 11)] = new PpdOption($lStart, $len, $value, $valueTranslation);
} else {
$this->warn($no, 'TODO: ' . $line);
}
} elseif (substr($mainKeyword, 0, 6) === 'Custom') {
- if ($optionKeyword === false) {
+ if ($optionKeyword === null) {
$this->warn($no, "$mainKeyword with no option keyword");
} elseif ($optionKeyword !== 'True') {
$this->warn($no, "$mainKeyword with option keyword other than 'True'; ignored");
@@ -560,7 +561,7 @@ class Ppd
$option->custom = new PpdOption($lStart, $len, $value, $valueTranslation);
}
} elseif (substr($mainKeyword, 0, 11) === 'ParamCustom') {
- if ($optionKeyword === false) {
+ if ($optionKeyword === null) {
$this->warn($no, "$mainKeyword with no option keyword");
} elseif (substr($mainKeyword, 11) !== $optionKeyword) {
$this->warn($no, "Don't know how to handle $mainKeyword with option keyword $optionKeyword "
@@ -569,20 +570,20 @@ class Ppd
$option = $this->getOption($optionKeyword, $currentBlock);
$option->customParam = new PpdOption($lStart, $len, $value, $valueTranslation);
}
- } elseif ($mainKeyword{0} === '?') {
+ } elseif ($mainKeyword[0] === '?') {
// Ignoring option query for now
- } elseif ($optionKeyword === false && !isset($this->KNOWN_KEYWORDS[$mainKeyword])) {
+ } elseif ($optionKeyword === null && !isset($this->KNOWN_KEYWORDS[$mainKeyword])) {
// Must be a definition for an option
$this->warn($no, "Don't know how to handle line with main keyword '$mainKeyword', no option keyword found.");
} else {
// Some option for some option ;)
- if ($optionKeyword === false) {
+ if ($optionKeyword === null) {
// We know that this is a known main keyword otherwise we would have hit the previous elseif block
$optionKeyword = $value;
$optionTranslation = $valueTranslation;
}
$option = $this->getOption($mainKeyword, $currentBlock);
- $optionInstance = new PpdOption($lStart, $len, $optionKeyword, $optionTranslation);
+ $optionInstance = new PpdOption($lStart, $len, $optionKeyword, $optionTranslation ?? $optionKeyword);
if ($this->binary_in_array($mainKeyword, $this->REPEATED_KEYWORDS)) {
// This can occur multiple times, just pile them up
$option->values[] = $optionInstance;
@@ -603,9 +604,9 @@ class Ppd
} elseif (strlen(trim($line)) !== 0) {
$this->warn($no, 'Invalid format; not empty and not starting with asterisk (*)');
}
- }
+ } // end while loop over ppd contents
//
- if ($currentBlock !== false) {
+ if ($currentBlock !== null) {
$this->error = 'Block ' . $currentBlock->id . ' (' . $currentBlock->type . ') was never closed.';
return;
}
@@ -615,11 +616,11 @@ class Ppd
$this->error = 'One or more required keywords missing';
}
}
- if ($this->error !== false) {
+ if ($this->error !== null) {
return;
}
// All required keywords exist
- if (preg_match('/utf\-?8/i', $this->requiredKeywords['LanguageEncoding'][0])) {
+ if (preg_match('/utf-?8/i', $this->requiredKeywords['LanguageEncoding'][0])) {
$this->sourceEncoding = false; // Would be a NOOP
} elseif (isset($this->ENCODINGS[$this->requiredKeywords['LanguageEncoding'][0]])) {
$this->sourceEncoding = $this->ENCODINGS[$this->requiredKeywords['LanguageEncoding'][0]];
@@ -669,12 +670,12 @@ class Ppd
}
}
- private function nextLineEnd($start)
+ private function nextLineEnd(int $start): ?int
{
if ($start >= $this->dataLen)
- return false;
+ return null;
while ($start < $this->dataLen) {
- $char = $this->data{$start};
+ $char = $this->data[$start];
if ($char === "\r" || $char === "\n")
return $start;
++$start;
@@ -682,27 +683,26 @@ class Ppd
return $this->dataLen;
}
- private function warn($lineNo, $message)
+ private function warn(int $lineNo, string $message): void
{
$line = 'Line ' . $lineNo . ': ' . $message;
$this->warnings[] = $line;
}
- private function escapeBinaryArray($array)
+ private static function escapeBinaryArray(array $array): string
{
- $chars = array_reduce(array_unique($array), function ($carry, $item) {
+ return array_reduce(array_unique($array), function ($carry, $item) {
return $carry . '\x' . dechex(ord($item));
}, '');
- return $chars;
}
- private function unhexTranslation($lineNo, $translation)
+ private function unhexTranslation(int $lineNo, string $translation): string
{
if (strpos($translation, '<') === false)
return $translation;
return preg_replace_callback('/<[^>]*>/', function ($match) use ($lineNo) {
if (preg_match_all('/[^a-fA-F0-9\<\>\s]/', $match[0], $out)) {
- $this->warn($lineNo, 'Invalid character(s) in hex substring: ' . $this->escapeBinaryArray($out[0]));
+ $this->warn($lineNo, 'Invalid character(s) in hex substring: ' . self::escapeBinaryArray($out[0]));
}
$string = preg_replace('/[^a-fA-F0-9]/', '', $match[0]);
if (strlen($string) % 2 !== 0) {
@@ -713,7 +713,7 @@ class Ppd
}, $translation);
}
- private function hexTranslation($translation)
+ private function hexTranslation(string $translation): string
{
return preg_replace_callback('/[\x00-\x1f\x7b-\xff\:\<\>]+/', function ($match) {
return '<' . unpack('H*', $match[0])[1] . '>';
@@ -724,23 +724,23 @@ class Ppd
* Get option object
*
* @param string $name option name
- * @param \PpdBlockInternal $block which block this option is defined in
- * @return \PpdSettingInternal the option object
+ * @param ?PpdBlockInternal $block which block this option is defined in
+ * @return PpdSettingInternal the option object
*/
- private function getOption($name, $block = false)
+ private function getOption(string $name, ?PpdBlockInternal $block = null): PpdSettingInternal
{
if (!isset($this->settings[$name])) {
$this->settings[$name] = new PpdSettingInternal();
$this->settings[$name]->block = $block;
- } elseif ($block !== false) {
- if ($this->settings[$name]->block === false || $block->isChildOf($this->settings[$name]->block)) {
+ } elseif ($block !== null) {
+ if ($this->settings[$name]->block === null || $block->isChildOf($this->settings[$name]->block)) {
$this->settings[$name]->block = $block;
}
}
return $this->settings[$name];
}
- private function binary_in_array($elem, $array)
+ private function binary_in_array($elem, $array): bool
{
$top = sizeof($array) - 1;
$bot = 0;
@@ -755,7 +755,7 @@ class Ppd
return false;
}
- private function validateLine($validator, $option, $value)
+ private static function validateLine($validator, ?string $option, string $value)
{
if (is_array($validator)) {
$oExp = $validator[0];
@@ -780,7 +780,7 @@ class Ppd
return true;
}
- private function getEolChar()
+ private function getEolChar(): string
{
$rn = substr_count("\r\n", $this->data);
$r = substr_count("\r", $this->data) - $rn;
@@ -799,21 +799,21 @@ class Ppd
*
*/
- public function getError()
+ public function getError(): ?string
{
return $this->error;
}
- public function getWarnings()
+ public function getWarnings(): array
{
return $this->warnings;
}
- public function getUISettings()
+ public function getUISettings(): array
{
$result = array();
foreach ($this->settings as $mk => $option) {
- $isUi = ($option->block !== false && $option->block->isUi()) || isset($this->UI_KEYWORDS[$mk]);
+ $isUi = ($option->block !== null && $option->block->isUi()) || isset($this->UI_KEYWORDS[$mk]);
if ($isUi) {
$result[] = $mk;
}
@@ -821,30 +821,30 @@ class Ppd
return $result;
}
- public function getSetting($name)
+ public function getSetting(string $name): ?PpdSetting
{
if (!isset($this->settings[$name]))
- return false;
+ return null;
return new PpdSetting($this->settings[$name], isset($this->UI_KEYWORDS[$name]), $this->encoder);
}
- public function removeSetting($name)
+ public function removeSetting(string $name): bool
{
if (!isset($this->settings[$name]))
return false;
$setting = $this->settings[$name];
$ranges = array();
- $this->mergeRanges($ranges, $setting->default);
- $this->mergeRanges($ranges, $setting->custom);
- $this->mergeRanges($ranges, $setting->customParam);
+ Ppd::mergeRangesFromOption($ranges, $setting->default);
+ Ppd::mergeRangesFromOption($ranges, $setting->custom);
+ Ppd::mergeRangesFromOption($ranges, $setting->customParam);
foreach ($setting->foomatic as $obj) {
- $this->mergeRanges($ranges, $obj);
+ Ppd::mergeRangesFromOption($ranges, $obj);
}
foreach ($setting->values as $obj) {
- $this->mergeRanges($ranges, $obj);
+ Ppd::mergeRangesFromOption($ranges, $obj);
}
- if ($setting->block !== false && $setting->block->isUi()) {
- $this->mergeRanges($ranges, $setting->block->start, $setting->block->end);
+ if ($setting->block !== null && $setting->block->isUi()) {
+ Ppd::mergeRanges($ranges, $setting->block->start, $setting->block->end);
}
$tmp = array_map(function ($e) { return $e[0]; }, $ranges);
array_multisort($tmp, SORT_NUMERIC, $ranges);
@@ -853,20 +853,20 @@ class Ppd
foreach ($ranges as $range) {
$new .= substr($this->data, $last, $range[0] - $last);
$last = $range[1];
- if ($this->data{$last} === "\r") {
+ if ($this->data[$last] === "\r") {
$last++;
}
- if ($this->data{$last} === "\n") {
+ if ($this->data[$last] === "\n") {
$last++;
}
}
$new .= substr($this->data, $last);
$this->data = $new;
$this->parse();
- return $this->error === false;
+ return $this->error === null;
}
- public function addEmptyOption($settingName, $option, $translation = false, $prepend = true)
+ public function addEmptyOption(string $settingName, string $option, ?string $translation = null, ?bool $prepend = true): bool
{
if (!isset($this->settings[$settingName]))
return false;
@@ -874,15 +874,15 @@ class Ppd
$pos = false;
if (!empty($setting->values)) {
if ($prepend) {
- $pos = array_reduce($setting->values, function ($carry, $option) { return min($carry, $option->lineOffset); }, PHP_INT_MAX);
+ $pos = array_reduce($setting->values, function (int $carry, PpdOption $option) { return min($carry, $option->lineOffset); }, PHP_INT_MAX);
} else {
- $pos = array_reduce($setting->values, function ($carry, $option) { return max($carry, $option->lineOffset); }, 0);
+ $pos = array_reduce($setting->values, function (int $carry, PpdOption $option) { return max($carry, $option->lineOffset); }, 0);
}
- } elseif ($setting->default !== false) {
+ } elseif ($setting->default !== null) {
$pos = $setting->default->lineOffset;
- } elseif ($setting->block !== false && $setting->block->isUi()) {
+ } elseif ($setting->block !== null && $setting->block->isUi()) {
$pos = $this->nextLineEnd($setting->block->start);
- while ($pos !== false && $pos < $this->dataLen && ($this->data{$pos} === "\r" || $this->data{$pos} === "\n")) {
+ while ($pos !== null && $pos < $this->dataLen && ($this->data[$pos] === "\r" || $this->data[$pos] === "\n")) {
$pos++;
}
}
@@ -890,23 +890,23 @@ class Ppd
return false;
}
$line = '*' . $settingName . ' ' . $option;
- if ($translation !== false) {
+ if ($translation !== null) {
$line .= '/' . $this->hexTranslation(($this->encoder)($translation, true));
}
$eol = $this->getEolChar();
$line .= ': ""' . $eol;
$this->data = substr($this->data, 0, $pos) . $line . substr($this->data, $pos);
$this->parse();
- return $this->error === false;
+ return $this->error === null;
}
- public function setDefaultOption($settingName, $optionName)
+ public function setDefaultOption($settingName, $optionName): bool
{
if (!isset($this->settings[$settingName]))
return false;
$setting = $this->settings[$settingName];
$line = '*Default' . $settingName . ': ' . $optionName;
- if ($setting->default !== false) {
+ if ($setting->default !== null) {
$start = $setting->default->lineOffset;
$end = $start + $setting->default->lineLen;
} elseif (empty($setting->values)) {
@@ -918,22 +918,21 @@ class Ppd
}
$this->data = substr($this->data, 0, $start) . $line . substr($this->data, $end);
$this->parse();
- return $this->error === false;
+ return $this->error === null;
}
- public function write($file)
+ public function write(string $file)
{
return file_put_contents($file, $this->data);
}
- private function mergeRanges(&$ranges, $start, $end = false)
+ private static function mergeRangesFromOption(array &$ranges, PpdOption $option): void
+ {
+ self::mergeRanges($ranges, $option->lineOffset, $option->lineOffset + $option->lineLen);
+ }
+
+ private static function mergeRanges(array &$ranges, int $start, int $end): void
{
- if (is_object($start) && get_class($start) === 'PpdOption') {
- $end = $start->lineOffset + $start->lineLen;
- $start = $start->lineOffset;
- }
- if ($start === false || $end === false)
- return;
if ($start >= $end)
return; // Don't even bother
foreach (array_keys($ranges) as $key) {
@@ -956,7 +955,7 @@ class Ppd
// $start must lie before range start, otherwise we'd have hit the case above
$end = $ranges[$key][1];
unset($ranges[$key]);
- continue;
+ //continue;
}
}
$ranges[] = array($start, $end);
@@ -965,7 +964,7 @@ class Ppd
/**
* @return bool whether there was at least one known option with format restriction violated.
*/
- public function hasInvalidOption()
+ public function hasInvalidOption(): bool
{
return $this->knownKeywordMalformed;
}
@@ -1013,15 +1012,13 @@ class PpdSetting
/**
* PpdSetting constructor.
- *
- * @param \PpdSettingInternal $setting
*/
- public function __construct($setting, $isUi, $enc)
+ public function __construct(PpdSettingInternal $setting, bool $isUi, callable $enc)
{
- if ($setting->default !== false) {
+ if ($setting->default !== null) {
$this->default = $setting->default->option;
}
- if ($setting->block !== false && $setting->block->isUi()) {
+ if ($setting->block !== null && $setting->block->isUi()) {
$this->uiOptionType = $setting->block->value;
$this->uiOptionTranslation = $enc($setting->block->translation);
$this->isUi = true;
@@ -1055,9 +1052,9 @@ class PpdSetting
class PpdSettingInternal
{
/**
- * @var \PpdOption
+ * @var ?PpdOption
*/
- public $default = false;
+ public $default = null;
/**
* @var \PpdOption[]
*/
@@ -1067,17 +1064,17 @@ class PpdSettingInternal
*/
public $foomatic = array();
/**
- * @var \PpdOption
+ * @var ?PpdOption
*/
- public $custom = false;
+ public $custom = null;
/**
- * @var \PpdOption
+ * @var ?PpdOption
*/
- public $customParam = false;
+ public $customParam = null;
/**
- * @var \PpdBlockInternal the innermost block this option resides in
+ * @var ?PpdBlockInternal the innermost block this option resides in
*/
- public $block = false;
+ public $block = null;
}
class PpdOption
@@ -1088,7 +1085,7 @@ class PpdOption
public $lineLen;
public $multiLine = false;
- public function __construct($lineOffset, $lineLen, $option, $optionTranslation)
+ public function __construct(int $lineOffset, int $lineLen, string $option, string $optionTranslation)
{
$this->option = $option;
$this->optionTranslation = $optionTranslation;
@@ -1106,13 +1103,13 @@ class PpdBlockInternal
public $translation;
public $type;
/**
- * @var \PpdBlockInternal[]
+ * @var PpdBlockInternal[]
*/
public $childBlocks = array();
/**
- * @var \PpdBlockInternal
+ * @var ?PpdBlockInternal
*/
- public $parent;
+ public $parent = null;
/**
* @var int start byte in ppd
@@ -1120,16 +1117,16 @@ class PpdBlockInternal
public $start;
/**
- * @var int|bool end byte in ppd, false if block is not closed
+ * @var ?int end byte in ppd, null if block is not closed
*/
- public $end = false;
+ public $end = null;
/**
* @var string value of opening line for block, e.g. 'PickOne' for OpenUI
*/
- public $value = false;
+ public $value = '';
- public function __construct($id, $translation, $type, $parent, $start)
+ public function __construct(string $id, string $translation, string $type, ?PpdBlockInternal $parent, int $start)
{
$this->id = $id;
$this->translation = $translation;
@@ -1141,16 +1138,16 @@ class PpdBlockInternal
/**
* @return bool true if this is a UI block
*/
- public function isUi()
+ public function isUi(): bool
{
return $this->type == 'UI' || $this->type === 'JCLUI';
}
/**
- * @param \PpdBlockInternal $block some other PpdBlock instance
+ * @param PpdBlockInternal $block some other PpdBlock instance
* @return bool true if this is a child of $block
*/
- public function isChildOf($block)
+ public function isChildOf(PpdBlockInternal $block): bool
{
$parent = $this->parent;
while ($parent !== false) {
diff --git a/modules-available/sysconfig/inc/shib.inc.php b/modules-available/sysconfig/inc/shib.inc.php
new file mode 100644
index 00000000..c3956572
--- /dev/null
+++ b/modules-available/sysconfig/inc/shib.inc.php
@@ -0,0 +1,150 @@
+<?php
+
+class Shib
+{
+ const SHIB_LOOKUP_JSON = '/var/cache/slx-admin/idp2suffix.json';
+
+ const SHIB_DISCO_JSON = '/var/cache/slx-admin/discofeed.json';
+
+ const RET_ERR = 0;
+ const RET_UPDATE = 1;
+ const RET_UNCHANGED = 2;
+
+ /**
+ * Updates the local copy of the JSON file for mapping IDP entityIDs to scope suffixes.
+ * If the list changed, all ShibAuth modules will be rebuilt.
+ *
+ * @return bool true on success, or if list didn't change. false otherwise.
+ */
+ public static function refreshIdpSuffixMap(): bool
+ {
+ $changed = false;
+ $ret = self::updateFromUrl(CONFIG_SHIB_LOOKUP_URL . '?what=id2suffix', self::SHIB_LOOKUP_JSON);
+ if ($ret === self::RET_ERR)
+ return false;
+ if ($ret === self::RET_UPDATE) {
+ $changed = true;
+ }
+ $ret = self::updateFromUrl(CONFIG_SHIB_DISCO_URL, self::SHIB_DISCO_JSON);
+ if ($ret === self::RET_ERR)
+ return false;
+ if ($ret === self::RET_UPDATE) {
+ $changed = true;
+ }
+ if ($changed) {
+ // Rebuild any modules in the background
+ $configs = [];
+ $list = ConfigModule::getAll('ShibAuth');
+ $parent = null;
+ foreach ($list as $mod) {
+ $r = $mod->generate(false, $parent);
+ if (is_string($r)) {
+ $parent = $r;
+ }
+ foreach (ConfigTgz::getAllForModule($mod->id()) as $tgz) {
+ $configs[$tgz->id()] = $tgz;
+ }
+ }
+ foreach ($configs as $tgz) {
+ $r = $tgz->generate(false, 0, $parent);
+ if (is_string($r)) {
+ $parent = $r;
+ }
+ }
+ }
+ return $ret;
+ }
+
+ private static function updateFromUrl(string $url, string $file): int
+ {
+ if (!file_exists($file) || filemtime($file) + 86400 < time()) {
+ $code = null;
+ $ret = Download::toFile($file . '.tmp',
+ $url, 15, $code);
+ if (!$ret)
+ return self::RET_ERR;
+ if (!file_exists($file)
+ || filesize($file) !== filesize($file . '.tmp')
+ || sha1_file($file, true) !== sha1_file($file . '.tmp', true)
+ ) {
+ rename($file . '.tmp', $file);
+ if ($code >= 200 && $code < 300)
+ return self::RET_UPDATE;
+ return self::RET_ERR; // Not 2xx range, keep file but return error
+ }
+ unlink($file . '.tmp');
+ }
+ return self::RET_UNCHANGED;
+ }
+
+ /**
+ * @return ?array of registrars (key) containing array{"registrar": string, "list": string[]}
+ */
+ public static function getListByRegistrar(): ?array
+ {
+ $disco = json_decode(file_get_contents(self::SHIB_DISCO_JSON), true);
+ $lookup = json_decode(file_get_contents(self::SHIB_LOOKUP_JSON), true);
+ if (!is_array($disco) || !is_array($lookup)) {
+ return null;
+ }
+ foreach ($disco as $item) {
+ if (!isset($item['entityID'])) {
+ continue;
+ }
+ $id = $item['entityID'];
+ $name = null;
+ $fallback = null;
+ foreach ($item['DisplayNames'] ?? [] as $dn) {
+ if ($dn['lang'] === LANG) {
+ $name = $dn['value'];
+ } elseif ($name === null && $dn['lang'] === 'en') {
+ $name = $dn['value'];
+ } elseif ($fallback === null) {
+ $fallback = $dn['value'];
+ }
+ }
+ if ($name === null) {
+ $name = $fallback;
+ }
+ $registrar = $lookup[$id]['regauth'] ?? 'unknown';
+ if (!isset($return[$registrar])) {
+ $return[$registrar] = ['list' => [], 'registrar' => $registrar];
+ }
+ $return[$registrar]['list'][] = [
+ 'id' => $id,
+ 'name' => $name ?? $id,
+ ];
+ }
+ return $return;
+ }
+
+ public static function getIdp2SuffixList(): ?array
+ {
+ $lookup = json_decode(file_get_contents(self::SHIB_LOOKUP_JSON), true);
+ if (!is_array($lookup))
+ return null;
+ return $lookup;
+ }
+
+ /**
+ * Map given list of registrar IDs back to all the IdP entityIDs they cover.
+ * @param string[] $regs
+ * @return string[]
+ */
+ public static function explodeRegistrars(array $regs): array
+ {
+ if (empty($regs))
+ return [];
+ $idps = [];
+ $reg2idps = Shib::getListByRegistrar();
+ foreach ($regs as $reg) {
+ if (!isset($reg2idps[$reg]['list']))
+ continue;
+ foreach ($reg2idps[$reg]['list'] as $elem) {
+ $idps[] = $elem['id'];
+ }
+ }
+ return $idps;
+ }
+
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/inc/sysconfig.inc.php b/modules-available/sysconfig/inc/sysconfig.inc.php
index 13549948..362ce1a9 100644
--- a/modules-available/sysconfig/inc/sysconfig.inc.php
+++ b/modules-available/sysconfig/inc/sysconfig.inc.php
@@ -3,17 +3,41 @@
class SysConfig
{
- const GLOBAL_MINIMAL_CONFIG = '/opt/openslx/configs/config-global.tgz';
+ /**
+ * @return array{array{configid: int, title: string, filepath: string, status: string, locs: string}}
+ */
+ public static function getAll(): array
+ {
+ return Database::queryAll("SELECT c.configid, c.title, c.filepath, c.status, Group_Concat(cl.locationid) AS locs
+ FROM configtgz c
+ LEFT JOIN configtgz_location cl USING (configid) GROUP BY c.configid");
+ }
- public static function getAll()
+ public static function archiveContentsFromTask($status, &$userGroupWarn = null) : array
{
- $res = Database::simpleQuery("SELECT c.configid, c.title, c.filepath, c.status, Group_Concat(cl.locationid) AS locs FROM configtgz c"
- . " LEFT JOIN configtgz_location cl USING (configid) GROUP BY c.configid");
- $ret = array();
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
- $ret[] = $row;
+ // Sort files for better display
+ $dirs = array();
+ foreach ($status['data']['entries'] as $file) {
+ if ($file['isdir']) continue;
+ $dirs[dirname($file['name'])][] = $file;
+ if ($file['userId'] > 0 || $file['groupId'] > 0) {
+ $userGroupWarn = true;
+ }
+ }
+ ksort($dirs);
+ $list = array();
+ foreach ($dirs as $dir => $files) {
+ $list[] = array(
+ 'name' => $dir,
+ 'isdir' => true
+ );
+ sort($files);
+ foreach ($files as $file) {
+ $file['size'] = Util::readableFileSize($file['size']);
+ $list[] = $file;
+ }
}
- return $ret;
+ return $list;
}
} \ No newline at end of file
diff --git a/modules-available/sysconfig/install.inc.php b/modules-available/sysconfig/install.inc.php
index 1ccec59b..4af019b1 100644
--- a/modules-available/sysconfig/install.inc.php
+++ b/modules-available/sysconfig/install.inc.php
@@ -7,6 +7,7 @@ $update[] = tableCreate('configtgz', "
`title` varchar(200) NOT NULL,
`filepath` varchar(255) NOT NULL,
`status` enum('OK','OUTDATED','MISSING') NOT NULL DEFAULT 'MISSING',
+ `warnings` TEXT NULL DEFAULT NULL,
`dateline` int(10) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`configid`)
");
@@ -16,7 +17,7 @@ $update[] = tableCreate('configtgz_module', "
`title` varchar(200) NOT NULL,
`moduletype` varchar(16) NOT NULL,
`filepath` varchar(250) NOT NULL,
- `contents` text NOT NULL,
+ `contents` longblob NOT NULL,
`version` int(10) unsigned NOT NULL DEFAULT '0',
`status` enum('OK','MISSING','OUTDATED') NOT NULL DEFAULT 'MISSING',
`dateline` int(10) unsigned NOT NULL DEFAULT '0',
@@ -40,15 +41,13 @@ $update[] = tableCreate('configtgz_location', "
");
// Constraints
-if (in_array(UPDATE_DONE, $update)) {
- // To self
- $update[] = tableAddConstraint('configtgz_x_module', 'configid', 'configtgz', 'configid',
- '');
- $update[] = tableAddConstraint('configtgz_x_module', 'moduleid', 'configtgz_module', 'moduleid',
- '');
- $update[] = tableAddConstraint('configtgz_location', 'configid', 'configtgz', 'configid',
- 'ON DELETE CASCADE ON UPDATE CASCADE');
-}
+$update[] = tableAddConstraint('configtgz_x_module', 'configid', 'configtgz', 'configid',
+ 'ON DELETE CASCADE ON UPDATE CASCADE');
+$update[] = tableAddConstraint('configtgz_x_module', 'moduleid', 'configtgz_module', 'moduleid',
+ 'ON DELETE CASCADE ON UPDATE CASCADE');
+$update[] = tableAddConstraint('configtgz_location', 'configid', 'configtgz', 'configid',
+ 'ON DELETE CASCADE ON UPDATE CASCADE');
+// No constraint to location table since we use locationid 0 for global (NULL would require special handling for UPDATE)
// Update path
@@ -88,7 +87,7 @@ if (!tableHasColumn('configtgz_module', 'dateline')) {
$update[] = UPDATE_DONE;
// Infer from module's filemtime
$res = Database::simpleQuery('SELECT moduleid, filepath FROM configtgz_module');
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ foreach ($res as $row) {
Database::exec('UPDATE configtgz_module SET dateline = :mtime WHERE moduleid = :moduleid',
['moduleid' => $row['moduleid'], 'mtime' => filemtime($row['filepath'])]);
}
@@ -103,25 +102,77 @@ if (!tableHasColumn('configtgz', 'dateline')) {
INNER JOIN configtgz_x_module cxm USING (configid)
INNER JOIN configtgz_module m USING (moduleid)
GROUP BY configid');
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ foreach ($res as $row) {
Database::exec('UPDATE configtgz SET dateline = :mtime WHERE configid = :configid',
['configid' => $row['configid'], 'mtime' => $row['dateline']]);
}
}
+// 2020-01-16: Change contents column type
+Database::exec("ALTER TABLE `configtgz_module` CHANGE `contents` `contents` LONGBLOB NOT NULL");
+
+// 2020-11-02: Add warnings column
+if (!tableHasColumn('configtgz', 'warnings')) {
+ if (Database::exec("ALTER TABLE `configtgz` ADD `warnings` TEXT NULL DEFAULT NULL") === false) {
+ finalResponse(UPDATE_FAILED, 'Could not add warnings to configtgz: ' . Database::lastError());
+ }
+ $update[] = UPDATE_DONE;
+}
+
// ----- rebuild configs ------
-// TEMPORARY HACK; Rebuild configs.. move somewhere else?
+// PERMANENT HACK; Rebuild configs.. move somewhere else?
Module::isAvailable('sysconfig');
$list = ConfigModule::getAll();
-if ($list === false) {
- EventLog::warning('Could not regenerate AD/LDAP configs - please do so manually');
+$parentTask = null;
+$configList = [];
+if ($list === null) {
+ EventLog::warning('Could not regenerate configs - please do so manually');
} else {
- foreach ($list as $ad) {
- if ($ad->needRebuild()) {
- $ad->generate(false);
+ foreach ($list as $confMod) {
+ if ($confMod->moduleType() === 'SshConfig') {
+ // 2020-11-12: Split SshConfig into SshConfig and SshKey
+ $pubkey = $confMod->getData('publicKey');
+ if (!empty($pubkey)) {
+ error_log('Legacy module with pubkey ' . $confMod->id());
+ $key = ConfigModule::getInstanceOrNull('SshKey');
+ if ($key !== null) {
+ $key->setData('publicKey', $pubkey);
+ if ($key->insert($confMod->title())) {
+ // Insert worked, remove key from old module, add this module to the same configs
+ $task = $key->generate(false, $parentTask);
+ if ($task !== false) {
+ $parentTask = $task;
+ }
+ error_log('Inserted new module with id ' . $key->id());
+ $confMod->setData('publicKey', false);
+ $confMod->update();
+ $configs = ConfigTgz::getAllForModule($confMod->id());
+ foreach ($configs as $config) {
+ // Add newly created key-only module to all configs
+ $new = array_merge($config->getModuleIds(), [$key->id()]);
+ error_log(implode(',', $config->getModuleIds()) . ' -> ' . implode(',', $new));
+ $config->update('', $new);
+ $configList[$config->id()] = $config;
+ }
+ }
+ }
+ }
+ }
+ if ($confMod->needRebuild()) {
+ $update[] = UPDATE_DONE;
+ $task = $confMod->generate(false, $parentTask);
+ if ($task !== false) {
+ $parentTask = $task;
+ }
}
}
+ foreach ($configList as $config) {
+ $config->generate(false, 0, $parentTask);
+ }
}
+// Start any changed services
+ConfigModuleBaseLdap::ldadp();
+
// Create response for browser
responseFromArray($update);
diff --git a/modules-available/sysconfig/lang/de/config-module.json b/modules-available/sysconfig/lang/de/config-module.json
index 4e178b65..80eb852a 100644
--- a/modules-available/sysconfig/lang/de/config-module.json
+++ b/modules-available/sysconfig/lang/de/config-module.json
@@ -8,9 +8,20 @@
"group_authentication": "Authentifizierung",
"group_branding": "Einrichtungsspezifisches Logo",
"group_generic": "Generisch",
- "group_sshconfig": "SSH",
+ "group_loginscreen": "Anmeldebildschirm Styling",
+ "group_screensaver": "Bildschirmschoner Styling",
+ "group_sshconfig": "SSH-D\u00e4mon",
+ "group_sshkey": "SSH-Key",
"ldapAuth_description": "Mit diesem Modul l\u00e4sst sich eine generische LDAP-Authentifizierung einrichten.",
- "ldapAuth_title": "LDAP Authentifizierung",
+ "ldapAuth_title": "LDAP-Authentifizierung",
+ "loginscreen_description": "Mit diesem Module k\u00f6nnen Sie Texte und einige Einstellungen des Anmeldebildschirms anpassen.",
+ "loginscreen_title": "Design\/Texte Anmeldebildschrim",
+ "screensaver_description": "Mit diesem Modul k\u00f6nnen sie den Style (QSS) und die Texte des Bildschirmschoners anpassen.",
+ "screensaver_title": "Design\/Texte Bildschirmschoner",
+ "shibauth_description": "Mit diesem Modul l\u00e4sst sich die Authentifizierung mittels Shibboleth\/SSO am Client einrichten.",
+ "shibauth_title": "Shibboleth-Authentifizierung",
"sshconfig_description": "Mit diesem Modul l\u00e4sst sich steuern, ob und wie der sshd auf den gebooteten Clients startet, und welche Funktionen er zur Verf\u00fcgung stellt. Wenn Sie keinen sshd auf den Clients nutzen wollen, brauchen Sie kein solches Modul zu erstellen.",
- "sshconfig_title": "SSH-D\u00e4mon"
+ "sshconfig_title": "SSH-D\u00e4mon",
+ "sshkey_description": "Einen \u00f6ffentlichen SSH-Schl\u00fcssel zu den authorized_keys des root-Benutzers hinzuf\u00fcgen. Mit dem zugeh\u00f6rigen privaten Schl\u00fcssel kann dann via SSH auf die gebooteten Clients zugegriffen werden, sofern root-Login im zugeh\u00f6rigen SSH-D\u00e4mon-Modul aktiviert wurde.",
+ "sshkey_title": "SSH-Key"
} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/de/messages.json b/modules-available/sysconfig/lang/de/messages.json
index 5bceb2f0..81cdb0f9 100644
--- a/modules-available/sysconfig/lang/de/messages.json
+++ b/modules-available/sysconfig/lang/de/messages.json
@@ -1,5 +1,5 @@
{
- "config-activated": "Konfiguration {{0}} wurde aktiviert",
+ "config-delete-error": "Datenbankfehler: {{0}}",
"config-deleted": "Konfiguration {{0}} wurde gel\u00f6scht",
"config-invalid": "Konfiguration mit ID {{0}} existiert nicht",
"could-not-determine-binddn": "Konnte Bind-DN nicht ermitteln ({{0}})",
@@ -9,15 +9,18 @@
"module-added": "Modul erfolgreich hinzugef\u00fcgt",
"module-deleted": "Modul {{0}} wurde gel\u00f6scht",
"module-edited": "Modul wurde aktualisiert",
- "module-in-use": "Modul {{0}} wird noch durch Konfiguration {{1}} verwendet",
"module-rebuild-failed": "Neubau des Moduls fehlgeschlagen",
"module-rebuilding": "Modul wird neu generiert",
"module-rebuilt": "Modul wurde neu generiert",
"no-image": "Unter der angegebenen URL konnte kein SVG-Bild gefunden werden",
- "no-noconfig-active": "Es wurde noch keine Systemkonfiguration ausgew\u00e4hlt.",
+ "no-noconfig-active": "Es wurde noch keine Systemkonfiguration ausgew\u00e4hlt",
+ "no-organization-selected": "Keine Organisation ausgew\u00e4hlt",
+ "no-session-data": "Keine Sitzungsdaten zum Editieren. Bitte erneut beginnen.",
+ "regex-mismatch": "Regex stimmt nicht \u00fcberein: {{0}} -> {{1}}",
"remote-timeout": "Konnte Ressource {{0}} nicht herunterladen ({{1}})",
"replacing-config": "Ersetzen von Konfiguration {{0}}",
"replacing-module": "Ersetzen von Modul {{0}}",
+ "shib-list-update-failed": "Aktualisierung der IdP-\/Organisationsliste fehlgeschlagen",
"unsuccessful-action": "Nicht erfolgreich",
"upload-failed": "Upload schlug fehl: {{0}}"
} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/de/module.json b/modules-available/sysconfig/lang/de/module.json
index 4b401437..71eb70cc 100644
--- a/modules-available/sysconfig/lang/de/module.json
+++ b/modules-available/sysconfig/lang/de/module.json
@@ -1,11 +1,39 @@
{
- "config-module": "Konfigurationsmodul",
- "lang_clientSshConfig": "SSH-Konfiguration",
"lang_configurationCompilation": "Konfiguration zusammenstellen",
"lang_contentOf": "Inhalt von",
"lang_moduleAdd": "Modul hinzuf\u00fcgen",
+ "lang_moduleAssign": "Modul zu Systemkonfigurationen zuweisen",
"lang_noModuleFromThisGroup": "(Kein Modul dieser Gruppe)",
"lang_unknwonTaskManager": "Unbekannter Taskmanager-Fehler",
+ "location-column-header": "Lokalisierung",
+ "ls_clock-background-style": "Hintergrund-Style (QSS) f\u00fcr die Statusleiste der Uhr (oben)",
+ "ls_clock-shadow": "Schatten der Uhrzeit (X-Versatz, Y-Versatz, Farbe, Unsch\u00e4rfe)",
+ "ls_clock-text-style": "Style (QSS) f\u00fcr die Uhrzeit (oben)",
+ "ls_guest-session-button-text": "Beschriftung der Schaltfl\u00e4che, mit der die Gastsitzung ausgew\u00e4hlt wird",
+ "ls_guest-session-start-button-text": "Beschriftung der Schaltfl\u00e4che, mit der die Gastsitzung gestartet wird",
+ "ls_guest-session-start-text": "Hinweistext \u00fcber der Gastsitzungsstartschaltfl\u00e4che",
+ "ls_password-placeholder": "Platzhalter f\u00fcr das Kennwortseingabefeld",
+ "ls_qr-session-button-text": "Beschriftung der Schaltfl\u00e4che, mit der der Login via QR-Code ausgew\u00e4hlt wird",
+ "ls_reset-timeout": "Zeit in Sekunden, nach der bei Inaktivit\u00e4t wieder zur Auswahl zur\u00fcckgekehrt wird",
+ "ls_shib-session-button-text": "Beschriftung der Schaltfl\u00e4che, mit der der Login via Shibboleth\/bwIDM ausgew\u00e4hlt wird",
+ "ls_user-session-button-text": "Beschriftung der Schaltfl\u00e4che, mit der der Login via einfacher Nutzer\/Kennwort-Maske ausgew\u00e4hlt wird",
+ "ls_username-placeholder": "Platzhalter f\u00fcr das Nutzernamenseingabefeld",
"module_name": "Lokalisierung + Integration",
- "page_title": "Lokalisierung + Integration"
+ "page_title": "Lokalisierung + Integration",
+ "saver_DescriptionIdleKill": "Wenn der Bildschirmschoner aktiv ist, und ein gesetztes Inaktivit\u00e4tstimeout bevorsteht, wird dieser Text unter dem Countdown angezeigt. Sie k\u00f6nnen den Text unterschiedlich formulieren abh\u00e4ngig davon, ob zum Beenden des Bildschirmschoners das Nutzerpassword eingegeben werden muss.",
+ "saver_DescriptionNoTimeout": "Ein Bildschirmschoner ohne Timeout. Der Rechner kann beliebig lange mit aktivem Bildschirmschoner laufen.",
+ "saver_DescriptionShutdown": "Wenn ein fest geplanter Shutdown oder Reboot des Rechners bevorsteht, wird im Bildschirmschoner dieser Text unter dem Countdown angezeigt. Auch hier l\u00e4sst sich der Text auf Wunsch unterschiedlich gestalten je nach dem, ob der Bildschirmschoner mit einem Passwort entsperrt werden muss.",
+ "saver_MessageDefaultIdleKill": "Diese Sitzung wird bei Inaktivit\u00e4t in %1 beendet.",
+ "saver_MessageDefaultIdleKillLocked": "Diese Sitzung wird in %1 beendet, wenn sie nicht entsperrt wird.",
+ "saver_MessageDefaultNoTimeout": "Dieser Bildschirm wird gerade geschont.",
+ "saver_MessageDefaultNoTimeoutLocked": "Dieser Rechner ist gesperrt.",
+ "saver_MessageDefaultShutdown": "Achtung: Rechner wird in %1 heruntergefahren!",
+ "saver_MessageDefaultShutdownLocked": "Achtung: Rechner wird in %1 heruntergefahren!",
+ "saver_QssDefault": "#Saver {\r\n background: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 #443, stop:1 #000)\r\n}\r\n\r\nQLabel {\r\n color: #f64;\r\n}\r\n\r\n#lblClock {\r\n color: #999;\r\n font-size: 20pt;\r\n}\r\n\r\n#lblHeader {\r\n font-size: 20pt;\r\n}\r\n",
+ "saver_TextDefaultIdleKill": "<html><body>\r\nKeine Nutzeraktivit\u00e4t festgestellt.<br>\r\nZum oben angegebenen Zeitpunkt wird die aktuell laufende Sitzung beendet, wenn der Rechner nicht mehr verwendet wird.<br>\r\nAlle noch laufenden Programme werden ohne Nachfrage geschlossen.<br>\r\nStellen Sie daher sicher, bis zum angegebenen Zeitpunkt<br>\r\ns\u00e4mtliche sich in Bearbeitung befindlichen Daten abzuspeichern.<br>\r\n<br>\r\nDies dient dazu zu vermeiden, dass ein Rechner stundenlang belegt ist und somit<br>\r\nanderen Nutzern nicht zur Verf\u00fcgung steht.\r\n<\/body><\/html>",
+ "saver_TextDefaultIdleKillLocked": "<html><body>\r\nZum oben angegebenen Zeitpunkt wird die aktuell laufende Sitzung beendet,<br>\r\nwenn sie zuvor nicht wieder entsperrt wird.<br>\r\nAlle noch laufenden Programme werden ohne Nachfrage geschlossen.<br>\r\nStellen Sie daher sicher, bis zum angegebenen Zeitpunkt s\u00e4mtliche sich in<br>\r\nBearbeitung befindlichen Daten abzuspeichern, bzw. die Sitzung wieder zu entsperren.<br>\r\n<br>\r\nDies dient dazu zu vermeiden, dass ein Rechner stundenlang gesperrt wird und somit<br>\r\nanderen Nutzern nicht zur Verf\u00fcgung steht.\r\n<\/body><\/html>",
+ "saver_TextDefaultShutdown": "<html><body>\r\nAchtung: Zum oben angegebenen Zeitpunkt wird der Computer heruntergefahren bzw. neugestartet.<br>\r\nAlle noch laufenden Programme werden ohne Nachfrage beendet. Stellen Sie daher sicher, bis<br>\r\nzum angegebenen Zeitpunkt s\u00e4mtliche Daten abzuspeichern und die Sitzung zu verlassen.\r\n<\/body><\/html>",
+ "saver_TitleIdleKill": "Idle Kill",
+ "saver_TitleNoTimeout": "Ohne Sitzungs- oder Shutdown-Timeout",
+ "saver_TitleShutdown": "Herunterfahren"
} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/de/template-tags.json b/modules-available/sysconfig/lang/de/template-tags.json
index 8e6ba87a..eb9efb09 100644
--- a/modules-available/sysconfig/lang/de/template-tags.json
+++ b/modules-available/sysconfig/lang/de/template-tags.json
@@ -1,6 +1,7 @@
{
"lang_activate": "Aktivieren",
"lang_activateGlobally": "Als globale Konfiguration setzen",
+ "lang_activateGloballyText": "Sie k\u00f6nnen die soeben erzeugte Konfiguration direkt als Standardkonfiguration setzen. Diese wird f\u00fcr alle R\u00e4ume verwendet, die keine eigene Konfiguration zugewiesen haben.",
"lang_active": "Aktiv",
"lang_adStarted": "Der AD-Proxy wird nun konfiguriert und gestartet...",
"lang_adText1": "Zum Einrichten der Benutzerauthentifizierung \u00fcber ein Active Directory wird neben der Adresse des anzusprechenden Servers ein dedizierter Benutzer im AD ben\u00f6tigt, \u00fcber welchen das AD angesprochen wird. Der Benutzer sollte in der gleichen Dom\u00e4ne sein, wie die Benutzer, die sich sp\u00e4ter an den Arbeitsstationen anmelden werden. Ansonsten ist es notwendig, die Suchbasis anzugeben.",
@@ -8,8 +9,8 @@
"lang_adText3": "Normalerweise k\u00f6nnen Sie als Bind DN die Kurzform im Format dom\u00e4ne\\benutzer angeben. Wenn dies nicht funktioniert, m\u00fcssen Sie den DN des Benutzers ermitteln. Z.B. unter Eingabe des folgenden Befehls auf einem DC:",
"lang_adText4": "Nach Eingabe aller ben\u00f6tigten Daten wird im n\u00e4chsten Schritt \u00fcberpr\u00fcft, ob die Kommunikation mit dem AD m\u00f6glich ist.",
"lang_add": "Hinzuf\u00fcgen",
- "lang_allowPass": "Login mit Passwort zulassen",
- "lang_allowPassInfo": "Wenn aktiviert, l\u00e4sst der sshd Logins mit Benutzername\/Passwort-Kombination zu. Ansonsten werden nur Logins nach dem pubkey-Verfahren zugelassen.",
+ "lang_assignToConfigs": "Zuweisung zu Konfigurationen \u00e4ndern",
+ "lang_assignToConfigsLong": "Mit dieser Funktion k\u00f6nnen sie f\u00fcr ein Modul direkt die Zuweisung zu mehreren Konfigurationen \u00e4ndern.",
"lang_asteriskMandatory": "Mit (*) gekennzeichnete Felder sind Pflichtfelder",
"lang_availableModules": "Verf\u00fcgbare Konfigurationsmodule",
"lang_availableSystem": "Verf\u00fcgbare Systemkonfigurationen",
@@ -44,9 +45,8 @@
"lang_driveLetterNote": "WICHTIG: Bitte w\u00e4hlen Sie einen Laufwerksbuchstaben, der in den eingesetzten VMs verf\u00fcgbar ist, da ansonsten auf einen anderen Buchstaben ausgewichen werden muss.",
"lang_editLong": "Modul oder Konfiguration bearbeiten.",
"lang_editingLocationInfo": "Sie setzen die Konfiguration eines bestimmten Raums\/Orts, nicht die globale Konfiguration",
- "lang_fixNumeric": "Numerischen Account-Namen muss ein 's' vorangestellt werden",
- "lang_fixNumericDescription": "Wenn Sie diese Option aktivieren, m\u00fcssen Benutzer, deren Account-Name nur aus Ziffern besteht, diesem ein 's' voranstellen beim Login. Diese Option ist beim alten Login-Manager (KDM) zwingend erforderlich, da sonst der Loginvorgang fehlschl\u00e4gt. Mit dem neuen lightdm-basierten Login-Screen lassen sich numerische Account-Namen jedoch direkt verwenden. Wenn Sie an Ihrer Einrichtung keine numerischen Account-Namen verwenden, hat diese Option keine Auswirkung.",
"lang_folderRedirection": "Folder Redirection",
+ "lang_forceRootOwner": "Besitzrechte des Inhalts auf root:root setzen",
"lang_genUid": "uid-Nummern generieren",
"lang_genUidDescription": "Wenn aktiviert, generiert der Satellitenserver nummerische IDs f\u00fcr die Benutzer, anstatt diese aus dem LDAP\/AD zu extrahieren.",
"lang_generateModule": "Modul erzeugen",
@@ -70,14 +70,22 @@
"lang_legend": "Legende",
"lang_listenPort": "Listen port",
"lang_listenPortInfo": "Der Port, auf dem der sshd lauscht. Der offizielle Standard ist 22.",
+ "lang_loginScreenIntroText": "Hiermit lassen sich Texte sowie einige Einstellungen des Anmeldebildschirms anpassen.",
"lang_mapModeNative": "Nativ direkt in der VM einbinden [openslx.exe]",
"lang_mapModeNativeFallback": "Nativ in der VM einbinden; Fallback auf VMware Shared Folders",
"lang_mapModeNone": "Verzeichnisse nicht durchreichen",
"lang_mapModeVmware": "VMware Shared Folders [VMwareTools]",
+ "lang_modStillUsedBy": "Modul noch in Verwendung durch:",
+ "lang_mode": "Modus",
+ "lang_modeAdvanced": "Fortgeschrittener Modus",
+ "lang_modeEasy": "Vereinfachter Modus",
"lang_moduleChoose": "Bitte w\u00e4hlen Sie aus, welche Art Konfigurationsmodul Sie erstellen m\u00f6chten.",
"lang_moduleConfiguration": "Konfigurationsmodule",
"lang_moduleName": "Modulname",
+ "lang_moduleOwnerWarn": "Einige Dateien oder Verzeichnisse in diesem Archiv haben als Besitzer order Gruppe etwas anderes als \"root\" gesetzt. Dies ist nur in besonderen F\u00e4llen sinnvoll bzw. erforderlich.",
"lang_moduleTitle": "Titel",
+ "lang_moduleUnused": "Ungenutzt",
+ "lang_moduleUnusedLong": "Dieses Modul ist mit keiner Systemkonfiguration verkn\u00fcpft.",
"lang_mountOptionsNote": "Diese Einstellungen beziehen sich nur auf Linux und \u00e4hnliche Systeme (sowohl das MiniLinux als auch laufende VMs) und beeinflussen die Optionen, die beim Mounten des Verzeichnisses verwendet werden sollen. Sofern es im LDAP\/AD ein Nutzerattribut gibt, welches die passenden Optionen enth\u00e4lt, k\u00f6nnen Sie dieses hier angeben. Das Attribut wird dann vorrangig behandelt. Ist das Attribut leer oder nicht vorhanden, werden die Optionen verwendet, die Sie im Feld \"feste Mount-Optionen\" eingetragen haben. Sind beide Felder leer, werden verschiedene Optionen automatisch durchprobiert.",
"lang_name": "Name",
"lang_newConfiguration": "Neue Konfiguration",
@@ -91,14 +99,30 @@
"lang_noValidCert": "Der Server besitzt kein oder ein nicht valides Zertifikat.",
"lang_onProblemSearchBase": "Werden keine Benutzer gefunden, dann \u00fcberpr\u00fcfen Sie bitte die Suchbasis",
"lang_or": "oder",
+ "lang_pwlogin_user_only": "Alle au\u00dfer root",
+ "lang_qssInfoText": "QSS sind Qt-spezifische Stylesheets, die an CSS angelehnt sind. Damit ist es m\u00f6glich, das Design anzupassen.",
"lang_rebuild": "Neu generieren",
"lang_rebuildLong": "Modul oder Konfiguration neu generieren. Das entsprechende Modul bzw. Konfiguration ist aktuell und sollte nicht neu generiert werden m\u00fcssen.",
"lang_rebuildOutdatedLong": "Modul oder Konfiguration neu generieren. Das entsprechende Modul bzw. Konfiguration ist veraltet oder nicht vorhanden.",
"lang_redirectionWarning": "ACHTUNG: Diese Funktion ist experimentell. Sie biegt nach dem Starten mittels openslx.exe die ausgew\u00e4hlten Verzeichnisse auf das Home-Verzeichnis des angemeldeten Benutzers um (getestet mit Windows 7 und 10). Da hierzu undokumentierte Windows-Einstellungen zur Laufzeit ge\u00e4ndert werden ist nicht garantiert, dass diese Methode in sp\u00e4teren Versionen\/Updates von Windows noch funktioniert. Wir empfehlen, stattdessen die Verzeichnisse - sofern gew\u00fcnscht - bereits in der Vorlage auf den oben konfigurierten Laufwerksbuchstaben des Home-Verzeichnisses umzukonfigurieren.",
+ "lang_replaces": "Ersetzt Modul: ",
"lang_restartWizard": "Wizard neu starten",
"lang_rootKey": "root pubkey (\u00f6ffentlicher Schl\u00fcssel)",
- "lang_rootKeyInfo": "Tragen Sie hier den \u00f6ffentlichen Schl\u00fcssel eines Schl\u00fcsselpaars ein, mit dem Sie sich als root-Benutzer an den Clients anmelden wollen. Lassen Sie das Feld leer, um diese Funktion nicht zu verwenden.",
+ "lang_rootKeyInfo": "Tragen Sie hier den \u00f6ffentlichen Schl\u00fcssel eines Schl\u00fcsselpaars ein, mit dem Sie sich als root-Benutzer an den Clients anmelden wollen.",
+ "lang_screenBackground": "Hintergrund",
+ "lang_screenBackgroundDescription": " - Ein Hintergrund, bestehend aus einem zweifarbigem Gradienten.",
+ "lang_screenClock": "Uhr",
+ "lang_screenColor": "Farbe",
+ "lang_screenHeader": "Header",
+ "lang_screenLabel": "Label",
+ "lang_screenLocked": "Sperrbildschirm",
+ "lang_screenQss": "QSS",
+ "lang_screenSize": "Gr\u00f6\u00dfe",
+ "lang_screenText": "Inhaltstext Bearbeiten",
+ "lang_screenTextInherit": "Werte Erben",
+ "lang_screenUnlocked": "Bildschirmschoner",
"lang_searchBase": "Suchbasis",
+ "lang_selectDeselectAll": "Alle an-\/abw\u00e4hlen",
"lang_selectFile": "Bitte w\u00e4hlen Sie ein Archiv",
"lang_selectHomeAttribute": "Home-Attribut",
"lang_selfSignedNote": "Das Zertifikat des Servers scheint selbst signiert zu sein. Wenn Sie fortfahren wird versucht, die Zertifikatskette vom Server abzufragen. Dies ist in den meisten F\u00e4llen erfolgreich, sollte aber nur getan werden wenn Sie wissen, dass das Zertifikat des Servers von einer unbekannten CA signiert wurde. Falls die Authentifizierung anschlie\u00dfend nicht funktioniert, \u00fcberpr\u00fcfen Sie die LDAP-Proxy Logs auf der Serverstatus-Seite.",
@@ -114,12 +138,23 @@
"lang_shareModeNote": "\"Nativer Modus mit Fallback auf VMware\" ist experimentell und kann dazu f\u00fchren, dass die VM in regelm\u00e4\u00dfigen Abst\u00e4nden H\u00e4nger hat.",
"lang_shareOther": "Andere (Saved Games, Kontakte, Favoriten, ...)",
"lang_shareRemapMode": "Einbindemodus",
+ "lang_shibEnabledMethods": "Aktivierte Anmeldemethoden",
+ "lang_shibEntitlements": "Entitlements",
+ "lang_shibEntitlementsPretext": "Wenn Sie den Login der unten ausgew\u00e4hlten Einrichtungen weiter einschr\u00e4nken wollen, k\u00f6nnen Sie in diesem Feld eine Liste von erforderlichen Entitlements angeben. Alle hier angegebenen Entitlements m\u00fcssen bei einem Nutzer vorhanden sein. Trennen Sie mehrere Attribute mit einem Semikolon.",
+ "lang_shibIntroText": "Hier k\u00f6nnen Sie die Anmeldung am Client mittels Single-Sign-on (SSO, z.B. Shibboleth) aktivieren. Dies erm\u00f6glicht einen Login via Browser, oder QR-Code. Dadurch wird z.B. die Nutzung von 2FA m\u00f6glich, sofern dies durch den genutzten IdP umgesetzt wird.\r\nBitte beachten Sie, dass es zur Zeit nicht m\u00f6glich ist, automatisiert ein Home-Verzeichnis f\u00fcr den Nutzer einzubinden, da die erforderlichen Daten beim Anmeldevorgang nicht \u00fcbertragen werden.",
+ "lang_shibSelectOrgs": "Organisationen ausw\u00e4hlen, denen die Anmeldung gew\u00e4hrt wird.",
+ "lang_shibUseBrowser": "Anmeldung durch Browser im Login-Screen erlauben",
+ "lang_shibUseQrCode": "Anmeldung durch das Scannen eines QR-Codes erlauben",
+ "lang_shibUseUserLogin": "Klassische Anmeldung mittels hinterlegtem LDAP\/AD erlauben",
"lang_show": "Ansehen",
"lang_showLong": "Inhalt des Moduls anzeigen.",
"lang_skip": "Weiter",
- "lang_sshMultipleHeadsup": "Wenn Sie mehrere Pubkeys angeben wollen, k\u00f6nnen Sie entsprechend mehrere Module vom Typ SSH zu einer Systemkonfiguration hinzuf\u00fcgen. In diesem Fall sollten Sie jedoch darauf achten, dass alle Konfigurationen die gleichen Einstellungen f\u00fcr Port und Passwortlogin haben, da ansonsten undefiniert ist, welche der Einstellungen greifen wird.",
+ "lang_sshAllowPass": "Login via Passwort zulassen",
+ "lang_sshAllowPassInfo": "Legt fest, ob sich per SSH mit Passwort eingeloggt werden darf, oder nur das pubkey-Verfahren erlaubt ist.",
+ "lang_sshAllowedUsers": "Zugelassene Nutzer",
+ "lang_sshAllowedUsersInfo": "Legt fest, welche Nutzer sich per SSH einloggen d\u00fcrfen. Der spezielle Nutzer \"demo\" kann sich generell nicht per SSH einloggen, unabh\u00e4ngig der Konfiguration.",
"lang_ssl": "SSL",
- "lang_sslDescription": "Die Verbindung zum AD-Server mit SSL sichern. (Die Verbindung zwischen Client und Proxy wird in jedem Fall mit SSL abgewickelt.)",
+ "lang_sslDescription": "Die Verbindung zum AD\/LDAP-Server mit SSL sichern. (Die Verbindung zwischen Client und Proxy wird in jedem Fall mit SSL abgewickelt.)",
"lang_supportedFiles": "Unterst\u00fctzte Archivformate",
"lang_systemConfiguration": "Systemkonfiguration",
"lang_systemConfigurationAlert": "Bevor Sie eine Systemkonfiguration erstellen k\u00f6nnen, m\u00fcssen Sie zun\u00e4chst ein Konfigurationsmodul erzeugen.",
@@ -134,5 +169,8 @@
"lang_userDirectory": "Benutzerverzeichnis",
"lang_userDirectoryInfo1": "Optionale Angabe: Wenn die Clients f\u00fcr die Benutzer ein eigenes Verzeichnis (Homeverzeichnis, Benutzerverzeichnis) von einem Server einbinden sollen, geben Sie bitte hier das Format in UNC-Notation an, also z.B.",
"lang_userDirectoryInfo2": "%s ist dabei ein Platzhalter f\u00fcr den Login-Namen des Benutzers.",
- "lang_userDirectoryInfo3": "Das Verzeichnis wird mit den gleichen Zugangsdaten eingebunden, die der Benutzer beim Login angibt. (D.h. kein Kerberos Support o.\u00e4.)"
+ "lang_userDirectoryInfo3": "Das Verzeichnis wird mit den gleichen Zugangsdaten eingebunden, die der Benutzer beim Login angibt. (D.h. kein Kerberos Support o.\u00e4.)",
+ "lang_user_all": "Alle Nutzer",
+ "lang_user_root_only": "Nur root",
+ "lang_user_user_only": "Alle au\u00dfer root"
} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/en/config-module.json b/modules-available/sysconfig/lang/en/config-module.json
index efe6f697..7c17cf80 100644
--- a/modules-available/sysconfig/lang/en/config-module.json
+++ b/modules-available/sysconfig/lang/en/config-module.json
@@ -8,9 +8,20 @@
"group_authentication": "Authentication",
"group_branding": "Branding",
"group_generic": "Generic",
- "group_sshconfig": "SSH",
+ "group_loginscreen": "Login screen styling",
+ "group_screensaver": "Screen saver styling",
+ "group_sshconfig": "SSH config",
+ "group_sshkey": "SSH key",
"ldapAuth_description": "This module enables you to create a simple LDAP authentication module.",
- "ldapAuth_title": "LDAP Authentication",
+ "ldapAuth_title": "LDAP authentication",
+ "loginscreen_description": "Here you can customize texts and labels of the login screen, as well as a few settings.",
+ "loginscreen_title": "Login screen customization",
+ "screensaver_description": "With this module you can customize the style (QSS) and texts of the screensaver.",
+ "screensaver_title": "Screensaver customization",
+ "shibauth_description": "Using this module you can enable Shibboleth\/SSO login on the client.",
+ "shibauth_title": "Shibboleth authentication",
"sshconfig_description": "Here you can set whether the sshd on the clients will start, and what options it will use.",
- "sshconfig_title": "SSH daemon"
+ "sshconfig_title": "SSH daemon",
+ "sshkey_description": "Add a public key to the authorized_keys file of the root user. You can then use the according private key to log in on a running client as root via SSH. root login needs to be enabled in the according SSH daemon module.",
+ "sshkey_title": "SSH key"
} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/en/messages.json b/modules-available/sysconfig/lang/en/messages.json
index 6e50b80c..ea20695d 100644
--- a/modules-available/sysconfig/lang/en/messages.json
+++ b/modules-available/sysconfig/lang/en/messages.json
@@ -1,5 +1,5 @@
{
- "config-activated": "Configuration {{0}} has been activated",
+ "config-delete-error": "Database error: {{0}}",
"config-deleted": "Deleted configuration {{0}}",
"config-invalid": "Configuration with id {{0}} does not exist",
"could-not-determine-binddn": "Could not determine bind dn ({{0}})",
@@ -9,15 +9,18 @@
"module-added": "Module successfully added",
"module-deleted": "Module {{0}} was deleted",
"module-edited": "Module has been edited",
- "module-in-use": "Module {{0}} is still used by Configuration {{1}}",
"module-rebuild-failed": "Rebuilding module failed",
"module-rebuilding": "Module is rebuilding...",
"module-rebuilt": "Module was rebuilt",
"no-image": "Could not find an SVG-image at the given URL",
"no-noconfig-active": "No system configuration created\/selected yet.",
+ "no-organization-selected": "No irganization selected",
+ "no-session-data": "No session data, please start over",
+ "regex-mismatch": "Regex mismatch: {{0}} -> {{1}}",
"remote-timeout": "Could not download resource {{0}} ({{1}})",
"replacing-config": "Replace config {{0}}",
"replacing-module": "Replace module {{0}}",
+ "shib-list-update-failed": "Updating IdP\/organization list failed",
"unsuccessful-action": "Not successful",
"upload-failed": "Upload failed: {{0}}"
} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/en/module.json b/modules-available/sysconfig/lang/en/module.json
index ba2d0591..3a379660 100644
--- a/modules-available/sysconfig/lang/en/module.json
+++ b/modules-available/sysconfig/lang/en/module.json
@@ -1,11 +1,39 @@
{
- "config-module": "Config module",
- "lang_clientSshConfig": "SSH configuration",
"lang_configurationCompilation": "Compile configuration",
"lang_contentOf": "Content of",
"lang_moduleAdd": "Add Module",
+ "lang_moduleAssign": "Assign Module to System Configurations",
"lang_noModuleFromThisGroup": "(No module from this group)",
"lang_unknwonTaskManager": "Unknown Task Manager error",
+ "location-column-header": "SysConfig",
+ "ls_clock-background-style": "Background style (QSS) for the status bar of the clock (top)",
+ "ls_clock-shadow": "Shadow of the clock (X-offset, Y-offset, color, blur)",
+ "ls_clock-text-style": "Style (QSS) for the clock time (top)",
+ "ls_guest-session-button-text": "Label of the button to select the guest session",
+ "ls_guest-session-start-button-text": "Label of the button to start the guest session",
+ "ls_guest-session-start-text": "Hint above the guest session start button",
+ "ls_password-placeholder": "Placeholder for the password input field",
+ "ls_qr-session-button-text": "Label of the button to select login via QR code",
+ "ls_reset-timeout": "Time in seconds after which it returns to the selection screen due to inactivity",
+ "ls_shib-session-button-text": "Label of the button to select login via Shibboleth\/bwIDM",
+ "ls_user-session-button-text": "Label of the button to select login via simple user\/password mask",
+ "ls_username-placeholder": "Placeholder for the username input field",
"module_name": "Localization",
- "page_title": "Localize and integrate"
+ "page_title": "Localize and integrate",
+ "saver_DescriptionIdleKill": "If the screen saver is active and a set inactivity timeout is pending, this text is displayed below the countdown. You can phrase the text differently depending on whether the user's password must be entered to unlock the screensaver. ",
+ "saver_DescriptionNoTimeout": "A screensaver without a timeout. The machine can run indefinitely with the currently active session\/screensaver.",
+ "saver_DescriptionShutdown": "If a scheduled shutdown or reboot of the computer is pending, this text is displayed below the countdown in the screensaver. Here, too, the text can be customized depending on whether the screensaver needs to be unlocked with a password.",
+ "saver_MessageDefaultIdleKill": "This session will end in %1 when inactive.",
+ "saver_MessageDefaultIdleKillLocked": "This session will end in %1 if the session is not unlocked.",
+ "saver_MessageDefaultNoTimeout": "This screen is in saving mode.",
+ "saver_MessageDefaultNoTimeoutLocked": "This computer is locked.",
+ "saver_MessageDefaultShutdown": "Caution: Computer will shutdown in %1!",
+ "saver_MessageDefaultShutdownLocked": "Caution: Computer will shutdown in %1!",
+ "saver_QssDefault": "#Saver {\r\n background: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 #443, stop:1 #000)\r\n}\r\n\r\nQLabel {\r\n color: #f64;\r\n}\r\n\r\n#lblClock {\r\n color: #999;\r\n font-size: 20pt;\r\n}\r\n\r\n#lblHeader {\r\n font-size: 20pt;\r\n}\r\n",
+ "saver_TextDefaultIdleKill": "<html><body>\r\nNo user activity detected.<br>\r\nIf the computer is not used until the time specified above, the session will end.<br>\r\nAll running applications will be closed without further requests.<br>\r\nMake sure that all files and changes are saved before the time runs out.<br>\r\n<br>\r\nIt prevents computers from being locked for hours and not being available to other users.\r\n<\/body><\/html>",
+ "saver_TextDefaultIdleKillLocked": "<html><body>\r\nThe current session will end by the time specified above if the computer isn't unlocked before.<br>\r\nAll running applications will be closed without further requests.<br>\r\nMake sure that all files and changes are saved or the session is unlocked before the time runs out.<br>\r\n<br>\r\nIt prevents computers from being occupied for hours and not being available to other users.\r\n<\/body><\/html>",
+ "saver_TextDefaultShutdown": "<html><body>\r\nCaution: The computer will shutdown at the specified time above.<br>\r\nAll running applications will be closed without further requests.<br>\r\nMake sure to save all files and changes and leave the session before the time runs out.\r\n<\/body><\/html>",
+ "saver_TitleIdleKill": "Idle Kill",
+ "saver_TitleNoTimeout": "No Timeout",
+ "saver_TitleShutdown": "Shutdown"
} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/en/template-tags.json b/modules-available/sysconfig/lang/en/template-tags.json
index 0921ccdb..8b765b58 100644
--- a/modules-available/sysconfig/lang/en/template-tags.json
+++ b/modules-available/sysconfig/lang/en/template-tags.json
@@ -1,6 +1,7 @@
{
"lang_activate": "Activate",
"lang_activateGlobally": "Set as global\/default configuration",
+ "lang_activateGloballyText": "You can now set the configuration as default. The default configuration will be used for all clients not in a location with a specific configuration applied.",
"lang_active": "Active",
"lang_adStarted": "The AD-proxy is now configured and started ...",
"lang_adText1": "To set up user authentication through Active Directory, a dedicated user is required in AD next to the address of the server, which is addressed by the AD.",
@@ -8,8 +9,8 @@
"lang_adText3": "Next the distinguished name of the user must be specified. You can determine this by dsquery command line program on a domain controller as the following call:",
"lang_adText4": "After entering all required data in the next step, it checks whether communication is possible with the AD.",
"lang_add": "Add",
- "lang_allowPass": "Allow password login",
- "lang_allowPassInfo": "When active, logins via username and password are allowed. Otherwise, only pubkey authentication is possible.",
+ "lang_assignToConfigs": "Assign\/unassign module to configurations",
+ "lang_assignToConfigsLong": "Change assignment of module to multiple configurations.",
"lang_asteriskMandatory": "Fields marked with (*) are mandatory",
"lang_availableModules": "Available Configuration Modules",
"lang_availableSystem": "Available System Configuration",
@@ -44,9 +45,8 @@
"lang_driveLetterNote": "IMPORTANT: Pick a drive letter for the home directory that will be free in the Virtual Machines. Otherwise, a random letter will be assigned.",
"lang_editLong": "Edit module or configuration.",
"lang_editingLocationInfo": "You're setting the configuration for a specific location, not the global one",
- "lang_fixNumeric": "Numeric account names have to be prefixed by 's'",
- "lang_fixNumericDescription": "If enabled, users with account names that consist entirely of digits have to prefix their user id by 's' when logging in. This is required with the old login manager (KDM) to prevent crashes. The new lightdm-based login manager will accept numeric account names, so you can leave this option disabled. If your organization doesn't have any numeric account names, this option will have no effect.",
"lang_folderRedirection": "Folder Redirection",
+ "lang_forceRootOwner": "Change ownership of archive content to root:root",
"lang_genUid": "Generate uid numbers",
"lang_genUidDescription": "When selected, the satellite server will generate numeric IDs for the users, instead of extracting them from AD\/LDAP.",
"lang_generateModule": "Generating module",
@@ -70,14 +70,22 @@
"lang_legend": "Legend",
"lang_listenPort": "Listen port",
"lang_listenPortInfo": "Listen port for the sshd. Default is 22.",
+ "lang_loginScreenIntroText": "Here you can customize texts\/labels of the login screen, as well as a few settings.",
"lang_mapModeNative": "Natively map inside the VM [openslx.exe]",
"lang_mapModeNativeFallback": "Natively map inside VM; fallback to VMware Shared Folders",
"lang_mapModeNone": "Don't map shares at all",
"lang_mapModeVmware": "VMware Shared Folders [VMwareTools]",
+ "lang_modStillUsedBy": "Module still in use by:",
+ "lang_mode": "Mode",
+ "lang_modeAdvanced": "Advanced Mode",
+ "lang_modeEasy": "Easy Mode",
"lang_moduleChoose": "Please select which type of configuration module you want to create.",
"lang_moduleConfiguration": "Module Configuration",
"lang_moduleName": "Module Name",
+ "lang_moduleOwnerWarn": "Some files or directories in this archive belong to another user or group than \"root\". This is only necessary\/required in special cases.",
"lang_moduleTitle": "Title",
+ "lang_moduleUnused": "Unused",
+ "lang_moduleUnusedLong": "This module is not attached to any system configuration.",
"lang_mountOptionsNote": "These settings are relevant for the MiniLinux and VMs containing non-Windows OSes. If you specify an LDAP user attribute, its contents will be used as mount options when mounting the user's home directory. If the attribute is not specified or its contents are empty, the mount attributes specified in the other field will be used. If you leave both fields empty, the clients will try to determine the options automatically.",
"lang_name": "Name",
"lang_newConfiguration": "New Configuration",
@@ -91,14 +99,30 @@
"lang_noValidCert": "The server did not supply a certificate, or the certificate is invalid.",
"lang_onProblemSearchBase": "If no users are found, please check the search base",
"lang_or": "or",
+ "lang_pwlogin_user_only": "Everyone except root",
+ "lang_qssInfoText": "QSS are Qt-specific style sheets, similar to CSS. You can use it to customize the general design.",
"lang_rebuild": "Rebuild",
"lang_rebuildLong": "Rebuild module or configuration.",
"lang_rebuildOutdatedLong": "Rebuild module or configuration. The module\/configuration is outdated or missing and should be regenerated.",
- "lang_redirectionWarning": "WARNING: This feature is experimental. It remaps the selected folders after the VM booted (via openslx.exe) to the logged in user's home drive. This might cause problems with applications that start before the pathes are patched, as they will see the old unpatched settings. Please note that this is usign undocumented or unsupported techniques to achieve this goal. It is not guaranteed that this method will work in future versions or updates of Windows. If you want to reliably remap these directories, you might want to change their locations in the VM before uploading it.",
+ "lang_redirectionWarning": "WARNING: This feature is experimental. It remaps the selected folders after the VM booted (via openslx.exe) to the logged in user's home drive. This might cause problems with applications that start before the paths are patched, as they will see the old unpatched settings. Please note that this is usign undocumented or unsupported techniques to achieve this goal. It is not guaranteed that this method will work in future versions or updates of Windows. If you want to reliably remap these directories, you might want to change their locations in the VM before uploading it.",
+ "lang_replaces": "Replaces module: ",
"lang_restartWizard": "Restart wizard",
"lang_rootKey": "root pubkey",
- "lang_rootKeyInfo": "Here you can add the public key of a keypair that you want to use for authentication as root-user. Leave this field blank to disable the feature.",
+ "lang_rootKeyInfo": "Here you can add the public key of a keypair that you want to use for authentication as root-user.",
+ "lang_screenBackground": "Background",
+ "lang_screenBackgroundDescription": " - A background consisting of a gradient with two colors.",
+ "lang_screenClock": "Clock",
+ "lang_screenColor": "Color",
+ "lang_screenHeader": "Header",
+ "lang_screenLabel": "Label",
+ "lang_screenLocked": "Lockscreen",
+ "lang_screenQss": "QSS",
+ "lang_screenSize": "Size",
+ "lang_screenText": "Edit Content",
+ "lang_screenTextInherit": "Inherit Values",
+ "lang_screenUnlocked": "Screensaver",
"lang_searchBase": "Search Base",
+ "lang_selectDeselectAll": "(De)select all",
"lang_selectFile": "Please select an archive",
"lang_selectHomeAttribute": "Home attribute",
"lang_selfSignedNote": "The certificate of this server cannot be verified using the builtin trust store. If you know that the server's certificate was signed by an unknown CA, you can try to proceed. The chain will then be extracted from the server, which should be successful in most cases. If the authentication module does not work afterwards, check the LDAP-proxy logs on the server status page.",
@@ -114,12 +138,23 @@
"lang_shareModeNote": "\"Native mode with fallback\" is experimental and known to cause temporary freezes with some VMs. Use with care.",
"lang_shareOther": "Other (Saved Games, Contacts, Favorites, ...)",
"lang_shareRemapMode": "Mapping mode",
+ "lang_shibEnabledMethods": "Enabled login methods",
+ "lang_shibEntitlements": "Entitlements",
+ "lang_shibEntitlementsPretext": "If you want to further restrict login for the institutions selected below, you can specify a list of required entitlements in this field. All entitlements listed here must be present for a user. Separate multiple attributes with a semicolon.",
+ "lang_shibIntroText": "Here you can activate client login using single sign-on (SSO, e.g. Shibboleth). This enables login via browser or QR code. This makes it possible to use 2FA, for example, provided that this is implemented by the IdP being used. Please note that this makes it impossible to automatically mount the user's home directory, as the according meta-data is not provided.",
+ "lang_shibSelectOrgs": "Select organizations that should be allowed to login.",
+ "lang_shibUseBrowser": "Allow logging in via browser embedded in login screen",
+ "lang_shibUseQrCode": "Allow logging in by scanning a QR code",
+ "lang_shibUseUserLogin": "Allow classic login via configured LDAP\/AD",
"lang_show": "Show",
"lang_showLong": "Show content of module.",
"lang_skip": "Next",
- "lang_sshMultipleHeadsup": "If you want to add multiple SSH keys to the system, you can simply create multiple modules of type SSH and add them all to your system config. Note that you should have matching port and password settings for those configs, as it is currently undefined which of the settings will apply to the final configuration.",
+ "lang_sshAllowPass": "Allow login with password",
+ "lang_sshAllowPassInfo": "Set whether users can log in via SSH using the account's password; otherwise, only pubkey-auth is enabled.",
+ "lang_sshAllowedUsers": "Allowed users",
+ "lang_sshAllowedUsersInfo": "Decides which users are allowed to log in via SSH. The special user account \"demo\" is never allowed to log in via SSH, regardless of this setting.",
"lang_ssl": "SSL",
- "lang_sslDescription": "Use SSL encryption to talk to AD server.",
+ "lang_sslDescription": "Use SSL encryption to talk to AD\/LDAP server.",
"lang_supportedFiles": "Supported File Formats",
"lang_systemConfiguration": "System Configuration",
"lang_systemConfigurationAlert": "Before you can create a system configuration, you must first create a configuration module.",
@@ -134,5 +169,8 @@
"lang_userDirectory": "User Directory",
"lang_userDirectoryInfo1": "Optional: If the clients should embed a separate directory (home directory, user directory) from a server for the user, please enter here the format in UNC notation, eg",
"lang_userDirectoryInfo2": "%s is a placeholder for the user's login name.",
- "lang_userDirectoryInfo3": "The directory is loaded with the same credentials that the user specifies when login. (That is no Kerberos support, etc.)"
+ "lang_userDirectoryInfo3": "The directory is loaded with the same credentials that the user specifies when login. (That is no Kerberos support, etc.)",
+ "lang_user_all": "Everyone",
+ "lang_user_root_only": "Only root",
+ "lang_user_user_only": "Everyone except root"
} \ No newline at end of file
diff --git a/modules-available/sysconfig/page.inc.php b/modules-available/sysconfig/page.inc.php
index 05a83924..49ff4835 100644
--- a/modules-available/sysconfig/page.inc.php
+++ b/modules-available/sysconfig/page.inc.php
@@ -22,39 +22,6 @@ class Page_SysConfig extends Page
private $haveOverriddenLocations = false;
- /**
- * Add a known configuration module. Every addmoule_* file should call this
- * for its module provided.
- *
- * @param string $id Internal identifier for the module
- * @param string $startClass Class to start wizard for creating such a module
- * @param string $title Title of this module type
- * @param string $description Description for this module type
- * @param string $group Title for group this module type belongs to
- * @param bool $unique Can only one such module be added to a config?
- * @param int $sortOrder Lower comes first, alphabetical ordering otherwiese
- */
- public static function addModule($id, $startClass, $title, $description, $group, $unique, $sortOrder = 0)
- {
- self::$moduleTypes[$id] = array(
- 'startClass' => $startClass,
- 'title' => $title,
- 'description' => $description,
- 'group' => $group,
- 'unique' => $unique,
- 'sortOrder' => $sortOrder
- );
- }
-
- /**
- *
- * @return array All registered module types
- */
- public static function getModuleTypes()
- {
- return self::$moduleTypes;
- }
-
protected function doPreprocess()
{
User::load();
@@ -75,7 +42,7 @@ class Page_SysConfig extends Page
// Location valid?
if ($this->currentLoc !== 0 && !isset($this->locations[$this->currentLoc])) {
Message::addError('locations.invalid-location-id', $this->currentLoc);
- Util::redirect('?do=sysconfig');
+ Util::redirect('?do=sysconfig', 404);
}
// Action handling
@@ -146,7 +113,7 @@ class Page_SysConfig extends Page
Render::addTemplate('sysconfig_heading');
- $action = Request::any('action', 'list');
+ $action = Request::any('action', 'list', 'string');
switch ($action) {
case 'addmodule':
User::assertPermission('module.edit');
@@ -177,25 +144,20 @@ class Page_SysConfig extends Page
return;
case 'module':
User::assertPermission('module.view-list');
- $listid = Request::post('list');
- if ($listid !== false) {
- $this->listModuleContents($listid);
- return;
- }
- break;
+ $listid = Request::post('list', Request::REQUIRED, 'int');
+ $this->listModuleContents($listid);
+ return;
case 'config':
User::assertPermission('config.view-list');
- $listid = Request::post('list');
- if ($listid !== false) {
- $this->listConfigContents($listid);
- return;
- }
- break;
+ $listid = Request::post('list', Request::REQUIRED, 'int');
+ $this->listConfigContents($listid);
+ return;
+ default:
}
Message::addError('invalid-action', $action, 'main');
}
- private function getLocationNames($locations, $ids)
+ private function getLocationNames(array $locations, array $ids): string
{
$ret = array();
foreach ($ids as $id) {
@@ -213,12 +175,12 @@ class Page_SysConfig extends Page
private function listConfigs()
{
// Configs
- $res = Database::simpleQuery("SELECT c.configid, c.title, c.filepath, c.status, c.dateline,
- GROUP_CONCAT(DISTINCT cl.locationid) AS loclist, GROUP_CONCAT(cxm.moduleid) AS modlist
+ $res = Database::simpleQuery("SELECT c.configid, c.title, c.filepath, c.status, c.dateline, c.warnings,
+ GROUP_CONCAT(DISTINCT cl.locationid) AS loclist, GROUP_CONCAT(DISTINCT cxm.moduleid) AS modlist
FROM configtgz c
LEFT JOIN configtgz_x_module cxm USING (configid)
LEFT JOIN configtgz_location cl ON (c.configid = cl.configid)
- GROUP BY configid
+ GROUP BY configid, title
ORDER BY title ASC");
$configs = array();
if ($this->currentLoc !== 0) {
@@ -227,7 +189,7 @@ class Page_SysConfig extends Page
$locationName = false;
}
$hasDefault = false;
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ foreach ($res as $row) {
if (is_null($row['loclist'])) {
$locList = array();
} else {
@@ -247,6 +209,8 @@ class Page_SysConfig extends Page
$this->haveOverriddenLocations = true;
}
$configs[] = array(
+ 'warnings' => $row['warnings'],
+ 'warnings_hidden' => (!empty($row['warnings']) && $row['status'] === 'OK') ? '' : 'hidden',
'configid' => $row['configid'],
'config' => $row['title'],
'modlist' => $row['modlist'],
@@ -273,7 +237,7 @@ class Page_SysConfig extends Page
private function listModules()
{
// Config modules
- $modules = ConfigModule::getAll();
+ $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);
@@ -282,6 +246,7 @@ class Page_SysConfig extends Page
'havemodules' => (count($modules) > 0)
);
Permission::addGlobalTags($data['perms'], null, ['module.edit', 'module.download']);
+ Permission::addGlobalTags($data['perms'], null, ['config.edit']);
Render::addTemplate('list-modules', $data);
}
@@ -291,7 +256,7 @@ class Page_SysConfig extends Page
$row = Database::queryFirst("SELECT title, filepath FROM configtgz_module WHERE moduleid = :moduleid LIMIT 1", array('moduleid' => $moduleid));
if ($row === false) {
Message::addError('config-invalid', $moduleid);
- Util::redirect('?do=sysconfig&locationid=' . $this->currentLoc);
+ Util::redirect('?do=sysconfig&locationid=' . $this->currentLoc, 404);
}
// find files in that archive
@@ -302,29 +267,9 @@ class Page_SysConfig extends Page
$status = Taskmanager::waitComplete($status, 4000);
if (!Taskmanager::isFinished($status) || Taskmanager::isFailed($status)) {
Taskmanager::addErrorMessage($status);
- Util::redirect('?do=sysconfig&locationid=' . $this->currentLoc);
- }
-
- // Sort files for better display
- $dirs = array();
- foreach ($status['data']['entries'] as $file) {
- if ($file['isdir'])
- continue;
- $dirs[dirname($file['name'])][] = $file;
- }
- ksort($dirs);
- $list = array();
- foreach ($dirs as $dir => $files) {
- $list[] = array(
- 'name' => $dir,
- 'isdir' => true
- );
- sort($files);
- foreach ($files as $file) {
- $file['size'] = Util::readableFileSize($file['size']);
- $list[] = $file;
- }
+ Util::redirect('?do=sysconfig&locationid=' . $this->currentLoc, 500);
}
+ $list = SysConfig::archiveContentsFromTask($status);
// render the template
Render::addDialog(Dictionary::translate('lang_contentOf') . ' ' . $row['title'], false, 'custom-filelist', array(
@@ -338,7 +283,7 @@ class Page_SysConfig extends Page
$config = Database::queryFirst("SELECT title FROM configtgz WHERE configid = :configid LIMIT 1", array('configid' => $configid));
if ($config === false) {
Message::addError('config-invalid', $configid);
- Util::redirect('?do=sysconfig&locationid=' . $this->currentLoc);
+ Util::redirect('?do=sysconfig&locationid=' . $this->currentLoc, 404);
}
// fetch the data
$res = Database::simpleQuery("SELECT module.moduleid, module.title AS moduletitle"
@@ -348,7 +293,7 @@ class Page_SysConfig extends Page
. " ORDER BY module.title ASC", array('configid' => $configid));
$modules = array();
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ foreach ($res as $row) {
$modules[] = array(
'module' => $row['moduletitle'],
'moduleid' => $row['moduleid']
@@ -363,11 +308,7 @@ class Page_SysConfig extends Page
private function activateConfig()
{
- $configid = Request::post('activate', false, 'int');
- if ($configid === false) {
- Message::addError('main.empty-field');
- Util::redirect('?do=sysconfig&locationid=' . $this->currentLoc);
- }
+ $configid = Request::post('activate', Request::REQUIRED, 'int');
// Validate that either the configid is valid (in case we override for a specific location)
// or that if the locationid is 0 (=global) that the configid exists, because it's not allowed
// to unset the global config
@@ -375,7 +316,7 @@ class Page_SysConfig extends Page
$row = Database::queryFirst("SELECT title, filepath FROM configtgz WHERE configid = :configid LIMIT 1", array('configid' => $configid));
if ($row === false) {
Message::addError('config-invalid', $configid);
- Util::redirect('?do=sysconfig&locationid=' . $this->currentLoc);
+ Util::redirect('?do=sysconfig&locationid=' . $this->currentLoc, 404);
}
}
$locationid = $this->currentLoc;
@@ -386,98 +327,115 @@ class Page_SysConfig extends Page
Database::exec("INSERT INTO configtgz_location (locationid, configid) VALUES (:locationid, :configid)"
. " ON DUPLICATE KEY UPDATE configid = :configid", compact('locationid', 'configid'));
}
- Event::activeConfigChanged();
- Util::redirect('?do=sysconfig&locationid=' . $this->currentLoc);
+ $task = ConfigModuleBaseLdap::ldadp();
+ if ($task !== false) {
+ TaskmanagerCallback::addCallback($task, 'ldadpStartup');
+ }
+ Util::redirect('?do=sysconfig&locationid=' . $this->currentLoc, 200);
}
private function rebuildConfig()
{
- $configid = Request::post('rebuild', 'MISSING');
+ $configid = Request::post('rebuild', Request::REQUIRED, 'int');
$config = ConfigTgz::get($configid);
- if ($config === false) {
+ if ($config === null) {
Message::addError('config-invalid', $configid);
- Util::redirect('?do=sysconfig&locationid=' . $this->currentLoc);
+ Util::redirect('?do=sysconfig&locationid=' . $this->currentLoc, 404);
}
- $ret = $config->generate(false, 500); // TODO
- if ($ret === true)
+ $ret = $config->generate(false, 500);
+ Audit::overrideResponseCode(200);
+ if ($ret === true) {
Message::addSuccess('module-rebuilt', $config->title());
- elseif ($ret === false)
+ } elseif ($ret === false) {
Message::addError('module-rebuild-failed', $config->title());
- else
+ Audit::overrideResponseCode(500);
+ } else {
Message::addInfo('module-rebuilding', $config->title());
+ }
Util::redirect('?do=sysconfig&locationid=' . $this->currentLoc);
}
private function delModule()
{
- $moduleid = Request::post('del', 'MISSING');
- $row = Database::queryFirst("SELECT title, filepath FROM configtgz_module WHERE moduleid = :moduleid LIMIT 1", array('moduleid' => $moduleid));
- if ($row === false) {
+ $moduleid = Request::post('del', Request::REQUIRED, 'int');
+ $module = Database::queryFirst("SELECT title, filepath FROM configtgz_module WHERE moduleid = :moduleid LIMIT 1", array('moduleid' => $moduleid));
+ if ($module === false) {
Message::addError('config-invalid', $moduleid);
- Util::redirect('?do=sysconfig');
+ Util::redirect('?do=sysconfig', 404);
}
- $existing = Database::queryFirst("SELECT title FROM configtgz_x_module"
- . " 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');
+ // Get config.tgz using this module *before* deleting it
+ $existing = Database::simpleQuery("SELECT configid FROM configtgz_x_module
+ WHERE moduleid = :moduleid", array('moduleid' => $moduleid));
+ // Delete DB entries and file
+ Database::exec("DELETE FROM configtgz_module WHERE moduleid = :moduleid LIMIT 1", array('moduleid' => $moduleid));
+ // Rebuild depending config.tgz files
+ foreach ($existing as $crow) {
+ $config = ConfigTgz::get($crow['configid']);
+ if ($config !== null) {
+ $config->generate();
+ }
}
+ // Delete the module from disk
$task = Taskmanager::submit('DeleteFile', array(
- 'file' => $row['filepath']
+ 'file' => $module['filepath']
));
- if (isset($task['statusCode']) && $task['statusCode'] === Taskmanager::TASK_WAITING) {
- $task = Taskmanager::waitComplete($task['id']);
+ if (Taskmanager::isTask($task)) {
+ $task = Taskmanager::waitComplete($task['id'], 10000);
}
- if (!isset($task['statusCode']) || $task['statusCode'] === Taskmanager::TASK_ERROR) {
- Message::addWarning('main.task-error', $task['data']['error']);
- } elseif ($task['statusCode'] === Taskmanager::TASK_FINISHED) {
- Message::addSuccess('module-deleted', $row['title']);
+ if (!Taskmanager::isFinished($task)) {
+ Message::addWarning('main.task-error', $task['data']['error'] ?? 'unknown');
+ } else {
+ Message::addSuccess('module-deleted', $module['title']);
}
- Database::exec("DELETE FROM configtgz_module WHERE moduleid = :moduleid LIMIT 1", array('moduleid' => $moduleid));
- Util::redirect('?do=sysconfig');
+ Util::redirect('?do=sysconfig', 200);
}
private function downloadModule()
{
- $moduleid = Request::post('download', 'MISSING');
+ $moduleid = Request::post('download', Request::REQUIRED);
$row = Database::queryFirst("SELECT title, filepath FROM configtgz_module WHERE moduleid = :moduleid LIMIT 1", array('moduleid' => $moduleid));
if ($row === false) {
Message::addError('config-invalid', $moduleid);
- Util::redirect('?do=sysconfig');
+ Util::redirect('?do=sysconfig', 404);
}
if (!Util::sendFile($row['filepath'], $row['title'] . '.tgz'))
- Util::redirect('?do=sysconfig');
+ Util::redirect('?do=sysconfig', 500);
exit(0);
}
private function rebuildModule()
{
- $moduleid = Request::post('rebuild', 'MISSING');
+ $moduleid = Request::post('rebuild', Request::REQUIRED);
$module = ConfigModule::get($moduleid);
- if ($module === false) {
+ if ($module === null) {
Message::addError('config-invalid', $moduleid);
- Util::redirect('?do=sysconfig');
+ Util::redirect('?do=sysconfig', 404);
}
- $ret = $module->generate(false, 250);
- if ($ret === true)
- Message::addSuccess('module-rebuilt', $module->title());
- elseif ($ret === false)
+ $ret = $module->generate(false, null, 500);
+ if ($ret === false) {
Message::addError('module-rebuild-failed', $module->title());
- else
+ Util::redirect('?do=sysconfig', 500);
+ }
+ if ($ret === true) {
+ Message::addSuccess('module-rebuilt', $module->title());
+ } else {
Message::addInfo('module-rebuilding', $module->title());
+ }
Util::redirect('?do=sysconfig');
}
private function delConfig()
{
- $configid = Request::post('del', 'MISSING');
+ $configid = Request::post('del', Request::REQUIRED);
$config = ConfigTgz::get($configid);
- if ($config === false) {
+ if ($config === null) {
Message::addError('config-invalid', $configid);
- Util::redirect('?do=sysconfig&locationid=' . $this->currentLoc);
+ Util::redirect('?do=sysconfig&locationid=' . $this->currentLoc, 404);
}
- if ($config->delete() !== false) {
+ if ($config->delete() === false) {
+ Message::addError('config-delete-error', Database::lastError());
+ Audit::overrideResponseCode(500);
+ } else {
Message::addSuccess('config-deleted', $config->title());
}
Util::redirect('?do=sysconfig&locationid=' . $this->currentLoc);
@@ -512,28 +470,27 @@ class Page_SysConfig extends Page
*/
protected function doAjax()
{
- if (Request::post('action') === 'status') {
+ $action = Request::any('action', '', 'string');
+ if ($action === 'status') {
$mods = Request::post('mods');
$confs = Request::post('confs');
- $outMods = array();
- $outConfs = array();
$mods = explode(',', $mods);
$confs = explode(',', $confs);
// Mods
- $res = Database::simpleQuery("SELECT moduleid FROM configtgz_module
+ $outMods = Database::queryAll("SELECT moduleid AS id FROM configtgz_module
WHERE moduleid in (:mods) AND status = 'OK'", compact('mods'));
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
- $outMods[] = $row['moduleid'];
- }
// Confs
- $res = Database::simpleQuery("SELECT configid FROM configtgz
+ $outConfs = Database::queryAll("SELECT configid AS id, warnings FROM configtgz
WHERE configid in (:confs) AND status = 'OK'", compact('confs'));
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
- $outConfs[] = $row['configid'];
- }
Header('Content-Type: application/json');
die(json_encode(array('mods' => $outMods, 'confs' => $outConfs)));
}
+ if ($action === 'addmodule') {
+ User::load();
+ User::assertPermission('module.edit');
+ $this->initAddModule();
+ AddModule_Base::ajax();
+ }
}
}
diff --git a/modules-available/sysconfig/templates/ad-selfsearch.html b/modules-available/sysconfig/templates/ad-selfsearch.html
index e6a19468..0eefc372 100644
--- a/modules-available/sysconfig/templates/ad-selfsearch.html
+++ b/modules-available/sysconfig/templates/ad-selfsearch.html
@@ -42,7 +42,6 @@
{{#mapping}}
<input type="hidden" name="mapping[{{field}}]" value="{{value}}">
{{/mapping}}
- <input name="fixnumeric" value="{{fixnumeric}}" type="hidden">
<input name="genuid" value="{{genuid}}" type="hidden">
<button type="submit" class="btn btn-primary">&laquo; {{lang_back}}</button>
</form>
@@ -67,7 +66,6 @@
{{#mapping}}
<input type="hidden" name="mapping[{{field}}]" value="{{value}}">
{{/mapping}}
- <input name="fixnumeric" value="{{fixnumeric}}" type="hidden">
<input name="genuid" value="{{genuid}}" type="hidden">
<input name="fingerprint" value="{{fingerprint}}" type="hidden">
<button id="nextbutton" type="submit" class="btn btn-primary" style="display:none">{{lang_skip}} &raquo;</button>
diff --git a/modules-available/sysconfig/templates/ad-start.html b/modules-available/sysconfig/templates/ad-start.html
index 274473ff..6dd6208a 100644
--- a/modules-available/sysconfig/templates/ad-start.html
+++ b/modules-available/sysconfig/templates/ad-start.html
@@ -21,7 +21,7 @@
<input type="hidden" name="edit" value="{{edit}}">
<div class="input-group">
<span class="input-group-addon slx-ga2">{{lang_moduleTitle}}</span>
- <input tabindex="1" name="title" value="{{title}}" type="text" class="form-control" autofocus>
+ <input tabindex="1" name="title" value="{{title}}" type="text" class="form-control" autofocus required>
</div>
<div class="input-group">
<span class="input-group-addon slx-ga2">Server *</span>
@@ -67,8 +67,8 @@
<br>
<div>
<div class="checkbox">
- <input id="num-cb" type="checkbox" name="genuid" {{#genuid}}checked{{/genuid}}>
- <label for="num-cb"><b>{{lang_genUid}}</b></label>
+ <input id="genuid-cb" type="checkbox" name="genuid" {{#genuid}}checked{{/genuid}}>
+ <label for="genuid-cb"><b>{{lang_genUid}}</b></label>
</div>
<div>
<i>{{lang_genUidDescription}}</i>
@@ -77,17 +77,7 @@
<br>
<div>
<div class="checkbox">
- <input id="num-cb" type="checkbox" name="fixnumeric" {{#fixnumeric}}checked{{/fixnumeric}}>
- <label for="num-cb"><b>{{lang_fixNumeric}}</b></label>
- </div>
- <div>
- <i>{{lang_fixNumericDescription}}</i>
- </div>
- </div>
- <br>
- <div>
- <div class="checkbox">
- <input if="ssl-cb" type="checkbox" name="ssl" onchange="$('#cert-box').css('display', this.checked ? '' : 'none')" {{#ssl}}checked{{/ssl}}>
+ <input id="ssl-cb" type="checkbox" name="ssl" onchange="$('#cert-box').css('display', this.checked ? '' : 'none')" {{#ssl}}checked{{/ssl}}>
<label for="ssl-cb"><b>{{lang_ssl}}</b></label>
</div>
<div>
diff --git a/modules-available/sysconfig/templates/ad_ldap-checkconnection.html b/modules-available/sysconfig/templates/ad_ldap-checkconnection.html
index e686c29f..ced65650 100644
--- a/modules-available/sysconfig/templates/ad_ldap-checkconnection.html
+++ b/modules-available/sysconfig/templates/ad_ldap-checkconnection.html
@@ -30,7 +30,6 @@
<input type="hidden" name="mapping[{{field}}]" value="{{value}}">
{{/mapping}}
- <input name="fixnumeric" value="{{fixnumeric}}" type="hidden">
<input name="genuid" value="{{genuid}}" type="hidden">
<button type="submit" class="btn btn-primary">&laquo; {{lang_back}}</button>
</form>
@@ -55,7 +54,6 @@
{{#mapping}}
<input type="hidden" name="mapping[{{field}}]" value="{{value}}">
{{/mapping}}
- <input name="fixnumeric" value="{{fixnumeric}}" type="hidden">
<input name="genuid" value="{{genuid}}" type="hidden">
<input name="originalbinddn" value="{{binddn}}" type="hidden">
<button id="nextbutton" type="submit" class="btn btn-primary" style="display:none">{{lang_next}} &raquo;</button>
diff --git a/modules-available/sysconfig/templates/ad_ldap-checkcredentials.html b/modules-available/sysconfig/templates/ad_ldap-checkcredentials.html
index d698d994..b560eecd 100644
--- a/modules-available/sysconfig/templates/ad_ldap-checkcredentials.html
+++ b/modules-available/sysconfig/templates/ad_ldap-checkcredentials.html
@@ -25,7 +25,6 @@
{{#mapping}}
<input type="hidden" name="mapping[{{field}}]" value="{{value}}">
{{/mapping}}
- <input name="fixnumeric" value="{{fixnumeric}}" type="hidden">
<input name="genuid" value="{{genuid}}" type="hidden">
<button type="submit" class="btn btn-primary">&laquo; {{lang_back}}</button>
</form>
@@ -49,13 +48,13 @@
{{#mapping}}
<input type="hidden" name="mapping[{{field}}]" value="{{value}}">
{{/mapping}}
- <input name="fixnumeric" value="{{fixnumeric}}" type="hidden">
<input name="genuid" value="{{genuid}}" type="hidden">
<input name="fingerprint" value="{{fingerprint}}" type="hidden">
<input name="originalbinddn" value="{{binddn}}" type="hidden">
<button id="nextbutton" type="submit" class="btn btn-primary" style="display:none">{{lang_skip}} &raquo;</button>
</form>
</div>
+<div class="clearfix"></div>
<script type="text/javascript">
function ldapCb(task)
{
diff --git a/modules-available/sysconfig/templates/ad_ldap-homedir.html b/modules-available/sysconfig/templates/ad_ldap-homedir.html
index 8a6c10de..33f55c16 100644
--- a/modules-available/sysconfig/templates/ad_ldap-homedir.html
+++ b/modules-available/sysconfig/templates/ad_ldap-homedir.html
@@ -17,7 +17,6 @@
{{#mapping}}
<input type="hidden" name="mapping[{{field}}]" value="{{value}}">
{{/mapping}}
- <input name="fixnumeric" value="{{fixnumeric}}" type="hidden">
<input name="genuid" value="{{genuid}}" type="hidden">
<input name="fingerprint" value="{{fingerprint}}" type="hidden">
diff --git a/modules-available/sysconfig/templates/assign.html b/modules-available/sysconfig/templates/assign.html
new file mode 100644
index 00000000..f06b581e
--- /dev/null
+++ b/modules-available/sysconfig/templates/assign.html
@@ -0,0 +1,33 @@
+<form role="form" enctype="multipart/form-data" method="post" action="?do=SysConfig&amp;action=addmodule&amp;step=AddModule_Assign">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="edit" value="{{edit}}">
+ <input type="hidden" name="assign" value="true">
+
+ <h3>{{name}}</h3>
+
+ {{#configs}}
+ <div class="input-group">
+ <span class="input-group-addon">
+ <div class="checkbox">
+ <input type="checkbox" name="configs[]" value="{{configid}}" id="config{{configid}}" {{checked}}>
+ <label></label>
+ </div>
+ </span>
+ <label class="form-control config-label" for="config{{configid}}">
+ <span>{{title}}</span>
+ {{#replaces}}<span class="text-danger">{{lang_replaces}} {{.}}</span>{{/replaces}}
+ </label>
+ </div>
+ {{/configs}}
+
+ <div class="text-right" style="margin-top: 12px">
+ <button type="submit" class="btn btn-primary"><span class="glyphicon glyphicon-floppy-disk"></span> {{lang_save}}</button>
+ </div>
+</form>
+
+<style>
+ .config-label {
+ display: flex !important;
+ justify-content: space-between !important;
+ }
+</style> \ No newline at end of file
diff --git a/modules-available/sysconfig/templates/branding-check.html b/modules-available/sysconfig/templates/branding-check.html
index d48f9631..80eb4d48 100644
--- a/modules-available/sysconfig/templates/branding-check.html
+++ b/modules-available/sysconfig/templates/branding-check.html
@@ -20,7 +20,7 @@
<input type="hidden" name="edit" value="{{edit}}">
<div class="form-group">
<label for="title-id">{{lang_title}}</label>
- <input type="text" name="title" value="{{title}}" id ="title-id" class="form-control" placeholder="Name des Moduls">
+ <input type="text" name="title" value="{{title}}" id ="title-id" class="form-control" placeholder="Name des Moduls" required>
</div>
<div class="btn-group">
<a class="btn btn-default" href="?do=SysConfig&action=addmodule&step=Branding_Start">{{lang_cancel}}</a>
diff --git a/modules-available/sysconfig/templates/branding-start.html b/modules-available/sysconfig/templates/branding-start.html
index 0db085d9..a6346552 100644
--- a/modules-available/sysconfig/templates/branding-start.html
+++ b/modules-available/sysconfig/templates/branding-start.html
@@ -14,7 +14,7 @@
<input type="text" class="form-control" readonly placeholder="{{lang_selectFile}}">
<span class="input-group-btn">
<span class="btn btn-default btn-file">
- {{lang_browseForFile}}&hellip; <input type="file" name="file" id="input-file">
+ {{lang_browseForFile}}&hellip; <input type="file" accept="image/svg+xml" name="file" id="input-file">
</span>
</span>
</div>
diff --git a/modules-available/sysconfig/templates/cfg-finish.html b/modules-available/sysconfig/templates/cfg-finish.html
index 891a3e65..7ba71c04 100644
--- a/modules-available/sysconfig/templates/cfg-finish.html
+++ b/modules-available/sysconfig/templates/cfg-finish.html
@@ -6,8 +6,9 @@
<input type="hidden" name="token" value="{{token}}">
<input type="hidden" name="action" value="config">
<input type="hidden" name="activate" value="{{configid}}">
- <div class="text-right">
- <a href="?do=SysConfig" class="btn btn-default">&laquo; {{lang_backToSysconfig}}</a>
+ <a href="?do=SysConfig" class="btn btn-default">&laquo; {{lang_backToSysconfig}}</a>
+ <div class="slx-space">
+ <p>{{lang_activateGloballyText}}</p>
<button type="submit" class="btn btn-default">
<span class="glyphicon glyphicon-globe"></span>
{{lang_activateGlobally}}
diff --git a/modules-available/sysconfig/templates/cfg-start.html b/modules-available/sysconfig/templates/cfg-start.html
index b4628cba..018cf89f 100644
--- a/modules-available/sysconfig/templates/cfg-start.html
+++ b/modules-available/sysconfig/templates/cfg-start.html
@@ -3,7 +3,7 @@
<input type="hidden" name="edit" value="{{edit}}">
<div class="input-group">
<span class="input-group-addon">{{lang_name}} *</span>
- <input type="text" name="title" value="{{title}}" class="form-control" placeholder="{{lang_configuration}}" autofocus="autofocus">
+ <input type="text" name="title" value="{{title}}" class="form-control" placeholder="{{lang_configuration}}" autofocus="autofocus" required>
</div>
<hr>
<p>{{lang_configurationChoose}}</p>
diff --git a/modules-available/sysconfig/templates/custom-filelist.html b/modules-available/sysconfig/templates/custom-filelist.html
index 344eece3..20cedfda 100644
--- a/modules-available/sysconfig/templates/custom-filelist.html
+++ b/modules-available/sysconfig/templates/custom-filelist.html
@@ -4,11 +4,23 @@
{{#files}}
<tr>
{{#isdir}}
- <td class="fileEntry slx-bold" colspan="2">{{name}}</td>
+ <td class="fileEntry slx-bold" colspan="4">{{name}}</td>
{{/isdir}}
{{^isdir}}
- <td class="fileEntry">{{name}}</td>
- <td>{{size}}</td>
+ <td class="fileEntry">
+ {{name}}
+ {{#linkTarget}}
+ -&gt;
+ <span class="text-nowrap">{{linkTarget}}</span>
+ {{/linkTarget}}
+ </td>
+ <td class="text-nowrap">{{user}}{{#user}}{{#group}}/{{/group}}{{/user}}{{group}}</td>
+ <td class="text-nowrap">{{userId}}:{{groupId}}</td>
+ <td class="text-nowrap">
+ {{^linkTarget}}
+ {{size}}
+ {{/linkTarget}}
+ </td>
{{/isdir}}
</tr>
{{/files}}
diff --git a/modules-available/sysconfig/templates/custom-fileselect.html b/modules-available/sysconfig/templates/custom-fileselect.html
index f14a6fde..5f190f08 100644
--- a/modules-available/sysconfig/templates/custom-fileselect.html
+++ b/modules-available/sysconfig/templates/custom-fileselect.html
@@ -4,7 +4,8 @@
<input type="hidden" name="edit" value="{{edit}}">
<div class="input-group">
<span class="input-group-addon">{{lang_moduleName}}</span>
- <input type="text" name="title" value="{{title}}" class="form-control" placeholder="Mein Konfigurationsmodul" autofocus="autofocus">
+ <input type="text" name="title" value="{{title}}" class="form-control" placeholder="Mein Konfigurationsmodul"
+ autofocus="autofocus" required>
</div>
<div class="pull-right">
<button type="submit" class="btn btn-primary">{{lang_next}} &raquo;</button>
@@ -12,20 +13,43 @@
<div class="clearfix"></div>
<hr>
<p>{{lang_checkFileContent}}</p>
+ {{#userGroupWarn}}
+ <div class="alert alert-warning">
+ {{lang_moduleOwnerWarn}}
+ </div>
+ <div class="checkbox">
+ <input id="force-owner" type="checkbox" name="force-owner" value="1" checked>
+ <label for="force-owner">{{lang_forceRootOwner}}</label>
+ </div>
+ <div class="slx-space"></div>
+ {{/userGroupWarn}}
<table class="table table-bordered table-condensed">
- {{#files}}
+ {{#files}}
<tr>
{{#isdir}}
- <td class="fileEntry slx-bold" colspan="2">{{name}}</td>
+ <td class="fileEntry slx-bold" colspan="4">{{name}}</td>
{{/isdir}}
{{^isdir}}
- <td class="fileEntry">{{name}}</td>
- <td>{{size}}</td>
+ <td class="fileEntry">
+ {{name}}
+ {{#linkTarget}}
+ -&gt;
+ <span class="text-nowrap">{{linkTarget}}</span>
+ {{/linkTarget}}
+ </td>
+ <td class="text-nowrap">{{user}}{{#user}}{{#group}}/{{/group}}{{/user}}{{group}}</td>
+ <td class="text-nowrap">{{userId}}:{{groupId}}</td>
+ <td class="text-nowrap">
+ {{^linkTarget}}
+ {{size}}
+ {{/linkTarget}}
+ </td>
{{/isdir}}
</tr>
- {{/files}}
+ {{/files}}
</table>
<div class="pull-right">
<button type="submit" class="btn btn-primary">{{lang_next}} &raquo;</button>
</div>
+ <div class="clearfix"></div>
</form>
diff --git a/modules-available/sysconfig/templates/js.html b/modules-available/sysconfig/templates/js.html
index 157e8d12..63e2b8c6 100644
--- a/modules-available/sysconfig/templates/js.html
+++ b/modules-available/sysconfig/templates/js.html
@@ -1,4 +1,11 @@
-<div class="hidden" id="confirm-delete">{{lang_confirmDeleteQuestion}}</div>
+<div class="hidden" id="confirm-delete">
+ {{lang_confirmDeleteQuestion}}
+ <div id="delete-item-list" class="hidden">
+ <div class="slx-space"></div>
+ <div class="slx-bold">{{lang_modStillUsedBy}}</div>
+ <ul></ul>
+ </div>
+</div>
<script type="application/javascript"><!--
document.addEventListener("DOMContentLoaded", function () {
checkBuildStatus();
diff --git a/modules-available/sysconfig/templates/ldap-finish.html b/modules-available/sysconfig/templates/ldap-finish.html
index a735e792..bd998bfd 100644
--- a/modules-available/sysconfig/templates/ldap-finish.html
+++ b/modules-available/sysconfig/templates/ldap-finish.html
@@ -12,6 +12,7 @@
<div id="finish" class="pull-right" style="display:none">
<a href="?do=SysConfig" class="btn btn-primary">{{lang_toSystemConfiguration}}</a>
</div>
+<div class="clearfix"></div>
<script type="text/javascript">
function ldapCb(task)
{
diff --git a/modules-available/sysconfig/templates/ldap-start.html b/modules-available/sysconfig/templates/ldap-start.html
index b3495741..e6c98680 100644
--- a/modules-available/sysconfig/templates/ldap-start.html
+++ b/modules-available/sysconfig/templates/ldap-start.html
@@ -11,7 +11,7 @@
<input type="hidden" name="edit" value="{{edit}}">
<div class="input-group">
<span class="input-group-addon slx-ga2">{{lang_moduleTitle}}</span>
- <input tabindex="1" name="title" value="{{title}}" type="text" class="form-control">
+ <input tabindex="1" name="title" value="{{title}}" type="text" class="form-control" required>
</div>
<div class="input-group">
<span class="input-group-addon slx-ga2">Server *</span>
@@ -68,8 +68,8 @@
<br>
<div>
<div class="checkbox">
- <input id="num-cb" type="checkbox" name="genuid" {{#genuid}}checked{{/genuid}}>
- <label for="num-cb"><b>{{lang_genUid}}</b></label>
+ <input id="genuid-cb" type="checkbox" name="genuid" {{#genuid}}checked{{/genuid}}>
+ <label for="genuid-cb"><b>{{lang_genUid}}</b></label>
</div>
<div>
<i>{{lang_genUidDescription}}</i>
@@ -78,16 +78,6 @@
<br>
<div>
<div class="checkbox">
- <input id="num-cb" type="checkbox" name="fixnumeric" {{#fixnumeric}}checked{{/fixnumeric}}>
- <label for="num-cb"><b>{{lang_fixNumeric}}</b></label>
- </div>
- <div>
- <i>{{lang_fixNumericDescription}}</i>
- </div>
- </div>
- <br>
- <div>
- <div class="checkbox">
<input id="ssl-cb" type="checkbox" name="ssl" onchange="$('#cert-box').css('display', this.checked ? '' : 'none')" {{#ssl}}checked{{/ssl}}>
<label for="ssl-cb"><b>{{lang_ssl}}</b></label>
</div>
diff --git a/modules-available/sysconfig/templates/list-configs.html b/modules-available/sysconfig/templates/list-configs.html
index ea6705da..1370155f 100644
--- a/modules-available/sysconfig/templates/list-configs.html
+++ b/modules-available/sysconfig/templates/list-configs.html
@@ -20,11 +20,17 @@
<input type="hidden" name="locationid" value="{{locationid}}">
<table id="conftable" class="slx-table table-hover" style="width:100%">
{{#configs}}
- <tr>
- <td data-id="{{configid}}" data-modlist="{{modlist}}" class="confrow slx-pointer" width="100%" title="{{dateline_s}}">
- <table class="slx-ellipsis"><tr><td>{{config}}</td></tr></table>
+ <tr data-id="{{configid}}" data-modlist="{{modlist}}" class="confrow">
+ <td class="title slx-pointer" width="100%" title="{{dateline_s}}">
+ <table class="slx-ellipsis"><tr><td>
+ <button type="button" class="btn btn-xs btn-default btn-warnings {{warnings_hidden}}" data-confirm="#confirm-mod-{{configid}}">
+ <span class="glyphicon glyphicon-exclamation-sign text-danger"></span>
+ </button>
+ {{config}}
+ </td></tr></table>
</td>
<td>
+ <pre id="confirm-mod-{{configid}}" class="hidden row-warnings">{{warnings}}</pre>
{{^current}}
<button class="btn btn-primary btn-xs" name="activate" value="{{configid}}" {{perms.config.assign.disabled}}>
<span class="glyphicon glyphicon-flag"></span>
@@ -49,10 +55,10 @@
{{^locationid}}
<button
{{#needrebuild}}
- class="refconf btn btn-primary btn-xs"
+ class="btn-rebuild btn btn-primary btn-xs"
{{/needrebuild}}
{{^needrebuild}}
- class="refconf btn btn-default btn-xs"
+ class="btn-rebuild btn btn-default btn-xs"
{{/needrebuild}}
name="rebuild" value="{{configid}}" title="{{lang_rebuild}}"
{{perms.config.edit.disabled}}>
@@ -66,7 +72,7 @@
href="?do=SysConfig&amp;action=addconfig&amp;edit={{configid}}" title="{{lang_edit}}">
<span class="glyphicon glyphicon-edit"></span>
</a>
- <button type="submit" class="btn btn-danger btn-xs" name="del" value="{{configid}}"
+ <button type="submit" class="btn btn-danger btn-xs btn-del-config" name="del" value="{{configid}}"
title="{{lang_delete}}" {{perms.config.edit.disabled}} data-confirm="#confirm-delete"
data-title="{{config}}">
<span class="glyphicon glyphicon-trash"></span>
diff --git a/modules-available/sysconfig/templates/list-legend.html b/modules-available/sysconfig/templates/list-legend.html
index 809a0449..dc271aae 100644
--- a/modules-available/sysconfig/templates/list-legend.html
+++ b/modules-available/sysconfig/templates/list-legend.html
@@ -22,9 +22,18 @@
{{lang_editLong}}
</p>
<p>
+ <span class="btn btn-success btn-xs" title="{{lang_assignToConfigs}}"><span class="glyphicon glyphicon-arrow-right"></span>
+ </span>
+ {{lang_assignToConfigsLong}}
+ </p>
+ <p>
<span class="btn btn-danger btn-xs" title="{{lang_delete}}"><span class="glyphicon glyphicon-trash"></span></span>
{{lang_deleteLong}}
</p>
+ <p>
+ <span class="glyphicon glyphicon-question-sign" title="{{lang_moduleUnused}}"></span>
+ {{lang_moduleUnusedLong}}
+ </p>
{{#showLocationBadge}}
<p>
<span class="badge">+4</span>
diff --git a/modules-available/sysconfig/templates/list-modules.html b/modules-available/sysconfig/templates/list-modules.html
index fee3e0f3..9f2ee872 100644
--- a/modules-available/sysconfig/templates/list-modules.html
+++ b/modules-available/sysconfig/templates/list-modules.html
@@ -10,10 +10,13 @@
<input type="hidden" name="action" value="module">
<table id="modtable" class="slx-table table-hover" style="width:100%">
{{#modules}}
- <tr>
+ <tr data-id="{{id}}" class="modrow">
<td class="badge text-nowrap">{{moduleType}}</td>
- <td data-id="{{id}}" class="modrow slx-pointer" width="100%" title="{{lang_lastEdited}} {{dateline_s}}">
- <table class="slx-ellipsis"><tr><td>{{title}}</td></tr></table>
+ <td class="title slx-pointer" width="100%" title="{{lang_lastEdited}} {{dateline_s}}">
+ <table class="slx-ellipsis"><tr><td>
+ <span class="glyphicon glyphicon-question-sign pull-right icon-unused hidden" title="{{lang_moduleUnused}}"></span>
+ {{title}}
+ </td></tr></table>
</td>
<td class="text-nowrap">
{{#allowDownload}}
@@ -27,20 +30,25 @@
<td class="text-nowrap">
<button
{{#needRebuild}}
- class="refmod btn btn-primary btn-xs"
+ class="btn-rebuild btn btn-primary btn-xs"
{{/needRebuild}}
{{^needRebuild}}
- class="refmod btn btn-default btn-xs"
+ class="btn-rebuild btn btn-default btn-xs"
{{/needRebuild}}
name="rebuild" value="{{id}}" title="{{lang_rebuild}}" {{perms.module.edit.disabled}}>
<span class="glyphicon glyphicon-refresh"></span>
</button>
<a class="btn btn-success btn-xs {{perms.module.edit.disabled}}"
- href="?do=SysConfig&amp;action=addmodule&amp;step={{moduleType}}_Start&amp;edit={{id}}"
- title="{{lang_edit}}">
- <span class="glyphicon glyphicon-edit"></span>
+ href="?do=SysConfig&amp;action=addmodule&amp;step={{moduleType}}_Start&amp;edit={{id}}"
+ title="{{lang_edit}}">
+ <span class="glyphicon glyphicon-edit"></span>
</a>
- <button type="submit" class="btn btn-danger btn-xs" name="del" value="{{id}}"
+ <a class="btn btn-success btn-xs {{perms.config.edit.disabled}}"
+ href="?do=SysConfig&action=addmodule&step=AddModule_Assign&edit={{id}}"
+ title="{{lang_assignToConfigs}}">
+ <span class="glyphicon glyphicon-arrow-right"></span>
+ </a>
+ <button type="submit" class="btn btn-danger btn-xs btn-del-module" name="del" value="{{id}}"
title="{{lang_delete}}" {{perms.module.edit.disabled}} data-confirm="#confirm-delete"
data-title="{{title}}">
<span class="glyphicon glyphicon-trash"></span>
diff --git a/modules-available/sysconfig/templates/loginscreen-start.html b/modules-available/sysconfig/templates/loginscreen-start.html
new file mode 100644
index 00000000..da42a48e
--- /dev/null
+++ b/modules-available/sysconfig/templates/loginscreen-start.html
@@ -0,0 +1,38 @@
+<form role="form" method="post" action="?do=SysConfig&amp;action=addmodule&amp;step={{next}}">
+<input type="hidden" name="token" value="{{token}}">
+<input type="hidden" name="edit" value="{{edit}}">
+
+<div>{{lang_loginScreenIntroText}}</div>
+<div class="slx-space"></div>
+
+<div class="input-group">
+ <span class="input-group-addon">{{lang_moduleTitle}}</span>
+ <input tabindex="1" name="title" value="{{title}}" type="text" class="form-control" autofocus required>
+</div>
+<div class="slx-space"></div>
+
+{{#fields}}
+ <div class="slx-smallspace"></div>
+<div>
+ <label for="id-{{field}}">{{caption}}</label>
+ <input id="id-{{field}}" name="{{field}}" value="{{value}}" type="text" class="form-control" {{#regex}}pattern="{{regex}}"{{/regex}}>
+</div>
+{{/fields}}
+
+<div class="slx-space"></div>
+
+<div>
+ <i>{{lang_qssInfoText}}</i>
+</div>
+<div class="slx-space">
+ <a href="https://www.bwlehrpool.de/wiki/doku.php/satellite/qss" target="_blank">
+ QSS
+ <span class="glyphicon glyphicon-new-window"></span>
+ </a>
+</div>
+
+<div class="pull-right">
+ <button type="submit" class="btn btn-primary"><span class="glyphicon glyphicon-floppy-disk"></span> {{lang_save}}</button>
+</div>
+<div class="clearfix"></div>
+</form> \ No newline at end of file
diff --git a/modules-available/sysconfig/templates/screensaver-start.html b/modules-available/sysconfig/templates/screensaver-start.html
new file mode 100644
index 00000000..cbf2f021
--- /dev/null
+++ b/modules-available/sysconfig/templates/screensaver-start.html
@@ -0,0 +1,131 @@
+<form role="form" enctype="multipart/form-data" method="post" action="?do=SysConfig&amp;action=addmodule&amp;step={{step}}">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="next" value="{{next}}">
+ <input type="hidden" name="id" value="{{id}}">
+ <input type="hidden" name="edit" value="{{edit}}">
+
+ <div class="form-group">
+ <div class="input-group">
+ <span class="input-group-addon">{{lang_moduleName}}</span>
+ <input type="text" tabindex="1" name="title" value="{{title}}" class="form-control" autofocus required>
+ </div>
+ </div>
+
+ <div>
+ <i>{{lang_qssInfoText}}</i>
+ </div>
+ <div class="slx-space">
+ <a href="https://www.bwlehrpool.de/wiki/doku.php/satellite/qss" target="_blank">
+ QSS
+ <span class="glyphicon glyphicon-new-window"></span>
+ </a>
+ </div>
+
+ <input type="hidden" id="helper-mode" name="helper_mode" value="false">
+ <div class="form-group">
+ <div class="input-group btn-group">
+ <span class="input-group-addon slx-ga">{{lang_mode}}</span>
+ <a class="btn btn-default" tabindex="2" id="btn-easy-mode" type="button" onclick="switchMode(1)">
+ <span class="glyphicon glyphicon-user"></span>
+ {{lang_modeEasy}}
+ </a>
+ <a class="btn btn-default active" tabindex="3" id="btn-advanced-mode" onclick="switchMode(0)">
+ <span class="glyphicon glyphicon-education"></span>
+ {{lang_modeAdvanced}}
+ </a>
+ </div>
+ </div>
+
+ <div class="form-group" id="advanced-mode">
+ <label for="qss">{{lang_screenQss}}</label>
+ <textarea tabindex="4" id="qss" name="qss" rows="20" class="form-control">{{qss}}</textarea>
+ </div>
+
+ <div id="easy-mode" hidden>
+ <div class="form-group">
+ <label>{{lang_screenBackground}}</label>
+ {{lang_screenBackgroundDescription}}
+ <div class="input-group">
+ <span class="input-group-addon slx-ga">{{lang_screenColor}} 1</span>
+ <input id="screensaver-background-color-1" tabindex="5" type="text" name="bg_color_1" value="" class="form-control" placeholder="#443">
+ </div>
+ <div class="input-group">
+ <span class="input-group-addon slx-ga">{{lang_screenColor}} 2</span>
+ <input id="screensaver-background-color-2" tabindex="6" type="text" name="bg_color_2" value="" class="form-control" placeholder="#000">
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label >{{lang_screenLabel}}</label>
+ <div class="input-group">
+ <span class="input-group-addon slx-ga">{{lang_screenColor}}</span>
+ <input type="text" tabindex="7" name="label_color" value="" class="form-control" placeholder="#f64">
+ </div>
+ <div class="input-group">
+ <span class="input-group-addon slx-ga">{{lang_screenSize}}</span>
+ <input type="number" tabindex="8" name="label_size" value="10" class="form-control" placeholder="10">
+ <span class="input-group-addon">pt</span>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label >{{lang_screenClock}}</label>
+ <div class="input-group">
+ <span class="input-group-addon slx-ga">{{lang_screenColor}}</span>
+ <input type="text" tabindex="9" name="clock_color" value="" class="form-control" placeholder="#999">
+ </div>
+ <div class="input-group">
+ <span class="input-group-addon slx-ga">{{lang_screenSize}}</span>
+ <input type="number" tabindex="10" name="clock_size" value="20" class="form-control" placeholder="20">
+ <span class="input-group-addon">pt</span>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label >{{lang_screenHeader}}</label>
+ <div class="input-group">
+ <span class="input-group-addon slx-ga">{{lang_screenColor}}</span>
+ <input type="text" tabindex="11" name="header_color" value="" class="form-control" placeholder="#f640">
+ </div>
+ <div class="input-group">
+ <span class="input-group-addon slx-ga">{{lang_screenSize}}</span>
+ <input type="number" tabindex="12" name="header_size" value="20" class="form-control" placeholder="20">
+ <span class="input-group-addon">pt</span>
+ </div>
+ </div>
+ </div>
+
+ <hr>
+ <div class="btn-group">
+ <a class="btn btn-default" id="btn-back" tabindex="5"
+ {{#edit}}href="?do=sysconfig"{{/edit}}{{^edit}}href="?do=SysConfig&action=addmodule"{{/edit}}>{{lang_back}}</a>
+ </div>
+ <div class="btn-group pull-right">
+ <button type="submit" id="btn-next" tabindex="6" class="btn btn-primary">{{lang_next}} &raquo;</button>
+ </div>
+ <div class="clearfix"></div>
+</form>
+
+<script type="text/javascript">
+ function switchMode(mode) {
+ // 0 = advanced mode
+ // 1 = easy mode
+ if (mode === 0) {
+ $('#easy-mode').hide();
+ $('#advanced-mode').show();
+ $('#btn-easy-mode').removeClass('active');
+ $('#btn-advanced-mode').addClass('active');
+ $('#helper-mode').val('false');
+ $('#btn-back').prop('tabindex', 5);
+ $('#btn-next').prop('tabindex', 6);
+ } else if (mode === 1) {
+ $('#advanced-mode').hide();
+ $('#easy-mode').show();
+ $('#btn-advanced-mode').removeClass('active');
+ $('#btn-easy-mode').addClass('active');
+ $('#helper-mode').val('true');
+ $('#btn-back').prop('tabindex', 13);
+ $('#btn-next').prop('tabindex', 14);
+ }
+ }
+</script>
diff --git a/modules-available/sysconfig/templates/screensaver-text.html b/modules-available/sysconfig/templates/screensaver-text.html
new file mode 100644
index 00000000..acf39cc5
--- /dev/null
+++ b/modules-available/sysconfig/templates/screensaver-text.html
@@ -0,0 +1,121 @@
+<form role="form" enctype="multipart/form-data" method="post" action="?do=SysConfig&amp;action=addmodule&amp;step={{step}}">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" id="next" name="next" value="{{next}}">
+ <input type="hidden" name="id" value="{{id}}">
+ <input type="hidden" name="edit" value="{{edit}}">
+
+ <div class="form-group">
+ <h4><label>{{title}}</label></h4>
+ <h5>{{description}}</h5>
+
+ <h4>{{lang_screenUnlocked}}</h4>
+ <div class="input-group">
+ <span class="input-group-addon slx-ga">{{lang_title}}</span>
+ <input type="text" tabindex="1" name="msg_value" value="{{msg_value}}" class="form-control">
+ </div>
+ </div>
+
+ <div class="form-group">
+ <span class="input-group-addon top-addon">{{lang_screenText}}</span>
+ <textarea class="form-control summernote" id ="text-id" name="text_value" rows="5" cols="30">{{text_value}}</textarea>
+ </div>
+ <hr>
+ <h4>{{lang_screenLocked}}</h4>
+ <input type="hidden" class="slx-ga" id="inherit_locked" name="inherit_locked" value="{{inherit_locked}}">
+ <div class="form-group">
+ <div class="input-group btn-group">
+ <span class="input-group-addon slx-ga">{{lang_screenTextInherit}}</span>
+ <a class="btn btn-default" id="btn-inherit-on" type="button" onclick="switchMode(true)" tabindex="2">
+ <span class="glyphicon glyphicon-ok"></span>
+ </a>
+ <a class="btn btn-default active" id="btn-inherit-off" onclick="switchMode(false)" tabindex="3">
+ <span class="glyphicon glyphicon-remove"></span>
+ </a>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <div class="input-group">
+ <span class="input-group-addon slx-ga">{{lang_title}}</span>
+ <input type="text" id="msg-locked-id" tabindex="4" name="msg_locked_value" value="{{msg_locked_value}}" class="form-control">
+ </div>
+ </div>
+
+ <div class="form-group">
+ <span class="input-group-addon top-addon">{{lang_screenText}}</span>
+ <textarea class="form-control summernote" id ="text-locked-id" name="text_locked_value" rows="5" cols="30">{{text_locked_value}}</textarea>
+ </div>
+
+ <div class="btn-group">
+ <button class="btn btn-default" type="submit" onclick="goBack()" tabindex="5">{{lang_back}}</button>
+ </div>
+ <div class="btn-group pull-right">
+ <button type="submit" class="btn btn-primary" tabindex="6">
+ {{#lastStep}}
+ <span class="glyphicon glyphicon-floppy-disk"></span>
+ {{lang_save}}
+ {{/lastStep}}
+ {{^lastStep}}{{lang_next}} &raquo;{{/lastStep}}
+ </button>
+ </div>
+ <div class="clearfix"></div>
+</form>
+
+<script type="text/javascript">
+ document.addEventListener("DOMContentLoaded", function () {
+ // Init summernote to e.g. disable video because xscreensaver can't handle it
+ $('.summernote').summernote({
+ toolbar: [
+ // [groupName, [list of button]]
+ ['style', ['bold', 'italic', 'underline', 'clear']],
+ ['font', ['strikethrough', 'superscript', 'subscript']],
+ ['fontsize', ['fontsize']],
+ ['color', ['color']],
+ ['para', ['style', 'ul', 'ol', 'paragraph']],
+ ['height', ['height']],
+ ['insert', ['picture', 'link', 'table', 'hr']],
+ ['misc', ['undo', 'redo', 'codeview', 'fullscreen']]
+ ]
+ });
+ switchMode({{inherit_locked}});
+ }, false);
+
+ function switchMode(mode) {
+ // true = inherit on
+ // false = inherit off
+ if (mode) {
+ $('#msg-locked-id').prop('disabled', true);
+ $('#text-locked-id').summernote('disable');
+ $('#btn-inherit-on').addClass('active');
+ $('#btn-inherit-off').removeClass('active');
+ $('#inherit_locked').val(true);
+ } else {
+ $('#msg-locked-id').prop('disabled', false);
+ $('#text-locked-id').summernote('enable');
+ $('#btn-inherit-on').removeClass('active');
+ $('#btn-inherit-off').addClass('active');
+ $('#inherit_locked').val(false);
+ }
+ }
+
+ function goBack() {
+ $('#next').val('{{prev}}');
+ }
+</script>
+
+<style>
+ .top-addon {
+ border-right: 1px solid #ccc !important;
+ border-top-right-radius: 4px !important;
+ border-bottom-left-radius: 0 !important;
+ border-bottom-right-radius: 0 !important;
+ border-bottom: 0 !important;
+ }
+
+ /* Used to override some summernote css to get a proper addon header */
+ .note-editor.note-frame {
+ border-color: #ccc !important;
+ border-top-left-radius: 0 !important;
+ border-top-right-radius: 0 !important;
+ }
+</style> \ No newline at end of file
diff --git a/modules-available/sysconfig/templates/shibauth-orgs.html b/modules-available/sysconfig/templates/shibauth-orgs.html
new file mode 100644
index 00000000..97cdf69d
--- /dev/null
+++ b/modules-available/sysconfig/templates/shibauth-orgs.html
@@ -0,0 +1,64 @@
+<form role="form" method="post" action="?do=SysConfig&amp;action=addmodule&amp;step={{next}}">
+<input type="hidden" name="token" value="{{token}}">
+<input type="hidden" name="edit" value="{{edit}}">
+
+<div>{{lang_shibSelectOrgs}}</div>
+<p>
+ {{lang_shibEntitlementsPretext}}
+</p>
+<div class="form-group">
+ <label for="entitlements">{{lang_shibEntitlements}}</label>
+ <input class="form-control" id="entitlements" name="entitlements" type="text" value="{{entitlements}}">
+</div>
+<div class="slx-space"></div>
+<hr>
+
+{{#list}}
+
+ <div id="frame_{{reghash}}" class="panel panel-default">
+ <div class="panel-heading">
+ <div class="panel-title">
+ <a class="collapsed" data-toggle="collapse" data-parent="#accordion" href="#panel_{{reghash}}">
+ &downarrow; {{registrar}}
+ </a>
+ </div>
+ </div>
+ <div id="panel_{{reghash}}" class="panel-collapse collapse">
+ <div class="panel-body">
+ <div>
+ <button type="button" class="btn btn-sm btn-default reg-group-toggle">
+ <span class="glyphicon glyphicon-check"></span>
+ {{lang_selectDeselectAll}}
+ </button>
+ </div>
+ <div class="reg-group">
+ {{#list}}
+ <div class="checkbox">
+ <input type="checkbox" name="idp[]" value="{{id}}" id="ch_{{idphash}}" {{checked}}>
+ <label for="ch_{{idphash}}">{{name}}</label>
+ </div>
+ {{/list}}
+ </div>
+ </div>
+ </div>
+ </div>
+
+{{/list}}
+
+<div class="pull-right">
+ <button type="submit" class="btn btn-primary">{{lang_next}} &raquo;</button>
+</div>
+<div class="clearfix"></div>
+</form>
+
+<script>
+ document.addEventListener('DOMContentLoaded', function() {
+ $('.reg-group-toggle').click(function() {
+ var $c = $(this);
+ var $checks = $c.closest('.panel-body').find('.reg-group .checkbox input');
+ var cc = $checks.filter('input:checked').length;
+ console.log($checks.length, cc);
+ $checks.prop('checked', cc < ($checks.length - cc));
+ });
+ });
+</script> \ No newline at end of file
diff --git a/modules-available/sysconfig/templates/shibauth-start.html b/modules-available/sysconfig/templates/shibauth-start.html
new file mode 100644
index 00000000..255f69f0
--- /dev/null
+++ b/modules-available/sysconfig/templates/shibauth-start.html
@@ -0,0 +1,53 @@
+<form role="form" method="post" action="?do=SysConfig&amp;action=addmodule&amp;step={{next}}">
+<input type="hidden" name="token" value="{{token}}">
+<input type="hidden" name="edit" value="{{edit}}">
+
+<div>{{lang_shibIntroText}}</div>
+<div class="slx-space"></div>
+
+<div class="input-group">
+ <span class="input-group-addon">{{lang_moduleTitle}}</span>
+ <input tabindex="1" name="title" value="{{title}}" type="text" class="form-control" autofocus required>
+</div>
+<div class="slx-space"></div>
+{{lang_shibEnabledMethods}}
+<div class="input-group">
+ <div class="checkbox">
+ <input type="checkbox" id="cbbrowser" name="browser" {{#browser}}checked{{/browser}}>
+ <label for="cbbrowser">
+ {{lang_shibUseBrowser}}
+ </label>
+ </div>
+</div>
+<div class="input-group">
+ <div class="checkbox">
+ <input type="checkbox" id="cbqrcode" name="qrcode" {{#qrcode}}checked{{/qrcode}}>
+ <label for="cbqrcode">
+ {{lang_shibUseQrCode}}
+ </label>
+ </div>
+</div>
+<div class="input-group">
+ <div class="checkbox">
+ <input type="checkbox" id="cbuserlogin" name="userlogin" {{#userlogin}}checked{{/userlogin}}>
+ <label for="cbuserlogin">
+ {{lang_shibUseUserLogin}}
+ </label>
+ </div>
+</div>
+
+<div class="slx-space"></div>
+
+<div id="idp-update"></div>
+
+<div class="pull-right">
+ <button type="submit" class="btn btn-primary">{{lang_next}} &raquo;</button>
+</div>
+<div class="clearfix"></div>
+</form>
+
+<script>
+ document.addEventListener('DOMContentLoaded', function() {
+ $('#idp-update').load('?do=SysConfig&action=addmodule&step=ShibAuth_Start');
+ });
+</script> \ No newline at end of file
diff --git a/modules-available/sysconfig/templates/sshconfig-start.html b/modules-available/sysconfig/templates/sshconfig-start.html
index 33108161..b56be415 100644
--- a/modules-available/sysconfig/templates/sshconfig-start.html
+++ b/modules-available/sysconfig/templates/sshconfig-start.html
@@ -3,35 +3,43 @@
<input type="hidden" name="edit" value="{{edit}}">
<div class="input-group">
<span class="input-group-addon">{{lang_moduleName}}</span>
- <input type="text" name="title" value="{{title}}" class="form-control" autofocus="autofocus">
+ <input type="text" name="title" value="{{title}}" class="form-control" autofocus="autofocus" required>
</div>
+ <br>
<div class="form-group">
- <div class="checkbox">
- <input type="checkbox" name="allowPasswordLogin" value="yes" {{#apl}}checked{{/apl}}>
- <label><b>{{lang_allowPass}}</b></label>
- </div>
+ <label>{{lang_sshAllowedUsers}}
+ <select class="form-control" name="allowedUsersLogin">
+ <option value="ROOT_ONLY" {{USR_ROOT_ONLY_selected}}>{{lang_user_root_only}}</option>
+ <option value="USER_ONLY" {{USR_USER_ONLY_selected}}>{{lang_user_user_only}}</option>
+ <option value="ALL" {{USR_ALL_selected}}>{{lang_user_all}}</option>
+ </select>
+ </label>
<div>
- <i>{{lang_allowPassInfo}}</i>
+ <i>{{lang_sshAllowedUsersInfo}}</i>
</div>
</div>
<div class="form-group">
- <label for="root-key">{{lang_rootKey}}</label>
- <input class="form-control" type="text" name="publicKey" value="{{publicKey}}" id="root-key" pattern="[a-z0-9\-]+ +[a-zA-Z0-9=/\+]+ +.*">
- <i>{{lang_rootKeyInfo}}</i>
+ <label>{{lang_sshAllowPass}}
+ <select class="form-control" name="allowPasswordLogin">
+ <option value="NO" {{PWD_NO_selected}}>{{lang_no}}</option>
+ <option value="USER_ONLY" {{PWD_USER_ONLY_selected}}>{{lang_pwlogin_user_only}}</option>
+ <option value="YES" {{PWD_YES_selected}}>{{lang_yes}}</option>
+ </select>
+ </label>
+ <div>
+ <i>{{lang_sshAllowPassInfo}}</i>
+ </div>
</div>
<div class="form-group">
<label for="port">{{lang_listenPort}}</label>
<input class="form-control" type="text" name="listenPort" value="{{listenPort}}" id="port" pattern="\d+" placeholder="22">
<i>{{lang_listenPortInfo}}</i>
</div>
- <p>
- <i>
- {{lang_sshMultipleHeadsup}}
- </i>
- </p>
+ <div class="btn-group">
+ <a class="btn btn-default" href="?do=SysConfig&action=addmodule">{{lang_back}}</a>
+ </div>
<div class="btn-group pull-right">
<button type="submit" class="btn btn-primary"><span class="glyphicon glyphicon-floppy-disk"></span> {{lang_save}}</button>
</div>
<div class="clearfix"></div>
</form>
-
diff --git a/modules-available/sysconfig/templates/sshkey-start.html b/modules-available/sysconfig/templates/sshkey-start.html
new file mode 100644
index 00000000..8033740c
--- /dev/null
+++ b/modules-available/sysconfig/templates/sshkey-start.html
@@ -0,0 +1,21 @@
+<form role="form" enctype="multipart/form-data" method="post" action="?do=SysConfig&amp;action=addmodule&amp;step={{step}}">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="edit" value="{{edit}}">
+ <div class="input-group">
+ <span class="input-group-addon">{{lang_moduleName}}</span>
+ <input type="text" name="title" value="{{title}}" class="form-control" autofocus="autofocus" required>
+ </div>
+ <div class="form-group">
+ <label for="root-key">{{lang_rootKey}}</label>
+ <input class="form-control" type="text" name="publicKey" value="{{publicKey}}" id="root-key" required pattern="[a-z0-9\-]+ +[a-zA-Z0-9=/\+]+ +.*">
+ <i>{{lang_rootKeyInfo}}</i>
+ </div>
+ <div class="btn-group">
+ <a class="btn btn-default" href="?do=SysConfig&action=addmodule">{{lang_back}}</a>
+ </div>
+ <div class="btn-group pull-right">
+ <button type="submit" class="btn btn-primary"><span class="glyphicon glyphicon-floppy-disk"></span> {{lang_save}}</button>
+ </div>
+ <div class="clearfix"></div>
+</form>
+