fix reduce
fix reduce


Former-commit-id: 9bf62392d9d505afcc2ed31f23d49c84a98a2091

<?php <?php
   
/** /**
* Databaase class. * Databaase class.
*/ */
class SetteeDatabase { class SetteeDatabase {
   
/** /**
* Base URL of the CouchDB REST API * Base URL of the CouchDB REST API
*/ */
private $conn_url; private $conn_url;
   
/** /**
* HTTP REST Client instance * HTTP REST Client instance
*/ */
protected $rest_client; protected $rest_client;
   
/** /**
* Name of the database * Name of the database
*/ */
private $dbname; private $dbname;
   
/** /**
* Default constructor * Default constructor
*/ */
function __construct($conn_url, $dbname) { function __construct($conn_url, $dbname) {
$this->conn_url = $conn_url; $this->conn_url = $conn_url;
$this->dbname = $dbname; $this->dbname = $dbname;
$this->rest_client = SetteeRestClient::get_instance($this->conn_url); $this->rest_client = SetteeRestClient::get_instance($this->conn_url);
} }
   
/** /**
* Get UUID from CouchDB * Get UUID from CouchDB
* *
* @return * @return
* CouchDB-generated UUID string * CouchDB-generated UUID string
* *
*/ */
function gen_uuid() { function gen_uuid() {
$ret = $this->rest_client->http_get('_uuids'); $ret = $this->rest_client->http_get('_uuids');
return $ret['decoded']->uuids[0]; // should never be empty at this point, so no checking return $ret['decoded']->uuids[0]; // should never be empty at this point, so no checking
} }
   
/** /**
* Create or update a document database * Create or update a document database
* *
* @param $document * @param $document
* PHP object, a PHP associative array, or a JSON String representing the document to be saved. PHP Objects and arrays are JSON-encoded automatically. * PHP object, a PHP associative array, or a JSON String representing the document to be saved. PHP Objects and arrays are JSON-encoded automatically.
* *
* <p>If $document has a an "_id" property set, it will be used as document's unique id (even for "create" operation). * <p>If $document has a an "_id" property set, it will be used as document's unique id (even for "create" operation).
* If "_id" is missing, CouchDB will be used to generate a UUID. * If "_id" is missing, CouchDB will be used to generate a UUID.
* *
* <p>If $document has a "_rev" property (revision), document will be updated, rather than creating a new document. * <p>If $document has a "_rev" property (revision), document will be updated, rather than creating a new document.
* You have to provide "_rev" if you want to update an existing document, otherwise operation will be assumed to be * You have to provide "_rev" if you want to update an existing document, otherwise operation will be assumed to be
* one of creation and you will get a duplicate document exception from CouchDB. Also, you may not provide "_rev" but * one of creation and you will get a duplicate document exception from CouchDB. Also, you may not provide "_rev" but
* not provide "_id" since that is an invalid input. * not provide "_id" since that is an invalid input.
* *
* @param $allowRevAutoDetection * @param $allowRevAutoDetection
* Default: false. When true and _rev is missing from the document, save() function will auto-detect latest revision * Default: false. When true and _rev is missing from the document, save() function will auto-detect latest revision
* for a document and use it. This option is "false" by default because it involves an extra http HEAD request and * for a document and use it. This option is "false" by default because it involves an extra http HEAD request and
* therefore can make save() operation slightly slower if such auto-detection is not required. * therefore can make save() operation slightly slower if such auto-detection is not required.
* *
* @return * @return
* document object with the database id (uuid) and revision attached; * document object with the database id (uuid) and revision attached;
* *
* @throws SetteeCreateDatabaseException * @throws SetteeCreateDatabaseException
*/ */
function save($document, $allowRevAutoDetection = false) { function save($document, $allowRevAutoDetection = false) {
if (is_string($document)) { if (is_string($document)) {
$document = json_decode($document); $document = json_decode($document);
} }
   
// Allow passing of $document as an array (for syntactic simplicity and also because in JSON world it does not matter) // Allow passing of $document as an array (for syntactic simplicity and also because in JSON world it does not matter)
if (is_array($document)) { if (is_array($document)) {
$document = (object) $document; $document = (object) $document;
} }
   
if (empty($document->_id) && empty($document->_rev)) { if (empty($document->_id) && empty($document->_rev)) {
$id = $this->gen_uuid(); $id = $this->gen_uuid();
} elseif (empty($document->_id) && !empty($document->_rev)) { } elseif (empty($document->_id) && !empty($document->_rev)) {
throw new SetteeWrongInputException("Error: You can not save a document with a revision provided, but missing id"); throw new SetteeWrongInputException("Error: You can not save a document with a revision provided, but missing id");
} else { } else {
$id = $document->_id; $id = $document->_id;
   
if ($allowRevAutoDetection) { if ($allowRevAutoDetection) {
try { try {
$rev = $this->get_rev($id); $rev = $this->get_rev($id);
} catch (SetteeRestClientException $e) { } catch (SetteeRestClientException $e) {
// auto-detection may fail legitimately, if a document has never been saved before (new doc), so skipping error // auto-detection may fail legitimately, if a document has never been saved before (new doc), so skipping error
} }
if (!empty($rev)) { if (!empty($rev)) {
$document->_rev = $rev; $document->_rev = $rev;
} }
} }
} }
   
$full_uri = $this->dbname . "/" . $this->safe_urlencode($id); $full_uri = $this->dbname . "/" . $this->safe_urlencode($id);
$document_json = json_encode($document, JSON_NUMERIC_CHECK); $document_json = json_encode($document, JSON_NUMERIC_CHECK);
   
$ret = $this->rest_client->http_put($full_uri, $document_json); $ret = $this->rest_client->http_put($full_uri, $document_json);
   
$document->_id = $ret['decoded']->id; $document->_id = $ret['decoded']->id;
$document->_rev = $ret['decoded']->rev; $document->_rev = $ret['decoded']->rev;
   
return $document; return $document;
} }
   
/** /**
* @param $doc * @param $doc
* @param $name * @param $name
* @param $content * @param $content
* Content of the attachment in a string-buffer format. This function will automatically base64-encode content for * Content of the attachment in a string-buffer format. This function will automatically base64-encode content for
* you, so you don't have to do it. * you, so you don't have to do it.
* @param $mime_type * @param $mime_type
* Optional. Will be auto-detected if not provided * Optional. Will be auto-detected if not provided
* @return void * @return void
*/ */
public function add_attachment($doc, $name, $content, $mime_type = null) { public function add_attachment($doc, $name, $content, $mime_type = null) {
if (empty($doc->_attachments) || !is_object($doc->_attachments)) { if (empty($doc->_attachments) || !is_object($doc->_attachments)) {
$doc->_attachments = new stdClass(); $doc->_attachments = new stdClass();
} }
   
if (empty($mime_type)) { if (empty($mime_type)) {
$mime_type = $this->rest_client->content_mime_type($content); $mime_type = $this->rest_client->content_mime_type($content);
} }
   
$doc->_attachments->$name = new stdClass(); $doc->_attachments->$name = new stdClass();
$doc->_attachments->$name->content_type = $mime_type; $doc->_attachments->$name->content_type = $mime_type;
$doc->_attachments->$name->data = base64_encode($content); $doc->_attachments->$name->data = base64_encode($content);
} }
   
/** /**
* @param $doc * @param $doc
* @param $name * @param $name
* @param $file * @param $file
* Full path to a file (e.g. as returned by PHP's realpath function). * Full path to a file (e.g. as returned by PHP's realpath function).
* @param $mime_type * @param $mime_type
* Optional. Will be auto-detected if not provided * Optional. Will be auto-detected if not provided
* @return void * @return void
*/ */
public function add_attachment_file($doc, $name, $file, $mime_type = null) { public function add_attachment_file($doc, $name, $file, $mime_type = null) {
$content = file_get_contents($file); $content = file_get_contents($file);
$this->add_attachment($doc, $name, $content, $mime_type); $this->add_attachment($doc, $name, $content, $mime_type);
} }
   
/** /**
* *
* Retrieve a document from CouchDB * Retrieve a document from CouchDB
* *
* @throws SetteeWrongInputException * @throws SetteeWrongInputException
* *
* @param $id * @param $id
* Unique ID (usually: UUID) of the document to be retrieved. * Unique ID (usually: UUID) of the document to be retrieved.
* @return * @return
* database document in PHP object format. * database document in PHP object format.
*/ */
function get($id) { function get($id) {
if (empty($id)) { if (empty($id)) {
throw new SetteeWrongInputException("Error: Can't retrieve a document without a uuid."); throw new SetteeWrongInputException("Error: Can't retrieve a document without a uuid.");
} }
   
$full_uri = $this->dbname . "/" . $this->safe_urlencode($id); $full_uri = $this->dbname . "/" . $this->safe_urlencode($id);
$full_uri = str_replace("%3Frev%3D", "?rev=", $full_uri); $full_uri = str_replace("%3Frev%3D", "?rev=", $full_uri);
$ret = $this->rest_client->http_get($full_uri); $ret = $this->rest_client->http_get($full_uri);
return $ret['decoded']; return $ret['decoded'];
} }
   
/** /**
* *
* Get the latest revision of a document with document id: $id in CouchDB. * Get the latest revision of a document with document id: $id in CouchDB.
* *
* @throws SetteeWrongInputException * @throws SetteeWrongInputException
* *
* @param $id * @param $id
* Unique ID (usually: UUID) of the document to be retrieved. * Unique ID (usually: UUID) of the document to be retrieved.
* @return * @return
* database document in PHP object format. * database document in PHP object format.
*/ */
function get_rev($id) { function get_rev($id) {
if (empty($id)) { if (empty($id)) {
throw new SetteeWrongInputException("Error: Can't query a document without a uuid."); throw new SetteeWrongInputException("Error: Can't query a document without a uuid.");
} }
   
$full_uri = $this->dbname . "/" . $this->safe_urlencode($id); $full_uri = $this->dbname . "/" . $this->safe_urlencode($id);
$headers = $this->rest_client->http_head($full_uri); $headers = $this->rest_client->http_head($full_uri);
if (empty($headers['Etag'])) { if (empty($headers['Etag'])) {
throw new SetteeRestClientException("Error: could not retrieve revision. Server unexpectedly returned empty Etag"); throw new SetteeRestClientException("Error: could not retrieve revision. Server unexpectedly returned empty Etag");
} }
$etag = str_replace('"', '', $headers['Etag']); $etag = str_replace('"', '', $headers['Etag']);
return $etag; return $etag;
} }
   
/** /**
* Delete a document * Delete a document
* *
* @param $document * @param $document
* a PHP object or JSON representation of the document that has _id and _rev fields. * a PHP object or JSON representation of the document that has _id and _rev fields.
* *
* @return void * @return void
*/ */
function delete($document) { function delete($document) {
if (!is_object($document)) { if (!is_object($document)) {
$document = json_decode($document); $document = json_decode($document);
} }
   
$full_uri = $this->dbname . "/" . $this->safe_urlencode($document->_id) . "?rev=" . $document->_rev; $full_uri = $this->dbname . "/" . $this->safe_urlencode($document->_id) . "?rev=" . $document->_rev;
$this->rest_client->http_delete($full_uri); $this->rest_client->http_delete($full_uri);
} }
   
/* ----------------- View-related functions -------------- */ /* ----------------- View-related functions -------------- */
   
/** /**
* Create a new view or update an existing one. * Create a new view or update an existing one.
* *
* @param $design_doc * @param $design_doc
* @param $view_name * @param $view_name
* @param $map_src * @param $map_src
* Source code of the map function in Javascript * Source code of the map function in Javascript
* @param $reduce_src * @param $reduce_src
* Source code of the reduce function in Javascript (optional) * Source code of the reduce function in Javascript (optional)
* @return void * @return void
*/ */
function save_view($design_doc, $view_name, $map_src, $reduce_src = null) { function save_view($design_doc, $view_name, $map_src, $reduce_src = null) {
$obj = new stdClass(); $obj = new stdClass();
$obj->_id = "_design/" . urlencode($design_doc); $obj->_id = "_design/" . urlencode($design_doc);
$view_name = urlencode($view_name); $view_name = urlencode($view_name);
$obj->views->$view_name->map = $map_src; $obj->views->$view_name->map = $map_src;
if (!empty($reduce_src)) { if (!empty($reduce_src)) {
$obj->views->$view_name->reduce = $reduce_src; $obj->views->$view_name->reduce = $reduce_src;
} }
   
// allow safe updates (even if slightly slower due to extra: rev-detection check). // allow safe updates (even if slightly slower due to extra: rev-detection check).
return $this->save($obj, true); return $this->save($obj, true);
} }
   
/** /**
* Create a new view or update an existing one. * Create a new view or update an existing one.
* *
* @param $design_doc * @param $design_doc
* @param $view_name * @param $view_name
* @param $key * @param $key
* key parameter to a view. Can be a single value or an array (for a range). If passed an array, function assumes * key parameter to a view. Can be a single value or an array (for a range). If passed an array, function assumes
* that first element is startkey, second: endkey. * that first element is startkey, second: endkey.
* @param $descending * @param $descending
* return results in descending order. Please don't forget that if you are using a startkey/endkey, when you change * return results in descending order. Please don't forget that if you are using a startkey/endkey, when you change
* order you also need to swap startkey and endkey values! * order you also need to swap startkey and endkey values!
* *
* @return void * @return void
*/ */
function get_view($design_doc, $view_name, $key = null, $descending = false, $limit = false, $reduce = null, $startdocid = null) { function get_view($design_doc, $view_name, $key = null, $descending = false, $limit = false, $reduce = null, $startdocid = null) {
$id = "_design/" . urlencode($design_doc); $id = "_design/" . urlencode($design_doc);
$view_name = urlencode($view_name); $view_name = urlencode($view_name);
$id .= "/_view/$view_name"; $id .= "/_view/$view_name";
   
$data = array(); $data = array();
if (!empty($key)) { if (!empty($key)) {
if (is_string($key)) { if (is_string($key)) {
$data = "key=" . '"' . $key . '"'; $data = "key=" . '"' . $key . '"';
} elseif (is_array($key)) { } elseif (is_array($key)) {
list($startkey, $endkey) = $key; list($startkey, $endkey) = $key;
$data = "startkey=" . '"' . $startkey . '"&' . "endkey=" . '"' . $endkey . '"'; $data = "startkey=" . '"' . $startkey . '"&' . "endkey=" . '"' . $endkey . '"';
} }
   
if ($descending) { if ($descending) {
$data .= "&descending=true"; $data .= "&descending=true";
} }
if ($startdocid != null) { if ($startdocid != null) {
$data .= "&startkey_docid='$startdocid'"; $data .= "&startkey_docid='$startdocid'";
} }
if ($reduce != null) { if ($reduce === true) {
if ($reduce == true) {  
$data .= "&reduce=true"; $data .= "&reduce=true";
} else { } else if ($reduce === false){
   
$data .= "&reduce=false"; $data .= "&reduce=false";
}  
} }
if ($limit) { if ($limit) {
$data .= "&limit=" . $limit; $data .= "&limit=" . $limit;
} }
} }
   
   
   
if (empty($id)) { if (empty($id)) {
throw new SetteeWrongInputException("Error: Can't retrieve a document without a uuid."); throw new SetteeWrongInputException("Error: Can't retrieve a document without a uuid.");
} }
   
$full_uri = $this->dbname . "/" . $this->safe_urlencode($id); $full_uri = $this->dbname . "/" . $this->safe_urlencode($id);
   
$full_uri = str_replace("%253Fgroup%253D", "?group=", $full_uri); $full_uri = str_replace("%253Fgroup%253D", "?group=", $full_uri);
$full_uri = str_replace("%253Flimit%253D", "?limit=", $full_uri); $full_uri = str_replace("%253Flimit%253D", "?limit=", $full_uri);
$ret = $this->rest_client->http_get($full_uri, $data); $ret = $this->rest_client->http_get($full_uri, $data);
//$ret['decoded'] = str_replace("?k","&k",$ret['decoded']); //$ret['decoded'] = str_replace("?k","&k",$ret['decoded']);
return $ret['decoded']; return $ret['decoded'];
} }
   
/** /**
* @param $id * @param $id
* @return * @return
* return a properly url-encoded id. * return a properly url-encoded id.
*/ */
private function safe_urlencode($id) { private function safe_urlencode($id) {
//-- System views like _design can have "/" in their URLs. //-- System views like _design can have "/" in their URLs.
$id = rawurlencode($id); $id = rawurlencode($id);
if (substr($id, 0, 1) == '_') { if (substr($id, 0, 1) == '_') {
$id = str_replace('%2F', '/', $id); $id = str_replace('%2F', '/', $id);
} }
return $id; return $id;
} }
   
/** Getter for a database name */ /** Getter for a database name */
function get_name() { function get_name() {
return $this->dbname; return $this->dbname;
} }
   
} }
   
