From: Johnnie Lamar Odom II Date: Thu, 6 Apr 2017 19:23:00 +0000 (-0500) Subject: Initial public alpha release - Louisborg - Send functionality only. X-Git-Url: https://git.planwatch.world/gitweb/?a=commitdiff_plain;h=1ac84f6dd8e9a8bd47fe497dc3e5bfcf31bd83a9;p=planworld_public.git Initial public alpha release - Louisborg - Send functionality only. --- 1ac84f6dd8e9a8bd47fe497dc3e5bfcf31bd83a9 diff --git a/code/backend/epi-utils.php b/code/backend/epi-utils.php new file mode 100644 index 0000000..6260f10 --- /dev/null +++ b/code/backend/epi-utils.php @@ -0,0 +1,280 @@ + \n$http_request\n", $debug); + + fputs($query_fd, $http_request, strlen($http_request)); + + dbg1("receiving response...", $debug); + + while (!feof($query_fd)) { + $line = fgets($query_fd, 4096); + if (!isset($header_parsed)) { + if ($line === "\r\n" || $line === "\n") { + $header_parsed = 1; + } + dbg2("got header - $line", $debug); + } + else { + $response_buf .= $line; + } + } + + fclose($query_fd); + } + else { + dbg1("socket open failed", $debug); + } + } + else { + dbg1("missing param(s)", $debug); + } + + dbg1("got response:. \n$response_buf\n\n", $debug); + + return $response_buf; +} + +function xu_fault_code($code, $string) { + return array('faultCode' => $code, + 'faultString' => $string); +} + + +function find_and_decode_xml($buf, $debug) { + if (strlen($buf)) { + $xml_begin = substr($buf, strpos($buf, " "xmlrpc"); + } + + $response_buf = ""; + if ($host && $uri && $port) { + $request_xml = xmlrpc_encode_request($method, $args, $output); + $response_buf = xu_query_http_post($request_xml, $host, $uri, $port, $debug, (isset($timeout) ? $timeout : ''), (isset($user) ? $user : ''), (isset($pass) ? $pass : ''), (isset($secure) ? $secure : null)); + + $retval = find_and_decode_xml($response_buf, $debug); + } + return $retval; +} + +/* call an xmlrpc method on a remote http server. legacy support. */ +function xu_rpc_http($method, $args, $host, $uri="/", $port=80, $debug=false, $timeout=0, $user=false, $pass=false, $secure=false) { + return xu_rpc_http_concise( + array( + method => $method, + args => $args, + host => $host, + uri => $uri, + port => $port, + debug => $debug, + timeout => $timeout, + user => $user, + pass => $pass, + secure => $secure + )); +} + +/* call an xmlrpc method on a remote http server with curl library, +* which supports ssl, etc. +* +* note that the curl php extension must be loaded/present in your +* php build. +*/ +/* note: this is not yet known to be working */ +function xu_rpc_http_curl($method_name, $args, $url, $debug=false) { + if ($url) { + $request = xmlrpc_encode_request($method_name, $args); + + $content_len = strlen($request); + + preg_match('/^(.*?\/\/.*?) (\/.*)/',$url,$matches); + $hostport = $matches[1]; + $uri = $matches[2]; + + dbg1("opening curl to $url", $debug); + + $http_request = + "POST ".$uri." HTTP/1.0\r\n" . + "User-Agent: xmlrpc-epi-php/0.2 (PHP) \r\n" . + "Content-Type: text/xml\r\n" . + "Content-Length: $content_len\r\n" . + "\r\n" . + $request; + + dbg1("sending http request: \n$http_request\n", $debug); + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $hostport); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $http_request); + curl_setopt($ch, CURLOPT_HEADER, 0); + $response_buf = curl_exec($ch); + curl_close($ch); + + dbg1("got response:. \n$response_buf\n\n", $debug); + + $retval = find_and_decode_xml($response_buf, $debug); + } + return $retval; +} + +/* call curl function with same convention as xu_rpc_http. +* for easy backwards compatibility with existing scripts. +*/ +/* note: this is not yet known to be working */ +function xu_rpc_http_curl_compat($method_name, $args, $host, $uri="/", $port=80, $debug=false, $start="http://") { + $url = "$start$host:$port$uri"; + return xu_rpc_http_curl($method_name, $args, $url, $debug); +} + +/* note: untested. should work. */ +function xu_is_fault($arg) { + return (is_array($arg) && isset($arg[0][faultCode])); +} + +/* sets some http headers and prints xml */ +function xu_server_send_http_response($xml) { + header("Content-type: text/xml"); + header("Content-length: " . strlen($xml) ); + echo $xml; +} + + +function dbg($msg) { + echo "

$msg

