locations = array(); $this->currentLoc = 0; } else { $this->locations = Location::getLocationsAssoc(); $this->currentLoc = Request::any('locationid', 0, 'int'); } // Location valid? if ($this->currentLoc !== 0 && !isset($this->locations[$this->currentLoc])) { Message::addError('locations.invalid-location-id', $this->currentLoc); Util::redirect('?do=sysconfig'); } // Action handling $action = Request::any('action', 'list'); // Load all addmodule classes, as they populate the $moduleTypes array require_once Page::getModule()->getDir() . '/addmodule.inc.php'; foreach (glob(Page::getModule()->getDir() . '/addmodule_*.inc.php') as $file) { require_once $file; } // Action: "addmodule" (upload new module) if ($action === 'addmodule') { User::assertPermission('module.edit'); $this->initAddModule(); AddModule_Base::preprocess(); } if ($action === 'module') { // Action: "delmodule" (delete module) if (Request::post('del', 'no') !== 'no') { User::assertPermission('module.edit'); $this->delModule(); } if (Request::post('download', 'no') !== 'no') { User::assertPermission('module.download'); $this->downloadModule(); } if (Request::post('rebuild', 'no') !== 'no') { User::assertPermission('module.edit'); $this->rebuildModule(); } } // Action: "addconfig" (compose config from one or more modules) if ($action === 'addconfig') { User::assertPermission('config.edit'); $this->initAddConfig(); AddConfig_Base::preprocess(); } if ($action === 'config') { // Action: "delconfig" (delete config) if (Request::post('del', 'no') !== 'no') { User::assertPermission('config.edit'); $this->delConfig(); } // Action "activate" (set sysconfig as active) if (Request::post('activate', 'no') !== 'no') { User::assertPermission('config.assign', $this->currentLoc); $this->activateConfig(); } // Action "rebuild" (rebuild config.tgz from its modules) if (Request::post('rebuild', 'no') !== 'no') { User::assertPermission('config.edit'); $this->rebuildConfig(); } } } /** * Render module; called by main script when this module page should render * its content. */ protected function doRender() { Render::addTemplate('sysconfig_heading'); $action = Request::any('action', 'list', 'string'); switch ($action) { case 'addmodule': User::assertPermission('module.edit'); AddModule_Base::render(); return; case 'addconfig': User::assertPermission('config.edit'); AddConfig_Base::render(); return; case 'list': $pMods = User::hasPermission('module.view-list'); $pConfs = User::hasPermission('config.view-list'); if (!($pMods || $pConfs)) { User::assertPermission('config.view-list'); } Render::openTag('div', array('class' => 'row')); if ($pConfs) { $this->listConfigs(); } if ($this->currentLoc === 0 && $pMods) { $this->listModules(); } Render::closeTag('div'); if ($this->currentLoc === 0) { Render::addTemplate('list-legend', array('showLocationBadge' => $this->haveOverriddenLocations)); } Render::addTemplate('js'); // Make this js snippet a template so i18n works return; case 'module': User::assertPermission('module.view-list'); $listid = Request::post('list', Request::REQUIRED, 'int'); $this->listModuleContents($listid); return; case 'config': User::assertPermission('config.view-list'); $listid = Request::post('list', Request::REQUIRED, 'int'); $this->listConfigContents($listid); return; default: } Message::addError('invalid-action', $action, 'main'); } private function getLocationNames(array $locations, array $ids): string { $ret = array(); foreach ($ids as $id) { settype($id, 'int'); if (isset($locations[$id])) { $ret[] = $locations[$id]['locationname']; } } return implode(', ', $ret); } /** * List all configurations and configuration modules. */ private function listConfigs() { // Configs $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, title ORDER BY title ASC"); $configs = array(); if ($this->currentLoc !== 0) { $locationName = $this->locations[$this->currentLoc]['locationname']; } else { $locationName = false; } $hasDefault = false; foreach ($res as $row) { if (is_null($row['loclist'])) { $locList = array(); } else { $locList = explode(',', $row['loclist']); } $isDefault = in_array((string)$this->currentLoc, $locList, true); $hasDefault |= $isDefault; if ($this->currentLoc !== 0) { $locCount = 0; } else { $locCount = count($locList); if ($isDefault) { $locCount--; } } if ($locCount > 0) { $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'], 'current' => $isDefault, 'loclist' => $row['loclist'], 'readableLocList' => $this->getLocationNames($this->locations, $locList), 'locationCount' => $locCount, 'needrebuild' => ($row['status'] !== 'OK'), 'dateline_s' => Util::prettyTime($row['dateline']), ); } $data = array( 'locationid' => $this->currentLoc, 'locationname' => $locationName, 'havelocations' => Module::isAvailable('locations'), 'configs' => $configs, 'inheritConfig' => !$hasDefault, ); Permission::addGlobalTags($data['perms'], null, ['config.edit']); Permission::addGlobalTags($data['perms'], $this->currentLoc, ['config.assign']); Render::addTemplate('list-configs', $data); } private function listModules() { // Config modules $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); $data = array( 'modules' => $modules, '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); } private function listModuleContents($moduleid) { // fetch the data $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); } // find files in that archive $status = Taskmanager::submit('ListArchive', array( 'file' => $row['filepath'] )); if (isset($status['id'])) $status = Taskmanager::waitComplete($status, 4000); if (!Taskmanager::isFinished($status) || Taskmanager::isFailed($status)) { Taskmanager::addErrorMessage($status); Util::redirect('?do=sysconfig&locationid=' . $this->currentLoc); } $list = SysConfig::archiveContentsFromTask($status); // render the template Render::addDialog(Dictionary::translate('lang_contentOf') . ' ' . $row['title'], false, 'custom-filelist', array( 'files' => $list, )); } private function listConfigContents($configid) { // get config name $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); } // fetch the data $res = Database::simpleQuery("SELECT module.moduleid, module.title AS moduletitle" . " FROM configtgz_module module" . " INNER JOIN configtgz_x_module USING (moduleid)" . " WHERE configtgz_x_module.configid = :configid" . " ORDER BY module.title ASC", array('configid' => $configid)); $modules = array(); foreach ($res as $row) { $modules[] = array( 'module' => $row['moduletitle'], 'moduleid' => $row['moduleid'] ); } // render the template Render::addDialog(Dictionary::translate('lang_contentOf') . ' ' . $config['title'], false, 'config-module-list', array( 'modules' => $modules )); } private function activateConfig() { $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 if ($this->currentLoc === 0 || $configid !== 0) { $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); } } $locationid = $this->currentLoc; if ($configid === 0) { Database::exec("DELETE FROM configtgz_location WHERE locationid = :locationid", compact('locationid')); } else { Database::exec("INSERT INTO configtgz_location (locationid, configid) VALUES (:locationid, :configid)" . " ON DUPLICATE KEY UPDATE configid = :configid", compact('locationid', 'configid')); } $task = ConfigModuleBaseLdap::ldadp(); if ($task !== false) { TaskmanagerCallback::addCallback($task, 'ldadpStartup'); } Util::redirect('?do=sysconfig&locationid=' . $this->currentLoc); } private function rebuildConfig() { $configid = Request::post('rebuild', Request::REQUIRED, 'int'); $config = ConfigTgz::get($configid); if ($config === null) { Message::addError('config-invalid', $configid); Util::redirect('?do=sysconfig&locationid=' . $this->currentLoc); } $ret = $config->generate(false, 500); // TODO if ($ret === true) Message::addSuccess('module-rebuilt', $config->title()); elseif ($ret === false) Message::addError('module-rebuild-failed', $config->title()); else Message::addInfo('module-rebuilding', $config->title()); Util::redirect('?do=sysconfig&locationid=' . $this->currentLoc); } private function delModule() { $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'); } // 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)); $task = Taskmanager::submit('DeleteFile', array( 'file' => $module['filepath'] )); if (isset($task['statusCode']) && $task['statusCode'] === Taskmanager::TASK_WAITING) { $task = Taskmanager::waitComplete($task['id']); } 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', $module['title']); } // Rebuild depending config.tgz foreach ($existing as $crow) { $config = ConfigTgz::get($crow['configid']); if ($config !== null) { $config->generate(); } } Util::redirect('?do=sysconfig'); } private function downloadModule() { $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'); } if (!Util::sendFile($row['filepath'], $row['title'] . '.tgz')) Util::redirect('?do=sysconfig'); exit(0); } private function rebuildModule() { $moduleid = Request::post('rebuild', Request::REQUIRED); $module = ConfigModule::get($moduleid); if ($module === null) { Message::addError('config-invalid', $moduleid); Util::redirect('?do=sysconfig'); } $ret = $module->generate(false, null, 500); if ($ret === true) Message::addSuccess('module-rebuilt', $module->title()); elseif ($ret === false) Message::addError('module-rebuild-failed', $module->title()); else Message::addInfo('module-rebuilding', $module->title()); Util::redirect('?do=sysconfig'); } private function delConfig() { $configid = Request::post('del', Request::REQUIRED); $config = ConfigTgz::get($configid); if ($config === null) { Message::addError('config-invalid', $configid); Util::redirect('?do=sysconfig&locationid=' . $this->currentLoc); } if ($config->delete() === false) { Message::addError('config-delete-error', Database::lastError()); } else { Message::addSuccess('config-deleted', $config->title()); } Util::redirect('?do=sysconfig&locationid=' . $this->currentLoc); } private function initAddModule() { ConfigModule::loadDb(); 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 Page::getModule()->getDir() . '/addmodule_' . strtolower($out[1]) . '.inc.php'; } AddModule_Base::setStep($step); } private function initAddConfig() { ConfigModule::loadDb(); require_once Page::getModule()->getDir() . '/addconfig.inc.php'; $step = Request::any('step', 0); if ($step === 0) $step = 'AddConfig_Start'; AddConfig_Base::setStep($step); } /** * If modules need updates (blue refresh buttons), we query their state * via ajax, in case they are about to generate. This happens for example * if you edit a module and a bunch of configs depend on it and will be * rebuilt. */ protected function doAjax() { $action = Request::any('action', '', 'string'); if ($action === 'status') { $mods = Request::post('mods'); $confs = Request::post('confs'); $mods = explode(',', $mods); $confs = explode(',', $confs); // Mods $outMods = Database::queryAll("SELECT moduleid AS id FROM configtgz_module WHERE moduleid in (:mods) AND status = 'OK'", compact('mods')); // Confs $outConfs = Database::queryAll("SELECT configid AS id, warnings FROM configtgz WHERE configid in (:confs) AND status = 'OK'", compact('confs')); 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(); } } }