From a6b4976e8592b054fc7cefde379ccc6e9d79c324 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Mon, 6 Jun 2016 18:30:49 +0200 Subject: [install] Work on install mechanism so modules can independently install/update tables --- install.php | 240 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ script/install.js | 108 ++++++++++++++++++++++++ 2 files changed, 348 insertions(+) create mode 100644 install.php create mode 100644 script/install.js diff --git a/install.php b/install.php new file mode 100644 index 00000000..e579dd52 --- /dev/null +++ b/install.php @@ -0,0 +1,240 @@ + $status, 'message' => $message)); + } else { + echo 'STATUS=', $status, "\n"; + echo 'MESSAGE=', $message; + } + exit; +} + +define('UPDATE_DONE', 'UPDATE_DONE'); // Process completed successfully. This is a success return code. +define('UPDATE_NOOP', 'UPDATE_NOOP'); // Nothing had to be done, everything is up to date. This is also a success code. +define('UPDATE_RETRY', 'UPDATE_RETRY'); // Install/update process failed, but should be retried later. +define('UPDATE_FAILED', 'UPDATE_FAILED'); // Fatal error occured, retry will not resolve the issue. + +/* + * Helper functions for dealing with the database + */ + +function tableHasColumn($table, $column) +{ + $table = preg_replace('/\W/', '', $table); + $column = preg_replace('/\W/', '', $column); + $res = Database::simpleQuery("DESCRIBE `$table`", array(), true); + if ($res !== false) { + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + if ((is_array($column) && in_array($row['Field'], $column)) || (is_string($column) && $row['Field'] === $column)) + return true; + } + } + return false; +} + +function tableDropColumn($table, $column) +{ + $table = preg_replace('/\W/', '', $table); + $column = preg_replace('/\W/', '', $column); + $res = Database::simpleQuery("DESCRIBE `$table`", array(), true); + if ($res !== false) { + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + if ((is_array($column) && in_array($row['Field'], $column)) || (is_string($column) && $row['Field'] === $column)) + Database::exec("ALTER TABLE `$table` DROP `{$row['Field']}`"); + } + } +} + +function tableExists($table) +{ + $res = Database::simpleQuery("SHOW TABLES", array(), true); + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + if ($row['Tables_in_openslx'] === $table) + return true; + } + return false; +} + +/* + * Rest of install script.... + */ + +if (!isset($_SERVER['REMOTE_ADDR']) || isset($_REQUEST['direct'])) { + define('DIRECT_MODE', true); +} else { + define('DIRECT_MODE', false); +} + +define('AJAX', ((isset($_REQUEST['async'])) || (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest'))); + +error_reporting(E_ALL); +chdir(dirname($_SERVER['SCRIPT_FILENAME'])); + +// Autoload classes from ./inc which adhere to naming scheme .inc.php +spl_autoload_register(function ($class) { + $file = 'inc/' . preg_replace('/[^a-z0-9]/', '', mb_strtolower($class)) . '.inc.php'; + if (!file_exists($file)) + return; + require_once $file; +}); + +if (!is_readable('config.php')) { + finalResponse(UPDATE_FAILED, 'config.php does not exist!'); +} + +require_once 'config.php'; + +if (CONFIG_SQL_PASS === '%MYSQL_OPENSLX_PASS%') { + finalResponse(UPDATE_FAILED, 'mysql credentials not configured yet!'); +} + +// Explicitly connect to the database so it won't call Util::traceError() on failure +if (!Database::init(true)) { + finalResponse(UPDATE_RETRY, 'Connecting to the database failed'); +} + +// Good to go so far + +/** + * @param \Module $module + * @return bool + */ +function hasUpdateScript($module) +{ + return is_readable($module->getDir() . '/install.inc.php'); +} + +function runUpdateScript($module) +{ + require_once $module->getDir() . '/install.inc.php'; +} + +// Build dependency tree +Module::init(); +$modules = Module::getEnabled(); +if (empty($modules)) { + finalResponse(UPDATE_NOOP, 'No active modules, nothing to do'); +} + +if (DIRECT_MODE) { + // + // Direct mode - plain + $new = array(); + foreach ($modules as $entry) { + if (hasUpdateScript($entry)) { + $new[] = $entry; + } + } + $modules = $new; + if (empty($modules)) { + finalResponse(UPDATE_NOOP, 'No modules with install scripts, nothing to do'); + } + // Get array where the key maps a module identifier to the next module object + $assoc = array(); + $count = count($modules); + for ($i = 0; $i < $count; ++$i) { + $assoc[$modules[$i]->getIdentifier()] = $modules[($i + 1) % $count]; + } + + if (!empty($argv[1])) { + $last = $argv[1]; + } else { + $last = Request::any('last', '', 'string'); + } + if (!empty($last) && isset($assoc[$last])) { + $module = $assoc[$last]; + } + if (!isset($module)) { + $module = $modules[0]; + } + echo 'MODULE=', $module->getIdentifier(), "\n"; + runUpdateScript($module); +} else { + // + // Interactive web based mode + $mod = Request::any('module', false, 'string'); + if ($mod !== false) { + // Execute specific module + $module = Module::get($mod, true); + if ($module === false) { + finalResponse(UPDATE_NOOP, 'Given module does not exist!'); + } + if (!hasUpdateScript($module)) { + finalResponse(UPDATE_NOOP, 'Given module has no install script'); + } + runUpdateScript($module); + finalResponse(UPDATE_DONE, 'Module did not report status; assuming OK'); + } + // Show the page that shows status and triggers jobs + echo << + + + Install/Update SLXadmin + + + + + +