"; flush(); +} +function dbg1($msg, $debug_level) { + if ($debug_level >= 1) { + dbg($msg); + } +} +function dbg2($msg, $debug_level) { + if ($debug_level >= 2) { + dbg($msg); + } +} + + +?> \ No newline at end of file diff --git a/code/client/htaccess.rename b/code/client/htaccess.rename new file mode 100755 index 0000000..2f8d74b --- /dev/null +++ b/code/client/htaccess.rename @@ -0,0 +1,15 @@ +# Planworld Client .htaccess file. +# Change name to .htaccess (with leading period) and place in "client" folder. +# This allows Javascript clients to access the page. +Header set Access-Control-Allow-Origin * +# Rewrite any request to this directory so that it goes to index.php +# and sends the full requested URL as an argument called "apiurl" +RewriteEngine On +# Replace the path in the next line with the path to the client directory +# as a web client or browser would access it relative to the server name. +# i.e. "http://www.myserver.com/~/myuser/planworldv3/client" would become +# "/~/myuser/planworldv3/client" (without quotes) +RewriteBase /~jlodom/code01/planworld_live/client +RewriteCond %{REQUEST_FILENAME} !-d +RewriteCond %{REQUEST_FILENAME} !-f +RewriteRule ^(.*)$ index.php?apiurl=$1 [QSA,L] diff --git a/code/client/index.php b/code/client/index.php new file mode 100644 index 0000000..167eb2b --- /dev/null +++ b/code/client/index.php @@ -0,0 +1,264 @@ + $value ) { + if( is_numeric($key) ){ + $key = 'row'; + } + if( is_array($value) ) { + $subnode = $xml_data->addChild($key); + array_to_xml($value, $subnode); + } else { + $xml_data->addChild("$key",htmlspecialchars("$value")); + } + } + return $xml_data; +} + + + +/* MAIN METHOD */ + +/* Receive the API request. This works in tandem with an .htaccess file in the same directory. */ +if(!empty($_REQUEST['apiurl'])){ + + /* Key Variables. */ + $stringToken = null; + $floatClientVersion = 0; + $stringCategory = ''; + $stringPlanworldVerb = ''; + $stringPlanworldVerbSuffix = 'Get'; + $stringFormat = 'txt'; + $arrayArguments = array(); + $postData = null; + $boolValidUrl = true; + $boolSecurityCheckPassed = true; + $urlPath = ''; + $urlQuery = ''; + $pathArray = array(); + + /* Url Parsing, Key Variable Assignment, and Element Validation */ + $urlArray = parse_url($_REQUEST['apiurl']); + if(array_key_exists('path', $urlArray)){ + $urlPath = $urlArray['path']; + } + /* Get Token */ + if(array_key_exists('token', $_REQUEST)){ + if(ctype_alnum($_REQUEST['token'])){ /* Do some proper validation when validators have been built. */ + $stringToken = strval($_REQUEST['token']); + if(file_exists($pathClientLibBase . '/security.php')){ + include_once($pathClientLibBase . '/security.php'); + $boolSecurityCheckPassed = securityCheck($_SERVER['REMOTE_ADDR'], $stringToken); + } + } + } + /*Get Format */ + $urlPath = trim(rtrim($urlPath,'/')); + $intFormatLastPeriod = strrpos($urlPath, '.'); + $intFormatLastSlash = strrpos($urlPath, '/'); + if((($intFormatLastPeriod - $intFormatLastSlash) > 0) && ($intFormatLastPeriod !== false)){ + $stringFormat = strtolower(ltrim(substr($urlPath, $intFormatLastPeriod), '.')); + $urlPath = rtrim(substr($urlPath, 0, $intFormatLastPeriod), '/'); + if(!(in_array($stringFormat, $arraySupportedFormats))){ + $stringFormat = 'txt'; + } + } + /* Examine for Basic Elements */ + /* Check for off-by-one here. */ + + $pathArray = explode('/', $urlPath); + if(count($pathArray) < 3){ + $boolValidUrl = false; + } + else{ + /* API Call Version */ + $floatClientVersion = number_format(floatval($pathArray[0]), 2); /* A bad value will return 0 anyway. We are hardcoding decimals to 2. */ + if($floatClientVersion == 0){ + $boolValidUrl = false; + } + /* Category */ + if(ctype_alnum($pathArray[1])){ + $stringCategory = $pathArray[1]; + } + else{ + $boolValidUrl = false; + } + /* Verb */ + if(ctype_alnum($pathArray[2])){ + $stringPlanworldVerb = $pathArray[2]; + } + else{ + $boolValidUrl = false; + } + /* Get post data if available. */ + if(!empty($_POST['planworld_post'])){ /* Is this supposed to be hard-coded? */ + $stringPlanworldVerbSuffix = 'Post'; + $postData = $_POST['planworld_post']; + } + /* Save the other array elements as arguments. */ + if(count($pathArray) > 3){ + $arrayArguments = array_splice($pathArray, 3); + } + } + + + /* Execute Planworld Verb */ + if(($boolValidUrl) && ($boolSecurityCheckPassed)){ + if(file_exists($pathClientLibBase . '/' . $floatClientVersion . '/' . $stringCategory . '.php')){ + include_once($pathClientLibBase . '/' . $floatClientVersion . '/' . $stringCategory . '.php'); + $functionVerb = $stringPlanworldVerb . $stringPlanworldVerbSuffix; + if(function_exists($functionVerb)){ + $arrayRestInputs = array( + 'token' => $stringToken, + 'arguments' => $arrayArguments, + 'post' => $postData, + 'enginebasedir' => dirname(__FILE__) . '/../' + ); + $output = $functionVerb($arrayRestInputs); + $finalOutput = ''; + if(is_array($output)){ + if(strcmp($stringFormat, 'xml') == 0){ + $xmlOutput = new SimpleXMLElement('<' . $functionVerb . '>'); + array_to_xml($output, $xmlOutput); + $finalOutput = $xmlOutput->asXML(); + } + else if(strcmp($stringFormat, 'json') == 0){ + $jsonArray = array($stringPlanworldVerb => $output); + $finalOutput = json_encode($jsonArray); + } + else{ + $finalOutput = print_r($output,true); /* This is not debug code, this is to output text for now. */ + } + } + else if(is_object($output)){ + // We do not currently handle objects. + } + else{ + $stringOutput = strval($output); + $finalOutput = $stringOutput; + if(strcmp($stringFormat, 'xml') == 0){ + $finalOutput = '<' . $functionVerb . '>' . htmlspecialchars($stringOutput) . ''; + } + else if(strcmp($stringFormat, 'json') == 0){ + $jsonArray = array($stringPlanworldVerb => $stringOutput); + $finalOutput = json_encode($jsonArray); + } + else{ + $finalOutput = print_r($finalOutput,true); /* This is not debug code, this is to output text for now. */ + } + } + statusCode(200); + echo $finalOutput; + } + else{ + statusCode(400); + echo 'Either you are requesting an incorrect method, or this node has not implemented this method.'; + } + } + else if(file_exists($pathClientLibBase . '/' . $floatClientVersion)){ + statusCode(501); + echo 'Invalid Planworld REST category.'; + } + else{ + statusCode(501); + echo 'Unsupported Planworld API version.'; + } + } + else if(!($boolSecurityCheckPassed)){ + statusCode(403); + echo 'IP Locked by Intruder Detection or Invalid Token.'; + + } + else{ + statusCode(400); + echo 'Invalid REST Url.'; + } +} +else{ + statusCode(400); + echo 'No REST Url.'; +} + + +?> \ No newline at end of file diff --git a/code/client/oldjson.php b/code/client/oldjson.php new file mode 100644 index 0000000..bbf73ee --- /dev/null +++ b/code/client/oldjson.php @@ -0,0 +1,54 @@ + $v) $result[] = json_encode($k).':'.json_encode($v); + return '{' . join(',', $result) . '}'; + } + } +} +?> \ No newline at end of file diff --git a/code/clientlib/3.00/send.php b/code/clientlib/3.00/send.php new file mode 100644 index 0000000..e250e73 --- /dev/null +++ b/code/clientlib/3.00/send.php @@ -0,0 +1,95 @@ +retrieveToken($arrayRestInputs['token'])) && (count($arrayRestInputs['arguments']) > 0)){ + $arguments = $arrayRestInputs['arguments']; + $toUser = new User($arguments[0]); + $objectSend = new Send(); + $sendReturn = $objectSend->getMessages($objectToken->uid, $toUser->getUserID()); + if($sendReturn < 0){ + return ''; + } + else{ + if(is_array($sendReturn)){ + $limit = count($sendReturn); + for($i = 0; $i < $limit; $i++){ + $sendReturn[$i]['sent'] = date(DATE_ATOM, $sendReturn[$i]['sent']); + $sendReturn[$i]['seen'] = date(DATE_ATOM, $sendReturn[$i]['seen']); + } + } + return $sendReturn; + } + } + else{ + return ''; /* BUG: If the client does not send a token, an empty string is returned. An invalid token should result in an error somewhere. */ + } + } + else{ + return ''; + } +} + + +/* Send a message to a user. Takes two arguments -- user to send to and message to send. ) */ +function sendPost($arrayRestInputs){ + if((file_exists($arrayRestInputs['enginebasedir'] . '/lib/Send.php')) && (file_exists($arrayRestInputs['enginebasedir'] . '/lib/NodeToken.php')) ){ + require_once($arrayRestInputs['enginebasedir'] . '/lib/Send.php'); + require_once($arrayRestInputs['enginebasedir'] . '/lib/NodeToken.php'); + $objectToken = new NodeToken (); + if(($objectToken->retrieveToken($arrayRestInputs['token'])) && (count($arrayRestInputs['arguments']) > 0) && (!(empty($arrayRestInputs['post'])))){ + $arguments = $arrayRestInputs['arguments']; + $toUser = new User($arguments[0]); + $objectSend = new Send(); + $sendReturn = $objectSend->sendMessage($objectToken->uid, $toUser->getUserID(), $arrayRestInputs['post']); + return $sendReturn; + } + else{ + return false; + } + } + else{ + return false; + } +} + + +/* Get a list of send conversations with the other user, the time of the last message, and whether that message was seen. Takes no arguments. */ +function sendlistGet($arrayRestInputs){ + if((file_exists($arrayRestInputs['enginebasedir'] . '/lib/Send.php')) && (file_exists($arrayRestInputs['enginebasedir'] . '/lib/NodeToken.php')) ){ + require_once($arrayRestInputs['enginebasedir'] . '/lib/Send.php'); + require_once($arrayRestInputs['enginebasedir'] . '/lib/NodeToken.php'); + $objectToken = new NodeToken (); + if($objectToken->retrieveToken($arrayRestInputs['token'])){ + $objectSend = new Send(); + $sendlistReturn = $objectSend->getSendList($objectToken->uid); + if($sendlistReturn < 0){ + return ''; + } + else{ + if(is_array($sendlistReturn)){ + $limit = count($sendlistReturn); + for($i = 0; $i < $limit; $i++){ + $sendlistReturn[$i]['senddate'] = date(DATE_ATOM, $sendlistReturn[$i]['senddate']); + $sendlistReturn[$i]['seen'] = date(DATE_ATOM, $sendlistReturn[$i]['seen']); + } + } + return $sendlistReturn; + } + } + else{ + return ''; + } + } + else{ + return ''; + } +} +?> \ No newline at end of file diff --git a/code/clientlib/3.00/system.php b/code/clientlib/3.00/system.php new file mode 100644 index 0000000..4421a9d --- /dev/null +++ b/code/clientlib/3.00/system.php @@ -0,0 +1,22 @@ + \ No newline at end of file diff --git a/code/clientlib/security.php b/code/clientlib/security.php new file mode 100644 index 0000000..83c57a3 --- /dev/null +++ b/code/clientlib/security.php @@ -0,0 +1,33 @@ +retrieveToken($token)){ + if($securityCheckObject->ipCheck($ip)){ + $boolPass = true; + } + else{ + $securityCheckObject->addIpFailure($ip); + $boolPass = false; + } + } + else{ + $securityCheckObject->addIpFailure($ip); + $boolPass = false; + } + return $boolPass; + +} + + + +?> \ No newline at end of file diff --git a/code/config.php b/code/config.php new file mode 100644 index 0000000..4f1343a --- /dev/null +++ b/code/config.php @@ -0,0 +1,32 @@ + diff --git a/code/lib/NodeToken.php b/code/lib/NodeToken.php new file mode 100644 index 0000000..fea073b --- /dev/null +++ b/code/lib/NodeToken.php @@ -0,0 +1,218 @@ +dbh = Planworld::_connect(); + $this->valid = false; + } + + + /* Fill a freshly-constructed token with new values. */ + function createToken($usernameFromAuth, $clientname){ + $badToken = true; + $token = false; + $this->username = $usernameFromAuth; + while($badToken){ + $proposedTokenNumber = NODE_TOKEN_PREFIX + rand(1, 999999); + $checkToken = new NodeToken(); + if ($checkToken->retrieveToken($proposedTokenNumber) == false){ + $this->tokenNumber = $proposedTokenNumber; + $badToken = false; + } + else{ + $proposedTokenNumber = 0; + $badToken = true; + } + unset($checkToken); + } + if(!ctype_alnum($clientname)){ + $clientname = "anoncow"; + } + $this->clientname = $clientname; + $this->expire = time() + TOKEN_LIFE; + $tempUser = new User($this->username); + $this->uid = $tempUser->getUserID(); + if($this->uid > 0){ + $this->destroyUserClientTokens($this->uid, $this->clientname); + try{ + if(!$this->dbh){ + throw new PDOException('Database connection not initialized.'); + } + $query = $this->dbh->prepare('INSERT INTO tokens (token, uid, expire, clientname) VALUES (:token, :uid, :expire, :clientname)'); + $queryArray = array('token' => $this->tokenNumber, 'uid' => $this->uid, 'expire' => $this->expire, 'clientname' => $this->clientname); + $query->execute($queryArray); + $this->valid = true; + } + catch(PDOException $badquery){ + $this->valid = false; + } + } + else{ + $this->valid = false; + } + return $this->valid; + } + + + /* Populate a token object with existing token information from DB. */ + function retrieveToken($token){ + $this->valid = false; + if($this->validTokenFormat($token)){ /* Prevent SQL injection or other exploits. */ + $this->tokenNumber = $token; + try{ + if(!$this->dbh){ + throw new PDOException('Database connection not initialized.'); + } + $query = $this->dbh->prepare('SELECT uid, expire, clientname FROM tokens WHERE token=:token'); + $queryArray = array('token' => $this->tokenNumber); + $query->execute($queryArray); + $result = $query->fetch(); + $this->uid = (int)$result['uid']; + $this->clientname = (string)$result['clientname']; + $this->expire = (int)$result['expire']; + if(time() < $this->expire){ + $this->valid = true; + } + /* Token exists but is expired. Get rid of it. */ + else{ + $this->destroyToken($this->tokenNumber); + $this->valid = false; + } + $tempUser = new User($this->uid); + $this->username = $tempUser->getUsername(); + if($this->username == false){ + $this->valid = false; + } + } + catch(PDOException $badquery){ + $this->valid = false; + return false; + } + } + else{ + $this->valid = false; + } + return $this->valid; + } + + + /* Destroy a specific token, usually because of expiration. */ + function destroyToken($token){ + try{ + if(!$this->dbh){ + throw new PDOException('Database connection not initialized.'); + } + $query = $this->dbh->prepare('DELETE FROM tokens WHERE token=:token'); + $queryArray = array('token' => $token); + return $query->execute($queryArray); + } + catch(PDOException $badquery){ + return false; + } + } + + + /* Destroy tokens that are expire before now. */ + function destroyExpiredTokens($time){ + if(is_numeric($time)){ + try{ + if(!$this->dbh){ + throw new PDOException('Database connection not initialized.'); + } + $query = $this->dbh->prepare('DELETE FROM tokens WHERE expire<=:time'); + $queryArray = array('time' => $time); + return $query->execute($queryArray); + } + catch(PDOException $badquery){ + return false; + } + } + else{ + return false; + } + } + + + /* Utility function. Weed out duplicate user tokens with the same client name. */ + function destroyUserClientTokens($uid, $clientname){ + if(is_numeric($uid)){ + try{ + if(!$this->dbh){ + throw new PDOException('Database connection not initialized.'); + } + $query = $this->dbh->prepare('DELETE FROM tokens WHERE uid=:uid AND clientname=:clientname'); + $queryArray = array('uid' => $uid, 'clientname' => $clientname); + return $query->execute($queryArray); + } + catch(PDOException $badquery){ + return false; + } + } + else{ + return false; + } + } + + + /* Destroy all tokens for the given user. */ + function destroyUserTokens($uid){ + if(is_numeric($uid)){ + try{ + if(!$this->dbh){ + throw new PDOException('Database connection not initialized.'); + } + $query = $this->dbh->prepare('DELETE FROM tokens WHERE uid= uid'); + $queryArray = array('uid' => $uid); + return $query->execute($queryArray); + } + catch(PDOException $badquery){ + return false; + } + } + else{ + return false; + } + } + + + /* Test the validity of a token's format. + In particular, this guards against SQL injection. */ + function validTokenFormat($token){ + $isValid = false; + if(ctype_alnum($token)){ + $isValid = true; + } + else{ + $isValid = false; + } + return $isValid; + } + + + /* End. */ +} + +?> \ No newline at end of file diff --git a/code/lib/Planworld.php b/code/lib/Planworld.php new file mode 100644 index 0000000..3534145 --- /dev/null +++ b/code/lib/Planworld.php @@ -0,0 +1,852 @@ +setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); + } + catch(PDOException $nodbh){ + return false; /* Previously this returned PLANWORLD_ERROR (i.e. -1) but if we set it to false it makes it easier to throw exceptions up the call stack. */ + } + } + return $dbh; + } + + /* + int Planworld::addUser ($uid) + adds user with name $uid; returns user id + In a more sophisticated world we would return different errors based on issue. + */ + public static function addUser ($uid) { + if (empty($uid)) { + return false; + } + /* This statement is an excellent candidate for revision in a future release. JLO2 20161212 */ + if (strstr($uid, '@')) { + $remote = 'Y'; + } + else { + $remote = 'N'; + } + try{ + $intId = Planworld::incrementUserSeq(); + $dbh = Planworld::_connect(); + if(!$dbh){ + throw new PDOException('Database connection not initialized.'); + } + $query = $dbh->prepare('INSERT INTO users (id, username, remote, first_login) VALUES (:id, :username, :remote, :first_login)'); + $queryArray = array('id' => $intId, 'username' => addslashes($uid), 'remote' => $remote, 'first_login'=> time()); + $result = $query->execute($queryArray); + if(count($result) < 1){ + return PLANWORLD_ERROR; + } + else{ + return $intId; + } + } + catch(PDOException $badquery){ + echo 'OOO'; + return PLANWORLD_ERROR; + } + return PLANWORLD_ERROR; + } + + + /* int incrementUserSeq + Exists because we are not using auto-incrementation anymore. + */ + public static function incrementUserSeq() { + try{ + $dbh = Planworld::_connect(); + if(!$dbh){ + throw new PDOException('Database connection not initialized.'); + } + $queryRead = $dbh->prepare('SELECT id FROM userid_seq'); + $queryRead->execute(); + $resultRead = $queryRead->fetch(); + $intNewId = (int)$resultRead['id'] + 1; + $query = $dbh->prepare('UPDATE userid_seq SET id=:id'); + $queryArray = array('id' => $intNewId); + $query->execute($queryArray); + return $intNewId; + } + catch(PDOException $badquery){ + return PLANWORLD_ERROR; + } + return PLANWORLD_ERROR; + } + + /* + int Planworld::nameToID ($uid) + converts textual $uid to numeric representation + If the user is not found but seems remote, add user. + */ + function nameToID ($uid) { + static $table; /* persistent lookup table */ + if (is_string($uid)) { + if (isset($table[$uid])) { + return $table[$uid]; + } + else { + try{ + $dbh = Planworld::_connect(); + if(!$dbh){ + throw new PDOException('Database connection not initialized.'); + } + $query = $dbh->prepare('SELECT id FROM users WHERE username= :username'); + $queryArray = array('username' => $uid); + $query->execute($queryArray); + $result = $query->fetch(); + /* Handle remote user who has not been added yet. */ + if ((!$result) && strstr($uid, '@')) { + return Planworld::addUser($uid); + } + else if (!$result) { + return PLANWORLD_ERROR; + } + else { + $table[$uid] = (int) $result['id']; + return (int) $table[$uid]; + } + } + catch(PDOException $badquery){ + return PLANWORLD_ERROR; + } + } + } + else { + return PLANWORLD_ERROR; + } + } + + + /* + string Planworld::idToName ($uid) + converts numeric $uid to string representation + Note that the argument must not look like a string -- it must be sent as an int. + */ + function idToName ($uid) { + static $table; /* persistent lookup table */ + if (is_int($uid)) { + if (isset($table[$uid])) { + return $table[$uid]; + } + else { + try{ + $dbh = Planworld::_connect(); + if(!$dbh){ + throw new PDOException('Database connection not initialized.'); + } + $query = $dbh->prepare('SELECT username FROM users WHERE id= :uid'); + $queryArray = array('uid' => $uid); + $query->execute($queryArray); + $result = $query->fetch(); + if (!$result){ + return PLANWORLD_ERROR; + } + else { + $table[$uid] = $result['username']; + return $table[$uid]; + } + } + catch(PDOException $badquery){ + return PLANWORLD_ERROR; + } + } + } + else { + return PLANWORLD_ERROR; + } + return PLANWORLD_ERROR; + } + + + /* + bool Planworld::isUser ($uid) + returns whether $uid is an actual user + If uid is numeric, make sure it is sent as an int and not a string. + This function is static because of how it is called from User.php and likely others. + */ +public static function isUser ($uid, $force=false) { + static $table; + $query = ''; /* Placed here to define. Handled appropriately if continues null. */ + if (isset($table[$uid]) && !$force) { + return $table[$uid]; + } + else if(empty($uid)){ + return PLANWORLD_ERROR; + } + try{ + $dbh = Planworld::_connect(); + if(!$dbh){ + throw new PDOException('Database connection not initialized.'); + } + if (is_int($uid)) { + $query = $dbh->prepare('SELECT COUNT(id) AS count FROM users WHERE id= :uid'); + } + else if (is_string($uid)) { + $uid = addslashes($uid); + $query = $dbh->prepare('SELECT COUNT(id) AS count FROM users WHERE username= :uid'); + } + $queryArray = array('uid' => $uid); + $query->execute($queryArray); + $result = $query->fetch(); + if (!$result){ + return PLANWORLD_ERROR; + } + else if($result['count'] < 1){ + $table[$uid] = false; + return false; + } + else if($result['count'] == 1){ + $table[$uid] = true; + return true; + } + else{ + return PLANWORLD_ERROR; + } + } + catch(PDOException $badquery){ + return PLANWORLD_ERROR; + } + return PLANWORLD_ERROR; + } + + + /* + bool Planworld::isRemoteUser ($uid) + returns whether $uid is a remote user (assuming that $uid is a valid user) + */ + function isRemoteUser ($uid) { + try{ + $dbh = Planworld::_connect(); + if(!$dbh){ + throw new PDOException('Database connection not initialized.'); + } + if (is_int($uid)) { + $query = $dbh->prepare('SELECT remote FROM users WHERE id= :uid'); + } + else if (is_string($uid)) { + $uid = addslashes($uid); + $query = $dbh->prepare('SELECT remote FROM users WHERE username= :uid'); + } + $queryArray = array('uid' => $uid); + $query->execute($queryArray); + $result = $query->fetch(); + if (!$result){ + return PLANWORLD_ERROR; + } + else{ + return ($result['remote'] == 'Y') ? true : false; + } + } + catch(PDOException $badquery){ + return PLANWORLD_ERROR; + } + return PLANWORLD_ERROR; + } + + + + /* + bool Planworld::isWorldViewable ($uid) + returns whether $uid has a world-viewable plan + */ + function isWorldViewable ($uid) { + try{ + $dbh = Planworld::_connect(); + if(!$dbh){ + throw new PDOException('Database connection not initialized.'); + } + if (is_int($uid)) { + $query = $dbh->prepare('SELECT world FROM users WHERE id= :uid'); + } + else if (is_string($uid)) { + $uid = addslashes($uid); + $query = $dbh->prepare('SELECT world FROM users WHERE username= :uid'); + } + $queryArray = array('uid' => $uid); + $query->execute($queryArray); + $result = $query->fetch(); + if (!$result){ + return PLANWORLD_ERROR; + } + else{ + return ($result['world'] == 'Y') ? true : false; + } + } + catch(PDOException $badquery){ + return PLANWORLD_ERROR; + } + return PLANWORLD_ERROR; + } + + + /* + int Planworld::getRandomUser () + pick a user (with a plan) at random + Changed in version 3 to grab all UIDs with local plans and get the random value from PHP. + This was done because the long SQL query is faster than expected and no two databases use + the same syntax for retrieving random values. Better to use a cross-db compatible syntax + and then have PHP do the randomization. + */ + function getRandomUser() { + try{ + $dbh = Planworld::_connect(); + if(!$dbh){ + throw new PDOException('Database connection not initialized.'); + } $query = $dbh->prepare('SELECT DISTINCT uid FROM plans'); + $query->execute(); + $result = $query->fetchAll(); + if (!$result){ + return PLANWORLD_ERROR; + } + else{ + $randomKey = array_rand($result, 1); + return (int) $result[$randomKey]['uid']; + } + } + catch(PDOException $badquery){ + return PLANWORLD_ERROR; + } + } + + + + + /** + * array Planworld::getNodeInfo ($host) + * Returns node information for $host. + */ + function getNodeInfo ($host) { + try{ + $dbh = Planworld::_connect(); + if(!$dbh){ + throw new PDOException('Database connection not initialized.'); + } + $query = $dbh->prepare('SELECT name, hostname, path, port, version FROM nodes WHERE name= :host'); + $queryArray = array('host' => $host); + $query->execute($queryArray); + $result = $query->fetch(); + if (!$result){ + return PLANWORLD_ERROR; + } + else { + $return = array('Name' => $result['name'], + 'Hostname' => $result['hostname'], + 'Path' => $result['path'], + 'Port' => (int) $result['port'], + 'Version' => (int) $result['version']); + return $return; + } + } + catch(PDOException $badquery){ + return PLANWORLD_ERROR; + } + return PLANWORLD_ERROR; + } + + + + /** + * array Planworld::getNodes () + * Return the node list. + */ + function getNodes () { + try{ + $dbh = Planworld::_connect(); + if(!$dbh){ + throw new PDOException('Database connection not initialized.'); + } + $query = $dbh->prepare('SELECT name, hostname, path, port, version FROM nodes ORDER BY name'); + $query->execute(); + $result = $query->fetchAll(); + if (!$result){ + return PLANWORLD_ERROR; + } + else { + $return = array(); + foreach($result as $row){ + $return[] = array('Name' => $row['name'], + 'Hostname' => $row['hostname'], + 'Path' => $row['path'], + 'Port' => (int) $row['port'], + 'Version' => (int) $row['version']); + } + return $return; + } + } + catch(PDOException $badquery){ + return PLANWORLD_ERROR; + } + return PLANWORLD_ERROR; + } + + + + /** + * array Planworld::getTimezones () + * Return the list of available timezones. + */ + function getTimezones () { + try{ + $dbh = Planworld::_connect(); + if(!$dbh){ + throw new PDOException('Database connection not initialized.'); + } + $query = $dbh->prepare('SELECT name FROM timezones ORDER BY name'); + $query->execute(); + $result = $query->fetchAll(); + if (!$result){ + return PLANWORLD_ERROR; + } + else { + $return = array(); + foreach($result as $row){ + $return[] = $row['name']; + } + return $return; + } + } + catch(PDOException $badquery){ + return PLANWORLD_ERROR; + } + return PLANWORLD_ERROR; + } + + + + + + function getAllUsers () { + try{ + $dbh = Planworld::_connect(); + if(!$dbh){ + throw new PDOException('Database connection not initialized.'); + } + $query = $dbh->prepare('SELECT username FROM users WHERE last_login!=0 AND remote="N" ORDER BY username'); + $query->execute(); + $result = $query->fetchAll(); + if (!$result){ + return PLANWORLD_ERROR; + } + else { + $return = array(); + foreach($result as $row){ + $return[] = $row['username']; + } + return $return; + } + } + catch(PDOException $badquery){ + return PLANWORLD_ERROR; + } + return PLANWORLD_ERROR; + } + + + + + /** + * array Planworld::getAllUsersWithPlans ($start) + * Returns a list of usernames (optionally filtered) for users who have plans. + * The original version of this function built the query with if/then statements. + * The PDO version is a bit more convoluted due to how prepared statements properly work. + * Note: (JLO2) I don't underand what the '#' entry is supposed to do. At first I thought it + * might look for all plans ending with a number, but looking at the original regex it appears + * to ask specifically for plans starting with a number, of which there is only one at this time + * and that one appears to be a mistake rather than some sort of clever system function. + * Note also that the LIKE value is weakly escaped (try doing something like $start = '%an' ) + * but I will allow it because it is a filter rather than an exploit and might prove useful. + */ + + function getAllUsersWithPlans ($start = null) { + $query = ''; + $result = ''; + try{ + $dbh = Planworld::_connect(); + if(!$dbh){ + throw new PDOException('Database connection not initialized.'); + } + if(!isset($start)){ + $query = $dbh->prepare('SELECT username FROM users, plans WHERE users.id=plans.uid ORDER BY username'); + $query->execute(); + } + else if ($start == '#'){ + $query = $dbh->prepare('SELECT username FROM users, plans WHERE users.id=plans.uid AND users.username REGEXP "^[0-9].*" ORDER BY username'); + $query->execute(); + } + else{ + $query = $dbh->prepare('SELECT username FROM users, plans WHERE users.id=plans.uid AND users.username LIKE :start ORDER BY username'); + $queryArray = array('start' => $start . '%'); + $query->execute($queryArray); + } + $result = $query->fetchAll(); + if (!$result){ + return PLANWORLD_ERROR; + } + else { + $return = array(); + foreach($result as $row){ + $return[] = $row['username']; + } + return $return; + } + } + catch(PDOException $badquery){ + return PLANWORLD_ERROR; + } + return PLANWORLD_ERROR; + } + + + + + + /** + * Fetch the $num most recent updates. + * JLO2 20150424 + * The PDO implementation differs from the original implementation in two key respects. + * 1. PDO does not support a LIMIT statement, and it appears that LIMIT is not cross-platform anyway. + * The work-around for this is to fetch all rows and then limit the array that is created. + * 2. The return is no longer a raw /DBSQL row but is instead a matrix (array of arrays). + * This means that functions which call this method will need to be adjusted. + */ + function getLastUpdates ($num=false) { + try{ + $dbh = Planworld::_connect(); + if(!$dbh){ + throw new PDOException('Database connection not initialized.'); + } + $query = $dbh->prepare('SELECT username, last_update FROM users WHERE remote="N" ORDER BY last_update DESC'); + $query->execute(); + $result = $query->fetchAll(); + if (!$result){ + return PLANWORLD_ERROR; + } + else { + $return = array(); + if((is_int($num)) && ($num > 0) && ($num < count($result))){ + $result = array_slice($result, 0, $num); + } + foreach($result as $row){ + $return[] = array('Username' => $row['username'], 'Last_update' => $row['last_update']); + } + return $return; + } + } + catch(PDOException $badquery){ + return PLANWORLD_ERROR; + } + return PLANWORLD_ERROR; + } + + + + + + /** + * Fetch the $num newest users. + * JLO2 20150424 + * The PDO implementation differs from the original implementation in two key respects. + * 1. PDO does not support a LIMIT statement with BindParam (I dislike BindParam), and it appears that LIMIT is not cross-platform anyway. + * The work-around for this is to fetch all rows and then limit the array that is created. + * I can hear some of you screaming now, but if DB indexes are correct the query should be fast + * and the work done on the DB side is roughly the same in any case. + * 2. The return is no longer a raw /DBSQL row but is instead a matrix (array of arrays). + * This means that functions which call this method will need to be adjusted. + */ + function getNewUsers ($num=false) { + try{ + $dbh = Planworld::_connect(); + if(!$dbh){ + throw new PDOException('Database connection not initialized.'); + } + $query = $dbh->prepare('SELECT username, first_login, last_update FROM users WHERE remote="N" AND last_login > 0 ORDER BY first_login DESC'); + $query->execute(); + $result = $query->fetchAll(); + if (!$result){ + return PLANWORLD_ERROR; + } + else { + $return = array(); + if((is_int($num)) && ($num > 0) && ($num < count($result))){ + $result = array_slice($result, 0, $num); + } + foreach($result as $row){ + $return[] = array('Username' => $row['username'], 'First_login' => $row['first_login'], 'Last_update' => $row['last_update']); + } + return $return; + } + } + catch(PDOException $badquery){ + return PLANWORLD_ERROR; + } + return PLANWORLD_ERROR; + } + + + + + + + + /** + * int | array(int) Planworld::getLastLogin ($uid, $host) + * Gets the last login time for $uid (from $host, if applicable) + */ + + function getLastLogin ($uid, $host=null) { + $dbh = Planworld::_connect(); + $boolReturnArray = true; + /* The operation is presumed to be happening on the local node. */ + if((!isset($host)) && ($dbh)){ + if(!is_array($uid)){ + $uid = array($uid); + $boolReturnArray = false; + } + $return = array(); + foreach ($uid as $single){ + if(is_int($single)){ + $query = $dbh->prepare('SELECT id, last_login FROM users WHERE id= :single'); + $queryArray = array('single' => $single); + $query->execute($queryArray); + $result = $query->fetch(); + $return[$result['id']] = (int) $result['last_login']; + } + else{ + $query = $dbh->prepare('SELECT username as id, last_login FROM users WHERE username= :single'); + $queryArray = array('single' => $single); + $query->execute($queryArray); + $result = $query->fetch(); + $return[$result['id']] = (int) $result['last_login']; + } + } + if(!$boolReturnArray){ + return $return[$uid[0]]; + } + else{ + return $return; + } + } + /* $host is set so this is a remote operation. Find the node to send the operation to. */ + else if ($node = Planworld::getNodeInfo($host)) { + /* remote fetch-by-username (forced) */ + if ($node['Version'] < 2) { + $result = Planworld::_call($node, 'users.getLastLogin', array($uid)); + } + else { + $result = Planworld::_call($node, 'planworld.user.getLastLogin', array($uid)); + } + /* Question: Should we have previously declared result since otherwise it is declared during if/then? + Obviously the code has been working for years as-is but I (JLO2) still don't like having variables + out of scope, even if only incidentally. + */ + /* Freshening the remote cache and returning: Many values were received. */ + if (is_array($result)) { + /* freshen the cache */ + foreach ($result as $u=>$t) { + try{ + if(!$dbh){ + throw new PDOException('Database connection not initialized.'); + } + $query = $dbh->prepare('UPDATE users SET last_login= :last_login WHERE username= :username'); + $fullu = $u . '@' . $host; + $queryArray = array('last_login' => $t, 'username' => $fullu); + $query->execute($queryArray); + } + catch(PDOException $badquery){ + pi(); /* Using in place of No-Op as we do not need to do anything if this fails. */ + } + } + } + /* Freshening the remote cache and returning: A single value was received. */ + else { + try{ + if(!$dbh){ + throw new PDOException('Database connection not initialized.'); + } + $query = $dbh->prepare('UPDATE users SET last_login= :last_login WHERE username= :username'); + $fullu = $uid . '@' . $host; + $queryArray = array('last_login' => $result, 'username' => $fullu); + $query->execute($queryArray); + } + catch(PDOException $badquery){ + pi(); /* Using in place of No-Op as we do not need to do anything if this fails. */ + } + } + return $result; + } + /* $host is set but the node is unknown to us. Return false because of unknown node. */ + else{ + return false; + } + } + + + /** + * int | array(int) Planworld::getLastUpdate ($uid, $host) + * Gets the last update time for $uid (from $host, if applicable) + */ + function getLastUpdate ($uid, $host=null) { + $dbh = Planworld::_connect(); + $boolReturnArray = true; + /* The operation is presumed to be happening on the local node. */ + if((!isset($host)) && ($dbh)){ + if(!is_array($uid)){ + $uid = array($uid); + $boolReturnArray = false; + } + $return = array(); + foreach ($uid as $single){ + if(is_int($single)){ + $query = $dbh->prepare('SELECT id, last_update FROM users WHERE id= :single'); + $queryArray = array('single' => $single); + $query->execute($queryArray); + $result = $query->fetch(); + $return[$result['id']] = (int) $result['last_update']; + } + else{ + $query = $dbh->prepare('SELECT username as id, last_update FROM users WHERE username= :single'); + $queryArray = array('single' => $single); + $query->execute($queryArray); + $result = $query->fetch(); + $return[$result['id']] = (int) $result['last_update']; + } + } + if(!$boolReturnArray){ + return $return[$uid[0]]; + } + else{ + return $return; + } + } + /* $host is set so this is a remote operation. Find the node to send the operation to. */ + else if ($node = Planworld::getNodeInfo($host)) { + /* remote fetch-by-username (forced) */ + if ($node['Version'] < 2) { + $result = Planworld::_call($node, 'users.getLastUpdate', array($uid)); + } + else { + $result = Planworld::_call($node, 'planworld.user.getLastUpdate', array($uid)); + } + /* Question: Should we have previously declared result since otherwise it is declared during if/then? + Obviously the code has been working for years as-is but I (JLO2) still don't like having variables + out of scope, even if only incidentally. + */ + /* Freshening the remote cache and returning: Many values were received. */ + if (is_array($result)) { + /* freshen the cache */ + foreach ($result as $u=>$t) { + try{ + if(!$dbh){ + throw new PDOException('Database connection not initialized.'); + } + $query = $dbh->prepare('UPDATE users SET last_update= :last_login WHERE username= :username'); + $fullu = $u . '@' . $host; + $queryArray = array('last_update' => $t, 'username' => $fullu); + $query->execute($queryArray); + } + catch(PDOException $badquery){ + pi(); /* Using in place of No-Op as we do not need to do anything if this fails. */ + } + } + } + /* Freshening the remote cache and returning: A single value was received. */ + else { + try{ + if(!$dbh){ + throw new PDOException('Database connection not initialized.'); + } + $query = $dbh->prepare('UPDATE users SET last_update= :last_login WHERE username= :username'); + $fullu = $uid . '@' . $host; + $queryArray = array('last_update' => $result, 'username' => $fullu); + $query->execute($queryArray); + } + catch(PDOException $badquery){ + pi(); /* Using in place of No-Op as we do not need to do anything if this fails. */ + } + } + return $result; + } + /* $host is set but the node is unknown to us. Return false because of unknown node. */ + else{ + return false; + } + } + + + /** + * Fetches a preference for this user. + */ + function getPreference ($uid, $name) { + try{ + $dbh = Planworld::_connect(); + if(!$dbh){ + throw new PDOException('Database connection not initialized.'); + } + $query = $dbh->prepare('SELECT value FROM preferences WHERE uid= :uid AND name= :name'); + $queryArray = array('uid' => $uid, 'name' => $name); + $query->execute($queryArray); + $result = $query->fetch(); + if (!$result){ + return PLANWORLD_ERROR; + } + else { + return $result; + } + } + catch(PDOException $badquery){ + return PLANWORLD_ERROR; + } + return PLANWORLD_ERROR; + } + + + public static function getRFC3339DateFromTimeStamp($timestampUnix){ + $returnDate = new DateTime(); + $returnDate->setTimestamp($timestampUnix); + return $returnDate->format(DateTime::RFC3339); + } + + function basicTextSanitization($textArbitrary){ + $textSanitized = htmlentities(strip_tags(addslashes($textArbitrary))); + return $textSanitized; + } + + /* THINK LONG AND HARD ABOUT WHAT YOU ARE DOING HERE VS. CONFIG.PHP */ + public static function getArrayOfAllowedPreferences(){ + return array('admin','journal','journal_divider','journal_entries','journal_order','send_forward','snitchtracker','timezone'); + } + +} +?> \ No newline at end of file diff --git a/code/lib/Security.php b/code/lib/Security.php new file mode 100644 index 0000000..c2e34e4 --- /dev/null +++ b/code/lib/Security.php @@ -0,0 +1,143 @@ +dbh = Planworld::_connect(); + if(defined("IPTIMEOUT")){ + $this->timeout = IPTIMEOUT; + } + else{ + $this->timeout = 1200; + } + if(defined("MAXBADLOGINS")){ + $this->maxBadLogins = MAXBADLOGINS; + } + else{ + $this->maxBadLogins = 1; + } + + } + + + function ipCheck($ip){ + $boolIpCheckPassed = false; + if($this->currentIpFailures($ip) < $this->maxBadLogins){ + $boolIpCheckPassed = true; + } + else{ + $boolIpCheckPassed = false; + } + return $boolIpCheckPassed; + } + + function resetIpTimeout($ip){ + $boolSuccess=false; + if (filter_var($ip, FILTER_VALIDATE_IP)){ + try{ + if(!$this->dbh){ + throw new PDOException('Database connection not initialized.'); + } + $query = $this->dbh->prepare('UPDATE security SET errorcount=0, lasterror=0 where ip=:ip'); + $queryArray = array('ip' => $ip); + $result=$query->execute($queryArray); + $boolSuccess = true; + } + catch(PDOException $badquery){ + $boolSuccess = false; + } + } + else{ + $boolSuccess = false; + } + return $boolSuccess; + + } + + + function addIpFailure($ip){ + $boolSuccess=false; + if (filter_var($ip, FILTER_VALIDATE_IP)){ + try{ + if(!$this->dbh){ + throw new PDOException('Database connection not initialized.'); + } + $queryCount = $this->dbh->prepare('SELECT COUNT("ip") as count FROM security WHERE ip=:ip'); + $queryCountArray = array('ip' => $ip); + $queryCount->execute($queryCountArray); + $resultCount = $queryCount->fetch(); + $intCount = (int)$resultCount['count']; + if($intCount > 0){ + $intCurrentFailures = $this->currentIpFailures($ip); + $queryUpdate = $this->dbh->prepare('UPDATE security SET errorcount=:errorcount, lasterror=:timestamp WHERE ip=:ip'); + $queryUpdateArray = array('ip' => $ip, 'errorcount' => ($intCurrentFailures + 1), 'timestamp' => time()); /* $intCurrentFailures++ doesn't work */ + $queryUpdate->execute($queryUpdateArray); + } + else{ + $queryInsert = $this->dbh->prepare('INSERT INTO security (ip, errorcount, lasterror) VALUES (:ip,"1",:timestamp)'); + $queryInsertArray = array('ip' => $ip, 'timestamp' => time()); + $queryInsert->execute($queryInsertArray); + } + $boolSuccess = true; + } + catch(PDOException $badquery){ + $boolSuccess = false; + } + } + else{ + $boolSuccess = false; + } + return $boolSuccess; + + } + + + function currentIpFailures($ip){ + $intCurrentFailures = 0; + if (filter_var($ip, FILTER_VALIDATE_IP)){ + try{ + if(!$this->dbh){ + throw new PDOException('Database connection not initialized.'); + } + $query = $this->dbh->prepare('SELECT errorcount, lasterror FROM security WHERE ip=:ip'); + $queryArray = array('ip' => $ip); + $query->execute($queryArray); + $result = $query->fetch(); + $timeLastError = (int)$result['lasterror']; + if(time() - $timeLastError > $this->timeout){ + $intCurrentFailures = 0; + $this->resetIpTimeout($ip); + } + else{ + $intCurrentFailures = (int)$result['errorcount']; + } + } + catch(PDOException $badquery){ + pi(); /* NO OP */ + } + } + return $intCurrentFailures; + + } + + + + /* End. */ +} + +?> \ No newline at end of file diff --git a/code/lib/Send.php b/code/lib/Send.php new file mode 100644 index 0000000..8422254 --- /dev/null +++ b/code/lib/Send.php @@ -0,0 +1,153 @@ +isUser($uid)) && ($planworldForGetMessages->isUser($to_uid))){ + $dbh = Planworld::_connect(); + try{ + $query = $dbh->prepare('UPDATE send SET seen= :currenttime WHERE uid= :uid AND to_uid= :to_uid AND seen = 0'); + $queryArray = array('currenttime' => time(), 'uid' => $uid, 'to_uid' => $to_uid); + $query->execute($queryArray); + $query2 = $dbh->prepare('select u.username as fromuser, u2.username as touser, s.sent, s.seen, s.message from users u, send s LEFT JOIN users as u2 on u2.id=s.to_uid where ((s.uid=:uid AND s.to_uid=:to_uid) OR (s.uid=:to_uid AND s.to_uid=:uid)) and s.uid=u.id ORDER BY s.sent ASC'); + $queryArray2 = array('uid' => $uid, 'to_uid' => $to_uid); + $query2->execute($queryArray2); + $result = $query2->fetchAll(); + if (!$result){ + return PLANWORLD_ERROR; + } + else { + return $result; + } + } + catch(PDOException $badquery){ + return PLANWORLD_ERROR; + } + } + return PLANWORLD_ERROR; + } + + + /** + * Send a message from $uid to $to_uid + */ + function sendMessage ($uid, $to_uid, $message) { + $dbh = Planworld::_connect(); + if(!$dbh){ + return false; + } + $planworldForSendMessages = new Planworld(); + /* If the message is being sent to a remote user, do this. */ + if ($planworldForSendMessages->isRemoteUser($to_uid)) { + list($to_user, $host) = split("@", $planworldForSendMessages->idToName($to_uid)); + $from_user = $planworldForSendMessages->idToName($uid) . "@" . PW_NAME; + $nodeinfo = $planworldForSendMessages->getNodeInfo($host); + xu_rpc_http_concise(array('method' => 'planworld.send.sendMessage', + 'args' => array($from_user, $to_user, $message), + 'host' => $nodeinfo['Hostname'], + 'uri' => $nodeinfo['Path'], + 'port' => $nodeinfo['Port'], + 'debug' => 0)); + try{ + $query = $dbh->prepare('INSERT INTO send (uid, to_uid, sent, seen, message) VALUES (:uid,:to_uid,:currenttime,:currenttime,:message)'); + $queryArray = array('uid' => $uid, 'to_uid' => $to_uid, 'currenttime' => time(), 'message'=> $planworldForSendMessages->basicTextSanitization($message)); + return $query->execute($queryArray); + } + catch(PDOException $badquery){ + return false; + } + } + /* If the message is being sent to a local user, do this. */ + else { + /* This is sort of a secret feature. Forwarding of send messages from one user to another (i.e. if you had an old user or a shared user. */ + $fwd = $planworldForSendMessages->getPreference($to_uid, 'send_forward'); + $to_uid_final = $to_uid; + $message_final = $message; + if (($fwd != PLANWORLD_ERROR) && ($fwd)) { + $to_uid_final = $planworldForSendMessages->nameToId($fwd); + /* error_log("forwarding to ${fwd_uid} ({$fwd})"); */ + /* If the message is being forwarded to a remote user, do this. */ + if ($planworldForSendMessages->isRemoteUser($to_uid_final)) { + $message_final = "[fwd:" . $planworldForSendMessages->idToName($to_uid) . "@" . PW_NAME . "] " . $message; + list($to_user, $host) = split("@", $fwd); + if (!$planworldForSendMessages->isRemoteUser($uid)) { + $from_user = $planworldForSendMessages->idToName($uid) . "@" . PW_NAME; + } + else { + $from_user = $planworldForSendMessages->idToName($uid); + list($f_user, $f_host) = split('@', $from_user); + if ($f_host == $host) { + $from_user = $f_user; + } + } + $nodeinfo = $planworldForSendMessages->getNodeInfo($host); + xu_rpc_http_concise(array('method' => 'planworld.send.sendMessage', + 'args' => array($from_user, $to_user, $message_final), + 'host' => $nodeinfo['Hostname'], + 'uri' => $nodeinfo['Path'], + 'port' => $nodeinfo['Port'], + 'debug' => 0)); + } + else { + $message_final = "[fwd:" . $planworldForSendMessages->idToName($to_uid) . "] " . $message; + } + } + try{ + $query = $dbh->prepare('INSERT INTO send (uid, to_uid, sent, seen, message) VALUES (:uid,:to_uid,:currenttime,:currenttime,:message)'); + $queryArray = array('uid' => $uid, 'to_uid' => $to_uid_final, 'currenttime' => time(), 'message'=> $planworldForSendMessages->basicTextSanitization($message_final)); + return $query->execute($queryArray); + } + catch(PDOException $badquery){ + return false; + } + } + return false; + } + + + /** + * Return a watchlist of send information. Validation and date processing handled by api library. + */ + + function getSendList ($uid) { + $planworldForGetSendList = new Planworld(); + if($planworldForGetSendList->isUser($uid)){ + $dbh = Planworld::_connect(); + try{ + $query = $dbh->prepare('SELECT name, isinbound, MAX(SENDDATE) AS senddate, seen FROM + (SELECT U.Username AS name, "TRUE" AS isinbound, MAX(S.SENT) AS senddate, seen FROM SEND S, USERS U + WHERE U.ID=S.UID AND s.to_uid=:uid GROUP BY S.UID + UNION + SELECT U.Username AS name, "FALSE" AS isinbound, MAX(S.SENT) AS senddate, seen FROM SEND S, USERS U + WHERE U.ID=S.TO_UID AND s.uid=:uid GROUP BY S.TO_UID) SUB + GROUP BY NAME ORDER BY name'); + $queryArray = array('uid' => $uid); + $query->execute($queryArray); + $result = $query->fetchAll(); + if (!$result){ + return PLANWORLD_ERROR; + } + else { + return $result; + } + } + catch(PDOException $badquery){ + return PLANWORLD_ERROR; + } + } + return PLANWORLD_ERROR; + } + + +} +?> diff --git a/code/lib/User.php b/code/lib/User.php new file mode 100755 index 0000000..a5bd7b2 --- /dev/null +++ b/code/lib/User.php @@ -0,0 +1,994 @@ +dbh = Planworld::_connect(); + $this->type = 'local'; + if (is_string($uid)) { + $this->username = $uid; + } + else if (is_int($uid)) { + $this->userID = (int)$uid; + } + /* check if this user exists */ + if (isset($this->userID) || $this->isUser()) { + $this->load(); + } + } + + + + /* The original version of this function returns false rather than Planworld error, so we continue that habit. + Obviously the function loads user information. + */ + function load(){ + $continue = false; + $query = ''; /* Placed here to define. Handled appropriately if continues null. */ + $queryArray = array(); + try{ + if(!$this->dbh){ + throw new PDOException('Database connection not initialized.'); + } + if (isset($this->username)){ + $query = $this->dbh->prepare('SELECT * FROM users WHERE username= :username'); + $queryArray = array('username' => addslashes($this->username)); + $continue = true; + } + else if (isset($this->userID)){ + $query = $this->dbh->prepare('SELECT * FROM users WHERE id= :uid'); + $queryArray = array('uid' => $this->userID); + $continue = true; + } + else{ + $continue = false; + } + } + catch(PDOException $badquery){ + $continue = false; + } + if($continue){ + try{ + $query->execute($queryArray); + $result = $query->fetch(); + if (!$result){ + return false; + } + else{ + $this->userID = (int) $result['id']; + $this->username = $result['username']; + $this->remoteUser = ($result['remote'] == 'Y') ? true : false; + $this->world = ($result['world'] == 'Y') ? true : false; + $this->snitch = ($result['snitch'] == 'Y') ? true : false; + $this->archive = $result['archive']; + $this->snitchDisplayNum = $result['snitch_views']; + $this->views = $result['views']; + $this->watchOrder = $result['watch_order']; + $this->theme = $result['theme_id']; + $this->snitchEnabled = $result['snitch_activated']; + $this->lastLogin = $result['last_login']; + $this->lastUpdate = $result['last_update']; + if ($tz = $this->getPreference('timezone')) { + $this->timezone = $tz; + } + $this->changed = false; + return true; + } + } + catch(PDOException $badquery){ + return false; + } + } + else{ + return false; + } + return false; + + } + + + + + + /** + * Create this user + */ + function create () { + /* create the user */ + if (isset($this->username)){ + $this->userID = Planworld::addUser($this->username); + } + + /* set the valid flag */ + $this->valid = true; + } + + + /** + * Fetches a preference for this user. + */ + function getPreference ($name) { + if (isset($this->prefs[$name])){ + return $this->prefs[$name]; + } + else if((!empty(trim($name))) && (in_array($name, Planworld::getArrayOfAllowedPreferences()))){ + try{ + if(!$this->dbh){ + throw new PDOException('Database connection not initialized.'); + } + $query = $this->dbh->prepare('SELECT value FROM preferences WHERE uid= :uid AND name= :name'); + $queryArray = array('uid' => $this->userID, 'name' => $name); + $query->execute($queryArray); + $result = $query->fetch(); + if(isset($result['value'])){ + if((strcasecmp($result['value'], 'true')) == 0){ + $this->prefs[$name] = true; + return true; + } + else if((strcasecmp($result['value'], 'false')) == 0){ + $this->prefs[$name] = false; + return false; + } + else{ + $this->prefs[$name] = $result['value']; + return $result['value']; + } + } + else{ + $this->prefs[$name] = false; + return false; + } + } + catch(PDOException $badquery){ + $this->prefs[$name] = false; + return false; + } + } + else{ + return false; + } + return false; + } + + + + /** + * Sets a preference for this user. + * @public + * @returns string + */ + function setPreference ($name, $val) { + $success = false; + if(!(in_array($name, Planworld::getArrayOfAllowedPreferences()))){ + $success = false; + } + else{ + $this->clearPreference ($name); /* Don't check for true return because the preference may start out blank. */ + /* A huge flaw in this method is that preference calues are not checked for validity. + But then, right now the journal divider preference is sort of impossible to check and awful. + We might consider breaking it out into a separate table along with shared. */ + + + } + + + $query = "DELETE FROM preferences WHERE uid=" . $this->userID . " AND name='" . $name . "'"; + $this->dbh->query($query); + + /* don't add false preferences (lack is assumed to be false) */ + if (!$val || strtolower($val) != 'false' || $val != '') { + $query = "INSERT INTO preferences (uid, name, value) VALUES (" . $this->userID . ", '{$name}', '{$val}')"; + $this->dbh->query($query); + } + + $this->prefs[$name] = $val; + } + + + + /** + * Clears a preference for this user. + */ + function clearPreference ($name) { + $success = false; + if(!(in_array($name, Planworld::getArrayOfAllowedPreferences()))){ + $success = false; + } + else{ + try{ + if(!$this->dbh){ + throw new PDOException('Database connection not initialized.'); + } + $query = $this->dbh->prepare('DELETE FROM preferences WHERE uid= :uid AND name LIKE(:prefname)'); + $queryArray = array('uid' => $this->userID, 'prefname' => $name); + $success = $query->execute($queryArray); + /* We do this after the query but within the block so that if it blows up the catch will still give us a false return. + The point is that if we get a false, we should re-investigate because preference state is likely unpredictable. */ + unset($this->prefs[$name]); + } + catch(PDOException $badquery){ + $success = false; + } + } + return $success; + } + + + + + /** + * Saves user information. + * @public + * @returns bool Whether any data was actually saved + */ + // STILL REQUIRES CLEANUP, DOCUMENTATION, ONE NOTED CHANGE, AND A CHECK OF ALL IF AND CONDITIONALS. + function save () { + $boolSaveResult = false; + $errorCount = 0; + if ($this->changed) { + /* column <-> variable mapping */ + $info = array(); + $info['username'] = strval($this->username); + $info['remote'] = (strval($this->remoteUser)) ? 'Y' : 'N'; + $info['world'] = (strval($this->world)) ? 'Y' : 'N'; + $info['snitch'] = (strval($this->snitch)) ? 'Y' : 'N'; + $info['snitch_views'] = $this->snitchDisplayNum; + $info['archive'] = strval($this->archive); + $info['watch_order'] = strval($this->watchOrder); + $info['theme_id'] = $this->theme; + $info['snitch_activated'] = $this->snitchEnabled; + $info['last_login'] = $this->lastLogin; + $info['last_update'] = $this->lastUpdate; + $info['last_ip'] = strval($this->last_ip); + $whereKey = ''; + $whereValue = ''; + $boolContinue = false; + if(isset($this->username)){ + $whereKey = 'username'; + $whereValue = $this->username; + $boolContinue = true; + } + else if(isset($this->userID)){ + $whereKey = 'id'; + $whereValue = $this->userID; + $boolContinue = true; + } + else{ + $boolContinue = false; + } + if($boolContinue){ + /* Run an update for each attribue on this user, but only if the attribute value is not empty. */ + foreach($info as $key => $value){ + if(!empty($value)){ + try{ + if(!$this->dbh){ + throw new PDOException('Database connection not initialized.'); + } + $query = $this->dbh->prepare('UPDATE users SET :key = :value WHERE :wherekey = :wherevalue'); + $queryArray = array('wherekey' => $whereKey, 'wherevalue' => $whereValue, 'key' => $key, 'value' => $value); + $query->execute($queryArray); + } + catch(PDOException $badquery){ + $errorCount++; + } + } + else{ + $errorCount++; + } + } + /* This can be adjusted depending upon one's tolerance for errors. */ + if($errorCount == 0){ + $boolSaveResult = true; + $this->changed = false; + } + else if(($errorCount < 12) && ($errorCount > 0)){ + $boolSaveResult = true; + $this->changed = true; + } + else{ + $boolSaveResult = false; + $this->changed = true; + } + } + else{ + $boolSaveResult = false; + } + } + else{ + /* No change, but we were successful anyway. */ + $boolSaveResult = true; + } + /* save any changed planwatch data if need be */ + if (isset($this->planwatch)) { + $this->planwatch->save(); + } + return $boolSaveResult; + } + + + /** + * Loads a Planwatch object containing this users planwatch. + * @public + */ + function loadPlanwatch () { + if (!isset($this->planwatch)) { + // FIX AND ADD WHEN PLANWATCH IMPLEMENTED + //$this->planwatch = new Planwatch($this); + return false; + } + else{ + return PLANWORLD_ERROR; + } + } + + + /** + * Set snitch registration status. + * @param val True / false registration + * @public + * @returns void + */ + function setSnitch ($val) { + $this->changed = true; + if (!$val && $this->snitch) { + $this->clearSnitch(); + } + else if ($val && $val != $this->snitch) { + $this->startSnitch(); + } + else { + $this->changed = false; + } + $this->snitch = $val; + } + + + /** + * Clear this user's snitch list. + * @private + * @returns boolean + */ + function clearSnitch () { + try{ + if(!$this->dbh){ + throw new PDOException('Database connection not initialized.'); + } + $this->snitch = false; + $this->snitchEnabled = 0; + $this->changed = true; + $query = $this->dbh->prepare('DELETE FROM snitch WHERE uid= :uid'); + $queryArray = array('uid' => $this->userID); + return $query->execute($queryArray); + } + catch(PDOException $badquery){ + return false; + } + } + + + /** + * Resets this user's snitch views. + * @public + * @returns boolean + */ + function resetSnitchViews () { + try{ + if(!$this->dbh){ + throw new PDOException('Database connection not initialized.'); + } + $query = $this->dbh->prepare('UPDATE snitch SET views=0 WHERE uid=:uid'); + $queryArray = array('uid' => $this->userID); + return $query->execute($queryArray); + } + catch(PDOException $badquery){ + return false; + } + + } + + /** + * Is this user snitch registered? + * @public + * @returns bool + */ + function getSnitch () { + return $this->snitch; + } + + + /** + * Returns the number of snitch views that this user has had. + * @public + */ + function getNumSnitchViews () { + + try{ + if(!$this->dbh){ + throw new PDOException('Database connection not initialized.'); + } + $query = $this->dbh->prepare('SELECT COUNT(*) as count FROM snitch WHERE uid=:uid'); + $queryArray = array('uid' => $this->userID); + $query->execute($queryArray); + $result = $query->fetch(); + return $result['count']; + } + catch(PDOException $badquery){ + return PLANWORLD_ERROR; + } + } + + + + + /* The previous version of this function allowed ordering. + We will assume in the future that the client will handle ordering. */ + function getSnitchViews () { + $arrayDicedSnitchViews = array(); + try{ + if(!$this->dbh){ + throw new PDOException('Database connection not initialized.'); + } + $query = $this->dbh->prepare('SELECT snitch.s_uid, snitch.last_view, snitch.views, u2.username as s_name, u2.last_update FROM users, snitch LEFT JOIN users as u2 ON snitch.s_uid=u2.id WHERE snitch.uid=:uid AND users.id=:uid AND snitch.last_view > users.snitch_activated ORDER BY u2.last_update'); + $queryArray = array('uid' => $this->userID); + $query->execute($queryArray); + $result = $query->fetchall(); + $this->loadPlanwatch(); + foreach ($result as $row){ + $next = array( + 'name' => $row['s_name'], + 'date' => Planworld::getRFC3339DateFromTimeStamp($row['last_view']), + 'views' => $row['views'], + 'lastupdate' => Planworld::getRFC3339DateFromTimeStamp($row['last_update']), + //'inplanwatch' => (bool) $this->planwatch->inPlanwatch($row['s_name']) + 'inplanwatch' => false // DUMMY CODE FIX WHEN PLANWATCH IMPLEMENTED + ); + $arrayDicedSnitchViews[] = $next; + } + return $arrayDicedSnitchViews; + + } + catch(PDOException $badquery){ + return PLANWORLD_ERROR; + } + return PLANWORLD_ERROR; + } + + + /** + * Increment the number of plan views for this user. + * @public + * @returns int + */ + function addView () { + $this->views++; + try{ + if(!$this->dbh){ + throw new PDOException('Database connection not initialized.'); + } + $query = $this->dbh->prepare('UPDATE users SET views= :views WHERE id= :uid'); + $queryArray = array('views' => $this->views,'uid' => $this->userID); + $query->execute($queryArray); + } + catch(PDOException $badquery){ + return PLANWORLD_ERROR; /* We know if this returns a negative number that something went awry. */ + } + return $this->views; + } + + + + /** + * Add a view by $user to this user's snitch list. + * @param user Viewing user. + * @returns true (1) on success and -1 (Planworld error) on failure + */ + function addSnitchView (&$user) { + + /* First add an entry to the Snitch Tracker */ + $intCurrentViews = 0; + $intSnitchRows = -1; + $suidForAddSnitchView = $user->getUserID(); + if($suidForAddSnitchView){ + /* Check whether a snitch relationship already exists between these One users. This will determine the next query. */ + try{ + if(!$this->dbh){ + throw new PDOException('Database connection not initialized.'); + } + $queryOne = $this->dbh->prepare('SELECT views from snitch where uid=:uid AND s_uid=:s_uid'); + $queryOneArray = array('uid' => $this->userID, 's_uid' => $suidForAddSnitchView); + $queryOne->execute($queryOneArray); + $resultOne = $queryOne->fetchall(); + if(is_numeric(count($resultOne))){ + $intNewViewCount = 1; + $intSnitchRows = count($resultOne); + if($intSnitchRows == 1){ + $intNewViewCount = $resultOne[0]['views'] + 1; + $queryTwo = $this->dbh->prepare('UPDATE snitch SET last_view=:last_view, views=:views WHERE uid=:uid AND s_uid=:s_uid'); + $queryTwoArray = array('uid' => $this->userID, 's_uid' => $suidForAddSnitchView, 'last_view' => time(), 'views' => $intNewViewCount); + return $queryTwo->execute($queryTwoArray); + } + else if ($intSnitchRows == 0){ + $queryThree = $this->dbh->prepare('INSERT INTO snitch (uid, s_uid, last_view, views) VALUES (:uid,:s_uid,:last_view,:views)'); + $queryThreeArray = array('uid' => $this->userID, 's_uid' => $suidForAddSnitchView, 'last_view' => time(), 'views' => $intNewViewCount); + return $queryThree->execute($queryThreeArray); + } + else{ + return PLANWORLD_ERROR; + } + } + else{ + return PLANWORLD_ERROR; + } + } + catch(PDOException $badquery){ + return PLANWORLD_ERROR; + } + } + else { + return PLANWORLD_ERROR; + } + } + + + + + /** + * Starts this user's snitch list. + * @private + * @returns void + */ + function startSnitch () { + $this->snitch = true; + $this->snitchEnabled = time(); + $this->changed = true; + } + + + + + /** + * void setLastLogin ($ts) + * update's users last login time to $ts (timestamp) + */ + function setLastLogin ($ts) { + $this->changed = true; + $this->lastLogin = $ts; + } + + /** + * void setLastUpdate ($ts) + * update's users last update time to $ts (timestamp) + */ + function setLastUpdate ($ts) { + $this->changed = true; + $this->lastUpdate = $ts; + } + + /** + * void setLastIP ($ip) + * update user's last known ip address + */ + function setLastIP ($ip) { + $this->changed = true; + $this->last_ip = $ip; + } + + /** + * Get archive settings. + * @public + * @returns string + */ + function getArchive () { + return $this->archive; + } + + /** + * Set this user's archival settings + * @param val (Y) Public / (P) private / (N) off archiving + * @public + * @returns bool + */ + function setArchive ($val) { + $val = strtoupper($val); + if ($val == 'Y' || $val == 'P' || $val == 'N') { + $this->archive = $val; + $this->changed = true; + return true; + } + else { + return false; + } + } + + /** + * Return the last time this user logged in. + * @public + * @returns int + */ + function getLastLogin () { + return $this->lastLogin; + } + + /** + * Return the last time this user updated his/her plan. + * @public + * @returns int + */ + function getLastUpdate () { + return $this->lastUpdate; + } + + + /* FLAG FUNCTIONS */ + + /** + * Is this user an admin? + * @public + * @returns bool + */ + function isAdmin () { + if (!isset($this->admin)) { + $this->admin = $this->getPreference('admin'); + } + return $this->admin; + } + + + /** + * Is this user's plan archived? + * @public + * @returns bool + */ + function isArchived () { + return ($this->archive == 'Y' || $this->archive == 'P') ? true : false; + } + + + /** + * Is this user's plan archived publicly? + * @public + * @returns bool + */ + function isArchivedPublicly () { + return ($this->archive == 'Y') ? true : false; + } + + + /** + * Has this user been changed? + * @public + * @returns bool + */ + function isChanged () { + return $this->changed; + } + + /** + * Is this user remote? + * @public + * @returns bool + */ + function isRemoteUser () { + return $this->remoteUser; + } + + /** + * Is this a shared plan? + * @public + * @returns bool + */ + function isShared () { + return $this->getPreference('shared') && $this->shared; + } + + /** + * Mark this as an actively shared plan (not the logged-in user). + */ + function setShared () { + $this->shared = true; + } + + /** + * Set the user who is editing this shared plan. + */ + function setEditingUser ($user) { + $this->editor = $user; + } + + /** + * Is this shared for $uid to edit? + */ + function isSharedFor (&$uid) { + if (is_object($uid)) { + $username = $uid->getUsername(); + } else if (is_string($uid)) { + $username = $uid; + } else { + return false; + } + return $this->getPreference('shared') && $this->getPreference('shared_' . $username); + } + + + /** + * Return a list of the users for whom this plan is shared. + In previous versions this returned a list with newlines. Now it returns an array. + It is not called by any library functions -- previously it was called from interface. + */ + function getSharedUsers () { + try{ + if(!$this->dbh){ + throw new PDOException('Database connection not initialized.'); + } + $query = $this->dbh->prepare('SELECT name FROM preferences WHERE name LIKE "shared_%" AND uid= :uid'); + $queryArray = array('uid' => $this->userID); + $query->execute($queryArray); + $result = $query->fetchAll(); + if (!$result){ + return PLANWORLD_ERROR; + } + else{ + $return = array(); + foreach($result as $row){ + $return[] = $row['name']; + } + return $return; + } + } + catch(PDOException $badquery){ + return PLANWORLD_ERROR; + } + return PLANWORLD_ERROR; + } + + + + /** + * Return a list of users whose plans this user is allowed to edit. + */ + + function getPermittedPlans () { + try{ + if(!$this->dbh){ + throw new PDOException('Database connection not initialized.'); + } + $query = $this->dbh->prepare("SELECT uid, username FROM preferences as p, users as u WHERE p.name= :sharedname AND u.id=p.uid"); + $queryArray = array('sharedname' => 'shared_' . $this->username); + $query->execute($queryArray); + $result = $query->fetchAll(); + if (!$result){ + return false; + } + else{ + $return = array(); + foreach($result as $row){ + $return[] = $row['username']; + } + return $return; + } + } + catch(PDOException $badquery){ + return false; + } + return false; + } + + + + + + + /** + * Does this user exist? + * @public + * @returns bool + */ + function isUser () { + if (!isset($this->valid)) { + $planworldJustForIsUser = new Planworld(); + $this->valid = $planworldJustForIsUser->isUser($this->username, true); + } + return $this->valid; + } + + + /** + * Is this user's plan world-accessible? + * @public + * @returns bool + */ + function isWorld () { + return $this->getWorld(); + } + + + /** + * Is this user new? (NPD: for welcome pages) + * @public + * @returns bool + */ + function isNew() { + return !$this->lastUpdate; + } + + /** + * Get number of users to display on snitch list. + * @public + * @returns int + */ + function getSnitchDisplayNum () { + return $this->snitchDisplayNum; + } + + /** + * Set number of users to display on snitch list. + * @param num Number of users to display + * @public + * @returns void + */ + function setSnitchDisplayNum ($num) { + $this->snitchDisplayNum = $num; + $this->changed = true; + } + + + /** + * Get this user's preferred timezone. + * @public + * @returns string + */ + function getTimezone () { + return $this->timezone; + } + + /** + * Set the local timezone for this user. + * @param theme Timezone to use + * @public + * @returns void + */ + function setTimezone ($timezone) { + $this->setPreference('timezone', $timezone); + $this->timezone = $timezone; + } + + /** + * Get this user's type + * @public + * @returns string + */ + function gettype() { + return $this->type; + } + + /** + * Returns this user's userid. + * @public + * @returns int + */ + function getUserID () { + return $this->userID; + } + + /** + * Returns this user's username. + * @public + * @returns string + */ + function getUsername () { + return addslashes($this->username); + } + + /** + * Returns this user's planwatch ordering. + * @public + * @returns string + */ + function getWatchOrder () { + return $this->watchOrder; + } + + /** + * Set this user's planwatch ordering. + * @param type Type of ordering + * @public + * @returns void + */ + function setWatchOrder ($type) { + $this->watchOrder = $type; + $this->changed = true; + } + + /** + * Is this user's plan world-accessible? + * @public + * @returns bool + */ + function getWorld () { + return $this->world; + } + + /** + * Set this user's world-accessibility + * @param val True / false world-accessibility + * @public + * @returns void + */ + function setWorld ($val) { + $this->world = $val; + $this->changed = true; + } + + + + /* END CLASS */ +} +?> \ No newline at end of file diff --git a/code/scaffolding/adduser.php b/code/scaffolding/adduser.php new file mode 100644 index 0000000..158cb78 --- /dev/null +++ b/code/scaffolding/adduser.php @@ -0,0 +1,39 @@ +create(); + $userExists = new User($username); + $userId = $userExists->userID; + $pass = crypt($username, ((int)$userId + 45678)); + $message = 'User ' . $username . ' created with password ' . $pass; + } + } + + PrintHeader('fullpage.css', 'Test Planworld User Creation'); + PrintUsernameForm('adduser.php', 'Input a username to add that user to Planworld', $message); + + + + + PrintFooter(); + +?> \ No newline at end of file diff --git a/code/scaffolding/ajaxFunctions.js b/code/scaffolding/ajaxFunctions.js new file mode 100755 index 0000000..4b75a92 --- /dev/null +++ b/code/scaffolding/ajaxFunctions.js @@ -0,0 +1,50 @@ +var HTTP; +if (HTTP && (typeof HTTP != "object" || HTTP.NAME)) + throw new Error("Namespace 'HTTP' already exists"); + +// Create our namespace, and specify some meta-information +HTTP = {}; +HTTP.NAME = "HTTP"; // The name of this namespace +HTTP.VERSION = 1.0; // The version of this namespace + + +// This is a list of XMLHttpRequest creation factory functions to try +HTTP._factories = [ + function() { return new XMLHttpRequest(); }, + function() { return new ActiveXObject("Msxml2.XMLHTTP"); }, + function() { return new ActiveXObject("Microsoft.XMLHTTP"); } +]; + +// When we find a factory that works, store it here +HTTP._factory = null; + +// Create and return a new XMLHttpRequest object. +// +// The first time we're called, try the list of factory functions until +// we find one that returns a nonnull value and does not throw an +// exception. Once we find a working factory, remember it for later use. +// +HTTP.newRequest = function() { + if (HTTP._factory != null) return HTTP._factory(); + + for(var i = 0; i < HTTP._factories.length; i++) { + try { + var factory = HTTP._factories[i]; + var request = factory(); + if (request != null) { + HTTP._factory = factory; + return request; + } + } + catch(e) { + continue; + } + } + + // If we get here, none of the factory candidates succeeded, + // so throw an exception now and for all future calls. + HTTP._factory = function() { + throw new Error("XMLHttpRequest not supported"); + } + HTTP._factory(); // Throw an error +} \ No newline at end of file diff --git a/code/scaffolding/alpha.css b/code/scaffolding/alpha.css new file mode 100644 index 0000000..ab61a9f --- /dev/null +++ b/code/scaffolding/alpha.css @@ -0,0 +1,37 @@ +h3 { + color: #ede9af; + font-weight: bold; + font-size: 18px; +} + + +body { + font-size: 14px; + font-family: Georgia, Garamond, "Times New Roman", sans-serif; + background-color: #50529f; + color: #9fcfe9; +} + +*:link { + text-decoration: none; + color:inherit; +} + +div#sendRequestButton { + text-shadow: 2px 2px 24px #892431; + border-width: 1px; + font-size: 18px; + color: #ff4f74; +} + +div#planworldResult { + border-color: #9fcfe9; + border-style: ridge; + border-width: 3px; + font-size: 12px; + background-color: #aadfff; + color: #323366; + width: 60%; +} + + diff --git a/code/scaffolding/create_token.php b/code/scaffolding/create_token.php new file mode 100644 index 0000000..768e28b --- /dev/null +++ b/code/scaffolding/create_token.php @@ -0,0 +1,39 @@ +userID; + $comparepass = crypt($username, ((int)$userId + 45678)); + if(strcmp($password, $comparepass) == 0){ + $nodeToken = new NodeToken (); + if($nodeToken->createToken($username, 'testtokencreator')){ + $message = 'User token is ' . $nodeToken->tokenNumber; + } + } + else{ + $message = 'Bad username or password.'; + } + } + } + + PrintHeader('fullpage.css', 'Test Planworld Token Handling'); + PrintLoginForm('create_token.php', 'Enter username and password to create a Planworld token.', $message); + PrintFooter(); + +?> \ No newline at end of file diff --git a/code/scaffolding/debug_client.html b/code/scaffolding/debug_client.html new file mode 100644 index 0000000..0bd7783 --- /dev/null +++ b/code/scaffolding/debug_client.html @@ -0,0 +1,227 @@ + + + + + Planworld API Debug Client + + + + + + + +

