From abd38899f6f1fcf7c8f16eb83356c8ce1070cf71 Mon Sep 17 00:00:00 2001 From: dakkar Date: Thu, 17 Mar 2011 00:01:25 +0000 Subject: import from http://tobyelliott.wordpress.com/2009/09/11/weave-minimal-server/ --- .gitignore | 2 + README | 19 ++ create_user | 78 +++++ index.php | 503 +++++++++++++++++++++++++++++++ weave_basic_object.php | 241 +++++++++++++++ weave_storage.php | 787 +++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 1630 insertions(+) create mode 100644 .gitignore create mode 100644 README create mode 100644 create_user create mode 100644 index.php create mode 100644 weave_basic_object.php create mode 100644 weave_storage.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..727dad6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/weave_db + diff --git a/README b/README new file mode 100644 index 0000000..4c3a9f4 --- /dev/null +++ b/README @@ -0,0 +1,19 @@ +SERVER SETUP + +Add the following line to your apache config: + +Alias /weave //index.php + +Restart your apache server. Point your browser at https://localhost/weave/1.0/blah/info/collection. +Enter "blah" for the username and garbage for the pwd. Auth will fail, but it will +create the db (you can cancel the subsequent request for auth). You should now see a file +called weave_db in the directory. + +You can create and delete users by running the create_user script. + +CLIENT SETUP + +in about.config, set extensions.weave.serverURL to https:///weave/ + +You can run it under http, but this is insecure and not recommended. + diff --git a/create_user b/create_user new file mode 100644 index 0000000..f912c6c --- /dev/null +++ b/create_user @@ -0,0 +1,78 @@ +create_user($name, $pwd)) + echo "$name created\n"; + else + echo "An error occured\n"; + break; + case 'd': + fwrite(STDOUT, "Please enter username: "); + $name = trim(fgets(STDIN)); + if ($db->delete_user($name)) + echo "$name deleted\n"; + else + echo "An error occured\n"; + break; + case 'p': + fwrite(STDOUT, "Please enter username: "); + $name = trim(fgets(STDIN)); + fwrite(STDOUT, "Please enter new password: "); + $password = trim(fgets(STDIN)); + if ($db->change_password($name,$password)) + echo "password changed for $name\n"; + else + echo "An error occured\n"; + break; + default: + echo "unknown command"; + } + +?> \ No newline at end of file diff --git a/index.php b/index.php new file mode 100644 index 0000000..d8da439 --- /dev/null +++ b/index.php @@ -0,0 +1,503 @@ + '400 Bad Request', + '401' => '401 Unauthorized', + '404' => '404 Not Found', + '412' => '412 Precondition Failed', + '503' => '503 Service Unavailable'); + header('HTTP/1.1 ' . $headers{$code},true,$code); + + if ($code == 401) + { + header('WWW-Authenticate: Basic realm="Weave"'); + } + + exit(json_encode($message)); + } + + + header("Content-type: application/json"); + + + #get the http auth user data + + $auth_user = array_key_exists('PHP_AUTH_USER', $_SERVER) ? $_SERVER['PHP_AUTH_USER'] : null; + $auth_pw = array_key_exists('PHP_AUTH_PW', $_SERVER) ? $_SERVER['PHP_AUTH_PW'] : null; + + if (is_null($auth_user) || is_null($auth_pw)) + { + /* CGI/FCGI auth workarounds */ + $auth_str = null; + if (array_key_exists('Authorization', $_SERVER)) + /* Standard fastcgi configuration */ + $auth_str = $_SERVER['Authorization']; + else if (array_key_exists('AUTHORIZATION', $_SERVER)) + /* Alternate fastcgi configuration */ + $auth_str = $_SERVER['AUTHORIZATION']; + else if (array_key_exists('HTTP_AUTHORIZATION', $_SERVER)) + /* IIS/ISAPI and newer (yet to be released) fastcgi */ + $auth_str = $_SERVER['HTTP_AUTHORIZATION']; + else if (array_key_exists('REDIRECT_HTTP_AUTHORIZATION', $_SERVER)) + /* mod_rewrite - per-directory internal redirect */ + $auth_str = $_SERVER['REDIRECT_HTTP_AUTHORIZATION']; + if (!is_null($auth_str)) + { + /* Basic base64 auth string */ + if (preg_match('/Basic\s+(.*)$/', $auth_str)) + { + $auth_str = substr($auth_str, 6); + $auth_str = base64_decode($auth_str, true); + if ($auth_str != FALSE) { + $tmp = explode(':', $auth_str); + if (count($tmp) == 2) + { + $auth_user = $tmp[0]; + $auth_pw = $tmp[1]; + } + } + } + } + } + + $server_time = round(microtime(1), 2); + header("X-Weave-Timestamp: " . $server_time); + + #Basic path extraction and validation. No point in going on if these are missing + $path = '/'; + if (!empty($_SERVER['PATH_INFO'])) { + $path = $_SERVER['PATH_INFO']; + } + else if (!empty($_SERVER['ORIG_PATH_INFO'])) { + $path = $_SERVER['ORIG_PATH_INFO']; + } + $path = substr($path, 1); #chop the lead slash + list($version, $username, $function, $collection, $id) = explode('/', $path.'//'); + + if ($version != '0.5' && $version != '1.0') + report_problem('Function not found', 404); + + # Lowercase username before checking path + $username = strtolower($username); + $auth_user = strtolower($auth_user); + + if (!$username) + report_problem(3, 400); + + if ($auth_user != $username) + report_problem(5, 401); + + #quick check to make sure that any non-storage function calls are just using GET + if ($function != 'storage' && $_SERVER['REQUEST_METHOD'] != 'GET') + report_problem(1, 400); + + #only a get has meaning without a collection (GET returns a collection list) + if (!$collection && $_SERVER['REQUEST_METHOD'] != 'GET') + report_problem(1, 400); + + #storage requires a collection to have been passed in. Info requires a directive + if (!$collection) + report_problem(1, 400); + + #hook up with the db + try + { + $db = new WeaveStorage($username); + } + catch(Exception $e) + { + report_problem($e->getMessage(), $e->getCode()); + } + + #Auth the user + try + { + if (!$db->authenticate_user($auth_pw)) + report_problem('Authentication failed', '401'); + } + catch(Exception $e) + { + report_problem($e->getMessage(), $e->getCode()); + } + + #user passes, onto actually getting the data + if ($_SERVER['REQUEST_METHOD'] == 'GET') + { + if ($function == 'info') + { + switch ($collection) + { + case 'quota': + exit(json_encode(array($db->get_storage_total()))); + case 'collections': + exit(json_encode($db->get_collection_list_with_timestamps())); + case 'collection_counts': + exit(json_encode($db->get_collection_list_with_counts())); + default: + report_problem(1, 400); + } + } + elseif ($function == 'storage') + { + if ($id) #retrieve a single record + { + try + { + $wbo = $db->retrieve_objects($collection, $id, 1); #get the full contents of one record + } + catch(Exception $e) + { + report_problem($e->getMessage(), $e->getCode()); + } + + if (count($wbo) > 0) + echo $wbo[0]->json(); + else + report_problem("record not found", 404); + } + else #retrieve a batch of records. Sadly, due to potential record sizes, have the storage object stream the output... + { + $full = array_key_exists('full', $_GET) ? $_GET['full'] : null; + $outputter = new WBOJsonOutput($full); + if (array_key_exists('HTTP_ACCEPT', $_SERVER) + && !preg_match('/\*\/\*/', $_SERVER['HTTP_ACCEPT']) + && !preg_match('/application\/json/', $_SERVER['HTTP_ACCEPT'])) + { + if (preg_match('/application\/whoisi/', $_SERVER['HTTP_ACCEPT'])) + { + header("Content-type: application/whoisi"); + $outputter->set_format('whoisi'); + } + elseif (preg_match('/application\/newlines/', $_SERVER['HTTP_ACCEPT'])) + { + header("Content-type: application/newlines"); + $outputter->set_format('newlines'); + } + + } + + try + { + $ids = $db->retrieve_objects($collection, null, $full, $outputter, + array_key_exists('parentid', $_GET) ? $_GET['parentid'] : null, + array_key_exists('predecessorid', $_GET) ? $_GET['predecessorid'] : null, + array_key_exists('newer', $_GET) ? $_GET['newer'] : null, + array_key_exists('older', $_GET) ? $_GET['older'] : null, + array_key_exists('sort', $_GET) ? $_GET['sort'] : null, + array_key_exists('limit', $_GET) ? $_GET['limit'] : null, + array_key_exists('offset', $_GET) ? $_GET['offset'] : null, + array_key_exists('ids', $_GET) ? explode(',', $_GET['ids']) : null, + array_key_exists('index_above', $_GET) ? $_GET['index_above'] : null, + array_key_exists('index_below', $_GET) ? $_GET['index_below'] : null, + array_key_exists('depth', $_GET) ? $_GET['depth'] : null + ); + } + catch(Exception $e) + { + report_problem($e->getMessage(), $e->getCode()); + } + } + } + } + else if ($_SERVER['REQUEST_METHOD'] == 'PUT') #add a single record to the server + { + $putdata = fopen("php://input", "r"); + $json = ''; + while ($data = fread($putdata,2048)) {$json .= $data;}; + + $wbo = new wbo(); + if (!$wbo->extract_json($json)) + report_problem(6, 400); + + if (array_key_exists('HTTP_X_IF_UNMODIFIED_SINCE', $_SERVER) + && $db->get_max_timestamp($collection) > round((float)$_SERVER['HTTP_X_IF_UNMODIFIED_SINCE'], 2)) + report_problem(4, 412); + + #use the url if the json object doesn't have an id + if (!$wbo->id() && $id) { $wbo->id($id); } + + $wbo->collection($collection); + $wbo->modified($server_time); #current microtime + + if ($wbo->validate()) + { + try + { + #if there's no payload (as opposed to blank), then update the metadata + if ($wbo->payload_exists()) + $db->store_object($wbo); + else + $db->update_object($wbo); + } + catch (Exception $e) + { + report_problem($e->getMessage(), $e->getCode()); + } + echo json_encode($wbo->modified()); + } + else + { + report_problem(8, 400); + } + } + else if ($_SERVER['REQUEST_METHOD'] == 'POST') + { + #stupid php being helpful with input data... + $putdata = fopen("php://input", "r"); + $jsonstring = ''; + while ($data = fread($putdata,2048)) {$jsonstring .= $data;} + $json = json_decode($jsonstring, true); + + if ($json === null) + report_problem(6, 400); + + if (array_key_exists('HTTP_X_IF_UNMODIFIED_SINCE', $_SERVER) + && $db->get_max_timestamp($collection) > round((float)$_SERVER['HTTP_X_IF_UNMODIFIED_SINCE'], 2)) + report_problem(4, 412); + + + $success_ids = array(); + $failed_ids = array(); + + + try + { + $db->begin_transaction(); + } + catch(Exception $e) + { + report_problem($e->getMessage(), $e->getCode()); + } + + foreach ($json as $wbo_data) + { + $wbo = new wbo(); + if (!$wbo->extract_json($wbo_data)) + { + $failed_ids[$wbo->id()] = $wbo->get_error(); + continue; + } + + $wbo->collection($collection); + $wbo->modified($server_time); + + + if ($wbo->validate()) + { + try + { + #if there's no payload (as opposed to blank), then update the metadata + if ($wbo->payload_exists()) + { + $db->store_object($wbo); + } + else + { + $db->update_object($wbo); + } + } + catch (Exception $e) + { + $failed_ids[$wbo->id()] = $e->getMessage(); + continue; + } + $success_ids[] = $wbo->id(); + } + else + { + $failed_ids[$wbo->id()] = $wbo->get_error(); + } + } + $db->commit_transaction(); + + echo json_encode(array('success' => $success_ids, 'failed' => $failed_ids)); + } + else if ($_SERVER['REQUEST_METHOD'] == 'DELETE') + { + if (array_key_exists('HTTP_X_IF_UNMODIFIED_SINCE', $_SERVER) + && $db->get_max_timestamp($collection) > round((float)$_SERVER['HTTP_X_IF_UNMODIFIED_SINCE'], 2)) + report_problem(4, 412); + + $timestamp = round(microtime(1), 2); + if ($id) + { + try + { + $db->delete_object($collection, $id); + } + catch(Exception $e) + { + report_problem($e->getMessage(), $e->getCode()); + } + echo json_encode($timestamp); + } + else + { + try + { + $db->delete_objects($collection, null, + array_key_exists('parentid', $_GET) ? $_GET['parentid'] : null, + array_key_exists('predecessorid', $_GET) ? $_GET['predecessorid'] : null, + array_key_exists('newer', $_GET) ? $_GET['newer'] : null, + array_key_exists('older', $_GET) ? $_GET['older'] : null, + array_key_exists('sort', $_GET) ? $_GET['sort'] : null, + array_key_exists('limit', $_GET) ? $_GET['limit'] : null, + array_key_exists('offset', $_GET) ? $_GET['offset'] : null, + array_key_exists('ids', $_GET) ? explode(',', $_GET['ids']) : null, + array_key_exists('index_above', $_GET) ? $_GET['index_above'] : null, + array_key_exists('index_below', $_GET) ? $_GET['index_below'] : null + ); + } + catch(Exception $e) + { + report_problem($e->getMessage(), $e->getCode()); + } + echo json_encode($timestamp); + } + } + else + { + #bad protocol. There are protocols left? HEAD, I guess. + report_problem(1, 400); + } + + +#The datasets we might be dealing with here are too large for sticking it all into an array, so +#we need to define a direct-output method for the storage class to use. If we start producing multiples +#(unlikely), we can put them in their own class. + +class WBOJsonOutput +{ + private $_full = null; + private $_comma_flag = 0; + private $_output_format = 'json'; + + function __construct ($full = null) + { + $this->_full = $full; + } + + function set_format($format) + { + $this->_output_format = $format; + } + + + function output($sth) + { + if (($rowcount = $sth->rowCount()) > 0) + { + header('X-Weave-Records: ' . $rowcount); + } + if ($this->_output_format == 'newlines') + { + return $this->output_newlines($sth); + } + elseif ($this->_output_format == 'whoisi') + { + return $this->output_whoisi($sth); + } + else + { + return $this->output_json($sth); + } + } + + function output_json($sth) + { + echo '['; + + while ($result = $sth->fetch(PDO::FETCH_ASSOC)) + { + if ($this->_comma_flag) { echo ','; } else { $this->_comma_flag = 1; } + if ($this->_full) + { + $wbo = new wbo(); + $wbo->populate($result); + echo $wbo->json(); + } + else + echo json_encode($result{'id'}); + } + + echo ']'; + return 1; + } + + function output_whoisi($sth) + { + while ($result = $sth->fetch(PDO::FETCH_ASSOC)) + { + if ($this->_full) + { + $wbo = new wbo(); + $wbo->populate($result); + $output = $wbo->json(); + } + else + $output = json_encode($result{'id'}); + echo pack('N', mb_strlen($output, '8bit')) . $output; + } + return 1; + } + + function output_newlines($sth) + { + while ($result = $sth->fetch(PDO::FETCH_ASSOC)) + { + if ($this->_full) + { + $wbo = new wbo(); + $wbo->populate($result); + echo preg_replace('/\n/', '\u000a', $wbo->json()); + } + else + echo json_encode($result{'id'}); + echo "\n"; + } + return 1; + } +} +?> diff --git a/weave_basic_object.php b/weave_basic_object.php new file mode 100644 index 0000000..ef30c69 --- /dev/null +++ b/weave_basic_object.php @@ -0,0 +1,241 @@ +_error[] = "unable to extract from json"; + return 0; + } + + #must have an id, or all sorts of badness happens. However, it can be added later + if (array_key_exists('id', $extracted)) + { + $this->id($extracted['id']); + } + + if (array_key_exists('parentid', $extracted)) + { + $this->parentid($extracted['parentid']); + } + + if (array_key_exists('predecessorid', $extracted)) + { + $this->predecessorid($extracted['predecessorid']); + } + + if (array_key_exists('depth', $extracted)) + { + $this->depth($extracted['depth']); + } + + if (array_key_exists('sortindex', $extracted)) + { + $this->sortindex($extracted['sortindex']); + } + + if (array_key_exists('payload', $extracted)) + { + $this->payload($extracted['payload']); + } + return 1; + } + + function populate(&$datahash) + { + $this->id($datahash['id']); + $this->collection($datahash['collection']); + $this->parentid($datahash['parentid']); + $this->modified($datahash['modified']); + $this->predecessorid($datahash['predecessorid']); + $this->sortindex($datahash['sortindex']); + $this->payload($datahash['payload']); + + $this->depth($datahash['depth']); + } + + function id($id = null) + { + if (!is_null($id)) { $this->wbo_hash['id'] = $id; } + return array_key_exists('id', $this->wbo_hash) ? $this->wbo_hash['id'] : null; + } + + function collection($collection = null) + { + if (!is_null($collection)){ $this->_collection = $collection; } + return $this->_collection; + } + + function parentid($parentid = null) + { + if (!is_null($parentid)){ $this->wbo_hash['parentid'] = $parentid; } + return array_key_exists('parentid', $this->wbo_hash) ? $this->wbo_hash['parentid'] : null; + } + + function parentid_exists() + { + return array_key_exists('parentid', $this->wbo_hash); + } + + function predecessorid($predecessorid = null) + { + if (!is_null($predecessorid)){ $this->wbo_hash['predecessorid'] = $predecessorid; } + return array_key_exists('predecessorid', $this->wbo_hash) ? $this->wbo_hash['predecessorid'] : null; + } + + function predecessorid_exists() + { + return array_key_exists('predecessorid', $this->wbo_hash); + } + + function modified($modified = null) + { + if (!is_null($modified)){ $this->wbo_hash['modified'] = round((float)$modified, 2); } + return array_key_exists('modified', $this->wbo_hash) ? $this->wbo_hash['modified'] : null; + } + + function modified_exists() + { + return array_key_exists('modified', $this->wbo_hash); + } + + function payload($payload = null) + { + if (!is_null($payload)){ $this->wbo_hash['payload'] = $payload; } + return array_key_exists('payload', $this->wbo_hash) ? $this->wbo_hash['payload'] : null; + } + + function payload_exists() + { + return array_key_exists('payload', $this->wbo_hash); + } + + function payload_size() + { + return mb_strlen($this->wbo_hash['payload'], '8bit'); + } + + function sortindex($index = null) + { + if (!is_null($index)){ $this->wbo_hash['sortindex'] = (int)$index; } + return array_key_exists('sortindex', $this->wbo_hash) ? $this->wbo_hash['sortindex'] : null; + } + + function sortindex_exists() + { + return array_key_exists('sortindex', $this->wbo_hash); + } + + function depth($depth = null) + { + if (!is_null($depth)){ $this->wbo_hash['depth'] = (int)$depth; } + return array_key_exists('depth', $this->wbo_hash) ? $this->wbo_hash['depth'] : null; + } + + function depth_exists() + { + return array_key_exists('depth', $this->wbo_hash); + } + + + function validate() + { + + if (!$this->id() || strlen($this->id()) > 64) + { $this->_error[] = "invalid id"; } + + if ($this->parentid_exists() && strlen($this->parentid()) > 64) + { $this->_error[] = "invalid parentid"; } + + if ($this->predecessorid_exists() && strlen($this->predecessorid()) > 64) + { $this->_error[] = "invalid predecessorid"; } + + if (!is_numeric($this->modified())) + { $this->_error[] = "invalid modified date"; } + + if (!$this->modified()) + { $this->_error[] = "no modification date"; } + + if (!$this->_collection || strlen($this->_collection) > 64) + { $this->_error[] = "invalid collection"; } + + if ($this->depth_exists() && !is_numeric($this->depth())) + { $this->_error[] = "invalid depth"; } + + if ($this->sortindex_exists() && !is_numeric($this->sortindex())) + { $this->_error[] = "invalid sortindex"; } + + if ($this->payload_exists()) + { + if (!is_string($this->wbo_hash['payload'])) + { $this->_error[] = "payload needs to be json-encoded"; } + } + + return !$this->get_error(); + } + + function get_error() + { + return $this->_error; + } + + function clear_error() + { + $this->_error = array(); + } + + function json() + { + return json_encode($this->wbo_hash); + } +} + + +?> \ No newline at end of file diff --git a/weave_storage.php b/weave_storage.php new file mode 100644 index 0000000..c82873d --- /dev/null +++ b/weave_storage.php @@ -0,0 +1,787 @@ +_username = $username; + $path = explode('/', $_SERVER['SCRIPT_FILENAME']); + $db_name = 'weave_db'; + array_pop($path); + array_push($path, $db_name); + $db_name = implode('/', $path); + + $create_tables = !file_exists($db_name); + + try + { + $this->_dbh = new PDO('sqlite:' . $db_name); + $this->_dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + } + catch( PDOException $exception ) + { + throw new Exception("Database unavailable", 503); + } + + if ($create_tables) + $this->setup_db(); + } + + function get_connection() + { + return $this->_dbh; + } + + function begin_transaction() + { + try + { + $this->_dbh->beginTransaction(); + } + catch( PDOException $exception ) + { + error_log("begin_transaction: " . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + return 1; + } + + function commit_transaction() + { + $this->_dbh->commit(); + return 1; + } + + function get_max_timestamp($collection) + { + if (!$collection) + { + return 0; + } + + try + { + $select_stmt = 'select max(modified) from wbo where username = :username and collection = :collection'; + $sth = $this->_dbh->prepare($select_stmt); + $sth->bindParam(':username', $this->_username); + $sth->bindParam(':collection', $collection); + $sth->execute(); + } + catch( PDOException $exception ) + { + error_log("get_max_timestamp: " . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + + $result = $sth->fetchColumn(); + return round((float)$result, 2); + } + + function get_collection_list() + { + try + { + $select_stmt = 'select distinct(collection) from wbo where username = :username'; + $sth = $this->_dbh->prepare($select_stmt); + $sth->bindParam(':username', $this->_username); + $sth->execute(); + } + catch( PDOException $exception ) + { + error_log("get_collection_list: " . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + + + $collections = array(); + while ($result = $sth->fetchColumn()) + { + $collections[] = $result; + } + + return $collections; + } + + + function get_collection_list_with_timestamps() + { + try + { + $select_stmt = 'select collection, max(modified) as timestamp from wbo where username = :username group by collection'; + $sth = $this->_dbh->prepare($select_stmt); + $sth->bindParam(':username', $this->_username); + $sth->execute(); + } + catch( PDOException $exception ) + { + error_log("get_collection_list: " . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + + $collections = array(); + while ($result = $sth->fetch(PDO::FETCH_NUM)) + { + $collections[$result[0]] = (float)$result[1]; + } + + return $collections; + } + + function get_collection_list_with_counts() + { + try + { + $select_stmt = 'select collection, count(*) as ct from wbo where username = :username group by collection'; + $sth = $this->_dbh->prepare($select_stmt); + $sth->bindParam(':username', $this->_username); + $sth->execute(); + } + catch( PDOException $exception ) + { + error_log("get_collection_list_with_counts: " . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + + + $collections = array(); + while ($result = $sth->fetch(PDO::FETCH_NUM)) + { + $collections[$result[0]] = (int)$result[1]; + } + + return $collections; + } + + function store_object(&$wbo) + { + + try + { + $insert_stmt = 'replace into wbo (username, id, collection, parentid, predecessorid, sortindex, modified, payload, payload_size, depth) + values (:username, :id, :collection, :parentid, :predecessorid, :sortindex, :modified, :payload, :payload_size, :depth)'; + $sth = $this->_dbh->prepare($insert_stmt); + $sth->bindParam(':username', $this->_username); + $sth->bindParam(':id', $wbo->id()); + $sth->bindParam(':collection', $wbo->collection()); + $sth->bindParam(':parentid', $wbo->parentid()); + $sth->bindParam(':predecessorid', $wbo->predecessorid()); + $sth->bindParam(':depth', $wbo->depth()); + $sth->bindParam(':sortindex', $wbo->sortindex()); + $sth->bindParam(':modified', $wbo->modified()); + $sth->bindParam(':payload', $wbo->payload()); + $sth->bindParam(':payload_size', $wbo->payload_size()); + + $sth->execute(); + + } + catch( PDOException $exception ) + { + error_log("store_object: " . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + return 1; + } + + + function update_object(&$wbo) + { + $update = "update wbo set "; + $params = array(); + $update_list = array(); + + #make sure we have an id and collection. No point in continuing otherwise + if (!$wbo->id() || !$wbo->collection()) + { + error_log('Trying to update without a valid id or collection!'); + return 0; + } + + if ($wbo->parentid_exists()) + { + $update_list[] = "parentid = ?"; + $params[] = $wbo->parentid(); + } + + if ($wbo->parentid_exists()) + { + $update_list[] = "predecessorid = ?"; + $params[] = $wbo->predecessorid(); + } + + if ($wbo->depth_exists()) + { + $update_list[] = "depth = ?"; + $params[] = $wbo->depth(); + } + + if ($wbo->sortindex_exists()) + { + $update_list[] = "sortindex = ?"; + $params[] = $wbo->sortindex(); + } + + if ($wbo->payload_exists()) + { + $update_list[] = "payload = ?"; + $update_list[] = "payload_size = ?"; + $params[] = $wbo->payload(); + $params[] = $wbo->payload_size(); + } + + # Don't modify the timestamp on a depth-only change + if ($wbo->parentid_exists() || $wbo->payload_exists()) + { + #better make sure we have a modified date. Should have been handled earlier + if (!$wbo->modified_exists()) + { + error_log("Called update_object with no defined timestamp. Please check"); + $wbo->modified(microtime(1)); + } + $update_list[] = "modified = ?"; + $params[] = $wbo->modified(); + + } + + + if (count($params) == 0) + { + return 0; + } + + $update .= join($update_list, ","); + + $update .= " where username = ? and collection = ? and id = ?"; + $params[] = $this->_username; + $params[] = $wbo->collection(); + $params[] = $wbo->id(); + + try + { + $sth = $this->_dbh->prepare($update); + $sth->execute($params); + } + catch( PDOException $exception ) + { + error_log("update_object: " . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + return 1; + } + + function delete_object($collection, $id) + { + try + { + $delete_stmt = 'delete from wbo where username = :username and collection = :collection and id = :id'; + $sth = $this->_dbh->prepare($delete_stmt); + $sth->bindParam(':username', $this->_username); + $sth->bindParam(':collection', $collection); + $sth->bindParam(':id', $id); + $sth->execute(); + } + catch( PDOException $exception ) + { + error_log("delete_object: " . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + return 1; + } + + function delete_objects($collection, $id = null, $parentid = null, $predecessorid = null, $newer = null, + $older = null, $sort = null, $limit = null, $offset = null, $ids = null, + $index_above = null, $index_below = null) + { + $params = array(); + $select_stmt = ''; + + if ($limit || $offset || $sort) + { + #sqlite can't do sort or limit deletes without special compiled versions + #so, we need to grab the set, then delete it manually. + + $params = $this->retrieve_objects($collection, $id, 0, 0, $parentid, $predecessorid, $newer, $older, $sort, $limit, $offset, $ids, $index_above, $index_below); + if (!count($params)) + { + return 1; #nothing to delete + } + $paramqs = array(); + $select_stmt = "delete from wbo where username = ? and collection = ? and id in (" . join(", ", array_pad($paramqs, count($params), '?')) . ")"; + array_unshift($params, $collection); + array_unshift($params, $username); + } + else + { + + $select_stmt = "delete from wbo where username = ? and collection = ?"; + $params[] = $this->_username; + $params[] = $collection; + + + if ($id) + { + $select_stmt .= " and id = ?"; + $params[] = $id; + } + + if ($ids && count($ids) > 0) + { + $qmarks = array(); + $select_stmt .= " and id in ("; + foreach ($ids as $temp) + { + $params[] = $temp; + $qmarks[] = '?'; + } + $select_stmt .= implode(",", $qmarks); + $select_stmt .= ')'; + } + + if ($parentid) + { + $select_stmt .= " and parentid = ?"; + $params[] = $parentid; + } + + if ($predecessorid) + { + $select_stmt .= " and predecessorid = ?"; + $params[] = $parentid; + } + + if ($index_above) + { + $select_stmt .= " and sortindex > ?"; + $params[] = $parentid; + } + + if ($index_below) + { + $select_stmt .= " and sortindex < ?"; + $params[] = $parentid; + } + + if ($newer) + { + $select_stmt .= " and modified > ?"; + $params[] = $newer; + } + + if ($older) + { + $select_stmt .= " and modified < ?"; + $params[] = $older; + } + + if ($sort == 'index') + { + $select_stmt .= " order by sortindex desc"; + } + else if ($sort == 'newest') + { + $select_stmt .= " order by modified desc"; + } + else if ($sort == 'oldest') + { + $select_stmt .= " order by modified"; + } + + if ($limit) + { + $select_stmt .= " limit " . intval($limit); + if ($offset) + { + $select_stmt .= " offset " . intval($offset); + } + } + } + + try + { + $sth = $this->_dbh->prepare($select_stmt); + $sth->execute($params); + } + catch( PDOException $exception ) + { + error_log("delete_objects: " . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + return 1; + } + + function retrieve_object($collection, $id) + { + try + { + $select_stmt = 'select * from wbo where username = :username and collection = :collection and id = :id'; + $sth = $this->_dbh->prepare($select_stmt); + $sth->bindParam(':username', $this->_username); + $sth->bindParam(':collection', $collection); + $sth->bindParam(':id', $id); + $sth->execute(); + } + catch( PDOException $exception ) + { + error_log("retrieve_object: " . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + + $result = $sth->fetch(PDO::FETCH_ASSOC); + + $wbo = new wbo(); + $wbo->populate($result); + return $wbo; + } + + function retrieve_objects($collection, $id = null, $full = null, $direct_output = null, $parentid = null, + $predecessorid = null, $newer = null, $older = null, $sort = null, + $limit = null, $offset = null, $ids = null, + $index_above = null, $index_below = null, $depth = null) + { + $full_list = $full ? '*' : 'id'; + + + $select_stmt = "select $full_list from wbo where username = ? and collection = ?"; + $params[] = $this->_username; + $params[] = $collection; + + + if ($id) + { + $select_stmt .= " and id = ?"; + $params[] = $id; + } + + if ($ids && count($ids) > 0) + { + $qmarks = array(); + $select_stmt .= " and id in ("; + foreach ($ids as $temp) + { + $params[] = $temp; + $qmarks[] = '?'; + } + $select_stmt .= implode(",", $qmarks); + $select_stmt .= ')'; + } + + if ($ids && count($ids) > 0) + { + $qmarks = array(); + $select_stmt .= " and id in ("; + foreach ($ids as $temp) + { + $params[] = $temp; + $qmarks[] = '?'; + } + $select_stmt .= implode(",", $qmarks); + $select_stmt .= ')'; + } + + if ($parentid) + { + $select_stmt .= " and parentid = ?"; + $params[] = $parentid; + } + + + if ($predecessorid) + { + $select_stmt .= " and predecessorid = ?"; + $params[] = $predecessorid; + } + + if ($index_above) + { + $select_stmt .= " and sortindex > ?"; + $params[] = $parentid; + } + + if ($index_below) + { + $select_stmt .= " and sortindex < ?"; + $params[] = $parentid; + } + + if ($newer) + { + $select_stmt .= " and modified > ?"; + $params[] = $newer; + } + + if ($older) + { + $select_stmt .= " and modified < ?"; + $params[] = $older; + } + + + if ($depth) + { + $select_stmt .= " and depth = ?"; + $params[] = $depth; + } + + if ($sort == 'index') + { + $select_stmt .= " order by sortindex desc"; + } + else if ($sort == 'newest') + { + $select_stmt .= " order by modified desc"; + } + else if ($sort == 'oldest') + { + $select_stmt .= " order by modified"; + } + + if ($limit) + { + $select_stmt .= " limit " . intval($limit); + if ($offset) + { + $select_stmt .= " offset " . intval($offset); + } + } + + try + { + $sth = $this->_dbh->prepare($select_stmt); + $sth->execute($params); + } + catch( PDOException $exception ) + { + error_log("retrieve_collection: " . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + + if ($direct_output) + return $direct_output->output($sth); + + $ids = array(); + while ($result = $sth->fetch(PDO::FETCH_ASSOC)) + { + if ($full) + { + $wbo = new wbo(); + $wbo->populate($result); + $ids[] = $wbo; + } + else + $ids[] = $result{'id'}; + } + return $ids; + } + + function get_storage_total() + { + try + { + $select_stmt = 'select round(sum(length(payload))/1024) from wbo where username = :username'; + $sth = $this->_dbh->prepare($select_stmt); + $sth->bindParam(':username', $this->_username); + $sth->execute(); + } + catch( PDOException $exception ) + { + error_log("get_storage_total: " . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + + return (int)$sth->fetchColumn(); + } + + function get_user_quota() + { + return null; + } + + function delete_user($username) + { + if (!$username) + { + throw new Exception("3", 404); + } + + try + { + $delete_stmt = 'delete from users where username = :username'; + $sth = $this->_dbh->prepare($delete_stmt); + $sth->bindParam(':username', $username); + $sth->execute(); + $sth->closeCursor(); + + $delete_wbo_stmt = 'delete from wbo where username = :username'; + $sth = $this->_dbh->prepare($delete_wbo_stmt); + $sth->bindParam(':username', $username); + $sth->execute(); + + } + catch( PDOException $exception ) + { + error_log("delete_user: " . $exception->getMessage()); + return 0; + } + return 1; + } + + function create_user($username, $password) + { + try + { + $create_statement = "insert into users values (:username, :md5)"; + + $sth = $this->_dbh->prepare($create_statement); + $sth->bindParam(':username', $username); + $sth->bindParam(':md5', md5($password)); + $sth->execute(); + } + catch( PDOException $exception ) + { + error_log("create_user:" . $exception->getMessage()); + return 0; + } + return 1; + } + + function change_password($username, $password) + { + try + { + $update_statement = "update users set md5 = :md5 where username = :username"; + + $sth = $this->_dbh->prepare($update_statement); + $sth->bindParam(':username', $username); + $sth->bindParam(':md5', md5($password)); + $sth->execute(); + } + catch( PDOException $exception ) + { + error_log("change_password:" . $exception->getMessage()); + return 0; + } + return 1; + } + + function authenticate_user($password) + { + try + { + $select_stmt = 'select username from users where username = :username and md5 = :md5'; + $sth = $this->_dbh->prepare($select_stmt); + $sth->bindParam(':username', $this->_username); + $sth->bindParam(':md5', md5($password)); + $sth->execute(); + } + catch( PDOException $exception ) + { + error_log("authenticate_user: " . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + + if (!$result = $sth->fetch(PDO::FETCH_ASSOC)) + { + return null; + } + + return 1; + } + + function setup_db() + { + + try + { + $create_statement = <<< end +create table wbo +( + username text, + id text, + collection text, + parentid text, + predecessorid int, + modified real, + sortindex int, + depth int, + payload text, + payload_size int, + primary key (username,collection,id) +) +end; + + $create_statement2 = <<< end +create table users +( + username text, + md5 text, + primary key (username) +) +end; + + $index1 = 'create index parentindex on wbo (username, parentid)'; + $index2 = 'create index predecessorindex on wbo (username, predecessorid)'; + $index3 = 'create index modifiedindex on wbo (username, collection, modified)'; + + + $sth = $this->_dbh->prepare($create_statement); + $sth->execute(); + $sth = $this->_dbh->prepare($create_statement2); + $sth->execute(); + $sth = $this->_dbh->prepare($index1); + $sth->execute(); + $sth = $this->_dbh->prepare($index2); + $sth->execute(); + $sth = $this->_dbh->prepare($index3); + $sth->execute(); + } + catch( PDOException $exception ) + { + error_log("initialize_user_db:" . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + } +} + + + ?> \ No newline at end of file -- cgit v1.2.3