diff options
author | Simon Rettberg | 2014-06-27 21:15:35 +0200 |
---|---|---|
committer | Simon Rettberg | 2014-06-27 21:15:35 +0200 |
commit | 16aacada0f64240b7ec35026f0e207b7d0fd37df (patch) | |
tree | 26f4b434cd8eb7d3cd0c8eaba6cff6d096213761 | |
parent | Added doxygen comments to Taskmanager class (diff) | |
download | slx-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.php | 34 | ||||
-rw-r--r-- | inc/download.inc.php | 93 | ||||
-rw-r--r-- | inc/util.inc.php | 99 | ||||
-rw-r--r-- | modules/sysconfig/addmodule_branding.inc.php | 213 | ||||
-rw-r--r-- | templates/sysconfig/branding-check.html | 29 | ||||
-rw-r--r-- | templates/sysconfig/branding-start.html | 19 |
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&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> + |