diff options
Diffstat (limited to 'apps')
59 files changed, 4876 insertions, 0 deletions
diff --git a/apps/mm/controllers/Messages.class.php b/apps/mm/controllers/Messages.class.php new file mode 100644 index 0000000..d9decc9 --- /dev/null +++ b/apps/mm/controllers/Messages.class.php @@ -0,0 +1,98 @@ +<?php +require_once('Login.class.php'); +require_once('Auth.class.php'); +require_once('Database.class.php'); +require_once('Message.class.php'); +require_once('MimeMailParser.class.php'); + +Router::register('messages', 'Messages', 'index'); +Router::register('messages/index', 'Messages', 'index'); +Router::register('messages/*', 'Messages', 'message'); + +class Messages extends Controller { + private $msgdir; + + public function __construct() { + $db = Database::getInstance(); + $this->msgdir = $db->getSysConf('msgdir'); + } + + public function index($routed, $remainder) { + $parser = new MimeMailParser(); + $messages = array(); + $dh = opendir($this->msgdir); + while (($file = readdir($dh)) !== false) { + $path = $this->msgdir."/$file"; + if (is_file($path)) { + $parser->setPath($path); + + $date_string = $parser->getHeader('date'); + $date = strtotime($date_string); + if (!isset($messages[$date])) { + $messages[$date] = array(); + } + $messages[$date][] = + array('id'=>$file, + 'subject'=>$parser->getHeader('subject'), + 'from'=>$parser->getHeader('from')); + } + } + closedir($dh); + + $this->showView('messages/index', array('messages' => $messages)); + exit(); + } + + public function message($routed, $remainder) { + $uid = Login::isLoggedIn(); + if ($uid===false || !Auth::getInstance($uid)->isUser()) { + $this->http401($routed, $remainder); + return; + } + + $msg_id = $remainder[0];// We can trust the router that this is set + $msg = new Message($msg_id); + if ($msg === false) { + $this->http404($routed, $remainder); + return; + } + + @$part = $remainder[1]; + @$subpart = $remainder[2]; + + switch ($part) { + case '': + $this->showView('messages/frame', + array('msg'=>$msg)); + break; + case 'body': + require_once('Mime.class.php'); + header('Content-type: '.Mime::ext2mime(PAGE_EXT)); + $map = array('html'=>'html', + 'txt' =>'text'); + echo $msg->getMessageBody($map[PAGE_EXT]); + break; + case 'attachment': + $attachment_id = $subpart; + $attachments = $msg->getAttachments(); + $attachment = $attachments[$attachment_id]; + + $type = $attachment->getContentType(); + $filename = $attachment->getFilename(); + + header('Content-Type: '.$type); + header('Content-Disposition: attachment; filename='.$filename ); + while($bytes = $attachment->read()) { + echo $bytes; + } + break; + default: + array_push($routed, array_shift($remainder)); + $this->http404($routed, $remainder); + } + } + + public function http401($routed, $remainder) { + $this->showView('messages/401', array('uid'=>Login::isLoggedIn())); + } +}
\ No newline at end of file diff --git a/apps/mm/ext/GoogleVoice.class.php b/apps/mm/ext/GoogleVoice.class.php new file mode 100644 index 0000000..9638416 --- /dev/null +++ b/apps/mm/ext/GoogleVoice.class.php @@ -0,0 +1,84 @@ +<?PHP
+/*
+Version 0.2
+License This code is released under the MIT Open Source License. Feel free to do whatever you want with it.
+Author lostleon@gmail.com, http://www.lostleon.com/
+LastUpdate 05/28/2010
+*/
+class GoogleVoice
+{
+ public $username;
+ public $password;
+ public $status;
+ private $lastURL;
+ private $login_auth;
+ private $inboxURL = 'https://www.google.com/voice/m/';
+ private $loginURL = 'https://www.google.com/accounts/ClientLogin';
+ private $smsURL = 'https://www.google.com/voice/m/sendsms';
+
+ public function __construct($username, $password)
+ {
+ $this->username = $username;
+ $this->password = $password;
+ }
+
+ public function getLoginAuth()
+ {
+ $login_param = "accountType=GOOGLE&Email={$this->username}&Passwd={$this->password}&service=grandcentral&source=com.lostleon.GoogleVoiceTool";
+ $ch = curl_init($this->loginURL);
+ curl_setopt($ch, CURLOPT_HEADER, 0);
+ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+ curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (iPhone; U; CPU iPhone OS 2_2_1 like Mac OS X; en-us) AppleWebKit/525.18.1 (KHTML, like Gecko) Version/3.1.1 Mobile/5H11 Safari/525.20");
+ curl_setopt($ch, CURLOPT_REFERER, $this->lastURL);
+ curl_setopt($ch, CURLOPT_POST, "application/x-www-form-urlencoded");
+ curl_setopt($ch, CURLOPT_POST, true);
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $login_param);
+ $html = curl_exec($ch);
+ $this->lastURL = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
+ curl_close($ch);
+ $this->login_auth = $this->match('/Auth=([A-z0-9_-]+)/', $html, 1);
+ return $this->login_auth;
+ }
+
+ public function get_rnr_se()
+ {
+ $this->getLoginAuth();
+ $ch = curl_init($this->inboxURL);
+ curl_setopt($ch, CURLOPT_HEADER, 0);
+ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+ $headers = array("Authorization: GoogleLogin auth=".$this->login_auth, 'User-Agent: Mozilla/5.0 (iPhone; U; CPU iPhone OS 2_2_1 like Mac OS X; en-us) AppleWebKit/525.18.1 (KHTML, like Gecko) Version/3.1.1 Mobile/5H11 Safari/525.20');
+ curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
+ $html = curl_exec($ch);
+ $this->lastURL = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
+ curl_close($ch);
+ $_rnr_se = $this->match('!<input.*?name="_rnr_se".*?value="(.*?)"!ms', $html, 1);
+ return $_rnr_se;
+ }
+
+ public function sms($to_phonenumber, $smstxt)
+ {
+ $_rnr_se = $this->get_rnr_se();
+ $sms_param = "id=&c=&number=".urlencode($to_phonenumber)."&smstext=".urlencode($smstxt)."&_rnr_se=".urlencode($_rnr_se);
+ $ch = curl_init($this->smsURL);
+ curl_setopt($ch, CURLOPT_HEADER, 0);
+ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+ $headers = array("Authorization: GoogleLogin auth=".$this->login_auth, 'User-Agent: Mozilla/5.0 (iPhone; U; CPU iPhone OS 2_2_1 like Mac OS X; en-us) AppleWebKit/525.18.1 (KHTML, like Gecko) Version/3.1.1 Mobile/5H11 Safari/525.20');
+ curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
+ curl_setopt($ch, CURLOPT_REFERER, $this->lastURL);
+ curl_setopt($ch, CURLOPT_POST, true);
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $sms_param);
+ $this->status = curl_exec($ch);
+ $this->lastURL = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
+ curl_close($ch);
+ return $this->status;
+ }
+
+ private function match($regex, $str, $out_ary = 0)
+ {
+ return preg_match($regex, $str, $match) == 1 ? $match[$out_ary] : false;
+ }
+}
+?>
diff --git a/apps/mm/ext/Identica.class.php b/apps/mm/ext/Identica.class.php new file mode 100644 index 0000000..a3e62a9 --- /dev/null +++ b/apps/mm/ext/Identica.class.php @@ -0,0 +1,491 @@ +<?php +/** + * Copyright (c) <2009> Gianluca Urgese <g.urgese@jasone.it> + * + * 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 main identica-php class. Create an object to use your Identi.ca account from php. +*/ +class Identica { + /** Username:password format string */ + private $credentials; + + /** Contains the last HTTP status code returned */ + private $http_status; + + /** Contains the last API call */ + private $last_api_call; + + /** Contains the application calling the API */ + private $application_source; + + /** + * Identi.ca class constructor. + * @param username is a alphanumeric string to perform login on Identi.ca. + * @param password is a alphanumeric string to perform login on Identi.ca. + * @param source is the name of your application. + * @return An Identica object to use to perform all the operation. + */ + function Identica($username, $password, $source=false) { + $this->credentials = sprintf("%s:%s", $username, $password); + $this->application_source = $source; + } + + /** + * Returns the 20 most recent statuses from non-protected users who have set a custom user icon. + * @param format is the extension for the result file (xml, json, rss, atom). + * @param since_id returns only statuses with an ID greater than (that is, more recent than) the specified ID. + * @return the public timeline in the specified format. + */ + function getPublicTimeline($format, $since_id = 0) { + $api_call = sprintf("http://identi.ca/api/statuses/public_timeline.%s", $format); + if ($since_id > 0) { + $api_call .= sprintf("?since_id=%d", $since_id); + } + return $this->APICall($api_call); + } + + /** + * Returns the 20 most recent statuses posted by the authenticating user and that user's friends. + * @param format is the extension for the result file (xml, json, rss, atom). + * @param id returns only statuses from specified ID. + * @param since returns only statuses with an ID greater than (that is, more recent than) the specified ID. + * @return the friends timeline in the specified format. + */ + function getFriendsTimeline($format, $id = NULL, $since = NULL) { + if ($id != NULL) { + $api_call = sprintf("http://identi.ca/api/statuses/friends_timeline/%s.%s", $id, $format); + } + else { + $api_call = sprintf("http://identi.ca/api/statuses/friends_timeline.%s", $format); + } + if ($since != NULL) { + $api_call .= sprintf("?since=%s", urlencode($since)); + } + return $this->APICall($api_call, true); + } + + /** + * Returns the 20 most recent statuses posted from the authenticating user. + * @param format is the extension for the result file (xml, json, rss, atom). + * @param id get only statuses from specified ID. + * @param count specifies the number of statuses to retrieve. May not be greater than 200. + * @param since get only statuses with an ID greater than (that is, more recent than) the specified ID. + * @return the 20 most recent statuses posted from the authenticating user. + */ + function getUserTimeline($format, $id = NULL, $count = 20, $since = NULL) { + if ($id != NULL) { + $api_call = sprintf("http://identi.ca/api/statuses/user_timeline/%s.%s", $id, $format); + } + else { + $api_call = sprintf("http://identi.ca/api/statuses/user_timeline.%s", $format); + } + if ($count != 20) { + $api_call .= sprintf("?count=%d", $count); + } + if ($since != NULL) { + $api_call .= sprintf("%ssince=%s", (strpos($api_call, "?count=") === false) ? "?" : "&", urlencode($since)); + } + return $this->APICall($api_call, true); + } + + /** + * Returns a single status, specified by the id parameter below. The status's author will be returned inline. + * @param format is the extension for the result file (xml, json, rss, atom). + * @param id get only statuses from specified ID. + * @return a single status, specified by the id parameter. + */ + function showStatus($format, $id) { + $api_call = sprintf("http://identi.ca/api/statuses/show/%d.%s", $id, $format); + return $this->APICall($api_call); + } + + /** + * Updates the authenticating user's status. Request must be a POST. Statuses over 140 characters will be forceably truncated. + * @param status is the text of your status update. + * @return the current update from authenticating user. + */ + function updateStatus($status) { + $status = urlencode(stripslashes(urldecode($status))); + $api_call = sprintf("http://identi.ca/api/statuses/update.xml?status=%s", $status); + return $this->APICall($api_call, true, true); + } + + /** + * Returns a list of replies from authenticating user. + * @param format is the extension for the result file (xml, json, rss, atom). + * @param page specifies the page of results to retrieve. + * @return list of replies from authenticating user. + */ + function getReplies($format, $page = 0) { + $api_call = sprintf("http://identi.ca/api/statuses/replies.%s", $format); + if ($page) { + $api_call .= sprintf("?page=%d", $page); + } + return $this->APICall($api_call, true); + } + + /** + * Destroys the status specified by the required ID parameter. The authenticating user must be the author of the specified status. + * @param format is the extension for the result file (xml, json, rss, atom). + * @param id the ID of the status to destroy. + * @return a destroyed status specified by the id parameter. + */ + function destroyStatus($format, $id) { + $api_call = sprintf("http://identi.ca/api/statuses/destroy/%d.%s", $id, $format); + return $this->APICall($api_call, true, true); + } + + /** + * Returns a user's friends, each with current status inline. They are ordered by the order in which + * they were added as friends, 100 at a time. Use the page option to access older friends. With no + * user specified, request defaults to the authenticated user's friends. + * @param format is the extension for the result file (xml, json, rss, atom). + * @param id is the ID of the user for whom to request a list of friends. + * @return a user's friends. + */ + function getFriends($format, $id = NULL) { + if ($id != NULL) { + $api_call = sprintf("http://identi.ca/api/statuses/friends/%s.%s", $id, $format); + } + else { + $api_call = sprintf("http://identi.ca/api/statuses/friends.%s", $format); + } + return $this->APICall($api_call, true); + } + + /** + * Returns a user's followers. They are ordered by the order in which they joined Identi.ca, 100 at a time. + * @param format is the extension for the result file (xml, json, rss, atom). + * @param lite specified if status must be show. + * @return a user's followers. + */ + function getFollowers($format, $lite = NULL) { + $api_call = sprintf("http://identi.ca/api/statuses/followers.%s%s", $format, ($lite) ? "?lite=true" : NULL); + return $this->APICall($api_call, true); + } + + /** + * Returns extended information of a given user, specified by ID or email as per the required id parameter. + * @param format is the extension for the result file (xml, json). + * @param id is the ID of specified user. + * @param email is the email of specified user. + * @return extended information of a given user. + */ + function showUser($format, $id, $email = NULL) { + if ($email == NULL) { + $api_call = sprintf("http://identi.ca/api/users/show/%s.%s", $id, $format); + } + else { + $api_call = sprintf("http://identi.ca/api/users/show.xml?email=%s", $email); + } + return $this->APICall($api_call, true); + } + + /** + * Returns a list of the 20 most recent direct messages sent to the authenticating user. The XML + * and JSON versions include detailed information about the sending and recipient users. + * @param format is the extension for the result file (xml, json, rss, atom). + * @param since get only messages from an ID greater than (that is, more recent than) the specified ID. + * @param since_id returns only statuses from the specified ID. + * @param page Specifies the page of direct messages to retrieve. + * @return a list of the 20 most recent direct messages. + */ + function getMessages($format, $since = NULL, $since_id = 0, $page = 1) { + $api_call = sprintf("http://identi.ca/api/direct_messages.%s", $format); + if ($since != NULL) { + $api_call .= sprintf("?since=%s", urlencode($since)); + } + if ($since_id > 0) { + $api_call .= sprintf("%ssince_id=%d", (strpos($api_call, "?since") === false) ? "?" : "&", $since_id); + } + if ($page > 1) { + $api_call .= sprintf("%spage=%d", (strpos($api_call, "?since") === false) ? "?" : "&", $page); + } + return $this->APICall($api_call, true); + } + + /** + * Returns a list of the 20 most recent direct messages sent by the authenticating user. The XML + * and JSON versions include detailed information about the sending and recipient users. + * @param format is the extension for the result file (xml, json, rss, atom). + * @param since get only messages from an ID greater than (that is, more recent than) the specified ID. + * @param since_id returns only statuses from the specified ID. + * @param page specifies the page of direct messages to retrieve. + * @return a list of the 20 most recent sent direct messages. + */ + function getSentMessages($format, $since = NULL, $since_id = 0, $page = 1) { + $api_call = sprintf("http://identi.ca/api/direct_messages/sent.%s", $format); + if ($since != NULL) { + $api_call .= sprintf("?since=%s", urlencode($since)); + } + if ($since_id > 0) { + $api_call .= sprintf("%ssince_id=%d", (strpos($api_call, "?since") === false) ? "?" : "&", $since_id); + } + if ($page > 1) { + $api_call .= sprintf("%spage=%d", (strpos($api_call, "?since") === false) ? "?" : "&", $page); + } + return $this->APICall($api_call, true); + } + + /** + * Sends a new direct message to the specified user from the authenticating user. Request must be a POST. + * @param format is the extension for the result file (xml, json). + * @param user is the ID of specified user to send the message. + * @param text is the text of your direct message. + * @return the sent message in the requested format when successful. + */ + function newMessage($format, $user, $text) { + $text = urlencode(stripslashes(urldecode($text))); + $api_call = sprintf("http://identi.ca/api/direct_messages/new.%s?user=%s&text=%s", $format, $user, $text); + return $this->APICall($api_call, true, true); + } + + /** + * Destroys the direct message specified in the required ID parameter. The authenticating user + * must be the recipient of the specified direct message. + * @param format is the extension for the result file (xml, json). + * @param id is the ID of specified direct message. + * @return the message destroyed. + */ + function destroyMessage($format, $id) { + $api_call = sprintf("http://identi.ca/api/direct_messages/destroy/%s.%s", $id, $format); + return $this->APICall($api_call, true, true); + } + + /** + * Allows the authenticating users to follow the user specified in the ID parameter. + * @param format is the extension for the result file (xml, json). + * @param id is the ID of specified user. + * @return the befriended user in the requested format when successful. Returns a string describing the + * failure condition when unsuccessful. If you are already friends with the user an HTTP 403 will be returned. + */ + function createFriendship($format, $id) { + $api_call = sprintf("http://identi.ca/api/friendships/create/%s.%s", $id, $format); + return $this->APICall($api_call, true, true); + } + + /** + * Allows the authenticating users to unfollow the user specified in the ID parameter. + * @param format is the extension for the result file (xml, json). + * @param id is the ID of specified user. + * @return the unfollowed user in the requested format when successful. Returns a string + * describing the failure condition when unsuccessful. + */ + function destroyFriendship($format, $id) { + $api_call = sprintf("http://identi.ca/api/friendships/destroy/%s.%s", $id, $format); + return $this->APICall($api_call, true, true); + } + + /** + * Tests for the existence of friendship between two users. + * @param format is the extension for the result file (xml, json). + * @param user_a is the ID of the first specified user. + * @param user_b is the ID of the second specified user. + * @return true if user_a follows user_b, otherwise will return false. + */ + function friendshipExists($format, $user_a, $user_b) { + $api_call = sprintf("http://identi.ca/api/friendships/exists.%s?user_a=%s&user_b=%s", $format, $user_a, $user_b); + return $this->APICall($api_call, true); + } + + /** + * Tests if supplied user credentials are valid. + * @param format is the extension for the result file (xml, json). + * @return an HTTP 200 OK response code and a representation of the requesting user if authentication + * was successful; returns a 401 status code and an error message if not. + */ + function verifyCredentials($format = NULL) { + $api_call = sprintf("http://identi.ca/api/account/verify_credentials%s", ($format != NULL) ? sprintf(".%s", $format) : NULL); + return $this->APICall($api_call, true); + } + + /** + * Ends the session of the authenticating user, returning a null cookie. + * @return NULL + */ + function endSession() { + $api_call = "http://identi.ca/api/account/end_session"; + return $this->APICall($api_call, true); + } + + /** + * Update user's location in the profile. + * @param location is the user's location . + * @return NULL. + */ + function updateLocation($format, $location) { + $api_call = sprintf("http://identi.ca/api/account/update_location.%s?location=%s", $format, $location); + return $this->APICall($api_call, true, true); + } + + /** + * Sets which device Identi.ca delivers updates to for the authenticating user. + * @param format is the extension for the result file (xml, json). + * @param device must be one of: sms, im, none. + * @return user's profile details in a selected format. + */ + function updateDeliveryDevice($format, $device) { + $api_call = sprintf("http://identi.ca/api/account/update_delivery_device.%s?device=%s", $format, $device); + return $this->APICall($api_call, true, true); + } + + /** + * Returns the remaining number of API requests available to the requesting user before the API + * limit is reached for the current hour. Calls to rateLimitStatus() do not count against the rate limit. + * @param format is the extension for the result file (xml, json). + * @return remaining number of API requests. + */ + function rateLimitStatus($format) { + $api_call = sprintf("http://identi.ca/api/account/rate_limit_status.%s", $format); + return $this->APICall($api_call, true); + } + + /** + * Returns the 20 most recent favorite statuses for the authenticating user or user + * specified by the ID parameter in the requested format. + * @param format is the extension for the result file (xml, json, rss, atom). + * @param id is the ID of specified user. + * @param page specifies the page of favorites to retrieve. + * @return a list of the 20 most recent favorite statuses. + */ + function getFavorites($format, $id = NULL, $page = 1) { + if ($id == NULL) { + $api_call = sprintf("http://identi.ca/api/favorites.%s", $format); + } + else { + $api_call = sprintf("http://identi.ca/api/favorites/%s.%s", $id, $format); + } + if ($page > 1) { + $api_call .= sprintf("?page=%d", $page); + } + return $this->APICall($api_call, true); + } + + /** + * Favorites the status specified in the ID parameter as the authenticating user. + * @param format is the extension for the result file (xml, json). + * @param id is the ID of specified user. + * @return the favorite status when successful. + */ + function createFavorite($format, $id) { + $api_call = sprintf("http://identi.ca/api/favorites/create/%d.%s", $id, $format); + return $this->APICall($api_call, true, true); + } + + /** + * Un-favorites the status specified in the ID parameter as the authenticating user. + * @param format is the extension for the result file (xml, json). + * @param id is the ID of specified user. + * @return the un-favorited status in the requested format when successful. + */ + function destroyFavorite($format, $id) { + $api_call = sprintf("http://identi.ca/api/favorites/destroy/%d.%s", $id, $format); + return $this->APICall($api_call, true, true); + } + + /** + * Enables device notifications for updates from the specified user. + * @param format is the extension for the result file (xml, json). + * @param id is the ID of specified user. + * @return the specified user when successful. + */ + function follow($format, $id) { + $api_call = sprintf("http://identi.ca/api/notifications/follow/%d.%s", $id, $format); + return $this->APICall($api_call, true, true); + } + + /** + * Disables notifications for updates from the specified user to the authenticating user. + * @param format is the extension for the result file (xml, json). + * @param id is the ID of specified user. + * @return the specified user when successful. + */ + function leave($format, $id) { + $api_call = sprintf("http://identi.ca/api/notifications/leave/%d.%s", $id, $format); + return $this->APICall($api_call, true, true); + } + + /** + * Blocks the user specified in the ID parameter as the authenticating user. Destroys a friendship to the blocked user if it exists. + * @param format is the extension for the result file (xml, json). + * @param id is the ID of specified user. + * @return the blocked user in the requested format when successful. + */ + function createBlock($format, $id) { + $api_call = sprintf("http://identi.ca/api/blocks/create/%d.%s", $id, $format); + return $this->APICall($api_call, true, true); + } + + /** + * Un-blocks the user specified in the ID parameter for the authenticating user. + * @param format is the extension for the result file (xml, json). + * @param id is the ID of specified user. + * @return the un-blocked user in the requested format when successful. + */ + function destroyBlock($format, $id) { + $api_call = sprintf("http://identi.ca/api/blocks/destroy/%d.%s", $id, $format); + return $this->APICall($api_call, true, true); + } + + /** + * Returns true or false in the requested format with a 200 OK HTTP status code. + * @param format is the extension for the result file (xml, json). + * @return test results. + */ + function test($format) { + $api_call = sprintf("http://identi.ca/api/help/test.%s", $format); + return $this->APICall($api_call, true); + } + + private function APICall($api_url, $require_credentials = false, $http_post = false) { + $curl_handle = curl_init(); + if($this->application_source){ + $api_url .= "&source=" . $this->application_source; + } + curl_setopt($curl_handle, CURLOPT_URL, $api_url); + if ($require_credentials) { + curl_setopt($curl_handle, CURLOPT_USERPWD, $this->credentials); + } + if ($http_post) { + curl_setopt($curl_handle, CURLOPT_POST, true); + } + curl_setopt($curl_handle, CURLOPT_RETURNTRANSFER, TRUE); + $identica_data = curl_exec($curl_handle); + $this->http_status = curl_getinfo($curl_handle, CURLINFO_HTTP_CODE); + $this->last_api_call = $api_url; + curl_close($curl_handle); + return $identica_data; + } + + function lastStatusCode() { + return $this->http_status; + } + + function lastAPICall() { + return $this->last_api_call; + } +} +?> diff --git a/apps/mm/ext/MimeMailParser.class.php b/apps/mm/ext/MimeMailParser.class.php new file mode 100644 index 0000000..0080199 --- /dev/null +++ b/apps/mm/ext/MimeMailParser.class.php @@ -0,0 +1,447 @@ +<?php
+
+require_once('MimeMailParser_attachment.class.php');
+
+/**
+ * Fast Mime Mail parser Class using PHP's MailParse Extension
+ * @author gabe@fijiwebdesign.com
+ * @url http://www.fijiwebdesign.com/
+ * @license http://creativecommons.org/licenses/by-sa/3.0/us/
+ * @version $Id$
+ */
+class MimeMailParser {
+
+ /**
+ * PHP MimeParser Resource ID
+ */
+ public $resource;
+
+ /**
+ * A file pointer to email
+ */
+ public $stream;
+
+ /**
+ * A text of an email
+ */
+ public $data;
+
+ /**
+ * Stream Resources for Attachments
+ */
+ public $attachment_streams;
+
+ /**
+ * Inialize some stuff
+ * @return
+ */
+ public function __construct() {
+ $this->attachment_streams = array();
+ }
+
+ /**
+ * Free the held resouces
+ * @return void
+ */
+ public function __destruct() {
+ // clear the email file resource
+ if (is_resource($this->stream)) {
+ fclose($this->stream);
+ }
+ // clear the MailParse resource
+ if (is_resource($this->resource)) {
+ mailparse_msg_free($this->resource);
+ }
+ // remove attachment resources
+ foreach($this->attachment_streams as $stream) {
+ fclose($stream);
+ }
+ }
+
+ /**
+ * Set the file path we use to get the email text
+ * @return Object MimeMailParser Instance
+ * @param $mail_path Object
+ */
+ public function setPath($path) {
+ // should parse message incrementally from file
+ $this->resource = mailparse_msg_parse_file($path);
+ $this->stream = fopen($path, 'r');
+ $this->parse();
+ return $this;
+ }
+
+ /**
+ * Set the Stream resource we use to get the email text
+ * @return Object MimeMailParser Instance
+ * @param $stream Resource
+ */
+ public function setStream($stream) {
+
+ // streams have to be cached to file first
+ if (get_resource_type($stream) == 'stream') {
+ $tmp_fp = tmpfile();
+ if ($tmp_fp) {
+ while(!feof($stream)) {
+ fwrite($tmp_fp, fread($stream, 2028));
+ }
+ fseek($tmp_fp, 0);
+ $this->stream =& $tmp_fp;
+ } else {
+ throw new Exception('Could not create temporary files for attachments. Your tmp directory may be unwritable by PHP.');
+ return false;
+ }
+ fclose($stream);
+ } else {
+ $this->stream = $stream;
+ }
+
+ $this->resource = mailparse_msg_create();
+ // parses the message incrementally low memory usage but slower
+ while(!feof($this->stream)) {
+ mailparse_msg_parse($this->resource, fread($this->stream, 2082));
+ }
+ $this->parse();
+ return $this;
+ }
+
+ /**
+ * Set the email text
+ * @return Object MimeMailParser Instance
+ * @param $data String
+ */
+ public function setText($data) {
+ $this->resource = mailparse_msg_create();
+ // does not parse incrementally, fast memory hog might explode
+ mailparse_msg_parse($this->resource, $data);
+ $this->data = $data;
+ $this->parse();
+ return $this;
+ }
+
+ /**
+ * Parse the Message into parts
+ * @return void
+ * @private
+ */
+ private function parse() {
+ $structure = mailparse_msg_get_structure($this->resource);
+ $this->parts = array();
+ foreach($structure as $part_id) {
+ $part = mailparse_msg_get_part($this->resource, $part_id);
+ $this->parts[$part_id] = mailparse_msg_get_part_data($part);
+ }
+ }
+
+ /**
+ * Retrieve the Email Headers
+ * @return Array
+ */
+ public function getHeaders() {
+ if (isset($this->parts[1])) {
+ return $this->getPartHeaders($this->parts[1]);
+ } else {
+ throw new Exception('MimeMailParser::setPath() or MimeMailParser::setText() must be called before retrieving email headers.');
+ }
+ return false;
+ }
+ /**
+ * Retrieve the raw Email Headers
+ * @return string
+ */
+ public function getHeadersRaw() {
+ if (isset($this->parts[1])) {
+ return $this->getPartHeaderRaw($this->parts[1]);
+ } else {
+ throw new Exception('MimeMailParser::setPath() or MimeMailParser::setText() must be called before retrieving email headers.');
+ }
+ return false;
+ }
+
+ /**
+ * Retrieve a specific Email Header
+ * @return String
+ * @param $name String Header name
+ */
+ public function getHeader($name) {
+ if (isset($this->parts[1])) {
+ $headers = $this->getPartHeaders($this->parts[1]);
+ if (isset($headers[$name])) {
+ return $headers[$name];
+ }
+ } else {
+ throw new Exception('MimeMailParser::setPath() or MimeMailParser::setText() must be called before retrieving email headers.');
+ }
+ return false;
+ }
+
+ /**
+ * Returns the part for the message body in the specified format
+ * @return Part or False if not found
+ * @param $type String[optional]
+ */
+ public function getMessageBodyPart($type = 'text') {
+ $mime_types = array(
+ 'text'=> 'text/plain',
+ 'html'=> 'text/html'
+ );
+ $attachment_dispositions = array("attachment","inline");
+ if (in_array($type, array_keys($mime_types))) {
+ foreach($this->parts as $part) {
+ $disposition = $this->getPartContentDisposition($part);
+ $mime_type = $this->getPartContentType($part);
+ if ( (!in_array($disposition, $attachment_dispositions)) &&
+ ($mime_type == $mime_types[$type]) ) {
+ return $part;
+ }
+ }
+ } else {
+ throw new Exception('Invalid type specified for MimeMailParser::getMessageBodyPart. "type" can either be text or html.');
+ }
+ return false;
+ }
+
+ /**
+ * Returns the email message body in the specified format
+ * @return Mixed String Body or False if not found
+ * @param $type Object[optional]
+ */
+ public function getMessageBody($type = 'text') {
+ $body = false;
+ $part = $this->getMessageBodyPart($type);
+ if ($part!==false) {
+ $headers = $this->getPartHeaders($part);
+ $body = $this->decode($this->getPartBody($part), array_key_exists('content-transfer-encoding', $headers) ? $headers['content-transfer-encoding'] : '');
+ }
+ return $body;
+ }
+
+ /**
+ * get the headers for the message body part.
+ * @return Array
+ * @param $type Object[optional]
+ */
+ public function getMessageBodyHeaders($type = 'text') {
+ $headers = false;
+ $part = $this->getMessageBodyPart($type);
+ if ($part!==false) {
+ $headers = $this->getPartHeaders($part);
+ }
+ return $headers;
+ }
+
+
+ /**
+ * Returns the attachments contents in order of appearance
+ * @return Array
+ * @param $type Object[optional]
+ */
+ public function getAttachments() {
+ $attachments = array();
+ $dispositions = array("attachment","inline");
+ foreach($this->parts as $part) {
+ $disposition = $this->getPartContentDisposition($part);
+ if (in_array($disposition, $dispositions)) {
+ $headers = $this->getPartHeaders($part);
+ $attachments[] = new MimeMailParser_attachment(
+ $part['disposition-filename'],
+ $this->getPartContentType($part),
+ $this->getAttachmentStream($part),
+ $disposition,
+ $headers
+ );
+ }
+ }
+ return $attachments;
+ }
+
+ /**
+ * Return the Headers for a MIME part
+ * @return Array
+ * @param $part Array
+ */
+ private function getPartHeaders($part) {
+ if (isset($part['headers'])) {
+ return $part['headers'];
+ }
+ return false;
+ }
+
+ /**
+ * Return a Specific Header for a MIME part
+ * @return Array
+ * @param $part Array
+ * @param $header String Header Name
+ */
+ private function getPartHeader($part, $header) {
+ if (isset($part['headers'][$header])) {
+ return $part['headers'][$header];
+ }
+ return false;
+ }
+
+ /**
+ * Return the ContentType of the MIME part
+ * @return String
+ * @param $part Array
+ */
+ private function getPartContentType($part) {
+ if (isset($part['content-type'])) {
+ return $part['content-type'];
+ }
+ return false;
+ }
+
+ /**
+ * Return the Content Disposition
+ * @return String
+ * @param $part Array
+ */
+ private function getPartContentDisposition($part) {
+ if (isset($part['content-disposition'])) {
+ return $part['content-disposition'];
+ }
+ return false;
+ }
+
+ /**
+ * Retrieve the raw Header of a MIME part
+ * @return String
+ * @param $part Object
+ */
+ private function getPartHeaderRaw(&$part) {
+ $header = '';
+ if ($this->stream) {
+ $header = $this->getPartHeaderFromFile($part);
+ } else if ($this->data) {
+ $header = $this->getPartHeaderFromText($part);
+ } else {
+ throw new Exception('MimeMailParser::setPath() or MimeMailParser::setText() must be called before retrieving email parts.');
+ }
+ return $header;
+ }
+ /**
+ * Retrieve the Body of a MIME part
+ * @return String
+ * @param $part Object
+ */
+ private function getPartBody(&$part) {
+ $body = '';
+ if ($this->stream) {
+ $body = $this->getPartBodyFromFile($part);
+ } else if ($this->data) {
+ $body = $this->getPartBodyFromText($part);
+ } else {
+ throw new Exception('MimeMailParser::setPath() or MimeMailParser::setText() must be called before retrieving email parts.');
+ }
+ return $body;
+ }
+
+ /**
+ * Retrieve the Header from a MIME part from file
+ * @return String Mime Header Part
+ * @param $part Array
+ */
+ private function getPartHeaderFromFile(&$part) {
+ $start = $part['starting-pos'];
+ $end = $part['starting-pos-body'];
+ fseek($this->stream, $start, SEEK_SET);
+ $header = fread($this->stream, $end-$start);
+ return $header;
+ }
+ /**
+ * Retrieve the Body from a MIME part from file
+ * @return String Mime Body Part
+ * @param $part Array
+ */
+ private function getPartBodyFromFile(&$part) {
+ $start = $part['starting-pos-body'];
+ $end = $part['ending-pos-body'];
+ fseek($this->stream, $start, SEEK_SET);
+ $body = fread($this->stream, $end-$start);
+ return $body;
+ }
+
+ /**
+ * Retrieve the Header from a MIME part from text
+ * @return String Mime Header Part
+ * @param $part Array
+ */
+ private function getPartHeaderFromText(&$part) {
+ $start = $part['starting-pos'];
+ $end = $part['starting-pos-body'];
+ $header = substr($this->data, $start, $end-$start);
+ return $header;
+ }
+ /**
+ * Retrieve the Body from a MIME part from text
+ * @return String Mime Body Part
+ * @param $part Array
+ */
+ private function getPartBodyFromText(&$part) {
+ $start = $part['starting-pos-body'];
+ $end = $part['ending-pos-body'];
+ $body = substr($this->data, $start, $end-$start);
+ return $body;
+ }
+
+ /**
+ * Read the attachment Body and save temporary file resource
+ * @return String Mime Body Part
+ * @param $part Array
+ */
+ private function getAttachmentStream(&$part) {
+ $temp_fp = tmpfile();
+
+ array_key_exists('content-transfer-encoding', $part['headers']) ? $encoding = $part['headers']['content-transfer-encoding'] : $encoding = '';
+
+ if ($temp_fp) {
+ if ($this->stream) {
+ $start = $part['starting-pos-body'];
+ $end = $part['ending-pos-body'];
+ fseek($this->stream, $start, SEEK_SET);
+ $len = $end-$start;
+ $written = 0;
+ $write = 2028;
+ $body = '';
+ while($written < $len) {
+ if (($written+$write < $len )) {
+ $write = $len - $written;
+ }
+ $part = fread($this->stream, $write);
+ fwrite($temp_fp, $this->decode($part, $encoding));
+ $written += $write;
+ }
+ } else if ($this->data) {
+ $attachment = $this->decode($this->getPartBodyFromText($part), $encoding);
+ fwrite($temp_fp, $attachment, strlen($attachment));
+ }
+ fseek($temp_fp, 0, SEEK_SET);
+ } else {
+ throw new Exception('Could not create temporary files for attachments. Your tmp directory may be unwritable by PHP.');
+ return false;
+ }
+ return $temp_fp;
+ }
+
+
+ /**
+ * Decode the string depending on encoding type.
+ * @return String the decoded string.
+ * @param $encodedString The string in its original encoded state.
+ * @param $encodingType The encoding type from the Content-Transfer-Encoding header of the part.
+ */
+ private function decode($encodedString, $encodingType) {
+ if (strtolower($encodingType) == 'base64') {
+ return base64_decode($encodedString);
+ } else if (strtolower($encodingType) == 'quoted-printable') {
+ return quoted_printable_decode($encodedString);
+ } else {
+ return $encodedString;
+ }
+ }
+
+}
+
+
+?>
diff --git a/apps/mm/ext/MimeMailParser_attachment.class.php b/apps/mm/ext/MimeMailParser_attachment.class.php new file mode 100644 index 0000000..6bd327c --- /dev/null +++ b/apps/mm/ext/MimeMailParser_attachment.class.php @@ -0,0 +1,136 @@ +<?php
+
+/**
+ * Model of an Attachment
+ */
+class MimeMailParser_attachment {
+
+ /**
+ * @var $filename Filename
+ */
+ public $filename;
+ /**
+ * @var $content_type Mime Type
+ */
+ public $content_type;
+ /**
+ * @var $content File Content
+ */
+ private $content;
+ /**
+ * @var $extension Filename extension
+ */
+ private $extension;
+ /**
+ * @var $content_disposition Content-Disposition (attachment or inline)
+ */
+ public $content_disposition;
+ /**
+ * @var $headers An Array of the attachment headers
+ */
+ public $headers;
+
+ private $stream;
+
+ public function __construct($filename, $content_type, $stream, $content_disposition, $headers) {
+ $this->filename = $filename;
+ $this->content_type = $content_type;
+ $this->stream = $stream;
+ $this->content = null;
+ $this->content_disposition = $content_disposition;
+ $this->headers = $headers;
+ }
+
+ /**
+ * retrieve the attachment filename
+ * @return String
+ */
+ public function getFilename() {
+ return $this->filename;
+ }
+
+ /**
+ * Retrieve the Attachment Content-Type
+ * @return String
+ */
+ public function getContentType() {
+ return $this->content_type;
+ }
+
+ /**
+ * Retrieve the Attachment Content-Disposition
+ * @return String
+ */
+ public function getContentDisposition() {
+ return $this->content_disposition;
+ }
+
+ /**
+ * Retrieve the Attachment Headers
+ * @return String
+ */
+ public function getHeaders() {
+ return $this->headers;
+ }
+
+ /**
+ * Retrieve the file extension
+ * @return String
+ */
+ public function getFileExtension() {
+ if (!$this->extension) {
+ $ext = substr(strrchr($this->filename, '.'), 1);
+ if ($ext == 'gz') {
+ // special case, tar.gz
+ // todo: other special cases?
+ $ext = preg_match("/\.tar\.gz$/i", $ext) ? 'tar.gz' : 'gz';
+ }
+ $this->extension = $ext;
+ }
+ return $this->extension;
+ }
+
+ /**
+ * Read the contents a few bytes at a time until completed
+ * Once read to completion, it always returns false
+ * @return String
+ * @param $bytes Int[optional]
+ */
+ public function read($bytes = 2082) {
+ return feof($this->stream) ? false : fread($this->stream, $bytes);
+ }
+
+ /**
+ * Retrieve the file content in one go
+ * Once you retreive the content you cannot use MimeMailParser_attachment::read()
+ * @return String
+ */
+ public function getContent() {
+ if ($this->content === null) {
+ fseek($this->stream, 0);
+ while(($buf = $this->read()) !== false) {
+ $this->content .= $buf;
+ }
+ }
+ return $this->content;
+ }
+
+ /**
+ * Allow the properties
+ * MimeMailParser_attachment::$name,
+ * MimeMailParser_attachment::$extension
+ * to be retrieved as public properties
+ * @param $name Object
+ */
+ public function __get($name) {
+ if ($name == 'content') {
+ return $this->getContent();
+ } else if ($name == 'extension') {
+ return $this->getFileExtension();
+ }
+ return null;
+ }
+
+}
+
+?>
\ No newline at end of file diff --git a/apps/mm/ext/README.txt b/apps/mm/ext/README.txt new file mode 100644 index 0000000..6d31122 --- /dev/null +++ b/apps/mm/ext/README.txt @@ -0,0 +1,14 @@ +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 +GoogleVoice.class.php : class.googlevoice.php : https://code.google.com/p/phpgooglevoice/ +Identica.class.php : identica.lib.php : https://code.google.com/p/identica-php/ +MimeMailParser.class.php : MimeMailParser.class.php : https://code.google.com/p/php-mime-mail-parser/ +MimeMailParser_attachment.class.php : attachment.php : https://code.google.com/p/php-mime-mail-parser/ + +~ Luke Shumaker <http://lukeshu.ath.cx> +Happy Hacking! diff --git a/apps/mm/models/Message.class.php b/apps/mm/models/Message.class.php new file mode 100644 index 0000000..b2a2b7e --- /dev/null +++ b/apps/mm/models/Message.class.php @@ -0,0 +1,116 @@ +<?php +require_once('MimeMailParser.class.php'); +require_once('Database.class.php'); + +class Message extends Model { + public static function add($infile) { + $parser = new MimeMailParser(); + $parser->setPath($infile); + $id = preg_replace('/<(.*)>/', '$1', + $parser->getHeader('message-id')); + $id = str_replace('/', '', $id); // For security purposes + + $db = Database::getInstance(); + $msgdir = $db->getSysConf('msgdir'); + $msg_file = "$msgdir/$id"; + + rename($infile, $msg_file); + return new Message($id); + } + + private $_msgid; + private $_msgdir; + private $_parser; + + public function __construct($msgid) { + $this->_msgid = str_replace('/', '', $msgid); + if (!file_exists($this->file())) { + return false; + } + } + + public function msgid() { + return $this->_msgid; + } + + private function msgdir() { + if (!isset($this->_msgdir)) { + $db = Database::getInstance(); + $this->_msgdir = $db->getSysConf('msgdir'); + } + return $this->_msgdir; + } + + private function file() { + return $this->msgdir().'/'.$this->msgid(); + } + + private function parser() { + if (!isset($this->_parser)) { + $this->_parser = new MimeMailParser(); + $this->_parser->setPath($this->file()); + } + return $this->_parser; + } + + /** + * Retrieve the Email Headers + * @return Array + */ + public function getHeaders() { + return $this->parser()->getHeaders(); + } + + /** + * Retrieve the raw Email Headers + * @return string + */ + public function getHeadersRaw() { + return $this->parser()->getHeadersRaw(); + } + + /** + * Retrieve a specific Email Header + * @return String + * @param $name String Header name + */ + public function getHeader($name) { + return $this->parser()->getHeader($name); + } + + /** + * Returns the part for the message body in the specified format + * @return Part or False if not found + * @param $type String[optional] + */ + public function getMessageBodyPart($type = 'text') { + return $this->parser()->getMessageBodyPart($type); + } + + /** + * Returns the email message body in the specified format + * @return Mixed String Body or False if not found + * @param $type Object[optional] + */ + public function getMessageBody($type = 'text') { + return $this->parser()->getMessageBody($type); + } + + /** + * get the headers for the message body part. + * @return Array + * @param $type Object[optional] + */ + public function getMessageBodyHeaders($type = 'text') { + return $this->parser()->getMessageBodyHeaders($type); + } + + /** + * Returns the attachments contents in order of appearance + * @return Array + * @param $type Object[optional] + */ + public function getAttachments() { + return $this->parser()->getAttachments(); + } +} diff --git a/apps/mm/plugins/SenderGVSMS.class.php b/apps/mm/plugins/SenderGVSMS.class.php new file mode 100644 index 0000000..76db208 --- /dev/null +++ b/apps/mm/plugins/SenderGVSMS.class.php @@ -0,0 +1,34 @@ +<?php +require_once('Plugin.class.php'); +require_once('GoogleVoice.class.php'); + +class SenderGVSMS extends Plugin { + protected $config = array('username'=>'', + 'password'=>'', + 'length'=>160); + private $obj; + + public static function description() { + return 'Send messages over SMS via GoogleVoice.'; + } + + public static function configList() { + return array('username'=>'text', + 'password'=>'password'); + } + + public function init() { + $this->obj = new GoogleVoice($this->config['username'], + $this->config['password']); + } + + public function sendPrivate($phoneNum, $id, $subject, $body) { + global $shorturl, $messenger; + $url = $shorturl->get($messenger->id2url($id)); + $maxlen = $this->config['length']-(strlen($url)+1); + if($maxlen < strlen($subject)) { + $subject = substr($subject,0,$maxlen-3).'...'; + } + $this->obj->sms($phoneNum, $subject.' '.$url); + } +} diff --git a/apps/mm/plugins/SenderIdentica.class.php b/apps/mm/plugins/SenderIdentica.class.php new file mode 100644 index 0000000..ab55eb9 --- /dev/null +++ b/apps/mm/plugins/SenderIdentica.class.php @@ -0,0 +1,35 @@ +<?php +require_once('Plugin.class.php'); +require_once('Identica.class.php'); + +class SenderIdentica extends Plugin { + protected $config = array('username'=>'', + 'password'=>'', + 'length'=>140); + private $obj; + + public static function description() { + return 'Dent messages to Identi.ca.'; + } + + public static function configList() { + return array('username'=>'text', + 'password'=>'password', + 'length'=>'int'); + } + + public function init() { + $this->obj = new Identica($this->config['username'], + $this->config['password']); + } + + public function sendBroadcast($id, $subject, $body) { + global $shorturl, $messenger; + $url = $shorturl->get($messenger->id2url($id)); + $maxlen = $this->config['length']-(strlen($url)+1); + if($maxlen < strlen($subject)) { + $subject = substr($subject,0,$maxlen-3).'...'; + } + $this->obj->updateStatus($subject.' '.$url); + } +} diff --git a/apps/mm/scripts/newmail.php b/apps/mm/scripts/newmail.php new file mode 100644 index 0000000..e4fc740 --- /dev/null +++ b/apps/mm/scripts/newmail.php @@ -0,0 +1,36 @@ +<?php +// What directory are we in on the server? ///////////////////////////////////// +$_dir_scripts = dirname(__FILE__); +$_dir_app = dirname($_dir_scripts); +$_dir_apps = dirname($_dir_app); +$_dir_base = dirname($_dir_apps); +require_once($_dir_base.'/stub.php'); + +$cmdline = isset($argv[0]); // called from the command line +@$method = $_SERVER['REQUEST_METHOD']; // What HTTP method was used +if ( ($method=='PUT') || $cmdline ) { + // We're going to be uploading a new message. + + // uniqid() isn't 'secure', it doesn't need to be, it's to prevent + // random collisions. + $tmpfile = "$BASE/tmp/".uniqid(getmypid().'.'); + $infile = ($cmdline?'php://stdin':'php://input'); + $out = fopen($tmpfile, "w"); + $in = fopen($infile, "r"); + while ($data = fread($in, 1024)) + fwrite($out, $data); + fclose($out); + fclose($in); + + require_once('Message.class.php'); + $msg = Message::add($tmpfile); + $id = $msg->msgid(); + + if ($cmdline) { + echo $id."\n"; + } else { + $m->status('201 Created'); + header("Location: ".$m->baseUrl().'messages/'.$id); + } + exit(); +}
\ No newline at end of file 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 = "&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/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..a7cc629 --- /dev/null +++ b/apps/um/models/Auth.class.php @@ -0,0 +1,156 @@ +<?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/messages/401.html.php b/apps/um/views/pages/messages/401.html.php new file mode 100644 index 0000000..0b24f80 --- /dev/null +++ b/apps/um/views/pages/messages/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 messages.'); +} else { + // Logged in, so the account must not activated + $t->paragraph('Your account needs to be activated by an administrator '. + 'to view messages.'); +} +$t->footer(); diff --git a/apps/um/views/pages/messages/frame.html.php b/apps/um/views/pages/messages/frame.html.php new file mode 100644 index 0000000..30fc1cc --- /dev/null +++ b/apps/um/views/pages/messages/frame.html.php @@ -0,0 +1,58 @@ +<?php global $VARS; +$t = $VARS['template']; +$msg = $VARS['msg']; + +function messageLink($id) { + if (is_array($id)) { $id = $id[1]; } + global $VARS; $t = $VARS['template']; + + $msg = new Message($id); + $exists = ($msg?true:false); + + return sprintf('<<a href="%1$s"%2$s>%3$s</a>>', + $t->url("messages/$id/"), + ($exists?'':' class="http404"'), + htmlentities($id)); +} +function parseMessageIDs($string) { + $base = $_SERVER['REQUEST_URL']; + $html = preg_replace_callback( + '/<([^>]*)>/', + 'messageLink', + $string); + return $html; +} + +$t->header('View Message'); +$t->openTag('table'); +$t->row(array('To:' , htmlentities( $msg->getHeader('to' )))); +$t->row(array('From:' , htmlentities( $msg->getHeader('from' )))); +$t->row(array('Subject:' , htmlentities( $msg->getHeader('subject' )))); +$t->row(array('In-Reply-to:', parseMessageIDs($msg->getHeader('in-reply-to')))); +$t->row(array('References:' , parseMessageIDs($msg->getHeader('references' )))); +$t->closeTag('table'); + +$msg_id = htmlentities($msg->msgid()); + +$t->openTag('div', array('class'=>'message-body')); +if ($msg->getMessageBodyPart('html')!==false) { + $t->tag('h2', array(), 'HTML'); + $t->tag('iframe', array('src'=>$t->url("messages/$msg_id/body.html")), ''); +} +if ($msg->getMessageBodyPart('text')!==false) { + $t->tag('h2', array(), 'Plain Text'); + $t->tag('iframe', array('src'=>$t->url("messages/$msg_id/body.txt")), ''); +} +$t->closeTag('div'); +$t->tag('h2', array(), 'Attachments'); +$t->openTag('table'); +$attachments = $msg->getAttachments(); +foreach ($attachments as $id => $attachment) { + $t->row(array( + htmlentities($attachment->getContentType()), + $t->link($t->url("$msg_id/attachment/$id"), + htmlentities($attachment->getFilename())), + )); +} +$t->closeTag('table'); +$t->footer(); diff --git a/apps/um/views/pages/messages/index.html.php b/apps/um/views/pages/messages/index.html.php new file mode 100644 index 0000000..111b6c6 --- /dev/null +++ b/apps/um/views/pages/messages/index.html.php @@ -0,0 +1,25 @@ +<?php global $VARS; +$t = $VARS['template']; +$messages = $VARS['messages']; + +$t->header('Message Index'); +$t->tag('h1', array(), "Message Index"); + +$t->openTag('table'); +$t->row(array('From','Subject', 'Date')); +foreach ($messages as $date => $message_array) { + foreach ($message_array as $message) { + $url = $t->url('messages/'.$message['id'].'/'); + $subject = htmlentities($message['subject']); + $from = htmlentities($message['from']); + $date_str = str_replace(' ', ' ', date('Y-m-d H:i:s',$date)); + $t->row(array( + $t->link($url, $from , true), + $t->link($url, $subject , true), + $t->link($url, $date_str, true), + )); + } +} +$t->closeTag('table'); + +$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(); |