<?php <?php
   
/** /**
* HTTP REST Client for CouchDB API * HTTP REST Client for CouchDB API
*/ */
class SetteeRestClient { class SetteeRestClient {
/** /**
* HTTP Timeout in Milliseconds * HTTP Timeout in Milliseconds
*/ */
const HTTP_TIMEOUT = 2000; const HTTP_TIMEOUT = 2000;
private $base_url; private $base_url;
private $curl; private $curl;
private static $curl_workers = array(); private static $curl_workers = array();
   
/** /**
* Singleton factory method * Singleton factory method
*/ */
static function get_instance($base_url) { static function get_instance($base_url) {
   
if (empty(self::$curl_workers[$base_url])) { if (empty(self::$curl_workers[$base_url])) {
self::$curl_workers[$base_url] = new SetteeRestClient($base_url); self::$curl_workers[$base_url] = new SetteeRestClient($base_url);
} }
return self::$curl_workers[$base_url]; return self::$curl_workers[$base_url];
} }
/** /**
* Class constructor * Class constructor
*/ */
private function __construct($base_url) { private function __construct($base_url) {
$this->base_url = $base_url; $this->base_url = $base_url;
   
$curl = curl_init(); $curl = curl_init();
curl_setopt($curl, CURLOPT_USERAGENT, "Settee CouchDB Client/1.0"); curl_setopt($curl, CURLOPT_USERAGENT, "Settee CouchDB Client/1.0");
curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: application/json')); curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_HEADER, 0); curl_setopt($curl, CURLOPT_HEADER, 0);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($curl, CURLOPT_TIMEOUT_MS, self::HTTP_TIMEOUT); curl_setopt($curl, CURLOPT_TIMEOUT_MS, self::HTTP_TIMEOUT);
curl_setopt($curl, CURLOPT_FORBID_REUSE, false); // Connection-pool for CURL curl_setopt($curl, CURLOPT_FORBID_REUSE, false); // Connection-pool for CURL
   
$this->curl = $curl; $this->curl = $curl;
} }
   
/** /**
* Class destructor cleans up any resources * Class destructor cleans up any resources
*/ */
function __destruct() { function __destruct() {
curl_close($this->curl); curl_close($this->curl);
} }
   
/** /**
* HTTP HEAD * HTTP HEAD
* *
* @return * @return
* Raw HTTP Headers of the response. * Raw HTTP Headers of the response.
* *
* @see: http://www.php.net/manual/en/context.params.php * @see: http://www.php.net/manual/en/context.params.php
* *
*/ */
function http_head($uri) { function http_head($uri) {
curl_setopt($this->curl, CURLOPT_HEADER, 1); curl_setopt($this->curl, CURLOPT_HEADER, 1);
   
$full_url = $this->get_full_url($uri); $full_url = $this->get_full_url($uri);
curl_setopt($this->curl, CURLOPT_URL, $full_url); curl_setopt($this->curl, CURLOPT_URL, $full_url);
curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, 'HEAD'); curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, 'HEAD');
curl_setopt($this->curl, CURLOPT_NOBODY, true); curl_setopt($this->curl, CURLOPT_NOBODY, true);
   
   
$response = curl_exec($this->curl); $response = curl_exec($this->curl);
// Restore default values // Restore default values
curl_setopt($this->curl, CURLOPT_NOBODY, false); curl_setopt($this->curl, CURLOPT_NOBODY, false);
curl_setopt($this->curl, CURLOPT_HEADER, false); curl_setopt($this->curl, CURLOPT_HEADER, false);
$resp_code = curl_getinfo($this->curl, CURLINFO_HTTP_CODE); $resp_code = curl_getinfo($this->curl, CURLINFO_HTTP_CODE);
if ($resp_code == 404 ) { if ($resp_code == 404 ) {
throw new SetteeRestClientException("Couch document not found at: '$full_url'"); throw new SetteeRestClientException("Couch document not found at: '$full_url'");
} }
   
if (function_exists('http_parse_headers')) { if (function_exists('http_parse_headers')) {
$headers = http_parse_headers($response); $headers = http_parse_headers($response);
} }
else { else {
$headers = $this->_http_parse_headers($response); $headers = $this->_http_parse_headers($response);
} }
return $headers; return $headers;
} }
   