Modules

+ + +HERE; + foreach ($modules as $module) { + $id = $module->getIdentifier(); + echo ""; + } + echo << +

+ + + + + +HERE; + +} \ No newline at end of file diff --git a/script/install.js b/script/install.js new file mode 100644 index 00000000..2dec46d4 --- /dev/null +++ b/script/install.js @@ -0,0 +1,108 @@ +var onceOnlyGoddammit = false; +var slxModules = {}; +var slxTries = {}; +var slxCurrent = false; + +function slxRunInstall() { + if (onceOnlyGoddammit) + return; + onceOnlyGoddammit = true; + var first = false; + list = $('.id-col').each(function () { + var id = $(this).text(); + slxModules[id] = 'IDLE'; + slxTries[id] = 0; + if (first === false) { + first = id; + } + }); + if (first !== false) { + slxRun(first); + } +} + +function makeCallback(callback, userData) { + return function (firstParam) { + callback(this, userData, firstParam); + }; +} + +function slxRun(moduleName) { + var dest = $('#mod-' + moduleName); + if (dest.length !== 1 || typeof slxModules[moduleName] === 'undefined') { + alert('No such module: ' + moduleName); + return; + } + if (slxModules[moduleName] === 'IDLE' || slxModules[moduleName] === 'UPDATE_RETRY') { + if (slxTries[moduleName]++ > 3) + return; + slxModules[moduleName] = 'WORKING'; + dest.text('Working.....'); + slxCurrent = moduleName; + $.post('install.php', {module: moduleName}, makeCallback(slxDone, moduleName), 'json') + .always(makeCallback(slxTrigger, moduleName)); + } +} + +var slxDone = function (elem, moduleName, jsonReply) { + if (!jsonReply) { + jsonReply = {}; + } + if (!jsonReply.status) { + jsonReply.status = 'UPDATE_FAILED'; + jsonReply.message = 'Unknown/no status code received from server'; + } + var status = jsonReply.status; + if (jsonReply.message) { + status = status + ' (' + jsonReply.message + ')'; + } + console.log('D'); + console.log(elem); + slxModules[moduleName] = jsonReply.status; + $('#mod-' + moduleName).text(status); + if (jsonReply.status === 'UPDATE_NOOP' || jsonReply.status === 'UPDATE_DONE') { + $('#mod-' + moduleName).css('color', '#0c0'); + } + console.log('E'); +} + +var slxTrigger = function (elem, moduleName) { + if (slxModules[moduleName] === 'WORKING') { + slxModules[moduleName] = 'UPDATE_FAILED'; + $(elem).text('UPDATE_FAILED (No response from server)'); + } + if (slxCurrent === moduleName) { + slxCurrent = false; + slxRunNext(moduleName); + } +} + +function slxRunNext(lastModule) { + var next = false; + var first = false; + for (var key in slxModules) { + if (!slxModules.hasOwnProperty(key)) + continue; + // + if (slxTries[key] < 3 && (slxModules[key] === 'IDLE' || slxModules[key] === 'UPDATE_RETRY')) { + if (next === true) { + next = key; + break; + } + if (first === false) { + first = key; + } + } + if (next === false && key === lastModule) { + next = true; + } + } + if (next === false || next === true) { + next = first; + } + if (next !== false) { + slxRun(next); + } else { + alert('Done.'); + } +} \ No newline at end of file -- cgit v1.2.3-55-g7522
ModuleStatus
{$id}Waiting...