summaryrefslogtreecommitdiffstats
path: root/inc
diff options
context:
space:
mode:
Diffstat (limited to 'inc')
-rw-r--r--inc/database.inc.php128
-rw-r--r--inc/message.inc.php119
-rw-r--r--inc/render.inc.php221
-rw-r--r--inc/request.inc.php45
-rw-r--r--inc/rpc.inc.php59
-rw-r--r--inc/session.inc.php99
-rw-r--r--inc/user.inc.php176
-rw-r--r--inc/util.inc.php289
8 files changed, 1136 insertions, 0 deletions
diff --git a/inc/database.inc.php b/inc/database.inc.php
new file mode 100644
index 0000000..efc330f
--- /dev/null
+++ b/inc/database.inc.php
@@ -0,0 +1,128 @@
+<?php
+
+/**
+ * Handle communication with the database
+ * This is a very thin layer between you and PDO.
+ */
+class Database
+{
+
+ /**
+ *
+ * @var \PDO Database handle
+ */
+ private static $dbh = false;
+ private static $statements = array();
+
+ /**
+ * Get database schema version - used for checking for updates
+ * @return int Version of db schema
+ */
+ public static function getExpectedSchemaVersion()
+ {
+ return 9;
+ }
+
+ public static function needSchemaUpdate()
+ {
+ return Property::getCurrentSchemaVersion() < self::getExpectedSchemaVersion();
+ }
+
+ /**
+ * Connect to the DB if not already connected.
+ */
+ private static function init()
+ {
+ if (self::$dbh !== false)
+ return;
+ try {
+ if (CONFIG_SQL_FORCE_UTF8)
+ self::$dbh = new PDO(CONFIG_SQL_DSN, CONFIG_SQL_USER, CONFIG_SQL_PASS, array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"));
+ else
+ self::$dbh = new PDO(CONFIG_SQL_DSN, CONFIG_SQL_USER, CONFIG_SQL_PASS);
+ } catch (PDOException $e) {
+ Util::traceError('Connecting to the local database failed: ' . $e->getMessage());
+ }
+ }
+
+ /**
+ * If you just need the first row of a query you can use this.
+ *
+ * @return array|boolean Associative array representing row, or false if no row matches the query
+ */
+ public static function queryFirst($query, $args = array(), $ignoreError = false)
+ {
+ $res = self::simpleQuery($query, $args, $ignoreError);
+ if ($res === false)
+ return false;
+ return $res->fetch(PDO::FETCH_ASSOC);
+ }
+
+
+ /**
+ * Execute the given query and return the number of rows affected.
+ * Mostly useful for UPDATEs or INSERTs
+ *
+ * @param string $query Query to run
+ * @param array $args Arguments to query
+ * @param boolean $ignoreError Ignore query errors and just return false
+ * @return int|boolean Number of rows affected, or false on error
+ */
+ public static function exec($query, $args = array(), $ignoreError = false)
+ {
+ $res = self::simpleQuery($query, $args, $ignoreError);
+ if ($res === false)
+ return false;
+ return $res->rowCount();
+ }
+
+ /**
+ * Get id (promary key) of last row inserted.
+ *
+ * @return int the id
+ */
+ public static function lastInsertId()
+ {
+ return self::$dbh->lastInsertId();
+ }
+
+ /**
+ * Execute the given query and return the corresponding PDOStatement object
+ * Note that this will re-use PDOStatements, so if you run the same
+ * query again with different params, do not rely on the first PDOStatement
+ * still being valid. If you need to do something fancy, use Database::prepare
+ * @return \PDOStatement The query result object
+ */
+ public static function simpleQuery($query, $args = array(), $ignoreError = false)
+ {
+ self::init();
+ try {
+ if (!isset(self::$statements[$query])) {
+ self::$statements[$query] = self::$dbh->prepare($query);
+ } else {
+ self::$statements[$query]->closeCursor();
+ }
+ if (self::$statements[$query]->execute($args) === false) {
+ if ($ignoreError)
+ return false;
+ Util::traceError("Database Error: \n" . implode("\n", self::$statements[$query]->errorInfo()));
+ }
+ return self::$statements[$query];
+ } catch (Exception $e) {
+ if ($ignoreError)
+ return false;
+ Util::traceError("Database Error: \n" . $e->getMessage());
+ }
+ }
+
+ /**
+ * Simply calls PDO::prepare and returns the PDOStatement.
+ * You must call PDOStatement::execute manually on it.
+ */
+ public static function prepare($query)
+ {
+ self:init();
+ return self::$dbh->prepare($query);
+ }
+
+}
diff --git a/inc/message.inc.php b/inc/message.inc.php
new file mode 100644
index 0000000..7decc12
--- /dev/null
+++ b/inc/message.inc.php
@@ -0,0 +1,119 @@
+<?php
+
+class Message
+{
+ private static $list = array();
+ private static $alreadyDisplayed = array();
+ private static $flushed = false;
+
+ /**
+ * Add error message to page. If messages have not been flushed
+ * yet, it will be added to the queue, otherwise it will be added
+ * in place during rendering.
+ */
+ public static function addError($id)
+ {
+ self::add('error', $id, func_get_args());
+ }
+
+ public static function addWarning($id)
+ {
+ self::add('warning', $id, func_get_args());
+ }
+
+ public static function addInfo($id)
+ {
+ self::add('info', $id, func_get_args());
+ }
+
+ public static function addSuccess($id)
+ {
+ self::add('success', $id, func_get_args());
+ }
+
+ /**
+ * Internal function that adds a message. Used by
+ * addError/Success/Info/... above.
+ */
+ private static function add($type, $id, $params)
+ {
+ self::$list[] = array(
+ 'type' => $type,
+ 'id' => $id,
+ 'params' => array_slice($params, 1)
+ );
+ if (self::$flushed) self::renderList();
+ }
+
+ /**
+ * Render all currently queued messages, flushing the queue.
+ * After calling this, any further calls to add* will be rendered in
+ * place in the current page output.
+ */
+ public static function renderList()
+ {
+ // Non-Ajax
+ foreach (self::$list as $item) {
+ $message = $item['id']; // Dictionary::getMessage($item['id']);
+ foreach ($item['params'] as $index => $text) {
+ $message = str_replace('{{' . $index . '}}', '<b>' . htmlspecialchars($text) . '</b>', $message);
+ }
+ Render::addTemplate('messagebox-' . $item['type'], array('message' => $message));
+ self::$alreadyDisplayed[] = $item;
+ }
+ self::$list = array();
+ self::$flushed = true;
+ }
+
+ /**
+ * Get all queued messages, flushing the queue.
+ * Useful in api/ajax mode.
+ */
+ public static function asString()
+ {
+ $return = '';
+ foreach (self::$list as $item) {
+ $message = $item['id']; // Dictionary::getMessage($item['id']);
+ foreach ($item['params'] as $index => $text) {
+ $message = str_replace('{{' . $index . '}}', $text, $message);
+ }
+ $return .= '[' . $item['type'] . ']: ' . $message . "\n";
+ self::$alreadyDisplayed[] = $item;
+ }
+ self::$list = array();
+ return $return;
+ }
+
+ /**
+ * Deserialize any messages from the current HTTP request and
+ * place them in the message queue.
+ */
+ public static function fromRequest()
+ {
+ $messages = is_array($_REQUEST['message']) ? $_REQUEST['message'] : array($_REQUEST['message']);
+ foreach ($messages as $message) {
+ $data = explode('|', $message);
+ if (count($data) < 2 || !preg_match('/^(error|warning|info|success)$/', $data[0])) continue;
+ self::add($data[0], $data[1], array_slice($data, 1));
+ }
+ }
+
+ /**
+ * Turn the current message queue into a serialized version,
+ * suitable for appending to a GET or POST request
+ */
+ public static function toRequest()
+ {
+ $parts = array();
+ foreach (array_merge(self::$list, self::$alreadyDisplayed) as $item) {
+ $str = 'message[]=' . urlencode($item['type'] . '|' .$item['id']);
+ if (!empty($item['params'])) {
+ $str .= '|' . implode('|', $item['params']);
+ }
+ $parts[] = $str;
+ }
+ return implode('&', $parts);
+ }
+
+}
+
diff --git a/inc/render.inc.php b/inc/render.inc.php
new file mode 100644
index 0000000..0147709
--- /dev/null
+++ b/inc/render.inc.php
@@ -0,0 +1,221 @@
+<?php
+
+define('RENDER_DEFAULT_TITLE', 'bwLehrpool');
+
+
+require_once('Mustache/Autoloader.php');
+Mustache_Autoloader::register();
+
+/**
+ * HTML rendering helper class
+ */
+Render::init();
+
+class Render
+{
+
+ private static $mustache = false;
+ private static $body = '';
+ private static $header = '';
+ private static $footer = '';
+ private static $title = '';
+ private static $templateCache = array();
+ private static $tags = array();
+
+ public static function init()
+ {
+ if (self::$mustache !== false)
+ Util::traceError('Called Render::init() twice!');
+ self::$mustache = new Mustache_Engine;
+ }
+
+ /**
+ * Output the buffered, generated page
+ */
+ public static function output()
+ {
+ Header('Content-Type: text/html; charset=utf-8');
+ $zip = isset($_SERVER['HTTP_ACCEPT_ENCODING']) && (strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== false);
+ if ($zip)
+ ob_start();
+ echo
+ '<!DOCTYPE html>
+ <html>
+ <head>
+ <title>', RENDER_DEFAULT_TITLE, self::$title, '</title>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <!-- Bootstrap -->
+ <link href="', CONFIG_PREFIX, 'style/bootstrap.min.css" rel="stylesheet" media="screen">
+ <link href="', CONFIG_PREFIX, 'style/default.css" rel="stylesheet" media="screen">
+ <script type="text/javascript">
+ var TOKEN = "', Session::get('token'), '";
+ </script>
+ ',
+ self::$header
+ ,
+ ' </head>
+ <body>
+ <div class="container" id="mainpage">
+ ',
+ self::$body
+ ,
+ ' </div>
+ <script src="', CONFIG_PREFIX, 'script/jquery.js"></script>
+ <script src="', CONFIG_PREFIX, 'script/bootstrap.min.js"></script>
+ ',
+ self::$footer
+ ,
+ '</body>
+ </html>'
+ ;
+ if ($zip) {
+ Header('Content-Encoding: gzip');
+ ob_implicit_flush(false);
+ $gzip_contents = ob_get_contents();
+ ob_end_clean();
+ echo "\x1f\x8b\x08\x00\x00\x00\x00\x00";
+ echo substr(gzcompress($gzip_contents, 5), 0, -4);
+ }
+ }
+
+ /**
+ * Set the page title (title-tag)
+ */
+ public static function setTitle($title)
+ {
+ self::$title = ' - ' . $title;
+ }
+
+ /**
+ * Add raw html data to the header-section of the generated page
+ */
+ public static function addHeader($html)
+ {
+ self::$header .= $html . "\n";
+ }
+
+ /**
+ * Add raw html data to the footer-section of the generated page (right before the closing body tag)
+ */
+ public static function addFooter($html)
+ {
+ self::$footer .= $html . "\n";
+ }
+
+ /**
+ * Add given js script file from the script directory to the header
+ *
+ * @param string $file file name of script
+ */
+ public static function addScriptTop($file)
+ {
+ self::addHeader('<script src="', CONFIG_PREFIX, 'script/' . $file . '.js"></script>');
+ }
+
+ /**
+ * Add given js script file from the script directory to the bottom
+ *
+ * @param string $file file name of script
+ */
+ public static function addScriptBottom($file)
+ {
+ self::addFooter('<script src="', CONFIG_PREFIX, 'script/' . $file . '.js"></script>');
+ }
+
+ /**
+ * Add the given template to the output, using the given params for placeholders in the template
+ */
+ public static function addTemplate($template, $params = false)
+ {
+ self::$body .= self::parse($template, $params);
+ }
+
+ /**
+ * Add a dialog to the page output.
+ *
+ * @param string $title Title of the dialog window
+ * @param boolean $next URL to next dialog step, or false to hide the next button
+ * @param string $template template used to fill the dialog body
+ * @param array $params parameters for rendering the body template
+ */
+ public static function addDialog($title, $next, $template, $params = false)
+ {
+ self::addTemplate('dialog-generic', array(
+ 'title' => $title,
+ 'next' => $next,
+ 'body' => self::parse($template, $params)
+ ));
+ }
+
+ /**
+ * Add error message to page
+ */
+ public static function addError($message)
+ {
+ self::addTemplate('messagebox-error', array('message' => $message));
+ }
+
+ /**
+ * Parse template with given params and return; do not add to body
+ * @param string $template name of template, relative to templates/, without .html extension
+ * @return string Rendered template
+ */
+ public static function parse($template, $params = false)
+ {
+ // Load html snippet
+ $html = self::getTemplate($template);
+ // Always add token to parameter list
+ if (is_array($params) || $params === false || is_null($params))
+ $params['token'] = Session::get('token');
+ // Return rendered html
+ return self::$mustache->render($html, $params);
+ }
+
+ /**
+ * Open the given html tag, optionally adding the passed assoc array of params
+ */
+ public static function openTag($tag, $params = false)
+ {
+ array_push(self::$tags, $tag);
+ if (!is_array($params)) {
+ self::$body .= '<' . $tag . '>';
+ } else {
+ self::$body .= '<' . $tag;
+ foreach ($params as $key => $val) {
+ self::$body .= ' ' . $key . '="' . htmlspecialchars($val) . '"';
+ }
+ self::$body .= '>';
+ }
+ }
+
+ /**
+ * Close the given tag. Will check if it maches the tag last opened
+ */
+ public static function closeTag($tag)
+ {
+ if (empty(self::$tags))
+ Util::traceError('Tried to close tag ' . $tag . ' when no open tags exist.');
+ $last = array_pop(self::$tags);
+ if ($last !== $tag)
+ Util::traceError('Tried to close tag ' . $tag . ' when last opened tag was ' . $last);
+ self::$body .= '</' . $tag . '>';
+ }
+
+ /**
+ * Private helper: Load the given template and return it
+ */
+ private static function getTemplate($template)
+ {
+ if (isset(self::$templateCache[$template])) {
+ return self::$templateCache[$template];
+ }
+ // Load from disk
+ $data = @file_get_contents('templates/' . $template . '.html');
+ if ($data === false)
+ $data = '<b>Non-existent template ' . $template . ' requested!</b>';
+ self::$templateCache[$template] = & $data;
+ return $data;
+ }
+
+}
diff --git a/inc/request.inc.php b/inc/request.inc.php
new file mode 100644
index 0000000..bb212df
--- /dev/null
+++ b/inc/request.inc.php
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * Wrapper for getting fields from the request (GET, POST, ...)
+ */
+class Request
+{
+
+ /**
+ *
+ * @param string $key Key of field to get from $_GET
+ * @param string $default Value to return if $_GET does not contain $key
+ * @return mixed Field from $_GET, or $default if not set
+ */
+ public static function get($key, $default = false)
+ {
+ if (!isset($_GET[$key])) return $default;
+ return $_GET[$key];
+ }
+
+ /**
+ *
+ * @param string $key Key of field to get from $_POST
+ * @param string $default Value to return if $_POST does not contain $key
+ * @return mixed Field from $_POST, or $default if not set
+ */
+ public static function post($key, $default = false)
+ {
+ if (!isset($_POST[$key])) return $default;
+ return $_POST[$key];
+ }
+
+ /**
+ *
+ * @param string $key Key of field to get from $_REQUEST
+ * @param string $default Value to return if $_REQUEST does not contain $key
+ * @return mixed Field from $_REQUEST, or $default if not set
+ */
+ public static function any($key, $default = false)
+ {
+ if (!isset($_REQUEST[$key])) return $default;
+ return $_REQUEST[$key];
+ }
+
+}
diff --git a/inc/rpc.inc.php b/inc/rpc.inc.php
new file mode 100644
index 0000000..83029e4
--- /dev/null
+++ b/inc/rpc.inc.php
@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * Interface to the external rpc.
+ */
+class RPC
+{
+
+ /**
+ * UDP socket used for communication with the rpc
+ * @var resource
+ */
+ private static $sock = false;
+
+ private static function init()
+ {
+ if (self::$sock !== false)
+ return;
+ self::$sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
+ socket_set_option(self::$sock, SOL_SOCKET, SO_RCVTIMEO, array('sec' => 0, 'usec' => 300000));
+ socket_set_option(self::$sock, SOL_SOCKET, SO_SNDTIMEO, array('sec' => 1, 'usec' => 0));
+ socket_connect(self::$sock, '127.0.0.1', 1333);
+ }
+
+ public static function submit($data)
+ {
+ self::init();
+ if (empty($data)) {
+ $data = '{}';
+ } else {
+ $data = json_encode($data);
+ }
+ $sent = socket_send(self::$sock, $data, strlen($data), 0);
+ if ($sent != strlen($data)) {
+ return 'RPC send error';
+ }
+ $reply = self::readReply();
+ if ($reply === false) {
+ return 'RPC receive error';
+ }
+ return $reply;
+ }
+
+ /**
+ * Read reply from socket.
+ *
+ * @return mixed read reply as astring, or error message
+ */
+ private static function readReply()
+ {
+ for ($i = 0; $i < 3; ++$i) {
+ $bytes = socket_recvfrom(self::$sock, $buf, 90000, 0, $bla1, $bla2);
+ if ($bytes !== false)
+ return $buf;
+ }
+ return false;
+ }
+
+}
diff --git a/inc/session.inc.php b/inc/session.inc.php
new file mode 100644
index 0000000..b9adfcb
--- /dev/null
+++ b/inc/session.inc.php
@@ -0,0 +1,99 @@
+<?php
+
+
+class Session
+{
+ private static $sid = false;
+ private static $uid = false;
+ private static $data = false;
+
+ private static function generateSessionId()
+ {
+ if (self::$sid !== false) Util::traceError('Error: Asked to generate session id when already set.');
+ self::$sid = sha1(
+ mt_rand(0, 65535)
+ . $_SERVER['REMOTE_ADDR']
+ . mt_rand(0, 65535)
+ . $_SERVER['REMOTE_PORT']
+ . mt_rand(0, 65535)
+ . $_SERVER['HTTP_USER_AGENT']
+ . mt_rand(0, 65535)
+ . microtime(true)
+ . mt_rand(0, 65535)
+ );
+ }
+
+ public static function create()
+ {
+ self::generateSessionId();
+ self::$uid = 0;
+ self::$data = array();
+ }
+
+ public static function load()
+ {
+ // Try to load session id from cookie
+ if (!self::loadSessionId()) return false;
+ // Succeded, now try to load session data. If successful, job is done
+ if (self::readSessionData()) return true;
+ // Loading session data failed
+ self::delete();
+ }
+
+ public static function getUid()
+ {
+ return self::$uid;
+ }
+
+ public static function setUid($value)
+ {
+ if (self::$uid === false)
+ Util::traceError('Tried to set session data with no active session');
+ if (!is_numeric($value) || $value < 1)
+ Util::traceError('Invalid user id: ' . $value);
+ self::$uid = $value;
+ }
+
+ public static function get($key)
+ {
+ if (isset(self::$data[$key]))
+ return self::$data[$key];
+ return false;
+ }
+
+ private static function loadSessionId()
+ {
+ if (self::$sid !== false)
+ die('Error: Asked to load session id when already set.');
+ if (empty($_COOKIE['sid']))
+ return false;
+ $id = preg_replace('/[^a-zA-Z0-9]/', '', $_COOKIE['sid']);
+ if (empty($id))
+ return false;
+ self::$sid = $id;
+ return true;
+ }
+
+ public static function delete()
+ {
+ if (self::$sid === false) return;
+ Database::exec('DELETE FROM websession WHERE sid = :sid', array('sid' => self::$sid));
+ @setcookie('sid', '', time() - 8640000, null, null, !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off', true);
+ self::$sid = false;
+ self::$uid = false;
+ }
+
+ public static function save()
+ {
+ if (self::$sid === false || self::$uid === false || self::$uid === 0)
+ return;
+ $ret = Database::exec('INSERT INTO websession (sid, userid, dateline) '
+ . ' VALUES (:sid, :uid, UNIX_TIMESTAMP()) '
+ . ' ON DUPLICATE KEY UPDATE userid = VALUES(userid), dateline = VALUES(dateline)',
+ array('sid' => self::$sid, 'uid' => self::$uid));
+ if (!$ret) Util::traceError('Storing session data in dahdähbank failed.');
+ $ret = @setcookie('sid', self::$sid, time() + CONFIG_SESSION_TIMEOUT, null, null, !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off', true);
+ if (!$ret) Util::traceError('Error: Could not set Cookie for Client (headers already sent)');
+ }
+}
+
diff --git a/inc/user.inc.php b/inc/user.inc.php
new file mode 100644
index 0000000..30630d4
--- /dev/null
+++ b/inc/user.inc.php
@@ -0,0 +1,176 @@
+<?php
+
+class User
+{
+
+ private static $user = false;
+ private static $organization = NULL;
+ private static $isShib = false;
+ private static $isInDb = false;
+
+ public static function isLoggedIn()
+ {
+ return self::$user !== false;
+ }
+
+ public static function isShibbolethAuth()
+ {
+ return self::$isShib;
+ }
+
+ public static function isInDatabase()
+ {
+ return self::$isInDb;
+ }
+
+ public static function isLocalOnly()
+ {
+ return self::$user !== false && self::$isShib === false;
+ }
+
+ public static function getName()
+ {
+ if (!self::isLoggedIn())
+ return false;
+ return self::$user['firstname'] . ' ' . self::$user['lastname'];
+ }
+
+ public static function getLastName()
+ {
+ if (!self::isLoggedIn())
+ return false;
+ return self::$user['lastname'];
+ }
+
+ public static function hasFullName()
+ {
+ return self::$user !== false && !empty(self::$user['firstname']) && !empty(self::$user['lastname']);
+ }
+
+ public static function isTutor()
+ {
+ return isset(self::$user['role']) && self::$user['role'] === 'tutor';
+ }
+
+ public static function getOrganizationId()
+ {
+ $org = self::getOrganization();
+ if (!isset($org['organizationid']))
+ return false;
+ return $org['organizationid'];
+ }
+
+ public static function getRemoteOrganizationId()
+ {
+ if (empty(self::$user['organization']))
+ return false;
+ return self::$user['organization'];
+ }
+
+ public static function getOrganization()
+ {
+ if (!self::isLoggedIn())
+ return false;
+ if (is_null(self::$organization)) {
+ self::$organization = Database::queryFirst('SELECT organizationid, name FROM satellite_suffix '
+ . ' INNER JOIN satellite USING (organizationid) '
+ . ' WHERE suffix = :org LIMIT 1',
+ array('org' => self::$user['organization']));
+ }
+ return self::$organization;
+ }
+
+ public static function load()
+ {
+ if (self::isLoggedIn())
+ return true;
+ Session::load();
+ if (empty($_SERVER['persistent-id'])) {
+ if (Session::getUid() === false)
+ return false;
+ // Try user from local DB
+ self::$user = Database::queryFirst('SELECT userid, shibid, login, firstname, lastname, email FROM user WHERE userid = :uid LIMIT 1', array('uid' => Session::getUid()));
+ return self::$user !== false;
+ }
+ // Try bwIDM etc.
+ self::$isShib = true;
+ if (!isset($_SERVER['sn'])) $_SERVER['sn'] = '';
+ if (!isset($_SERVER['givenName'])) $_SERVER['givenName'] = '';
+ if (!isset($_SERVER['mail'])) $_SERVER['mail'] = '';
+ $shibId = md5($_SERVER['persistent-id']);
+ self::$user = array(
+ 'userid' => 0,
+ 'shibid' => $shibId,
+ 'login' => NULL,
+ 'firstname' => $_SERVER['givenName'],
+ 'lastname' => $_SERVER['sn'],
+ 'email' => $_SERVER['mail'],
+ );
+ // Figure out whether the user should be considered a tutor
+ if (isset($_SERVER['affiliation']) && preg_match('/(^|;)employee@/', $_SERVER['affiliation']))
+ self::$user['role'] = 'tutor';
+ elseif (isset($_SERVER['entitlement']) && strpos(";{$_SERVER['entitlement']};", ';http://bwidm.de/entitlement/bwLehrpool;') !== false)
+ self::$user['role'] = 'tutor';
+ // Try to figure out organization
+ if (isset($_SERVER['affiliation']) && preg_match('/@([a-zA-Z\-\._]+)(;|$)/', $_SERVER['affiliation'], $out))
+ self::$user['organization'] = $out[1];
+ // Get matching db entry if any
+ $user = Database::queryFirst('SELECT userid, login, firstname, lastname, email, fixedname FROM user WHERE shibid = :shibid LIMIT 1', array('shibid' => $shibId));
+ if ($user === false) {
+ // No match in database, user is not signed up
+ return true;
+ }
+ // Already signed up, see if we can fetch missing fields from DB
+ self::$user['login'] = $user['login'];
+ self::$isInDb = true;
+ foreach (array('firstname', 'lastname', 'email') as $key) {
+ if (empty(self::$user[$key]))
+ self::$user[$key] = $user[$key];
+ }
+ return true;
+ }
+
+ public static function deploy($anonymous)
+ {
+ if (empty(self::$user['shibid']))
+ Util::traceError('NO SHIBID');
+ if ($anonymous) {
+ Database::exec("INSERT INTO user (shibid, login, organizationid, firstname, lastname, email) "
+ . " VALUES (:shibid, :shibid, :org, '', '', '')", array(
+ 'shibid' => self::$user['shibid'],
+ 'org' => self::getOrganizationId()
+ ));
+ } else {
+ Database::exec("INSERT INTO user (shibid, login, organizationid, firstname, lastname, email) "
+ . " VALUES (:shibid, :shibid, :org, :firstname, :lastname, :email)", array(
+ 'shibid' => self::$user['shibid'],
+ 'firstname' => self::$user['firstname'],
+ 'lastname' => self::$user['lastname'],
+ 'email' => self::$user['email'],
+ 'org' => self::getOrganizationId()
+ ));
+ }
+ }
+
+ public static function login($user, $pass)
+ {
+ $ret = Database::queryFirst('SELECT userid, password FROM user WHERE login = :user LIMIT 1', array(':user' => $user));
+ if ($ret === false)
+ return false;
+ if (!Crypto::verify($pass, $ret['passwd']))
+ return false;
+ Session::create();
+ Session::setUid($ret['userid']);
+ Session::set('token', md5(rand() . time() . mt_rand() . $_SERVER['REMOTE_ADDR'] . rand() . $_SERVER['REMOTE_PORT'] . rand() . $_SERVER['HTTP_USER_AGENT'] . microtime(true)));
+ Session::save();
+ return true;
+ }
+
+ public static function logout()
+ {
+ Session::delete();
+ Header('Location: ?do=Main&fromlogout');
+ exit(0);
+ }
+
+}
diff --git a/inc/util.inc.php b/inc/util.inc.php
new file mode 100644
index 0000000..4378a08
--- /dev/null
+++ b/inc/util.inc.php
@@ -0,0 +1,289 @@
+<?php
+
+class Util
+{
+ private static $redirectParams = array();
+
+ /**
+ * Displays an error message and stops script execution.
+ * If CONFIG_DEBUG is true, it will also dump a stack trace
+ * and all globally defined variables.
+ * (As this might reveal sensistive data you should never enable it in production)
+ */
+ public static function traceError($message)
+ {
+ Header('HTTP/1.1 500 Internal Server Error');
+ Header('Content-Type: text/plain; charset=utf-8');
+ echo "--------------------\nFlagrant system error:\n$message\n--------------------\n\n";
+ if (defined('CONFIG_DEBUG') && CONFIG_DEBUG) {
+ debug_print_backtrace();
+ echo "\n\nSome variables for your entertainment:\n";
+ print_r($GLOBALS);
+ } else {
+ echo <<<SADFACE
+
+________________________¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶________
+____________________¶¶¶___________________¶¶¶¶_____
+________________¶¶¶_________________________¶¶¶¶___
+______________¶¶______________________________¶¶¶__
+___________¶¶¶_________________________________¶¶¶_
+_________¶¶_____________________________________¶¶¶
+________¶¶_________¶¶¶¶¶___________¶¶¶¶¶_________¶¶
+______¶¶__________¶¶¶¶¶¶__________¶¶¶¶¶¶_________¶¶
+_____¶¶___________¶¶¶¶____________¶¶¶¶___________¶¶
+____¶¶___________________________________________¶¶
+___¶¶___________________________________________¶¶_
+__¶¶____________________¶¶¶¶____________________¶¶_
+_¶¶_______________¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶______________¶¶__
+_¶¶____________¶¶¶¶___________¶¶¶¶¶___________¶¶___
+¶¶¶_________¶¶¶__________________¶¶__________¶¶____
+¶¶_________¶______________________¶¶________¶¶_____
+¶¶¶______¶________________________¶¶_______¶¶______
+¶¶¶_____¶_________________________¶¶_____¶¶________
+_¶¶¶___________________________________¶¶__________
+__¶¶¶________________________________¶¶____________
+___¶¶¶____________________________¶¶_______________
+____¶¶¶¶______________________¶¶¶__________________
+_______¶¶¶¶¶_____________¶¶¶¶¶_____________________
+SADFACE;
+ }
+ exit(0);
+ }
+
+ /**
+ * Redirects the user via a '302 Moved' header.
+ * An active session will be saved, any messages that haven't
+ * been displayed yet will be appended to the redirect.
+ * @param string $location Location to redirect to. "false" to redirect to same URL (useful after POSTs)
+ */
+ public static function redirect($location = false)
+ {
+ if ($location === false) {
+ $location = preg_replace('/(&|\?)message\[\]\=[^&]*/', '\1', $_SERVER['REQUEST_URI']);
+ }
+ Session::save();
+ $messages = Message::toRequest();
+ if (!empty($messages)) {
+ if (strpos($location, '?') === false) {
+ $location .= '?' . $messages;
+ } else {
+ $location .= '&' . $messages;
+ }
+ }
+ if (!empty(self::$redirectParams)) {
+ if (strpos($location, '?') === false) {
+ $location .= '?' . implode('&', self::$redirectParams);
+ } else {
+ $location .= '&' . implode('&', self::$redirectParams);
+ }
+ }
+ Header('Location: ' . $location);
+ exit(0);
+ }
+
+ public static function addRedirectParam($key, $value)
+ {
+ self::$redirectParams[] = $key .= '=' . urlencode($value);
+ }
+
+ /**
+ * Verify the user's token that protects agains CSRF.
+ * If the user is logged in and there is no token variable set in
+ * the request, or the submitted token does not match the user's
+ * token, this function will return false and display an error.
+ * If the token matches, or the user is not logged in, it will return true.
+ */
+ public static function verifyToken()
+ {
+ if (Session::get('token') === false)
+ return true;
+ if (isset($_REQUEST['token']) && Session::get('token') === $_REQUEST['token'])
+ return true;
+ Message::addError('token');
+ return false;
+ }
+
+ /**
+ * Simple markup "rendering":
+ * *word* is bold
+ * /word/ is italics
+ * _word_ is underlined
+ * \n is line break
+ */
+ public static function markup($string)
+ {
+ $string = htmlspecialchars($string);
+ $string = preg_replace('#(^|[\n \-_/\.])\*(.+?)\*($|[ \-_/\.\!\?,:])#is', '$1<b>$2</b>$3', $string);
+ $string = preg_replace('#(^|[\n \-\*/\.])_(.+?)_($|[ \-\*/\.\!\?,:])#is', '$1<u>$2</u>$3', $string);
+ $string = preg_replace('#(^|[\n \-_\*\.])/(.+?)/($|[ \-_\*\.\!\?,:])#is', '$1<i>$2</i>$3', $string);
+ return nl2br($string);
+ }
+
+ /**
+ * Convert given number to human readable file size string.
+ * Will append Bytes, KiB, etc. depending on magnitude of number.
+ *
+ * @param type $bytes numeric value of the filesize to make readable
+ * @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)
+ {
+ static $sz = array('Byte', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB');
+ $factor = floor((strlen($bytes) - 1) / 3);
+ if ($factor == 0) {
+ $decimals = 0;
+ } elseif ($decimals === -1) {
+ $decimals = 2 - floor((strlen($bytes) - 1) % 3);
+ }
+ return sprintf("%.{$decimals}f ", $bytes / pow(1024, $factor)) . $sz[$factor];
+ }
+
+ public static function sanitizeFilename($name)
+ {
+ return preg_replace('/[^a-zA-Z0-9_\-]+/', '_', $name);
+ }
+
+ public static function safePath($path, $prefix = '')
+ {
+ if (empty($path))
+ return false;
+ $path = trim($path);
+ if ($path{0} == '/' || preg_match('/[\x00-\x19\?\*]/', $path))
+ return false;
+ if (strpos($path, '..') !== false)
+ return false;
+ if (substr($path, 0, 2) !== './')
+ $path = "./$path";
+ if (empty($prefix))
+ return $path;
+ if (substr($prefix, 0, 2) !== './')
+ $prefix = "./$prefix";
+ if (substr($path, 0, strlen($prefix)) !== $prefix)
+ return false;
+ return $path;
+ }
+
+ /**
+ * Create human readable error description from a $_FILES[<..>]['error'] code
+ *
+ * @param int $code the code to turn into an error description
+ * @return string the error description
+ */
+ public static function uploadErrorString($code)
+ {
+ switch ($code) {
+ case UPLOAD_ERR_INI_SIZE:
+ $message = "The uploaded file exceeds the upload_max_filesize directive in php.ini";
+ break;
+ case UPLOAD_ERR_FORM_SIZE:
+ $message = "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form";
+ break;
+ case UPLOAD_ERR_PARTIAL:
+ $message = "The uploaded file was only partially uploaded";
+ break;
+ case UPLOAD_ERR_NO_FILE:
+ $message = "No file was uploaded";
+ break;
+ case UPLOAD_ERR_NO_TMP_DIR:
+ $message = "Missing a temporary folder";
+ break;
+ case UPLOAD_ERR_CANT_WRITE:
+ $message = "Failed to write file to disk";
+ break;
+ case UPLOAD_ERR_EXTENSION:
+ $message = "File upload stopped by extension";
+ break;
+
+ default:
+ $message = "Unknown upload error";
+ break;
+ }
+ 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;
+ }
+
+ /**
+ * Check whether $arrax contains all keys given in $keyList
+ * @param array $array An array
+ * @param array $keyList A list of strings which must all be valid keys in $array
+ * @return boolean
+ */
+ public static function hasAllKeys($array, $keyList)
+ {
+ if (!is_array($array))
+ return false;
+ foreach ($keyList as $key) {
+ if (!isset($array[$key]))
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Send a file to user for download.
+ *
+ * @param type $file path of local file
+ * @param type $name name of file to send to user agent
+ * @param type $delete delete the file when done?
+ * @return boolean false: file could not be opened.
+ * true: error while reading the file
+ * - on success, the function does not return
+ */
+ public static function sendFile($file, $name, $delete = false)
+ {
+ while ((@ob_get_level()) > 0)
+ @ob_end_clean();
+ $fh = @fopen($file, 'rb');
+ if ($fh === false) {
+ Message::addError('error-read', $file);
+ return false;
+ }
+ Header('Content-Type: application/octet-stream', true);
+ Header('Content-Disposition: attachment; filename=' . str_replace(array(' ', '=', ',', '/', '\\', ':', '?'), '_', iconv('UTF-8', 'ASCII//TRANSLIT', $name)));
+ Header('Content-Length: ' . @filesize($file));
+ while (!feof($fh)) {
+ $data = fread($fh, 16000);
+ if ($data === false) {
+ echo "\r\n\nDOWNLOAD INTERRUPTED!\n";
+ if ($delete)
+ @unlink($file);
+ return true;
+ }
+ echo $data;
+ @ob_flush();
+ @flush();
+ }
+ @fclose($fh);
+ if ($delete)
+ @unlink($file);
+ exit(0);
+ }
+
+}