/** /**
* Backup PHP impl. for when PECL http_parse_headers() function is not available * Backup PHP impl. for when PECL http_parse_headers() function is not available
* *
* @param $header * @param $header
* @return array * @return array
* @source http://www.php.net/manual/en/function.http-parse-headers.php#77241 * @source http://www.php.net/manual/en/function.http-parse-headers.php#77241
*/ */
private function _http_parse_headers( $header ) { private function _http_parse_headers( $header ) {
$retVal = array(); $retVal = array();
$fields = explode("\r\n", preg_replace('/\x0D\x0A[\x09\x20]+/', ' ', $header)); $fields = explode("\r\n", preg_replace('/\x0D\x0A[\x09\x20]+/', ' ', $header));
foreach( $fields as $field ) { foreach( $fields as $field ) {
if( preg_match('/([^:]+): (.+)/m', $field, $match) ) { if( preg_match('/([^:]+): (.+)/m', $field, $match) ) {
$match[1] = preg_replace('/(?<=^|[\x09\x20\x2D])./e', 'strtoupper("\0")', strtolower(trim($match[1]))); $match[1] = preg_replace('/(?<=^|[\x09\x20\x2D])./e', 'strtoupper("\0")', strtolower(trim($match[1])));
if( isset($retVal[$match[1]]) ) { if( isset($retVal[$match[1]]) ) {
$retVal[$match[1]] = array($retVal[$match[1]], $match[2]); $retVal[$match[1]] = array($retVal[$match[1]], $match[2]);
} else { } else {
$retVal[$match[1]] = trim($match[2]); $retVal[$match[1]] = trim($match[2]);
} }
} }
} }
return $retVal; return $retVal;
} }
   
