summaryrefslogtreecommitdiff
path: root/apps
diff options
context:
space:
mode:
authorLuke Shumaker <LukeShu@sbcglobal.net>2012-01-07 08:21:00 -0800
committerLuke Shumaker <LukeShu@sbcglobal.net>2012-01-07 10:22:21 -0800
commit83e460cdc3fc09867a3adb48c3d0894579dd3050 (patch)
tree0771bd935b30971bf2c244b6f158ed7496b644e5 /apps
parent3d64793a1ee45857856be1cd71c3a0a040a3e869 (diff)
Refactor to separate the framework from the app; drop message stuff, this app is just user management. Add a json view for individual users
Diffstat (limited to 'apps')
-rw-r--r--apps/um/controllers/Authenticator.class.php57
-rw-r--r--apps/um/controllers/Config.class.php30
-rw-r--r--apps/um/controllers/Groups.class.php11
-rw-r--r--apps/um/controllers/Http404.class.php7
-rw-r--r--apps/um/controllers/Main.class.php9
-rw-r--r--apps/um/controllers/Plugins.class.php75
-rw-r--r--apps/um/controllers/Users.class.php371
-rw-r--r--apps/um/ext/PasswordHash.class.php253
-rw-r--r--apps/um/ext/README.txt12
-rw-r--r--apps/um/ext/recaptchalib.php277
-rw-r--r--apps/um/lib/DB.class.php164
-rw-r--r--apps/um/lib/Database.class.php403
-rw-r--r--apps/um/lib/Hasher.class.php18
-rw-r--r--apps/um/lib/Login.class.php41
-rw-r--r--apps/um/lib/Plugin.class.php25
-rw-r--r--apps/um/lib/PluginManager.class.php99
-rw-r--r--apps/um/lib/Singleton.class.php12
-rw-r--r--apps/um/lib/Site.class.php32
-rw-r--r--apps/um/models/Auth.class.php157
-rw-r--r--apps/um/models/ContactMethod.class.php29
-rw-r--r--apps/um/models/Group.class.php23
-rw-r--r--apps/um/models/User.class.php25
-rw-r--r--apps/um/plugins/InformationPlugin.class.php62
-rw-r--r--apps/um/plugins/ReCaptcha.class.php47
-rw-r--r--apps/um/views/Template.class.php316
-rw-r--r--apps/um/views/includes/header-include.php51
-rw-r--r--apps/um/views/includes/header.php84
-rw-r--r--apps/um/views/pages/auth/badrequest.html.php11
-rw-r--r--apps/um/views/pages/auth/index.html.php12
-rw-r--r--apps/um/views/pages/auth/login.html.php54
-rw-r--r--apps/um/views/pages/auth/logout.html.php6
-rw-r--r--apps/um/views/pages/groups/401.html.php15
-rw-r--r--apps/um/views/pages/http404.html.php16
-rw-r--r--apps/um/views/pages/index.html.php8
-rw-r--r--apps/um/views/pages/plugins/401.html.php9
-rw-r--r--apps/um/views/pages/plugins/index.html.php42
-rw-r--r--apps/um/views/pages/users/401.html.php15
-rw-r--r--apps/um/views/pages/users/404.html.php10
-rw-r--r--apps/um/views/pages/users/500.html.php15
-rw-r--r--apps/um/views/pages/users/created.html.php18
-rw-r--r--apps/um/views/pages/users/index.csv.php27
-rw-r--r--apps/um/views/pages/users/index.html.php104
-rw-r--r--apps/um/views/pages/users/individual.html.php147
-rw-r--r--apps/um/views/pages/users/individual.json.php27
-rw-r--r--apps/um/views/pages/users/new-locked.html.php9
-rw-r--r--apps/um/views/pages/users/new-logged-in.html.php8
-rw-r--r--apps/um/views/pages/users/new.html.php57
47 files changed, 3300 insertions, 0 deletions
diff --git a/apps/um/controllers/Authenticator.class.php b/apps/um/controllers/Authenticator.class.php
new file mode 100644
index 0000000..0c1bf0c
--- /dev/null
+++ b/apps/um/controllers/Authenticator.class.php
@@ -0,0 +1,57 @@
+<?php
+require_once('Login.class.php');
+require_once('Auth.class.php');
+
+Router::register('auth', 'Authenticator');
+
+class Authenticator extends Controller {
+ public function index($routed, $remainder) {
+ // If $_POST['action'] isn't set, it will trip on '', which is
+ // great, so we don't have to handle GET and PUT separately.
+ @$action = $_POST['action'];
+ switch ($action) {
+ case 'login' : $this->login(); break;
+ case 'logout': $this->logout(); break;
+ case '' : $this->maybe_login(); break;
+ default : $this->badrequest(); break;
+ }
+ }
+ private function login() {
+ $username = '';
+ $password = '';
+
+ $login = -1;
+ if ( isset($_POST['username']) && isset($_POST['password'])) {
+ $username = $_POST['username'];
+ $password = $_POST['password'];
+ $login = Login::login($username, $password);
+ }
+
+ $vars = array();
+ $vars['login_code'] = $login;
+ $vars['username'] = $username;
+ $vars['password'] = $password;
+ if (isset($_POST['url'])) {
+ $vars['url'] = $_POST['url'];
+ }
+
+ $this->showView('auth/login', $vars);
+ }
+ private function logout() {
+ Login::logout();
+ $this->showView('auth/logout');
+ }
+ private function maybe_login() {
+ $uid = Login::isLoggedIn();
+ if ($uid===false) {
+ $this->login();
+ } else {
+ $username = Auth::getInstance($uid)->getName();
+ $this->showView('auth/index',
+ array('username'=>$username));
+ }
+ }
+ private function badrequest() {
+ $this->showView('auth/badrequest');
+ }
+}
diff --git a/apps/um/controllers/Config.class.php b/apps/um/controllers/Config.class.php
new file mode 100644
index 0000000..dc6a884
--- /dev/null
+++ b/apps/um/controllers/Config.class.php
@@ -0,0 +1,30 @@
+<?php
+require_once('Auth.class.php');
+
+Router::register('config', 'Config', 'index');
+
+class Config extends Controller {
+ public function index($routed, $remainder) {
+ $uid = Login::isLoggedIn();
+ if ($uid===false || !Auth::getInstance($uid)->isAdmin()) {
+ $this->http401($routed, $remainder);
+ return;
+ }
+
+ $method = $_SERVER['REQUEST_METHOD'];
+ switch ($method) {
+ case 'PUT': $_POST = $_PUT;
+ case 'POST':
+ // We're PUTing an updated configuration.
+ $this->update();
+ break;
+ }
+ $this->show_index();
+ }
+ private function show_index() {
+
+ }
+ private function update() {
+
+ }
+}
diff --git a/apps/um/controllers/Groups.class.php b/apps/um/controllers/Groups.class.php
new file mode 100644
index 0000000..9d99d99
--- /dev/null
+++ b/apps/um/controllers/Groups.class.php
@@ -0,0 +1,11 @@
+<?php
+
+Router::register('groups/new' , 'Groups', 'new_group');
+Router::register('groups/index', 'Groups', 'index_file');
+Router::register('groups' , 'Groups', 'index_dir');
+Router::register('groups/*' , 'Groups', 'individual');
+
+class Groups extends Controller {
+ public static $illegal_names = array('', 'new', 'index');
+ // TODO
+}
diff --git a/apps/um/controllers/Http404.class.php b/apps/um/controllers/Http404.class.php
new file mode 100644
index 0000000..322feaa
--- /dev/null
+++ b/apps/um/controllers/Http404.class.php
@@ -0,0 +1,7 @@
+<?php
+
+class Http404 extends Controller {
+ public function index($routed, $remainder) {
+ $this->http404($routed, $remainder);
+ }
+}
diff --git a/apps/um/controllers/Main.class.php b/apps/um/controllers/Main.class.php
new file mode 100644
index 0000000..7651b62
--- /dev/null
+++ b/apps/um/controllers/Main.class.php
@@ -0,0 +1,9 @@
+<?php
+
+Router::register('index', 'Main', 'index');
+
+class Main extends Controller {
+ public function index($routed, $remainder) {
+ $this->showView('index');
+ }
+}
diff --git a/apps/um/controllers/Plugins.class.php b/apps/um/controllers/Plugins.class.php
new file mode 100644
index 0000000..2ed6e7a
--- /dev/null
+++ b/apps/um/controllers/Plugins.class.php
@@ -0,0 +1,75 @@
+<?php
+require_once('Login.class.php');
+require_once('Plugin.class.php');
+require_once('PluginManager.class.php');
+require_once('Auth.class.php');
+require_once('Database.class.php');
+
+Router::register('plugins', 'Plugins');
+
+class Plugins extends Controller {
+ public function index($routed, $remainder) {
+ $uid = Login::isLoggedIn();
+ if ($uid===false || !Auth::getInstance($uid)->isAdmin()) {
+ $this->http401($routed, $remainder);
+ return;
+ }
+
+ $method = $_SERVER['REQUEST_METHOD'];
+ switch ($method) {
+ case 'PUT': $_POST = $_PUT;
+ case 'POST':
+ // We're PUTing an updated user index.
+ $this->update();
+ break;
+ }
+ $this->show_index();
+ }
+
+ private function update() {
+ $db = Database::getInstance();
+
+ if (isset($_POST['plugins'])) {
+ $string = $db->arrayToValue($_POST['plugins']);
+ $db->setSysConf('plugins', $string);
+ }
+
+ if (isset($_POST['config'])) {
+ foreach ($_POST['config'] as $plugin_name => $plugin) {
+ foreach ($plugin as $param => $value) {
+ $db->setPluginConf($plugin_name,
+ $param,
+ $value);
+ }
+ }
+ }
+ }
+
+ private function show_index() {
+ $pm = PluginManager::getInstance();
+ $all_plugins = $pm->listPlugins();
+ $enabled_plugins = $pm->getActivePlugins();
+
+ $plugin_data = array();
+ foreach ($all_plugins as $plugin_name) {
+ $plugin = array();
+ $plugin['name'] = $plugin_name;
+ $plugin['key'] = 'config['.$plugin_name.']';
+ $plugin['active'] =
+ in_array($plugin_name, $enabled_plugins);
+ $plugin['description'] =
+ $pm->staticHook($plugin_name, 'description');
+ $plugin['config'] =
+ $pm->staticHook($plugin_name, 'configList');
+ $plugin_data[] = $plugin;
+ }
+
+ $vars = array();
+ $vars['plugins'] = $plugin_data;
+ $this->showView('plugins/index', $vars);
+ }
+
+ public function http401($routed, $remainder) {
+ $this->showView('plugins/401');
+ }
+}
diff --git a/apps/um/controllers/Users.class.php b/apps/um/controllers/Users.class.php
new file mode 100644
index 0000000..9978ef8
--- /dev/null
+++ b/apps/um/controllers/Users.class.php
@@ -0,0 +1,371 @@
+<?php
+require_once('Login.class.php');
+require_once('Auth.class.php');
+require_once('DB.class.php');
+require_once('PluginManager.class.php');
+require_once('Database.class.php');
+
+Router::register('users/new' , 'Users', 'new_user');
+Router::register('users/index', 'Users', 'index_file');
+Router::register('users' , 'Users', 'index_dir');
+Router::register('users/*' , 'Users', 'individual');
+
+class Users extends Controller {
+ // Index Views ///////////////////////////////////////////////
+
+ public function index($routed, $remainder) {
+ return $this->index_dir($routed, $remainder);
+ }
+
+ /**
+ * Handle POSTing a new user, or GETing the index.
+ */
+ public function index_dir($routed, $remainder) {
+ $method = $_SERVER['REQUEST_METHOD'];
+ switch ($method) {
+ case 'POST':
+ // We're POSTing a new user.
+ if ($this->registrationOpen()) {
+ $this->create_user();
+ } else {
+ $this->showView('users/new-locked', array());
+ exit();
+ }
+ break;
+ case 'HEAD': // fall-through to GET
+ case 'GET':
+ // We're GETing the index.
+ $this->show_index($routed, $remainder);
+ break;
+ }
+ }
+
+ /**
+ * Handle PUTing an updated user index, or GETing the index.
+ */
+ public function index_file($routed, $remainder) {
+ $method = $_SERVER['REQUEST_METHOD'];
+ switch ($method) {
+ case 'PUT': $_POST = $_PUT;
+ case 'POST':
+ // We're PUTing an updated user index.
+ $this->update_users();
+ break;
+ }
+ $this->show_index($routed, $remainder);
+ }
+
+ // Other Views ///////////////////////////////////////////////
+
+ /**
+ * Handle GETing the new user form.
+ *
+ * I would have named this `new', but that's a keyword.
+ */
+ public function new_user($routed, $vars) {
+ // since there will never be a remainder to `users/new', we can
+ // use that parameter to pass in some data.
+ if (Login::isLoggedIn()) {
+ $this->showView('users/new-logged-in', array());
+ exit();
+ }
+ if (!$this->registrationOpen()) {
+ $this->showView('users/new-locked', array());
+ exit();
+ }
+ if (!isset($vars['errors'])) $vars['errors'] = array();
+
+ $db = Database::getInstance();
+ $pm = PluginManager::getInstance();
+
+ $vars['antispam_html'] = $pm->callHook('antispam_html');
+ $vars['userlist'] = $db->getSysConf('anon_userlist');
+ $this->showView('users/new', $vars);
+ }
+
+ public function individual($routed, $remainder) {
+ $db = Database::getInstance();
+ $pm = PluginManager::getInstance();
+
+ $username = implode('/', $remainder);
+ if ($username == 'all') {
+ $uids = $db->listUsers();
+ } else {
+ $uids = array($db->getUID($username));
+ }
+
+ $vars = array();
+
+ if (count($uids)<2) {
+ $user = Auth::getInstance($uid);
+
+ if ($user->isGroup()) $uid = false; // ignore groups.
+
+ if ($uid===false) {
+ $this->http404($routed, $remainder);
+ exit();
+ }
+ if (!$user->canRead()) {
+ $this->http401($routed, $remainder);
+ exit();
+ }
+
+ $method = $_SERVER['REQUEST_METHOD'];
+ switch ($method) {
+ case 'PUT': $_POST = $_PUT;
+ case 'POST':
+ // We're PUTing updated user info.
+ if ($user->canEdit()) {
+ $vars = $this->update_user($user);
+ }
+ break;
+ }
+ }
+
+ $config_options = array();
+ $pm->callHook('userConfig', &$config_options);
+
+ $vars['users'] = array();
+ foreach ($uids as $uid) {
+ $vars['users'][] = Auth::getInstance($uid);
+ }
+ $vars['username'] = $username;
+ $vars['config_options'] = $config_options;
+ $vars['groups'] = $db->listGroupNames();
+ require_once('ContactMethod.class.php');
+ $this->showView('users/individual', $vars);
+ }
+
+ public function http404($routed, $remainder) {
+ $username = implode('/', $remainder);
+ $this->showView('users/404',
+ array('username'=>$username));
+ }
+
+ public function http401($routed, $remainder) {
+ $this->showView('users/401', array('uid'=>Login::isLoggedIn()));
+ }
+
+ // Other Functions ///////////////////////////////////////////
+
+ /**
+ * This will parse POST data to create a new user.
+ * If successfull it will show a message saying so.
+ * If not successfull, it will re-show the new-user form with errors
+ * explained.
+ */
+ private function create_user() {
+ $db = Database::getInstance();
+ $pm = PluginManager::getInstance();
+
+ $vars = array();
+ @$vars['username' ] = $_POST['auth_name'];
+ @$vars['password1'] = $_POST['auth_password' ];
+ @$vars['password2'] = $_POST['auth_password_verify'];
+ @$vars['email'] = $_POST['user_email'];
+
+ $vars['errors'] = array();
+ if ($db->getUID($vars['username'])!==false)
+ $vars['errors'][] = 'user exists';
+ if (!Auth::isNameLegal($vars['username']))
+ $vars['errors'][] = 'illegal name';
+ $matches = ($vars['password1'] == $vars['password2']);
+ if (!$matches) {
+ $vars['errors'][] = 'pw mixmatch';
+ }
+ if ($matches && $vars['password2'] == '') {
+ $vars['errors'][] = 'no pw';
+ }
+ if ($vars['email'] == '') {
+ $vars['errors'][] = 'no email';
+ }
+ foreach ($pm->callHook('antispam_verify') as $plugin=>$valid) {
+ if (!$valid) $vars['errors'][] = 'plugin_'.$plugin;
+ }
+
+ if (count($vars['errors']) > 0) {
+ $this->new_user($routed, $vars);
+ } else {
+ $username = $vars['username'];
+ $password = $vars['password1'];
+ $uid = $db->addUser($username, $password);
+ if ($uid===false) {
+ $this->showView('users/500');
+ } else {
+ Login::login($username, $password);
+ DB::set('users', $uid, 'email', $vars['email']);
+ $this->showView('users/created',
+ array('username'=>$username));
+ }
+ }
+ }
+
+ /**
+ * This will parse POST (really, PUT) data to update a single user
+ */
+ private function update_user($user) {
+ $vars = array();
+
+ $username = $user->getName();
+ // Change the username /////////////////////////////////////////
+ if (isset($_POST['auth_name'])) {
+ $new_name = $_POST['auth_name'];
+ if ($new_name != $username) {
+ $changed_name = $user->setName($new_name);
+ $username = $user->getName();
+ $vars['changed name'] = $changed_name;
+ }
+ }
+
+ // Change the password /////////////////////////////////////////
+ @$password1 = $_POST['auth_password' ];
+ @$password2 = $_POST['auth_password'.'_verify'];
+
+ // Check the verify box, not main box, so that we don't get
+ // tripped by browsers annoyingly autocompleting the password.
+ $is_set = ($password2 != '');
+
+ if ($is_set) {
+ $matches = ( $password1 == $password2 );
+ if ($matches) {
+ $user->setPassword($password1);
+ $vars['pw updated'] = true;
+ } else {
+ $vars['pw mixmatch'] = true;
+ }
+ }
+
+ // Change information //////////////////////////////////////////
+ $config_options = array();
+ $pm = PluginManager::getInstance();
+ $pm->callHook('userConfig', &$config_options);
+
+ foreach ($config_options as $group=>$options) {
+ foreach ($options as $option) {
+ $this->confText($user, $option[0]);
+ }
+ }
+
+ // Change contact info /////////////////////////////////////////
+ global $CONTACT_METHODS;
+ foreach ($CONTACT_METHODS as $method) {
+ $this->confText($user, $method->addr_slug);
+ }
+ $this->confArray($user, 'use');
+
+ // Change groups ///////////////////////////////////////////////
+ $this->confArray($user, 'groups');
+
+ return $vars;
+ }
+
+ private function confArray($user, $key) {
+ if (isset($_POST[$key]) && is_array($_POST[$key])) {
+ $user->setConfArray($key, $_POST[$key]);
+ }
+ }
+
+ private function confText($user, $name) {
+ if (isset($_POST["user_$name"])) {
+ $user->setConf($name, $_POST["user_$name"]);
+ }
+ }
+
+
+ /**
+ * This will parse POST (really, PUT) data to update multiple users.
+ */
+ private function update_users() {
+ $attribs = $this->getIndexAttribs();
+ $form = new Form(null, null);
+ foreach ($attribs as $attrib) {
+ $key = $attrib['key'];
+ if (isset($_POST[$key]) && is_array($_POST[$key])) {
+ $old = $_POST['_old'][$key];
+ foreach ($_POST[$key] as $uid => $value) {
+ @$value_base = $old[$uid];
+ $set = DB::set('users', $uid, $key, $value, $value_base);
+ if (is_string($set)) {
+ echo "<pre>\n";
+ echo "Error: Value changed elsewhere, ".
+ "and I don't have real handling ".
+ "for this yet.\n";
+ echo "UID: $uid\n";
+ echo "Name: ".$user->getName()."\n";
+ echo "Key: $key\n";
+ echo "Value: Original : ";
+ var_dump($value_base);
+ echo "Value: Other edit: ";
+ var_dump($value_fork);
+ echo "Value: This edit : ";
+ var_dump($value);
+ echo "</pre>";
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * This will show the user index.
+ */
+ private function show_index($routed, $remainder) {
+ $db = Database::getInstance();
+
+ $logged_in_user = Auth::getInstance(Login::isLoggedIn());
+ $anon_userlist = $db->getSysConf('anon_userlist')=='true';
+ if (!$anon_userlist && !$logged_in_user->isUser()) {
+ $this->http401($routed, $remainder);
+ exit();
+ }
+
+ $vars = array();
+ $vars['attribs'] = $this->getIndexAttribs();
+ $vars['users'] = array();
+ $uids = $db->listUsers();
+ foreach ($uids as $uid) {
+ $vars['users'][$uid] = array();
+ foreach ($vars['attribs'] as $attrib) {
+ $key = $attrib['key'];
+ $props = DB::get('users', $uid, $key);
+ $vars['users'][$uid][$key] = $props;
+ }
+ }
+ $this->showView('users/index', $vars);
+ }
+
+ function attrib($key, $name, $type='string') {
+ return array('key'=>$key, 'name'=>$name, 'type'=>$type);
+ }
+ private function getIndexAttribs() {
+ $user = Auth::getInstance(Login::isLoggedIn());
+
+ $attribs = array();
+ $attribs[] = $this->attrib('auth_uid', 'UID');
+ if ($user->isUser()) {
+ $attribs[] = $this->attrib('auth_user', 'Active', 'bool');
+ if ($user->isAdmin()) {
+ $attribs[] = $this->attrib('auth_admin', 'Admin', 'bool');
+ $attribs[] = $this->attrib('auth_delete', 'Delete', 'bool');
+ }
+ $attribs[] = $this->attrib('lastname','Last');
+ $attribs[] = $this->attrib('firstname','First');
+ $attribs[] = $this->attrib('hsclass','Class of');
+ $attribs[] = $this->attrib('phone','Phone number');
+ $attribs[] = $this->attrib('email','Email');
+ }
+ $attribs[] = $this->attrib('auth_name', 'Username');
+
+ return $attribs;
+ }
+
+ private function registrationOpen() {
+ $db = Database::getInstance();
+ $val = $db->getSysConf('registration_open');
+ switch ($val) {
+ case 'true': return true;
+ case 'false': return false;
+ default: return true;
+ }
+ }
+}
diff --git a/apps/um/ext/PasswordHash.class.php b/apps/um/ext/PasswordHash.class.php
new file mode 100644
index 0000000..12958c7
--- /dev/null
+++ b/apps/um/ext/PasswordHash.class.php
@@ -0,0 +1,253 @@
+<?php
+#
+# Portable PHP password hashing framework.
+#
+# Version 0.3 / genuine.
+#
+# Written by Solar Designer <solar at openwall.com> in 2004-2006 and placed in
+# the public domain. Revised in subsequent years, still public domain.
+#
+# There's absolutely no warranty.
+#
+# The homepage URL for this framework is:
+#
+# http://www.openwall.com/phpass/
+#
+# Please be sure to update the Version line if you edit this file in any way.
+# It is suggested that you leave the main version number intact, but indicate
+# your project name (after the slash) and add your own revision information.
+#
+# Please do not change the "private" password hashing method implemented in
+# here, thereby making your hashes incompatible. However, if you must, please
+# change the hash type identifier (the "$P$") to something different.
+#
+# Obviously, since this code is in the public domain, the above are not
+# requirements (there can be none), but merely suggestions.
+#
+class PasswordHash {
+ var $itoa64;
+ var $iteration_count_log2;
+ var $portable_hashes;
+ var $random_state;
+
+ function PasswordHash($iteration_count_log2, $portable_hashes)
+ {
+ $this->itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
+
+ if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31)
+ $iteration_count_log2 = 8;
+ $this->iteration_count_log2 = $iteration_count_log2;
+
+ $this->portable_hashes = $portable_hashes;
+
+ $this->random_state = microtime();
+ if (function_exists('getmypid'))
+ $this->random_state .= getmypid();
+ }
+
+ function get_random_bytes($count)
+ {
+ $output = '';
+ if (is_readable('/dev/urandom') &&
+ ($fh = @fopen('/dev/urandom', 'rb'))) {
+ $output = fread($fh, $count);
+ fclose($fh);
+ }
+
+ if (strlen($output) < $count) {
+ $output = '';
+ for ($i = 0; $i < $count; $i += 16) {
+ $this->random_state =
+ md5(microtime() . $this->random_state);
+ $output .=
+ pack('H*', md5($this->random_state));
+ }
+ $output = substr($output, 0, $count);
+ }
+
+ return $output;
+ }
+
+ function encode64($input, $count)
+ {
+ $output = '';
+ $i = 0;
+ do {
+ $value = ord($input[$i++]);
+ $output .= $this->itoa64[$value & 0x3f];
+ if ($i < $count)
+ $value |= ord($input[$i]) << 8;
+ $output .= $this->itoa64[($value >> 6) & 0x3f];
+ if ($i++ >= $count)
+ break;
+ if ($i < $count)
+ $value |= ord($input[$i]) << 16;
+ $output .= $this->itoa64[($value >> 12) & 0x3f];
+ if ($i++ >= $count)
+ break;
+ $output .= $this->itoa64[($value >> 18) & 0x3f];
+ } while ($i < $count);
+
+ return $output;
+ }
+
+ function gensalt_private($input)
+ {
+ $output = '$P$';
+ $output .= $this->itoa64[min($this->iteration_count_log2 +
+ ((PHP_VERSION >= '5') ? 5 : 3), 30)];
+ $output .= $this->encode64($input, 6);
+
+ return $output;
+ }
+
+ function crypt_private($password, $setting)
+ {
+ $output = '*0';
+ if (substr($setting, 0, 2) == $output)
+ $output = '*1';
+
+ $id = substr($setting, 0, 3);
+ # We use "$P$", phpBB3 uses "$H$" for the same thing
+ if ($id != '$P$' && $id != '$H$')
+ return $output;
+
+ $count_log2 = strpos($this->itoa64, $setting[3]);
+ if ($count_log2 < 7 || $count_log2 > 30)
+ return $output;
+
+ $count = 1 << $count_log2;
+
+ $salt = substr($setting, 4, 8);
+ if (strlen($salt) != 8)
+ return $output;
+
+ # We're kind of forced to use MD5 here since it's the only
+ # cryptographic primitive available in all versions of PHP
+ # currently in use. To implement our own low-level crypto
+ # in PHP would result in much worse performance and
+ # consequently in lower iteration counts and hashes that are
+ # quicker to crack (by non-PHP code).
+ if (PHP_VERSION >= '5') {
+ $hash = md5($salt . $password, TRUE);
+ do {
+ $hash = md5($hash . $password, TRUE);
+ } while (--$count);
+ } else {
+ $hash = pack('H*', md5($salt . $password));
+ do {
+ $hash = pack('H*', md5($hash . $password));
+ } while (--$count);
+ }
+
+ $output = substr($setting, 0, 12);
+ $output .= $this->encode64($hash, 16);
+
+ return $output;
+ }
+
+ function gensalt_extended($input)
+ {
+ $count_log2 = min($this->iteration_count_log2 + 8, 24);
+ # This should be odd to not reveal weak DES keys, and the
+ # maximum valid value is (2**24 - 1) which is odd anyway.
+ $count = (1 << $count_log2) - 1;
+
+ $output = '_';
+ $output .= $this->itoa64[$count & 0x3f];
+ $output .= $this->itoa64[($count >> 6) & 0x3f];
+ $output .= $this->itoa64[($count >> 12) & 0x3f];
+ $output .= $this->itoa64[($count >> 18) & 0x3f];
+
+ $output .= $this->encode64($input, 3);
+
+ return $output;
+ }
+
+ function gensalt_blowfish($input)
+ {
+ # This one needs to use a different order of characters and a
+ # different encoding scheme from the one in encode64() above.
+ # We care because the last character in our encoded string will
+ # only represent 2 bits. While two known implementations of
+ # bcrypt will happily accept and correct a salt string which
+ # has the 4 unused bits set to non-zero, we do not want to take
+ # chances and we also do not want to waste an additional byte
+ # of entropy.
+ $itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+
+ $output = '$2a$';
+ $output .= chr(ord('0') + $this->iteration_count_log2 / 10);
+ $output .= chr(ord('0') + $this->iteration_count_log2 % 10);
+ $output .= '$';
+
+ $i = 0;
+ do {
+ $c1 = ord($input[$i++]);
+ $output .= $itoa64[$c1 >> 2];
+ $c1 = ($c1 & 0x03) << 4;
+ if ($i >= 16) {
+ $output .= $itoa64[$c1];
+ break;
+ }
+
+ $c2 = ord($input[$i++]);
+ $c1 |= $c2 >> 4;
+ $output .= $itoa64[$c1];
+ $c1 = ($c2 & 0x0f) << 2;
+
+ $c2 = ord($input[$i++]);
+ $c1 |= $c2 >> 6;
+ $output .= $itoa64[$c1];
+ $output .= $itoa64[$c2 & 0x3f];
+ } while (1);
+
+ return $output;
+ }
+
+ function HashPassword($password)
+ {
+ $random = '';
+
+ if (CRYPT_BLOWFISH == 1 && !$this->portable_hashes) {
+ $random = $this->get_random_bytes(16);
+ $hash =
+ crypt($password, $this->gensalt_blowfish($random));
+ if (strlen($hash) == 60)
+ return $hash;
+ }
+
+ if (CRYPT_EXT_DES == 1 && !$this->portable_hashes) {
+ if (strlen($random) < 3)
+ $random = $this->get_random_bytes(3);
+ $hash =
+ crypt($password, $this->gensalt_extended($random));
+ if (strlen($hash) == 20)
+ return $hash;
+ }
+
+ if (strlen($random) < 6)
+ $random = $this->get_random_bytes(6);
+ $hash =
+ $this->crypt_private($password,
+ $this->gensalt_private($random));
+ if (strlen($hash) == 34)
+ return $hash;
+
+ # Returning '*' on error is safe here, but would _not_ be safe
+ # in a crypt(3)-like function used _both_ for generating new
+ # hashes and for validating passwords against existing hashes.
+ return '*';
+ }
+
+ function CheckPassword($password, $stored_hash)
+ {
+ $hash = $this->crypt_private($password, $stored_hash);
+ if ($hash[0] == '*')
+ $hash = crypt($password, $stored_hash);
+
+ return $hash == $stored_hash;
+ }
+}
+
+?>
diff --git a/apps/um/ext/README.txt b/apps/um/ext/README.txt
new file mode 100644
index 0000000..78c4f70
--- /dev/null
+++ b/apps/um/ext/README.txt
@@ -0,0 +1,12 @@
+These are class files that I've gathered from around the internet.
+
+I've renamed the files to follow a standard scheme.
+
+This is where each file came from:
+
+My Name : Original Name : From
+PasswordHash.class.php : PasswordHash.php : http://www.openwall.com/phpass/
+recaptchalib.php : recaptchalib.php : https://code.google.com/p/recaptcha/
+
+~ Luke Shumaker <http://lukeshu.ath.cx>
+Happy Hacking!
diff --git a/apps/um/ext/recaptchalib.php b/apps/um/ext/recaptchalib.php
new file mode 100644
index 0000000..32c4f4d
--- /dev/null
+++ b/apps/um/ext/recaptchalib.php
@@ -0,0 +1,277 @@
+<?php
+/*
+ * This is a PHP library that handles calling reCAPTCHA.
+ * - Documentation and latest version
+ * http://recaptcha.net/plugins/php/
+ * - Get a reCAPTCHA API Key
+ * https://www.google.com/recaptcha/admin/create
+ * - Discussion group
+ * http://groups.google.com/group/recaptcha
+ *
+ * Copyright (c) 2007 reCAPTCHA -- http://recaptcha.net
+ * AUTHORS:
+ * Mike Crawford
+ * Ben Maurer
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/**
+ * The reCAPTCHA server URL's
+ */
+define("RECAPTCHA_API_SERVER", "http://www.google.com/recaptcha/api");
+define("RECAPTCHA_API_SECURE_SERVER", "https://www.google.com/recaptcha/api");
+define("RECAPTCHA_VERIFY_SERVER", "www.google.com");
+
+/**
+ * Encodes the given data into a query string format
+ * @param $data - array of string elements to be encoded
+ * @return string - encoded request
+ */
+function _recaptcha_qsencode ($data) {
+ $req = "";
+ foreach ( $data as $key => $value )
+ $req .= $key . '=' . urlencode( stripslashes($value) ) . '&';
+
+ // Cut the last '&'
+ $req=substr($req,0,strlen($req)-1);
+ return $req;
+}
+
+
+
+/**
+ * Submits an HTTP POST to a reCAPTCHA server
+ * @param string $host
+ * @param string $path
+ * @param array $data
+ * @param int port
+ * @return array response
+ */
+function _recaptcha_http_post($host, $path, $data, $port = 80) {
+
+ $req = _recaptcha_qsencode ($data);
+
+ $http_request = "POST $path HTTP/1.0\r\n";
+ $http_request .= "Host: $host\r\n";
+ $http_request .= "Content-Type: application/x-www-form-urlencoded;\r\n";
+ $http_request .= "Content-Length: " . strlen($req) . "\r\n";
+ $http_request .= "User-Agent: reCAPTCHA/PHP\r\n";
+ $http_request .= "\r\n";
+ $http_request .= $req;
+
+ $response = '';
+ if( false == ( $fs = @fsockopen($host, $port, $errno, $errstr, 10) ) ) {
+ die ('Could not open socket');
+ }
+
+ fwrite($fs, $http_request);
+
+ while ( !feof($fs) )
+ $response .= fgets($fs, 1160); // One TCP-IP packet
+ fclose($fs);
+ $response = explode("\r\n\r\n", $response, 2);
+
+ return $response;
+}
+
+
+
+/**
+ * Gets the challenge HTML (javascript and non-javascript version).
+ * This is called from the browser, and the resulting reCAPTCHA HTML widget
+ * is embedded within the HTML form it was called from.
+ * @param string $pubkey A public key for reCAPTCHA
+ * @param string $error The error given by reCAPTCHA (optional, default is null)
+ * @param boolean $use_ssl Should the request be made over ssl? (optional, default is false)
+
+ * @return string - The HTML to be embedded in the user's form.
+ */
+function recaptcha_get_html ($pubkey, $error = null, $use_ssl = false)
+{
+ if ($pubkey == null || $pubkey == '') {
+ die ("To use reCAPTCHA you must get an API key from <a href='https://www.google.com/recaptcha/admin/create'>https://www.google.com/recaptcha/admin/create</a>");
+ }
+
+ if ($use_ssl) {
+ $server = RECAPTCHA_API_SECURE_SERVER;
+ } else {
+ $server = RECAPTCHA_API_SERVER;
+ }
+
+ $errorpart = "";
+ if ($error) {
+ $errorpart = "&amp;error=" . $error;
+ }
+ return '<script type="text/javascript" src="'. $server . '/challenge?k=' . $pubkey . $errorpart . '"></script>
+
+ <noscript>
+ <iframe src="'. $server . '/noscript?k=' . $pubkey . $errorpart . '" height="300" width="500" frameborder="0"></iframe><br/>
+ <textarea name="recaptcha_challenge_field" rows="3" cols="40"></textarea>
+ <input type="hidden" name="recaptcha_response_field" value="manual_challenge"/>
+ </noscript>';
+}
+
+
+
+
+/**
+ * A ReCaptchaResponse is returned from recaptcha_check_answer()
+ */
+class ReCaptchaResponse {
+ var $is_valid;
+ var $error;
+}
+
+
+/**
+ * Calls an HTTP POST function to verify if the user's guess was correct
+ * @param string $privkey
+ * @param string $remoteip
+ * @param string $challenge
+ * @param string $response
+ * @param array $extra_params an array of extra variables to post to the server
+ * @return ReCaptchaResponse
+ */
+function recaptcha_check_answer ($privkey, $remoteip, $challenge, $response, $extra_params = array())
+{
+ if ($privkey == null || $privkey == '') {
+ die ("To use reCAPTCHA you must get an API key from <a href='https://www.google.com/recaptcha/admin/create'>https://www.google.com/recaptcha/admin/create</a>");
+ }
+
+ if ($remoteip == null || $remoteip == '') {
+ die ("For security reasons, you must pass the remote ip to reCAPTCHA");
+ }
+
+
+
+ //discard spam submissions
+ if ($challenge == null || strlen($challenge) == 0 || $response == null || strlen($response) == 0) {
+ $recaptcha_response = new ReCaptchaResponse();
+ $recaptcha_response->is_valid = false;
+ $recaptcha_response->error = 'incorrect-captcha-sol';
+ return $recaptcha_response;
+ }
+
+ $response = _recaptcha_http_post (RECAPTCHA_VERIFY_SERVER, "/recaptcha/api/verify",
+ array (
+ 'privatekey' => $privkey,
+ 'remoteip' => $remoteip,
+ 'challenge' => $challenge,
+ 'response' => $response
+ ) + $extra_params
+ );
+
+ $answers = explode ("\n", $response [1]);
+ $recaptcha_response = new ReCaptchaResponse();
+
+ if (trim ($answers [0]) == 'true') {
+ $recaptcha_response->is_valid = true;
+ }
+ else {
+ $recaptcha_response->is_valid = false;
+ $recaptcha_response->error = $answers [1];
+ }
+ return $recaptcha_response;
+
+}
+
+/**
+ * gets a URL where the user can sign up for reCAPTCHA. If your application
+ * has a configuration page where you enter a key, you should provide a link
+ * using this function.
+ * @param string $domain The domain where the page is hosted
+ * @param string $appname The name of your application
+ */
+function recaptcha_get_signup_url ($domain = null, $appname = null) {
+ return "https://www.google.com/recaptcha/admin/create?" . _recaptcha_qsencode (array ('domains' => $domain, 'app' => $appname));
+}
+
+function _recaptcha_aes_pad($val) {
+ $block_size = 16;
+ $numpad = $block_size - (strlen ($val) % $block_size);
+ return str_pad($val, strlen ($val) + $numpad, chr($numpad));
+}
+
+/* Mailhide related code */
+
+function _recaptcha_aes_encrypt($val,$ky) {
+ if (! function_exists ("mcrypt_encrypt")) {
+ die ("To use reCAPTCHA Mailhide, you need to have the mcrypt php module installed.");
+ }
+ $mode=MCRYPT_MODE_CBC;
+ $enc=MCRYPT_RIJNDAEL_128;
+ $val=_recaptcha_aes_pad($val);
+ return mcrypt_encrypt($enc, $ky, $val, $mode, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0");
+}
+
+
+function _recaptcha_mailhide_urlbase64 ($x) {
+ return strtr(base64_encode ($x), '+/', '-_');
+}
+
+/* gets the reCAPTCHA Mailhide url for a given email, public key and private key */
+function recaptcha_mailhide_url($pubkey, $privkey, $email) {
+ if ($pubkey == '' || $pubkey == null || $privkey == "" || $privkey == null) {
+ die ("To use reCAPTCHA Mailhide, you have to sign up for a public and private key, " .
+ "you can do so at <a href='http://www.google.com/recaptcha/mailhide/apikey'>http://www.google.com/recaptcha/mailhide/apikey</a>");
+ }
+
+
+ $ky = pack('H*', $privkey);
+ $cryptmail = _recaptcha_aes_encrypt ($email, $ky);
+
+ return "http://www.google.com/recaptcha/mailhide/d?k=" . $pubkey . "&c=" . _recaptcha_mailhide_urlbase64 ($cryptmail);
+}
+
+/**
+ * gets the parts of the email to expose to the user.
+ * eg, given johndoe@example,com return ["john", "example.com"].
+ * the email is then displayed as john...@example.com
+ */
+function _recaptcha_mailhide_email_parts ($email) {
+ $arr = preg_split("/@/", $email );
+
+ if (strlen ($arr[0]) <= 4) {
+ $arr[0] = substr ($arr[0], 0, 1);
+ } else if (strlen ($arr[0]) <= 6) {
+ $arr[0] = substr ($arr[0], 0, 3);
+ } else {
+ $arr[0] = substr ($arr[0], 0, 4);
+ }
+ return $arr;
+}
+
+/**
+ * Gets html to display an email address given a public an private key.
+ * to get a key, go to:
+ *
+ * http://www.google.com/recaptcha/mailhide/apikey
+ */
+function recaptcha_mailhide_html($pubkey, $privkey, $email) {
+ $emailparts = _recaptcha_mailhide_email_parts ($email);
+ $url = recaptcha_mailhide_url ($pubkey, $privkey, $email);
+
+ return htmlentities($emailparts[0]) . "<a href='" . htmlentities ($url) .
+ "' onclick=\"window.open('" . htmlentities ($url) . "', '', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=500,height=300'); return false;\" title=\"Reveal this e-mail address\">...</a>@" . htmlentities ($emailparts [1]);
+
+}
+
+
+?>
diff --git a/apps/um/lib/DB.class.php b/apps/um/lib/DB.class.php
new file mode 100644
index 0000000..ac8dafe
--- /dev/null
+++ b/apps/um/lib/DB.class.php
@@ -0,0 +1,164 @@
+<?php
+require_once('Auth.class.php');
+require_once('Login.class.php');
+require_once('Database.class.php');
+
+class DB {
+ public static function set($table, $unit, $key, $value, $orig_value) {
+ $value_base = $orig_value;
+
+ $doit = true;
+ $forked = false;
+ $have_old = ($value_base!==null);
+ if ($have_old) {
+ $we_changed_it = $value_base != $value;
+ if ($we_changed_it) {
+ $value_fork = $this->getConfString($key);
+ $someone_else_changed_it =
+ $value_fork != $value_base;
+ if ($someone_else_changed_it) {
+ if ($value == $value_fork) {
+ // we might as well not have
+ $we_changed_it = false;
+ } else {
+ $forked = true;
+ }
+ }
+ }
+ if (!$we_changed_it) {
+ $doit = false;// nothing to do
+ }
+ }
+ if ($doit) {
+ return $this->setConf($key, $value);
+ }
+ if ($forked) {
+ return $value_fork;
+ }
+ }
+
+ public static function get($table, $unit, $key) {
+ switch ($table) {
+ case 'conf':
+ case 'plugins':
+ return self::admin_get($unit, $key);
+ break;
+ case 'users':
+ return self::user_get($unit, $key);
+ break;
+ default:
+ return false;
+ }
+ }
+ public static function raw_set($table, $unit, $key, $value) {
+ switch ($table) {
+ case 'conf':
+ case 'plugins':
+ return self::admin_get($unit, $key, $value);
+ break;
+ case 'users':
+ return self::user_set($unit, $key, $value);
+ break;
+ default:
+ return false;
+ }
+ }
+
+ private static function user_get($uid, $key) {
+ $user = Auth::getInstance($uid);
+ $logged_in_user = Auth::getInstance(Login::isLoggedIn());
+
+ $post_key = $key."[$uid]";
+ @$value = $_POST[$post_key];
+ $editable = $user->canEdit();
+
+ switch ($key) {
+ case 'auth_uid':
+ $value = $user->getUID();
+ $editable = false;
+ break;
+ case 'auth_name':
+ $value = $user->getName();
+ break;
+ case 'auth_user':
+ $editable = $editable && $logged_in_user->isAdmin();
+ $value = $user->isUser()?'true':'false';
+ break;
+ case 'auth_admin':
+ $editable = $editable && $logged_in_user->isAdmin();
+ $value = $user->isAdmin()?'true':'false';
+ break;
+ case 'auth_delete':
+ $editable = $editable && $logged_in_user->isAdmin();
+ $value = 'false';
+ break;
+ default:
+ $value = $user->getConf($key);
+ if ($value===false) $value='';
+ break;
+ }
+
+ return array('value'=>$value,
+ 'post_key'=>$post_key,
+ 'editable'=>$editable);
+ }
+ private static function user_set($uid, $key, $value) {
+ $user = Auth::getInstance($uid);
+
+ switch ($key) {
+ case 'auth_uid':
+ return false;
+ break;
+ case 'auth_name':
+ return $user->setName($value);
+ break;
+ case 'auth_user':
+ return $user->setUser($value=='true');
+ break;
+ case 'auth_admin':
+ return $user->setAdmin($value=='true');
+ break;
+ case 'auth_delete':
+ if ($value=='true') return $user->delete();
+ default:
+ return $user->setConf($key, $value);
+ break;
+ }
+ }
+
+ private static function admin_get($plugin, $key) {
+ $db = Database::getInstance();
+ $user = Auth::getInstance(Login::isLoggedIn());
+ if ($user->isAdmin()) {
+ $editable = true;
+ switch ($plugin) {
+ case 'system':
+ $value = $db->getSysConf($key);
+ break;
+ default:
+ $value = $db->getPluginConf($plugin, $key);
+ break;
+ }
+ } else {
+ $editable = false;
+ $value = false;
+ }
+
+ return array('value'=>$value,
+ 'post_key'=>'to be implemented',// FIXME
+ 'editable'=>$editable);
+ }
+ private static function admin_set($plugin, $key, $value) {
+ $db = Database::getInstance();
+ $user = Auth::getInstance(Login::isLoggedIn());
+ if (!$user->isAdmin()) {
+ return false;
+ }
+ switch ($plugin) {
+ case 'system':
+ return $db->setSysConf($key, $value);
+ default:
+ return $db->setPluginConf($plugin, $key, $value);
+ }
+ }
+}
diff --git a/apps/um/lib/Database.class.php b/apps/um/lib/Database.class.php
new file mode 100644
index 0000000..a76d891
--- /dev/null
+++ b/apps/um/lib/Database.class.php
@@ -0,0 +1,403 @@
+<?php
+require_once('Singleton.class.php');
+require_once('Hasher.class.php');
+
+class Database extends Singleton {
+ private static $me = null;
+ private $conf;
+ private $mysql;
+ private $db_prefix;
+
+ public function __construct($conf_file) {
+ $this->conf = $conf_file;
+ self::$me = $this;
+ }
+ public static function getInstance() {
+ return self::$me;
+ }
+
+ // Low-Level SQL functions /////////////////////////////////////////////
+
+ private function mysql() {
+ if (!isset($this->mysql)) {
+ $this->mysql_init();
+ }
+ return $this->mysql;
+ }
+ private function mysql_init() {
+ global $db_config;
+ require($this->conf);
+ $this->mysql = mysql_connect($db_config['host'],
+ $db_config['user'],
+ $db_config['password']);
+ mysql_set_charset($db_config['charset'], $this->mysql);
+ mysql_select_db($db_config['name'], $this->mysql);
+ $this->db_prefix = $db_config['prefix'];
+ unset($db_config);
+ }
+ private function mysql_table($table_name) {
+ $mysql = $this->mysql();
+ $prefix = $this->db_prefix;
+ return $prefix.mysql_real_escape_string($table_name, $mysql);
+ }
+ private function mysql_escape($string) {
+ $mysql = $this->mysql();
+ return mysql_real_escape_string($string, $mysql);
+ }
+ private function mysql_query($query) {
+ $mysql = $this->mysql();
+ return mysql_query($query, $mysql);
+ }
+ public function mysql_error() {
+ $mysql = $this->mysql();
+ return mysql_error($mysql);
+ }
+
+ // High-Level SQL functions ////////////////////////////////////////////
+
+ // The 'auth' table
+
+ public function getUID($username) {
+ $t = $this->mysql_table('auth');
+ $v = $this->mysql_escape($username);
+ $query =
+ "SELECT * \n".
+ "FROM $t \n".
+ "WHERE name='$v' ;";
+ $q = $this->mysql_query($query);
+ $user = mysql_fetch_array($q);
+ if (isset($user['uid'])) {
+ return (int)$user['uid'];
+ } else {
+ return false;
+ }
+ }
+ public function getUsername($uid) {
+ if (!is_int($uid)) return false;
+ $t = $this->mysql_table('auth');
+ $query =
+ "SELECT * \n".
+ "FROM $t \n".
+ "WHERE uid=$uid ;";
+ $q = $this->mysql_query($query);
+ $user = mysql_fetch_array($q);
+ if (isset($user['name'])) {
+ return $user['name'];
+ } else {
+ return false;
+ }
+ }
+ public function setUsername($uid, $username) {
+ if (!is_int($uid)) return false;
+ if ($this->getUID($username) !== false) {
+ return false;
+ }
+ $table = $this->mysql_table('auth');
+ $name = $this->mysql_escape($username);
+ $query =
+ "UPDATE $table \n".
+ "SET name='$name' \n".
+ "WHERE uid=$uid ;";
+ $q = $this->mysql_query($query);
+ return ($q?true:false);
+ }
+ public function getPasswordHash($uid) {
+ if (!is_int($uid)) return false;
+
+ $table = $this->mysql_table('auth');
+ $query =
+ "SELECT * \n".
+ "FROM $table \n".
+ "WHERE uid=$uid ;";
+ $q = $this->mysql_query($query);
+ $user = mysql_fetch_array($q);
+ if (isset($user['hash'])) {
+ return $user['hash'];
+ } else {
+ return false;
+ }
+ }
+ public function setPassword($uid, $password) {
+ if (!is_int($uid)) return false;
+ $table = $this->mysql_table('auth');
+
+ $hasher = Hasher::getInstance();
+ @$hash = $hasher->hash($password);
+ $query =
+ "UPDATE $table \n".
+ "SET hash='$hash' \n".
+ "WHERE uid=$uid ;";
+ $q = $this->mysql_query($query);
+ return ($q?true:false);
+ }
+ public function addUser($username, $password) {
+ $user_exits = $this->getUID($username);
+ if ($user_exists) {
+ return false;
+ }
+
+ $table = $this->mysql_table('auth');
+ $user = $this->mysql_escape($username);
+ $hasher = Hasher::getInstance();
+ @$hash = $hasher->hash($password);
+ $status = 0;
+ $query =
+ "INSERT INTO $table ( name, hash , status) \n".
+ "VALUES ('$user', '$hash', $status) ;";
+ $this->mysql_query($query);
+ $uid = $this->getUID($username);
+ return $uid;
+ }
+ public function getStatus($uid) {
+ if (!is_int($uid)) return false;
+ $table = $this->mysql_table('auth');
+ $query =
+ "SELECT * \n".
+ "FROM $table \n".
+ "WHERE uid=$uid ;";
+ $q = $this->mysql_query($query);
+ $user = mysql_fetch_array($q);
+ if (isset($user['status'])) {
+ return (int)$user['status'];
+ } else {
+ return false;
+ }
+ }
+ public function setStatus($uid, $status) {
+ if (!is_int($uid)) return false;
+ $table = $this->mysql_table('auth');
+ $s = $this->mysql_escape($status);
+ $query =
+ "UPDATE $table \n".
+ "SET status=$s \n".
+ "WHERE uid=$uid ;";
+ $q = $this->mysql_query($query);
+ return ($q?true:false);
+ }
+ public function countUsers() {
+ $table = $this->mysql_table('auth');
+ $query = "SELECT COUNT(*) FROM $table;";
+ $q = $this->mysql_query($query);
+ $row = mysql_fetch_array($q);
+ $count = $row[0];
+ return $count;
+ }
+ public function listGroups() {
+ $table = $this->mysql_table('auth');
+ $query =
+ "SELECT uid \n".
+ "FROM $table \n".
+ "WHERE status=3 ;";
+ $q = $this->mysql_query($query);
+ $groups = array();
+ while (($row = mysql_fetch_array($q)) !==false) {
+ $groups[] = (int)$row[0];
+ }
+ return $groups;
+ }
+ public function listGroupNames() {
+ $table = $this->mysql_table('auth');
+ $query =
+ "SELECT name \n".
+ "FROM $table \n".
+ "WHERE status=3 ;";
+ $q = $this->mysql_query($query);
+ $groups = array();
+ while (($row = mysql_fetch_array($q)) !==false) {
+ $groups[] = $row[0].'';
+ }
+ return $groups;
+ }
+ public function listUsers() {
+ $table = $this->mysql_table('auth');
+ $query =
+ "SELECT uid \n".
+ "FROM $table \n".
+ "WHERE status < 3 ;";
+ $q = $this->mysql_query($query);
+ $users = array();
+ while (($row = mysql_fetch_array($q)) !==false) {
+ $users[] = (int)$row[0];
+ }
+ return $users;
+ }
+
+ // The 'users' table
+
+ public function findUser($setting, $value) {
+ $t = $this->mysql_table('users');
+ $k = $this->mysql_escape($setting);
+ $v = $this->mysql_escape($value);
+ $query =
+ "SELECT * \n".
+ "FROM $t \n".
+ "WHERE k = '$k' \n".
+ "AND UPPER(v)=UPPER('$v') ;";
+ $q = $this->mysql_query($query);
+ $user = mysql_fetch_array($q);
+ if (isset($user['uid'])) {
+ return $user['uid'];
+ } else {
+ return false;
+ }
+ }
+ public function getUserConf($uid, $setting) {
+ if (!is_int($uid)) return false;
+ $t = $this->mysql_table('users');
+ $k = $this->mysql_escape($setting);
+ $query =
+ "SELECT * \n".
+ "FROM $t \n".
+ "WHERE k='$k' \n".
+ "AND uid=$uid ;";
+ $q = $this->mysql_query($query);
+ $row = mysql_fetch_array($q);
+ if (isset($row['v'])) {
+ return $row['v'];
+ } else {
+ return false;
+ }
+ }
+ public function setUserConf($uid, $setting, $value) {
+ if (!is_int($uid)) return false;
+ $isset = ($this->getUserConf($uid, $setting) !== false);
+ $t = $this->mysql_table('users');
+ $k = $this->mysql_escape($setting);
+ $v = $this->mysql_escape($value);
+ if ($isset) {
+ $query =
+ "UPDATE $t \n".
+ "SET v = '$v' \n".
+ "WHERE k = '$k' \n".
+ "AND uid = $uid ;";
+ } else {
+ $query =
+ "INSERT INTO $t ( uid, k , v ) \n".
+ "VALUES ($uid, '$k', '$v') ;";
+ }
+ $q = $this->mysql_query($query);
+ return ($q?true:false);
+ }
+ public function getUsersInGroup($groupname) {
+ $table = $this->mysql_table('users');
+ $group = $this->mysql_escape($groupname);
+ $query =
+ "SELECT uid \n".
+ "FROM $table \n".
+ "WHERE k='groups' \n".
+ "AND v LIKE '%,$group,%' ;";
+ $q = $this->mysql_query($query);
+ $users = array();
+ while (($row = mysql_fetch_array($q)) !==false) {
+ $users[] = $row[0];
+ }
+ return $users;
+ }
+
+ // The 'plugins' table
+
+ public function getPluginConf($plugin, $key) {
+ $t = $this->mysql_table('plugins');
+ $p = $this->mysql_escape($plugin);
+ $k = $this->mysql_escape($key);
+ $query =
+ "SELECT * \n".
+ "FROM $t \n".
+ "WHERE k='$k' \n".
+ "AND plugin='$p' ;";
+ $q = $this->mysql_query($query);
+ $row = mysql_fetch_array($q);
+ if (isset($row['v'])) {
+ return $row['v'];
+ } else {
+ return false;
+ }
+ }
+ public function setPluginConf($plugin, $key, $value) {
+ $isset = ($this->getPluginConf($plugin, $key) !== false);
+ $t = $this->mysql_table('plugins');
+ $p = $this->mysql_escape($plugin);
+ $k = $this->mysql_escape($key);
+ $v = $this->mysql_escape($value);
+ if ($isset) {
+ $query =
+ "UPDATE $t \n".
+ "SET v = '$v' \n".
+ "WHERE k = '$k' \n".
+ "AND plugin = '$p' ;";
+ } else {
+ $query =
+ "INSERT INTO $t (plugin, k , v ) \n".
+ "VALUES ('$p' , '$k', '$v') ;";
+ }
+ $q = $this->mysql_query($query);
+ return ($q?true:false);
+ }
+
+ // The 'conf' table
+
+ public function getSysConf($key) {
+ $t = $this->mysql_table('conf');
+ $k = $this->mysql_escape($key);
+ $query =
+ "SELECT * \n".
+ "FROM $t \n".
+ "WHERE k='$k' ;";
+ $q = $this->mysql_query($query);
+ $row = mysql_fetch_array($q);
+ if (isset($row['v'])) {
+ return $row['v'];
+ } else {
+ return false;
+ }
+ }
+ public function setSysConf($key, $value) {
+ $isset = ($this->getSysConf($key) !== false);
+ $t = $this->mysql_table('conf');
+ $k = $this->mysql_escape($key);
+ $v = $this->mysql_escape($value);
+ if ($isset) {
+ $query =
+ "UPDATE $t \n".
+ "SET v = '$v' \n".
+ "WHERE k = '$k' ;";
+ } else {
+ $query =
+ "INSERT INTO $t ( k , v ) \n".
+ "VALUES ('$k', '$v') ;";
+ }
+ $q = $this->mysql_query($query);
+ return ($q?true:false);
+ }
+
+ /**
+ * Strip out empty group names and duplicates, sort.
+ */
+ private static function sanitizeArray($in) {
+ $out = array();
+ foreach ($in as $item) {
+ if (($item !== '')&&(!in_array($item, $out))) {
+ $out[] = $item;
+ }
+ }
+ natsort($out);
+ return $out;
+ }
+ /**
+ * Translate an array into a value suitable to be stored into a
+ * key-value store in the database.
+ */
+ public static function arrayToValue($list) {
+ $out_list = self::sanitizeArray($list);
+ return ','.implode(',', $out_list).',';
+ }
+ /**
+ * Translate a value from arrayToValue() back into an array.
+ */
+ public static function valueToArray($value) {
+ $raw_list = explode(',', $value);
+ $out_list = self::sanitizeArray($raw_list);
+ return $out_list;
+ }
+
+}
diff --git a/apps/um/lib/Hasher.class.php b/apps/um/lib/Hasher.class.php
new file mode 100644
index 0000000..dc16d68
--- /dev/null
+++ b/apps/um/lib/Hasher.class.php
@@ -0,0 +1,18 @@
+<?php
+require_once('Singleton.class.php');
+require_once('PasswordHash.class.php');
+
+class Hasher extends Singleton {
+ private $pw_hash;
+
+ function __construct() {
+ $this->pw_hash = new PasswordHash(8, false);
+ }
+
+ public function hash($password) {
+ return $this->pw_hash->HashPassword($password);
+ }
+ public function check($password, $hash) {
+ return $this->pw_hash->CheckPassword($password, $hash);
+ }
+}
diff --git a/apps/um/lib/Login.class.php b/apps/um/lib/Login.class.php
new file mode 100644
index 0000000..bb21928
--- /dev/null
+++ b/apps/um/lib/Login.class.php
@@ -0,0 +1,41 @@
+<?php
+require_once('Database.class.php');
+require_once('Hasher.class.php');
+
+class Login {
+ /** Decalare an empty __construct() so that the login function doesn't
+ get mistaken for the costructor. */
+ public function __construct() {}
+
+ public static function login($username, $password) {
+ $db = Database::getInstance();
+ $hasher = Hasher::getInstance();
+
+ $uid = $db->getUID($username);
+ if ($uid!==false && $db->getStatus($uid)>=3)
+ $uid=false;
+ if ($uid===false) {
+ // user does not exist
+ return 2;
+ }
+ $hash = $db->getPasswordHash($uid);
+ if ($hasher->check($password, $hash)) {
+ // success
+ $_SESSION['uid'] = $uid;
+ return 0;
+ } else {
+ // wrong password
+ return 1;
+ }
+ }
+ public static function isLoggedIn() {
+ if ( isset($_SESSION['uid']) && ($_SESSION['uid']!='') ) {
+ return $_SESSION['uid'];
+ } else {
+ return false;
+ }
+ }
+ public static function logout() {
+ $_SESSION['uid'] = '';
+ }
+}
diff --git a/apps/um/lib/Plugin.class.php b/apps/um/lib/Plugin.class.php
new file mode 100644
index 0000000..9d2fc2e
--- /dev/null
+++ b/apps/um/lib/Plugin.class.php
@@ -0,0 +1,25 @@
+<?php
+
+abstract class Plugin {
+ protected $config = Array();
+
+ public abstract static function configList();
+ public abstract static function description();
+
+ public function configSet($param, $value) {
+ if (isset($this->config[$param])) {
+ $this->config[$param]=$value;
+ }
+ }
+
+ public function userConfig() { return array(); }
+ protected function addConfigGroup($arr, $group) {
+ if (!isset($arr[$group]))
+ $arr[$group] = array();
+ }
+
+ public abstract function init();
+
+ public function antispam_html() { return ''; }
+ public function antispam_verify() { return true; }
+}
diff --git a/apps/um/lib/PluginManager.class.php b/apps/um/lib/PluginManager.class.php
new file mode 100644
index 0000000..ce5a3ef
--- /dev/null
+++ b/apps/um/lib/PluginManager.class.php
@@ -0,0 +1,99 @@
+<?php
+require_once('Singleton.class.php');
+require_once('Database.class.php');
+
+class PluginManager extends Singleton {
+ public $plugins = array();
+ private $loaded = false;
+
+ /**
+ * Return an instance of the plugin with $plugin_name
+ */
+ public function loadPlugin($plugin_name) {
+ $db = Database::getInstance();
+
+ require_once("$plugin_name.class.php");
+ $obj = new $plugin_name;
+ $params = call_user_func("$plugin_name::configList");
+ foreach ($params as $param => $type) {
+ $value = $db->getPluginConf($plugin_name, $param);
+ if ($value!==false) {
+ switch ($type) {
+ case 'text':
+ case 'password':
+ $value = "$value";
+ break;
+ case 'int':
+ $value = (int)$value;
+ break;
+ }
+ $obj->configSet($param, $value);
+ }
+ }
+ return $obj;
+ }
+
+ /**
+ * Return an array of available plugin names.
+ */
+ public function listPlugins() {
+ $plugins = array();
+
+ $dirs = explode(PATH_SEPARATOR, PLUGINPATH);
+ foreach ($dirs as $dir) {
+ // Find all files in $dir with the ext `.class.php'
+ $files = glob($dir.'/*.class.php');
+ foreach ($files as $file) {
+ $plugins[] = preg_replace('@\.class\.php$@', '$1', basename($file));
+ }
+ }
+
+ return $plugins;
+ }
+
+ /**
+ * Return an array of enabled plugin names.
+ */
+ public function getActivePlugins() {
+ $db = Database::getInstance();
+ $string = $db->getSysConf('plugins');
+ return $db->valueToArray($string);
+ }
+
+ /**
+ * Set the enabled plugins.
+ */
+ public function setActivePlugins($plugins) {
+ $db = Database::getInstance();
+ $string = $db->arrayToValue($plugins);
+ return $db->setSysConf('plugins', $string);
+ }
+
+ /**
+ * Load the enabled plugins.
+ */
+ public function loadPlugins() {
+ if ($this->loaded) return;
+ $plugin_names = $this->getActivePlugins();
+ foreach ($plugin_names as $name) {
+ $this->plugins[$name] = $this->loadPlugin($name);
+ }
+ $this->loaded = true;
+ }
+
+ public function callHook($hook, $arg=null) {
+ $this->loadPlugins();
+ $ret = array();
+ foreach ($this->plugins as $name => $plugin) {
+ $ret[$name] = call_user_func(array($plugin, $hook),
+ &$arg);
+ }
+ return $ret;
+ }
+
+ public function staticHook($plugin_name, $hook) {
+ require_once("$plugin_name.class.php");
+ return call_user_func("$plugin_name::$hook");
+ }
+
+}
diff --git a/apps/um/lib/Singleton.class.php b/apps/um/lib/Singleton.class.php
new file mode 100644
index 0000000..2f8c74f
--- /dev/null
+++ b/apps/um/lib/Singleton.class.php
@@ -0,0 +1,12 @@
+<?php
+
+abstract class Singleton {
+ private static $instances = array();
+ public static function getInstance() {
+ $class = get_called_class();
+ if (!isset(self::$instances[$class])) {
+ self::$instances[$class] = new $class;
+ }
+ return self::$instances[$class];
+ }
+}
diff --git a/apps/um/lib/Site.class.php b/apps/um/lib/Site.class.php
new file mode 100644
index 0000000..1204089
--- /dev/null
+++ b/apps/um/lib/Site.class.php
@@ -0,0 +1,32 @@
+<?php
+require_once('Singleton.class.php');
+require_once('Database.class.php');
+
+class Site extends Singleton {
+ public function shortUrl($longUrl) {
+ $ch = curl_init('http://ur1.ca');
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_POST, true);
+ curl_setopt($ch, CURLOPT_POSTFILEDS,
+ 'longurl='.urlencode($longUrl));
+ $html = curl_exec();
+ preg_match('/Your ur1 is: <a href="([^"]*)">/',$html,$matches);
+ $shortUrl = $matches[1];
+ curl_close($ch);
+ return $shortUrl;
+ }
+
+ public function baseUrl() {
+ $base = $_SERVER['REQUEST_URI'];
+
+ $db = Database::getInstance();
+ if ($db !== null) {
+ $b = $db->getSysConf('baseurl');
+ if ($b != false) {
+ $base = $b;
+ }
+ }
+
+ return $base;
+ }
+}
diff --git a/apps/um/models/Auth.class.php b/apps/um/models/Auth.class.php
new file mode 100644
index 0000000..39f627e
--- /dev/null
+++ b/apps/um/models/Auth.class.php
@@ -0,0 +1,157 @@
+<?php
+require_once('Model.class.php');
+require_once('Login.class.php');
+require_once('Database.class.php');
+
+require_once('Group.class.php');
+require_once('User.class.php');
+
+class Auth extends Model {
+ /**********************************************************************\
+ * Multiton stuff *
+ \**********************************************************************/
+ private static $users = array();
+ public static function getInstance($uid) {
+ if (!isset(self::$users[$uid])) {
+ $type = Database::getInstance()->getStatus($uid);
+ switch ($type) {
+ case 0: // unactivated user
+ case 1: // user
+ case 2: $obj = new User($uid); // admin
+ case 3: $obj = new Group($uid);
+ case 4: $obj = new Auth($uid); // deleted
+ }
+ self::$users[$uid] = $obj;
+ }
+ return self::$users[$uid];
+ }
+
+ /**********************************************************************\
+ * Static stuff *
+ \**********************************************************************/
+ public static function isNameLegal($name) {
+ // Current rules:
+ // * Not in "$illegal_names"
+ // * Does not contain '.'
+ // * Fewer than 256 characters
+ $illegal_names = array('', 'new', 'index', 'all');
+ return true
+ && (!in_array($name, $illegal_names))
+ && (strpos($name,'.')===false)
+ && (strpos($name,'!')===false)
+ && (strlen($name)<256);
+ }
+
+ /**********************************************************************\
+ * Class stuff *
+ \**********************************************************************/
+ protected $uid = false;
+ public function __construct($uid) {
+ parent::__construct();
+ $this->uid = $uid;
+ }
+ public function getUID() {
+ return $this->uid;
+ }
+
+ /**********************************************************************\
+ * The 'auth' table. *
+ \**********************************************************************/
+
+ // Row Type ////////////////////////////////////////////////////////////
+ /**
+ * @return 0=unverified 1=user 2=admin 3=group 4=deleted
+ */
+ protected function getType() {
+ $type = $this->db->getStatus($this->uid);
+ return $type;
+ }
+ protected function setType($type) {
+ $logged_in_uid = Login::isLoggedIn();
+ $logged_in_obj = Auth::getInstance($logged_in_uid);
+ $is_admin = $logged_in_obj->isAdmin();
+ if (!$is_admin) return false;
+ return $this->db->setStatus($this->uid, $type);
+ }
+ public function isUser() {
+ $type = $this->getType();
+ return ($type===1) || ($type===2);
+ }
+ public function isAdmin() {
+ $type = $this->getType();
+ return ($type===2);
+ }
+ public function isGroup() {
+ $type = $this->getType();
+ return ($type===3);
+ }
+ public function setUser($is_user) {
+ $is_user = ($is_user?true:false);
+ if ($this->isUser() != $is_user) {
+ $this->setType($is_user?1:0);
+ }
+ }
+ public function setAdmin($is_admin) {
+ $is_admin = ($is_admin?true:false);
+ $is_user = $this->isUser();
+ $this->setType($is_admin?2:($is_user?1:0));
+ }
+ public function delete() {
+ $this->setType(4);
+ }
+
+ // Permissions /////////////////////////////////////////////////////////
+ public function canRead() {
+ $logged_in_uid = Login::isLoggedIn();
+ $is_me = ($logged_in_uid === $this->uid);
+
+ $logged_in_obj = Auth::getInstance($logged_in_uid);
+ $is_user = $logged_in_obj->isUser();
+
+ return ($is_me || $is_user);
+ }
+ public function canEdit() {
+ $logged_in_uid = Login::isLoggedIn();
+ $is_me = ($logged_in_uid === $this->uid);
+
+ $logged_in_obj = Auth::getInstance($logged_in_uid);
+ $is_admin = $logged_in_obj->isAdmin();
+
+ return ($is_me || $is_admin);
+ }
+
+ // [user|group]name ////////////////////////////////////////////////////
+ public function getName() {
+ if ($this->db===null) {
+ return false;
+ } else {
+ return $this->db->getUsername($this->uid);
+ }
+ }
+ public function setName($new_name) {
+ if (!$this->canEdit()) return false;
+ if (!self::isNameLegal($new_name)) return false;
+ return $this->db->setUsername($this->uid, $new_name);
+ }
+
+ /**********************************************************************\
+ * The 'users' table. *
+ \**********************************************************************/
+
+ public function getConf($setting) {
+ if (!$this->canRead()) return false;
+ return $this->db->getUserConf($this->uid, $setting);
+ }
+ public function setConf($setting, $value) {
+ if (!$this->canEdit()) return false;
+ return $this->db->setUserConf($this->uid, $setting, $value);
+ }
+ public function getConfArray($setting) {
+ $string = $this->getConf($setting);
+ return $this->db->valueToArray($string);
+ }
+ public function setConfArray($setting, $list) {
+ $string = $this->db->arrayToValue($list);
+ return $this->setConf($setting, $string);
+ }
+}
diff --git a/apps/um/models/ContactMethod.class.php b/apps/um/models/ContactMethod.class.php
new file mode 100644
index 0000000..1dd40ee
--- /dev/null
+++ b/apps/um/models/ContactMethod.class.php
@@ -0,0 +1,29 @@
+<?php
+global $CONTACT_METHODS;
+if (!isset($CONTACT_METHODS)) {
+ $CONTACT_METHODS = array();
+}
+
+class ContactMethod extends Model {
+ public $verb_slug = ''; // sms
+ public $addr_slug = ''; // phone
+ public $verb_text = ''; // text message
+ public $addr_text = ''; // phone number
+
+ public $handler = null;
+
+ public function __construct($verb_slug, $addr_slug,
+ $verb_text, $addr_text)
+ {
+ $this->verb_slug = $verb_slug;
+ $this->addr_slug = $addr_slug;
+ $this->verb_text = $verb_text;
+ $this->addr_text = $addr_text;
+
+ global $CONTACT_METHODS;
+ $CONTACT_METHODS[$verb_slug] = $this;
+ }
+ public function setHandler($handler) {
+ $this->handler = $handler;
+ }
+}
diff --git a/apps/um/models/Group.class.php b/apps/um/models/Group.class.php
new file mode 100644
index 0000000..f981a4f
--- /dev/null
+++ b/apps/um/models/Group.class.php
@@ -0,0 +1,23 @@
+<?php
+require_once('Auth.class.php');
+
+class Group extends Auth {
+ public function __construct($uid) {
+ parent::__construct($uid);
+ }
+ public function getUID() {
+ return $this->uid;
+ }
+
+ /**********************************************************************\
+ * The 'auth' table. *
+ \**********************************************************************/
+
+ /**********************************************************************\
+ * The 'users' table. *
+ \**********************************************************************/
+
+ public function getMembers() {
+ return $this->db->getUsersInGroup($this->getName());
+ }
+}
diff --git a/apps/um/models/User.class.php b/apps/um/models/User.class.php
new file mode 100644
index 0000000..b6dbede
--- /dev/null
+++ b/apps/um/models/User.class.php
@@ -0,0 +1,25 @@
+<?php
+require_once('Auth.class.php');
+
+class User extends Auth {
+ public function __construct($uid) {
+ parent::__construct($uid);
+ }
+ public function getUID() {
+ return $this->uid;
+ }
+
+ /**********************************************************************\
+ * The 'auth' table. *
+ \**********************************************************************/
+
+ public function setPassword($password) {
+ if (!$this->canEdit()) return false;
+ return $this->db->setPassword($this->uid, $password);
+ }
+
+ /**********************************************************************\
+ * The 'users' table. *
+ \**********************************************************************/
+
+}
diff --git a/apps/um/plugins/InformationPlugin.class.php b/apps/um/plugins/InformationPlugin.class.php
new file mode 100644
index 0000000..70ec8ac
--- /dev/null
+++ b/apps/um/plugins/InformationPlugin.class.php
@@ -0,0 +1,62 @@
+<?php
+
+require_once('Plugin.class.php');
+
+class InformationPlugin extends Plugin {
+ public static function configList() { return array(); }
+ public function init() {}
+ public static function description() {
+ return "Get information about the user.";
+ }
+ public function userConfig(&$arr) {
+ $group = 'Information';
+ $this->addConfigGroup($arr, $group);
+ $arr[$group][] = array('firstname','First Name','text');
+ $arr[$group][] = array('lastname','Last Name','text');
+ $arr[$group][] = array('school','Home School','text');
+ $arr[$group][] = array('hsclass','Highschool Class of','text');
+
+ $group = 'New Member Questions';
+ $this->addConfigGroup($arr, $group);
+ $arr[$group][] = array('q_why_team',
+ 'Why do you want to be a part of the robotics team?',
+ 'textarea');
+ $arr[$group][] = array('q_strengths',
+ 'What strengths do you have that can contribute to the robotics team?',
+ 'textarea');
+ $arr[$group][] = array('q_other_activities',
+ 'Other after school activities--sports, performing arts, extracurricular activities. (January through March)',
+ 'textarea');
+ $arr[$group][] = array('q_other_commitments',
+ 'After school/weekend work commitments. (January through March)',
+ 'textarea');
+ // TODO: Top 3 subteams
+ $arr[$group][] = array('q_who_you_know',
+ 'Who do you know that could possibly help our team this year? '.
+ 'This includes material resources, supplying or preparing food '.
+ 'for work nights/competitions, corporate sponsorship, travel, '.
+ 'engineers, computer programmers, web developers, and fundraisers.',
+ 'textarea');
+ $arr[$group][] = array('x_bogus_recommend',
+ "List two teachers who we could contact for your recommendation to be a member of this year's team.",
+ 'paragraph');
+ $arr[$group][] = array('q_teacher_recommend1', 'Teacher 1', 'text');
+ $arr[$group][] = array('q_teacher_recommend2', 'Teacher 2', 'text');
+ $arr[$group][] = array('x_bogus_agreement',
+ "I understand that if I am chosen to participate ".
+ "in the robotics project, I will represent my ".
+ "school in a positive manner. If I fail to do so, ".
+ "I may be removed from the team at any time. In ".
+ "addition, if I do not have good attendance ".
+ "during this project, I will not be allowed to ".
+ "travel with the team to the competitions. I also ".
+ "understand that all of the school rules will be ".
+ "in effect at all times.",
+ 'paragraph');
+ $arr[$group][] = array('q_i_agree',
+ "I agree",
+ 'checkbox');
+ }
+ public function sendPrivate($to, $id, $subject, $body) {}
+ public function sendBroadcast($id, $subject, $body) {}
+}
diff --git a/apps/um/plugins/ReCaptcha.class.php b/apps/um/plugins/ReCaptcha.class.php
new file mode 100644
index 0000000..165493b
--- /dev/null
+++ b/apps/um/plugins/ReCaptcha.class.php
@@ -0,0 +1,47 @@
+<?php
+// We only include the recaptchalib.php file when we use it because we don't
+// want it polluting the global namespace thing.
+
+class ReCaptcha extends Plugin {
+ protected $config = array('public_key'=>'',
+ 'private_key'=>'');
+ public static function description() {
+ return 'Add a reCaptcha to keep out spam users.';
+ }
+ public static function configList() {
+ return array('public_key'=>'text',
+ 'private_key'=>'text');
+ }
+ public function init() {}
+
+ private $resp = null;
+ private function getResp() {
+ if ($this->resp===null) {
+ require_once('recaptchalib.php');
+ @$response = $_POST['recaptcha_response_field'];
+ @$challenge = $_POST['recaptcha_challenge_field'];
+ $this->resp = recaptcha_check_answer($this->config['private_key'],
+ $_SERVER['REMOTE_ADDR'],
+ $challenge,
+ $response);
+ }
+ return $this->resp;
+ }
+
+ private function getError() {
+ if ($_POST["recaptcha_response_field"] && !$this->antispam_verify()) {
+ return $this->getResp()->error;
+ } else {
+ return false;
+ }
+ }
+
+ public function antispam_verify() {
+ return $this->getResp()->is_valid;
+ }
+
+ public function antispam_html() {
+ require_once('recaptchalib.php');
+ return recaptcha_get_html($this->config['public_key'], $this->getError());
+ }
+}
diff --git a/apps/um/views/Template.class.php b/apps/um/views/Template.class.php
new file mode 100644
index 0000000..9d55b75
--- /dev/null
+++ b/apps/um/views/Template.class.php
@@ -0,0 +1,316 @@
+<?php
+require_once('Singleton.class.php');
+require_once('Site.class.php');
+
+require_once('Login.class.php');// used to see if logged in
+require_once('Auth.class.php');// used to get username if we are
+
+class Template extends Singleton {
+ private $indent = 0;
+ private $ret = false;
+
+ public function status($status) {
+ header($_SERVER["SERVER_PROTOCOL"]." $status");
+ header("Status: $status");
+ }
+
+ public function setRet($ret) {
+ $this->ret = $ret;
+ }
+
+ private function tabs() {
+ $str = '';
+ for ($i=0;$i<$this->indent;$i++) { $str .= "\t"; }
+ return $str;
+ }
+
+ private function attr($attr='') {
+ $tags='';
+ if (is_array($attr)) {
+ foreach($attr as $key=>$value) {
+ $tags .= " $key=\"$value\"";
+ }
+ }
+ return $tags;
+ }
+
+ public function tag($tag, $attr='', $content=false) {
+ $tags = $this->attr($attr);
+ $str = $this->tabs()."<$tag$tags";
+ if ($content===false) {
+ $str.= " />";
+ } else {
+ $str.= ">$content</$tag>";
+ }
+ $str.= "\n";
+ if ($this->ret) return $str;
+ echo $str;
+ }
+
+ public function openTag($tag, $attr='') {
+ $tags = $this->attr($attr);
+ $str = $this->tabs()."<$tag$tags>\n";
+ $this->indent++;
+ if ($this->ret) return $str;
+ echo $str;
+ }
+
+ public function closeTag($tag) {
+ $this->indent--;
+ $str = $this->tabs()."</$tag>\n";
+ if ($this->ret) return $str;
+ echo $str;
+ }
+
+ public function text($text) {
+ $str = $this->tabs().$text."\n";
+ if ($this->ret) return $str;
+ echo $str;
+ }
+
+ public function paragraph($text, $attr='', $return=false) {
+ $tabs = $this->tabs();
+ $tags = $this->attr($attr);
+ $str = $tabs."<p$tags>";
+ $str.= wordwrap($text, 78-($this->indent*8), "\n$tabs ");
+ $str.= "</p>\n";
+ if ($this->ret||$return) return $str;
+ echo $str;
+ }
+
+ public function link($target, $text, $return=false) {
+ $ret = $this->ret;
+ $this->ret |= $return;
+ $str = $this->tag('a', array('href'=>$target), $text);
+ $this->ret = $ret;
+ if ($this->ret||$return) return $str;
+ echo $str;
+ }
+ public function url($page) {
+ return Site::getInstance()->baseUrl().$page;
+ }
+
+ public function row($cells) {
+ $str = $this->openTag('tr');
+ foreach ($cells as $cell)
+ $str.= $this->tag('td', array(), $cell);
+ $str.= $this->closeTag('tr');
+ if ($this->ret) return $str;
+ echo $str;
+ }
+ private function css($file, $media) {
+ $str.= $this->tag('link', array('rel'=>"stylesheet",
+ 'type'=>"text/css",
+ 'href'=>$this->url($file),
+ 'media'=>$media));
+ if ($this->ret) return $str;
+ echo $str;
+ }
+ public function header($title) {
+ // username=false if not logged in or not connected to DB
+ $username = Auth::getInstance(Login::isLoggedIn())->getName();
+
+ $ret = $this->ret;
+ $this->ret = true;
+
+ $logged_in = ($username!==false);
+
+ $str = '<?xml version="1.0" encoding="utf-8"?>'."\n";
+ $str.= '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"'."\n";
+ $str.= '"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">'."\n";
+
+ $xmlns = "http://www.w3.org/1999/xhtml";
+ $str.= $this->openTag('html', array('xmlns'=>$xmlns,
+ 'lang'=>"en-us",
+ 'dir'=>"ltr"));
+ $this->indent = 0; // don't indent for the <html> tag
+
+ $str.= $this->openTag('head');
+ $str.= $this->tag('title', array(), htmlspecialchars($title));
+ $str.= $this->css('style.css', 'all');
+ $str.= $this->css('screen.css', 'screen');
+ $str.= $this->css('print.css', 'print');
+ $str.= $this->closeTag('head');
+
+ $body_class = 'logged'.($logged_in?'in':'out');
+ $str.= $this->openTag('body', array('class'=>$body_class));
+
+ $str.= $this->openTag('div', array('class'=>'infobar'));
+ if ($logged_in) {
+ $user = htmlentities($username);
+
+ $str.= $this->link($this->url(''), "Home");
+ $str.= $this->link($this->url("users/$user"),"@$user");
+ $str.= $this->logout_button('Logout');
+ } else {
+ $url=$_SERVER['REQUEST_URI'];
+ $str.= $this->openTag('form',
+ array('action'=>$this->url('auth'),
+ 'method'=>'post'));
+ $str.= $this->tag('input', array('type'=>'hidden',
+ 'name'=>'action',
+ 'value'=>'login'));
+ $str.= $this->tag('input', array('type'=>'hidden',
+ 'name'=>'url',
+ 'value'=>$url));
+ $str.= $this->tag('label',
+ array('for'=>'username'),'Username:');
+ $str.= $this->tag('input', array('type'=>'text',
+ 'name'=>'username',
+ 'id'=>'username'));
+ $str.= $this->tag('label',
+ array('for'=>'password'),'Password:');
+ $str.= $this->tag('input', array('type'=>'password',
+ 'name'=>'password',
+ 'id'=>'password'));
+ $str.= $this->tag('input', array('type'=>'submit',
+ 'value'=>'Login'));
+ $str.= $this->link($this->url("users/new"),'New Account');
+ $str.= $this->closeTag('form');
+ }
+ $str.= $this->closeTag('div');
+
+ $str.= $this->openTag('div',array('class'=>'main'));
+ $str.= $this->openTag('div',array('class'=>'main_sub'));
+
+ $this->ret = $ret;
+ if ($this->ret) return $str;
+ echo $str;
+ }
+
+ public function footer() {
+ $str = $this->closeTag('div');
+ $str.= $this->closeTag('div');
+ $str.= $this->closeTag('body');
+ $str.= $this->closeTag('html');
+
+ if ($this->ret) return $str;
+ echo $str;
+ }
+
+ public function openFieldset($name, $lock=false) {
+ $class = ($lock?' class="readonly"':'');
+ $str = $this->text("<fieldset$class><legend>$name</legend><ul>");
+ $this->indent++;
+ if ($this->ret) return $str;
+ echo $str;
+ }
+
+ public function closeFieldset() {
+ $this->indent--;
+ $str = $this->text("</ul></fieldset>");
+ if ($this->ret) return $str;
+ echo $str;
+ }
+
+ public function input($id, $label, $hint, $html, $tags=null) {
+ if ($tags===null) { $tags=array(); }
+ $str = $this->openTag('li', $tags);
+ $str.= $this->tag('label', array('for'=>$id), $label);
+ $str.= $this->text($html);
+ if (strlen($hint)>0) {
+ $str.=$this->paragraph($hint,
+ Array('class'=>'form_data'));
+ }
+ $str.= $this->closeTag('li');
+ if ($this->ret) return $str;
+ echo $str;
+ }
+
+ private function inputStr($type, $id, $default, $lock) {
+ $value = htmlentities($default);
+ $tag = ($lock?"readonly='readonly' ":'');
+ return "<input type='$type' name='$id' id='$id' value=\"$value\" $tag/>";
+ }
+ public function inputTextArea($id, $label, $hint='', $default='', $lock=FALSE) {
+ $value = htmlentities($default);
+ $tag = ($lock?"readonly='readonly' ":'');
+ return $this->input($id, $label, $hint,
+ "<textarea name='$id' id='$id' $tag>$value</textarea>",
+ array('class'=>'wide'));
+ }
+
+ public function inputText($id, $label, $hint='', $default='', $lock=FALSE) {
+ return $this->input($id, $label, $hint,
+ $this->inputStr('text', $id, $default, $lock));
+ }
+
+ public function inputPassword($id, $label, $hint='', $default='', $lock=FALSE) {
+ return $this->input($id, $label, $hint,
+ $this->inputStr('password', $id, $default, $lock));
+ }
+
+ public function inputNewPassword($id, $label, $default='', $lock=FALSE) {
+ return $this->input($id, $label,
+ "Type the same password twice, to make sure you don't mistype.",
+ $this->inputStr('password', $id, $default, $lock).
+ "\n".$this->tabs()."\t".
+ $this->inputStr('password', $id.'_verify', $default,$lock));
+ }
+ public function inputBool($id, $label, $hint='', $default=FALSE, $lock=FALSE) {
+ $tag = '';
+ if ($lock) $tag.= "readonly='readonly' ";
+ if ($default) $tag.= "checked='checked' ";
+ return $this->input($id, $label, $hint,
+ "<input type='hidden' name='$id' value='false' />".
+ "<input type='checkbox' id='$id' name='$id' value='true' $tag>");
+
+ $attrib = array('type'=>'checkbox',
+ 'id'=>$id,
+ 'name'=>$name.'[]',
+ 'value'=>$value);
+ if ($default) $attrib['checked']='checked';
+ if ($lock ) $attrib['readonly']='readonly';
+
+ $str = $this->openTag('li');
+ $str.= $this->tag('input', $attrib);
+ $str.= $this->tag('label', array('for'=>$id), $label);
+ $str.= $this->closeTag('li');
+
+ if ($this->ret) return $str;
+ echo $str;
+
+ }
+
+ public function inputBoolArray($name, $value, $label, $default=FALSE, $lock=FALSE) {
+ $id = $name.'_'.$value;
+ $attrib = array('type'=>'checkbox',
+ 'id'=>$id,
+ 'name'=>$name.'[]',
+ 'value'=>$value);
+ if ($default) $attrib['checked']='checked';
+ if ($lock ) $attrib['readonly']='readonly';
+
+ $str = $this->openTag('li');
+ $str.= $this->tag('input', $attrib);
+ $str.= $this->tag('label', array('for'=>$id), $label);
+ $str.= $this->closeTag('li');
+
+ if ($this->ret) return $str;
+ echo $str;
+
+ }
+
+ public function inputP($text, $error=false) {
+ $str = $this->openTag('li');
+ $str.=$this->paragraph($text,
+ array('class'=>($error?' error':'')));
+ $str.= $this->closeTag('li');
+ if ($this->ret) return $str;
+ echo $str;
+ }
+
+ public function logout_button($text) {
+ $str = $this->openTag('form',array('action'=>$this->url('auth'),
+ 'method'=>"post",
+ 'style'=>'display:inline'));
+ $str.= $this->tag('input', array('type'=>'hidden',
+ 'name'=>'action',
+ 'value'=>'logout'));
+ $str.= $this->tag('input', array('type'=>'submit',
+ 'value'=>$text));
+ $str.= $this->closeTag('form');
+ if ($this->ret) return $str;
+ echo $str;
+ }
+}
diff --git a/apps/um/views/includes/header-include.php b/apps/um/views/includes/header-include.php
new file mode 100644
index 0000000..3826b3a
--- /dev/null
+++ b/apps/um/views/includes/header-include.php
@@ -0,0 +1,51 @@
+<?php
+function language_attributes() {
+ echo 'dir="ltr" lang="en-US"';
+}
+
+function bloginfo($param) {
+ switch ($param) {
+ case 'charset': echo 'UTF-8'; break;
+ case 'stylesheet_url': echo "/wp/wp-content/themes/kilabytes/style.css"; break;
+ case 'pingback_url': echo 'http://www.mckenzierobotics.org/wp/xmlrpc.php'; break;
+ default: echo ''; break;
+ }
+}
+
+function wp_title($foo, $bar, $baz) {
+ global $_title_;
+ echo $_title_;
+}
+
+function get_bloginfo($foo, $bar) {
+ return false;
+}
+
+function get_template_directory_uri() {
+ return 'http://www.mckenzierobotics.org/wp/wp-content/themes/twentyeleven';
+}
+
+function is_singular() {
+ return false;
+}
+
+function wp_head() {
+ echo '';
+}
+
+global $paged, $page;
+$paged = 0;
+$page = 0;
+
+$username = Auth::getInstance(Login::isLoggedIn())->getName();
+$logged_in = ($username!==false);
+
+$ret = $this->ret;
+$this->ret = true;
+
+function body_class() {
+ $body_class = 'logged'.($logged_in?'in':'out');
+ echo 'class="'+$body_class+'"';
+}
+
+require(dirname(__FILE__)+"/header.php"); \ No newline at end of file
diff --git a/apps/um/views/includes/header.php b/apps/um/views/includes/header.php
new file mode 100644
index 0000000..0a3e161
--- /dev/null
+++ b/apps/um/views/includes/header.php
@@ -0,0 +1,84 @@
+<?php
+function language_attributes() {
+ echo 'dir="ltr" lang="en-US"';
+}
+/**
+ * The Header for our theme.
+ *
+ * Displays all of the <head> section and everything up till <div id="main">
+ *
+ * @package WordPress
+ * @subpackage Twenty_Eleven
+ * @since Twenty Eleven 1.0
+ */
+?><!DOCTYPE html>
+<!--[if IE 6]>
+<html id="ie6" <?php language_attributes(); ?>>
+<![endif]-->
+<!--[if IE 7]>
+<html id="ie7" <?php language_attributes(); ?>>
+<![endif]-->
+<!--[if IE 8]>
+<html id="ie8" <?php language_attributes(); ?>>
+<![endif]-->
+<!--[if !(IE 6) | !(IE 7) | !(IE 8) ]><!-->
+<html <?php language_attributes(); ?>>
+<!--<![endif]-->
+<head>
+<meta charset="<?php bloginfo( 'charset' ); ?>" />
+<meta name="viewport" content="width=device-width" />
+<title><?php
+ /*
+ * Print the <title> tag based on what is being viewed.
+ */
+ global $_title_;
+ echo $_title_;
+ ?></title>
+<link rel="profile" href="http://gmpg.org/xfn/11" />
+<link rel="stylesheet" type="text/css" media="all" href="<?php bloginfo( 'stylesheet_url' ); ?>" />
+<link rel="pingback" href="<?php bloginfo( 'pingback_url' ); ?>" />
+<!--[if lt IE 9]>
+<script src="<?php echo get_template_directory_uri(); ?>/js/html5.js" type="text/javascript"></script>
+<![endif]-->
+<?php
+ /* We add some JavaScript to pages with the comment form
+ * to support sites with threaded comments (when in use).
+ */
+ if ( is_singular() && get_option( 'thread_comments' ) )
+ wp_enqueue_script( 'comment-reply' );
+
+ /* Always have wp_head() just before the closing </head>
+ * tag of your theme, or you will break many plugins, which
+ * generally use this hook to add elements to <head> such
+ * as styles, scripts, and meta tags.
+ */
+ wp_head();
+?>
+</head>
+
+<body <?php body_class(); ?>>
+<div id="page" class="hfeed">
+ <header id="branding" role="banner">
+ <hgroup>
+ <?php $url = esc_url(home_url('/')); ?>
+ <h1 id="team-number"><a href="<?php echo $url;?>">1024</a></h1>
+ <h1 id="team-name"><a href="<?php echo $url;?>" rel="home">Kil-A-Bytes</a></h1>
+ <h2><a href="<?php echo $url;?>">M<span class="lower">c</span>Kenzie Center for Innovation and Technology</a></h2>
+ </hgroup>
+
+ <div class="only-search with-image">
+ <?php get_search_form(); ?>
+ </div>
+
+ <nav id="access" role="navigation">
+ <h3 class="assistive-text"><?php _e( 'Main menu', 'twentyeleven' ); ?></h3>
+ <?php /* Allow screen readers / text browsers to skip the navigation menu and get right to the good stuff. */ ?>
+ <div class="skip-link"><a class="assistive-text" href="#content" title="<?php esc_attr_e( 'Skip to primary content', 'twentyeleven' ); ?>"><?php _e( 'Skip to primary content', 'twentyeleven' ); ?></a></div>
+ <div class="skip-link"><a class="assistive-text" href="#secondary" title="<?php esc_attr_e( 'Skip to secondary content', 'twentyeleven' ); ?>"><?php _e( 'Skip to secondary content', 'twentyeleven' ); ?></a></div>
+ <?php /* Our navigation menu. If one isn't filled out, wp_nav_menu falls back to wp_page_menu. The menu assiged to the primary position is the one used. If none is assigned, the menu with the lowest ID is used. */ ?>
+ <?php wp_nav_menu( array( 'theme_location' => 'primary' ) ); ?>
+ </nav><!-- #access -->
+ </header><!-- #branding -->
+
+
+ <div id="main"> \ No newline at end of file
diff --git a/apps/um/views/pages/auth/badrequest.html.php b/apps/um/views/pages/auth/badrequest.html.php
new file mode 100644
index 0000000..c1fe726
--- /dev/null
+++ b/apps/um/views/pages/auth/badrequest.html.php
@@ -0,0 +1,11 @@
+<?php global $VARS;
+$t = $VARS['template'];
+
+$t->status('400 Bad Request');
+$t->header('Authentication');
+$t->paragraph('The recieved POST request was malformed/invalid. '.
+ 'If you got here from a link, this is a bug; '.
+ 'Let the admin know.'.
+ 'If you got here from outside, then the API is being '.
+ 'used incorrectly.');
+$t->footer();
diff --git a/apps/um/views/pages/auth/index.html.php b/apps/um/views/pages/auth/index.html.php
new file mode 100644
index 0000000..ac80140
--- /dev/null
+++ b/apps/um/views/pages/auth/index.html.php
@@ -0,0 +1,12 @@
+<?php global $VARS;
+$t = $VARS['template'];
+$username = $VARS['username'];
+
+$t->header('Authentication');
+
+$t->openTag('div',array('class'=>'login'));
+$t->text("Logged in as ".htmlentities($username).'.');
+$t->logout_button('Logout');
+$t->closeTag('div');
+
+$t->footer(); \ No newline at end of file
diff --git a/apps/um/views/pages/auth/login.html.php b/apps/um/views/pages/auth/login.html.php
new file mode 100644
index 0000000..4e3e0e6
--- /dev/null
+++ b/apps/um/views/pages/auth/login.html.php
@@ -0,0 +1,54 @@
+<?php global $VARS;
+$t = $VARS['template'];
+$username = $VARS['username'];
+$password = $VARS['password'];
+
+switch ($VARS['login_code']) {
+case 1: $t->status('401 Unauthorized'); break;
+case 2: $t->status('404 Not Found'); break;
+}
+
+$t->header('Authentication');
+
+$t->openTag('form',array('action'=>$t->url('auth'), 'method'=>"post"));
+$t->openFieldset('Login');
+switch ($VARS['login_code']) {
+case -1: break;
+case 0:
+ $t->inputP('Successfully logged in as '.
+ htmlentities($username).'.');
+ if (isset($VARS['url'])) {
+ $url = htmlentities($VARS['url']);
+ $t->inputP($t->link($url,
+ 'Return to the page you were on.',
+ true));
+ }
+ $t->closeFieldset();
+ $t->closeTag('form');
+ return;
+ break;
+case 1:
+ $t->inputP("Password does not match username.",
+ array('class'=>'error'));
+ break;
+case 2:
+ $t->inputP("Username <q>$username</q> does not exist.");
+ $username = '';
+ break;
+}
+$t->inputText( 'username', 'Username:', '', $username);
+$t->inputPassword('password', 'Password:', '', $password);
+$t->openTag('li');
+$t->tag('input', array('type'=>'submit', 'value'=>'Login'));
+$t->closeTag('li');
+$t->closeFieldset();
+$t->tag('input', array('type'=>'hidden',
+ 'name'=>'action',
+ 'value'=>'login'));
+if (isset($VARS['url'])) {
+ $url = htmlentities($VARS['url']);
+ $t->tag('input', array('type'=>'hidden',
+ 'name'=>'url',
+ 'value'=>$url));
+}
+$t->closeTag('form');
diff --git a/apps/um/views/pages/auth/logout.html.php b/apps/um/views/pages/auth/logout.html.php
new file mode 100644
index 0000000..2d00998
--- /dev/null
+++ b/apps/um/views/pages/auth/logout.html.php
@@ -0,0 +1,6 @@
+<?php global $VARS;
+$t = $VARS['template'];
+
+$t->header('Authentication');
+$t->paragraph('Logged out');
+$t->footer();
diff --git a/apps/um/views/pages/groups/401.html.php b/apps/um/views/pages/groups/401.html.php
new file mode 100644
index 0000000..23e3778
--- /dev/null
+++ b/apps/um/views/pages/groups/401.html.php
@@ -0,0 +1,15 @@
+<?php global $VARS;
+$t = $VARS['template'];
+
+$t->status('401 Unauthorized');
+$t->header('Unauthorized');
+$t->tag('h1', array(), "401: Unauthorized");
+if ($VARS['uid']===false) {
+ // Not logged in
+ $t->paragraph('You need to be logged in to view group-data.');
+} else {
+ // Logged in, so the account must not activated
+ $t->paragraph('Your account needs to be activated by an administrator '.
+ 'to group-data.');
+}
+$t->footer();
diff --git a/apps/um/views/pages/http404.html.php b/apps/um/views/pages/http404.html.php
new file mode 100644
index 0000000..730b0ee
--- /dev/null
+++ b/apps/um/views/pages/http404.html.php
@@ -0,0 +1,16 @@
+<?php global $VARS;
+$t = $VARS['template'];
+
+$routed = implode('/', $VARS['routed']);
+$remainder = implode('/', $VARS['remainder']);
+$full = $routed.'/'.$remainder;
+
+$t->status('404 Not Found');
+$t->header('Page Not Found');
+$t->tag('h1',array(),"404: Not Found");
+$t->paragraph("Awe man, the page you requested wasn't found.");
+$t->paragraph('This folder was found: '.
+ '<tt>'.$t->link($t->url($routed), $routed.'/', true).'</tt>');
+$t->paragraph("But this file in it wasn't: ".
+ '<tt>'.$full.'</tt>');
+$t->footer();
diff --git a/apps/um/views/pages/index.html.php b/apps/um/views/pages/index.html.php
new file mode 100644
index 0000000..71b0091
--- /dev/null
+++ b/apps/um/views/pages/index.html.php
@@ -0,0 +1,8 @@
+<?php global $VARS;
+$t = $VARS['template'];
+
+$t->header('Main Page');
+$t->tag('h1', array(), "Message Manager");
+$t->paragraph($t->link($t->url('users/new'),'Register to be on the team', true), array('style'=>'font-size:5em'));
+$t->link($t->url('users'), 'List of all users');
+$t->footer();
diff --git a/apps/um/views/pages/plugins/401.html.php b/apps/um/views/pages/plugins/401.html.php
new file mode 100644
index 0000000..5b1b222
--- /dev/null
+++ b/apps/um/views/pages/plugins/401.html.php
@@ -0,0 +1,9 @@
+<?php global $VARS;
+$t = $VARS['template'];
+
+$t->status('401 Unauthorized');
+$t->header('Unauthorized');
+$t->tag('h1',array(),"401: Unauthorized");
+$t->paragraph('You need to be logged in as an admin to edit global plugin '.
+ 'settings.');
+$t->footer();
diff --git a/apps/um/views/pages/plugins/index.html.php b/apps/um/views/pages/plugins/index.html.php
new file mode 100644
index 0000000..b182288
--- /dev/null
+++ b/apps/um/views/pages/plugins/index.html.php
@@ -0,0 +1,42 @@
+<?php global $VARS;
+require_once('Database.class.php');
+$t = $VARS['template'];
+$plugins = $VARS['plugins'];
+$db = Database::getInstance();
+
+$t->header('Administrator Plugin Management');
+$t->openTag('form',array('method'=>'post','action'=>$t->url('plugins')));
+
+foreach ($plugins as $plugin) {
+ $t->setRet(true);
+ $props = array('type'=>'checkbox',
+ 'name'=>'plugins[]',
+ 'id'=>'plugins_'.$plugin['name'],
+ 'value'=>$plugin['name']);
+ if ($plugin['active']==true) {
+ $props['checked'] = 'checked';
+ }
+ $box = $t->tag('input', $props);
+ $t->setRet(false);
+ $t->openFieldset($plugin['name'].$box);
+
+ $t->inputP($plugin['description']);
+ foreach ($plugin['config'] as $param => $type) {
+ $name = $plugin['key'].'['.$param.']';
+ $value = $db->getPluginConf($plugin['name'], $param);
+ $hint = "Type: $type";
+ switch ($type) {
+ case 'text':
+ case 'int':
+ $t->inputText( $name, $param, $hint, $value); break;
+ case 'password':
+ $t->inputPassword($name, $param, $hint, $value); break;
+ }
+ }
+ $t->closeFieldset();
+}
+
+$t->tag('input', array('type'=>'submit',
+ 'value'=>'Save/Update'));
+$t->closeTag('form');
+$t->footer();
diff --git a/apps/um/views/pages/users/401.html.php b/apps/um/views/pages/users/401.html.php
new file mode 100644
index 0000000..0a5a1ce
--- /dev/null
+++ b/apps/um/views/pages/users/401.html.php
@@ -0,0 +1,15 @@
+<?php global $VARS;
+$t = $VARS['template'];
+
+$t->status('401 Unauthorized');
+$t->header('Unauthorized');
+$t->tag('h1', array(), "401: Unauthorized");
+if ($VARS['uid']===false) {
+ // Not logged in
+ $t->paragraph('You need to be logged in to view user-data.');
+} else {
+ // Logged in, so the account must not activated
+ $t->paragraph('Your account needs to be activated by an administrator '.
+ 'to view user-data.');
+}
+$t->footer();
diff --git a/apps/um/views/pages/users/404.html.php b/apps/um/views/pages/users/404.html.php
new file mode 100644
index 0000000..00f9dca
--- /dev/null
+++ b/apps/um/views/pages/users/404.html.php
@@ -0,0 +1,10 @@
+<?php global $VARS;
+$t = $VARS['template'];
+$username = $VARS['username'];
+
+$t->status('404 Not Found');
+$t->header('User Not Found');
+$t->tag('h1',array(),"404: Not Found");
+$t->paragraph('No user with the name <q>'.
+ htmlentities($username).'</q> exists.');
+$t->footer();
diff --git a/apps/um/views/pages/users/500.html.php b/apps/um/views/pages/users/500.html.php
new file mode 100644
index 0000000..339fe63
--- /dev/null
+++ b/apps/um/views/pages/users/500.html.php
@@ -0,0 +1,15 @@
+<?php global $VARS;
+require_once('Database.class.php');
+$t = $VARS['template'];
+$db = Database::getInstance();
+
+$t->status('500 Internal Server Error');
+$t->header('Unknown error');
+$t->paragraph("An unknown error was encountered when creating ".
+ "the user. The username appears to be free, and ".
+ "the passwords match, so I'm assuming that the ".
+ "error is on our end. Sorry.");
+$t->paragraph("Here's a dump of the SQL error stack, it may ".
+ "help us find the issue:");
+$t->tag('pre', array(), htmlentities($db->mysql_error()));
+$t->footer();
diff --git a/apps/um/views/pages/users/created.html.php b/apps/um/views/pages/users/created.html.php
new file mode 100644
index 0000000..d3027cc
--- /dev/null
+++ b/apps/um/views/pages/users/created.html.php
@@ -0,0 +1,18 @@
+<?php global $VARS;
+$t = $VARS['template'];
+$username = $VARS['username'];
+
+//$t->status('201 Created');
+header('Location: '.$t->url("users/$username"));
+$t->header('User created');
+/*$t->paragraph("You can go ahead and fill out more of your ".
+ "user information, (click the @username link at ".
+ "the top) but will need to wait for an ".
+ "administrator to approve your account before ".
+ "you can really use the site. Actually, ".
+ "filling your info out might help approval, so ".
+ "that the administrator can more easily see who ".
+ "you are.");
+*/
+$t->tag('h2',array(), $t->link($t->url("users/$username"), 'Go on to step 2'));
+$t->footer();
diff --git a/apps/um/views/pages/users/index.csv.php b/apps/um/views/pages/users/index.csv.php
new file mode 100644
index 0000000..0a69cee
--- /dev/null
+++ b/apps/um/views/pages/users/index.csv.php
@@ -0,0 +1,27 @@
+<?php global $VARS;
+$attribs = $VARS['attribs'];
+$users = $VARS['users'];
+
+function escape($value) {
+ if (is_bool($value)) {
+ return ($value?'true':'false');
+ } else {
+ $chars = "'" . '"' . '\\' . ',';
+ return addcslashes($value, $chars);
+ }
+}
+
+$arr = array();
+foreach ($attribs as $attrib) {
+ $arr[] = escape($attrib['name']);
+}
+echo implode(',', $arr)."\n";
+
+foreach ($users as $user) {
+ $arr = array();
+ foreach ($attribs as $attrib) {
+ $props = $user[$attrib['key']];
+ $arr[] = escape($props['value']);
+ }
+ echo implode(',', $arr)."\n";
+}
diff --git a/apps/um/views/pages/users/index.html.php b/apps/um/views/pages/users/index.html.php
new file mode 100644
index 0000000..159ff76
--- /dev/null
+++ b/apps/um/views/pages/users/index.html.php
@@ -0,0 +1,104 @@
+<?php global $VARS;
+$t = $VARS['template'];
+$attribs = $VARS['attribs'];
+$users = $VARS['users'];
+require_once('Login.class.php');
+
+$t->header('Users');
+
+$t->paragraph($t->link($t->url('users.csv'), "Download this as a spreadsheet.", true));
+
+$t->openTag('form', array('action'=>$t->url('users/index'),
+ 'method'=>'post'));
+
+if (Login::isLoggedIn()) {
+ $t->tag('input', array('type'=>'submit',
+ 'value'=>'Save/Update'));
+}
+
+$t->openTag('table', array('class'=>'sortable', 'id'=>'bar'));
+
+function table_head($attribs, $t) {
+ $t->openTag('tr');
+ foreach ($attribs as $attrib) {
+ switch ($attrib['type']) {
+ case 'bool': $class = 'small'; break;
+ default: $class = ''; break;
+ }
+ $t->tag('th', array('class'=>$class), $attrib['name']);
+ }
+ if (Login::isLoggedIn()) {
+ $t->tag('th', array(), '-');
+ }
+ $t->closeTag('tr');
+}
+
+$t->openTag('thead');
+table_head($attribs, $t);
+$t->closeTag('thead');
+
+$t->openTag('tfoot');
+table_head($attribs, $t);
+$t->closeTag('tfoot');
+
+$t->openTag('tbody');
+
+foreach ($users as $user) {
+ $t->openTag('tr');
+
+ foreach ($attribs as $attrib) {
+ $t->openTag('td');
+
+ $props = $user[$attrib['key']];
+
+ $bool = $attrib['type']=='bool';
+ if ($bool) {
+ $value = $props['value']=='true';
+ } else {
+ $value = $props['value'];
+ }
+ $editable = $props['editable'];
+ $post_key = $props['post_key'];
+
+ $arr = array('name'=>$post_key);
+ if (!$editable) {
+ $arr['readonly'] = 'readonly';
+ if ($bool) $arr['disabled'] = $disabled;
+ }
+ if ($bool) {
+ $t->tag('input', array('type'=>'hidden', 'name'=>$post_key, 'value'=>'false'));
+ if ($value==true) {
+ $arr['checked'] = 'checked';
+ }
+ $arr['value'] = 'true';
+ $arr['type'] = 'checkbox';
+ } else {
+ $t->tag('span', array('class'=>'cell_width'), $value);
+ $arr['value'] = $value;
+ $arr['type'] = 'text';
+ }
+
+ $t->tag('input', array('name'=>'_old['.$arr['name'].']',
+ 'value'=>$arr['value'],
+ 'type'=>'hidden'));
+ $t->tag('input', $arr);
+ $t->closeTag('td');
+ }
+
+ if (Login::isLoggedIn()) {
+ $t->openTag('td');
+ $t->link($t->url('users/'.$user['auth_name']['value']), 'More');
+ $t->closeTag('td');
+ }
+ $t->closeTag('tr');
+}
+
+$t->closeTag('tbody');
+$t->closeTag('table');
+
+if (Login::isLoggedIn()) {
+ $t->tag('input', array('type'=>'submit',
+ 'value'=>'Save/Update'));
+}
+
+$t->footer();
diff --git a/apps/um/views/pages/users/individual.html.php b/apps/um/views/pages/users/individual.html.php
new file mode 100644
index 0000000..39360b7
--- /dev/null
+++ b/apps/um/views/pages/users/individual.html.php
@@ -0,0 +1,147 @@
+<?php global $VARS, $CONTACT_METHODS;
+$t = $VARS['template'];
+$users = $VARS['users'];
+$username = $VARS['username'];
+
+function inputText($user, $key, $label, $hint='') {
+ global $VARS; $t = $VARS['template'];
+ $current_setting = $user->getConf($key);
+ $t->inputText("user_$key", $label, $hint, $current_setting,
+ !$user->canEdit());
+}
+function inputTextarea($user, $key, $label, $hint='') {
+ global $VARS; $t = $VARS['template'];
+ $current_setting = $user->getConf($key);
+ $t->inputTextarea("user_$key", $label, $hint, $current_setting,
+ !$user->canEdit());
+}
+
+function inputBool($user, $key, $label, $hint='') {
+ global $VARS; $t = $VARS['template'];
+ $current_setting = $user->getConf($key)=='true';
+ $t->inputBool("user_$key", $label, $hint, $current_setting,
+ !$user->canEdit());
+}
+
+function inputArray($user, $key, $arr) {
+ global $VARS; $t = $VARS['template'];
+ $defaults = $user->getConfArray($key);
+
+ foreach ($arr as $value => $label) {
+ $t->inputBoolArray($key, $value, $label,
+ in_array($value, $defaults), !$user->canEdit());
+ }
+}
+
+function inputField($user, $arr) {
+ $fieldname = $arr[0];
+ $fieldlabel = $arr[1];
+ $fieldtype = $arr[2];
+
+ switch ($fieldtype) {
+ case 'text':
+ inputText($user, $fieldname, $fieldlabel, '');
+ break;
+ case 'textarea':
+ inputTextarea($user, $fieldname, $fieldlabel, '');
+ break;
+ case 'paragraph':
+ global $VARS; $t = $VARS['template'];
+ $t->inputP($fieldlabel);
+ break;
+ case 'checkbox':
+ inputBool($user, $fieldname, $fieldlabel, '');
+ break;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+if (count($users)>1) {
+ $t->header("Users: $username");
+} else {
+ $t->header("User: $username");
+}
+
+foreach($users as $user) {
+$username = $user->getName();
+
+$t->tag('h1', array(), ($user->canEdit()?'Edit':'View')." User <q>$username</q> (UID: ".$user->getUID().")");
+
+if ($user->canEdit()) {
+ $t->openTag('form', array('method'=>'post',
+ 'action'=>$t->url("users/$username")));
+} else {
+ $t->openTag('form');
+}
+
+$t->openFieldset("Login / Authentication");
+// Username ////////////////////////////////////////////////////////////////////
+if (isset($VARS['changed name']) && !$VARS['changed name']) {
+ $t->inputP("Error setting username to ".
+ "<q>$new_name</q>. This is probably because".
+ " a user with that name already exists.",
+ true);
+}
+$t->inputText('auth_name','Username',
+ "This is the name you use to log in, but it is also a ".
+ "short name that is used in various places, think of it ".
+ "as a sort of <q>Twitter name</q>.",
+ $user->getName(), !$user->canEdit());
+// Password ////////////////////////////////////////////////////////////////////
+if (@$VARS['pw_updated']===true) {
+ $t->inputP('Password successfully updated.');
+}
+if (@$VARS['pw mixmatch']===true) {
+ $t->inputP("Passwords don't match.", true);
+}
+if ($user->canEdit()) $t->inputNewPassword('auth_password','Reset Password');
+////////////////////////////////////////////////////////////////////////////////
+$t->closeFieldset();
+
+$t->openFieldset("Contact");
+// TODO: I should make this a setting for admins to set.
+$hints = array('email'=>
+ "Right now you can only have one email address, ".
+ "but I'm working on making it so you can have ".
+ "multiple.",
+ 'phone'=>
+ "A home phone number isn't much use here because it is ".
+ "used to text-message you (if you enable it), and ".
+ "contact you at competition."
+ );
+$use_arr = array();
+foreach ($CONTACT_METHODS as $method) {
+ inputText($user,
+ $method->addr_slug,
+ ucwords($method->addr_text),
+ $hints[$method->addr_slug]);
+ $use_arr[$method->verb_slug] = ucwords($method->verb_text);
+}
+
+$t->inputP("When I recieve a message, notify me using the following methods:");
+inputArray($user, 'use', $use_arr);
+$t->closeFieldSet();
+
+foreach ($VARS['config_options'] as $groupname=>$options) {
+ $t->openFieldset($groupname);
+ foreach ($options as $option) {
+ inputField($user, $option);
+ }
+ $t->closeFieldset();
+}
+
+$t->openFieldSet('Groups');
+$group_arr = array();
+foreach ($VARS['groups'] as $group_name) {
+ $group_arr[$group_name] = ucwords($group_name);
+}
+inputArray($user, 'groups', $group_arr);
+$t->closeFieldset();
+
+if ($user->canEdit()) {
+ $t->tag('input', array('type'=>'submit', 'value'=>'Save'));
+}
+$t->closeTag('form');
+}
+$t->footer();
diff --git a/apps/um/views/pages/users/individual.json.php b/apps/um/views/pages/users/individual.json.php
new file mode 100644
index 0000000..c3dee50
--- /dev/null
+++ b/apps/um/views/pages/users/individual.json.php
@@ -0,0 +1,27 @@
+<?php global $VARS, $CONTACT_METHODS;
+$t = $VARS['template'];
+$users = $VARS['users'];
+$username = $VARS['username'];
+
+$json = array();
+
+foreach ($users as $user_obj) {
+ $user_json = array();
+ $user_json['username'] = $user_obj->getName();
+ $user_json['uid'] = $user_obj->getUID();
+ foreach ($CONTACT_METHODS as $method) {
+ $field = $method->addr_slug;
+ $user_json[$field] = $user_obj->getConf($field);
+ }
+ foreach ($VARS['config_options'] as $groupname=>$options) {
+ foreach ($options as $option) {
+ $fieldname = $option[0];
+ $fieldlabel = $option[1];
+ $fieldtype = $option[2];
+ $user_json[$fieldname] = $user_obj->getConf($fieldname);
+ }
+ }
+ $json[] = $user_json;
+}
+
+echo json_encode($json); \ No newline at end of file
diff --git a/apps/um/views/pages/users/new-locked.html.php b/apps/um/views/pages/users/new-locked.html.php
new file mode 100644
index 0000000..dc7ad0d
--- /dev/null
+++ b/apps/um/views/pages/users/new-locked.html.php
@@ -0,0 +1,9 @@
+<?php global $VARS;
+$t = $VARS['template'];
+
+$t->status('403 Forbidden');
+$t->header('Create new user');
+
+$t->paragraph("Sorry, new user registration is disabled.");
+
+$t->footer();
diff --git a/apps/um/views/pages/users/new-logged-in.html.php b/apps/um/views/pages/users/new-logged-in.html.php
new file mode 100644
index 0000000..51823fe
--- /dev/null
+++ b/apps/um/views/pages/users/new-logged-in.html.php
@@ -0,0 +1,8 @@
+<?php global $VARS;
+$t = $VARS['template'];
+
+$t->header('Create new user');
+
+$t->paragraph("Dude, you're logged in, what are you doing creating an account?");
+
+$t->footer();
diff --git a/apps/um/views/pages/users/new.html.php b/apps/um/views/pages/users/new.html.php
new file mode 100644
index 0000000..9df376f
--- /dev/null
+++ b/apps/um/views/pages/users/new.html.php
@@ -0,0 +1,57 @@
+<?php global $VARS;
+$t = $VARS['template'];
+
+$t->header('Create new user');
+
+$t->openTag('form', array('method'=>'post',
+ 'action'=>$t->url('users')));
+
+$t->openFieldset("New User: Step 1");
+
+if ($VARS['userlist']) {
+ $t->inputP("If you may have already created a username, please, ".
+ "<em>please</em> check the ".
+ $t->link($t->url('users/'), 'user-list', true).
+ " to find your old username, instead of creating a new ".
+ "user. If you don't like the name, you can log in and ".
+ "change it.");
+}
+
+if (in_array('illegal name', $VARS['errors'])) {
+ $t->inputP("That is a forbidden username.", true);
+}
+if (in_array('user exists', $VARS['errors'])) {
+ $t->inputP("A user with that name already exists.");
+}
+$t->inputText('auth_name','Username',
+ "This is the name you use to log in, but it is also a ".
+ "short name that is used in various places, think of it ".
+ "as a sort of <q>Twitter name</q>.",$VARS['username']);
+
+@$password = $VARS['password1'];
+if (in_array('pw mixmatch', $VARS['errors'])) {
+ $t->inputP("The passwords didn't match.", true);
+ $password = '';
+}
+if (in_array('no pw', $VARS['errors'])) {
+ $t->inputP("You must set a password.", true);
+ $password = '';
+}
+$t->inputNewPassword('auth_password','Password', $password);
+
+if (in_array('no email', $VARS['errors'])) {
+ $t->inputP("You must provide an email address.", true);
+}
+$t->inputText('user_email', 'Email Address',
+ 'This is so that we can contact you. (duh).', $VARS['email']);
+$t->closeFieldset();
+
+foreach ($VARS['antispam_html'] as $html) {
+ echo $html;
+}
+
+$t->tag('input', array('type'=>'submit', 'value'=>'Go on to Step 2'));
+
+$t->closeTag('form');
+
+$t->footer();