diff --git a/.gitignore b/.gitignore index 7aa7065..ac8f2f2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,5 @@ dumps incoming_symbols logs htdocs/lib/logs -.hg +.hg* diff --git a/htdocs/.htaccess b/htdocs/.htaccess new file mode 100644 index 0000000..8aa276d --- /dev/null +++ b/htdocs/.htaccess @@ -0,0 +1 @@ +php_flag display_errors on diff --git a/htdocs/account.php b/htdocs/account.php new file mode 100644 index 0000000..81ca00e --- /dev/null +++ b/htdocs/account.php @@ -0,0 +1,59 @@ +isAnonymous()) +{ + $S->loginRedirect(); +} +$user = $S->user; + +if (isset($_POST["user_id"])) +{ + $user->name = $_POST["name"]; + $msg = $user->update() ? "Information updated" : "Update failed"; + http::redirect("/account.php?msg=" . urlencode($msg)); +} +Layout::header(); +?> + +

Account Details

+ + + + + +
+ + + + + + + + + + + + + + + + + + +
Name 
Emailemail); ?>
Access GrantedisAllowed() ? "yes" : "no" ?>
Admin PrivilegesisAdmin() ? "yes" : "no" ?>
+
+ +requireUser(); + +Layout::header(); +?> + +Not implemented + + + +

This application is used for analyzing crash reports and statitstics for the Singularity +Viewer project.

+ +

Access to this tool is granted to the members of the development team. +The main goal is to identify the most common problems and improve the expierience +for the users of Singularity Viewer.