/** /**
* HTTP GET * HTTP GET
*/ */
function http_get($uri, $data = array()) { function http_get($uri, $data = array()) {
$data = (is_array($data)) ? http_build_query($data) : $data; $data = (is_array($data)) ? http_build_query($data) : $data;
if (!empty($data)) { if (!empty($data)) {
$uri .= "?$data"; $uri .= "?$data";
} }
return $this->http_request('GET', $uri); return $this->http_request('GET', $uri);
} }
/** /**
* HTTP PUT * HTTP PUT
*/ */
function http_put($uri, $data = array()) { function http_put($uri, $data = array()) {
return $this->http_request('PUT', $uri, $data); return $this->http_request('PUT', $uri, $data);
} }
   
/** /**
* HTTP DELETE * HTTP DELETE
*/ */
function http_delete($uri, $data = array()) { function http_delete($uri, $data = array()) {
return $this->http_request('DELETE', $uri, $data); return $this->http_request('DELETE', $uri, $data);
} }
   
/** /**
* Generic implementation of a HTTP Request. * Generic implementation of a HTTP Request.
* *
* @param $http_method * @param $http_method
* @param $uri * @param $uri
* @param array $data * @param array $data
* @return * @return
* an array containing json and decoded versions of the response. * an array containing json and decoded versions of the response.
*/ */
private function http_request($http_method, $uri, $data = array()) { private function http_request($http_method, $uri, $data = array()) {
$data = (is_array($data)) ? http_build_query($data) : $data; $data = (is_array($data)) ? http_build_query($data) : $data;
   
if (!empty($data)) { if (!empty($data)) {
curl_setopt($this->curl, CURLOPT_HTTPHEADER, array('Content-Length: ' . strlen($data))); curl_setopt($this->curl, CURLOPT_HTTPHEADER, array('Content-Length: ' . strlen($data)));
curl_setopt($this->curl, CURLOPT_POSTFIELDS, $data); curl_setopt($this->curl, CURLOPT_POSTFIELDS, $data);
} }
   
curl_setopt($this->curl, CURLOPT_URL, $this->get_full_url($uri)); curl_setopt($this->curl, CURLOPT_URL, $this->get_full_url($uri));
curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, $http_method); curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, $http_method);
   
