summaryrefslogtreecommitdiffstats
path: root/inc
diff options
context:
space:
mode:
authorSimon Rettberg2015-01-20 18:07:24 +0100
committerSimon Rettberg2015-01-20 18:07:24 +0100
commit1ff2bc4f3c694b7c76df8e57056c51ca39a23a34 (patch)
tree0eb19164af66b3d4e8bf639a710f323b631d23ee /inc
parentRework config module class structure. Still some TODOs though.... (diff)
downloadslx-admin-1ff2bc4f3c694b7c76df8e57056c51ca39a23a34.tar.gz
slx-admin-1ff2bc4f3c694b7c76df8e57056c51ca39a23a34.tar.xz
slx-admin-1ff2bc4f3c694b7c76df8e57056c51ca39a23a34.zip
config module structure completed. Many other fixes. Hidden pw field support.
Diffstat (limited to 'inc')
-rw-r--r--inc/configmodule.inc.php419
-rw-r--r--inc/configmodule/adauth.inc.php130
-rw-r--r--inc/configmodule/branding.inc.php58
-rw-r--r--inc/configmodule/customodule.inc.php37
-rw-r--r--inc/configmodules.inc.php94
-rw-r--r--inc/database.inc.php6
-rw-r--r--inc/property.inc.php24
-rw-r--r--inc/render.inc.php5
-rw-r--r--inc/taskmanager.inc.php4
-rw-r--r--inc/taskmanagercallback.inc.php49
-rw-r--r--inc/trigger.inc.php5
-rw-r--r--inc/util.inc.php17
12 files changed, 610 insertions, 238 deletions
diff --git a/inc/configmodule.inc.php b/inc/configmodule.inc.php
new file mode 100644
index 00000000..5eff92ba
--- /dev/null
+++ b/inc/configmodule.inc.php
@@ -0,0 +1,419 @@
+<?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('inc/configmodule/*.inc.php') as $file) {
+ require_once $file;
+ }
+ }
+
+ /**
+ * Get all known config modules.
+ *
+ * @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 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);
+
+ /**
+ * Module specific version of generate.
+ *
+ * @param string $tgz File name of tgz module to write final output to
+ * @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 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;
+ }
+
+ /**
+ * 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 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))
+ return $this->markUpdated($tmpTgz); // Finished
+ 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' => $id
+ )) !== 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;
+ }
+ }
+
+ 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
+ return 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;
+ }
+
+ 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();
+ foreach (self::$moduleTypes as $module) {
+ $instance = new $module['moduleClass'];
+ $instance->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/inc/configmodule/adauth.inc.php b/inc/configmodule/adauth.inc.php
index c0d4860c..06ac5460 100644
--- a/inc/configmodule/adauth.inc.php
+++ b/inc/configmodule/adauth.inc.php
@@ -1,7 +1,7 @@
<?php
-ConfigModules::registerModule(
- ConfigModule_AdAuth::MODID, // ID
+ConfigModule::registerModule(
+ 'AdAuth', // ID
Dictionary::translate('config-module', 'adAuth_title'), // Title
Dictionary::translate('config-module', 'adAuth_description'), // Description
Dictionary::translate('config-module', 'group_authentication'), // Group
@@ -10,115 +10,51 @@ ConfigModules::registerModule(
class ConfigModule_AdAuth extends ConfigModule
{
- const MODID = 'AdAuth';
- public static function insert($title, $server, $searchbase, $binddn, $bindpw, $home)
+ const VERSION = 1;
+
+ private static $REQUIRED_FIELDS = array('server', 'searchbase', 'binddn');
+ private static $OPTIONAL_FIELDS = array('bindpw', 'home');
+
+ protected function generateInternal($tgz, $parent)
{
- Database::exec("LOCK TABLE configtgz_module WRITE");
- Database::exec("INSERT INTO configtgz_module (title, moduletype, filepath, contents) "
- . " VALUES (:title, :modid, '', '')", array('title' => $title, 'modid' => self::MODID));
- $id = Database::lastInsertId();
- if (!is_numeric($id)) Util::traceError('Inserting new AD config to DB did not yield a numeric insert id');
- // Entry created, now try to get a free port for the proxy
- $res = Database::simpleQuery("SELECT moduleid, contents FROM configtgz_module WHERE moduletype = :modid", array(
- 'modid' => self::MODID
- ));
- $ports = array();
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
- if ($row['moduleid'] == $id) {
- // ...
- } else {
- $data = json_decode($row['contents'], true);
- if (isset($data['proxyport'])) $ports[] = $data['proxyport'];
- }
- }
- $port = 3300;
- while (in_array($port, $ports)) {
- $port++;
- }
- // Port determined, carry on...
- $ownEntry = array(
- 'server' => $server,
- 'searchbase' => $searchbase,
- 'binddn' => $binddn,
- 'bindpw' => $bindpw,
- 'home' => $home,
- 'proxyport' => $port
- );
- $data = json_encode($ownEntry);
- if ($data === false) Util::traceError('Serializing the AD data failed.');
- $moduleTgz = CONFIG_TGZ_LIST_DIR . '/modules/AD_AUTH_id_' . $id . '.' . mt_rand() . '.tgz';
- Database::exec("UPDATE configtgz_module SET filepath = :filename, contents = :contents WHERE moduleid = :id LIMIT 1", array(
- 'id' => $id,
- 'filename' => $moduleTgz,
- 'contents' => $data
- ));
- Database::exec("UNLOCK TABLES");
- // Add archive file name to array before returning it
- $ownEntry['moduleid'] = $id;
- $ownEntry['filename'] = $moduleTgz;
- return $ownEntry;
+ $config = $this->moduleData;
+ $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('CreateAdConfig', $config);
}
- /**
- * To be called if the server ip changes, as it's embedded in the AD module configs.
- * This will then recreate all AD tgz modules.
- */
- private static function rebuildAll($parent = NULL)
+ protected function moduleVersion()
{
- // Stop all running instances of ldadp
- $task = Taskmanager::submit('LdadpLauncher', array(
- 'parentTask' => $parent,
- 'failOnParentFail' => false,
- 'ids' => array()
- ));
- $ads = self::getAll();
- if (empty($ads)) // Nothing to do
- return false;
+ return self::VERSION;
+ }
- if (isset($task['id']))
- $parent = $task['id'];
- foreach ($ads as $ad) {
- $ad['parentTask'] = $parent;
- $ad['failOnParentFail'] = false;
- $ad['proxyip'] = Property::getServerIp();
- $task = Taskmanager::submit('CreateAdConfig', $ad);
- if (isset($task['id']))
- $parent = $task['id'];
- }
- Trigger::ldadp($parent);
- return $parent;
+ protected function validateConfig()
+ {
+ // Check if required fields are filled
+ return Util::hasAllKeys($this->moduleData, self::$REQUIRED_FIELDS);
}
-
- /**
- * Get all existing AD proxy configs.
- *
- * @return array array of ad configs in DB with fields:
- * moduleid, filename, server, searchbase, binddn, bindpw, home, proxyport
- */
- public static function getAll()
+
+ public function setData($key, $value)
{
- $res = Database::simpleQuery("SELECT moduleid, filepath, contents FROM configtgz_module WHERE moduletype = :modid", array(
- 'modid' => self::MODID
- ));
- $mods = array();
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
- $data = json_decode($row['contents'], true);
- $data['moduleid'] = $row['moduleid'];
- $data['filename'] = $row['filepath'];
- $mods[] = $data;
- }
- return $mods;
+ 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()
{
- self::rebuildAll();
+ $this->generate(false);
}
-
+
}
diff --git a/inc/configmodule/branding.inc.php b/inc/configmodule/branding.inc.php
index f293fda6..e8bd1da7 100644
--- a/inc/configmodule/branding.inc.php
+++ b/inc/configmodule/branding.inc.php
@@ -1,6 +1,6 @@
<?php
-ConfigModules::registerModule(
+ConfigModule::registerModule(
ConfigModule_Branding::MODID, // ID
Dictionary::translate('config-module', 'branding_title'), // Title
Dictionary::translate('config-module', 'branding_description'), // Description
@@ -10,35 +10,43 @@ ConfigModules::registerModule(
class ConfigModule_Branding extends ConfigModule
{
+
const MODID = 'Branding';
+ const VERSION = 1;
+
+ private $tmpFile = false;
- public static function insert($title, $archive)
+ protected function generateInternal($tgz, $parent)
{
- Database::exec("INSERT INTO configtgz_module (title, moduletype, filepath, contents) "
- . " VALUES (:title, :modid, '', '')", array('title' => $title, 'modid' => self::MODID));
- $id = Database::lastInsertId();
- if (!is_numeric($id))
- Util::traceError('Inserting new Branding Module into DB did not yield a numeric insert id');
- // Move tgz
- $moduleTgz = CONFIG_TGZ_LIST_DIR . '/modules/BRANDING_id_' . $id . '.' . mt_rand() . '.tgz';
- $task = Taskmanager::submit('MoveFile', array(
- 'source' => $archive,
- 'destination' => $moduleTgz
- ));
- $task = Taskmanager::waitComplete($task, 3000);
- if (Taskmanager::isFailed($task) || $task['statusCode'] !== TASK_FINISHED) {
- Taskmanager::addErrorMessage($task);
- Database::exec("DELETE FROM configtgz_module WHERE moduleid = :moduleid LIMIT 1", array(
- 'moduleid' => $id
- ));
+ if (!$this->validateConfig()) {
+ if ($this->archive() !== false && file_exists($this->archive()))
+ return true; // No new temp file given, old archive still exists, pretend it worked...
return false;
}
- // Update with path
- Database::exec("UPDATE configtgz_module SET filepath = :filename WHERE moduleid = :id LIMIT 1", array(
- 'id' => $id,
- 'filename' => $moduleTgz
+ $task = Taskmanager::submit('MoveFile', array(
+ 'source' => $this->tmpFile,
+ 'destination' => $tgz,
+ 'parentTask' => $parent,
+ 'failOnParentFail' => false
));
- return true;
+ 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;
+ }
+
}
diff --git a/inc/configmodule/customodule.inc.php b/inc/configmodule/customodule.inc.php
index 89f0aca6..195f738f 100644
--- a/inc/configmodule/customodule.inc.php
+++ b/inc/configmodule/customodule.inc.php
@@ -1,6 +1,6 @@
<?php
-ConfigModules::registerModule(
+ConfigModule::registerModule(
ConfigModule_CustomModule::MODID, // ID
Dictionary::translate('config-module', 'custom_title'), // Title
Dictionary::translate('config-module', 'custom_description'), // Description
@@ -12,5 +12,40 @@ ConfigModules::registerModule(
class ConfigModule_CustomModule extends ConfigModule
{
const MODID = 'CustomModule';
+ const VERSION = 1;
+
+ private $tmpFile = false;
+ protected function generateInternal($tgz, $parent)
+ {
+ if (!$this->validateConfig()) {
+ if ($this->archive() !== false && file_exists($this->archive()))
+ return true; // No new temp file given, old archive still exists, pretend it worked...
+ return false;
+ }
+ $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;
+ }
}
diff --git a/inc/configmodules.inc.php b/inc/configmodules.inc.php
deleted file mode 100644
index bf870e4f..00000000
--- a/inc/configmodules.inc.php
+++ /dev/null
@@ -1,94 +0,0 @@
-<?php
-
-class ConfigModules
-{
-
- private static $moduleTypes = 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('inc/configmodule/*.inc.php') as $file) {
- require_once $file;
- }
- }
-
- /**
- * Get all known config modules.
- *
- * @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
- );
- }
-
- /**
- * 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();
- foreach (self::$moduleTypes as $module) {
- $instance = new $module['moduleClass'];
- $instance->event_serverIpChanged();
- }
- }
-
-}
-
-/**
- * Base class for config modules
- */
-abstract class ConfigModule
-{
-
- public function event_serverIpChanged()
- {
-
- }
-
-}
diff --git a/inc/database.inc.php b/inc/database.inc.php
index 2c535d04..efc330fe 100644
--- a/inc/database.inc.php
+++ b/inc/database.inc.php
@@ -20,7 +20,7 @@ class Database
*/
public static function getExpectedSchemaVersion()
{
- return 8;
+ return 9;
}
public static function needSchemaUpdate()
@@ -47,7 +47,8 @@ class Database
/**
* If you just need the first row of a query you can use this.
- * Will return an associative array, or false if no row matches the query
+ *
+ * @return array|boolean Associative array representing row, or false if no row matches the query
*/
public static function queryFirst($query, $args = array(), $ignoreError = false)
{
@@ -64,6 +65,7 @@ class Database
*
* @param string $query Query to run
* @param array $args Arguments to query
+ * @param boolean $ignoreError Ignore query errors and just return false
* @return int|boolean Number of rows affected, or false on error
*/
public static function exec($query, $args = array(), $ignoreError = false)
diff --git a/inc/property.inc.php b/inc/property.inc.php
index c16c8ad7..7c3b7d37 100644
--- a/inc/property.inc.php
+++ b/inc/property.inc.php
@@ -41,12 +41,14 @@ class Property
*/
private static function set($key, $value, $minage = 0)
{
- Database::exec("INSERT INTO property (name, value, dateline) VALUES (:key, :value, :dateline)"
- . " ON DUPLICATE KEY UPDATE value = VALUES(value), dateline = VALUES(dateline)", array(
- 'key' => $key,
- 'value' => $value,
- 'dateline' => ($minage === 0 ? 0 : time() + ($minage * 60))
- ));
+ if (self::$cache === false || self::get($key) != $value) { // Simple compare, so it works for numbers accidentally casted to string somewhere
+ Database::exec("INSERT INTO property (name, value, dateline) VALUES (:key, :value, :dateline)"
+ . " ON DUPLICATE KEY UPDATE value = VALUES(value), dateline = VALUES(dateline)", array(
+ 'key' => $key,
+ 'value' => $value,
+ 'dateline' => ($minage === 0 ? 0 : time() + ($minage * 60))
+ ));
+ }
if (self::$cache !== false) {
self::$cache[$key] = $value;
}
@@ -181,5 +183,15 @@ class Property
{
return self::get('password-type', 'password');
}
+
+ public static function setNeedsCallback($value)
+ {
+ return self::set('need-callback', $value, 5);
+ }
+
+ public static function getNeedsCallback()
+ {
+ return self::get('need-callback', 0);
+ }
}
diff --git a/inc/render.inc.php b/inc/render.inc.php
index 5827dcf9..31ba5a7d 100644
--- a/inc/render.inc.php
+++ b/inc/render.inc.php
@@ -69,7 +69,8 @@ class Render
',
self::$footer
,
- '</html>'
+ '</body>
+ </html>'
;
if ($zip) {
Header('Content-Encoding: gzip');
@@ -98,7 +99,7 @@ class Render
}
/**
- * Add raw html data to the footer-section of the generated page (after the closing body tag)
+ * Add raw html data to the footer-section of the generated page (right before the closing body tag)
*/
public static function addFooter($html)
{
diff --git a/inc/taskmanager.inc.php b/inc/taskmanager.inc.php
index 05db5b5d..5045fc75 100644
--- a/inc/taskmanager.inc.php
+++ b/inc/taskmanager.inc.php
@@ -29,7 +29,7 @@ class Taskmanager
* @param array $data data to pass to the task. the structure depends on the task.
* @param boolean $async if true, the function will not wait for the reply of the taskmanager, which means
* the return value is just true (and you won't know if the task could acutally be started)
- * @return array struct representing the task status, or result of submit, false on communication error
+ * @return array struct representing the task status (as a result of submit); false on communication error
*/
public static function submit($task, $data = false, $async = false)
{
@@ -154,7 +154,7 @@ class Taskmanager
{
if (!is_array($task) || !isset($task['statusCode']) || !isset($task['id']))
return false;
- if ($task['statusCode'] === TASK_ERROR || $task['statusCode'] === TASK_FINISHED)
+ if ($task['statusCode'] !== TASK_WAITING && $task['statusCode'] !== TASK_PROCESSING)
return true;
return false;
}
diff --git a/inc/taskmanagercallback.inc.php b/inc/taskmanagercallback.inc.php
index 951a1de3..a42f4819 100644
--- a/inc/taskmanagercallback.inc.php
+++ b/inc/taskmanagercallback.inc.php
@@ -13,7 +13,7 @@ class TaskmanagerCallback
* @param string|array $task Task or Task ID to define callback for
* @param string $callback name of callback function, must be a static method in this class
*/
- public static function addCallback($task, $callback)
+ public static function addCallback($task, $callback, $args = NULL)
{
if (!call_user_func_array('method_exists', array('TaskmanagerCallback', $callback))) {
EventLog::warning("addCallback: Invalid callback function: $callback");
@@ -25,10 +25,16 @@ class TaskmanagerCallback
EventLog::warning("addCallback: Not a valid task id: $task");
return;
}
- Database::exec("INSERT INTO callback (taskid, dateline, cbfunction) VALUES (:task, UNIX_TIMESTAMP(), :callback)", array(
+ if (is_null($args))
+ $args = '';
+ else
+ $args = serialize($args);
+ Database::exec("INSERT INTO callback (taskid, dateline, cbfunction, args) VALUES (:task, UNIX_TIMESTAMP(), :callback, :args)", array(
'task' => $task,
- 'callback' => $callback
+ 'callback' => $callback,
+ 'args' => $args
));
+ Property::setNeedsCallback(1);
}
/**
@@ -39,7 +45,7 @@ class TaskmanagerCallback
public static function getPendingCallbacks()
{
$retval = array();
- $res = Database::simpleQuery("SELECT taskid, cbfunction FROM callback");
+ $res = Database::simpleQuery("SELECT taskid, cbfunction, args FROM callback");
while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
$retval[$row['taskid']][] = $row;
}
@@ -50,7 +56,7 @@ class TaskmanagerCallback
* Handle the given callback. Will delete the entry from the callback
* table if appropriate.
*
- * @param array $callback entry from the callback table (cbfunction + taskid)
+ * @param array $callback entry from the callback table (cbfunction + taskid + args)
* @param array $status status of the task as returned by the taskmanager. If NULL it will be queried.
*/
public static function handleCallback($callback, $status = NULL)
@@ -69,7 +75,10 @@ class TaskmanagerCallback
if (!call_user_func_array('method_exists', $func)) {
Eventlog::warning("handleCallback: Callback {$callback['cbfunction']} doesn't exist.");
} else {
- call_user_func($func, $status);
+ if (empty($callback['args']))
+ call_user_func($func, $status);
+ else
+ call_user_func($func, $status, unserialize($callback['args']));
}
}
}
@@ -85,13 +94,35 @@ class TaskmanagerCallback
EventLog::warning("Could not start/stop LDAP-AD-Proxy instances", $task['data']['messages']);
}
+ /**
+ * Result of restoring the server configuration
+ */
public static function dbRestored($task)
{
- error_log("dbRestored.");
- if (Taskmanager::isFinished($task) && !Taskmanager::isFailed($task)) {
- error_log("LOGGING.");
+ if (!Taskmanager::isFailed($task)) {
EventLog::info('Configuration backup restored.');
}
}
+
+ public static function adConfigCreate($task)
+ {
+ if (Taskmanager::isFailed($task))
+ EventLog::warning("Could not generate Active Directory configuration", $task['data']['error']);
+ }
+
+ /**
+ * Generating a config module has finished.
+ *
+ * @param array $task task obj
+ * @param array $args has keys 'moduleid' and optionally 'deleteOnError' and 'tmpTgz'
+ */
+ public static function cbConfModCreated($task, $args)
+ {
+ if (Taskmanager::isFailed($task)) {
+ ConfigModule::generateFailed($task, $args);
+ } else {
+ ConfigModule::generateSucceeded($args);
+ }
+ }
}
diff --git a/inc/trigger.inc.php b/inc/trigger.inc.php
index 38b25540..0410b2be 100644
--- a/inc/trigger.inc.php
+++ b/inc/trigger.inc.php
@@ -136,6 +136,7 @@ class Trigger
*/
public static function checkCallbacks()
{
+ $tasksLeft = false;
$callbackList = TaskmanagerCallback::getPendingCallbacks();
foreach ($callbackList as $taskid => $callbacks) {
$status = Taskmanager::status($taskid);
@@ -146,7 +147,11 @@ class Trigger
}
if (Taskmanager::isFailed($status) || Taskmanager::isFinished($status))
Taskmanager::release($status);
+ else
+ $tasksLeft = true;
}
+ if (!$tasksLeft)
+ Property::setNeedsCallback(0);
}
private static function triggerDaemons($action, $parent, &$taskids)
diff --git a/inc/util.inc.php b/inc/util.inc.php
index cce46536..9d0eced9 100644
--- a/inc/util.inc.php
+++ b/inc/util.inc.php
@@ -216,4 +216,21 @@ SADFACE;
return true;
}
+ /**
+ * Check whether $arrax contains all keys given in $keyList
+ * @param array $array An array
+ * @param array $keyList A list of strings which must all be valid keys in $array
+ * @return boolean
+ */
+ public static function hasAllKeys($array, $keyList)
+ {
+ if (!is_array($array))
+ return false;
+ foreach ($keyList as $key) {
+ if (!isset($array[$key]))
+ return false;
+ }
+ return true;
+ }
+
}