summaryrefslogtreecommitdiffstats
path: root/modules-available/sysconfig
diff options
context:
space:
mode:
authorSimon Rettberg2016-05-11 19:00:30 +0200
committerSimon Rettberg2016-05-11 19:00:30 +0200
commit1cc1c2ed092c46eb35893c1d85accb24cf43d7f9 (patch)
tree95c1302f4a1ae441e174a1dca64133e2873f8297 /modules-available/sysconfig
parentAdd PhpStorm prefs (diff)
downloadslx-admin-1cc1c2ed092c46eb35893c1d85accb24cf43d7f9.tar.gz
slx-admin-1cc1c2ed092c46eb35893c1d85accb24cf43d7f9.tar.xz
slx-admin-1cc1c2ed092c46eb35893c1d85accb24cf43d7f9.zip
Still working in modularization cleanup and refinement
Diffstat (limited to 'modules-available/sysconfig')
-rw-r--r--modules-available/sysconfig/addmodule.inc.php3
-rw-r--r--modules-available/sysconfig/addmodule_adauth.inc.php12
-rw-r--r--modules-available/sysconfig/inc/configmodule.inc.php515
-rw-r--r--modules-available/sysconfig/inc/configmodule/adauth.inc.php75
-rw-r--r--modules-available/sysconfig/inc/configmodule/branding.inc.php56
-rw-r--r--modules-available/sysconfig/inc/configmodule/customodule.inc.php56
-rw-r--r--modules-available/sysconfig/inc/configmodule/ldapauth.inc.php77
-rw-r--r--modules-available/sysconfig/inc/configmodule/sshconfig.inc.php63
-rw-r--r--modules-available/sysconfig/inc/configtgz.inc.php317
-rw-r--r--modules-available/sysconfig/lang/de/config-module.json16
-rw-r--r--modules-available/sysconfig/lang/en/config-module.json16
-rw-r--r--modules-available/sysconfig/lang/pt/config-module.json3
-rw-r--r--modules-available/sysconfig/page.inc.php12
13 files changed, 1208 insertions, 13 deletions
diff --git a/modules-available/sysconfig/addmodule.inc.php b/modules-available/sysconfig/addmodule.inc.php
index 913a37c1..30ca1d2c 100644
--- a/modules-available/sysconfig/addmodule.inc.php
+++ b/modules-available/sysconfig/addmodule.inc.php
@@ -100,8 +100,9 @@ abstract class AddModule_Base
if (self::$instance === false) {
Util::traceError('No step instance yet');
}
- if (self::$instance->edit !== false)
+ if (self::$instance->edit !== false) {
Message::addInfo('replacing-module', self::$instance->edit->title());
+ }
self::$instance->renderInternal();
}
diff --git a/modules-available/sysconfig/addmodule_adauth.inc.php b/modules-available/sysconfig/addmodule_adauth.inc.php
index cb7bfb4e..1e76f108 100644
--- a/modules-available/sysconfig/addmodule_adauth.inc.php
+++ b/modules-available/sysconfig/addmodule_adauth.inc.php
@@ -25,7 +25,7 @@ class AdAuth_Start extends AddModule_Base
$data['server'] = $out[1];
}
$data['step'] = 'AdAuth_CheckConnection';
- Render::addDialog(Dictionary::translate('config-module', 'adAuth_title'), false, 'ad-start', $data);
+ Render::addDialog(Dictionary::translateFile('config-module', 'adAuth_title'), false, 'ad-start', $data);
}
}
@@ -88,7 +88,7 @@ class AdAuth_CheckConnection extends AddModule_Base
} else {
$data['next'] = 'AdAuth_CheckCredentials';
}
- Render::addDialog(Dictionary::translate('config-module', 'adAuth_title'), false, 'ad_ldap-checkconnection', $data);
+ Render::addDialog(Dictionary::translateFile('config-module', 'adAuth_title'), false, 'ad_ldap-checkconnection', $data);
}
}
@@ -165,7 +165,7 @@ class AdAuth_SelfSearch extends AddModule_Base
} else {
$data['next'] = 'AdAuth_CheckCredentials';
}
- Render::addDialog(Dictionary::translate('config-module', 'adAuth_title'), false, 'ad-selfsearch',
+ Render::addDialog(Dictionary::translateFile('config-module', 'adAuth_title'), false, 'ad-selfsearch',
array_merge($this->taskIds, $data));
}
@@ -218,7 +218,7 @@ class AdAuth_HomeAttrCheck extends AddModule_Base
protected function renderInternal()
{
- Render::addDialog(Dictionary::translate('config-module', 'adAuth_title'), false, 'ad-selfsearch', array_merge($this->taskIds, array(
+ Render::addDialog(Dictionary::translateFile('config-module', 'adAuth_title'), false, 'ad-selfsearch', array_merge($this->taskIds, array(
'edit' => Request::post('edit'),
'title' => Request::post('title'),
'server' => Request::post('server'),
@@ -289,7 +289,7 @@ class AdAuth_CheckCredentials extends AddModule_Base
protected function renderInternal()
{
- Render::addDialog(Dictionary::translate('config-module', 'adAuth_title'), false, 'ad_ldap-checkcredentials', array_merge($this->taskIds, array(
+ Render::addDialog(Dictionary::translateFile('config-module', 'adAuth_title'), false, 'ad_ldap-checkcredentials', array_merge($this->taskIds, array(
'edit' => Request::post('edit'),
'title' => Request::post('title'),
'server' => Request::post('server') . ':' . Request::post('port'),
@@ -407,7 +407,7 @@ class AdAuth_Finish extends AddModule_Base
protected function renderInternal()
{
- Render::addDialog(Dictionary::translate('config-module', 'adAuth_title'), false, 'ad-finish', $this->taskIds);
+ Render::addDialog(Dictionary::translateFile('config-module', 'adAuth_title'), false, 'ad-finish', $this->taskIds);
}
}
diff --git a/modules-available/sysconfig/inc/configmodule.inc.php b/modules-available/sysconfig/inc/configmodule.inc.php
new file mode 100644
index 00000000..7b92ff89
--- /dev/null
+++ b/modules-available/sysconfig/inc/configmodule.inc.php
@@ -0,0 +1,515 @@
+<?php
+
+/**
+ * Base class for config modules
+ */
+abstract class ConfigModule
+{
+
+ /**
+ * @var array list of known module types
+ */
+ private static $moduleTypes = false;
+
+ private $moduleId = 0;
+ private $moduleArchive = false;
+ private $moduleTitle = false;
+ private $currentVersion = 0;
+ protected $moduleData = false;
+
+ /**
+ * Load all known config module types. This is done
+ * by including *.inc.php from inc/configmodule/. The
+ * files there should in turn call ConfigModule::registerModule()
+ * to register themselves.
+ */
+ public static function loadDb()
+ {
+ if (self::$moduleTypes !== false)
+ return;
+ self::$moduleTypes = array();
+ foreach (glob(dirname(__FILE__) . '/configmodule/*.inc.php', GLOB_NOSORT) as $file) {
+ require_once $file;
+ }
+ }
+
+ /**
+ * Get all known config module types.
+ *
+ * @return array list of modules
+ */
+ public static function getList()
+ {
+ self::loadDb();
+ return self::$moduleTypes;
+ }
+
+ /**
+ * Add a known configuration module. Every inc/configmodule/*.inc.php should call this.
+ *
+ * @param string $id Identifier for the module.
+ * The module class must be called ConfigModule_{$id}, the wizard start class {$id}_Start.
+ * The wizard's classes should be located in modules/sysconfig/addmodule_{$id_lowercase}.inc.php
+ * @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 registerModule($id, $title, $description, $group, $unique, $sortOrder = 0)
+ {
+ if (isset(self::$moduleTypes[$id]))
+ Util::traceError("Config Module $id already registered!");
+ $moduleClass = 'ConfigModule_' . $id;
+ $wizardClass = $id . '_Start';
+ if (!class_exists($moduleClass))
+ Util::traceError("Class $moduleClass does not exist!");
+ if (get_parent_class($moduleClass) !== 'ConfigModule')
+ Util::traceError("$moduleClass does not have ConfigModule as its parent!");
+ self::$moduleTypes[$id] = array(
+ 'title' => $title,
+ 'description' => $description,
+ 'group' => $group,
+ 'unique' => $unique,
+ 'sortOrder' => $sortOrder,
+ 'moduleClass' => $moduleClass,
+ 'wizardClass' => $wizardClass
+ );
+ }
+
+ /**
+ * Get fresh instance of ConfigModule subclass for given module type.
+ *
+ * @param string $moduleType name of module type
+ * @return \ConfigModule module instance
+ */
+ public static function getInstance($moduleType)
+ {
+ self::loadDb();
+ if (!isset(self::$moduleTypes[$moduleType]))
+ return false;
+ return new self::$moduleTypes[$moduleType]['moduleClass'];
+ }
+
+ /**
+ * Get module instance from id.
+ *
+ * @param int $moduleId module id to get
+ * @return ConfigModule The requested module from DB, or false on error
+ */
+ public static function get($moduleId)
+ {
+ $ret = Database::queryFirst("SELECT title, moduletype, filepath, contents, version FROM configtgz_module "
+ . " WHERE moduleid = :moduleid LIMIT 1", array('moduleid' => $moduleId));
+ if ($ret === false)
+ return false;
+ $instance = self::getInstance($ret['moduletype']);
+ if ($instance === false)
+ return false;
+ $instance->currentVersion = $ret['version'];
+ $instance->moduleArchive = $ret['filepath'];
+ $instance->moduleData = json_decode($ret['contents'], true);
+ $instance->moduleId = $moduleId;
+ $instance->moduleTitle = $ret['title'];
+ return $instance;
+ }
+
+ /**
+ * Get module instances from module type.
+ *
+ * @param int $moduleType module type to get
+ * @return array The requested modules from DB, or false on error
+ */
+ public static function getAll($moduleType = false)
+ {
+ if ($moduleType === false) {
+ $ret = Database::simpleQuery("SELECT moduleid, title, moduletype, filepath, contents, version FROM configtgz_module");
+ } else {
+ $ret = Database::simpleQuery("SELECT moduleid, title, moduletype, filepath, contents, version FROM configtgz_module "
+ . " WHERE moduletype = :moduletype", array('moduletype' => $moduleType));
+ }
+ if ($ret === false)
+ return false;
+ $list = array();
+ while ($row = $ret->fetch(PDO::FETCH_ASSOC)) {
+ $instance = self::getInstance($row['moduletype']);
+ if ($instance === false)
+ return false;
+ $instance->currentVersion = $row['version'];
+ $instance->moduleArchive = $row['filepath'];
+ $instance->moduleData = json_decode($row['contents'], true);
+ $instance->moduleId = $row['moduleid'];
+ $instance->moduleTitle = $row['title'];
+ $list[] = $instance;
+ }
+ return $list;
+ }
+
+ /**
+ * Get the module version.
+ *
+ * @return int module version
+ */
+ protected abstract function moduleVersion();
+
+ /**
+ * Validate the module's configuration.
+ *
+ * @return boolean ok or not
+ */
+ protected abstract function validateConfig();
+
+ /**
+ * 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)
+ */
+ public abstract function setData($key, $value);
+
+ /**
+ * 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
+ * @return mixed Module specific data
+ */
+ public function getData($key)
+ {
+ if ($key === false)
+ return $this->moduleData;
+ if (!is_array($this->moduleData) || !isset($this->moduleData[$key]))
+ return false;
+ return $this->moduleData[$key];
+ }
+
+ /**
+ * 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
+ * @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);
+
+ private final function createFileName()
+ {
+ return CONFIG_TGZ_LIST_DIR . '/modules/'
+ . $this->moduleType() . '_id-' . $this->moduleId . '__' . mt_rand() . '-' . time() . '.tgz';
+ }
+
+ /**
+ * Get module id (in db)
+ *
+ * @return int id
+ */
+ public final function id()
+ {
+ return $this->moduleId;
+ }
+
+ /**
+ * Get module title.
+ *
+ * @return string
+ */
+ public final function title()
+ {
+ return $this->moduleTitle;
+ }
+
+ /**
+ * Get module archive file name.
+ *
+ * @return string tgz file absolute path
+ */
+ public final function archive()
+ {
+ return $this->moduleArchive;
+ }
+
+ /**
+ * Get the module type.
+ *
+ * @return string module type
+ */
+ public final function moduleType()
+ {
+ $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 . '"');
+ return $out[1];
+ }
+
+ /**
+ * Insert this config module into DB. Only
+ * valid if the object was created using the creating constructor,
+ * not if the instance was created using a database entry (static get method).
+ *
+ * @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)
+ {
+ if ($this->moduleId !== 0)
+ Util::traceError('ConfigModule::insert called when moduleId != 0');
+ if (!$this->validateConfig())
+ return false;
+ $this->moduleTitle = $title;
+ // Insert
+ Database::exec("INSERT INTO configtgz_module (title, moduletype, filepath, contents, version, status) "
+ . " VALUES (:title, :type, '', :contents, :version, :status)", array(
+ 'title' => $title,
+ 'type' => $this->moduleType(),
+ 'contents' => json_encode($this->moduleData),
+ 'version' => 0,
+ 'status' => 'MISSING'
+ ));
+ $this->moduleId = Database::lastInsertId();
+ if (!is_numeric($this->moduleId))
+ Util::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,
+ 'moduleid' => $this->moduleId
+ ));
+ return true;
+ }
+
+ /**
+ * Update the given module in database. This will not regenerate
+ * the module's tgz.
+ *
+ * @return boolean true on success, false otherwise
+ */
+ public final function update($title)
+ {
+ if ($this->moduleId === 0)
+ Util::traceError('ConfigModule::update called when moduleId == 0');
+ if (empty($title))
+ $title = $this->moduleTitle;
+ if (!$this->validateConfig())
+ return false;
+ // Update
+ Database::exec("UPDATE configtgz_module SET title = :title, contents = :contents, status = :status "
+ . " WHERE moduleid = :moduleid LIMIT 1", array(
+ 'moduleid' => $this->moduleId,
+ 'title' => $title,
+ 'contents' => json_encode($this->moduleData),
+ 'status' => 'OUTDATED'
+ ));
+ return true;
+ }
+
+ /**
+ * Generate the module's tgz, don't wait for completion.
+ * 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 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)
+ {
+ if ($this->moduleId === 0 || $this->moduleTitle === false)
+ Util::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
+ if ($timeoutMs > 0 && isset($ret['id']) && !Taskmanager::isFinished($ret))
+ $ret = Taskmanager::waitComplete($ret, $timeoutMs);
+ if ($ret === true || (isset($ret['statusCode']) && $ret['statusCode'] === TASK_FINISHED)) {
+ // Already Finished
+ if (file_exists($this->moduleArchive) && !file_exists($tmpTgz))
+ $tmpTgz = false; // If generateInternal succeeded and there's no tmpTgz, it means the file didn't have to be updated
+ return $this->markUpdated($tmpTgz);
+ }
+ if (!is_array($ret) || !isset($ret['id']) || Taskmanager::isFailed($ret)) {
+ if (is_array($ret)) // Failed
+ Taskmanager::addErrorMessage($ret);
+ if ($deleteOnError)
+ $this->delete();
+ else
+ $this->markFailed();
+ return false;
+ }
+ // Still running, add callback
+ TaskmanagerCallback::addCallback($ret, 'cbConfModCreated', array(
+ 'moduleid' => $this->moduleId,
+ 'deleteOnError' => $deleteOnError,
+ 'tmpTgz' => $tmpTgz
+ ));
+ return $ret['id'];
+ }
+
+ /**
+ * Delete the module.
+ */
+ public final function delete()
+ {
+ if ($this->moduleId === 0)
+ Util::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;
+ if ($ret !== false) {
+ if ($this->moduleArchive)
+ Taskmanager::submit('DeleteFile', array('file' => $this->moduleArchive), true);
+ $this->moduleId = 0;
+ $this->moduleData = false;
+ $this->moduleTitle = false;
+ $this->moduleArchive = false;
+ }
+ return $ret;
+ }
+
+ private final function markUpdated($tmpTgz)
+ {
+ if ($this->moduleId === 0)
+ Util::traceError('ConfigModule::markUpdated called with invalid module id!');
+ if ($this->moduleArchive === false)
+ $this->moduleArchive = $this->createFileName();
+ // Move file
+ if ($tmpTgz === false) {
+ if (!file_exists($this->moduleArchive)) {
+ EventLog::failure('ConfigModule::markUpdated for "' . $this->moduleTitle . '" called with no tmpTgz and no existing tgz!');
+ $this->markFailed();
+ return false;
+ }
+ } else {
+ $task = Taskmanager::submit('MoveFile', array(
+ 'source' => $tmpTgz,
+ 'destination' => $this->moduleArchive
+ ));
+ $task = Taskmanager::waitComplete($task, 5000);
+ if (Taskmanager::isFailed($task) || !Taskmanager::isFinished($task)) {
+ if (!API && !AJAX)
+ Taskmanager::addErrorMessage($task);
+ else
+ EventLog::failure('Could not move ' . $tmpTgz . ' to ' . $this->moduleArchive . ' while generating "' . $this->moduleTitle . '"');
+ $this->markFailed();
+ return false;
+ }
+ }
+ // Update DB entry
+ $retval = Database::exec("UPDATE configtgz_module SET filepath = :filename, version = :version, status = 'OK' WHERE moduleid = :id LIMIT 1", array(
+ 'id' => $this->moduleId,
+ 'filename' => $this->moduleArchive,
+ 'version' => $this->moduleVersion()
+ )) !== false;
+ // Update related config.tgzs
+ $configs = ConfigTgz::getAllForModule($this->moduleId);
+ foreach ($configs as $config) {
+ $config->markOutdated();
+ $config->generate();
+ }
+ return $retval;
+ }
+
+ private final function markFailed()
+ {
+ if ($this->moduleId === 0)
+ Util::traceError('ConfigModule::markFailed called with invalid module id!');
+ if ($this->moduleArchive === false)
+ $this->moduleArchive = $this->createFileName();
+ if (!file_exists($this->moduleArchive))
+ $status = 'MISSING';
+ else
+ $status = 'OUTDATED';
+ return 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;
+ }
+
+ ################# Callbacks ##############
+
+ /**
+ * Event callback for when the server ip changed.
+ * Override this if you need to handle this, otherwise
+ * the base implementation does nothing.
+ */
+ public function event_serverIpChanged()
+ {
+ // Do::Nothing()
+ }
+
+ ##################### STATIC CALLBACKS #####################
+
+ /**
+ * 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()
+ {
+ self::loadDb();
+ $list = self::getAll();
+ foreach ($list as $mod) {
+ $mod->event_serverIpChanged();
+ }
+ }
+
+ /**
+ * 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'
+ */
+ public static function generateFailed($task, $args)
+ {
+ 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) {
+ 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 = '';
+ EventLog::failure("Generating module '" . $module->moduleTitle . "' failed.", $error);
+ if ($args['deleteOnError'])
+ $module->delete();
+ else
+ $module->markFailed();
+ }
+
+ /**
+ * (Re)generating a config module succeeded. Update db entry.
+ *
+ * @param array $args contains 'moduleid' and optionally 'deleteOnError' and 'tmpTgz'
+ */
+ public static function generateSucceeded($args)
+ {
+ 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) {
+ 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);
+ }
+
+}
diff --git a/modules-available/sysconfig/inc/configmodule/adauth.inc.php b/modules-available/sysconfig/inc/configmodule/adauth.inc.php
new file mode 100644
index 00000000..a03be43c
--- /dev/null
+++ b/modules-available/sysconfig/inc/configmodule/adauth.inc.php
@@ -0,0 +1,75 @@
+<?php
+
+ConfigModule::registerModule(
+ ConfigModule_AdAuth::MODID, // ID
+ Dictionary::translateFile('config-module', 'adAuth_title'), // Title
+ Dictionary::translateFile('config-module', 'adAuth_description'), // Description
+ Dictionary::translateFile('config-module', 'group_authentication'), // Group
+ true // Only one per config?
+);
+
+class ConfigModule_AdAuth extends ConfigModule
+{
+
+ const MODID = 'AdAuth';
+ const VERSION = 1;
+
+ private static $REQUIRED_FIELDS = array('server', 'searchbase', 'binddn');
+ private static $OPTIONAL_FIELDS = array('bindpw', 'home', 'ssl', 'fingerprint', 'certificate', 'homeattr');
+
+ protected function generateInternal($tgz, $parent)
+ {
+ Trigger::ldadp($this->id(), $parent);
+ $config = $this->moduleData;
+ if (isset($config['certificate']) && !is_string($config['certificate'])) {
+ unset($config['certificate']);
+ }
+ if (preg_match('/^([^\:]+)\:(\d+)$/', $config['server'], $out)) {
+ $config['server'] = $out[1];
+ $config['adport'] = $out[2];
+ } else {
+ if (isset($config['certificate'])) {
+ $config['adport'] = 636;
+ } else {
+ $config['adport'] = 389;
+ }
+ }
+ $config['parentTask'] = $parent;
+ $config['failOnParentFail'] = false;
+ $config['proxyip'] = Property::getServerIp();
+ $config['proxyport'] = 3100 + $this->id();
+ $config['filename'] = $tgz;
+ $config['moduleid'] = $this->id();
+ return Taskmanager::submit('CreateLdapConfig', $config);
+ }
+
+ protected function moduleVersion()
+ {
+ return self::VERSION;
+ }
+
+ protected function validateConfig()
+ {
+ // Check if required fields are filled
+ return Util::hasAllKeys($this->moduleData, self::$REQUIRED_FIELDS);
+ }
+
+ public function setData($key, $value)
+ {
+ if (!in_array($key, self::$REQUIRED_FIELDS) && !in_array($key, self::$OPTIONAL_FIELDS))
+ return false;
+ $this->moduleData[$key] = $value;
+ return true;
+ }
+
+ // ############## Callbacks #############################
+
+ /**
+ * Server IP changed - rebuild all AD modules.
+ */
+ public function event_serverIpChanged()
+ {
+ $this->generate(false);
+ }
+
+}
diff --git a/modules-available/sysconfig/inc/configmodule/branding.inc.php b/modules-available/sysconfig/inc/configmodule/branding.inc.php
new file mode 100644
index 00000000..479b406c
--- /dev/null
+++ b/modules-available/sysconfig/inc/configmodule/branding.inc.php
@@ -0,0 +1,56 @@
+<?php
+
+ConfigModule::registerModule(
+ ConfigModule_Branding::MODID, // ID
+ 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?
+);
+
+class ConfigModule_Branding extends ConfigModule
+{
+
+ const MODID = 'Branding';
+ const VERSION = 1;
+
+ private $tmpFile = false;
+
+ protected function generateInternal($tgz, $parent)
+ {
+ if (!$this->validateConfig()) {
+ return $this->archive() !== false && file_exists($this->archive()); // No new temp file given, old archive still exists, pretend it worked...
+ }
+ $task = Taskmanager::submit('MoveFile', array(
+ 'source' => $this->tmpFile,
+ 'destination' => $tgz,
+ 'parentTask' => $parent,
+ 'failOnParentFail' => false
+ ));
+ return $task;
+ }
+
+ protected function moduleVersion()
+ {
+ return self::VERSION;
+ }
+
+ protected function validateConfig()
+ {
+ return $this->tmpFile !== false && file_exists($this->tmpFile);
+ }
+
+ public function setData($key, $value)
+ {
+ if ($key !== 'tmpFile' || !is_string($value) || !file_exists($value))
+ return false;
+ $this->tmpFile = $value;
+ return true;
+ }
+
+ public function getData($key)
+ {
+ return false;
+ }
+
+}
diff --git a/modules-available/sysconfig/inc/configmodule/customodule.inc.php b/modules-available/sysconfig/inc/configmodule/customodule.inc.php
new file mode 100644
index 00000000..09b621cc
--- /dev/null
+++ b/modules-available/sysconfig/inc/configmodule/customodule.inc.php
@@ -0,0 +1,56 @@
+<?php
+
+ConfigModule::registerModule(
+ ConfigModule_CustomModule::MODID, // ID
+ 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
+);
+
+class ConfigModule_CustomModule extends ConfigModule
+{
+ const MODID = 'CustomModule';
+ const VERSION = 1;
+
+ private $tmpFile = false;
+
+ protected function generateInternal($tgz, $parent)
+ {
+ if (!$this->validateConfig()) {
+ return $this->archive() !== false && file_exists($this->archive()); // No new temp file given, old archive still exists, pretend it worked...
+ }
+ $task = Taskmanager::submit('MoveFile', array(
+ 'source' => $this->tmpFile,
+ 'destination' => $tgz,
+ 'parentTask' => $parent,
+ 'failOnParentFail' => false
+ ));
+ return $task;
+ }
+
+ protected function moduleVersion()
+ {
+ return self::VERSION;
+ }
+
+ protected function validateConfig()
+ {
+ return $this->tmpFile !== false && file_exists($this->tmpFile);
+ }
+
+ public function setData($key, $value)
+ {
+ if ($key !== 'tmpFile' || !file_exists($value))
+ return false;
+ $this->tmpFile = $value;
+ return true;
+ }
+
+ public function getData($key)
+ {
+ return false;
+ }
+
+}
diff --git a/modules-available/sysconfig/inc/configmodule/ldapauth.inc.php b/modules-available/sysconfig/inc/configmodule/ldapauth.inc.php
new file mode 100644
index 00000000..0f386033
--- /dev/null
+++ b/modules-available/sysconfig/inc/configmodule/ldapauth.inc.php
@@ -0,0 +1,77 @@
+<?php
+
+ConfigModule::registerModule(
+ ConfigModule_LdapAuth::MODID, // ID
+ Dictionary::translateFile('config-module', 'ldapAuth_title'), // Title
+ Dictionary::translateFile('config-module', 'ldapAuth_description'), // Description
+ Dictionary::translateFile('config-module', 'group_authentication'), // Group
+ true // Only one per config?
+);
+
+class ConfigModule_LdapAuth extends ConfigModule
+{
+
+ const MODID = 'LdapAuth';
+ const VERSION = 1;
+
+ private static $REQUIRED_FIELDS = array('server', 'searchbase');
+ private static $OPTIONAL_FIELDS = array('binddn', 'bindpw', 'home', 'ssl', 'fingerprint', 'certificate');
+
+ protected function generateInternal($tgz, $parent)
+ {
+ Trigger::ldadp($this->id(), $parent);
+ $config = $this->moduleData;
+ if (isset($config['certificate']) && !is_string($config['certificate'])) {
+ unset($config['certificate']);
+ }
+ if (preg_match('/^([^\:]+)\:(\d+)$/', $config['server'], $out)) {
+ $config['server'] = $out[1];
+ $config['adport'] = $out[2]; // sic!
+ } else {
+ if (isset($config['certificate'])) {
+ $config['adport'] = 636;
+ } else {
+ $config['adport'] = 389;
+ }
+ }
+ $config['parentTask'] = $parent;
+ $config['failOnParentFail'] = false;
+ $config['proxyip'] = Property::getServerIp();
+ $config['proxyport'] = 3100 + $this->id();
+ $config['filename'] = $tgz;
+ $config['moduleid'] = $this->id();
+ $config['plainldap'] = true;
+ return Taskmanager::submit('CreateLdapConfig', $config);
+ }
+
+ protected function moduleVersion()
+ {
+ return self::VERSION;
+ }
+
+ protected function validateConfig()
+ {
+ // Check if required fields are filled
+ return Util::hasAllKeys($this->moduleData, self::$REQUIRED_FIELDS);
+ }
+
+ public function setData($key, $value)
+ {
+ if (!in_array($key, self::$REQUIRED_FIELDS) && !in_array($key, self::$OPTIONAL_FIELDS))
+ return false;
+ $this->moduleData[$key] = $value;
+ return true;
+ }
+
+ // ############## Callbacks #############################
+
+ /**
+ * Server IP changed - rebuild all LDAP modules.
+ */
+ public function event_serverIpChanged()
+ {
+ error_log('Calling generate on ' . $this->title());
+ $this->generate(false);
+ }
+
+}
diff --git a/modules-available/sysconfig/inc/configmodule/sshconfig.inc.php b/modules-available/sysconfig/inc/configmodule/sshconfig.inc.php
new file mode 100644
index 00000000..b1d58153
--- /dev/null
+++ b/modules-available/sysconfig/inc/configmodule/sshconfig.inc.php
@@ -0,0 +1,63 @@
+<?php
+
+ConfigModule::registerModule(
+ ConfigModule_SshConfig::MODID, // ID
+ 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?
+);
+
+class ConfigModule_SshConfig extends ConfigModule
+{
+ const MODID = 'SshConfig';
+ const VERSION = 1;
+
+ protected function generateInternal($tgz, $parent)
+ {
+ if (!$this->validateConfig())
+ return false;
+ $config = $this->moduleData + array(
+ 'filename' => $tgz,
+ 'failOnParentFail' => false,
+ 'parent' => $parent
+ );
+ // Create config module, which will also check if the pubkey is valid
+ return Taskmanager::submit('SshdConfigGenerator', $config);
+ }
+
+ protected function moduleVersion()
+ {
+ return self::VERSION;
+ }
+
+ protected function validateConfig()
+ {
+ return isset($this->moduleData['publicKey']) && isset($this->moduleData['allowPasswordLogin']) && isset($this->moduleData['listenPort']);
+ }
+
+ public function setData($key, $value)
+ {
+ switch ($key) {
+ case 'publicKey':
+ break;
+ case 'allowPasswordLogin':
+ if ($value === true || $value === 'yes')
+ $value = 'yes';
+ elseif ($value === false || $value === 'no')
+ $value = 'no';
+ else
+ return false;
+ break;
+ case 'listenPort':
+ if (!is_numeric($value) || $value < 1 || $value > 65535)
+ return false;
+ break;
+ default:
+ return false;
+ }
+ $this->moduleData[$key] = $value;
+ return true;
+ }
+
+}
diff --git a/modules-available/sysconfig/inc/configtgz.inc.php b/modules-available/sysconfig/inc/configtgz.inc.php
new file mode 100644
index 00000000..b51d2787
--- /dev/null
+++ b/modules-available/sysconfig/inc/configtgz.inc.php
@@ -0,0 +1,317 @@
+<?php
+
+class ConfigTgz
+{
+
+ private $configId = 0;
+ private $configTitle = false;
+ private $file = false;
+ private $modules = array();
+
+ private function __construct()
+ {
+ ;
+ }
+
+ public function id()
+ {
+ return $this->configId;
+ }
+
+ public function title()
+ {
+ return $this->configTitle;
+ }
+
+ public function areAllModulesUpToDate()
+ {
+ if (!$this->configId > 0)
+ Util::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')
+ return false;
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public function isActive()
+ {
+ return readlink(CONFIG_HTTP_DIR . '/default/config.tgz') === $this->file;
+ }
+
+ public function getModuleIds()
+ {
+ $ret = array();
+ foreach ($this->modules as $module) {
+ $ret[] = $module['moduleid'];
+ }
+ return $ret;
+ }
+
+ public function update($title, $moduleIds)
+ {
+ if (!is_array($moduleIds))
+ return false;
+ $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
+ foreach ($moduleIds as $module) {
+ $idstr .= ',' . (int)$module; // Casting to int should make it safe
+ }
+ $res = Database::simpleQuery("SELECT moduleid, filepath, status FROM configtgz_module WHERE moduleid IN ($idstr)");
+ // 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)) {
+ Database::exec("INSERT INTO configtgz_x_module (configid, moduleid) VALUES (:configid, :moduleid)", array(
+ 'configid' => $this->configId,
+ 'moduleid' => $row['moduleid']
+ ));
+ $this->modules[] = $row;
+ }
+ // Update name
+ Database::exec("UPDATE configtgz SET title = :title, status = :status WHERE configid = :configid LIMIT 1", array(
+ 'configid' => $this->configId,
+ 'title' => $title,
+ 'status' => 'OUTDATED'
+ ));
+ return true;
+ }
+
+ public static function insert($title, $moduleIds)
+ {
+ if (!is_array($moduleIds))
+ return false;
+ $instance = new ConfigTgz;
+ $instance->configTitle = $title;
+ // Create output file name (config.tgz)
+ do {
+ $instance->file = CONFIG_TGZ_LIST_DIR . '/config-' . Util::sanitizeFilename($instance->configTitle) . '-' . mt_rand() . '-' . time() . '.tgz';
+ } while (file_exists($instance->file));
+ Database::exec("INSERT INTO configtgz (title, filepath, status) VALUES (:title, :filepath, :status)", array(
+ 'title' => $instance->configTitle,
+ 'filepath' => $instance->file,
+ 'status' => 'MISSING'
+ ));
+ $instance->configId = Database::lastInsertId();
+ $instance->modules = array();
+ // Get all modules to put in config
+ $idstr = '0'; // Passed directly in query. Make sure no SQL injection is possible
+ foreach ($moduleIds as $module) {
+ $idstr .= ',' . (int)$module; // Casting to int should make it safe
+ }
+ $res = Database::simpleQuery("SELECT moduleid, filepath, status FROM configtgz_module WHERE moduleid IN ($idstr)");
+ // Make connection
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ Database::exec("INSERT INTO configtgz_x_module (configid, moduleid) VALUES (:configid, :moduleid)", array(
+ 'configid' => $instance->configId,
+ 'moduleid' => $row['moduleid']
+ ));
+ $instance->modules[] = $row;
+ }
+ return $instance;
+ }
+
+ public static function get($configId)
+ {
+ $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, 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;
+ }
+ return $instance;
+ }
+
+ public static function getAllForModule($moduleId)
+ {
+ $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;
+ $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, 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;
+ }
+ $list[] = $instance;
+ }
+ return $list;
+ }
+
+ /**
+ * 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)
+ {
+ 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) {
+ EventLog::warning('generateFailed callback for config id ' . $args['configid'] . ', 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 = '';
+ EventLog::failure("Generating config.tgz '" . $config->configTitle . "' failed.", $error);
+ if ($args['deleteOnError'])
+ $config->delete();
+ else
+ $config->markFailed();
+ }
+
+ /**
+ * (Re)generating a config tgz succeeded. Update db entry.
+ *
+ * @param array $args contains 'configid' and optionally 'deleteOnError'
+ */
+ public static function generateSucceeded($args)
+ {
+ 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) {
+ EventLog::warning('generateSucceeded callback for config id ' . $args['configid'] . ', but no instance could be generated.');
+ return;
+ }
+ $config->markUpdated();
+ }
+
+ /**
+ *
+ * @param type $deleteOnError
+ * @param type $timeoutMs
+ * @return string - OK (success)
+ * - OUTDATED (updating failed, but old version still exists)
+ * - MISSING (failed and no old version available)
+ */
+ public function generate($deleteOnError = false, $timeoutMs = 0)
+ {
+ if (!($this->configId > 0) || !is_array($this->modules) || $this->file === false)
+ Util::traceError ('configId <= 0 or modules not array in ConfigTgz::rebuild()');
+ $files = array();
+ foreach ($this->modules as $module) {
+ if (!empty($module['filepath']) && file_exists($module['filepath']))
+ $files[] = $module['filepath'];
+ }
+ // Hand over to tm
+ $task = Taskmanager::submit('RecompressArchive', array(
+ 'inputFiles' => $files,
+ 'outputFile' => $this->file
+ ));
+ // Wait for completion
+ if ($timeoutMs > 0 && !Taskmanager::isFailed($task) && !Taskmanager::isFinished($task))
+ $task = Taskmanager::waitComplete($task, $timeoutMs);
+ if ($task === true || (isset($task['statusCode']) && $task['statusCode'] === TASK_FINISHED)) {
+ // Success!
+ $this->markUpdated();
+ return true;
+ }
+ if (!is_array($task) || !isset($task['id']) || Taskmanager::isFailed($task)) {
+ // Failed...
+ Taskmanager::addErrorMessage($task);
+ if (!$deleteOnError)
+ $this->markFailed();
+ else
+ $this->delete();
+ return false;
+ }
+ // Still running, add callback
+ TaskmanagerCallback::addCallback($task, 'cbConfTgzCreated', array(
+ 'configid' => $this->configId,
+ 'deleteOnError' => $deleteOnError
+ ));
+ return $task['id'];
+ }
+
+ public function delete()
+ {
+ 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;
+ if ($ret !== false) {
+ if ($this->file !== false)
+ Taskmanager::submit('DeleteFile', array('file' => $this->file), true);
+ $this->configId = 0;
+ $this->modules = false;
+ $this->file = false;
+ }
+ return $ret;
+ }
+
+ public function markOutdated()
+ {
+ if ($this->configId === 0)
+ Util::traceError('ConfigTgz::markOutdated called with invalid config id!');
+ return $this->mark('OUTDATED');
+ }
+
+ private function markUpdated()
+ {
+ if ($this->configId === 0)
+ Util::traceError('ConfigTgz::markUpdated called with invalid config id!');
+ Event::activeConfigChanged();
+ if ($this->areAllModulesUpToDate())
+ return $this->mark('OK');
+ return $this->mark('OUTDATED');
+ }
+
+ private function markFailed()
+ {
+ 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');
+ }
+
+ private function mark($status)
+ {
+ Database::exec("UPDATE configtgz SET status = :status WHERE configid = :configid LIMIT 1", array(
+ 'configid' => $this->configId,
+ 'status' => $status
+ ));
+ return $status;
+ }
+
+}
diff --git a/modules-available/sysconfig/lang/de/config-module.json b/modules-available/sysconfig/lang/de/config-module.json
new file mode 100644
index 00000000..4e178b65
--- /dev/null
+++ b/modules-available/sysconfig/lang/de/config-module.json
@@ -0,0 +1,16 @@
+{
+ "adAuth_description": "Mit diesem Modul ist die Anmeldung an den Client-PCs mit den Benutzerkonten eines Active Directory m\u00f6glich. Je nach Konfiguration ist auch die Nutzung eines Benutzerverzeichnisses auf dem Client m\u00f6glich.",
+ "adAuth_title": "Active Directory Authentifizierung",
+ "branding_description": "Das Logo wird am Anmeldebildschirm und beim Starten des Systems angezeigt.",
+ "branding_title": "Einrichtungsspezifisches Logo",
+ "custom_description": "Mit einem generischen Modul ist es m\u00f6glich, beliebige Dateien zum Grundsystem hinzuzuf\u00fcgen.\r\nNutzen Sie dieses Modul, um z.B. spezielle Konfigurationsdateien auf den Client PCs zu verwenden, die sich nicht mit einem der anderen Wizards erstellen l\u00e4sst. Das Hinzuf\u00fcgen eines Erweiterten Moduls erfordert in der Regel zumindest grundlegende Systemkenntnisse im Linuxbereich.",
+ "custom_title": "Generisches Modul",
+ "group_authentication": "Authentifizierung",
+ "group_branding": "Einrichtungsspezifisches Logo",
+ "group_generic": "Generisch",
+ "group_sshconfig": "SSH",
+ "ldapAuth_description": "Mit diesem Modul l\u00e4sst sich eine generische LDAP-Authentifizierung einrichten.",
+ "ldapAuth_title": "LDAP 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"
+} \ 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
new file mode 100644
index 00000000..efe6f697
--- /dev/null
+++ b/modules-available/sysconfig/lang/en/config-module.json
@@ -0,0 +1,16 @@
+{
+ "adAuth_description": "With this module you can enable logging in on the clients with Active Directory accounts. It's optionally possible to specify a network share used as the user's personal directory.",
+ "adAuth_title": "Active Directory authentication",
+ "branding_description": "The logo will be displayed on the login screen and during system startup.",
+ "branding_title": "Institution's logo",
+ "custom_description": "Using a custom module it is possible to add or override any system file on the client.\r\nYou can use this to make special customizations that aren't possible using one of the configuration wizards. Some basic knowledge about Linux is probably required for this.",
+ "custom_title": "Custom module",
+ "group_authentication": "Authentication",
+ "group_branding": "Branding",
+ "group_generic": "Generic",
+ "group_sshconfig": "SSH",
+ "ldapAuth_description": "This module enables you to create a simple LDAP authentication module.",
+ "ldapAuth_title": "LDAP 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"
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/pt/config-module.json b/modules-available/sysconfig/lang/pt/config-module.json
new file mode 100644
index 00000000..c44dc44f
--- /dev/null
+++ b/modules-available/sysconfig/lang/pt/config-module.json
@@ -0,0 +1,3 @@
+[
+
+] \ No newline at end of file
diff --git a/modules-available/sysconfig/page.inc.php b/modules-available/sysconfig/page.inc.php
index d4fe4a37..3f14a95d 100644
--- a/modules-available/sysconfig/page.inc.php
+++ b/modules-available/sysconfig/page.inc.php
@@ -54,8 +54,8 @@ class Page_SysConfig extends Page
$action = Request::any('action', 'list');
// Load all addmodule classes, as they populate the $moduleTypes array
- require_once 'modules/sysconfig/addmodule.inc.php';
- foreach (glob('modules/sysconfig/addmodule_*.inc.php') as $file) {
+ require_once Page::getModule()->getDir() . '/addmodule.inc.php';
+ foreach (glob(Page::getModule()->getDir() . '/addmodule_*.inc.php') as $file) {
require_once $file;
}
@@ -375,10 +375,10 @@ class Page_SysConfig extends Page
private function initAddModule()
{
ConfigModule::loadDb();
- require_once 'modules/sysconfig/addmodule.inc.php';
- $step = Request::any('step', 'AddModule_Start');
+ require_once Page::getModule()->getDir() . '/addmodule.inc.php';
+ $step = Request::any('step', 'AddModule_Start', 'string');
if (!class_exists($step) && preg_match('/^([a-zA-Z0-9]+)_/', $step, $out)) {
- require_once 'modules/sysconfig/addmodule_' . strtolower($out[1]) . '.inc.php';
+ require_once Page::getModule()->getDir() . '/addmodule_' . strtolower($out[1]) . '.inc.php';
}
AddModule_Base::setStep($step);
}
@@ -386,7 +386,7 @@ class Page_SysConfig extends Page
private function initAddConfig()
{
ConfigModule::loadDb();
- require_once 'modules/sysconfig/addconfig.inc.php';
+ require_once Page::getModule()->getDir() . '/addconfig.inc.php';
$step = Request::any('step', 0);
if ($step === 0)
$step = 'AddConfig_Start';