$response = curl_exec($this->curl); $response = curl_exec($this->curl);
$response_decoded = $this->decode_response($response); $response_decoded = $this->decode_response($response);
$response = array('json' => $response, 'decoded'=>$response_decoded); $response = array('json' => $response, 'decoded'=>$response_decoded);
   
$this->check_status($response,$uri); $this->check_status($response,$uri);
   
return $response; return $response;
} }
/** /**
* Check http status for safe return codes * Check http status for safe return codes
* *
* @throws SetteeRestClientException * @throws SetteeRestClientException
*/ */
private function check_status($response,$uri) { private function check_status($response,$uri) {
$resp_code = curl_getinfo($this->curl, CURLINFO_HTTP_CODE); $resp_code = curl_getinfo($this->curl, CURLINFO_HTTP_CODE);
   
if ($resp_code < 199 || $resp_code > 399 || !empty($response['decoded']->error)) { if ($resp_code < 199 || $resp_code > 399 || !empty($response['decoded']->error)) {
$msg = "CouchDB returned: \"HTTP 1.1. $resp_code\". ERROR: " . $response['json'] . $uri; $msg = "CouchDB returned: \"HTTP 1.1. $resp_code\". ERROR: " . $response['json'] . $uri;
throw new SetteeRestClientException($msg); throw new SetteeRestClientException($msg);
} }
} }
   
/** /**
* @param $path * @param $path
* Full path to a file (e.g. as returned by PHP's realpath function). * Full path to a file (e.g. as returned by PHP's realpath function).
* @return void * @return void
*/ */
public function file_mime_type ($path) { public function file_mime_type ($path) {
$ftype = 'application/octet-stream'; $ftype = 'application/octet-stream';
if (function_exists("finfo_file")) { if (function_exists("finfo_file")) {
$finfo = new finfo(FILEINFO_MIME_TYPE | FILEINFO_SYMLINK); $finfo = new finfo(FILEINFO_MIME_TYPE | FILEINFO_SYMLINK);
$fres = $finfo->file($path); $fres = $finfo->file($path);
if (is_string($fres) && !empty($fres)) { if (is_string($fres) && !empty($fres)) {
$ftype = $fres; $ftype = $fres;
} }
} }
   
return $ftype; return $ftype;
} }
   
/** /**
* @param $content * @param $content
* content of a file in a string buffer format. * content of a file in a string buffer format.
* @return void * @return void
*/ */
public function content_mime_type ($content) { public function content_mime_type ($content) {
$ftype = 'application/octet-stream'; $ftype = 'application/octet-stream';
   
if (function_exists("finfo_file")) { if (function_exists("finfo_file")) {
$finfo = new finfo(FILEINFO_MIME_TYPE | FILEINFO_SYMLINK); $finfo = new finfo(FILEINFO_MIME_TYPE | FILEINFO_SYMLINK);
$fres = $finfo->buffer($content); $fres = $finfo->buffer($content);
if (is_string($fres) && !empty($fres)) { if (is_string($fres) && !empty($fres)) {
$ftype = $fres; $ftype = $fres;
} }
} }
   
return $ftype; return $ftype;
} }
   
/** /**
* *
* @param $json * @param $json
* json-encoded response from CouchDB * json-encoded response from CouchDB
* *
* @return * @return
* decoded PHP object * decoded PHP object
*/ */
private function decode_response($json) { private function decode_response($json) {
return json_decode($json); return json_decode($json);
} }
   
/** /**
* Get full URL from a partial one * Get full URL from a partial one
*/ */
private function get_full_url($uri) { private function get_full_url($uri) {
// We do not want "/", "?", "&" and "=" separators to be encoded!!! // We do not want "/", "?", "&" and "=" separators to be encoded!!!
$uri = str_replace(array('%2F', '%3F', '%3D', '%26'), array('/', '?', '=', '&'), urlencode($uri)); $uri = str_replace(array('%2F', '%3F', '%3D', '%26'), array('/', '?', '=', '&'), urlencode($uri));
return $this->base_url . '/' . $uri; return $this->base_url . '/' . $uri;
} }
} }
   
class SetteeRestClientException extends Exception {} class SetteeRestClientException extends Exception {}
   
<?php <?php
   
