summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Rettberg2014-06-27 21:15:35 +0200
committerSimon Rettberg2014-06-27 21:15:35 +0200
commit16aacada0f64240b7ec35026f0e207b7d0fd37df (patch)
tree26f4b434cd8eb7d3cd0c8eaba6cff6d096213761
parentAdded doxygen comments to Taskmanager class (diff)
downloadslx-admin-16aacada0f64240b7ec35026f0e207b7d0fd37df.tar.gz
slx-admin-16aacada0f64240b7ec35026f0e207b7d0fd37df.tar.xz
slx-admin-16aacada0f64240b7ec35026f0e207b7d0fd37df.zip
New SysConfig module for adding a logo
-rw-r--r--inc/configmodule.inc.php34
-rw-r--r--inc/download.inc.php93
-rw-r--r--inc/util.inc.php99
-rw-r--r--modules/sysconfig/addmodule_branding.inc.php213
-rw-r--r--templates/sysconfig/branding-check.html29
-rw-r--r--templates/sysconfig/branding-start.html19
6 files changed, 402 insertions, 85 deletions
diff --git a/inc/configmodule.inc.php b/inc/configmodule.inc.php
index 5c789275..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,
+ '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/util.inc.php b/inc/util.inc.php
index ea28b4fa..45a6b684 100644
--- a/inc/util.inc.php
+++ b/inc/util.inc.php
@@ -79,88 +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.
*
@@ -249,5 +167,22 @@ class Util
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;
+ }
}
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/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&amp;action=addmodule&amp;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&amp;action=addmodule&amp;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>
+