+ +isAnonymous()) +{ + if ($S->user->isAllowed()) + { + print '

Your account has been granted access.

'; + } + else + { + print '

Your account has no access to the system at this time.

'; + } +} +Layout::footer(); + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/htdocs/lib/.htaccess b/htdocs/lib/.htaccess new file mode 100644 index 0000000..8d2f256 --- /dev/null +++ b/htdocs/lib/.htaccess @@ -0,0 +1 @@ +deny from all diff --git a/htdocs/lib/DBH.php b/htdocs/lib/DBH.php new file mode 100644 index 0000000..d8f186b --- /dev/null +++ b/htdocs/lib/DBH.php @@ -0,0 +1,185 @@ +db_name = $db_name; + $this->db_pass = $db_pass; + $this->db_user = $db_user; + $this->db_host = $db_host; + + $this->dbh = @mysql_connect($db_host, $db_user, $db_pass); + + if (!$this->dbh) { + DBH::log("[error] connection to database failed"); + DBH::log("[error] connection string used '$conn_str'"); + return false; + } + + if (!mysql_select_db($db_name)) { + DBH::log("[error] database {$db_name} dose not exist"); + return false; + } + + + $this->query("SET SQL_MODE='TRADITIONAL'"); + $this->query("SET NAMES 'utf8'"); + return true; + + } + + function query($q) + { + $res = @mysql_query($q, $this->dbh); + + if (!$res) { + DBH::log("[error] ".$q); + DBH::log("[error_msg] " . mysql_error($this->dbh)); + $this->last_error = mysql_error($this->dbh); + + $e = debug_backtrace(); + $c = count($e); + $btr = ""; + + for ($i=0; $i<$c; $i++) { + $btr .= "{$e[$i]['class']}::{$e[$i]['function']} {$e[$i]['file']}({$e[$i]['line']})\n"; + } + + DBH::log("[backtrace]\n".$btr); + + return false; + } else { + if ($res !== TRUE) { + $result_id = (int)$res; + if (!isset($this->field_desc[$result_id])) { + $nf = mysql_num_fields($res); + for ($i=0; $i<$nf; $i++) { + $this->field_desc[$result_id][mysql_field_name($res, $i)] = mysql_field_type($res, $i); + } + } + } + DBH::log("[success] ".$q); + return $res; + } + } + + function loadFromDbRow(&$obj, $res, $row) + { + foreach ($row as $symbolicName => $nativeName){ + if ($nativeName && ($this->field_desc[(int)$res][$symbolicName] == "timestamp" || + $this->field_desc[(int)$res][$symbolicName] == "date" || + $this->field_desc[(int)$res][$symbolicName] == "datetime")) { + $obj->{$symbolicName} = strtotime($nativeName); + } else { + $obj->{$symbolicName} = $nativeName; + } + } + return true; + } + + function insertID() + { + return @mysql_insert_id($this->dbh); + } + + function numRows($res) + { + return @mysql_num_rows($res); + } + + function affectedRows() + { + return @mysql_affected_rows($this->dbh); + } + + function fieldName($res, $num) + { + return @mysql_field_name($res, $num); + } + + function numFields($res) + { + return @mysql_num_fields($res); + } + + function fetchRow($res) + { + return @mysql_fetch_assoc($res); + } + + function begin() + { + return $this->query('begin'); + } + + function rollback() + { + return $this->query('rollback'); + } + + function commit() + { + return $this->query('commit'); + } + + /* FIXME: port from postgres */ + function nextId($seq) + { + $res = $this->query("select nextval('$seq') as n"); + + if (!$res || !$row = $this->fetchRow($res)) { + return false; + } else { + return (int)$row['n']; + } + } + + /* Date time conversion */ + function db2unix($s) + { + return strtotime($s); + } + +} +/* +* Local variables: +* tab-width: 4 +* c-basic-offset: 4 +* End: +* vim600: sw=4 ts=4 fdm=marker +* vim<600: sw=4 ts=4 +*/ +?> \ No newline at end of file diff --git a/htdocs/lib/GoogleOpenID.php b/htdocs/lib/GoogleOpenID.php new file mode 100644 index 0000000..abe5e1e --- /dev/null +++ b/htdocs/lib/GoogleOpenID.php @@ -0,0 +1,478 @@ +redirect(); + * + * OR + * + * $handle = GoogleOpenID::getAssociationHandle(); // <--save this! valid for two weeks! + * + * $googleGateway = GoogleOpenID::createRequest("http://www.mydomain.com/checkauth.php", $handle, true); + * $googleGateway->redirect(); + * + * + * When you want to recieve a Google OpenID response, simply pass the given + * parameters to GoogleOpenID::create(). In most cases, you should just be able + * to pass the $_GET variable. + * + * To continue the previous example, the following code would go in checkauth.php + * + * $googleResponse = GoogleOpenID::create($_GET); + * $sucess = $googleResponse->success();//true or false + * $user_identity = $googleResponse->identity();//the user's ID + * $user_email = $googleResponse->email();//the user's email + * + * OR, even easier + * + * $googleResponse = GoogleOpenID::getResponse(); // <-- automatically reads $_GET + * + * Advanced users can create a slightly more customized request using the create + * method. It accepts an associative array of openid parameters and sets those + * that it deems appropriate to set (mostly, the parameters that don't have a + * definite value when interacting with Google) + * + * + * Full class signature: + * + * public static createRequest(String, [String], [Boolean]) + * public static getResponse() + * public static create(Array) + * public static getAssociationHandle([String]) + * public static getEndPoint() + * public redirect() + * public getArray() + * public endPoint() + * public success() + * public assoc_handle() + * public email() + * public identity() + * + * Features to implement: + * + * -In constructor, fix relative->absolute URL conversion (it is messy/buggy) + * -In getAssociationHandle(), use encryption + * -Verify Google's response with signed, sig etc + ****************************************************************************/ + + class GoogleOpenID{ + //the google discover url + const google_discover_url = "https://www.google.com/accounts/o8/id"; + + //some constant parameters + const openid_ns = "http://specs.openid.net/auth/2.0"; + //required for email attribute exchange + const openid_ns_ext1 = "http://openid.net/srv/ax/1.0"; + const openid_ext1_mode = "fetch_request"; + const openid_ext1_type_email = "http://schema.openid.net/contact/email"; + const openid_ext1_required = "email"; + + //parameters set by constructor + private $mode;//the mode (checkid_setup, id_res, or cancel) + private $response_nonce; + private $return_to;//return URL + private $realm;//the realm that the user is being asked to trust + private $assoc_handle;//the association handle between this service and Google + private $claimed_id;//the id claimed by the user + private $identity;//for google, this is the same as claimed_id + private $signed; + private $sig; + private $email;//the user's email address + + //if true, fetch email address + private $require_email; + + //private constructor + private function GoogleOpenID($mode, $op_endpoint, $response_nonce, $return_to, $realm, $assoc_handle, $claimed_id, $signed, $sig, $email, $require_email){ + + //if assoc_handle is null, fetch one + if(is_null($assoc_handle)) + $assoc_handle = GoogleOpenID::getAssociationHandle(); + + //if return_to is a relative URL, make it absolute + if(stripos($return_to, "http://")!==0 && + stripos($return_to, "https://")!==0){ + //if the first character is a slash, delete it + if(substr($return_to, 0, 1)=="/") + $return_to = substr($return_to, 1); + //get the position of server_name + $server_name_pos = stripos($return_to, $_SERVER['SERVER_NAME']); + //if server_name is already at position zero + if($server_name_pos != false && $server_name_pos==0){ + $return_to = "http://".$return_to; + } else { + $return_to = "http://".$_SERVER['SERVER_NAME']."/".$return_to; + }//else (server name not at position zero) + }//if return_to is relative + + //if realm is null, attempt to set it via return_to + if(is_null($realm)){ + //if return_to is set + if(!is_null($return_to)){ + $pieces = parse_url($return_to); + $realm = $pieces['scheme']."://".$pieces['host']; + }//if return_to set + }//if realm null + + $this->mode = $mode; + $this->op_endpoint = $op_endpoint; + $this->response_nonce = $response_nonce; + $this->return_to = $return_to; + $this->realm = $realm; + $this->assoc_handle = $assoc_handle; + $this->claimed_id = $claimed_id; + $this->identity = $claimed_id; + $this->signed = $signed; + $this->sig = $sig; + $this->email = $email; + $this->require_email = ($require_email) ? true : false; + }//GoogleOpenID + + //static creator that accepts only a return_to URL + //this creator should be used when creating a GoogleOpenID for a redirect + public static function createRequest($return_to, $assoc_handle=null, $require_email=false){ + return new GoogleOpenID("checkid_setup", null, null, $return_to, null, $assoc_handle, "http://specs.openid.net/auth/2.0/identifier_select", null, null, null, $require_email); + }//createRequest + + //static creator that accepts an associative array of parameters and + //sets only the setable attributes (does not overwrite constants) + public static function create($params){ + //loop through each parameter + foreach($params as $param => $value){ + switch($param){ + case "openid_mode": + //check validity of mode + if($value=="checkid_setup" || + $value=="id_res" || + $value=="cancel") + $mode = $value; + else + $mode = "cancel"; + continue 2; + + case "openid_op_endpoint": + $op_endpoint = $value; + continue 2; + + case "openid_response_nonce": + $response_nonce = $value; + continue 2; + + case "openid_return_to": + $return_to = $value; + continue 2; + + case "openid_realm": + $realm = $value; + continue 2; + + case "openid_assoc_handle": + $assoc_handle = $value; + continue 2; + + case "openid_claimed_id": + $claimed_id = $value; + continue 2; + + case "openid_identity": + $claimed_id = $value; + continue 2; + + case "openid_signed": + $signed = $value; + continue 2; + + case "openid_sig": + $sig = $value; + continue 2; + + case "openid_ext1_value_email": + $email = $value; + continue 2; + + case "require_email": + $require_email = $value; + continue 2; + + default: + continue 2; + }//switch param + }//loop through params + + //if require email is not set, set it to false + if(!is_bool($require_email)) + $require_email = false; + //if mode is not set, set to default for redirection + if(is_null($mode)) + $mode = "checkid_setup"; + //if return_to is not set and mode is checkid_setup, throw an error + if(is_null($return_to) && $mode=="checkid_setup") + throw new Exception("GoogleOpenID.create() needs parameter openid.return_to"); + + //return a new GoogleOpenID with the given parameters + return new GoogleOpenID($mode, $op_endpoint, $response_nonce, $return_to, $realm, $assoc_handle, $claimed_id, $signed, $sig, $email, $require_email); + }//create + + //creates and returns a GoogleOpenID from the $_GET variable + public static function getResponse(){ + return GoogleOpenID::create($_GET); + }//getResponse + + //fetches an association handle from google. association handles are valid + //for two weeks, so coders should do their best to save association handles + //externally and pass them to createRequest() + //NOTE: This function does not use encryption, but it SHOULD! At the time + //I wrote this I wanted it done fast, and I couldn't seem to find a good + //two-way SHA-1 or SHA-256 library for PHP. Encryption is not my thing, so + //it remains unimplemented. + public static function getAssociationHandle($endpoint=null){ + //if no endpoint given + if(is_null($endpoint)) + //fetch one from Google + $request_url = GoogleOpenID::getEndPoint(); + //if endpoint given, set it + else + $request_url = $endpoint; + + //append parameters (these never change) + $request_url .= "?openid.ns=".urlencode(GoogleOpenID::openid_ns); + $request_url .= "&openid.mode=associate"; + $request_url .= "&openid.assoc_type=HMAC-SHA1"; + $request_url .= "&openid.session_type=no-encryption"; + + //create a CURL session with the request URL + $c = curl_init($request_url); + + //set a few options + curl_setopt($c, CURLOPT_RETURNTRANSFER, true); + curl_setopt($c, CURLOPT_HEADER, false); + + //get the contents of request URL + $request_contents = curl_exec($c); + + //close the CURL session + curl_close($c); + + //a handle to be returned + $assoc_handle = null; + + //split the response into lines + $lines = explode("\n", $request_contents); + + //loop through each line + foreach($lines as $line){ + //if this line is assoc_handle + if(substr($line, 0, 13)=="assoc_handle:"){ + //save the assoc handle + $assoc_handle = substr($line, 13); + //exit the loop + break; + }//if this line is assoc_handle + }//loop through lines + + //return the handle + return $assoc_handle; + }//getAssociationHandle + + //fetches an endpoint from Google + public static function getEndPoint(){ + //fetch the request URL + $request_url = GoogleOpenID::google_discover_url; + + //create a CURL session with the request URL + $c = curl_init($request_url); + + //set a few options + curl_setopt($c, CURLOPT_RETURNTRANSFER, true); + curl_setopt($c, CURLOPT_HEADER, false); + + //fetch the contents of the request URL + $request_contents = curl_exec($c); + + //close the CURL session + curl_close($c); + + //create a DOM document so we can extract the URI element + $domdoc = new DOMDocument(); + $domdoc->loadXML($request_contents); + + //fetch the contents of the URI element + $uri = $domdoc->getElementsByTagName("URI"); + $uri = $uri->item(0)->nodeValue; + + //return the given URI + return $uri; + }//getEndPoint + + //returns an associative array of all openid parameters for this openid + //session. the array contains all the GET attributes that would be sent + //or that have been recieved, meaning: + // + //if mode = "cancel" returns only the mode and ns attributes + //if mode = "id_res" returns all attributes that are not null + //if mode = "checkid_setup" returns only attributes that need to be sent + // in the HTTP request + public function getArray(){ + //an associative array to return + $ret = array(); + + $ret['openid.ns'] = GoogleOpenID::openid_ns; + + //if mode is cancel, return only ns and mode + if($this->mode=="cancel"){ + $ret['openid.mode'] = "cancel"; + return $ret; + }//if cancel + + //set attributes that are returned for all cases + if(!is_null($this->claimed_id)){ + $ret['openid.claimed_id'] = $this->claimed_id; + $ret['openid.identity'] = $this->claimed_id; + } + if(!is_null($this->return_to)) + $ret['openid.return_to'] = $this->return_to; + if(!is_null($this->realm)) + $ret['openid.realm'] = $this->realm; + if(!is_null($this->assoc_handle)) + $ret['openid.assoc_handle'] = $this->assoc_handle; + if(!is_null($this->mode)) + $ret['openid.mode'] = $this->mode; + + //set attributes that are returned only if this is a request + //and if getting email is required OR if this is a response and the + //email is given + if(($this->mode=="checkid_setup" AND $this->require_email) OR + ($this->mode=="id_res" AND !is_null($this->email))){ + $ret['openid.ns.ext1'] = GoogleOpenID::openid_ns_ext1; + $ret['openid.ext1.mode'] = GoogleOpenID::openid_ext1_mode; + $ret['openid.ext1.type.email'] = GoogleOpenID::openid_ext1_type_email; + $ret['openid.ext1.required'] = GoogleOpenID::openid_ext1_required; + if(!is_null($this->email)) + $ret['openid.ext1.value.email'] = $this->email; + }//if redirect and get email + + //set attributes that are returned only if this is a response + if($this->mode=="id_res"){ + $ret['openid.op_endpoint'] = $this->op_endpoint; + if(!is_null($this->response_nonce)) + $ret['openid.response_nonce'] = $this->response_nonce; + if(!is_null($this->signed)) + $ret['openid.signed'] = $this->signed; + if(!is_null($this->sig)) + $ret['openid.sig'] = $this->sig; + } + + //return the array + return $ret; + }//getArray + + //sends a request to google and fetches the url to which google is asking + //us to redirect (unless the endpoint is already known, in which case the + //function simply returns it) + public function endPoint(){ + //if we know the value of op_endpoint already + if(!is_null($this->op_endpoint)) + return $this->op_endpoint; + + //fetch the endpoint from Google + $endpoint = GoogleOpenID::getEndPoint(); + + //save it + $this->op_endpoint = $endpoint; + + //return the endpoint + return $endpoint; + }//getedPoint + + //returns the URL to which we should send a request (including all GET params) + public function getRequestURL(){ + //get all parameters + $params = $this->getArray(); + + //the base URL + $url = $this->endPoint(); + + //flag indicating whether to set a '?' or an '&' + $first_attribute = true; + + //loop through all params + foreach($params as $param => $value){ + //if first attribute print a ?, else print a & + if($first_attribute){ + $url .= "?"; + $first_attribute = false; + } else { + $url .= "&"; + }//else (not first attribute) + + $url .= urlencode($param) . "=" . urlencode($value); + }//loop through params + + //return the URL + return $url; + }//getRequestURL + + //redirects the browser to the appropriate request URL + public function redirect(){ + header("Location: ".$this->getRequestURL()); + }//redirect + + //returns true if the response was a success + public function success(){ + return ($this->mode=="id_res"); + }//success + + //returns the identity given in the response + public function identity(){ + if($this->mode!="id_res") + return null; + else + return $this->claimed_id; + }//identity + + //returns the email given in the response + public function email(){ + if($this->mode!="id_res") + return null; + else + return $this->email; + }//email + + //returns the assoc_handle + public function assoc_handle(){ + return $this->assoc_handle(); + }//assoc_handle + }//class GoogleOpenID +?> diff --git a/htdocs/lib/Layout.php b/htdocs/lib/Layout.php new file mode 100644 index 0000000..1329739 --- /dev/null +++ b/htdocs/lib/Layout.php @@ -0,0 +1,157 @@ + +* @copyright Copyright © 2012, Latif Khalifa +* +* Permission is hereby granted, free of charge, to any person obtaining +* a copy of this software and associated documentation files +* (the "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to permit +* persons to whom the Software is furnished to do so, subject to the +* following conditions: +* +* - The above copyright notice and this permission notice shall be included +* in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +*/ + +class Layout +{ + + function since($since) + { + $since = time() - $since; + $chunks = array( + array(60 * 60 * 24 * 365 , 'year'), + array(60 * 60 * 24 * 30 , 'month'), + array(60 * 60 * 24 * 7, 'week'), + array(60 * 60 * 24 , 'day'), + array(60 * 60 , 'hour'), + array(60 , 'minute'), + array(1 , 'second') + ); + + for ($i = 0, $j = count($chunks); $i < $j; $i++) { + $seconds = $chunks[$i][0]; + $name = $chunks[$i][1]; + if (($count = floor($since / $seconds)) != 0) { + break; + } + } + + $print = ($count == 1) ? '1 '.$name : "$count {$name}s"; + return $print; + } + + function header() + { + global $S; + + $menu = array(); + + if ($S->isAnonymous()) + { + $item = new stdClass; + $item->label = "Login"; + $item->link = "/login.php"; + $menu[] = $item; + } + else + { + if ($S->user->isAllowed()) + { + $item = new stdClass; + $item->label = "Crash reports"; + $item->link = "/crashes.php"; + $menu[] = $item; + + $item = new stdClass; + $item->label = "Statistics"; + $item->link = "/statistics.php"; + $menu[] = $item; + } + + if ($S->user->isAdmin()) + { + $item = new stdClass; + $item->label = "Users"; + $item->link = "/users.php"; + $menu[] = $item; + } + + $item = new stdClass; + $item->label = "My Account ({$S->user->email})"; + $item->link = "/account.php"; + $menu[] = $item; + + $item = new stdClass; + $item->label = "Logout"; + $item->link = "/logout.php"; + $menu[] = $item; + } + + ?> + + + + + + + Singularity Viewer Automated Build System + + + + + +
+
+ + +
+ + + + + + +
© 2013 Singularity Viewer Project
+
+
+
+ + + + +* @copyright Copyright © 2012, Latif Khalifa +* +* Permission is hereby granted, free of charge, to any person obtaining +* a copy of this software and associated documentation files +* (the "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to permit +* persons to whom the Software is furnished to do so, subject to the +* following conditions: +* +* - The above copyright notice and this permission notice shall be included +* in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +*/ + +class Option +{ + private static $optionArray = array(); + + /** + * Inits the option class. Must be called before any other method. + */ + public static function init() + { + global $DB; + + if(!$result = $DB->query("SELECT * FROM options")) { + return false; + } else { + while ($row = $DB->fetchRow($result)) { + self::$optionArray[$row['name']] = $row['value']; + } + return true; + } + } + + /** + * Updates an option. + * + * @param string $name Name of the option + * @param string $value New value for the option + * @return boolean + */ + public static function update($name, $value) + { + global $DB; + $result = $DB->query(kl_str_sql("DELETE FROM options WHERE name=!s", $name)); + $result = $DB->query(kl_str_sql("INSERT INTO options (name, value) VALUES(!s,!s)", $name,$value )); + self::$optionArray[$name] = $value; + return $result; + } + + + /** + * Gets an option. + * + * @param string $name Name of the option + * @return string Value of the option + */ + public static function get($name) + { + if (!isset(self::$optionArray[$name])) { + return NULL; + } + return self::$optionArray[$name]; + } +} + +/* +* Local variables: +* tab-width: 4 +* c-basic-offset: 4 +* End: +* vim600: sw=4 ts=4 fdm=marker +* vim<600: sw=4 ts=4 +*/ +?> \ No newline at end of file diff --git a/htdocs/lib/Session.php b/htdocs/lib/Session.php new file mode 100644 index 0000000..0ff172c --- /dev/null +++ b/htdocs/lib/Session.php @@ -0,0 +1,283 @@ + +* @copyright Copyright © 2012, Latif Khalifa +* +* Permission is hereby granted, free of charge, to any person obtaining +* a copy of this software and associated documentation files +* (the "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to permit +* persons to whom the Software is furnished to do so, subject to the +* following conditions: +* +* - The above copyright notice and this permission notice shall be included +* in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +*/ +class Session +{ + private $cookie; + public $timeout; + public $authenticated; + public $validsession; + + /** + * User object containing info about the user. + * + * @var User + */ + public $user; + + function __construct() + { + $this->timeout = 604800; // 7 days + $this->authenticated = false; + $this->validsession = false; + $this->cookie = "singularity_sid"; + $this->user = new User; + register_shutdown_function(array(&$this, 'shutdown')); + } + + function shutdown() + { + if ($this->persist || $this->ser_persist) { + $ser=serialize($this->persist); + if ($ser !== $this->ser_persist) { + $this->ser_persist = $ser == 'N;' ? '' : $ser; + $this->expires=time()+$this->timeout; + $this->update(); + } + } + } + + function add() + { + global $DB; + + $this->sid = md5(uniqid(rand())); + $this->expires=time() + $this->timeout; + + $DB->query(kl_str_sql("DELETE from session where sid=!s", $this->sid)); + $q = kl_str_sql("INSERT into session (sid, user_id, authenticated, expires, persist) ". + "values (!s, !i, !i, !t, !s)", + $this->sid, + $this->user->user_id, + $this->authenticated, + $this->expires, + $this->ser_persist + ); + + if ($DB->query($q)) { + setcookie($this->cookie, $this->sid, NULL, '/' . REL_DIR); + return true; + } else { + return false; + } + } + + function update() + { + global $DB; + + $q = kl_str_sql('UPDATE session SET user_id=!i, authenticated=!i, expires=!t, persist=!s WHERE sid=!s', + $this->user->user_id, $this->authenticated, $this->expires, $this->ser_persist, $this->sid); + + if (($res = $DB->query($q)) && $DB->affectedRows()) { + return true; + } else { + return $this->add(); + } + } + + function remove($sid = false) + { + global $DB; + + if (!$sid) { + $sid = $this->sid; + } + $this->sid = NULL; + $this->validsession = false; + $this->authenticated = 0; + $this->user = new User; + $this->persist = NULL; + $this->ser_persist = NULL; + $DB->query(kl_str_sql("DELETE from session where sid=!s", $sid)); + setcookie($this->cookie, '', 0, '/' . REL_DIR); + } + + function check() + { + global $DB; + if (isset($_GET[$this->cookie])) { + $this->sid = $_GET[$this->cookie]; + } else { + $this->sid = $_COOKIE[$this->cookie]; + } + + $error = true; + + if (!$this->sid) { + // No session id. Is anonymous access allowed? + if (Option::get('allow_anon_access') == '1') { + $this->user = new User(); + $this->user_id = $this->user->user_id; + $this->add(); + } else { + $this->user_id = NULL; + $this->add(); + } + $error = false; + } else { + $res = $DB->query(kl_str_sql('SELECT * from session where sid=!s', $this->sid)); + if ($res AND $row = $DB->fetchRow($res)) { + $this->authenticated = (int)$row['authenticated']; + $this->expires = strtotime($row['expires']); + $this->ser_persist = $row['persist']; + $this->user_id = (int)$row['user_id']; + + if (!$this->user_id) { + $this->user_id = NULL; + } + + if ($this->ser_persist) { + $this->persist=@unserialize($this->ser_persist); + } + + if ($this->expires >= time()) { + $error = false; + $this->validsession=true; + + if (!$this->user = User::get($this->user_id)) { + $error = true; + } + } + + if ($this->expires-time() < $this->timeout/2) { + $this->expires=time() + $this->timeout; + setcookie($this->cookie, $this->sid, NULL, '/' . REL_DIR); + if (!$this->update()) { + $error = true; + } + } + } + + if ($error) { + if ($this->sid) { + $this->remove($this->sid); + } + + $this->add(); + } + } + } // End function check + + function authenticate($username, $password) + { + $user = User::getByUsername($username); + if ($user AND $user->password === $password) { + $this->user = $user; + $this->user_id = $user->user_id; + $this->authenticated = true; + $this->persist->active_order = null; + $this->update(); + return true; + } + return false; + } + + function loginRedirect() + { + if ($l = strlen(REL_DIR)) { + $eatchars = $l + 2; + } else { + $eatchars = $l + 1; + } + $caller='/' . substr($_SERVER['PHP_SELF'], $eatchars); + $callerarg = array(); + + if ($nc = count($_GET)) { + foreach($_GET as $key => $value) { + $callerarg[] = urlencode($key) . '=' . urlencode($value); + } + $callerarg = implode('&', $callerarg); + } + $redirect = '/login.php?caller=' . urlencode($caller); + + if ($callerarg) { + $redirect .= "&callerarg=" . urlencode($callerarg); + } + + http::redirect($redirect); + } + + public function requireUser() + { + if ((Option::get('allow_anon_access') == '0' && $this->isAnonymous()) + || $this->user->isAnonymous()) { + $this->loginRedirect(); + } else if (!$this->user->isAllowed()) { + $this->remove($this->sid); + $this->loginRedirect(); + } + } + + /** + * Returns true if the user is anonymous. + * + * @return boolean + */ + public function isAnonymous() + { + if ($this->authenticated == 0) { + return true; + } else { + return false; + } + } + + /** + * Redirects the user to a login screen if he's not logged in as admin. + */ + public function requireAdmin() + { + if ($this->isAdmin() != true) { + $this->remove($this->sid); + $this->loginRedirect(); + } + } + + /** + * Returns true if user is admin. False if not. + * + * @return boolean + */ + public function isAdmin() + { + return ($this->authenticated && $this->user->isAdmin()); + } +} + +/* +* Local variables: +* tab-width: 4 +* c-basic-offset: 4 +* End: +* vim600: sw=4 ts=4 fdm=marker +* vim<600: sw=4 ts=4 +*/ +?> \ No newline at end of file diff --git a/htdocs/lib/User.php b/htdocs/lib/User.php new file mode 100644 index 0000000..872d0ff --- /dev/null +++ b/htdocs/lib/User.php @@ -0,0 +1,205 @@ +email))); + } + + /** + * Save the user as a new user. + * + * @return boolean + */ + public function save() + { + global $DB; + $query = kl_str_sql('INSERT INTO users( + name, + email, + login, + password, + is_admin, + is_allowed + ) VALUES (!s,!s,!s,!s,!i,!i)', + $this->name, + $this->email, + $this->login, + $this->password, + $this->is_admin, + $this->is_allowed + ); + + if (!$res = $DB->query($query)) { + return false; + } else { + $this->user_id = $DB->insertID(); + return $this->user_id; + } + } + + public function update() + { + global $DB; + $query = kl_str_sql(' + UPDATE users SET + name=!s, + email=!s, + login=!s, + password=!s, + is_admin=!i, + is_allowed=!i + WHERE user_id=!i', + $this->name, + $this->email, + $this->login, + $this->password, + $this->is_admin, + $this->is_allowed, + $this->user_id + ); +//echo $query; + if (!$DB->query($query)) { + return false; + } else { + return true; + } + } + + /** + * Get user by id. + * + * @param integer $id + * @return User + */ + public static function get($id) + { + global $DB; + + if (is_null($id)) { + return new User(); + } + + $query = kl_str_sql("SELECT * FROM users WHERE user_id=!i",$id); + + if(!$res = $DB->query($query) OR !$row = $DB->fetchRow($res)) { + return false; + } else { + $user = new User(); + $DB->loadFromDbRow($user, $res, $row); + return $user; + } + } + + public function isAnonymous() + { + return !$this->user_id; + } + + public function isAdmin() + { + return $this->is_admin; + } + + public function isAllowed() + { + return $this->is_allowed; + } + /** + * Get a user by username. + * + * @param string $username + * @return User + */ + public static function getByLogin($username) + { + global $DB; + + $query = kl_str_sql('SELECT * FROM users WHERE login=!s', $username); + + if(!$res = $DB->query($query) OR !$row = $DB->fetchRow($res)) { + return false; + } else { + $user = new User(); + $DB->loadFromDbRow($user, $res, $row); + return $user; + } + } + + /** + * Get an user by email. + * + * @param string $email + * @return User + */ + public static function getByEmail($email) + { + global $DB; + + $query = kl_str_sql('SELECT * FROM users WHERE cust_id!=1 AND email=!s', $email); + if (!$res = $DB->query($query) OR !$row = $DB->fetchRow($res)) { + return false; + } else { + $user = new User(); + $DB->loadFromDbRow($user, $res, $row); + return $user; + } + } + + /** + * Get all users. + * + * @return array Array of User + */ + public static function getAll() + { + global $DB; + + $query = kl_str_sql('SELECT * FROM users ORDER BY is_admin DESC, is_allowed DESC, user_id ASC'); + if(!$res = $DB->query($query)) { + return false; + } else { + $retval=array(); + while($row = $DB->fetchRow($res)) { + $tmp = new User(); + $DB->loadFromDbRow($tmp, $res, $row); + $retval[] = $tmp; + } + return $retval; + } + } + + function delete(){ + global $DB; + $query=kl_str_sql("DELETE FROM users WHERE user_id=!i",$this->user_id); + //echo $query; + if(!$res=$DB->query($query)){ + return false; + } + else{ + return true; + } + } + + public function setImportArray($array) + { + $this->name = $array['name']; + $this->email = $array['email']; + $this->login = $array['login']; + $this->password = $array['password']; + $this->is_admin = $array['is_admin']; + $this->is_allowed = $array['is_allowed']; + } +} +?> diff --git a/htdocs/lib/db_init.sql b/htdocs/lib/db_init.sql new file mode 100644 index 0000000..a5e7fe5 --- /dev/null +++ b/htdocs/lib/db_init.sql @@ -0,0 +1,34 @@ +drop table if exists raw_reports; +drop table if exists users; +drop table if exists options; +drop table if exists session; + +create table session( + sid varchar(128) not null primary key, + user_id integer, + authenticated tinyint, + expires datetime, + persist text +); + +create table options( + name varchar(128), + value text +); + +create table users( + user_id integer not null auto_increment primary key, + name varchar(255), + email varchar(255), + login varchar(255), + password varchar(255), + is_admin tinyint, + is_allowed tinyint +); + +create table raw_reports( + report_id integer not null auto_increment primary key, + reported timestamp not null default current_timestamp, + processed integer not null default 0, + raw_data longtext +); \ No newline at end of file diff --git a/htdocs/lib/ext_kl.php b/htdocs/lib/ext_kl.php new file mode 100644 index 0000000..a0ef17c --- /dev/null +++ b/htdocs/lib/ext_kl.php @@ -0,0 +1,115 @@ + "Y-m-d", + "str_sql_datetime_format" => "Y-m-d H:i:s", + "str_sql_quote_func" => "mysql_real_escape_string"); +/** + * This method validates the sql insert string must always be used in conjunction with an insert to the db + * @return string + */ + +function kl_str_sql() +{ + GLOBAL $php_str_sql_options_array; + $f = $php_str_sql_options_array['str_sql_quote_func']; + + $narg = func_num_args(); + $args = func_get_args(); + + if ($narg<1) { + trigger_error("At least one parameter required", E_USER_WARNING); + return ""; + } + + $offset = 0; + $flen = strlen($args[0]); + $res = ""; + $narg = 1; + + while ($offset < $flen) { + if (false !== ($pos = strpos($args[0],"!", $offset))) { + + $res .= substr($args[0], $offset, $pos-$offset); + + switch ($args[0][$pos+1]) { + + case 's': + if (is_null($args[$narg])) { + $res .= 'NULL'; + } else { + $res .= "'".$f($args[$narg])."'"; + } + $narg++; + break; + + + case 'b': + if (is_null($args[$narg])) { + $res .= 'NULL'; + } else { + $res .= "'".$f($args[$narg])."'"; + } + $narg++; + break; + + case 'i': + if (is_null($args[$narg])) { + $res .= 'NULL'; + } else { + $res .= (int)($args[$narg]); + } + $narg++; + break; + + case 'f': + if (is_null($args[$narg])) { + $res .= 'NULL'; + } else { + $res .= (double)($args[$narg]); + } + $narg++; + break; + + case 'd': + if (!($args[$narg])) { + $res .= 'NULL'; + } else { + $res .= "'".date($php_str_sql_options_array['str_sql_date_format'], $args[$narg])."'"; + } + $narg++; + break; + + case 't': + if (!($args[$narg])) { + $res .= 'NULL'; + } else { + $res .= "'".date($php_str_sql_options_array['str_sql_datetime_format'], $args[$narg])."'"; + } + $narg++; + break; + + + default: + $res .= "!".$args[0][$pos+1]; + } + $offset = $pos + 2; + } else { + $res .= substr($args[0], $offset); + $offset = $flen; + } + } + + return $res; + +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/htdocs/lib/http.php b/htdocs/lib/http.php new file mode 100644 index 0000000..dd02d7e --- /dev/null +++ b/htdocs/lib/http.php @@ -0,0 +1,132 @@ + +* @copyright Copyright © 2012, Latif Khalifa +* +* Permission is hereby granted, free of charge, to any person obtaining +* a copy of this software and associated documentation files +* (the "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to permit +* persons to whom the Software is furnished to do so, subject to the +* following conditions: +* +* - The above copyright notice and this permission notice shall be included +* in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +*/ + +class http +{ + public static function redirectAbsolute($url) + { + Header("Location: $url"); + Header("HTTP/1.0 302 Found"); + print " + +302 Found + +

Found

+The document was found here.

+ +"; + exit; + } + + + public static function redirect($url) + { + http::redirectAbsolute(URL_ROOT . $url); + + } + + public static function notFound() + { + header("HTTP/1.1 404 Not Found"); + print " + +404 Not Found + +

Not Found

+The requested URL was not found on this server.

+


+
{$_SERVER['SERVER_SIGNATURE']}
+ +"; + exit; + } + + public static function notAllowed() + { + header("HTTP/1.1 405 Method Not Allowed"); + print " + +405 Method Not Allowed + +

Method Not Allowed

+The requested HTTP method is not allowed for this URL.

+


+
{$_SERVER['SERVER_SIGNATURE']}
+ +"; + exit; + } + + public static function notModified() + { + header("HTTP/1.1 304 Not Modified"); + print " + +304 Not Modified + +

Not Modified

+The requested URL has not been modified.

+


+
{$_SERVER['SERVER_SIGNATURE']}
+ +"; + exit; + } + + public static function noCache() + { + header("Expires: Mon, 21 Jan 1980 06:01:01 GMT"); + header("Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0"); + header("Pragma: no-cache"); + } + + /** + * Send data for download. + * + * @param string $filename + * @param string $data + */ + public static function sendDownload($filename, $data) + { + header('Content-disposition: attachment; filename=' . urlencode($filename)); + echo $data; + } + +} +/* +* Local variables: +* tab-width: 4 +* c-basic-offset: 4 +* End: +* vim600: sw=4 ts=4 fdm=marker +* vim<600: sw=4 ts=4 +*/ +?> diff --git a/htdocs/lib/init.php b/htdocs/lib/init.php new file mode 100644 index 0000000..e8ebd87 --- /dev/null +++ b/htdocs/lib/init.php @@ -0,0 +1,88 @@ + 0 ? true:false; + define('USE_SSL', $init_ssl); + + $init_url = $init_ssl ? "https://" : "http://"; + + if ($init_ssl && $_SERVER['PORT']!=443) { + $init_port = $_SERVER['PORT']; + } + + if (!$init_ssl && $_SERVER['PORT']!=80) { + $init_port = $_SERVER['PORT']; + } + + $init_url .= $_SERVER['HTTP_HOST']; + + if ($init_port) { + $init_url .= ":" . $init_port; + } + + if (defined('REL_DIR') && strlen(REL_DIR)) { + $init_url .= '/' . REL_DIR; + } + + define ('URL_ROOT', $init_url); +} + +if (!defined('IMG_ROOT')) { + define('IMG_ROOT', URL_ROOT . '/images'); +} + + +$DB = new DBH(); + +$DB_NAME = "singucrash"; +$DB_USER = 'singucrash'; +$DB_PASS = '-*-secrit-*-'; +$DB_HOST = 'localhost'; + +if (!$DB->connect($DB_NAME, $DB_HOST, $DB_USER, $DB_PASS)) { + echo "System is down for mantainence. Please try again later."; + die(); +} + +Option::init(); + +$S = new Session(); +if (!defined('NO_SESSION') && PHP_SAPI != "cli") { + $S->check(); +} + + +/* Prevent XSS attacks via PHP_SELF */ +$_SERVER['PHP_SELF'] = htmlspecialchars($_SERVER['PHP_SELF']); +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ +?> \ No newline at end of file diff --git a/htdocs/login.php b/htdocs/login.php new file mode 100644 index 0000000..ffbfcda --- /dev/null +++ b/htdocs/login.php @@ -0,0 +1,38 @@ + + +

Please use your Google Account to login.

+ +Login Failed"; + +Layout::footer(); + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/htdocs/logout.php b/htdocs/logout.php new file mode 100644 index 0000000..0f35e4c --- /dev/null +++ b/htdocs/logout.php @@ -0,0 +1,15 @@ +remove(); +http::redirect("/"); + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/htdocs/process_login.php b/htdocs/process_login.php new file mode 100644 index 0000000..94f169c --- /dev/null +++ b/htdocs/process_login.php @@ -0,0 +1,49 @@ +success();//true or false +if (!$success) +{ + http::redirect("/login_failed.php"); +} + +$user_identity = $google_response->identity();//the user's ID +$user_email = $google_response->email();//the user's email + +$user = User::getByLogin($user_identity); + +if (!$user) +{ + $user = new User(); + $user->email = $user_email; + $user->is_admin = 0; + $user->is_allowed = 0; + $user->login = $user_identity; + if (!$user->save()) + { + http::redirect("/login_failed.php"); + } +} + +$S->user = $user; +$S->user_id = $user->user_id; +$S->authenticated = 1; +$S->update(); + +if (isset($_GET['caller'])) +{ + http::redirect($_GET['caller']); +} +http::redirect('/'); + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/htdocs/report.php b/htdocs/report.php new file mode 100644 index 0000000..08af7f3 --- /dev/null +++ b/htdocs/report.php @@ -0,0 +1,32 @@ +query($query)) + { + $report_id = $DB->insertID(); + } +} + + +header("Content-Type: application/llsd+xml"); +print 'messageReport saved with report_id=' . $report_id . 'successtrue'; +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/htdocs/singularity.css b/htdocs/singularity.css new file mode 100644 index 0000000..7c93d48 --- /dev/null +++ b/htdocs/singularity.css @@ -0,0 +1,109 @@ +html { + padding: 0; + margin: 0; + background-color: #1e1e1e; +} + +image { + border: none; +} + +body, select { + padding: 0; + margin: 0; + background-color: #1e1e1e; + font: normal 10pt "Lucida Grande","Lucida Sans Unicode",sans-serif; + color: #a0a0a0; +} + +div { + display: block; + padding: 0; + margin: 0; +} + +#everything { + background: url(images/body-bg.png) top repeat-x; + min-height: 100%; +} + +#page-wrapper { + margin: 0 auto; + width: 960px; +} + +#header { + margin-bottom: 10px; + width: 100%; + height: 144px; + background: url(images/singularity_logo.gif) left top no-repeat; + border-bottom: solid 2px black; +} + +.container { + padding: 5px; + margin-bottom: 10px; + background: url(images/container-bg.gif) top repeat-x; + border-bottom: solid 2px black; + background-color: #1b1b1b; +} + +input { + color: #a0a0a0; + background-color: #2C3737; + border: none; + border-bottom: solid 1px black; + padding: 2px 6px 2px 6px; +} + +input:hover { + background-color: #445A5E; +} + +a, a:link, a:hover, a:visited, a:active { + text-decoration: none; + color: #e0e0e0; +} + +.bottom-links { + text-align: center; +} + +td, th { + text-align: left; + margin: 0; + padding: 3px 20px 3px 3px; + vertical-align: top; + display: table-cell; + border: none; +} + +th { + border-bottom: solid 1px #404040; + background: url(images/container-bg.gif) top repeat-x; +} + +tr.rowhighlight:hover { + background-color: #2C3737; +} + +.menuitem { + float: right; + padding: 3px 15px 3px 15px; + margin-top: 15px; + margin-left: 2px; + background-color: #2C3737; + border-bottom: 1px solid #101010; + /* border: 1px solid red; */ +} + +.menuitem:hover { + background-color: #445A5E; +} + +pre { + font-family: monospace; + font-size: 8pt; + white-space: pre-wrap; + word-wrap: break-word; +} \ No newline at end of file diff --git a/htdocs/statistics.php b/htdocs/statistics.php new file mode 100644 index 0000000..3c95677 --- /dev/null +++ b/htdocs/statistics.php @@ -0,0 +1,23 @@ +requireUser(); + +Layout::header(); +?> + +Not implemented + +requireAdmin(); +$users = User::getAll(); + +if (isset($_REQUEST["action"])) +{ + $action = $_REQUEST["action"]; + $user_id = (int)$_REQUEST["id"]; + $user = User::get($user_id); + if ($user && !$user->isAdmin()) + { + switch ($action) + { + case "grant": + $user->is_allowed = 1; + $user->update(); + break; + + case "revoke": + $user->is_allowed = 0; + $user->update(); + break; + + case "remove": + $user->delete(); + break; + } + } + http::redirect("/users.php"); +} + +Layout::header(); +?> + +

User Accounts

+ + + + + + + + + + + + + + + + + +
IDNameEmailAccessActions
user_id ?>name); ?>email); ?>isAdmin() ? "Admin" : ($users[$i]->isAllowed() ? "Granted" : "No"); ?> +isAdmin()) + { + $action = $users[$i]->isAllowed() ? "revoke" : "grant"; + $url = URL_ROOT . "/users.php?action=$action&id=" . (int)$users[$i]->user_id; + $delete = URL_ROOT . "/users.php?action=remove&id=" . (int)$users[$i]->user_id; + print "{$action} access,  "; + print "delete account"; + } +?> +
+