function include_header_documents($title) { function include_header_documents($title) {
header('X-UA-Compatible: IE=edge,chrome=1'); header('X-UA-Compatible: IE=edge,chrome=1');
?> ?>
<!doctype html> <!doctype html>
<!-- paulirish.com/2008/conditional-stylesheets-vs-css-hacks-answer-neither/ --> <!-- paulirish.com/2008/conditional-stylesheets-vs-css-hacks-answer-neither/ -->
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7" lang="en"> <![endif]--> <!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7" lang="en"> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8" lang="en"> <![endif]--> <!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8" lang="en"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9" lang="en"> <![endif]--> <!--[if IE 8]> <html class="no-js lt-ie9" lang="en"> <![endif]-->
<!-- Consider adding a manifest.appcache: h5bp.com/d/Offline --> <!-- Consider adding a manifest.appcache: h5bp.com/d/Offline -->
<!--[if gt IE 8]><!--> <html class="no-js" lang="en"> <!--<![endif]--> <!--[if gt IE 8]><!--> <html class="no-js" lang="en"> <!--<![endif]-->
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
   
<title>Australian Disclosure Logs<?php if ($title != "") echo " - $title"; ?></title> <title>Australian Disclosure Logs<?php if ($title != "") echo " - $title"; ?></title>
<meta name="description" content=""> <meta name="description" content="">
   
<!-- Mobile viewport optimized: h5bp.com/viewport --> <!-- Mobile viewport optimized: h5bp.com/viewport -->
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<link rel="alternate" type="application/rss+xml" title="Latest Disclosure Log Entries" href="rss.xml.php" /> <link rel="alternate" type="application/rss+xml" title="Latest Disclosure Log Entries" href="rss.xml.php" />
<!-- Place favicon.ico and apple-touch-icon.png in the root directory: mathiasbynens.be/notes/touch-icons --> <!-- Place favicon.ico and apple-touch-icon.png in the root directory: mathiasbynens.be/notes/touch-icons -->
<meta name="google-site-verification" content="jkknX5g2FCpQvrW030b1Nq2hyoa6mb3EDiA7kCoHNj8" /> <meta name="google-site-verification" content="jkknX5g2FCpQvrW030b1Nq2hyoa6mb3EDiA7kCoHNj8" />
   
<!-- Le styles --> <!-- Le styles -->
<link href="css/bootstrap.min.css" rel="stylesheet"> <link href="css/bootstrap.min.css" rel="stylesheet">
<style type="text/css"> <style type="text/css">
body { body {
padding-top: 60px; padding-top: 60px;
padding-bottom: 40px; padding-bottom: 40px;
} }
.sidebar-nav { .sidebar-nav {
padding: 9px 0; padding: 9px 0;
} }
</style> </style>
<link href="css/bootstrap-responsive.min.css" rel="stylesheet"> <link href="css/bootstrap-responsive.min.css" rel="stylesheet">
   
<!-- HTML5 shim, for IE6-8 support of HTML5 elements --> <!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
<!--[if lt IE 9]> <!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script> <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]--> <![endif]-->
<!-- More ideas for your <head> here: h5bp.com/d/head-Tips --> <!-- More ideas for your <head> here: h5bp.com/d/head-Tips -->
   
<!-- All JavaScript at the bottom, except this Modernizr build. <!-- All JavaScript at the bottom, except this Modernizr build.
Modernizr enables HTML5 elements & feature detects for optimal performance. Modernizr enables HTML5 elements & feature detects for optimal performance.
Create your own custom Modernizr build: www.modernizr.com/download/ Create your own custom Modernizr build: www.modernizr.com/download/
<script src="js/libs/modernizr-2.5.3.min.js"></script>--> <script src="js/libs/modernizr-2.5.3.min.js"></script>-->
<script src="js/jquery.js"></script> <script src="js/jquery.js"></script>
<script type="text/javascript" src="js/flotr2.min.js"></script> <script type="text/javascript" src="js/flotr2.min.js"></script>
   
</head> </head>
<body> <body>
<div class="navbar navbar-inverse navbar-fixed-top"> <div class="navbar navbar-inverse navbar-fixed-top">
<div class="navbar-inner"> <div class="navbar-inner">
<div class="container-fluid"> <div class="container-fluid">
<a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse"> <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
<span class="icon-bar"></span> <span class="icon-bar"></span>
<span class="icon-bar"></span> <span class="icon-bar"></span>
<span class="icon-bar"></span> <span class="icon-bar"></span>
</a> </a>
<a class="brand" href="#">Australian Disclosure Logs</a> <a class="brand" href="#">Australian Disclosure Logs</a>
<div class="nav-collapse collapse"> <div class="nav-collapse collapse">
<p class="navbar-text pull-right"> <p class="navbar-text pull-right">
<small> <small>
Subsites on: Subsites on:
</small> </small>
<a href="http://orgs.disclosurelo.gs">Government Agencies</a> <a href="http://orgs.disclosurelo.gs">Government Agencies</a>
• <a href="http://lobbyists.disclosurelo.gs">Political Lobbyists</a> • <a href="http://lobbyists.disclosurelo.gs">Political Lobbyists</a>
• <a href="http://contracts.disclosurelo.gs">Government Contracts and Spending</a> • <a href="http://contracts.disclosurelo.gs">Government Contracts and Spending</a>
   
</p> </p>
<ul class="nav"> <ul class="nav">
<li><a href="agency.php">By Agency</a></li> <li><a href="agency.php">By Agency</a></li>
<li><a href="date.php">By Date</a></li> <li><a href="date.php">By Date</a></li>
<li><a href="disclogsList.php">List of Disclosure Logs</a></li> <li><a href="disclogsList.php">List of Disclosure Logs</a></li>
<li><a href="about.php">About</a></li> <li><a href="about.php">About</a></li>
   
</ul> </ul>
</div><!--/.nav-collapse --> </div><!--/.nav-collapse -->
</div> </div>
</div> </div>
</div> </div>
<div class="container"> <div class="container">
<?php <?php
} }
   