Planworld API Debug Client

+
+ + + + + + + + + + + + + + + + + + +
Planworld Node URL
API Version
Category
Planworld Noun
Argument 1
Argument 2
Argument 3
Argument 4
Token
Use Token? Yes + No
HTTP Method GET + POST
Output Format XML + JSON + TEXT
+

Post:

+ +
+

Request Output Unformatted:

+
+

API Result will appear here in unformatted form.

+
+

Request Output Decoded and Formatted:

+
+

API Result will appear here outside of its format wrapper.

+
+

Raw URL:

+
+

RESTful URL sent to node will be shown here.

+
+ + + diff --git a/code/scaffolding/fullpage.css b/code/scaffolding/fullpage.css new file mode 100644 index 0000000..9ab2b5a --- /dev/null +++ b/code/scaffolding/fullpage.css @@ -0,0 +1,136 @@ +body { + font-size: 14px; + font-family: Verdana, "Bitstream Vera Sans", sans-serif; + background-color: #acb0e6; + color: #000000; +} + +#work { + background-color: #7d86a2; + margin: 20px; + padding:10px; + word-wrap: break-word; +} + +h3 { + font-size: 24px; + text-align: center; + color: #404659; +} + +form +{ + font-size: 14px; + background-color: #7d86a2; + width: 280px; + border-radius: 5px; + margin: 0 auto; + text-align: center; +} + +fieldset +{ + width: 100px; + border: none; + margin: -10px -5px -10px -5px ; +} + +button +{ + background-color: #d8daf3; + color: black; + text-align:center; + border-radius: 4px; +} + +input { + border-radius: 0px; +} + +legend { + background: #54ac96; + color: #ffffff; +} + +fieldset { + text-align: left; +} + + +.smallform +{ + width: 60px; +} + +.smallform button{ + margin: 0 auto; + width: 60px; +} + +.notice +{ + color: #f9e936; + text-align: center; +} + +.fatal +{ + color: #ce2c2c +} + +label { +} + +a:link{ + color: #54ac96; +} + +a:visited{ + color: #98a0a0; +} + +a:active{ + color: #98a0a0; +} + +a:hover{ + color: #98a0a0; +} + +pre { + white-space: pre-wrap; /* css-3 */ + white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ + white-space: -pre-wrap; /* Opera 4-6 */ + white-space: -o-pre-wrap; /* Opera 7 */ + word-wrap: break-word; /* Internet Explorer 5.5+ */ +} + +table { + table-layout: auto; + width:1200px; + border-collapse:collapse; + font-size: 11px; + border: 1px solid rgba(84,172,150,0.76); +} + +td { + vertical-align:top; + word-wrap: break-word; + border: 1px solid rgba(84,172,150,0.76); + max-width: 420px; +} + +th { + vertical-align:middle; + word-wrap: break-word; + border: 2px solid rgba(84,172,150,0.76); + background: rgba(84,172,150,0.47); +} + +tr:nth-child(even) { + background: rgba(84,172,150,0.31); +} + +tr:nth-child(odd) { +background: rgba(84,172,150,0.2); +} \ No newline at end of file diff --git a/code/scaffolding/getusers.php b/code/scaffolding/getusers.php new file mode 100644 index 0000000..04c036a --- /dev/null +++ b/code/scaffolding/getusers.php @@ -0,0 +1,41 @@ +userID; + $comparepass = crypt($username, ((int)$userId + 45678)); + if(strcmp($password, $comparepass) == 0){ + $pw = new Planworld(); + $arrayUsers = $pw->getAllUsers(); + } + else{ + $message = 'Bad username or password.'; + } + } + } + + PrintHeader('fullpage.css', 'Test Planworld User Listing'); + PrintLoginForm('getusers.php', 'Enter username and password to print a list of all users on this system.', $message); + foreach($arrayUsers as $arrayUser){ + echo '
' . $arrayUser; + } + + PrintFooter(); + +?> \ No newline at end of file diff --git a/code/scaffolding/ttpdisplay_tools.php b/code/scaffolding/ttpdisplay_tools.php new file mode 100644 index 0000000..53e43cd --- /dev/null +++ b/code/scaffolding/ttpdisplay_tools.php @@ -0,0 +1,129 @@ +' . CR); + print('' . CR); + print('' . CR); + print(' ' . CR); + print(' ' . CR); + print(' ' . $pageTitle . '' . CR); + print(' ' . CR); + print(' ' . CR); + print(' 
' . CR); + print('

' . $pageTitle . '

' . CR); + print(' 
' . CR); +} + + +function PrintFooter(){ + print('' . CR); + print(' ' . CR); + print('' . CR); +} + + +function PrintLoginForm($loginTarget='index.php', $loginMessage='', $loginResult=''){ + print('
' . CR); + if(!empty($loginMessage)){ + print('
' . SanitizeMessages($loginMessage) . '
 
' . CR); + } + print('
' . CR); + print(' ' . CR); + print(' ' . CR); + print('
' . CR); + print('
' . CR); + print(' ' . CR); + print(' ' . CR); + print('
' . CR); + print('
' . CR); + print(' ' . CR); + print('
' . CR); + print('
' . $loginResult . '
 
' . CR); + print('
' . CR); +} + + + +function PrintUsernameForm($loginTarget='index.php', $loginMessage='', $loginResult=''){ + print('
' . CR); + if(!empty($loginMessage)){ + print('
' . SanitizeMessages($loginMessage) . '
 
' . CR); + } + print('
' . CR); + print(' ' . CR); + print(' ' . CR); + print('
' . CR); + print('
' . CR); + print(' ' . CR); + print('
' . CR); + print('
' . $loginResult . '
 
' . CR); + print('
' . CR); +} + + +function PrintLogoutForm($logoutTarget='index.php', $logoutMessage=''){ + print('
' . CR); + if(!(empty($logoutMessage))){ + print('
 
' . SanitizeMessages($logoutMessage) . '
' . CR); + } + print(' ' . CR); + print(' ' . CR); + print('
' . CR); +} + +/* A function to make messages better for display. Strips HTML and adds a break whenever a sentence ends. */ +function SanitizeMessages($unsanitizedMessage=''){ + return $sanitizedMessage = str_replace('. ','.
',strip_tags($unsanitizedMessage)); +} + + + +/* Given an arbitrary array of arrays, a keylist to the array on each line (i.e. the arrays in the array) and a title, print a standard HTML table. */ +function PrintComplexTable($threeDArray, $keyArray, $title){ + echo '' . CR . '' . CR; + /* Print Header Row */ + echo ''; + foreach($keyArray as $keyString){ + echo ''; + } + echo '' . CR; + foreach($threeDArray as $twoDArray){ + echo ''; + foreach($keyArray as $keyString){ + echo ''; + } + echo ''; + } + echo '
' . $title . '
' . $keyString . '
' . $twoDArray[$keyString] . '
' . CR; +} + + + +function PrintSimpleUserTable($printUserIdArray, $lookupTable, $title){ + echo '' . CR . '' . CR; + echo '' . CR; + $rowCount = 1; + foreach($printUserIdArray as $printUserId){ + echo ''; + echo ''; + echo '' . CR; + $rowCount++; + } + echo '
' . $title . '
RowUseridUsername
' . $rowCount . '' . $printUserId . ''; + if(array_key_exists((string)$printUserId, $lookupTable)){ + echo $lookupTable[(string)$printUserId]; + } + echo '
' . CR; +} + + +?> \ No newline at end of file diff --git a/description b/description new file mode 100644 index 0000000..3daf02b --- /dev/null +++ b/description @@ -0,0 +1 @@ +Public Git Repository for Work on NOTE implemenation of Planworld. diff --git a/setup/planworld_mysql_db_create.sql b/setup/planworld_mysql_db_create.sql new file mode 100644 index 0000000..de5577c --- /dev/null +++ b/setup/planworld_mysql_db_create.sql @@ -0,0 +1,579 @@ +/* This currently creates Planworld for mysql. + It does not result in a database with the same options as the Amherst Planworld. + Most of the tables and rows are created, but data types are simplified, + only indexes also supported by postgres are created, and storage type is not specified. + However, this should have no effect on the programmatic use of the database + (i.e. code that works against this database will also work against Amherst production and a postgres version) */ + +/* May need to add node definitions and cookies. */ + +USE mysql; +CREATE USER 'pwmysqluser'@'localhost' IDENTIFIED BY 'planworld'; +FLUSH PRIVILEGES; +CREATE DATABASE PLANWORLD_PUBLIC_MYSQL; +FLUSH PRIVILEGES; +USE PLANWORLD_PUBLIC_MYSQL; + +CREATE TABLE IF NOT EXISTS archive ( + uid bigint NOT NULL DEFAULT 0, + posted int NOT NULL DEFAULT 0, + name nchar(64) NOT NULL DEFAULT '', + pub nchar(1) NOT NULL DEFAULT 'N', + views int NOT NULL DEFAULT 0, + content text NOT NULL, + PRIMARY KEY (uid,posted) +); + +CREATE TABLE IF NOT EXISTS cookies ( + id int NOT NULL DEFAULT 0, + quote text NOT NULL, + author nchar(255) NOT NULL DEFAULT '', + s_uid bigint NOT NULL DEFAULT 0, + approved nchar(1) NOT NULL DEFAULT 'N', + PRIMARY KEY (id) +); + +CREATE TABLE IF NOT EXISTS cookies_seq ( + id int NOT NULL, + PRIMARY KEY (id) +); + +CREATE TABLE IF NOT EXISTS drafts ( + uid bigint NOT NULL DEFAULT 0, + date_saved timestamp NOT NULL, + content text NOT NULL, + PRIMARY KEY (uid,date_saved) +); + +CREATE TABLE IF NOT EXISTS globalstats ( + totalhits bigint NOT NULL DEFAULT 0 +); + +CREATE TABLE IF NOT EXISTS groupid_seq ( + id int NOT NULL, + PRIMARY KEY (id) +); + + +CREATE TABLE IF NOT EXISTS message ( + uid bigint NOT NULL DEFAULT 0, + to_uid bigint NOT NULL DEFAULT 0, + last_update int NOT NULL DEFAULT 0, + message text NOT NULL, + seen smallint NOT NULL DEFAULT 0, + PRIMARY KEY (uid,to_uid) +); + +CREATE TABLE IF NOT EXISTS news ( + id int UNIQUE NOT NULL DEFAULT 0, + news text NOT NULL, + date int NOT NULL DEFAULT 0, + live nchar(1) NOT NULL DEFAULT 'Z', + PRIMARY KEY (id) +); + +CREATE TABLE IF NOT EXISTS news_seq ( + id int NOT NULL, + PRIMARY KEY (id) +); + +CREATE TABLE IF NOT EXISTS nodes ( + name nchar(64) NOT NULL DEFAULT '', + hostname nchar(128) NOT NULL DEFAULT '', + path nchar(128) NOT NULL DEFAULT '', + port int NOT NULL DEFAULT 80, + version smallint NOT NULL DEFAULT 2, + PRIMARY KEY (name) +); + +CREATE TABLE IF NOT EXISTS online ( + uid bigint NOT NULL DEFAULT 0, + login int NOT NULL DEFAULT 0, + last_access int NOT NULL DEFAULT 0, + what nchar(64) NOT NULL DEFAULT '', + PRIMARY KEY (uid) +); + +CREATE TABLE IF NOT EXISTS plans ( + id int UNIQUE NOT NULL, + uid bigint NOT NULL DEFAULT 0, + content text NOT NULL, + PRIMARY KEY (uid) +); + +CREATE TABLE IF NOT EXISTS planwatch ( + uid bigint NOT NULL DEFAULT 0, + w_uid bigint NOT NULL DEFAULT 0, + gid bigint NOT NULL DEFAULT 1, + last_view int NOT NULL DEFAULT 0, + PRIMARY KEY (uid,w_uid) +); + +CREATE TABLE IF NOT EXISTS preferences ( + uid bigint NOT NULL DEFAULT 0, + name nchar(255) NOT NULL DEFAULT '', + value nchar(255) NOT NULL DEFAULT '', + PRIMARY KEY (uid,name) +); + +CREATE TABLE IF NOT EXISTS pw_groups ( + gid bigint NOT NULL DEFAULT 0, + uid bigint NOT NULL DEFAULT 0, + name nchar(64) NOT NULL DEFAULT '', + pos smallint NOT NULL DEFAULT 1, + PRIMARY KEY (gid,uid) +); + +CREATE TABLE IF NOT EXISTS security ( + ip nchar(15) NOT NULL, + errorcount int NOT NULL DEFAULT 0, + lasterror int NOT NULL DEFAULT 0, + PRIMARY KEY (ip) +); + +CREATE TABLE IF NOT EXISTS send ( + uid int NOT NULL DEFAULT 0, + to_uid int NOT NULL DEFAULT 0, + sent int NOT NULL DEFAULT 0, + seen int NOT NULL DEFAULT 0, + message text, + PRIMARY KEY (uid,to_uid,sent) +); + +CREATE TABLE IF NOT EXISTS snitch ( + uid bigint NOT NULL DEFAULT 0, + s_uid bigint NOT NULL DEFAULT 0, + last_view int NOT NULL DEFAULT 0, + views int NOT NULL DEFAULT 0, + PRIMARY KEY (uid,s_uid) +); + +CREATE TABLE IF NOT EXISTS snoop ( + uid bigint NOT NULL DEFAULT 0, + s_uid bigint NOT NULL DEFAULT 0, + referenced bigint NOT NULL DEFAULT 0, + PRIMARY KEY (uid,s_uid) +); + + +CREATE TABLE IF NOT EXISTS timezones ( + name nchar(128) NOT NULL DEFAULT '', + PRIMARY KEY (name) +); + +CREATE TABLE IF NOT EXISTS tokens ( + token nchar(64) NOT NULL, + uid bigint NOT NULL, + expire int NOT NULL, + clientname nchar(128) NOT NULL DEFAULT 'default' +); + +CREATE TABLE IF NOT EXISTS userid_seq ( + id int NOT NULL, + PRIMARY KEY (id) +); + +CREATE TABLE IF NOT EXISTS users ( + id int NOT NULL, + username nchar(128) UNIQUE DEFAULT NULL, + remote nchar(1) NOT NULL DEFAULT 'N', + world nchar(1) NOT NULL DEFAULT 'Y', + snitch nchar(1) NOT NULL DEFAULT 'N', + snitch_views smallint NOT NULL DEFAULT 0, + archive nchar(1) NOT NULL DEFAULT 'P', + archive_size int NOT NULL DEFAULT 0, + archive_size_pub int NOT NULL DEFAULT 0, + views int NOT NULL DEFAULT 0, + watch_order nchar(6) NOT NULL DEFAULT 'alph', + theme_id smallint NOT NULL DEFAULT 1, + snitch_activated int NOT NULL DEFAULT 0, + last_login int NOT NULL DEFAULT 0, + last_update int NOT NULL DEFAULT 0, + last_ip nchar(15) DEFAULT NULL, + first_login int DEFAULT NULL, + PRIMARY KEY (id) +); + + +/* Now populate initial values. */ + +insert into userid_seq (id) values (0); +insert into groupid_seq (id) values (0); +insert into news_seq (id) values (0); +insert into cookies_seq (id) values (0); + + + + +insert into timezones (name) values ('Europe/Andorra'); +insert into timezones (name) values ('Asia/Dubai'); +insert into timezones (name) values ('Asia/Kabul'); +insert into timezones (name) values ('America/Antigua'); +insert into timezones (name) values ('America/Anguilla'); +insert into timezones (name) values ('Europe/Tirane'); +insert into timezones (name) values ('Asia/Yerevan'); +insert into timezones (name) values ('America/Curacao'); +insert into timezones (name) values ('Africa/Luanda'); +insert into timezones (name) values ('Antarctica/McMurdo'); +insert into timezones (name) values ('Antarctica/South_Pole'); +insert into timezones (name) values ('Antarctica/Palmer'); +insert into timezones (name) values ('Antarctica/Mawson'); +insert into timezones (name) values ('Antarctica/Davis'); +insert into timezones (name) values ('Antarctica/Casey'); +insert into timezones (name) values ('Antarctica/Vostok'); +insert into timezones (name) values ('Antarctica/DumontDUrville'); +insert into timezones (name) values ('Antarctica/Syowa'); +insert into timezones (name) values ('America/Buenos_Aires'); +insert into timezones (name) values ('America/Rosario'); +insert into timezones (name) values ('America/Cordoba'); +insert into timezones (name) values ('America/Jujuy'); +insert into timezones (name) values ('America/Catamarca'); +insert into timezones (name) values ('America/Mendoz'); +insert into timezones (name) values ('Pacific/Pago_Pago'); +insert into timezones (name) values ('Europe/Vienna'); +insert into timezones (name) values ('Australia/Lord_Howe'); +insert into timezones (name) values ('Australia/Hobart'); +insert into timezones (name) values ('Australia/Melbourne'); +insert into timezones (name) values ('Australia/Sydney'); +insert into timezones (name) values ('Australia/Broken_Hill'); +insert into timezones (name) values ('Australia/Brisbane'); +insert into timezones (name) values ('Australia/Lindeman'); +insert into timezones (name) values ('Australia/Adelaide'); +insert into timezones (name) values ('Australia/Darwin'); +insert into timezones (name) values ('Australia/Perth'); +insert into timezones (name) values ('America/Aruba'); +insert into timezones (name) values ('Asia/Baku'); +insert into timezones (name) values ('Europe/Sarajevo'); +insert into timezones (name) values ('America/Barbados'); +insert into timezones (name) values ('Asia/Dhaka'); +insert into timezones (name) values ('Europe/Brussels'); +insert into timezones (name) values ('Africa/Ouagadougou'); +insert into timezones (name) values ('Europe/Sofia'); +insert into timezones (name) values ('Asia/Bahrain'); +insert into timezones (name) values ('Africa/Bujumbura'); +insert into timezones (name) values ('Africa/Porto-Novo'); +insert into timezones (name) values ('Atlantic/Bermuda'); +insert into timezones (name) values ('Asia/Brunei'); +insert into timezones (name) values ('America/La_Paz'); +insert into timezones (name) values ('America/Noronha'); +insert into timezones (name) values ('America/Belem'); +insert into timezones (name) values ('America/Fortaleza'); +insert into timezones (name) values ('America/Recife'); +insert into timezones (name) values ('America/Araguaina'); +insert into timezones (name) values ('America/Maceio'); +insert into timezones (name) values ('America/Sao_Paulo'); +insert into timezones (name) values ('America/Cuiaba'); +insert into timezones (name) values ('America/Porto_Velho'); +insert into timezones (name) values ('America/Boa_Vista'); +insert into timezones (name) values ('America/Manaus'); +insert into timezones (name) values ('America/Eirunepe'); +insert into timezones (name) values ('America/Rio_Branco'); +insert into timezones (name) values ('America/Nassau'); +insert into timezones (name) values ('Asia/Thimphu'); +insert into timezones (name) values ('Africa/Gaborone'); +insert into timezones (name) values ('Europe/Minsk'); +insert into timezones (name) values ('America/Belize'); +insert into timezones (name) values ('America/St_Johns'); +insert into timezones (name) values ('America/Halifax'); +insert into timezones (name) values ('America/Glace_Bay'); +insert into timezones (name) values ('America/Goose_Bay'); +insert into timezones (name) values ('America/Montreal'); +insert into timezones (name) values ('America/Nipigon'); +insert into timezones (name) values ('America/Thunder_Bay'); +insert into timezones (name) values ('America/Pangnirtung'); +insert into timezones (name) values ('America/Iqaluit'); +insert into timezones (name) values ('America/Rankin_Inlet'); +insert into timezones (name) values ('America/Winnipeg'); +insert into timezones (name) values ('America/Rainy_River'); +insert into timezones (name) values ('America/Cambridge_Bay'); +insert into timezones (name) values ('America/Regina'); +insert into timezones (name) values ('America/Swift_Current'); +insert into timezones (name) values ('America/Edmonton'); +insert into timezones (name) values ('America/Yellowknife'); +insert into timezones (name) values ('America/Inuvik'); +insert into timezones (name) values ('America/Dawson_Creek'); +insert into timezones (name) values ('America/Vancouver'); +insert into timezones (name) values ('America/Whitehorse'); +insert into timezones (name) values ('America/Dawson'); +insert into timezones (name) values ('Indian/Cocos'); +insert into timezones (name) values ('Africa/Kinshasa'); +insert into timezones (name) values ('Africa/Lubumbashi'); +insert into timezones (name) values ('Africa/Bangui'); +insert into timezones (name) values ('Africa/Brazzaville'); +insert into timezones (name) values ('Europe/Zurich'); +insert into timezones (name) values ('Africa/Abidjan'); +insert into timezones (name) values ('Pacific/Rarotonga'); +insert into timezones (name) values ('America/Santiago'); +insert into timezones (name) values ('Pacific/Easter'); +insert into timezones (name) values ('Africa/Douala'); +insert into timezones (name) values ('Asia/Harbin'); +insert into timezones (name) values ('Asia/Shanghai'); +insert into timezones (name) values ('Asia/Chungking'); +insert into timezones (name) values ('Asia/Urumqi'); +insert into timezones (name) values ('Asia/Kashgar'); +insert into timezones (name) values ('America/Bogota'); +insert into timezones (name) values ('America/Costa_Rica'); +insert into timezones (name) values ('America/Havana'); +insert into timezones (name) values ('Atlantic/Cape_Verde'); +insert into timezones (name) values ('Indian/Christmas'); +insert into timezones (name) values ('Asia/Nicosia'); +insert into timezones (name) values ('Europe/Prague'); +insert into timezones (name) values ('Europe/Berlin'); +insert into timezones (name) values ('Africa/Djibouti'); +insert into timezones (name) values ('Europe/Copenhagen'); +insert into timezones (name) values ('America/Dominica'); +insert into timezones (name) values ('America/Santo_Domingo'); +insert into timezones (name) values ('Africa/Algiers'); +insert into timezones (name) values ('America/Guayaquil'); +insert into timezones (name) values ('Pacific/Galapagos'); +insert into timezones (name) values ('Europe/Tallinn'); +insert into timezones (name) values ('Africa/Cairo'); +insert into timezones (name) values ('Africa/El_Aaiun'); +insert into timezones (name) values ('Africa/Asmera'); +insert into timezones (name) values ('Europe/Madrid'); +insert into timezones (name) values ('Africa/Ceuta'); +insert into timezones (name) values ('Atlantic/Canary'); +insert into timezones (name) values ('Africa/Addis_Ababa'); +insert into timezones (name) values ('Europe/Helsinki'); +insert into timezones (name) values ('Pacific/Fiji'); +insert into timezones (name) values ('Atlantic/Stanley'); +insert into timezones (name) values ('Pacific/Yap'); +insert into timezones (name) values ('Pacific/Truk'); +insert into timezones (name) values ('Pacific/Ponape'); +insert into timezones (name) values ('Pacific/Kosrae'); +insert into timezones (name) values ('Atlantic/Faeroe'); +insert into timezones (name) values ('Europe/Paris'); +insert into timezones (name) values ('Africa/Libreville'); +insert into timezones (name) values ('Europe/London'); +insert into timezones (name) values ('Europe/Belfast'); +insert into timezones (name) values ('America/Grenada'); +insert into timezones (name) values ('Asia/Tbilisi'); +insert into timezones (name) values ('America/Cayenne'); +insert into timezones (name) values ('Africa/Accra'); +insert into timezones (name) values ('Europe/Gibraltar'); +insert into timezones (name) values ('America/Scoresbysund'); +insert into timezones (name) values ('America/Godthab'); +insert into timezones (name) values ('America/Thule'); +insert into timezones (name) values ('Africa/Banjul'); +insert into timezones (name) values ('Africa/Conakry'); +insert into timezones (name) values ('America/Guadeloupe'); +insert into timezones (name) values ('Africa/Malabo'); +insert into timezones (name) values ('Europe/Athens'); +insert into timezones (name) values ('Atlantic/South_Georgia'); +insert into timezones (name) values ('America/Guatemala'); +insert into timezones (name) values ('Pacific/Guam'); +insert into timezones (name) values ('Africa/Bissau'); +insert into timezones (name) values ('America/Guyana'); +insert into timezones (name) values ('Asia/Hong_Kong'); +insert into timezones (name) values ('America/Tegucigalpa'); +insert into timezones (name) values ('Europe/Zagreb'); +insert into timezones (name) values ('America/Port-au-Prince'); +insert into timezones (name) values ('Europe/Budapest'); +insert into timezones (name) values ('Asia/Jakarta'); +insert into timezones (name) values ('Asia/Ujung_Pandang'); +insert into timezones (name) values ('Asia/Jayapura'); +insert into timezones (name) values ('Europe/Dublin'); +insert into timezones (name) values ('Asia/Jerusalem'); +insert into timezones (name) values ('Asia/Calcutta'); +insert into timezones (name) values ('Indian/Chagos'); +insert into timezones (name) values ('Asia/Baghdad'); +insert into timezones (name) values ('Asia/Tehran'); +insert into timezones (name) values ('Atlantic/Reykjavik'); +insert into timezones (name) values ('Europe/Rome'); +insert into timezones (name) values ('America/Jamaica'); +insert into timezones (name) values ('Asia/Amman'); +insert into timezones (name) values ('Asia/Tokyo'); +insert into timezones (name) values ('Africa/Nairobi'); +insert into timezones (name) values ('Asia/Bishkek'); +insert into timezones (name) values ('Asia/Phnom_Penh'); +insert into timezones (name) values ('Pacific/Tarawa'); +insert into timezones (name) values ('Pacific/Enderbury'); +insert into timezones (name) values ('Pacific/Kiritimati'); +insert into timezones (name) values ('Indian/Comoro'); +insert into timezones (name) values ('America/St_Kitts'); +insert into timezones (name) values ('Asia/Pyongyang'); +insert into timezones (name) values ('Asia/Seoul'); +insert into timezones (name) values ('Asia/Kuwait'); +insert into timezones (name) values ('America/Cayman'); +insert into timezones (name) values ('Asia/Almaty'); +insert into timezones (name) values ('Asia/Aqtobe'); +insert into timezones (name) values ('Asia/Aqtau'); +insert into timezones (name) values ('Asia/Vientiane'); +insert into timezones (name) values ('Asia/Beirut'); +insert into timezones (name) values ('America/St_Lucia'); +insert into timezones (name) values ('Europe/Vaduz'); +insert into timezones (name) values ('Asia/Colombo'); +insert into timezones (name) values ('Africa/Monrovia'); +insert into timezones (name) values ('Africa/Maseru'); +insert into timezones (name) values ('Europe/Vilnius'); +insert into timezones (name) values ('Europe/Luxembourg'); +insert into timezones (name) values ('Europe/Riga'); +insert into timezones (name) values ('Africa/Tripoli'); +insert into timezones (name) values ('Africa/Casablanca'); +insert into timezones (name) values ('Europe/Monaco'); +insert into timezones (name) values ('Europe/Chisinau'); +insert into timezones (name) values ('Indian/Antananarivo'); +insert into timezones (name) values ('Pacific/Majuro'); +insert into timezones (name) values ('Pacific/Kwajalein'); +insert into timezones (name) values ('Europe/Skopje'); +insert into timezones (name) values ('Africa/Bamako'); +insert into timezones (name) values ('Africa/Timbuktu'); +insert into timezones (name) values ('Asia/Rangoon'); +insert into timezones (name) values ('Asia/Ulaanbaatar'); +insert into timezones (name) values ('Asia/Hovd'); +insert into timezones (name) values ('Asia/Macao'); +insert into timezones (name) values ('Pacific/Saipan'); +insert into timezones (name) values ('America/Martinique'); +insert into timezones (name) values ('Africa/Nouakchott'); +insert into timezones (name) values ('America/Montserrat'); +insert into timezones (name) values ('Europe/Malta'); +insert into timezones (name) values ('Indian/Mauritius'); +insert into timezones (name) values ('Indian/Maldives'); +insert into timezones (name) values ('Africa/Blantyre'); +insert into timezones (name) values ('America/Mexico_City'); +insert into timezones (name) values ('America/Cancun'); +insert into timezones (name) values ('America/Merida'); +insert into timezones (name) values ('America/Monterrey'); +insert into timezones (name) values ('America/Mazatlan'); +insert into timezones (name) values ('America/Chihuahua'); +insert into timezones (name) values ('America/Hermosillo'); +insert into timezones (name) values ('America/Tijuana'); +insert into timezones (name) values ('Asia/Kuala_Lumpur'); +insert into timezones (name) values ('Asia/Kuching'); +insert into timezones (name) values ('Africa/Maputo'); +insert into timezones (name) values ('Africa/Windhoek'); +insert into timezones (name) values ('Pacific/Noumea'); +insert into timezones (name) values ('Africa/Niamey'); +insert into timezones (name) values ('Pacific/Norfolk'); +insert into timezones (name) values ('Africa/Lagos'); +insert into timezones (name) values ('America/Managua'); +insert into timezones (name) values ('Europe/Amsterdam'); +insert into timezones (name) values ('Europe/Oslo'); +insert into timezones (name) values ('Asia/Katmandu'); +insert into timezones (name) values ('Pacific/Nauru'); +insert into timezones (name) values ('Pacific/Niue'); +insert into timezones (name) values ('Pacific/Auckland'); +insert into timezones (name) values ('Pacific/Chatham'); +insert into timezones (name) values ('Asia/Muscat'); +insert into timezones (name) values ('America/Panama'); +insert into timezones (name) values ('America/Lima'); +insert into timezones (name) values ('Pacific/Tahiti'); +insert into timezones (name) values ('Pacific/Marquesas'); +insert into timezones (name) values ('Pacific/Gambier'); +insert into timezones (name) values ('Pacific/Port_Moresby'); +insert into timezones (name) values ('Asia/Manila'); +insert into timezones (name) values ('Asia/Karachi'); +insert into timezones (name) values ('Europe/Warsaw'); +insert into timezones (name) values ('America/Miquelon'); +insert into timezones (name) values ('Pacific/Pitcairn'); +insert into timezones (name) values ('America/Puerto_Rico'); +insert into timezones (name) values ('Asia/Gaza'); +insert into timezones (name) values ('Europe/Lisbon'); +insert into timezones (name) values ('Atlantic/Madeira'); +insert into timezones (name) values ('Atlantic/Azores'); +insert into timezones (name) values ('Pacific/Palau'); +insert into timezones (name) values ('America/Asuncion'); +insert into timezones (name) values ('Asia/Qatar'); +insert into timezones (name) values ('Indian/Reunion'); +insert into timezones (name) values ('Europe/Bucharest'); +insert into timezones (name) values ('Europe/Kaliningrad'); +insert into timezones (name) values ('Europe/Moscow'); +insert into timezones (name) values ('Europe/Samara'); +insert into timezones (name) values ('Asia/Yekaterinburg'); +insert into timezones (name) values ('Asia/Omsk'); +insert into timezones (name) values ('Asia/Novosibirsk'); +insert into timezones (name) values ('Asia/Krasnoyarsk'); +insert into timezones (name) values ('Asia/Irkutsk'); +insert into timezones (name) values ('Asia/Yakutsk'); +insert into timezones (name) values ('Asia/Vladivostok'); +insert into timezones (name) values ('Asia/Magadan'); +insert into timezones (name) values ('Asia/Kamchatka'); +insert into timezones (name) values ('Asia/Anadyr'); +insert into timezones (name) values ('Africa/Kigali'); +insert into timezones (name) values ('Asia/Riyadh'); +insert into timezones (name) values ('Pacific/Guadalcanal'); +insert into timezones (name) values ('Indian/Mahe'); +insert into timezones (name) values ('Africa/Khartoum'); +insert into timezones (name) values ('Europe/Stockholm'); +insert into timezones (name) values ('Asia/Singapore'); +insert into timezones (name) values ('Atlantic/St_Helena'); +insert into timezones (name) values ('Europe/Ljubljana'); +insert into timezones (name) values ('Arctic/Longyearbyen'); +insert into timezones (name) values ('Atlantic/Jan_Mayen'); +insert into timezones (name) values ('Europe/Bratislava'); +insert into timezones (name) values ('Africa/Freetown'); +insert into timezones (name) values ('Europe/San_Marino'); +insert into timezones (name) values ('Africa/Dakar'); +insert into timezones (name) values ('Africa/Mogadishu'); +insert into timezones (name) values ('America/Paramaribo'); +insert into timezones (name) values ('Africa/Sao_Tome'); +insert into timezones (name) values ('America/El_Salvador'); +insert into timezones (name) values ('Asia/Damascus'); +insert into timezones (name) values ('Africa/Mbabane'); +insert into timezones (name) values ('America/Grand_Turk'); +insert into timezones (name) values ('Africa/Ndjamena'); +insert into timezones (name) values ('Indian/Kerguelen'); +insert into timezones (name) values ('Africa/Lome'); +insert into timezones (name) values ('Asia/Bangkok'); +insert into timezones (name) values ('Asia/Dushanbe'); +insert into timezones (name) values ('Pacific/Fakaofo'); +insert into timezones (name) values ('Asia/Ashgabat'); +insert into timezones (name) values ('Africa/Tunis'); +insert into timezones (name) values ('Pacific/Tongatapu'); +insert into timezones (name) values ('Asia/Dili'); +insert into timezones (name) values ('Europe/Istanbul'); +insert into timezones (name) values ('America/Port_of_Spain'); +insert into timezones (name) values ('Pacific/Funafuti'); +insert into timezones (name) values ('Asia/Taipei'); +insert into timezones (name) values ('Africa/Dar_es_Salaam'); +insert into timezones (name) values ('Europe/Kiev'); +insert into timezones (name) values ('Europe/Uzhgorod'); +insert into timezones (name) values ('Europe/Zaporozhye'); +insert into timezones (name) values ('Europe/Simferopol'); +insert into timezones (name) values ('Africa/Kampala'); +insert into timezones (name) values ('Pacific/Johnston'); +insert into timezones (name) values ('Pacific/Midway'); +insert into timezones (name) values ('Pacific/Wake'); +insert into timezones (name) values ('America/New_York'); +insert into timezones (name) values ('America/Detroit'); +insert into timezones (name) values ('America/Louisville'); +insert into timezones (name) values ('America/Kentucky/Monticello'); +insert into timezones (name) values ('America/Indianapolis'); +insert into timezones (name) values ('America/Indiana/Marengo'); +insert into timezones (name) values ('America/Indiana/Knox'); +insert into timezones (name) values ('America/Indiana/Vevay'); +insert into timezones (name) values ('America/Chicago'); +insert into timezones (name) values ('America/Menominee'); +insert into timezones (name) values ('America/Denver'); +insert into timezones (name) values ('America/Boise'); +insert into timezones (name) values ('America/Shiprock'); +insert into timezones (name) values ('America/Phoenix'); +insert into timezones (name) values ('America/Los_Angeles'); +insert into timezones (name) values ('America/Anchorage'); +insert into timezones (name) values ('America/Juneau'); +insert into timezones (name) values ('America/Yakutat'); +insert into timezones (name) values ('America/Nome'); +insert into timezones (name) values ('America/Adak'); +insert into timezones (name) values ('Pacific/Honolulu'); +insert into timezones (name) values ('America/Montevideo'); +insert into timezones (name) values ('Asia/Samarkand'); +insert into timezones (name) values ('Asia/Tashkent'); +insert into timezones (name) values ('Europe/Vatican'); +insert into timezones (name) values ('America/St_Vincent'); +insert into timezones (name) values ('America/Caracas'); +insert into timezones (name) values ('America/Tortola'); +insert into timezones (name) values ('America/St_Thomas'); +insert into timezones (name) values ('Asia/Saigon'); +insert into timezones (name) values ('Pacific/Efate'); +insert into timezones (name) values ('Pacific/Wallis'); +insert into timezones (name) values ('Pacific/Apia'); +insert into timezones (name) values ('Asia/Aden'); +insert into timezones (name) values ('Indian/Mayotte'); +insert into timezones (name) values ('Europe/Belgrade'); +insert into timezones (name) values ('Africa/Johannesburg'); +insert into timezones (name) values ('Africa/Lusaka'); +insert into timezones (name) values ('Africa/Harare'); + +GRANT ALL PRIVILEGES ON PLANWORLD_PUBLIC_MYSQL.* TO 'pwmysqluser'@'localhost'; + + diff --git a/setup/readme.md b/setup/readme.md new file mode 100644 index 0000000..d039c4c --- /dev/null +++ b/setup/readme.md @@ -0,0 +1,190 @@ +**Readme For Planworld Alphas** +-------------------------------------- +_Last Updated 20170406_ by jlodom00 + + +_Releases in V3.00 Alpha Series_ +20170406 - "Louisbourg" - Send Only Release Without Clients + +_License_ +This release does not yet include proper attribution for authors (Seth Fitzsimmons, Baker Franke, Johnnie Odom et al) but it is licensed using the GPLv2 as the previous version was. Future releases will more properly note this. + +_Introduction_ +Use these instructions to set up an independent test instance of the in-progress Planworld v3 code for testing and client development. It contains only a tiny portion of the full Planworld functionality, and the code is not as well-documented or cleaned up as it will be by release. The instructions are clear to me (jlodom00), but I deal with this sort of thing all the time so feel free to reach out ( jlodom00@alumni.amherst.edu ) if you need help. + + +_Prerequisites:_ +1. Apache Web Server configured with rewrite and headers modules and with "AllowOverride All" enabled on the site. +2. PHP 5.2 or greater with common extensions (pdo w/ mysql support, crypt, json, xml ... more to be added I'm sure). +3. MySQL or MariaDB. + + +_Installation:_ +1. Create the planworld MySQL account and planworld database by running the MySQL commands contained in setup/planworld_mysql_db_create.sql +2. Copy everything in the code folder to the place on your apache server where planworld will run. +3. Edit code/config.php to match your configuration (it may not need any changes). +4. Modify client/htaccess.rename to fit the paths on your server and rename it to .htaccess (with leading period). +5. Edit client/index.php and change the path for $pathClientPrefix. +6. (Optional) Edit scaffolding/debug_client.html and change "var planworldbasedefault" and the nodeURL form input to the base client API url of your server (i.e. the URL a web browser would use to get to the client directory). + + +_Testing Setup And Alpha Administration_ + +1. You know that the engine is responding to API calls if you can open a web browser and go to http://myserver.com/path/to/client/3.00/system/version.xml (where /path/to/client/ is the URL path to the client directory) and get a page with "3.00" as a response. If you are having difficulty at this step, the problem is probably with your Apache configuration (especially your website config and very likely your Directory options). + +2. Planworld is empty when you start, so you will need to use the webpages in the scaffolding directory to add users, create tokens for them, and see what users exist on your system. If you have difficulty here, check your mysql setup. + +3. Once you have users and tokens, you can test API functionality with the debug client included in scaffolding/debug_client.html. See the Client API notes below for rough usage of this page. + + +_The Planworld Client API_ + +The major new feature of Planworld v3 is a RESTful API that can be used to write clients independent of the Planworld server code. This will allow for much more rapid and diverse development of user functionality in the future. It will also allow for major changes to the underlying server engine without disrupting the user experience. To deliver the API there is also a lot of work modernizing and cleaning up the underlying engine. + +*URL Structure* + +The basic structure of the API REST calls was determined around 2010 and has had only minor changes since then. A typical API call will look like the following: + +http://servername.com/some/path/client/0.00/category/verb/argument1/argument2.format?token=tokenumber + +A real-world example of this would be: + +https://somewhere.com/planworld/client/3.00/send/send/alice.xml?token=45678912 + +Breaking down those components: + +BASE PATH: http://servername.com/some/path/client/ + This is the web URL to the client folder on the Planworld engine web server. If you go to this address and receive the message "No REST Url." then you know you are on the right track. + +VERSION: 0.00/ + In order to make future-proofing easier, the version of the API used is represented here as a number with two decimal places. Currently only a value of 3.00 is supported. + +CATEGORY: category/ + + This portion of the path is meant to make grouping planworld calls/functions/verbs easier, especially for those implementing the engine. If you have trouble making an API call, (especially using the debug client), check to make sure that this value is correct for the verb that is being called. Current values are "system" and "send". + +VERB: verb/ + This is the actual verb or planworld function being called. These have different properties depending upon whether information is being retrieved (GET) or sent (POST) and the same verb may perform both roles. Current values are "version" (pairing with the "system" category), "send", and "sendlist" (both pairing with the "send" category). Of the four verbs listed, all support GET but only "send" supports POST. + +ARGUMENTS: argument1/argument2 + Planworld verbs may take an arbitrary number of arguments. Each of these is a separate directory-separator, but the last one should not have a trailing slash. + +FORMAT: .format + Data will be returned to the client in the format specified here. Allowed values are txt, xml, and json. The URL always replaces the last forward slash with a period followed by format (i.e. /urlend.format instead of /urlend/). For xml and json, data is returned as an array whose key is the name of the Planworld verb. + +TOKEN: ?token=tokenumber + Authentication is currently handled by a tokening system. The mid-term plan (post v3.00) is to move to OAuth, and so the token is implemented as a parameter rather than an integral part of the URL. In this alpha tokens are numeric, but by release they should be alphanumeric. Not all verbs require a token -- for example the "version" can be called anonymously. + +*Posting Data* + +For calls that send data to the engine (such as posting sends and plans), the data should be posted under the name "planworld_post". They use the same URL format as any other call, just with a HTTP POST instead of a GET. + +*Currently Supported Calls* + +Category - system + version : GET Returns the version of the Planworld API supported. No arguments. Does not require token. + +Category - Send (Tokens required for all) + send : GET Returns the full send conversation between the user and another user. Takes one argument -- the username of the other user in the conversation. + send : POST Send a new message to another user. Takes one argument -- the username of the other user in the conversation. + sendlist : GET Returns a list of every user with whom the current user has had a send conversation (similar to a watchlist). No arguments. + +*Writing a Client* + +For this alpha release, you will need to create a user and a token for that user before client calls can be made because there is no way to create tokens through the client API. Clients may be written on any platform and in any programming language -- as long as they can make GET and POST HTTP calls to the API URLs. The API is a REST API and generally conforms to the conventions established by that style. + +Future releases will include example clients in a variety of languages. They have been omitted from this release because we needed to ship. + + +_Planworld v3 Upgrade Work_ + +Much work has already been done on Planworld Version 3. Unfortunately, we cannot expose this previous work until some updates are made to the underlying libraries that form the lowest layer of the application. The database library that NOTE Planworld v2 uses is not only obsolete, but the successor library is obsolete also. Therefore it is necessary to replace every database call in the libraries with a much more forward-compatible call to the PHP PDO libraries. + +As large sections of the Planworld libraries are rewritten, the pre-existing API code can be layered on top of it and new functionality can be rolled out even before the entire codebase has been upgraded. Even though the current release only supports "send", the plumbing that has been done to make even this functionality possible forms a foundation for the rest of the work. + +The current work is to take functions from the old lib files that make database calls and rewrite them in the new files, one by one, to use PDO. + +The basic rules for this work are: +1. Old SQL calls should be rewritten to use PDO. +2. Where possible, the function should be made more paranoid -- it should assume that bad data will enter it and should be constructed to deal gracefully with failure. +3. Comments should be added. +4. Formatting should be cleaned up +5. Obsolete calls (such as those directly creating display formatting) should be eliminated. + + +*EXAMPLE* + +Here is a "before and after" example of the kind of work that needs to happen with the libraries. If you would like to contribute in this area, rather than working on clients, please contact Johnnie Odom and he will make libraries available for you to update. + +*Before* + + /* + string Planworld::idToName ($uid) + converts numeric $uid to string representation + */ + function idToName ($uid) { + static $table; /* persistent lookup table */ + if (is_int($uid)) { + $dbh = Planworld::_connect(); + if (isset($table[$uid])) { + return $table[$uid]; + } + else { + $query = "SELECT username FROM users WHERE id={$uid}"; + $result = $dbh->query($query); + if (isset($result) && !DB::isError($result)) { + if ($result->numRows() < 1) return PLANWORLD_ERROR; + $row = $result->fetchRow(); + $table[$uid] = $row['username']; + return $table[$uid]; + } + else { + return PLANWORLD_ERROR; + } + } + } + else { + return PLANWORLD_ERROR; + } + } + + +*After* + + +/* + string Planworld::idToName ($uid) + converts numeric $uid to string representation + Note that the argument must not look like a string -- it must be sent as an int. + */ + function idToName ($uid) { + static $table; /* persistent lookup table */ + if (is_int($uid)) { + $dbh = Planworld::_connect(); + if (isset($table[$uid])) { + return $table[$uid]; + } + else { + try{ + $query = $dbh->prepare('SELECT username FROM users WHERE id= :uid'); + $queryArray = array('uid' => $uid); + $query->execute($queryArray); + $result = $query->fetch(); + if (!$result){ + return PLANWORLD_ERROR; + } + else { + $table[$uid] = $result['username']; + return $table[$uid]; + } + } + catch(PDOException $badquery){ + return PLANWORLD_ERROR; + } + } + } + else { + return PLANWORLD_ERROR; + } + return PLANWORLD_ERROR; + } \ No newline at end of file