diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 00000000..0f844cab
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,2 @@
+github: alixaxel
+custom: https://paypal.me/alixaxel/10USD
diff --git a/.htaccess b/.htaccess
deleted file mode 100644
index 2ac8058d..00000000
--- a/.htaccess
+++ /dev/null
@@ -1,6 +0,0 @@
-
-RewriteEngine on
-RewriteCond %{REQUEST_FILENAME} !-f
-RewriteCond %{REQUEST_FILENAME} !-d
-RewriteRule ^(.*)$ index.php/$1 [L]
-
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..1b0003fb
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2014 Alix Axel
+
+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.
diff --git a/README.md b/README.md
index 09ad167c..a1ea391e 100644
--- a/README.md
+++ b/README.md
@@ -1,95 +1,119 @@
-#Arrest MySQL
+#ArrestDB
-Arrest MySQL is a "plug-n-play" RESTful API for your MySQL database. Arrest MySQL provides a REST API that maps directly to your database stucture with no configuation.
+ArrestDB is a "plug-n-play" RESTful API for SQLite, MySQL and PostgreSQL databases.
-For example lets suppose you have set up Arrest MySQL at http://api.example.com and your database has a table in it called "customers". To get a list of customers you would simply need to do:
+ArrestDB provides a REST API that maps directly to your database stucture with no configuation.
-```GET http://api.example.com/customers```
+Lets suppose you have set up ArrestDB at `http://api.example.com/` and that your database has a table named `customers`.
+To get a list of all the customers in the table you would simply need to do:
-Where "customers" is the table name. As a response you would get a JSON formatted list of customers. Or say you only want to get one customer, then you would do this:
+ GET http://api.example.com/customers/
-```GET http://api.example.com/customers/123```
+As a response, you would get a JSON formatted list of customers.
-Where "123" here is the ID of the customer. For more information on using Arrest MySQL see the Usage section below.
+Or, if you only want to get one customer, then you would append the customer `id` to the URL:
+
+ GET http://api.example.com/customers/123/
##Requirements
-1. Apache Server with PHP 5+
-2. MySQL 5+
+- PHP 5.4+ & PDO
+- SQLite / MySQL / PostgreSQL
-##30 Second Installation
+##Installation
-Simply put these files into a folder somewhere on your server. Then edit config.php and fill in your database details and you are good to go.
+Edit `index.php` and change the `$dsn` variable located at the top, here are some examples:
-##Usage
+- SQLite: `$dsn = 'sqlite://./path/to/database.sqlite';`
+- MySQL: `$dsn = 'mysql://[user[:pass]@]host[:port]/db/;`
+- PostgreSQL: `$dsn = 'pgsql://[user[:pass]@]host[:port]/db/;`
-If you edit index.php you will see how incredibly simple it is to set up Arrest MySQL. Note that you are left to **provide your own authentication** for your API when using Arrest MySQL.
+If you want to restrict access to allow only specific IP addresses, add them to the `$clients` array:
```php
-set_table_index('my_table', 'some_index');
-
- $arrest->rest();
-
-} catch (Exception $e) {
- echo $e;
-}
-?>
+$clients = array
+(
+ '127.0.0.1',
+ '127.0.0.2',
+ '127.0.0.3',
+);
```
-###API Design
+After you're done editing the file, place it in a public directory (feel free to change the filename).
-The actual API design is very straight forward and follows the design patterns of most other API's.
+If you're using Apache, you can use the following `mod_rewrite` rules in a `.htaccess` file:
-```
-create > POST /table
-read > GET /table[/id]
-update > PUT /table/id
-delete > DELETE /table/id
+```apache
+
+ RewriteEngine On
+ RewriteCond %{REQUEST_FILENAME} !-d
+ RewriteCond %{REQUEST_FILENAME} !-f
+ RewriteRule ^(.*)$ index.php/$1 [L,QSA]
+
```
-To put this into practice below are some example of how you would use an Arrest MySQL API:
+***Nota bene:*** You must access the file directly, including it from another file won't work.
-```
-// Get all rows from the "customers" table
-GET http://api.example.com/customers
-// Get a single row from the "customers" table (where "123" is the ID)
-GET http://api.example.com/customers/123
-// Get 50 rows from the "customers" table
-GET http://api.example.com/customers?limit=50
-// Get 50 rows from the "customers" table ordered by the "date" field
-GET http://api.example.com/customers?limit=50&order_by=date&order=desc
-
-// Create a new row in the "customers" table where the POST data corresponds to the database fields
-POST http://api.example.com/customers
-
-// Update customer "123" in the "customers" table where the PUT data corresponds to the database fields
-PUT http://api.example.com/customers/123
-
-// Delete customer "123" from the "customers" table
-DELETE http://api.example.com/customers/123
-```
+##API Design
+
+The actual API design is very straightforward and follows the design patterns of the majority of APIs.
+
+ (C)reate > POST /table
+ (R)ead > GET /table[/id]
+ (R)ead > GET /table[/column/content]
+ (U)pdate > PUT /table/id
+ (D)elete > DELETE /table/id
+
+To put this into practice below are some example of how you would use the ArrestDB API:
+
+ # Get all rows from the "customers" table
+ GET http://api.example.com/customers/
+
+ # Get a single row from the "customers" table (where "123" is the ID)
+ GET http://api.example.com/customers/123/
+
+ # Get all rows from the "customers" table where the "country" field matches "Australia" (`LIKE`)
+ GET http://api.example.com/customers/country/Australia/
+
+ # Get 50 rows from the "customers" table
+ GET http://api.example.com/customers/?limit=50
+
+ # Get 50 rows from the "customers" table ordered by the "date" field
+ GET http://api.example.com/customers/?limit=50&by=date&order=desc
+
+ # Create a new row in the "customers" table where the POST data corresponds to the database fields
+ POST http://api.example.com/customers/
+
+ # Update customer "123" in the "customers" table where the PUT data corresponds to the database fields
+ PUT http://api.example.com/customers/123/
+
+ # Delete customer "123" from the "customers" table
+ DELETE http://api.example.com/customers/123/
+
+Please note that `GET` calls accept the following query string variables:
+
+- `by` (column to order by)
+ - `order` (order direction: `ASC` or `DESC`)
+- `limit` (`LIMIT x` SQL clause)
+ - `offset` (`OFFSET x` SQL clause)
+
+Additionally, `POST` and `PUT` requests accept JSON-encoded and/or zlib-compressed payloads.
-###Responses
+> `POST` and `PUT` requests are only able to parse data encoded in `application/x-www-form-urlencoded`.
+> Support for `multipart/form-data` payloads will be added in the future.
-All responses are in the JSON format. For example a GET response from the "customers" table might look like:
+If your client does not support certain methods, you can use the `X-HTTP-Method-Override` header:
+
+- `PUT` = `POST` + `X-HTTP-Method-Override: PUT`
+- `DELETE` = `GET` + `X-HTTP-Method-Override: DELETE`
+
+Alternatively, you can also override the HTTP method by using the `_method` query string parameter.
+
+Since 1.5.0, it's also possible to atomically `INSERT` a batch of records by POSTing an array of arrays.
+
+##Responses
+
+All responses are in the JSON format. A `GET` response from the `customers` table might look like this:
```json
[
@@ -112,52 +136,73 @@ All responses are in the JSON format. For example a GET response from the "custo
]
```
-Successful POST, PUT, and DELETE responses will look like
+Successful `POST` responses will look like:
```json
{
"success": {
- "message": "Success",
- "code": 200
+ "code": 201,
+ "status": "Created"
}
}
```
-Errors are in the format:
+Successful `PUT` and `DELETE` responses will look like:
+
+```json
+{
+ "success": {
+ "code": 200,
+ "status": "OK"
+ }
+}
+```
+
+Errors are expressed in the format:
```json
{
"error": {
- "message": "No Content",
- "code": 204
+ "code": 400,
+ "status": "Bad Request"
}
}
```
The following codes and message are avaiable:
-* 200 Success
-* 204 No Content
-* 404 Not Found
+* `200` OK
+* `201` Created
+* `204` No Content
+* `400` Bad Request
+* `403` Forbidden
+* `404` Not Found
+* `409` Conflict
+* `503` Service Unavailable
-##Credits
+Also, if the `callback` query string is set *and* is valid, the returned result will be a [JSON-P response](http://en.wikipedia.org/wiki/JSONP):
-Arrest MySQL was created by [Gilbert Pellegrom](http://gilbert.pellegrom.me) from [Dev7studios](http://dev7studios.com).
+```javascript
+callback(JSON);
+```
-Please contribute by [reporting bugs](Arrest-MySQL/issues) and submitting [pull requests](Arrest-MySQL/pulls).
+Ajax-like requests will be minified, whereas normal browser requests will be human-readable.
-##License (MIT)
+##Changelog
+
+- **1.2.0** ~~support for JSON payloads in `POST` and `PUT` (optionally gzipped)~~
+- **1.3.0** ~~support for JSON-P responses~~
+- **1.4.0** ~~support for HTTP method overrides using the `X-HTTP-Method-Override` header~~
+- **1.5.0** ~~support for bulk inserts in `POST`~~
+- **1.6.0** ~~added support for PostgreSQL~~
+- **1.7.0** ~~fixed PostgreSQL connection bug, other minor improvements~~
+- **1.8.0** ~~fixed POST / PUT bug introduced in 1.5.0~~
+- **1.9.0** ~~updated to PHP 5.4 short array syntax~~
-Copyright © 2013 Dev7studios
+##Credits
-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:
+ArrestDB is a complete rewrite of [Arrest-MySQL](https://github.com/gilbitron/Arrest-MySQL) with several optimizations and additional features.
-The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+##License (MIT)
-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.
+Copyright (c) 2014 Alix Axel (alix.axel+github@gmail.com).
\ No newline at end of file
diff --git a/config.php b/config.php
deleted file mode 100644
index e0812d9c..00000000
--- a/config.php
+++ /dev/null
@@ -1,11 +0,0 @@
- 'localhost', // Usually localhost
- 'database' => '', // The database name
- 'username' => '', // The database username
- 'password' => '', // The database password
- 'verbose' => false // If true errors will be shown
-);
-
-?>
\ No newline at end of file
diff --git a/index.php b/index.php
index c00d6d77..7d291f18 100644
--- a/index.php
+++ b/index.php
@@ -1,28 +1,578 @@
set_table_index('my_table', 'some_index');
-
- $arrest->rest();
-
-} catch (Exception $e) {
- echo $e;
+$dsn = '';
+$clients = [];
+
+/**
+* The MIT License
+* http://creativecommons.org/licenses/MIT/
+*
+* ArrestDB 1.9.0 (github.com/alixaxel/ArrestDB/)
+* Copyright (c) 2014 Alix Axel
+**/
+
+if (strcmp(PHP_SAPI, 'cli') === 0)
+{
+ exit('ArrestDB should not be run from CLI.' . PHP_EOL);
+}
+
+if ((empty($clients) !== true) && (in_array($_SERVER['REMOTE_ADDR'], (array) $clients) !== true))
+{
+ exit(ArrestDB::Reply(ArrestDB::$HTTP[403]));
+}
+
+else if (ArrestDB::Query($dsn) === false)
+{
+ exit(ArrestDB::Reply(ArrestDB::$HTTP[503]));
+}
+
+if (array_key_exists('_method', $_GET) === true)
+{
+ $_SERVER['REQUEST_METHOD'] = strtoupper(trim($_GET['_method']));
+}
+
+else if (array_key_exists('HTTP_X_HTTP_METHOD_OVERRIDE', $_SERVER) === true)
+{
+ $_SERVER['REQUEST_METHOD'] = strtoupper(trim($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']));
+}
+
+ArrestDB::Serve('GET', '/(#any)/(#any)/(#any)', function ($table, $id, $data)
+{
+ $query = array
+ (
+ sprintf('SELECT * FROM "%s"', $table),
+ sprintf('WHERE "%s" %s ?', $id, (ctype_digit($data) === true) ? '=' : 'LIKE'),
+ );
+
+ if (isset($_GET['by']) === true)
+ {
+ if (isset($_GET['order']) !== true)
+ {
+ $_GET['order'] = 'ASC';
+ }
+
+ $query[] = sprintf('ORDER BY "%s" %s', $_GET['by'], $_GET['order']);
+ }
+
+ if (isset($_GET['limit']) === true)
+ {
+ $query[] = sprintf('LIMIT %u', $_GET['limit']);
+
+ if (isset($_GET['offset']) === true)
+ {
+ $query[] = sprintf('OFFSET %u', $_GET['offset']);
+ }
+ }
+
+ $query = sprintf('%s;', implode(' ', $query));
+ $result = ArrestDB::Query($query, $data);
+
+ if ($result === false)
+ {
+ $result = ArrestDB::$HTTP[404];
+ }
+
+ else if (empty($result) === true)
+ {
+ $result = ArrestDB::$HTTP[204];
+ }
+
+ return ArrestDB::Reply($result);
+});
+
+ArrestDB::Serve('GET', '/(#any)/(#num)?', function ($table, $id = null)
+{
+ $query = array
+ (
+ sprintf('SELECT * FROM "%s"', $table),
+ );
+
+ if (isset($id) === true)
+ {
+ $query[] = sprintf('WHERE "%s" = ? LIMIT 1', 'id');
+ }
+
+ else
+ {
+ if (isset($_GET['by']) === true)
+ {
+ if (isset($_GET['order']) !== true)
+ {
+ $_GET['order'] = 'ASC';
+ }
+
+ $query[] = sprintf('ORDER BY "%s" %s', $_GET['by'], $_GET['order']);
+ }
+
+ if (isset($_GET['limit']) === true)
+ {
+ $query[] = sprintf('LIMIT %u', $_GET['limit']);
+
+ if (isset($_GET['offset']) === true)
+ {
+ $query[] = sprintf('OFFSET %u', $_GET['offset']);
+ }
+ }
+ }
+
+ $query = sprintf('%s;', implode(' ', $query));
+ $result = (isset($id) === true) ? ArrestDB::Query($query, $id) : ArrestDB::Query($query);
+
+ if ($result === false)
+ {
+ $result = ArrestDB::$HTTP[404];
+ }
+
+ else if (empty($result) === true)
+ {
+ $result = ArrestDB::$HTTP[204];
+ }
+
+ else if (isset($id) === true)
+ {
+ $result = array_shift($result);
+ }
+
+ return ArrestDB::Reply($result);
+});
+
+ArrestDB::Serve('DELETE', '/(#any)/(#num)', function ($table, $id)
+{
+ $query = array
+ (
+ sprintf('DELETE FROM "%s" WHERE "%s" = ?', $table, 'id'),
+ );
+
+ $query = sprintf('%s;', implode(' ', $query));
+ $result = ArrestDB::Query($query, $id);
+
+ if ($result === false)
+ {
+ $result = ArrestDB::$HTTP[404];
+ }
+
+ else if (empty($result) === true)
+ {
+ $result = ArrestDB::$HTTP[204];
+ }
+
+ else
+ {
+ $result = ArrestDB::$HTTP[200];
+ }
+
+ return ArrestDB::Reply($result);
+});
+
+if (in_array($http = strtoupper($_SERVER['REQUEST_METHOD']), ['POST', 'PUT']) === true)
+{
+ if (preg_match('~^\x78[\x01\x5E\x9C\xDA]~', $data = file_get_contents('php://input')) > 0)
+ {
+ $data = gzuncompress($data);
+ }
+
+ if ((array_key_exists('CONTENT_TYPE', $_SERVER) === true) && (empty($data) !== true))
+ {
+ if (strncasecmp($_SERVER['CONTENT_TYPE'], 'application/json', 16) === 0)
+ {
+ $GLOBALS['_' . $http] = json_decode($data, true);
+ }
+
+ else if ((strncasecmp($_SERVER['CONTENT_TYPE'], 'application/x-www-form-urlencoded', 33) === 0) && (strncasecmp($_SERVER['REQUEST_METHOD'], 'PUT', 3) === 0))
+ {
+ parse_str($data, $GLOBALS['_' . $http]);
+ }
+ }
+
+ if ((isset($GLOBALS['_' . $http]) !== true) || (is_array($GLOBALS['_' . $http]) !== true))
+ {
+ $GLOBALS['_' . $http] = [];
+ }
+
+ unset($data);
}
-?>
\ No newline at end of file
+ArrestDB::Serve('POST', '/(#any)', function ($table)
+{
+ if (empty($_POST) === true)
+ {
+ $result = ArrestDB::$HTTP[204];
+ }
+
+ else if (is_array($_POST) === true)
+ {
+ $queries = [];
+
+ if (count($_POST) == count($_POST, COUNT_RECURSIVE))
+ {
+ $_POST = [$_POST];
+ }
+
+ foreach ($_POST as $row)
+ {
+ $data = [];
+
+ foreach ($row as $key => $value)
+ {
+ $data[sprintf('"%s"', $key)] = $value;
+ }
+
+ $query = array
+ (
+ sprintf('INSERT INTO "%s" (%s) VALUES (%s)', $table, implode(', ', array_keys($data)), implode(', ', array_fill(0, count($data), '?'))),
+ );
+
+ $queries[] = array
+ (
+ sprintf('%s;', implode(' ', $query)),
+ $data,
+ );
+ }
+
+ if (count($queries) > 1)
+ {
+ ArrestDB::Query()->beginTransaction();
+
+ while (is_null($query = array_shift($queries)) !== true)
+ {
+ if (($result = ArrestDB::Query($query[0], $query[1])) === false)
+ {
+ ArrestDB::Query()->rollBack(); break;
+ }
+ }
+
+ if (($result !== false) && (ArrestDB::Query()->inTransaction() === true))
+ {
+ $result = ArrestDB::Query()->commit();
+ }
+ }
+
+ else if (is_null($query = array_shift($queries)) !== true)
+ {
+ $result = ArrestDB::Query($query[0], $query[1]);
+ }
+
+ if ($result === false)
+ {
+ $result = ArrestDB::$HTTP[409];
+ }
+
+ else
+ {
+ $result = ArrestDB::$HTTP[201];
+ }
+ }
+
+ return ArrestDB::Reply($result);
+});
+
+ArrestDB::Serve('PUT', '/(#any)/(#num)', function ($table, $id)
+{
+ if (empty($GLOBALS['_PUT']) === true)
+ {
+ $result = ArrestDB::$HTTP[204];
+ }
+
+ else if (is_array($GLOBALS['_PUT']) === true)
+ {
+ $data = [];
+
+ foreach ($GLOBALS['_PUT'] as $key => $value)
+ {
+ $data[$key] = sprintf('"%s" = ?', $key);
+ }
+
+ $query = array
+ (
+ sprintf('UPDATE "%s" SET %s WHERE "%s" = ?', $table, implode(', ', $data), 'id'),
+ );
+
+ $query = sprintf('%s;', implode(' ', $query));
+ $result = ArrestDB::Query($query, $GLOBALS['_PUT'], $id);
+
+ if ($result === false)
+ {
+ $result = ArrestDB::$HTTP[409];
+ }
+
+ else
+ {
+ $result = ArrestDB::$HTTP[200];
+ }
+ }
+
+ return ArrestDB::Reply($result);
+});
+
+exit(ArrestDB::Reply(ArrestDB::$HTTP[400]));
+
+class ArrestDB
+{
+ public static $HTTP = [
+ 200 => [
+ 'success' => [
+ 'code' => 200,
+ 'status' => 'OK',
+ ],
+ ],
+ 201 => [
+ 'success' => [
+ 'code' => 201,
+ 'status' => 'Created',
+ ],
+ ],
+ 204 => [
+ 'error' => [
+ 'code' => 204,
+ 'status' => 'No Content',
+ ],
+ ],
+ 400 => [
+ 'error' => [
+ 'code' => 400,
+ 'status' => 'Bad Request',
+ ],
+ ],
+ 403 => [
+ 'error' => [
+ 'code' => 403,
+ 'status' => 'Forbidden',
+ ],
+ ],
+ 404 => [
+ 'error' => [
+ 'code' => 404,
+ 'status' => 'Not Found',
+ ],
+ ],
+ 409 => [
+ 'error' => [
+ 'code' => 409,
+ 'status' => 'Conflict',
+ ],
+ ],
+ 503 => [
+ 'error' => [
+ 'code' => 503,
+ 'status' => 'Service Unavailable',
+ ],
+ ],
+ ];
+
+ public static function Query($query = null)
+ {
+ static $db = null;
+ static $result = [];
+
+ try
+ {
+ if (isset($db, $query) === true)
+ {
+ if (strncasecmp($db->getAttribute(\PDO::ATTR_DRIVER_NAME), 'mysql', 5) === 0)
+ {
+ $query = strtr($query, '"', '`');
+ }
+
+ if (empty($result[$hash = crc32($query)]) === true)
+ {
+ $result[$hash] = $db->prepare($query);
+ }
+
+ $data = array_slice(func_get_args(), 1);
+
+ if (count($data, COUNT_RECURSIVE) > count($data))
+ {
+ $data = iterator_to_array(new \RecursiveIteratorIterator(new \RecursiveArrayIterator($data)), false);
+ }
+
+ if ($result[$hash]->execute($data) === true)
+ {
+ $sequence = null;
+
+ if ((strncmp($db->getAttribute(\PDO::ATTR_DRIVER_NAME), 'pgsql', 5) === 0) && (sscanf($query, 'INSERT INTO %s', $sequence) > 0))
+ {
+ $sequence = sprintf('%s_id_seq', trim($sequence, '"'));
+ }
+
+ switch (strstr($query, ' ', true))
+ {
+ case 'INSERT':
+ case 'REPLACE':
+ return $db->lastInsertId($sequence);
+
+ case 'UPDATE':
+ case 'DELETE':
+ return $result[$hash]->rowCount();
+
+ case 'SELECT':
+ case 'EXPLAIN':
+ case 'PRAGMA':
+ case 'SHOW':
+ return $result[$hash]->fetchAll();
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ else if (isset($query) === true)
+ {
+ $options = array
+ (
+ \PDO::ATTR_CASE => \PDO::CASE_NATURAL,
+ \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
+ \PDO::ATTR_EMULATE_PREPARES => false,
+ \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
+ \PDO::ATTR_ORACLE_NULLS => \PDO::NULL_NATURAL,
+ \PDO::ATTR_STRINGIFY_FETCHES => false,
+ );
+
+ if (preg_match('~^sqlite://([[:print:]]++)$~i', $query, $dsn) > 0)
+ {
+ $options += array
+ (
+ \PDO::ATTR_TIMEOUT => 3,
+ );
+
+ $db = new \PDO(sprintf('sqlite:%s', $dsn[1]), null, null, $options);
+ $pragmas = array
+ (
+ 'automatic_index' => 'ON',
+ 'cache_size' => '8192',
+ 'foreign_keys' => 'ON',
+ 'journal_size_limit' => '67110000',
+ 'locking_mode' => 'NORMAL',
+ 'page_size' => '4096',
+ 'recursive_triggers' => 'ON',
+ 'secure_delete' => 'ON',
+ 'synchronous' => 'NORMAL',
+ 'temp_store' => 'MEMORY',
+ 'journal_mode' => 'WAL',
+ 'wal_autocheckpoint' => '4096',
+ );
+
+ if (strncasecmp(PHP_OS, 'WIN', 3) !== 0)
+ {
+ $memory = 131072;
+
+ if (($page = intval(shell_exec('getconf PAGESIZE'))) > 0)
+ {
+ $pragmas['page_size'] = $page;
+ }
+
+ if (is_readable('/proc/meminfo') === true)
+ {
+ if (is_resource($handle = fopen('/proc/meminfo', 'rb')) === true)
+ {
+ while (($line = fgets($handle, 1024)) !== false)
+ {
+ if (sscanf($line, 'MemTotal: %d kB', $memory) == 1)
+ {
+ $memory = round($memory / 131072) * 131072; break;
+ }
+ }
+
+ fclose($handle);
+ }
+ }
+
+ $pragmas['cache_size'] = intval($memory * 0.25 / ($pragmas['page_size'] / 1024));
+ $pragmas['wal_autocheckpoint'] = $pragmas['cache_size'] / 2;
+ }
+
+ foreach ($pragmas as $key => $value)
+ {
+ $db->exec(sprintf('PRAGMA %s=%s;', $key, $value));
+ }
+ }
+
+ else if (preg_match('~^(mysql|pgsql)://(?:(.+?)(?::(.+?))?@)?([^/:@]++)(?::(\d++))?/(\w++)/?$~i', $query, $dsn) > 0)
+ {
+ if (strncasecmp($query, 'mysql', 5) === 0)
+ {
+ $options += array
+ (
+ \PDO::ATTR_AUTOCOMMIT => true,
+ \PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES "utf8" COLLATE "utf8_general_ci", time_zone = "+00:00";',
+ \PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,
+ );
+ }
+
+ $db = new \PDO(sprintf('%s:host=%s;port=%s;dbname=%s', $dsn[1], $dsn[4], $dsn[5], $dsn[6]), $dsn[2], $dsn[3], $options);
+ }
+ }
+ }
+
+ catch (\Exception $exception)
+ {
+ return false;
+ }
+
+ return (isset($db) === true) ? $db : false;
+ }
+
+ public static function Reply($data)
+ {
+ $bitmask = 0;
+ $options = ['UNESCAPED_SLASHES', 'UNESCAPED_UNICODE'];
+
+ if (empty($_SERVER['HTTP_X_REQUESTED_WITH']) === true)
+ {
+ $options[] = 'PRETTY_PRINT';
+ }
+
+ foreach ($options as $option)
+ {
+ $bitmask |= (defined('JSON_' . $option) === true) ? constant('JSON_' . $option) : 0;
+ }
+
+ if (($result = json_encode($data, $bitmask)) !== false)
+ {
+ $callback = null;
+
+ if (array_key_exists('callback', $_GET) === true)
+ {
+ $callback = trim(preg_replace('~[^[:alnum:]\[\]_.]~', '', $_GET['callback']));
+
+ if (empty($callback) !== true)
+ {
+ $result = sprintf('%s(%s);', $callback, $result);
+ }
+ }
+
+ if (headers_sent() !== true)
+ {
+ header(sprintf('Content-Type: application/%s; charset=utf-8', (empty($callback) === true) ? 'json' : 'javascript'));
+ }
+ }
+
+ return $result;
+ }
+
+ public static function Serve($on = null, $route = null, $callback = null)
+ {
+ static $root = null;
+
+ if (isset($_SERVER['REQUEST_METHOD']) !== true)
+ {
+ $_SERVER['REQUEST_METHOD'] = 'CLI';
+ }
+
+ if ((empty($on) === true) || (strcasecmp($_SERVER['REQUEST_METHOD'], $on) === 0))
+ {
+ if (is_null($root) === true)
+ {
+ $root = preg_replace('~/++~', '/', substr($_SERVER['PHP_SELF'], strlen($_SERVER['SCRIPT_NAME'])) . '/');
+ }
+
+ if (preg_match('~^' . str_replace(['#any', '#num'], ['[^/]++', '[0-9]++'], $route) . '~i', $root, $parts) > 0)
+ {
+ return (empty($callback) === true) ? true : exit(call_user_func_array($callback, array_slice($parts, 1)));
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/lib/arrest-mysql.php b/lib/arrest-mysql.php
deleted file mode 100644
index 2b870938..00000000
--- a/lib/arrest-mysql.php
+++ /dev/null
@@ -1,399 +0,0 @@
-
- * $arrest = new ArrestMySQL($db_config);
- * $arrest->rest();
- *
- *
- * Author: Gilbert Pellegrom
- * Website: http://dev7studios.com
- * Date: Jan 2013
- * Version 1.0
- */
-require('lib/db.php');
-
-class ArrestMySQL {
-
- /**
- * The instance of Database
- *
- * @var Database
- */
- private $db;
- /**
- * The structure of the database
- *
- * @var array
- */
- private $db_structure;
- /**
- * The URI segments
- *
- * @var array
- */
- private $segments;
- /**
- * Array of custom table indexes
- *
- * @var array
- */
- private $table_index;
-
- /**
- * Create an instance, optionally setting a base URI
- *
- * @param array $db_config An array of database config options. Format:
- *
- * $db_config = array(
- * 'server' => 'localhost',
- * 'database' => '',
- * 'username' => '',
- * 'password' => '',
- * 'verbose' => false
- * );
- *
- * @param string $base_uri Optional base URI if not in root folder
- * @access public
- */
- public function __construct($db_config, $base_uri = '')
- {
- $this->db = new Database($db_config);
- if(!$this->db->init()) throw new Exception($this->db->get_error());
-
- $this->db_structure = $this->map_db($db_config['database']);
- $this->segments = $this->get_uri_segments($base_uri);
- $this->table_index = array();
- }
-
- /**
- * Handle the REST calls and map them to corresponding CRUD
- *
- * @access public
- */
- public function rest()
- {
- header('Content-type: application/json');
- /*
- create > POST /table
- read > GET /table[/id]
- update > PUT /table/id
- delete > DELETE /table/id
- */
- switch ($_SERVER['REQUEST_METHOD']) {
- case 'POST':
- $this->create();
- break;
- case 'GET':
- $this->read();
- break;
- case 'PUT':
- $this->update();
- break;
- case 'DELETE':
- $this->delete();
- break;
- }
- }
-
- /**
- * Add a custom index (usually primary key) for a table
- *
- * @param string $table Name of the table
- * @param string $field Name of the index field
- * @access public
- */
- public function set_table_index($table, $field)
- {
- $this->table_index[$table] = $field;
- }
-
- /**
- * Map the stucture of the MySQL db to an array
- *
- * @param string $database Name of the database
- * @return array Returns array of db structure
- * @access private
- */
- private function map_db($database)
- {
- // Map db structure to array
- $tables_arr = array();
- $this->db->query('SHOW TABLES FROM '. $database);
- while($table = $this->db->fetch_array()){
- if(isset($table['Tables_in_'. $database])){
- $table_name = $table['Tables_in_'. $database];
- $tables_arr[$table_name] = array();
- }
- }
- foreach($tables_arr as $table_name=>$val){
- $this->db->query('SHOW COLUMNS FROM '. $table_name);
- $fields = $this->db->fetch_all();
- $tables_arr[$table_name] = $fields;
- }
- return $tables_arr;
- }
-
- /**
- * Get the URI segments from the URL
- *
- * @param string $base_uri Optional base URI if not in root folder
- * @return array Returns array of URI segments
- * @access private
- */
- private function get_uri_segments($base_uri)
- {
- // Fix REQUEST_URI if required
- if(!isset($_SERVER['REQUEST_URI'])){
- $_SERVER['REQUEST_URI'] = substr($_SERVER['PHP_SELF'], 1);
- if(isset($_SERVER['QUERY_STRING'])) $_SERVER['REQUEST_URI'] .= '?'. $_SERVER['QUERY_STRING'];
- }
-
- $url = '';
- $request_url = $_SERVER['REQUEST_URI'];
- $script_url = $_SERVER['PHP_SELF'];
- $request_url = str_replace($base_uri, '', $request_url);
- if($request_url != $script_url) $url = trim(preg_replace('/'. str_replace('/', '\/', str_replace('index.php', '', $script_url)) .'/', '', $request_url, 1), '/');
- $url = rtrim(preg_replace('/\?.*/', '', $url), '/');
-
- return explode('/', $url);
- }
-
- /**
- * Get a URI segment
- *
- * @param int $index Index of the URI segment
- * @return mixed Returns URI segment or false if none exists
- * @access private
- */
- private function segment($index)
- {
- if(isset($this->segments[$index])) return $this->segments[$index];
- return false;
- }
-
- /**
- * Handles a POST and inserts into the database
- *
- * @access private
- */
- private function create()
- {
- $table = $this->segment(0);
-
- if(!$table || !isset($this->db_structure[$table])){
- $error = array('error' => array(
- 'message' => 'Not Found',
- 'code' => 404
- ));
- die(json_encode($error));
- }
-
- if($data = $this->_post()){
- $this->db->insert($table, $data)
- ->query();
- $success = array('success' => array(
- 'message' => 'Success',
- 'code' => 200
- ));
- die(json_encode($success));
- } else {
- $error = array('error' => array(
- 'message' => 'No Content',
- 'code' => 204
- ));
- die(json_encode($error));
- }
- }
-
- /**
- * Handles a GET and reads from the database
- *
- * @access private
- */
- private function read()
- {
- $table = $this->segment(0);
- $id = intval($this->segment(1));
-
- if(!$table || !isset($this->db_structure[$table])){
- $error = array('error' => array(
- 'message' => 'Not Found',
- 'code' => 404
- ));
- die(json_encode($error));
- }
-
- if($id && is_int($id)) {
- $index = 'id';
- if(isset($this->table_index[$table])) $index = $this->table_index[$table];
- $this->db->select('*')
- ->from($table)
- ->where($index, $id)
- ->query();
- if($result = $this->db->fetch_array()){
- die(json_encode($result));
- } else {
- $error = array('error' => array(
- 'message' => 'No Content',
- 'code' => 204
- ));
- die(json_encode($error));
- }
- } else {
- $this->db->select('*')
- ->from($table)
- ->order_by($this->_get('order_by'), $this->_get('order'))
- ->limit(intval($this->_get('limit')), intval($this->_get('offset')))
- ->query();
- if($result = $this->db->fetch_all()){
- die(json_encode($result));
- } else {
- $error = array('error' => array(
- 'message' => 'No Content',
- 'code' => 204
- ));
- die(json_encode($error));
- }
- }
- }
-
- /**
- * Handles a PUT and updates the database
- *
- * @access private
- */
- private function update()
- {
- $table = $this->segment(0);
- $id = intval($this->segment(1));
-
- if(!$table || !isset($this->db_structure[$table]) || !$id){
- $error = array('error' => array(
- 'message' => 'Not Found',
- 'code' => 404
- ));
- die(json_encode($error));
- }
-
- $index = 'id';
- if(isset($this->table_index[$table])) $index = $this->table_index[$table];
- $this->db->select('*')
- ->from($table)
- ->where($index, $id)
- ->query();
- if($result = $this->db->fetch_array()){
- $this->db->update($table)
- ->set($this->_put())
- ->where($index, $id)
- ->query();
- $success = array('success' => array(
- 'message' => 'Success',
- 'code' => 200
- ));
- die(json_encode($success));
- } else {
- $error = array('error' => array(
- 'message' => 'No Content',
- 'code' => 204
- ));
- die(json_encode($error));
- }
- }
-
- /**
- * Handles a DELETE and deletes from the database
- *
- * @access private
- */
- private function delete()
- {
- $table = $this->segment(0);
- $id = intval($this->segment(1));
-
- if(!$table || !isset($this->db_structure[$table]) || !$id){
- $error = array('error' => array(
- 'message' => 'Not Found',
- 'code' => 404
- ));
- die(json_encode($error));
- }
-
- $index = 'id';
- if(isset($this->table_index[$table])) $index = $this->table_index[$table];
- $this->db->select('*')
- ->from($table)
- ->where($index, $id)
- ->query();
- if($result = $this->db->fetch_array()){
- $this->db->delete($table)
- ->where($index, $id)
- ->query();
- $success = array('success' => array(
- 'message' => 'Success',
- 'code' => 200
- ));
- die(json_encode($success));
- } else {
- $error = array('error' => array(
- 'message' => 'No Content',
- 'code' => 204
- ));
- die(json_encode($error));
- }
- }
-
- /**
- * Helper function to retrieve $_GET variables
- *
- * @param string $index Optional $_GET index
- * @return mixed Returns the $_GET var at the specified index,
- * the whole $_GET array or false
- * @access private
- */
- private function _get($index = '')
- {
- if($index){
- if(isset($_GET[$index]) && $_GET[$index]) return strip_tags($_GET[$index]);
- } else {
- if(isset($_GET) && !empty($_GET)) return $_GET;
- }
- return false;
- }
-
- /**
- * Helper function to retrieve $_POST variables
- *
- * @param string $index Optional $_POST index
- * @return mixed Returns the $_POST var at the specified index,
- * the whole $_POST array or false
- * @access private
- */
- private function _post($index = '')
- {
- if($index){
- if(isset($_POST[$index]) && $_POST[$index]) return $_POST[$index];
- } else {
- if(isset($_POST) && !empty($_POST)) return $_POST;
- }
- return false;
- }
-
- /**
- * Helper function to retrieve PUT variables
- *
- * @return mixed Returns the contents of PUT as an array
- * @access private
- */
- private function _put()
- {
- $output = array();
- parse_str(file_get_contents('php://input'), $output);
- return $output;
- }
-
-}
-
-?>
\ No newline at end of file
diff --git a/lib/db.php b/lib/db.php
deleted file mode 100644
index 34c5389e..00000000
--- a/lib/db.php
+++ /dev/null
@@ -1,330 +0,0 @@
-_config = $config;
- }
-
- /*
- * Initializes the database. Checks the configuration, connects, selects database.
- */
- public function init() {
- if(!$this->__check_config()) {
- return false;
- }
-
- if(!$this->__connect()) {
- return false;
- }
-
- if(!$this->__select_db()) {
- return false;
- }
-
- return true;
- }
-
- /*
- * Checks the configuration for blanks.
- */
- private function __check_config() {
- $config = $this->_config;
-
- if(empty($config["server"]) || empty($config["username"]) || empty($config["database"])) {
- $this->_error = "Configuration details were blank.";
- return false;
- }
-
- $this->_verbose = ($config["verbose"]) ? true : false;
-
- return true;
- }
-
- /*
- * Connects to the database.
- */
- private function __connect() {
- $connection = @mysql_connect($this->_config["server"], $this->_config["username"], $this->_config["password"]);
-
- if(!$connection) {
- $this->_error = ($this->_verbose) ? mysql_error() : "Could not connect to database.";
- return false;
- }
-
- return true;
- }
-
- /*
- * Selects the database to be working with.
- */
- private function __select_db() {
- $database = @mysql_select_db($this->_config["database"]);
-
- if(!$database) {
- $this->_error = ($this->_verbose) ? mysql_error() : "Could not select database.";
- return false;
- }
-
- return true;
- }
-
- /*
- * SELECT starter. $fields can be either a string or an array of strings to select.
- */
- public function select($fields) {
- $query = "SELECT";
-
- if(!empty($fields) && !is_array($fields)) {
- $query .= " {$fields}";
- } else if(is_array($fields)) {
- $query .= " `";
- $query .= implode("`,`", $fields);
- $query .= "`";
- } else {
- $query .= " *";
- }
-
- $this->_buildQuery = $query;
- return $this;
- }
-
- /*
- * Adds where the SELECT is going to be coming from (table wise).
- * select("*")
- * select("username")
- * select(array("username", "password"))
- */
- public function from($table) {
- $this->_buildQuery .= " FROM `{$table}`";
- return $this;
- }
-
- /*
- * UPDATE starter.
- * update("users")
- */
- public function update($table) {
- $this->_buildQuery = "UPDATE `{$table}`";
- return $this;
- }
-
- /*
- * DELETE starter.
- * delete("users")
- */
- public function delete($table) {
- $this->_buildQuery = "DELETE FROM `{$table}`";
- return $this;
- }
-
- /*
- * INSERT starter. $data is an array matched columns to values:
- * $data = array("username" => "Caleb", "email" => "caleb@mingle-graphics.com");
- * insert("users", array("username" => "Caleb", "password" => "hash"))
- */
- public function insert($table, $data) {
- $query = "INSERT INTO `{$table}` (";
- $keys = array_keys($data);
- $values = array_values($data);
-
- $query .= implode(", ", $keys);
- $query .= ") VALUES (";
-
- $array = array();
-
- foreach($values as $value) {
- $array[] = "'{$value}'";
- }
-
- $query .= implode(", ", $array) . ")";
-
- $this->_buildQuery = $query;
- return $this;
- }
-
- /*
- * SET. $data is an array matched key => value.
- * set(array("username" => "Caleb"))
- */
- public function set($data) {
- if(!is_array($data)) return $this;
-
- $query = "SET ";
- $array = array();
-
- foreach($data as $key => $value) {
- $array[] = "`{$key}`='{$value}'";
- }
-
- $query .= implode(", ", $array);
-
- $this->_buildQuery .= " " . $query;
- return $this;
- }
-
- /*
- * WHERE. $fields and $values can either be strings or arrays based on how many you need.
- * $operators can be an array to add in <, >, etc. Must match the index for $fields and $values.
- * where("username", "Caleb")
- * where(array("username", "password"), array("Caleb", "testing"))
- * where(array("username", "level"), array("Caleb", "10"), array("=", "<"))
- */
- public function where($fields, $values, $operators = '') {
- if(!is_array($fields) && !is_array($values)) {
- $operator = (empty($operators)) ? '=' : $operators[0];
- $query = " WHERE `{$fields}` {$operator} '{$values}'";
- } else {
- $array = array_combine($fields, $values);
- $query = " WHERE ";
-
- $data = array();
- $counter = 0;
-
- foreach($array as $key => $value) {
-
- $operator = (!empty($operators) && !empty($operators[$counter])) ? $operators[$counter] : '=';
-
- $data[] = "`{$key}` {$operator} '{$value}'";
-
- $counter++;
- }
-
- $query .= implode(" AND ", $data);
- }
-
- $this->_buildQuery .= $query;
- return $this;
- }
-
- /*
- * Order By:
- * order_by("username", "asc")
- */
- public function order_by($field, $direction = 'asc') {
- if($field) $this->_buildQuery .= " ORDER BY `{$field}` " . strtoupper($direction);
- return $this;
- }
-
- /*
- * Limit:
- * limit(1)
- * limit(1, 0)
- */
- public function limit($max, $min = '0') {
- if($max) $this->_buildQuery .= " LIMIT {$min},{$max}";
- return $this;
- }
-
- /*
- * Will return the object of data from the query.
- */
- public function fetch_object() {
- $object = @mysql_fetch_object($this->_query);
-
- if(!$object && $this->_verbose) {
- $this->_error = mysql_error();
- }
-
- return $object;
- }
-
- /*
- * Will return the array of data from the query.
- */
- public function fetch_array() {
- $array = @mysql_fetch_array($this->_query);
- if($array){
- foreach($array as $key=>$val){
- if(is_numeric($key)){
- unset($array[$key]);
- }
- }
- }
-
- if(!$array && $this->_verbose) {
- $this->_error = mysql_error();
- }
-
- return $array;
- }
-
- public function fetch_all() {
- $results = array();
- while($array = @mysql_fetch_array($this->_query)){
- foreach($array as $key=>$val){
- if(is_numeric($key)){
- unset($array[$key]);
- }
- }
- $results[] = $array;
- }
-
-
-
- if(!$array && $this->_verbose) {
- $this->_error = mysql_error();
- }
-
- return $results;
- }
-
- /*
- * Will return the number or rows affected from the query.
- */
- public function num_rows() {
- $num = @mysql_num_rows($this->_query);
-
- if(!$num && $this->_verbose) {
- $this->_error = mysql_error();
- }
-
- return $num;
- }
-
- /*
- * If $query_text is blank, query will be performed on the built query stored.
- */
- public function query($query_text = '') {
- $query_text = ($query_text == '') ? $this->_buildQuery : $query_text;
-
- $query = @mysql_query($query_text);
-
- if(!$query && $this->_verbose) {
- echo "MySQL Error:
";
- echo "" . mysql_error() . "
";
- }
-
- $this->_query = $query;
-
- return $this;
- }
-
- /*
- * Will return the current built query story in $this->_buildQuery;
- */
- public function get_query() {
- return $this->_buildQuery;
- }
-
- /*
- * Will return the current stored error.
- */
- public function get_error() {
- return $this->_error;
- }
-}
-
-?>
\ No newline at end of file