function include_footer_documents() { function include_footer_documents() {
global $ENV; global $ENV;
?> ?>
</div> <!-- /container --> </div> <!-- /container -->
<hr> <hr>
   
<footer> <footer>
<p>Not affiliated with or endorsed by any government agency.</p> <p>Not affiliated with or endorsed by any government agency.</p>
</footer> </footer>
<?php <?php
if ($ENV != "DEV") { if ($ENV != "DEV") {
echo "<script type='text/javascript'> echo "<script type='text/javascript'>
   
var _gaq = _gaq || []; var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-12341040-4']); _gaq.push(['_setAccount', 'UA-12341040-4']);
_gaq.push(['_setDomainName', 'disclosurelo.gs']); _gaq.push(['_setDomainName', 'disclosurelo.gs']);
_gaq.push(['_setAllowLinker', true]); _gaq.push(['_setAllowLinker', true]);
_gaq.push(['_trackPageview']); _gaq.push(['_trackPageview']);
   
(function() { (function() {
var ga = document.createElement('script'); var ga = document.createElement('script');
ga.type = 'text/javascript'; ga.type = 'text/javascript';
ga.async = true; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(ga, s); s.parentNode.insertBefore(ga, s);
})(); })();
   
</script>"; </script>";
} }
?> ?>
<!-- Le javascript <!-- Le javascript
================================================== --> ================================================== -->
<!-- Placed at the end of the document so the pages load faster --> <!-- Placed at the end of the document so the pages load faster -->
<!-- <!--
<script src="js/bootstrap-transition.js"></script> <script src="js/bootstrap-transition.js"></script>
<script src="js/bootstrap-alert.js"></script> <script src="js/bootstrap-alert.js"></script>
<script src="js/bootstrap-modal.js"></script> <script src="js/bootstrap-modal.js"></script>
<script src="js/bootstrap-dropdown.js"></script> <script src="js/bootstrap-dropdown.js"></script>
<script src="js/bootstrap-scrollspy.js"></script> <script src="js/bootstrap-scrollspy.js"></script>
<script src="js/bootstrap-tab.js"></script> <script src="js/bootstrap-tab.js"></script>
<script src="js/bootstrap-tooltip.js"></script> <script src="js/bootstrap-tooltip.js"></script>
<script src="js/bootstrap-popover.js"></script> <script src="js/bootstrap-popover.js"></script>
<script src="js/bootstrap-button.js"></script> <script src="js/bootstrap-button.js"></script>
<script src="js/bootstrap-collapse.js"></script> <script src="js/bootstrap-collapse.js"></script>
<script src="js/bootstrap-carousel.js"></script> <script src="js/bootstrap-carousel.js"></script>
<script src="js/bootstrap-typeahead.js"></script>--> <script src="js/bootstrap-typeahead.js"></script>-->
   
   
</body> </body>
</html> </html>
<?php <?php
} }
   
function truncate($string, $length, $stopanywhere = false) { function truncate($string, $length, $stopanywhere = false) {
//truncates a string to a certain char length, stopping on a word if not specified otherwise. //truncates a string to a certain char length, stopping on a word if not specified otherwise.
if (strlen($string) > $length) { if (strlen($string) > $length) {
//limit hit! //limit hit!
$string = substr($string, 0, ($length - 3)); $string = substr($string, 0, ($length - 3));
if ($stopanywhere) { if ($stopanywhere) {
//stop anywhere //stop anywhere
$string .= '...'; $string .= '...';
} else { } else {
//stop on a word. //stop on a word.
$string = substr($string, 0, strrpos($string, ' ')) . '...'; $string = substr($string, 0, strrpos($string, ' ')) . '...';
} }
} }
return $string; return $string;
} }
   
function displayLogEntry($row, $idtoname) { function displayLogEntry($row, $idtoname) {
$result = ""; $result = "";
$result .= '<div itemscope itemtype="http://schema.org/Article">'; $result .= '<div itemscope itemtype="http://schema.org/Article">';
$result .= '<h2> <span itemprop="datePublished">' . $row->value->date . "</span>: <span itemprop='name headline'>" . truncate($row->value->title, 120) . "</span>"; $result .= '<h2><a href="http://disclosurelo.gs/view.php?id='.$row->value->_id.'"> <span itemprop="datePublished">' . $row->value->date . "</span>: <span itemprop='name headline'>" . truncate($row->value->title, 120) . "</span>";
$result .= ' (<span itemprop="author publisher creator">' . $idtoname[$row->value->agencyID] . '</span>)</h2>'; $result .= ' (<span itemprop="author publisher creator">' . $idtoname[$row->value->agencyID] . '</span>)</a></h2>';
$result .= "<p itemprop='description articleBody text'> Title: " . $row->value->title . "<br/>"; $result .= "<p itemprop='description articleBody text'> Title: " . $row->value->title . "<br/>";
if (isset($row->value->description)) { if (isset($row->value->description)) {
$result .= str_replace("\n", "<br>", preg_replace("/(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+/", "",trim($row->value->description))); $result .= str_replace("\n", "<br>", preg_replace("/(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+/", "",trim($row->value->description)));
} }
if (isset($row->value->notes)) { if (isset($row->value->notes)) {
$result .= " <br>Note: " . $row->value->notes; $result .= " <br>Note: " . $row->value->notes;
} }
$result .= "</p>"; $result .= "</p>";
   
if (isset($row->value->links)) { if (isset($row->value->links)) {
$result .= '<h3>Links/Documents</h3><ul itemprop="associatedMedia">'; $result .= '<h3>Links/Documents</h3><ul itemprop="associatedMedia">';
foreach ($row->value->links as $link) { foreach ($row->value->links as $link) {
$result .= '<li itemscope itemtype="http://schema.org/MediaObject"><a href="' . htmlspecialchars ($link) . '" itemprop="url contentURL">' . htmlspecialchars ( $link) . "</a></li>"; $result .= '<li itemscope itemtype="http://schema.org/MediaObject"><a href="' . htmlspecialchars ($link) . '" itemprop="url contentURL">' . htmlspecialchars ( $link) . "</a></li>";
} }
   
$result .= "</ul>"; $result .= "</ul>";
} }
$result .= "<small><A itemprop='url' href='" . $row->value->url . "'>View original source...</a> ID: " . strip_tags($row->value->docID) . "</small>"; $result .= "<small><A itemprop='url' href='" . $row->value->url . "'>View original source...</a> ID: " . strip_tags($row->value->docID) . "</small>";
$result .= "</div>\n"; $result .= "</div>\n";
return $result; return $result;
} }
   
