diff options
author | raul | 2014-07-11 15:14:50 +0200 |
---|---|---|
committer | raul | 2014-07-11 15:14:50 +0200 |
commit | 0f802832400d75a81984d41152137cee119d836a (patch) | |
tree | 42cf503b23795a6689af5544c48e10b05a668197 | |
parent | [i18n] deleted old unnecessary files (diff) | |
parent | [i18n] deleted old unnecessary files (diff) | |
download | slx-admin-0f802832400d75a81984d41152137cee119d836a.tar.gz slx-admin-0f802832400d75a81984d41152137cee119d836a.tar.xz slx-admin-0f802832400d75a81984d41152137cee119d836a.zip |
XMerge branch 'i18n' of gitlab.c3sl.ufpr.br:cdn/slx-admin into i18n
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | apis/getconfig.inc.php | 14 | ||||
-rw-r--r-- | apis/init.inc.php | 2 | ||||
-rw-r--r-- | apis/taskmanager.inc.php | 6 | ||||
-rwxr-xr-x | external/build_ipxe.sh | 40 | ||||
-rw-r--r-- | external/tgz/list.php | 24 | ||||
-rw-r--r-- | inc/configmodule.inc.php | 36 | ||||
-rw-r--r-- | inc/download.inc.php | 93 | ||||
-rw-r--r-- | inc/property.inc.php | 20 | ||||
-rw-r--r-- | inc/render.inc.php | 1 | ||||
-rw-r--r-- | inc/taskmanager.inc.php | 65 | ||||
-rw-r--r-- | inc/trigger.inc.php | 99 | ||||
-rw-r--r-- | inc/util.inc.php | 137 | ||||
-rw-r--r-- | index.php | 2 | ||||
-rw-r--r-- | modules/main.inc.php | 2 | ||||
-rw-r--r-- | modules/news.inc.php | 70 | ||||
-rw-r--r-- | modules/serversetup.inc.php | 4 | ||||
-rw-r--r-- | modules/sysconfig.inc.php | 4 | ||||
-rw-r--r-- | modules/sysconfig/addconfig.inc.php | 3 | ||||
-rw-r--r-- | modules/sysconfig/addmodule_branding.inc.php | 213 | ||||
-rw-r--r-- | templates/serversetup/ipaddress.html | 2 | ||||
-rw-r--r-- | templates/serversetup/ipxe.html | 5 | ||||
-rw-r--r-- | templates/sysconfig/_page.html (renamed from templates/page-sysconfig-main.html) | 0 | ||||
-rw-r--r-- | templates/sysconfig/branding-check.html | 29 | ||||
-rw-r--r-- | templates/sysconfig/branding-start.html | 19 |
25 files changed, 664 insertions, 227 deletions
@@ -2,4 +2,5 @@ *.bak *.tmp *.swp +nbproject/ diff --git a/apis/getconfig.inc.php b/apis/getconfig.inc.php index 0a8db6be..84393dbc 100644 --- a/apis/getconfig.inc.php +++ b/apis/getconfig.inc.php @@ -1,8 +1,15 @@ <?php +/** + * Escape given string so it is a valid string in sh that can be surrounded + * by single quotes ('). This basically turns _'_ into _'"'"'_ + * + * @param string $string input + * @return string escaped sh string + */ function escape($string) { - return str_replace("'", "\\'", $string); + return str_replace("'", "'\"'\"'", $string); } // Dump config from DB @@ -12,12 +19,15 @@ $res = Database::simpleQuery('SELECT setting.setting, setting.defaultvalue, sett ORDER BY setting ASC'); // TODO: Add setting groups and sort order while ($row = $res->fetch(PDO::FETCH_ASSOC)) { if (is_null($row['value'])) $row['value'] = $row['defaultvalue']; - echo $row['setting'] . "='" . str_replace("'", "'\"'\"'", $row['value']) . "'\n"; + echo $row['setting'] . "='" . escape($row['value']) . "'\n"; } + // Additional "intelligent" config // Remote log URL echo "SLX_REMOTE_LOG='http://" . escape($_SERVER['SERVER_ADDR']) . "/slxadmin/api.php?do=clientlog'\n"; +// vm list url +echo "SLX_VMCHOOSER_BASE_URL='http://" . escape($_SERVER['SERVER_ADDR']) . "/vmchooser/'\n"; // VMStore path and type $vmstore = Property::getVmStoreConfig(); diff --git a/apis/init.inc.php b/apis/init.inc.php index cdd6ac05..d24b2cca 100644 --- a/apis/init.inc.php +++ b/apis/init.inc.php @@ -5,3 +5,5 @@ if ($_SERVER['REMOTE_ADDR'] !== '127.0.0.1') Trigger::ldadp(); Trigger::mount(); +Trigger::autoUpdateServerIp(); +Trigger::ipxe(); diff --git a/apis/taskmanager.inc.php b/apis/taskmanager.inc.php index f7ee6ac1..8365aac7 100644 --- a/apis/taskmanager.inc.php +++ b/apis/taskmanager.inc.php @@ -15,12 +15,6 @@ foreach ($_POST['ids'] as $id) { continue; } $return[] = $status; - // HACK HACK - should be pluggable - if (isset($status['statusCode']) && $status['statusCode'] === TASK_FINISHED // iPXE Update - && $id === Property::getIPxeTaskId() && Property::getServerIp() !== Property::getIPxeIp()) { - Property::setIPxeIp(Property::getServerIp()); - } - // -- END HACKS -- if (!isset($status['statusCode']) || ($status['statusCode'] !== TASK_WAITING && $status['statusCode'] !== TASK_PROCESSING)) { Taskmanager::release($id); } diff --git a/external/build_ipxe.sh b/external/build_ipxe.sh deleted file mode 100755 index 8cb23cd0..00000000 --- a/external/build_ipxe.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/bin/bash - -# Call: $0 <ip_file> <server_ip> <logfile> -# Self-Call: $0 --exec <ip_file> <server_ip> - -if [ $# -lt 3 ]; then - echo "Falscher Aufruf: Keine zwei Parameter angegeben!" - exit 1 -fi - -if [ "$1" != "--exec" ]; then - $0 --exec "$1" "$2" > "$3" 2>&1 & - RET=$! - echo "PID: ${RET}." - exit 0 -fi - -FILE="$2" -SERVER="$3" - -cd "/opt/openslx/ipxe/src" - -[ -e "bin/undionly.kkkpxe" ] && unlink "bin/undionly.kkkpxe" - -make bin/undionly.kkkpxe EMBED=../ipxelinux.ipxe,../pxelinux.0 - -if [ ! -e "bin/undionly.kkkpxe" -o "$(stat -c %s "bin/undionly.kkkpxe")" -lt 80000 ]; then - echo "Error compiling ipxelinux.0" - exit 1 -fi - -if ! cp "bin/undionly.kkkpxe" "/srv/openslx/tftp/ipxelinux.0"; then - echo "** Error copying ipxelinux.0 to target **" - exit 1 -fi - -echo -n "$SERVER" > "$FILE" -echo " ** SUCCESS **" -exit 0 - diff --git a/external/tgz/list.php b/external/tgz/list.php deleted file mode 100644 index 5c8d1c67..00000000 --- a/external/tgz/list.php +++ /dev/null @@ -1,24 +0,0 @@ -<?php - -/* -echo '['; - -$first = true; -foreach (glob('./*.tgz') as $file) { - if (!$first) echo ', '; - $first = false; - echo ' { "file" : "' . basename($file) . '", "description" : "<Beschreibung>" }'; -} -echo ' ]'; -*/ - -$files = array(); -foreach (glob('./*.tgz') as $file) { - $files[] = array( - 'file' => basename($file), - 'description' => 'Eine sinnvolle Beschreibung' - ); -} - -echo json_encode($files); - diff --git a/inc/configmodule.inc.php b/inc/configmodule.inc.php index e9fa40bb..c0838b5c 100644 --- a/inc/configmodule.inc.php +++ b/inc/configmodule.inc.php @@ -36,16 +36,44 @@ class ConfigModule ); $data = json_encode($ownEntry); if ($data === false) Util::traceError('Serializing the AD data failed.'); - $name = CONFIG_TGZ_LIST_DIR . '/modules/AD_AUTH_id_' . $id . '.' . mt_rand() . '.tgz'; + $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' => $name, - 'contents' => json_encode($ownEntry) + 'filename' => $moduleTgz, + 'contents' => $data )); // Add archive file name to array before returning it $ownEntry['moduleid'] = $id; - $ownEntry['filename'] = $name; + $ownEntry['filename'] = $moduleTgz; return $ownEntry; } + public static function insertBrandingModule($title, $archive) + { + Database::exec("INSERT INTO configtgz_module (title, moduletype, filepath, contents) " + . " VALUES (:title, 'BRANDING', '', '')", array('title' => $title)); + $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 + )); + return false; + } + // Update with path + Database::exec("UPDATE configtgz_module SET filepath = :filename WHERE moduleid = :id LIMIT 1", array( + 'id' => $id, + 'filename' => $moduleTgz + )); + return true; + } + } diff --git a/inc/download.inc.php b/inc/download.inc.php new file mode 100644 index 00000000..6485ee24 --- /dev/null +++ b/inc/download.inc.php @@ -0,0 +1,93 @@ +<?php + +class Download +{ + + /** + * Common initialization for download and downloadToFile + * Return file handle to header file + */ + private static function initCurl($url, $timeout, &$head) + { + $ch = curl_init(); + if ($ch === false) + Util::traceError('Could not initialize cURL'); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, ceil($timeout / 2)); + curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_AUTOREFERER, true); + curl_setopt($ch, CURLOPT_BINARYTRANSFER, true); + curl_setopt($ch, CURLOPT_MAXREDIRS, 6); + $tmpfile = tempnam('/tmp/', 'bwlp-'); + $head = fopen($tmpfile, 'w+b'); + if ($head === false) + Util::traceError("Could not open temporary head file $tmpfile for writing."); + curl_setopt($ch, CURLOPT_WRITEHEADER, $head); + return $ch; + } + + /** + * Read 10kb from the given file handle, seek to 0 first, + * close the file after reading. Returns data read + */ + private static function getContents($fh) + { + fseek($fh, 0, SEEK_SET); + $data = fread($fh, 10000); + fclose($fh); + return $data; + } + + /** + * Download file, obey given timeout in seconds + * Return data on success, false on failure + */ + public static function asString($url, $timeout, &$code) + { + $ch = self::initCurl($url, $timeout, $head); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $data = curl_exec($ch); + $head = self::getContents($head); + if (preg_match('#^HTTP/\d+\.\d+ (\d+) #', $head, $out)) { + $code = (int) $out[1]; + } else { + $code = 999; + } + curl_close($ch); + return $data; + } + + /** + * Download a file from a URL to file. + * + * @param string $target destination path to download file to + * @param string $url URL of file to download + * @param int $timeout timeout in seconds + * @param int $code HTTP status code passed out by reference + * @return boolean + */ + public static function toFile($target, $url, $timeout, &$code) + { + $fh = fopen($target, 'wb'); + if ($fh === false) + Util::traceError("Could not open $target for writing."); + $ch = self::initCurl($url, $timeout, $head); + curl_setopt($ch, CURLOPT_FILE, $fh); + $res = curl_exec($ch); + $head = self::getContents($head); + curl_close($ch); + fclose($fh); + if ($res === false) { + @unlink($target); + return false; + } + if (preg_match_all('#\bHTTP/\d+\.\d+ (\d+) #', $head, $out, PREG_SET_ORDER)) { + $code = (int) $out[count($out)-1][1]; + } else { + $code = '999 ' . curl_error($ch); + } + return true; + } + +} diff --git a/inc/property.inc.php b/inc/property.inc.php index 00c8018f..81de137f 100644 --- a/inc/property.inc.php +++ b/inc/property.inc.php @@ -62,26 +62,6 @@ class Property self::set('server-ip', $value); } - public static function getIPxeIp() - { - return self::get('ipxe-ip', 'not-set'); - } - - public static function setIPxeIp($value) - { - self::set('ipxe-ip', $value); - } - - public static function getIPxeTaskId() - { - return self::get('ipxe-task'); - } - - public static function setIPxeTaskId($value) - { - self::set('ipxe-task', $value); - } - public static function getBootMenu() { return json_decode(self::get('ipxe-menu'), true); diff --git a/inc/render.inc.php b/inc/render.inc.php index 1a34c45c..0b891c80 100644 --- a/inc/render.inc.php +++ b/inc/render.inc.php @@ -65,7 +65,6 @@ class Render ' </div> <script src="script/jquery.js"></script> <script src="script/bootstrap.min.js"></script> - <script src="script/bootstrap-tagsinput.min.js"></script> <script src="script/taskmanager.js"></script> ', self::$footer diff --git a/inc/taskmanager.inc.php b/inc/taskmanager.inc.php index 3862ac72..5813164a 100644 --- a/inc/taskmanager.inc.php +++ b/inc/taskmanager.inc.php @@ -6,6 +6,10 @@ class Taskmanager { + /** + * UDP socket used for communication with the task manager + * @var resource + */ private static $sock = false; private static function init() @@ -18,6 +22,15 @@ class Taskmanager socket_connect(self::$sock, '127.0.0.1', 9215); } + /** + * Start a task via the task manager. + * + * @param string $task name of task to start + * @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 + */ public static function submit($task, $data = false, $async = false) { self::init(); @@ -44,11 +57,22 @@ class Taskmanager return $reply; } - public static function status($taskId) + /** + * Query status of given task. + * + * @param mixed $task task id or task struct + * @return array status of task as array, or false on communication error + */ + public static function status($task) { + if (is_array($task) && isset($task['id'])) { + $task = $task['id']; + } + if (!is_string($task)) + return false; self::init(); $seq = (string) mt_rand(); - $message = "$seq, status, $taskId"; + $message = "$seq, status, $task"; $sent = socket_send(self::$sock, $message, strlen($message), 0); $reply = self::readReply($seq); if (!is_array($reply)) @@ -56,6 +80,13 @@ class Taskmanager return $reply; } + /** + * Wait for the given task's completion. + * + * @param type $task task to wait for + * @param int $timeout maximum time in ms to wait for completion of task + * @return array result/status of task, or false if it couldn't be queried + */ public static function waitComplete($task, $timeout = 1500) { if (is_array($task) && isset($task['id'])) { @@ -76,13 +107,19 @@ class Taskmanager $done = true; break; } - usleep(150000); + usleep(100000); } if ($done) self::release($task); return $status; } + /** + * Check whether the given task can be considered failed. + * + * @param mixed $task task id or struct representing task + * @return boolean true if task failed, false if finished successfully or still waiting/running + */ public static function isFailed($task) { if (!is_array($task) || !isset($task['statusCode']) || !isset($task['id'])) @@ -113,23 +150,35 @@ class Taskmanager Message::addError('task-error', $task['statusCode']); } - public static function release($taskId) + /** + * Release a given task from the task manager, so it won't keep the result anymore in case it's finished running. + * + * @param string $task task to release. can either be its id, or a struct representing the task, as returned + * by ::submit() or ::status() + */ + public static function release($task) { + if (is_array($task) && isset($task['id'])) { + $task = $task['id']; + } + if (!is_string($task)) + return; self::init(); $seq = (string) mt_rand(); - $message = "$seq, release, $taskId"; + $message = "$seq, release, $task"; socket_send(self::$sock, $message, strlen($message), 0); } /** - * - * @param type $seq + * Read reply from socket for given sequence number. + * + * @param string $seq * @return mixed the decoded json data for that message as an array, or null on error */ private static function readReply($seq) { $tries = 0; - while (($bytes = socket_recvfrom(self::$sock, $buf, 90000, 0, $bla1, $bla2)) !== false) { + while (($bytes = socket_recvfrom(self::$sock, $buf, 90000, 0, $bla1, $bla2)) !== false || socket_last_error() === 11) { $parts = explode(',', $buf, 2); if (count($parts) == 2 && $parts[0] == $seq) { return json_decode($parts[1], true); diff --git a/inc/trigger.inc.php b/inc/trigger.inc.php index 2fde45ab..e6f7cd31 100644 --- a/inc/trigger.inc.php +++ b/inc/trigger.inc.php @@ -18,30 +18,66 @@ class Trigger * @param boolean $force force recompilation even if it seems up to date * @return boolean|string true if already up to date, false if launching task failed, task-id otherwise */ - public static function ipxe($force = false) + public static function ipxe() { - if (!$force && Property::getIPxeIp() === Property::getServerIp()) - return true; // Nothing to do - $last = Property::getIPxeTaskId(); - if ($last !== false) { - $status = Taskmanager::status($last); - if (isset($status['statusCode']) && ($status['statusCode'] === TASK_WAITING || $status['statusCode'] === TASK_PROCESSING)) - return false; // Already compiling - } $data = Property::getBootMenu(); - $data['ip'] = Property::getServerIp(); $task = Taskmanager::submit('CompileIPxe', $data); if (!isset($task['id'])) return false; - Property::setIPxeTaskId($task['id']); return $task['id']; } /** - * + * Try to automatically determine the primary IP address of the server. + * This only works if the server has either one public IPv4 address (and potentially + * one or more non-public addresses), or one private address. + */ + public static function autoUpdateServerIp() + { + $task = Taskmanager::submit('LocalAddressesList'); + if ($task === false) + return; + $task = Taskmanager::waitComplete($task, 10000); + if (!isset($task['data']['addresses']) || empty($task['data']['addresses'])) + return; + + $serverIp = Property::getServerIp(); + $publicCandidate = 'none'; + $privateCandidate = 'none'; + foreach ($task['data']['addresses'] as $addr) { + if ($addr['ip'] === $serverIp) + return; + if (substr($addr['ip'], 0, 4) === '127.') + continue; + if (Util::isPublicIpv4($addr['ip'])) { + if ($publicCandidate === 'none') + $publicCandidate = $addr['ip']; + else + $publicCandidate = 'many'; + } else { + if ($privateCandidate === 'none') + $privateCandidate = $addr['ip']; + else + $privateCandidate = 'many'; + } + } + if ($publicCandidate !== 'none' && $publicCandidate !== 'many') { + Property::setServerIp($publicCandidate); + return; + } + if ($privateCandidate !== 'none' && $privateCandidate !== 'many') { + Property::setServerIp($privateCandidate); + return; + } + } + + /** + * Launch all ldadp instances that need to be running. + * + * @param string $parent if not NULL, this will be the parent task of the launch-task * @return boolean|string false on error, id of task otherwise */ - public static function ldadp() + public static function ldadp($parent = NULL) { $res = Database::simpleQuery("SELECT moduleid, configtgz.filepath FROM configtgz_module" . " INNER JOIN configtgz_x_module USING (moduleid)" @@ -56,17 +92,50 @@ class Trigger } } $task = Taskmanager::submit('LdadpLauncher', array( - 'ids' => $id + 'ids' => $id, + 'parentTask' => $parent, + 'failOnParentFail' => false )); if (!isset($task['id'])) return false; return $task['id']; } + /** + * 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. + */ + public static function rebuildAdModules() + { + $res = Database::simpleQuery("SELECT moduleid, filepath, content FROM configtgz_module" + . " WHERE moduletype = 'AD_AUTH'"); + if ($res->rowCount() === 0) + return; + + $task = Taskmanager::submit('LdadpLauncher', array('ids' => array())); // Stop all running instances + $parent = isset($task['id']) ? $task['id'] : NULL; + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + $config = json_decode($row['contents']); + $config['proxyip'] = Property::getServerIp(); + $config['moduleid'] = $row['moduleid']; + $config['filename'] = $row['filepath']; + $config['parentTask'] = $parent; + $config['failOnParentFail'] = false; + $task = Taskmanager::submit('CreateAdConfig', $config); + $parent = isset($task['id']) ? $task['id'] : NULL; + } + + } + + /** + * Mount the VM store into the server. + * + * @return array task status of mount procedure, or false on error + */ public static function mount() { $vmstore = Property::getVmStoreConfig(); - if (!is_array($vmstore)) return; + if (!is_array($vmstore)) return false; $storetype = $vmstore['storetype']; if ($storetype === 'nfs') $addr = $vmstore['nfsaddr']; if ($storetype === 'cifs') $addr = $vmstore['cifsaddr']; diff --git a/inc/util.inc.php b/inc/util.inc.php index 8b5a14e4..45a6b684 100644 --- a/inc/util.inc.php +++ b/inc/util.inc.php @@ -54,8 +54,10 @@ class Util */ public static function verifyToken() { - if (Session::get('token') === false) return true; - if (isset($_REQUEST['token']) && Session::get('token') === $_REQUEST['token']) return true; + if (Session::get('token') === false) + return true; + if (isset($_REQUEST['token']) && Session::get('token') === $_REQUEST['token']) + return true; Message::addError('token'); return false; } @@ -77,85 +79,6 @@ class Util } /** - * Common initialization for download and downloadToFile - * Return file handle to header file - */ - private static function initCurl($url, $timeout, &$head) - { - $ch = curl_init(); - if ($ch === false) Util::traceError('Could not initialize cURL'); - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, ceil($timeout / 2)); - curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); - curl_setopt($ch, CURLOPT_AUTOREFERER, true); - curl_setopt($ch, CURLOPT_BINARYTRANSFER, true); - curl_setopt($ch, CURLOPT_MAXREDIRS, 6); - $tmpfile = '/tmp/' . mt_rand() . '-' . time(); - $head = fopen($tmpfile, 'w+b'); - if ($head === false) Util::traceError("Could not open temporary head file $tmpfile for writing."); - curl_setopt($ch, CURLOPT_WRITEHEADER, $head); - return $ch; - } - - /** - * Read 10kb from the given file handle, seek to 0 first, - * close the file after reading. Returns data read - */ - private static function getContents($fh) - { - fseek($fh, 0, SEEK_SET); - $data = fread($fh, 10000); - fclose($fh); - return $data; - } - - /** - * Download file, obey given timeout in seconds - * Return data on success, false on failure - */ - public static function download($url, $timeout, &$code) - { - $ch = self::initCurl($url, $timeout, $head); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - $data = curl_exec($ch); - $head = self::getContents($head); - if (preg_match('#^HTTP/\d+\.\d+ (\d+) #', $head, $out)) { - $code = (int)$out[1]; - } else { - $code = 999; - } - curl_close($ch); - return $data; - } - - /** - * Download file, obey given timeout in seconds - * Return true on success, false on failure - */ - public static function downloadToFile($target, $url, $timeout, &$code) - { - $fh = fopen($target, 'wb'); - if ($fh === false) Util::traceError("Could not open $target for writing."); - $ch = self::initCurl($url, $timeout, $head); - curl_setopt($ch, CURLOPT_FILE, $fh); - $res = curl_exec($ch); - $head = self::getContents($head); - curl_close($ch); - fclose($fh); - if ($res === false) { - @unlink($target); - return false; - } - if (preg_match('#^HTTP/\d+\.\d+ (\d+) #', $head, $out)) { - $code = (int)$out[1]; - } else { - $code = '999 ' . curl_error($ch); - } - return true; - } - - /** * Convert given number to human readable file size string. * Will append Bytes, KiB, etc. depending on magnitude of number. * @@ -163,7 +86,8 @@ class Util * @param type $decimals number of decimals to show, -1 for automatic * @return type human readable string representing the given filesize */ - public static function readableFileSize($bytes, $decimals = -1) { + public static function readableFileSize($bytes, $decimals = -1) + { static $sz = array('Byte', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB'); $factor = floor((strlen($bytes) - 1) / 3); if ($factor == 0) { @@ -173,12 +97,12 @@ class Util } return sprintf("%.{$decimals}f ", $bytes / pow(1024, $factor)) . $sz[$factor]; } - + public static function sanitizeFilename($name) { return preg_replace('/[^a-zA-Z0-9_\-]+/', '_', $name); } - + /** * Create human readable error description from a $_FILES[<..>]['error'] code * @@ -217,5 +141,48 @@ class Util return $message; } -} + /** + * Is given string a public ipv4 address? + * + * @param string $ip_addr input to check + * @return boolean true iff $ip_addr is a valid public ipv4 address + */ + public static function isPublicIpv4($ip_addr) + { + if (!preg_match("/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/", $ip_addr)) + return false; + + $parts = explode(".", $ip_addr); + foreach ($parts as $part) { + if (!is_numeric($part) || $part > 255 || $part < 0) + return false; + } + if ($parts[0] == 0 || $parts[0] == 10 || $parts[0] == 127 || ($parts[0] > 223 && $parts[0] < 240)) + return false; + if (($parts[0] == 192 && $parts[1] == 168) || ($parts[0] == 169 && $parts[1] == 254)) + return false; + if ($parts[0] == 172 && $parts[1] > 15 && $parts[1] < 32) + return false; + + return true; + } + + /** + * Return contents of given file as string, but only read up to maxBytes bytes. + * + * @param string $file file to read + * @param int $maxBytes maximum length to read + * @return boolean success or failure + */ + public static function readFile($file, $maxBytes = 1000) + { + $fh = @fopen($file, 'rb'); + if ($fh === false) + return false; + $data = fread($fh, $maxBytes); + fclose($fh); + return $data; + } + +} @@ -130,7 +130,9 @@ Message::renderList(); Page::render(); if (defined('CONFIG_DEBUG') && CONFIG_DEBUG) { + Render::openTag('div', array('class' => 'container')); Message::addWarning('debug-mode'); + Render::closeTag('div'); } // Send page to client. diff --git a/modules/main.inc.php b/modules/main.inc.php index 232d6a0a..42a980e3 100644 --- a/modules/main.inc.php +++ b/modules/main.inc.php @@ -17,13 +17,11 @@ class Page_Main extends Page return; } // Logged in here - $ipxe = (Property::getServerIp() !== Property::getIPxeIp()); $sysconfig = !file_exists(CONFIG_HTTP_DIR . '/default/config.tgz'); $minilinux = !file_exists(CONFIG_HTTP_DIR . '/default/kernel') || !file_exists(CONFIG_HTTP_DIR . '/default/initramfs-stage31') || !file_exists(CONFIG_HTTP_DIR . '/default/stage32.sqfs'); $vmstore = !is_array(Property::getVmStoreConfig()); Render::addTemplate('page-main', array( 'user' => User::getName(), - 'ipxe' => $ipxe, 'sysconfig' => $sysconfig, 'minilinux' => $minilinux, 'vmstore' => $vmstore diff --git a/modules/news.inc.php b/modules/news.inc.php index 0c3f1f13..fc5c1892 100644 --- a/modules/news.inc.php +++ b/modules/news.inc.php @@ -2,11 +2,26 @@ class Page_News extends Page { + /** + * Member variables needed to represent a news entry. + * + * $newsId int ID of the news entry attributed by the database. + * $newsTitle string Title of the entry. + * $newsContent string Content as text. (TODO: html-Support?) + * $newsDate string Unix epoch date of the news' creation. + */ private $newsId = false; private $newsTitle = false; private $newsContent = false; private $newsDate = false; + /** + * Implementation of the abstract doPreprocess function + * + * Checks if the user is logged in and processes any + * action if one was specified in the request. + * + */ protected function doPreprocess() { // load user, we will need it later @@ -20,26 +35,47 @@ class Page_News extends Page // check which action we need to do $action = Request::any('action', 'show'); - if ($action === 'show') { + if ($action === 'clear') { + // clear news input fields + // TODO: is this the right way? + $this->newsId = false; + $this->newsTitle = false; + $this->newsContent = false; + $this->newsDate = false; + } elseif ($action === 'show') { // show news if (!$this->loadNews(Request::any('newsid'))) { Message::addError('news-empty'); } } elseif ($action === 'save') { // save to DB - $this->saveNews(); + if (!$this->saveNews()) { + // re-set the fields we got + Request::post('news-title') ? $this->newsTitle = Request::post('news-title') : $this->newsTitle = false; + Request::post('news-content') ? $this->newsContent = Request::post('news-content') : $this->newsContent = false; + } else { + Message::addSuccess('news-save-success'); + Util::redirect('?do=News'); + } } elseif ($action === 'delete') { // delete it $this->delNews(Request::post('newsid')); } else { + // unknown action, redirect user Message::addError('invalid-action', $action); Util::redirect('?do=News'); } } + /** + * Implementation of the abstract doRender function + * + * Fetch the list of news from the database and paginate it. + * + */ protected function doRender() { - // prepare the list of the older news + // fetch the list of the older news $lines = array(); $paginate = new Paginate("SELECT newsid, dateline, title, content FROM news ORDER BY dateline DESC", 10); $res = $paginate->exec(); @@ -57,7 +93,13 @@ class Page_News extends Page 'list' => $lines )); } - + /** + * Loads the news with the given ID into the form. + * + * @param int $newsId ID of the news to be shown. + * @return boolean true if loading that news worked + * + */ private function loadNews($newsId) { // check to see if we need to request a specific newsid @@ -79,29 +121,41 @@ class Page_News extends Page return $row !== false; } + /** + * Save the given $newsTitle and $newsContent as POST'ed into the database. + * + */ private function saveNews() { // check if news content were set by the user $newsTitle = Request::post('news-title'); $newsContent = Request::post('news-content'); - if ($newsContent !== false && $newsTitle !== false) { + if ($newsContent !== '' && $newsTitle !== '') { // we got title and content, save it to DB Database::exec("INSERT INTO news (dateline, title, content) VALUES (:dateline, :title, :content)", array( 'dateline' => time(), 'title' => $newsTitle, 'content' => $newsContent )); - // all done, redirect to main news page - Message::addSuccess('news-set-success'); - Util::redirect('?do=News'); + return true; + } else { + Message::addError('empty-field'); + return false; } } + /** + * Delete the news entry with ID $newsId + * + * @param int $newsId ID of the entry to be deleted. + */ private function delNews($newsId) { + // sanity check: is newsId even numeric? if (!is_numeric($newsId)) { Message::addError('value-invalid', 'newsid', $newsId); } else { + // check passed - do delete Database::exec("DELETE FROM news WHERE newsid = :newsid LIMIT 1", array( 'newsid' => $newsId )); diff --git a/modules/serversetup.inc.php b/modules/serversetup.inc.php index c03fe9e3..527c0940 100644 --- a/modules/serversetup.inc.php +++ b/modules/serversetup.inc.php @@ -45,7 +45,6 @@ class Page_ServerSetup extends Page 'ips' => $this->taskStatus['data']['addresses'] )); $data = $this->currentMenu; - $data['taskid'] = Property::getIPxeTaskId(); if (!isset($data['defaultentry'])) $data['defaultentry'] = 'net'; if ($data['defaultentry'] === 'net') @@ -101,7 +100,6 @@ class Page_ServerSetup extends Page } if ($valid) { Property::setServerIp($newAddress); - Trigger::ipxe(); } else { Message::addError('invalid-ip', $newAddress); } @@ -120,7 +118,7 @@ class Page_ServerSetup extends Page $this->currentMenu['timeout'] = $timeout; $this->currentMenu['custom'] = Request::post('custom', ''); Property::setBootMenu($this->currentMenu); - Trigger::ipxe(true); + Trigger::ipxe(); Util::redirect('?do=ServerSetup'); } diff --git a/modules/sysconfig.inc.php b/modules/sysconfig.inc.php index 724e288c..8be001e6 100644 --- a/modules/sysconfig.inc.php +++ b/modules/sysconfig.inc.php @@ -130,7 +130,7 @@ class Page_SysConfig extends Page $configs[] = array( 'configid' => $row['configid'], 'config' => $row['title'], - 'current' => readlink('/srv/openslx/www/boot/default/config.tgz') === $row['filepath'] + 'current' => readlink(CONFIG_HTTP_DIR . '/default/config.tgz') === $row['filepath'] ); } // Config modules @@ -142,7 +142,7 @@ class Page_SysConfig extends Page 'module' => $row['title'] ); } - Render::addTemplate('page-sysconfig-main', array( + Render::addTemplate('sysconfig/_page', array( 'configs' => $configs, 'modules' => $modules )); diff --git a/modules/sysconfig/addconfig.inc.php b/modules/sysconfig/addconfig.inc.php index 21ac93b4..1e2e870a 100644 --- a/modules/sysconfig/addconfig.inc.php +++ b/modules/sysconfig/addconfig.inc.php @@ -134,8 +134,7 @@ class AddConfig_Start extends AddConfig_Base } /** - * Start dialog for adding config. Ask for title, - * show selection of modules. + * Success dialog if adding config worked. */ class AddConfig_Finish extends AddConfig_Base { diff --git a/modules/sysconfig/addmodule_branding.inc.php b/modules/sysconfig/addmodule_branding.inc.php new file mode 100644 index 00000000..6515a850 --- /dev/null +++ b/modules/sysconfig/addmodule_branding.inc.php @@ -0,0 +1,213 @@ +<?php + +/* + * Wizard for including a branding logo. + */ + +Page_SysConfig::addModule('BRANDING', 'Branding_Start', 'Logo der Einrichtung', 'Dieses Modul dient dem Hinzufügen eines Logos der Hochschule/Universität, welches' + . ' dann z.B. auf dem Anmeldebildschirm angezeigt wird.', 'Branding', false +); + +class Branding_Start extends AddModule_Base +{ + + protected function renderInternal() + { + Render::addDialog('Einrichtungsspezifisches Logo', false, 'sysconfig/branding-start', array( + 'step' => 'Branding_ProcessFile', + )); + } + +} + +class Branding_ProcessFile extends AddModule_Base +{ + + private $task; + private $svgFile; + private $tarFile; + + protected function preprocessInternal() + { + $url = Request::post('url'); + if ((!isset($_FILES['file']['error']) || $_FILES['file']['error'] === UPLOAD_ERR_NO_FILE) && empty($url)) { + Message::addError('empty-field'); + Util::redirect('?do=SysConfig&action=addmodule&step=Branding_Start'); + } + + $this->svgFile = tempnam(sys_get_temp_dir(), 'bwlp-'); + if (isset($_FILES['file']['error']) && $_FILES['file']['error'] !== UPLOAD_ERR_NO_FILE) { + // Prefer uploaded image over URL (in case both are given) + if ($_FILES['file']['error'] !== UPLOAD_ERR_OK) { + Message::addError('upload-failed', Util::uploadErrorString($_FILES['file']['error'])); + Util::redirect('?do=SysConfig&action=addmodule&step=Branding_Start'); + } + if (!move_uploaded_file($_FILES["file"]["tmp_name"], $this->svgFile)) { + Message::addError('upload-failed', 'Moving temp file failed'); + Util::redirect('?do=SysConfig&action=addmodule&step=Branding_Start'); + } + } else { + // URL - launch task that fetches the SVG file from it + if (strpos($url, '://') === false) + $url = "http://$url"; + if (!$this->downloadSvg($this->svgFile, $url)) + Util::redirect('?do=SysConfig&action=addmodule&step=Branding_Start'); + } + chmod($this->svgFile, 0644); + $this->tarFile = '/tmp/bwlp-' . time() . '-' . mt_rand() . '.tgz'; + $this->task = Taskmanager::submit('BrandingGenerator', array( + 'tarFile' => $this->tarFile, + 'svgFile' => $this->svgFile + )); + $this->task = Taskmanager::waitComplete($this->task); + if (Taskmanager::isFailed($this->task)) { + @unlink($this->svgFile); + Taskmanager::addErrorMessage($this->task); + Util::redirect('?do=SysConfig&action=addmodule&step=Branding_Start'); + } + Session::set('logo_tgz', $this->tarFile); + Session::save(); + } + + protected function renderInternal() + { + $svg = $png = false; + if (isset($this->task['data']['pngFile'])) + $png = base64_encode(file_get_contents($this->task['data']['pngFile'])); + if (filesize($this->svgFile) < 1000000) + $svg = base64_encode(file_get_contents($this->svgFile)); + Render::addDialog('Einrichtungsspezifisches Logo', false, 'sysconfig/branding-check', array( + 'png' => $png, + 'svg' => $svg, + 'error' => $this->task['data']['error'], + 'step' => 'Branding_Finish', + ) + ); + @unlink($this->svgFile); + } + + /** + * Downlaod an svg file from the given url. This function has "wikipedia support", it tries to detect + * URLs in wikipedia articles or thumbnails and then find the actual svg file. + * + * @param string $svgName file to download to + * @param string $url url to download from + * @return boolean true of download succeded, false on download error (also returns true if downloaded file doesn't + * seem to be svg!) + */ + private function downloadSvg($svgName, $url) + { + // [wikipedia] Did someone paste a link to a thumbnail of the svg? Let's fix that... + if (preg_match('#^(.*)/thumb/(.*\.svg)/.*\.svg#', $url, $out)) { + $url = $out[1] . '/' . $out[2]; + } + for ($i = 0; $i < 5; ++$i) { + $code = 400; + if (!Download::toFile($svgName, $url, 3, $code) || $code < 200 || $code > 299) { + Message::addError('remote-timeout', $url, $code); + return false; + } + $content = Util::readFile($svgName, 25000); + // Is svg file? + if (strpos($content, '<svg') !== false) + return true; // Found an svg tag - don't try to find links to the actual image + + // [wikipedia] Try to be nice and detect links that might give a hint where the svg can be found + if (preg_match_all('#href="([^"]*upload.wikimedia.org/[^"]*/[^"]*/[^"]*\.svg|[^"]+/[^"]+:[^"]+\.svg[^"]*)"#', $content, $out, PREG_PATTERN_ORDER)) { + foreach ($out[1] as $res) { + if (!strpos($res, 'action=edit')) { + $new = $this->internetCombineUrl($url, html_entity_decode($res, ENT_COMPAT, 'UTF-8')); + if ($new !== $url) + break; + } + } + if ($new === $url) + break; + $url = $new; + continue; + } + break; + } + return true; + } + + /** + * Make relative url absolute. + * + * @param string $absolute absolute url to use as base + * @param string $relative relative url that will be converted to an absolute url + * @return string combined absolute url + */ + private function internetCombineUrl($absolute, $relative) + { + $p = parse_url($relative); + if (!empty($p["scheme"])) + return $relative; + + extract(parse_url($absolute)); + + $path = dirname($path); + + if ($relative{0} === '/') { + if ($relative{1} === '/') + return "$scheme:$relative"; + $cparts = array_filter(explode("/", $relative)); + } else { + $aparts = array_filter(explode("/", $path)); + $rparts = array_filter(explode("/", $relative)); + $cparts = array_merge($aparts, $rparts); + foreach ($cparts as $i => $part) { + if ($part == '.') { + $cparts[$i] = null; + } + if ($part == '..') { + $cparts[$i - 1] = null; + $cparts[$i] = null; + } + } + $cparts = array_filter($cparts); + } + $path = implode("/", $cparts); + $url = ""; + if ($scheme) + $url = "$scheme://"; + if (!empty($user)) { + $url .= "$user"; + if (!empty($pass)) { + $url .= ":$pass"; + } + $url .= "@"; + } + if ($host) + $url .= "$host/"; + $url .= $path; + return $url; + } + +} + +class Branding_Finish extends AddModule_Base +{ + + protected function preprocessInternal() + { + $title = Request::post('title'); + if ($title === false || empty($title)) { + Message::addError('missing-file'); // TODO: Ask for title again instead of starting over + Util::redirect('?do=SysConfig&action=addmodule&step=Branding_Start'); + } + $tgz = Session::get('logo_tgz'); + if ($tgz === false || !file_exists($tgz)) { + Message::addError('missing-file'); + Util::redirect('?do=SysConfig&action=addmodule&step=Branding_Start'); + } + if (!ConfigModule::insertBrandingModule($title, $tgz)) + Util::redirect('?do=SysConfig&action=addmodule&step=Branding_Start'); + Session::set('logo_tgz', false); + Session::save(); + // Yay + Message::addSuccess('module-added'); + Util::redirect('?do=SysConfig'); + } + +} diff --git a/templates/serversetup/ipaddress.html b/templates/serversetup/ipaddress.html index 0352ab90..acfbb408 100644 --- a/templates/serversetup/ipaddress.html +++ b/templates/serversetup/ipaddress.html @@ -10,7 +10,7 @@ <form method="post" action="?do=ServerSetup"> <input type="hidden" name="action" value="ip"> <input type="hidden" name="token" value="{{token}}"> - <table> + <table class="slx-table"> {{#ips}} <tr> <td>{{ip}}</td> diff --git a/templates/serversetup/ipxe.html b/templates/serversetup/ipxe.html index dddc0ecc..772777d1 100644 --- a/templates/serversetup/ipxe.html +++ b/templates/serversetup/ipxe.html @@ -31,11 +31,8 @@ <strong>{{lang_menuCustom}}</strong> <a class="btn btn-default btn-xs" data-toggle="modal" data-target="#help-custom"><span class="glyphicon glyphicon-question-sign"></span></a> <textarea class="form-control" name="custom">{{custom}}</textarea> </div> - - - <br> - <div data-tm-id="{{taskid}}" data-tm-log="error"> Status</div> </div> + <div class="panel-footer"> <button class="btn btn-primary" name="action" value="ipxe">{{lang_bootMenuCreate}}</button> </div> diff --git a/templates/page-sysconfig-main.html b/templates/sysconfig/_page.html index 086ea9af..086ea9af 100644 --- a/templates/page-sysconfig-main.html +++ b/templates/sysconfig/_page.html diff --git a/templates/sysconfig/branding-check.html b/templates/sysconfig/branding-check.html new file mode 100644 index 00000000..93be4f2d --- /dev/null +++ b/templates/sysconfig/branding-check.html @@ -0,0 +1,29 @@ +<p> + Unten sehen Sie zur Kontrolle noch einmal das ausgewählte Logo. Sollten Sie das Logo + nicht sehen können, prüfen Sie bitte, ob Sie ein valides SVG-Bild verwendet haben. + Alternativ ist es möglich, dass beim Verarbeiten des Bildes ein Fehler auftrat. Sie + können daher das Modul trotzdem speichern und testen, ob das Logo im bwLehrpool-System + angezeigt wird. +</p> +<div class="pull-left"> + {{#svg}} + <img src="data:image/svg+xml;base64,{{svg}}" width="192" height="192"> + {{/svg}} +</div> +<div class="pull-right"> + {{#png}} + <img src="data:image/png;base64,{{png}}"> + {{/png}} +</div> +<div class="clearfix"></div> +<div>{{error}}</div> +<div> + <form role="form" enctype="multipart/form-data" method="post" action="?do=SysConfig&action=addmodule&step={{step}}"> + <input type="hidden" name="token" value="{{token}}"> + <div class="form-group"> + <label for="title-id">Titel</label> + <input type="text" name="title" id ="title-id" class="form-control" placeholder="Name des Moduls"> + </div> + <button type="submit" class="btn btn-primary">Speichern</button> + </form> +</div> diff --git a/templates/sysconfig/branding-start.html b/templates/sysconfig/branding-start.html new file mode 100644 index 00000000..d26a5dee --- /dev/null +++ b/templates/sysconfig/branding-start.html @@ -0,0 +1,19 @@ +<p> + Für beste Ergebnisse sollten Sie ihr Einrichtungslogo im SVG Format hochladen. + das SVG-Format ist ein Vektorgrafikformat, was zum Skalieren vorteilhaft ist. + Eine Gute Quelle für SVG-Logos von Unis und Hochschulen ist ihr jeweiliger Wikipedia-Artikel. +</p> +<form role="form" enctype="multipart/form-data" method="post" action="?do=SysConfig&action=addmodule&step={{step}}"> + <input type="hidden" name="token" value="{{token}}"> + <div class="form-group"> + <label for="input-url">Bild von URL laden</label> + <input class="form-control" type="text" name="url" id="input-url"> + </div> + oder + <div class="form-group"> + <label for="input-file">Bild von lokalem Rechner hochladen</label> + <input class="form-control" type="file" name="file" id="input-file"> + </div> + <button type="submit" class="btn btn-primary">Hochladen</button> +</form> + |