<?php <?php
   
date_default_timezone_set("Australia/Sydney"); date_default_timezone_set("Australia/Sydney");
   
$basePath = ""; $basePath = "";
if (strstr($_SERVER['PHP_SELF'], "alaveteli/") if (strstr($_SERVER['PHP_SELF'], "alaveteli/")
|| strstr($_SERVER['PHP_SELF'], "admin/") || strstr($_SERVER['PHP_SELF'], "admin/")
|| strstr($_SERVER['PHP_SELF'], "lib/") || strstr($_SERVER['PHP_SELF'], "lib/")
|| strstr($_SERVER['PHP_SELF'], "include/") || strstr($_SERVER['PHP_SELF'], "include/")
|| strstr($_SERVER['PHP_SELF'], "documents/") || strstr($_SERVER['PHP_SELF'], "documents/")
|| $_SERVER['SERVER_NAME'] == "disclosurelo.gs" || $_SERVER['SERVER_NAME'] == "disclosurelo.gs"
  || $_SERVER['SERVER_NAME'] == "www.disclosurelo.gs"
) )
$basePath = "../"; $basePath = "../";
   
include_once ('couchdb.inc.php'); include_once ('couchdb.inc.php');
include_once ('template.inc.php'); include_once ('template.inc.php');
require_once $basePath.'lib/Requests/library/Requests.php'; require_once $basePath.'lib/Requests/library/Requests.php';
   
Requests::register_autoloader(); Requests::register_autoloader();
$ENV = "DEV"; $ENV = "DEV";
if (isset($_SERVER['SERVER_NAME']) && $_SERVER['SERVER_NAME'] != 'localhost') { if (isset($_SERVER['SERVER_NAME']) && $_SERVER['SERVER_NAME'] != 'localhost') {
   
require $basePath."lib/amon-php/amon.php"; require $basePath."lib/amon-php/amon.php";
Amon::config(array('address'=> 'http://127.0.0.1:2464', Amon::config(array('address'=> 'http://127.0.0.1:2464',
'protocol' => 'http', 'protocol' => 'http',
'secret_key' => "I2LJ6dOMmlnXgVAkTPFXd5M3ejkga8Gd2FbBt6iqZdw")); 'secret_key' => "I2LJ6dOMmlnXgVAkTPFXd5M3ejkga8Gd2FbBt6iqZdw"));
Amon::setup_exception_handler(); Amon::setup_exception_handler();
$ENV = "PROD"; $ENV = "PROD";
} }
   
# Convert a stdClass to an Array. http://www.php.net/manual/en/language.types.object.php#102735 # Convert a stdClass to an Array. http://www.php.net/manual/en/language.types.object.php#102735
   
function object_to_array(stdClass $Class) { function object_to_array(stdClass $Class) {
# Typecast to (array) automatically converts stdClass -> array. # Typecast to (array) automatically converts stdClass -> array.
$Class = (array) $Class; $Class = (array) $Class;
   
# Iterate through the former properties looking for any stdClass properties. # Iterate through the former properties looking for any stdClass properties.
# Recursively apply (array). # Recursively apply (array).
foreach ($Class as $key => $value) { foreach ($Class as $key => $value) {
if (is_object($value) && get_class($value) === 'stdClass') { if (is_object($value) && get_class($value) === 'stdClass') {
$Class[$key] = object_to_array($value); $Class[$key] = object_to_array($value);
} }
} }
return $Class; return $Class;
} }
   
# Convert an Array to stdClass. http://www.php.net/manual/en/language.types.object.php#102735 # Convert an Array to stdClass. http://www.php.net/manual/en/language.types.object.php#102735
   
function array_to_object(array $array) { function array_to_object(array $array) {
# Iterate through our array looking for array values. # Iterate through our array looking for array values.
# If found recurvisely call itself. # If found recurvisely call itself.
foreach ($array as $key => $value) { foreach ($array as $key => $value) {
if (is_array($value)) { if (is_array($value)) {
$array[$key] = array_to_object($value); $array[$key] = array_to_object($value);
} }
} }
   
# Typecast to (object) will automatically convert array -> stdClass # Typecast to (object) will automatically convert array -> stdClass
return (object) $array; return (object) $array;
} }
   
function dept_to_portfolio($deptName) { function dept_to_portfolio($deptName) {
return trim(str_replace("Department of", "", str_replace("Department of the", "Department of", $deptName))); return trim(str_replace("Department of", "", str_replace("Department of the", "Department of", $deptName)));
} }
function phrase_to_tag ($phrase) { function phrase_to_tag ($phrase) {
return str_replace(" ","_",str_replace("'","",str_replace(",","",strtolower($phrase)))); return str_replace(" ","_",str_replace("'","",str_replace(",","",strtolower($phrase))));
} }
function local_url() { function local_url() {
return "http://" . $_SERVER['HTTP_HOST'] . rtrim(dirname($_SERVER['PHP_SELF']), '/\\') . "/"; return "http://" . $_SERVER['HTTP_HOST'] . rtrim(dirname($_SERVER['PHP_SELF']), '/\\') . "/";
} }
function GetDomain($url) function GetDomain($url)
{ {
$nowww = ereg_replace('www\.','',$url); $nowww = ereg_replace('www\.','',$url);
$domain = parse_url($nowww); $domain = parse_url($nowww);
if(!empty($domain["host"])) if(!empty($domain["host"]))
{ {
return $domain["host"]; return $domain["host"];
} else } else
{ {
return $domain["path"]; return $domain["path"];
} }
} }