diff --git a/.gitignore b/.gitignore
index 96374c4..5baa208 100644
--- a/.gitignore
+++ b/.gitignore
@@ -41,3 +41,7 @@ $RECYCLE.BIN/
Network Trash Folder
Temporary Items
.apdisk
+
+# Local configuration
+config.local.php
+
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..29febea
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "lib/phpqrcode-git"]
+ path = lib/phpqrcode-git
+ url = git://git.code.sf.net/p/phpqrcode/git
diff --git a/.htaccess b/.htaccess
index 9862c54..bfe19a4 100644
--- a/.htaccess
+++ b/.htaccess
@@ -1,2 +1,2 @@
Options All -Indexes
-DirectoryIndex api.php
+DirectoryIndex index.php
diff --git a/CONSTANTS.php b/CONSTANTS.php
index 32a6cc5..a053d7d 100644
--- a/CONSTANTS.php
+++ b/CONSTANTS.php
@@ -3,27 +3,29 @@
define('API_SUCCESS_LIST', 1000);
define('API_SUCCESS_LIST_EMPTY', 1001);
- define('API_SUCCESS_UPDATE', 1002);
- define('API_SUCCESS_FAVORITE', 1003);
- define('API_SUCCESS_DELETE', 1004);
- define('API_SUCCESS_SAVE', 1005);
+ define('API_SUCCESS_UPDATE', 1002);
+ define('API_SUCCESS_FAVORITE', 1003);
+ define('API_SUCCESS_DELETE', 1004);
+ define('API_SUCCESS_SAVE', 1005);
define('API_SUCCESS_CLEAR', 1006);
define('API_ERROR_SERVER', 5000);
- define('API_ERROR_404', 5001);
- define('API_ERROR_403', 5002);
- define('API_ERROR_MISSING_FUNCTION', 5003);
- define('API_ERROR_NO_DATABASE', 5004);
- define('API_ERROR_CONFIG', 5005);
- define('API_ERROR_UNKNOWN', 5006);
+ define('API_ERROR_404', 5001);
+ define('API_ERROR_403', 5002);
+ define('API_ERROR_MISSING_FUNCTION', 5003);
+ define('API_ERROR_NO_DATABASE', 5004);
+ define('API_ERROR_CONFIG', 5005);
+ define('API_ERROR_UNKNOWN', 5006);
define('API_ERROR_DATABASE_CONNECT', 5012);
define('API_ERROR_MISSING_PARAMETER', 5013);
- define('API_ERROR_FUNCTION_NOT_SPECIFIED', 5014);
+ define('API_ERROR_FUNCTION_NOT_SPECIFIED', 5014);
define('API_ERROR_NOT_CONFIGURED', 5015);
define('API_ERROR_UPDATE_', 6001);
- define('API_ERROR_FAVORITE', 6002);
- define('API_ERROR_DELETE', 6003);
- define('API_ERROR_SAVE', 6004);
+ define('API_ERROR_FAVORITE', 6002);
+ define('API_ERROR_DELETE', 6003);
+ define('API_ERROR_SAVE', 6004);
define('API_ERROR_CLEAR', 6005);
+ define('API_ERROR_LIST', 6006);
+ define('API_ERROR_QRCODE', 6007);
?>
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..8a6b7f3
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,30 @@
+FROM alpine
+
+RUN apk update \
+ && apk add \
+ nginx \
+ php-fpm \
+ php-json \
+ php-pdo \
+ php-pdo_sqlite \
+ && rm -rf /var/cache/apk/*
+
+COPY . /shoppinglist
+
+VOLUME [ "/shoppinglist/data" ]
+
+RUN chown -R nginx:www-data /shoppinglist/ \
+ && chown -R nginx:www-data /shoppinglist/data/
+
+# Copy scripts
+COPY docker/nginx.conf /etc/nginx/
+COPY docker/php-fpm.conf /etc/php/
+COPY docker/entrypoint.sh /
+
+
+EXPOSE 80
+
+ENV API_KEY=""
+
+
+CMD [ "/entrypoint.sh" ]
diff --git a/INSTALL.php b/INSTALL.php
index fa05722..f69d732 100644
--- a/INSTALL.php
+++ b/INSTALL.php
@@ -1,288 +1,301 @@
-
-
-
-
-
-
- Install ShoppingList
-
-
-
-
- =') AND version_compare(phpversion(), '5.5', '<'))
- require_once('func.inc.php');
+
+
+
+
+
+
+
Install ShoppingList
+
+
+
+
+=') AND version_compare(phpversion(), '5.5', '<'))
+ require_once('func.inc.php');
- $htaccess = "Options ALL -Indexes\nDirectoryIndex api.php";
- if($dbtype === 'SQLite'){
- $htaccess .= "\n
\norder allow,deny\ndeny from all\n \n";
- }
-
- file_put_contents('.htaccess', $htaccess);
- //create config file value
+//Start on submit form
+if(isset($_POST['createConfig'])) {
+ function generateRandomPWD($length = 25) {
+ $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
+ $charactersLength = strlen($characters);
+ $randomString = '';
+ for ($i = 0; $i < $length; $i++) {
+ $randomString .= $characters[rand(0, $charactersLength - 1)];
+ }
+ return $randomString;
+ }
- if($createDBUser == "true") {
- $config_access = '
- "'.$filename.'",
- );
- //only for MySQL
- $MySQLConfig = array(
- \'host\' => "'.$dbhost.'",
- \'db\' => "shopping",
- \'user\' => "ShoppingListUser",
- \'password\' => "'.$dbrandom_pwd.'",
- );
-?>';
+ $qr_code = "{ \"url\": \"$api_url\", \"apikey\": \"$_apikey\", \"ssl\": $ssl}";
- //mysql dump for root access
- $dbdump_access = "CREATE USER 'ShoppingListUser'@'".$dbhost."' IDENTIFIED BY '".$dbrandom_pwd."';";
- $dbdump_access2 = "CREATE DATABASE shopping;";
- $dbdump_access3 = "GRANT ALL PRIVILEGES ON shopping.ShoppingList TO 'ShoppingListUser'@'".$dbhost."';";
+ ob_start();
+ QRCode::png($qr_code);
+ $image_string = base64_encode(ob_get_clean());
+ header('Content-Type: text/html');
- }
- else
- {
+ return $image_string;
+ }
- $config = '
- "'.$filename.'",
- );
- //only for MySQL
- $MySQLConfig = array(
- \'host\' => "'.$dbhost.'",
- \'db\' => "'.$dbname.'",
- \'user\' => "'.$dbuser.'",
- \'password\' => "'.$dbpassword.'",
- );
-?>';
- }
+ $filename = 'shoppinglist.sqlite';
+ //set up .htaccess rules for sqlite database if sqlite is used
- //mysql dump
- $dbdump = "
- CREATE TABLE ShoppingList (
- item VARCHAR(255),
- count VARCHAR(255),
- RID int(11) NOT NULL auto_increment,
- primary KEY (RID))
- ENGINE=InnoDB DEFAULT COLLATE=utf8_general_ci;";
+ $htaccess = "Options ALL -Indexes\nDirectoryIndex api.php";
+ if($dbtype === 'SQLite') {
+ $htaccess .= "\n
\norder allow,deny\ndeny from all\n \n";
+ }
- //try to open/create config.php file
- //success: write config value
- //error: message and get out config.php value
- if($handler = fopen('config.php', 'w')) {
- if($createDBUser == "true")
- fwrite($handler, $config_access);
- else
- fwrite($handler, $config);
-
- fclose($handler);
- }
- else
- {
- echo <<
Config.php
- It was not possible to create the config.php file. Please copy the code and paste it in the file.
-
+
+
-
+
diff --git a/README.md b/README.md
index 3bd2e45..65e2523 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,43 @@
# ShoppingList Backend
+##Docker Deployment
+For a fast deployment, use Docker!
+For now the container is only compatible with an sqlite database.
+
+###Build from source
+You can build the container from sources:
+
+> git clone https://github.com/GroundApps/ShoppingList_Backend/tree/devel
+> docker build -t shoppinglist .
+
+###Pull an already built container
+Or, if you are feeling lazy, just pull an already built container from the
+[Docker hub](https://hub.docker.com/r/lertsenem/shoppinglist):
+
+> docker pull lertsenem/shoppinglist
+
+###Run the container
+Finally, run the container with a command like this:
+
+> $ docker run \
+> -d \
+> --name shoppinglist \
+> -p 8000:80 \
+> -e "API_KEY=mysecretpassword" \
+> -v /tmp/sl_data:/shoppinglist/data \
+> lertsenem/shoppinglist
+
+* use the env variable 'API_KEY' to set the app password ;
+* mount the volume '/shoppinglist/data' to persist the sqlite database.
+
+Note that in regard to this last point you can (and should) use a volume-only
+container for portability reasons.
+
##Installation
###Requirements
* PHP >= 5.3.7
+* php-gd
* Apache Websever (we recommend a TLS Connection to the Server)
* MySQL or SQLite (you can select in the Install Script)
@@ -14,8 +48,14 @@ You can either use MySQL or SQLite. SQLite is easier to set up.
If you use Ubuntu you can use the [PPA](https://launchpad.net/~jklmnn/+archive/ubuntu/groundapps) by executing `add-apt-repository ppa:jklmnn/groundapps`.
To install the backend manually , go to http://your.path/.
Fill up the form, click on create!
+=======
+To install the backend manually, go to http://your.path/
+Fill up the form, click on create!
That's all.
+https is currently not supported for self-signed certificates.
+See the [wiki](https://github.com/GroundApps/ShoppingList/wiki) for more informations about installation and roadmap.
+
## Feedback
Please do never hesitate to open an issue!
I know there a some bugs, most likely because I had no idea how to do it otherwise and therefore had to use a workaround.
diff --git a/UPDATE.php b/UPDATE.php
new file mode 100644
index 0000000..0a11b4e
--- /dev/null
+++ b/UPDATE.php
@@ -0,0 +1,361 @@
+DEBUG";
+}
+if(ISSET($_POST['update'])){
+ update($_POST['zipURL'], $_POST['newVersion']);
+} else {
+ status();
+}
+
+function echoHead(){
+echo <<
+
+
+
+
+HEAD;
+}
+function echoStyle(){
+<<
+STYLE;
+echo <<
+
+
+
+BOOTSTRAP;
+}
+
+function echoPageStart(){
+echo <<
+PAGE_START;
+}
+
+function echoPageEnd(){
+echo <<
+PAGE_END;
+}
+
+function echoUpdateAvailable($curVersion, $newVersion, $updateText, $zipURL, $disabled){
+ echo <<
+
+
+
Update Available - ShoppingList Backend
+
+
+ Release Note: $updateText
+ Download URL: $zipURL
+
+
+
+
+ Update to version $newVersion
+
+
+
+STATUS;
+}
+
+function echoAllGood(){
+ echo <<
+
+
+
ShoppingList Backend
+
+
+ Congratulations!
+ Everything is up to date.
+
+
+
+ALLGOOD;
+}
+
+function echoChecklist($newVersion, $zipURL){
+echo <<
+
+
+
+CHECKLIST;
+}
+
+function echoJavascript(){
+echo <<
+ var progressDownloadElement = document.getElementById('progressDownload')
+ var progressElement = document.getElementById('progressDownload')
+ var downloadElement = document.getElementById('cb_download')
+ var extractElement = document.getElementById('cb_extract')
+ var backupElement = document.getElementById('cb_backup')
+ var filemoveElement = document.getElementById('cb_filemove')
+ var databaseElement = document.getElementById('cb_databaseupdate')
+ var databaseLabel = document.getElementById('databaseupdateLabel')
+
+ function updateProgress(percentage) {
+ progressElement.innerHTML = 'Download (' + percentage + '%' + ')';
+ }
+ function updateDownload(status) {
+ downloadElement.checked = status;
+ }
+ function updateExtract(status) {
+ extractElement.checked = status;
+ }
+ function updateBackup(status) {
+ backupElement.checked = status;
+ }
+ function updateFilemove(status) {
+ filemoveElement.checked = status;
+ }
+ function updateDatabase(status) {
+ databaseElement.checked = status;
+ }
+ function updateDatabaseNotNecessary() {
+ databaseLabel.style.setProperty("text-decoration", "line-through");
+ }
+
+
+JS;
+}
+
+function status(){
+ $githubData = getGithubData();
+ echoHead();
+ echoPageStart();
+ echoStyle();
+ echoJavascript();
+ if($githubData['version'] == 0){
+ echoError('Could not connect to GitHub API.');
+ die();
+ }
+ if($githubData['version'] == BACKEND_VERSION){
+ $updateDisabled = permissionCheck();
+ echoUpdateAvailable(BACKEND_VERSION, $githubData['version'], $githubData['releasenote'], $githubData['zipurl'], $updateDisabled);
+ } else {
+ echoAllGood();
+ }
+ echoPageEnd();
+}
+
+function update($zipURL, $newVersion){
+ echoPageStart();
+ echoHead();
+ echoStyle();
+ echoChecklist($newVersion, $zipURL);
+ echoJavascript();
+ $zipFILE = downloadUpdate($zipURL);
+ unzipDownloadedUpdate($zipFILE);
+ backup();
+ copyNewFiles();
+ updateDatabase();
+ echoPageEnd();
+}
+
+function echoSuccess($message){
+echo <<
+
+
+SUCCESS;
+}
+
+function echoError($errorMessage){
+echo <<
+
+
+ Oh no!
+ An error occured:
+ $errorMessage
+
+
+
+ERROR;
+
+}
+
+function permissionCheck(){
+ if (!is_writable(__DIR__)){
+ $user = get_current_user();
+ $dir = __DIR__;
+ echoError('No write permission for user "'.$user.'" for directory "'. $dir.'"');
+ return "disabled";
+ }
+}
+
+function downloadUpdate($zipurl){
+ $ch = curl_init() or die('Sorry cURL is not installed!');
+ $zipfile = __DIR__.'/update_'.$tag.'.zip';
+ $fp = fopen ($zipfile, 'w+');
+ $ch = curl_init();
+ curl_setopt($ch, CURLOPT_URL, $zipurl);
+ curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']);
+ curl_setopt($ch, CURLOPT_FILE, $fp);
+ curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
+ curl_setopt($ch,CURLOPT_NOPROGRESS,false);
+ curl_setopt($ch,CURLOPT_PROGRESSFUNCTION,'progress');
+ curl_exec($ch);
+ curl_close($ch);
+ echo '';
+ return $zipfile;
+ if(!file_exists($zipfile)){
+ echoError('Could not download file from GitHub!');
+ }
+}
+
+function unzipDownloadedUpdate($zipfile){
+ $zip = new ZipArchive;
+ $res = $zip->open($zipfile);
+ if ($res === TRUE) {
+ $zip->extractTo(__DIR__);
+ $zip->close();
+ echo '';
+ unlink($zipfile);
+ } else {
+ die ('Could not extract file!');
+ }
+}
+
+function backup(){
+ $backupFolder = __DIR__.'/backup/';
+ delDir($backupFolder);
+ if(!mkdir($backupFolder, 0770)){
+ echoError('Could not create backup folder!');
+ exit;
+ }
+ $iteratorBackup = new DirectoryIterator(__DIR__);
+ foreach ($iteratorBackup as $backupFile) {
+ $backupFileName = $backupFile->getFilename();
+ //REMOVE CHECK FOR update.sql FOR RELEASE, CURRENTLY USED BECAUSE ZIP FROM GITHUB HAS NONE AND IT WOULD BE DELETED
+ if ($backupFileName != '.' && $backupFileName != '..' && $backupFileName != 'config.php' && $backupFileName != 'update.sql' && $backupFileName != '.htaccess' && !$backupFile->isDir() && $backupFileName[0] != '.'){
+ if(!rename($backupFile->getPathname(), $backupFolder.$backupFile->getFilename())){
+ die ('Could not backup files!');
+ }
+ }
+ }
+ echo '';
+}
+
+function copyNewFiles(){
+ $iterator = new DirectoryIterator(__DIR__);
+ foreach ($iterator as $fileinfo) {
+ if ($fileinfo->isDir() && strlen(strstr($fileinfo->getFilename(),"GroundApps-ShoppingList_Backend"))>0) {
+ $iteratorUpdateDir = new DirectoryIterator($fileinfo->getPathname());
+ foreach ($iteratorUpdateDir as $updateFile) {
+ $updateFileName = $updateFile->getFilename();
+ if ($updateFileName != '.' && $updateFileName != '..' && $updateFileName != 'config.php' && $updateFileName != '.htaccess'){
+ rename($updateFile->getPathname(), __DIR__ .'/'.$updateFileName);
+ echo '';
+ }
+ }
+ delDir($fileinfo->getPathname());
+ }
+ }
+ echo '';
+}
+
+function getGithubData(){
+ $ch = curl_init() or die('Sorry cURL is not installed!');
+ $url = 'https://api.github.com/repos/GroundApps/ShoppingList_Backend/releases/latest';
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_URL,$url);
+ $agent = 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.0';
+ curl_setopt($ch,CURLOPT_HTTPHEADER,array('User-Agent: '.$agent));
+ $result = curl_exec($ch);
+ curl_close($ch);
+ $obj = json_decode($result);
+ $tag = $obj->tag_name;
+ $zipurl = $obj->zipball_url;
+ $releasenote = $obj->body;
+ //$tag = "v1.1";
+ $zipurl = 'https://api.github.com/repos/GroundApps/ShoppingList_Backend/zipball/v1';
+ return array(
+ "version" => substr($tag,1),
+ "zipurl" => $zipurl,
+ "releasenote" => $releasenote,
+ );
+}
+
+function updateDatabase(){
+ include('config.php');
+ //IMPLEMENT DATABASE ALTERATION
+ // WITH PDO
+ $updateFILE = __DIR__."/update.sql";
+ if(!file_exists($updateFILE)){
+ echo '';
+ } else {
+ $lines = file($updateFILE);
+ foreach ($lines as $line){
+ echo $line.' ';
+ //SQL FOR EACH LINE IN update.sql
+ }
+ echo '';
+ }
+ echoSuccess('Update done! Please check if everything works and then delete the backup folder.');
+}
+
+function progress($clientp,$dltotal,$dlnow,$ultotal,$ulnow){
+ if ($dltotal > 0){
+ echo '';
+ }
+ return(0);
+ }
+
+function delDir($dir) {
+ if (is_dir($dir)) {
+ $objects = scandir($dir);
+ foreach ($objects as $object) {
+ if ($object != "." && $object != "..") {
+ if (filetype($dir."/".$object) == "dir") delDir($dir."/".$object); else unlink($dir."/".$object);
+ }
+ }
+ reset($objects);
+ rmdir($dir);
+ }
+}
+
+?>
diff --git a/api.php b/api.php
index 4dfc75c..7e5c8f8 100644
--- a/api.php
+++ b/api.php
@@ -1,20 +1,21 @@
- init(); //TODO: put this to INSTALL.php
+
+session_start();
+if (isset($_SESSION['user_logged']) && $_SESSION['user_logged'] == 0 && $_SESSION['user_read'] == 1) {
+ echo $db->listall();
+ exit();
+} else if (! isset($_SESSION['user_logged']) || $_SESSION['user_logged'] != 1) {
if (!hash_equals($authKey, crypt($auth, $authKey))){
die (json_encode(array('type' => API_ERROR_403, 'content' => 'Authentication failed.')));
}
-
- $db = NEW DataBase($dbConfig);
-
- switch ($function){
- case 'listall':
+}
+
+
+switch ($function){
+
+ case 'listall':
echo $db->listall();
break;
- case 'save':
- if($db->exists($itemName)){
- echo $db->update($itemName, $itemCount);
- } else {
- echo $db->save($itemName, $itemCount);
- }
+
+ case 'save':
+ if($db->exists($itemName)){
+ echo $db->update($itemName, $itemCount, $itemChecked);
+ } else {
+ echo $db->save($itemName, $itemCount, $itemChecked);
+ }
break;
- case 'saveMultiple':
+
+ case 'saveMultiple':
echo $db->saveMultiple($jsonData);
break;
- case 'deleteMultiple':
+
+ case 'deleteMultiple':
echo $db->deleteMultiple($jsonData);
break;
- case 'update':
- echo $db->update($itemName, $itemCount);
+
+ case 'update':
+ echo $db->update($itemName, $itemCount, $itemChecked);
break;
- case 'delete':
+
+ case 'delete':
echo $db->delete($itemName);
break;
- case 'clear':
+
+ case 'clear':
echo $db->clear();
break;
- default:
- die (json_encode(array('type' => API_ERROR_FUNCTION_NOT_SPECIFIED, 'content' => 'function not specified')));
-
- }
+ case 'addQRcodeItem':
+ /* OUTPAN
+ $outpanApiKey='1a74a95c40a331e50d4b2c7fe311328c'; // taken from https://github.com/johncipponeri/outpan-api-java
+ $response = file_get_contents('https://api.outpan.com/v2/products/' . $itemName . '/?apikey=' . $outpanApiKey);
+ if($response!==false) {
+ $name = json_decode($response)->{'name'};
+ if ( $name != '' ) {
+ $itemName = $name;
+ $itemCount = 1;
+ $itemChecked = 'false';
+ if( $db->exists($itemName) ) {
+ echo $db->update($itemName, $itemCount, $itemChecked);
+ } else {
+ echo $db->save($itemName, $itemCount, $itemChecked);
+ }
+ } else {
+ die (json_encode(array('type' => API_ERROR_QRCODE, 'content' => 'Code not found: ' . $itemName)));
+ }
+ } else {
+ die (json_encode(array('type' => API_ERROR_QRCODE, 'content' => 'Error querying outpan.com')));
+ }
+ //*/
+ $opengtindbApiKey='400000000'; // taken from https://opengtindb.org/api.php
+ $response = file_get_contents('https://opengtindb.org/?ean=' . $itemName . '&cmd=query&queryid=' . $opengtindbApiKey);
+ if($response!==false) {
+ if(strpos($response,'error=0')!==false) {
+ $values = parse_ini_string(utf8_encode($response));
+ //file_put_contents('/tmp/sholi',print_r($values, true));
+ $name = trim($values['name']);
+ if ($name == '') $name = trim($values['detailname']);
+ if ($name == '') $name = trim($values['name_en']);
+ if ($name == '') $name = trim($values['detailname_en']);
+ if ($name == '') $name = trim($values['descr']);
-?>
+ if ( $name != '' ) {
+ $itemName = $name;
+ $itemCount = 1;
+ $itemChecked = 'false';
+ if( $db->exists($itemName) ) {
+ echo $db->update($itemName, $itemCount, $itemChecked);
+ } else {
+ echo $db->save($itemName, $itemCount, $itemChecked);
+ }
+ } else {
+ die (json_encode(array('type' => API_ERROR_QRCODE, 'content' => 'Code not found: ' . $itemName)));
+ }
+ } else {
+ die (json_encode(array('type' => API_ERROR_QRCODE, 'content' => 'Code not found: ' . $itemName . ', '.$response)));
+ }
+ } else {
+ die (json_encode(array('type' => API_ERROR_QRCODE, 'content' => 'Error querying opengtindb.org')));
+ }
+ break;
+
+ default:
+ die (json_encode(array('type' => API_ERROR_FUNCTION_NOT_SPECIFIED, 'content' => 'function not specified')));
+
+}
+?>
diff --git a/config.php b/config.php
index 7278a42..e6243ec 100644
--- a/config.php
+++ b/config.php
@@ -5,24 +5,35 @@
############################################################*/
/*##########################################################
- # Auth Key / Password, to get acces to the database #
+ # Auth Key / Password, to get acces to the database #
############################################################*/
- $authKey = '';
+ $authKey = '';
/*##########################################################
- # Branch: stable, testing, experimental #
- ############################################################*/
+ # Branch: stable, testing, experimental #
+ ############################################################*/
$branch = "stable";
/*##########################################################
- # Database: MySQL, SQLite #
- ############################################################*/
+ # Database: MySQL, SQLite #
+ ############################################################*/
$dataBase = "SQLite";
/*##########################################################
- # SQLite Config #
- ############################################################*/
+ # SQLite Config #
+ ############################################################*/
$SQLiteConfig = array('file' => "shoppinglist.sqlite");
/*##########################################################
- # MySQL Config #
+ # MySQL Config #
############################################################*/
- $MySQLConfig = array('host' => "host",'db' => "db",'user' => "user",'password' => "password",);
-?>
+ $MySQLConfig = [
+ 'host' => "localhost",
+ 'db' => "your_database_name",
+ 'user' => "your_mysql_user",
+ 'password' => "your_mysql_password",
+ ];
+ /*##########################################################
+ # if you put your config into a new file config.local.php #
+ # instead of changing the values above then in case of an #
+ # update of config.php you will not lose all your settings #
+ ############################################################*/
+ @include('config.local.php');
+?>
diff --git a/css/main.css b/css/main.css
new file mode 100644
index 0000000..ab2dd15
--- /dev/null
+++ b/css/main.css
@@ -0,0 +1,166 @@
+/******************
+ Shopping list css for web frontend
+ Reference : https://github.com/GroundApps/ShoppingList_Backend
+ Licence : http://www.gnu.org/licenses/agpl-3.0.fr.html
+*******************/
+#shopItems {
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+ zoom: 1;
+}
+
+#shopItems li {
+ margin: 2px 0px 12px 8px;
+ border-style: solid;
+ border-width: 0px;
+}
+
+.itemCheck { width: 40px ; }
+.itemName { }
+.itemQty { width: 36px;}
+.itemQty input { width: 30px;}
+.itemDelete { }
+
+.itemCheckedTD { font-family: FontAwesome; color: #a94442; }
+.itemCheckedName{ text-decoration:line-through; }
+.itemUncheckedTD { font-family: FontAwesome; color: #3c763d; }
+.itemCheckedTD i::before { content: '\f273'; }
+.itemUncheckedTD i::before { content: '\f274'; }
+
+body.dragging, body.dragging * {
+ cursor: move !important;
+}
+
+.dragged {
+ position: absolute;
+ opacity: 0.5;
+ z-index: 2000;
+}
+
+#shopItems li.placeholder {
+ position: relative;
+ /** More li styles **/
+}
+#shopItems li.placeholder:before {
+ position: absolute;
+ /** Define arrowhead **/
+}
+.icon::before {
+ font-family: FontAwesome;
+ font-style: normal;
+ font-weight: normal;
+ text-transform: none !important;
+}
+button::before, .button::before {
+ left: -0.5em;
+ padding: 0 0 0 0.75em;
+ color: #aaaaaa;
+ position: relative;
+}
+.row {
+ margin-left: unset;
+ margin-right: unset;
+}
+.row {
+ border-bottom: 1px solid transparent;
+ box-sizing: border-box;
+}
+.row > * {
+ box-sizing: border-box;
+ float: left;
+}
+.row::after, .row::before {
+ clear: both;
+ content: "";
+ display: block;
+ height: 0;
+}
+.row.uniform > * > *:first-child {
+ margin-top: 0;
+}
+.row.uniform > * > *:last-child {
+ margin-bottom: 0;
+}
+.row > * {
+ padding: 0 0 0 1em;
+}
+.row {
+ margin: 0 0 -1px -1em;
+}
+.row.uniform > * {
+ padding: 1em 0 0 1em;
+}
+.row.uniform {
+ margin: -1em 0 -1px -1em;
+}
+@media screen and (max-width: 1680px) {
+.row > * {
+ padding: 0 0 0 1em;
+}
+.row {
+ margin: 0 0 -1px -1em;
+}
+.row.uniform > * {
+ padding: 1em 0 0 1em;
+}
+.row.uniform {
+ margin: -1em 0 -1px -1em;
+}
+}
+@media screen and (max-width: 1280px) {
+.row > * {
+ padding: 0 0 0 1em;
+}
+.row {
+ margin: 0 0 -1px -1em;
+}
+.row.uniform > * {
+ padding: 1em 0 0 1em;
+}
+.row.uniform {
+ margin: -1em 0 -1px -1em;
+}
+}
+@media screen and (max-width: 980px) {
+.row > * {
+ padding: 0 0 0 1em;
+}
+.row {
+ margin: 0 0 -1px -1em;
+}
+.row.uniform > * {
+ padding: 1em 0 0 1em;
+}
+.row.uniform {
+ margin: -1em 0 -1px -1em;
+}
+}
+@media screen and (max-width: 736px) {
+.row > * {
+ padding: 0 0 0 1em;
+}
+.row {
+ margin: 0 0 -1px -1em;
+}
+.row.uniform > * {
+ padding: 1em 0 0 1em;
+}
+.row.uniform {
+ margin: -1em 0 -1px -1em;
+}
+}
+@media screen and (max-width: 480px) {
+.row > * {
+ padding: 0 0 0 1em;
+}
+.row {
+ margin: 0 0 -1px -1em;
+}
+.row.uniform > * {
+ padding: 1em 0 0 1em;
+}
+.row.uniform {
+ margin: -1em 0 -1px -1em;
+}
+}
diff --git a/db_connector.php b/db_connector.php
new file mode 100644
index 0000000..f0584a8
--- /dev/null
+++ b/db_connector.php
@@ -0,0 +1,178 @@
+type = $dbtype;
+ switch($dbtype){
+ case 'SQLite':
+ $db_pdo='sqlite:'.$dbargs['file'];
+ try{
+ $this->db = new PDO($db_pdo);
+ }catch(PDOException $e){
+ die(json_encode(array('type' => API_ERROR_DATABASE_CONNECT, 'content' => $e->getMessage())));
+ }
+ break;
+ case 'MySQL':
+ $db_pdo='mysql:host='.$dbargs['host'].';dbname='.$dbargs['db'].';charset=utf8mb4';
+ try{
+ $this->db = new PDO($db_pdo, $dbargs['user'], $dbargs['password'], array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'utf8mb4' COLLATE 'utf8mb4_bin'"));
+ }catch(PDOException $e){
+ die(json_encode(array('type' => API_ERROR_DATABASE_CONNECT, 'content' => $e->getMessage())));
+ }
+ break;
+ default:
+ die(json_encode(array('type' => API_ERROR_MISSING_PARAMETER, 'content' => 'Missing database parameters.')));
+ }
+ $this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+ //SQLITE $this->db->query('PRAGMA journal_mode=OFF;');
+ }
+
+ function init(){
+ $sql = "CREATE table $this->table(
+ item STRING PRIMARY KEY,
+ count INT NOT NULL,
+ checked INT NOT NULL,
+ category STRING);";
+ try{
+ $this->db->exec($sql);
+ }catch(PDOException $e){
+ //die(json_encode(array('type' => API_ERROR_UNKNOWN, 'content' => $e->getMessage()))); //uncomment after init() has been put to INSTALL.php
+ }
+ }
+
+ function listall(){
+ try{
+ $sql = "SELECT * FROM $this->table ORDER BY item ASC";
+ $val = $this->db->query($sql);
+ $stack = array();
+ foreach($val as $row){
+ array_push($stack, array(
+ 'itemTitle' => $row['item'],
+ 'itemCount' => $row['count'],
+ 'checked' => (bool)$row['checked'],
+ 'itemCategory' => $row['category']));
+ }
+ if(count($stack) == 0){
+ return json_encode(array('type' => API_SUCCESS_LIST_EMPTY));
+ }else{
+ return json_encode(array('type' => API_SUCCESS_LIST, 'items' => $stack));
+ }
+ }catch(PDOException $e){
+ die(json_encode(array('type' => API_ERROR_LIST, 'content' => $e->getMessage())));
+ }
+ }
+
+ function exists($item){
+ $stmt = $this->db->prepare("SELECT * from $this->table WHERE item=:item;");
+ $stmt->bindParam(':item', $item, PDO::PARAM_STR);
+ $stmt->execute();
+ return (bool)count($stmt->fetchAll());
+ }
+
+ function save($item, $count, $checked = false){
+ try{
+ $checked = ($checked=='true' ? 1:0);
+ $stmt = $this->db->prepare("INSERT INTO $this->table (item, count, checked) VALUES (:item, :count, :checked);");
+ $stmt->bindParam(':item', $item, PDO::PARAM_STR);
+ $stmt->bindParam(':count', $count, PDO::PARAM_INT);
+ $stmt->bindParam(':checked', $checked, PDO::PARAM_INT);
+ $stmt->execute();
+ return json_encode(array('type' => API_SUCCESS_SAVE, 'content' => $item.' saved.'));
+ }catch(PDOException $e){
+ return json_encode(array('type' => API_ERROR_SAVE, 'content' => $e->getMessage()));
+ }
+ }
+
+ function saveMultiple($jsonData){
+ if(empty($jsonData)){
+ die(json_encode(array('type' => API_ERROR_MISSING_PARAMETER, 'content' => 'parameter missing for saveMultiple')));
+ }
+ $itemList = json_decode($jsonData, true);
+ $success = true;
+ $errors = array();
+ foreach($itemList as $item){
+ $output = json_decode($this->exists($item['itemTitle']) ? $this->update($item['itemTitle'], $item['itemCount']) : $this->save($item['itemTitle'], $item['itemCount']),true);
+ if($output['type']!=API_SUCCESS_SAVE && $output['type']!=API_SUCCESS_UPDATE) {
+ $success = false;
+ array_push($errors, array('itemTitle' => $item['itemTitle'], 'error' => $output['content']));
+ }
+ }
+ if($success) {
+ return json_encode(array('type' => API_SUCCESS_SAVE, 'content' => count($itemList)>1 ? 'Multiple items saved.':$itemList[0]['itemTitle'].' saved.'));
+ } else {
+ return json_encode(array('type' => API_ERROR_SAVE, 'content' => $errors));
+ }
+ }
+
+ function update($item, $count, $checked = false){
+ try{
+ $stmt = $this->db->prepare("SELECT * from $this->table WHERE item=:item;");
+ $stmt->bindParam(':item', $item, PDO::PARAM_STR);
+ $stmt->execute();
+ $rows = $stmt->fetchAll();
+ $prev = $rows[0];
+
+ $checked = ($checked=='true' ? 1:0);
+ $stmt = $this->db->prepare("UPDATE $this->table SET count=:count, checked=:checked WHERE item=:item;");
+ $stmt->bindParam(':item', $item, PDO::PARAM_STR);
+ $stmt->bindParam(':count', $count, PDO::PARAM_INT);
+ $stmt->bindParam(':checked', $checked, PDO::PARAM_INT);
+ $stmt->execute();
+
+ if ($prev['count'] != $count)
+ return json_encode(array('type' => API_SUCCESS_UPDATE, 'content' => 'Update successful.'));
+
+ return json_encode(array('type' => API_SUCCESS_UPDATE, 'content' => ($checked ? '✔':'♻')));
+ }catch(PDOException $e){
+ return json_encode(array('type' => API_ERROR_UPDATE_, 'content' => $e->getMessage()));
+ }
+ }
+
+ function deleteMultiple($jsonData){
+ if(empty($jsonData)) {
+ die(json_encode(array('type' => API_ERROR_MISSING_PARAMETER, 'content' => 'Parameter missing for deleteMultiple.')));
+ }
+ $itemList = json_decode($jsonData, true);
+ $success = true;
+ $errors = array();
+ foreach($itemList as $item){
+ $output = json_decode($this->delete($item['itemTitle']),true);
+ if($output['type']!=API_SUCCESS_DELETE) {
+ $success = false;
+ array_push($errors, array('itemTitle' => $item['itemTitle'], 'error' => $output['content']));
+ }
+ }
+ if($success) {
+ return json_encode(array('type' => API_SUCCESS_DELETE, 'content' => count($itemList)>1 ? 'Multiple items deleted.':$itemList[0]['itemTitle'].' deleted.'));
+ } else {
+ return json_encode(array('type' => API_ERROR_DELETE, 'content' => $errors));
+ }
+ }
+
+ function delete($item){
+ try{
+ $stmt = $this->db->prepare("DELETE FROM $this->table WHERE item=:item");
+ $stmt->bindParam(':item', $item, PDO::PARAM_STR);
+ $stmt->execute();
+ return json_encode(array('type' => API_SUCCESS_DELETE, 'content' => 'Item deleted.'));
+ }catch(PDOException $e){
+ return json_encode(array('type' => API_ERROR_DELETE, 'content' => $e->getMessage()));
+ }
+ }
+
+ function clear(){
+ try{
+ $stmt = $this->db->exec("TRUNCATE TABLE $this->table;");
+ return json_encode(array('type' => API_SUCCESS_CLEAR, 'content' => 'Database cleared.'));
+ }catch(PDOException $e){
+ return json_encode(array('type' => API_ERROR_CLEAR, 'content' => $e->getMessage()));
+ }
+ }
+
+ }
diff --git a/docker/config_sqlite.php b/docker/config_sqlite.php
new file mode 100644
index 0000000..3aa0fce
--- /dev/null
+++ b/docker/config_sqlite.php
@@ -0,0 +1,9 @@
+ "data/shoppinglist.sqlite"
+ );
+?>
diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh
new file mode 100755
index 0000000..68161ba
--- /dev/null
+++ b/docker/entrypoint.sh
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+SL_ROOT="/shoppinglist"
+
+if [ -z "$API_KEY" ]; then
+ echo "You must define an API_KEY when running this container" >&2
+ echo "Use for example: -e \"API_KEY=mysecretpassword\"" >&2
+ exit 1
+fi
+
+# Check initialisation state
+if [ -e "$SL_ROOT/INSTALL.php" ]; then
+
+ # Hash the key and add it to config.php
+ hashed_apikey="$( php "$SL_ROOT/docker/hash_apikey.php" "$API_KEY" )"
+
+ sed "s%authKey *= *.*;%authKey = '$hashed_apikey';%" "$SL_ROOT/docker/config_sqlite.php" \
+ > "$SL_ROOT/config.php"
+
+ # No need for web install
+ rm "$SL_ROOT/INSTALL.php"
+fi
+
+# Reset permissions
+chown -R nginx:www-data "$SL_ROOT/data"
+
+# Run PHP-FPM
+php-fpm
+
+# Run nginx
+nginx
diff --git a/docker/hash_apikey.php b/docker/hash_apikey.php
new file mode 100644
index 0000000..9c8e5c6
--- /dev/null
+++ b/docker/hash_apikey.php
@@ -0,0 +1,6 @@
+
diff --git a/docker/nginx.conf b/docker/nginx.conf
new file mode 100644
index 0000000..eccf80c
--- /dev/null
+++ b/docker/nginx.conf
@@ -0,0 +1,63 @@
+# run nginx in foreground
+daemon off;
+
+error_log /dev/stderr;
+
+pid /var/run/nginx.pid;
+worker_processes 5;
+
+events {
+ worker_connections 4096;
+}
+
+http {
+ include /etc/nginx/mime.types;
+ include /etc/nginx/fastcgi.conf;
+
+ default_type application/octet-stream;
+
+ #tcp_nopush on;
+ #client_body_temp_path /tmp/nginx/body 1 2;
+ #fastcgi_temp_path /tmp/nginx/fastcgi_temp 1 2;
+
+ server {
+
+ listen 80;
+
+ access_log /dev/stdout;
+
+ root /shoppinglist;
+ index index.php index.html index.htm;
+
+ location = /robots.txt {
+ allow all;
+ log_not_found off;
+ access_log off;
+ }
+
+ location / {
+ try_files $uri $uri/ /index.html;
+ }
+
+ # Deny data/
+ location /data {
+ deny all;
+ }
+
+ # pass the PHP scripts to FastCGI server listening on /tmp/phpfpm.sock
+ location ~ [^/]\.php(/|$) {
+
+ fastcgi_split_path_info ^(.+?\.php)(/.*)$;
+
+ if (!-f $document_root$fastcgi_script_name) {
+ return 404;
+ }
+
+ fastcgi_pass unix:/tmp/phpfpm.sock;
+ fastcgi_index index.php;
+ fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+ include fastcgi_params;
+ }
+ }
+
+}
diff --git a/docker/php-fpm.conf b/docker/php-fpm.conf
new file mode 100644
index 0000000..a82ffed
--- /dev/null
+++ b/docker/php-fpm.conf
@@ -0,0 +1,24 @@
+error_log = /dev/stderr
+
+[www]
+user = nginx
+group = www-data
+listen = /tmp/phpfpm.sock
+listen.owner = nginx
+listen.group = www-data
+
+pm = ondemand
+pm.max_children = 75
+pm.process_idle_timeout = 10s
+pm.max_requests = 500
+
+chdir = /shoppinglist
+
+php_flag[display_errors] = on
+php_admin_value[memory_limit] = 128M
+php_admin_value[upload_max_filesize] = 2G
+php_admin_value[post_max_size] = 2G
+php_admin_value[always_populate_raw_post_data] = -1
+php_admin_value[output_buffering] = 0
+php_admin_value[php_value max_input_time] = 3600
+php_admin_value[php_value max_execution_time] = 3600
diff --git a/index.php b/index.php
new file mode 100644
index 0000000..4b97905
--- /dev/null
+++ b/index.php
@@ -0,0 +1,94 @@
+Invalid API Key";
+ }
+ }
+?>
+
+
+
+
+ Shopping List
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Shopping List
+
+ if (! isset($_SESSION['user_logged']) || $_SESSION['user_logged'] != 1) {
+?>
+
" method="post">
+
+ API Key
+
+
+
+
+
+
+ } else {
+?>
+
Refresh data
+ /* Add a category (well, will soon):
*/ ?>
+
Remove checked
+
Share
+
+
+
+ }
+?>
+
+
+
+
+
+
diff --git a/js/main.js b/js/main.js
new file mode 100644
index 0000000..47d7b96
--- /dev/null
+++ b/js/main.js
@@ -0,0 +1,419 @@
+/******************
+ Shopping list javascript for web frontend
+ Reference : https://github.com/GroundApps/ShoppingList_Backend
+ Licence : http://www.gnu.org/licenses/agpl-3.0.fr.html
+
+*******************/
+
+/*******************/
+
+var apiurl="api.php";
+
+/*******************/
+var BACKEND_VERSION=1.0;
+
+var API_SUCCESS_LIST=1000;
+var API_SUCCESS_LIST_EMPTY=1001;
+var API_SUCCESS_UPDATE=1002;
+var API_SUCCESS_FAVORITE=1003;
+var API_SUCCESS_DELETE=1004;
+var API_SUCCESS_SAVE=1005;
+var API_SUCCESS_CLEAR=1006;
+
+var API_ERROR_SERVER=5000;
+var API_ERROR_404=5001;
+var API_ERROR_403=5002;
+var API_ERROR_MISSING_FUNCTION=5003;
+var API_ERROR_NO_DATABASE=5004;
+var API_ERROR_CONFIG=5005;
+var API_ERROR_UNKNOWN=5006;
+var API_ERROR_DATABASE_CONNECT=5012;
+var API_ERROR_MISSING_PARAMETER=5013;
+var API_ERROR_FUNCTION_NOT_SPECIFIED=5014;
+var API_ERROR_NOT_CONFIGURED=5015;
+
+var API_ERROR_UPDATE_=6001;
+var API_ERROR_FAVORITE=6002;
+var API_ERROR_DELETE=6003;
+var API_ERROR_SAVE=6004;
+var API_ERROR_CLEAR=6005;
+var API_ERROR_LIST=6006;
+/*******************/
+
+/* web page structure
+
+
+
+
+
+ # accordeon
+
+
+
(Item add inputs and buttons)
+
+
+
+
+
+
+
+ */
+
+ var categoryList = new Array ("Uncategorized"); // TODO : make an object here
+
+ $.ajaxSetup({
+ url: apiurl,
+ async: true,
+ dataType: "json",
+ type: "POST"
+ });
+
+ // add a category ( id: category ref in categoryList, name: display name )
+ function addCategory(id,name) {
+ var read = $("#shopcategory").attr("data");
+ var content= ''+name+' ';
+ if (read!=null && read.length) {
+ content= ''+name+' ';
+ }
+ $("#shopcategory").append(content);
+ var addButtonOBJ=$('#cat_'+id);
+ addButtonOBJ.find( "#addItemButton" ).button().click(addItemClick);
+ addButtonOBJ.find( "#shopItems" ).sortable({
+ items: "li"
+ });
+ addButtonOBJ.find( "#addItemName" ).keypress(function( event ) {
+ if ( event.which == 13 ) {
+ event.preventDefault();
+ addButtonOBJ.find( "#addItemButton" ).click();
+ }
+ });
+ //XXX $("#title_"+id).droppable({drop: function (event,ui) { categoryMove(event, ui); } });
+ //XXX $("#title_"+id).droppable({drop: function (event,ui) { categoryMove(event, ui, $("#title_"+id)); } });
+
+ return addButtonOBJ;
+ }
+ // Returns category ref in categoryList, or -1 if not found
+ function categoryID(name) {
+ for (index = 0; index < categoryList.length; index++) {
+ if (name == categoryList[index]) {
+ return index;
+ }
+ }
+ return -1;
+ }
+ /* Returns jquery object of a category identified by display name.
+ Create categoryList entry and/or object if not found.*/
+ function GetCategoryOBJ(name) {
+ var index=categoryID(name);
+ if (index ==-1) {
+ index=categoryList.length;
+ categoryList[index]=name;
+ }
+ var catObject=$('#cat_'+index);
+ if (catObject.length ) {
+ return catObject
+ }
+ return addCategory(index,name)
+ }
+ // delete item in list. Only used by the delete button
+ function deleteItem(){
+ var itemNameOBJ=$(this).parent().find(".itemName");
+ var itemName=itemNameOBJ.html();
+ //$(this).closest("#shopItemEntry").remove();
+ var itemOBJ = $(this).closest("#shopItemEntry");
+
+ $.ajax({
+ data: {
+ auth: "none",
+ function: "delete",
+ item: itemName,
+ },
+ success: function (data) {
+ if (data.type == API_SUCCESS_DELETE) {
+ itemOBJ.remove();
+ }
+ }
+ });
+ }
+ // Add an item in the current category. Only used by "add" button of category
+ function addItemClick(){
+ // Get the input values
+ var addedItemObj=$(this).parent().parent().find("#addItemName");
+ var addedQtyObj=$(this).parent().parent().find("#addItemQty");
+ var addedItem=addedItemObj.val();
+ var addedQty=addedQtyObj.val();
+ // Get category pointer and add item
+ var category=$(this).closest("div[id*='cat_']");
+
+ if(addedQty>0) {
+ }
+ else {
+ addedQty = 1;
+ addedQtyObj.val(addedQty);
+ }
+
+ if(addedItem.length>0) {
+
+ $.ajax({
+ data: {
+ auth: "none",
+ function: "save",
+ item: addedItem,
+ count: addedQty
+ },
+ success: function (data) {
+ if (data.type == API_SUCCESS_SAVE) {
+ addItem(category,addedItem,addedQty,false);
+ // reset values
+ addedItemObj.val("");
+ addedQtyObj.val("1");
+ }
+ else if (data.type == API_SUCCESS_UPDATE) {
+ // reset values
+ addedItemObj.val("");
+ addedQtyObj.val("1");
+ // easier than finding out on which item to update the QTY
+ refresh();
+ }
+ }
+ });
+
+ $(this).blur();
+ }
+ else addedItemObj.focus();
+ }
+ /* Add an item (name : display name, amount : amount of items, checked : bool )
+ to the category object categoryOBJ */
+ function addItem(categoryOBJ, name, amount, checked) {
+ var isChecked,isCheckedName;
+ var read = $("#shopcategory").attr("data");
+ if (checked) { isChecked="itemCheckedTD"; isCheckedName=" itemCheckedName";}
+ else { isChecked="itemUncheckedTD";isCheckedName=""; }
+ var itemVal=$(' ');
+ if (read!=null && read.length) {
+ itemVal=$(' ' +
+ ' ' +
+ ' '+amount+'' +
+ ' '+name+' '+
+ '
');
+ }
+ $(itemVal).appendTo(categoryOBJ.find("#shopItems"));
+ // set events
+ if (read==null || read.length==0) {
+ $(itemVal).find( ".itemDelete" ).click(deleteItem);
+ $(itemVal).find( ".itemCheck" ).click(checkItemToggle);
+ $(itemVal).find( "#itemQtyValue" ).change(updateItem);
+ }
+ }
+
+ // Toggle check/uncheck of an item. Only used by "add" button of category
+ function checkItemToggle() {
+ var itemNameOBJ=$(this).parent().find(".itemName");
+ var itemUnchecked=$(this).hasClass("itemUncheckedTD");
+ if (itemUnchecked)
+ {
+ $(this).removeClass("itemUncheckedTD");
+ $(this).addClass("itemCheckedTD");
+ itemNameOBJ.addClass("itemCheckedName");
+ } else
+ {
+ $(this).removeClass("itemCheckedTD");
+ $(this).addClass("itemUncheckedTD");
+ itemNameOBJ.removeClass("itemCheckedName");
+ }
+
+ var itemOBJ = $(this).closest("#shopItemEntry");
+ var itemName=itemOBJ.find(".itemName").html();
+ var itemQty=itemOBJ.find("#itemQtyValue").val();
+
+ $.ajax({
+ data: {
+ auth: "none",
+ function: "update",
+ item: itemName,
+ count: itemQty,
+ checked: itemUnchecked ? "true" : "false"
+ },
+ success: function (data) {
+ if (data.type != API_SUCCESS_UPDATE) {
+ //refresh(); not required for toggling
+ }
+ }
+ });
+
+ }
+
+ // Update item linked by itemObject
+ function updateItem()
+ {
+ var itemOBJ = $(this).closest("#shopItemEntry");
+ var itemName=itemOBJ.find(".itemName").html();
+ var itemQty=itemOBJ.find("#itemQtyValue").val();
+
+ if(itemQty>0) {
+ }
+ else {
+ itemQty = 1;
+ itemOBJ.find("#itemQtyValue").val(itemQty);
+ }
+
+ $.ajax({
+ data: {
+ auth: "none",
+ function: "save",
+ item: itemName,
+ count: itemQty
+ },
+ success: function (data) {
+ if (data.type != API_SUCCESS_UPDATE) {
+ refresh();
+ }
+ }
+ });
+
+ }
+
+ // Delete all and refresh from DB
+ function refresh(){
+ var addedItem="Error";
+ var addedQty="Error";
+ var addedchecked="Error";
+ var addedcategory="Error";
+
+ //Remove all
+ $("#shopcategory").empty();
+ categoryList=[];
+ var defcategory=GetCategoryOBJ("Uncategorized");
+
+ $.ajax({
+ data: {
+ auth: "none",
+ function: "listall"
+ },
+ success: function (data) {
+
+ //alert(data.type);
+ if (data.type == API_SUCCESS_LIST) {
+ for (var x = 0; x < data.items.length; x++) {
+ addedItem = data.items[x].itemTitle;
+ addedQty = data.items[x].itemCount;
+ checked = data.items[x].checked;
+ addedcategory = data.items[x].itemCategory;
+ if (addedcategory == null) {
+ addItem(defcategory, addedItem, addedQty, checked);
+ } else {
+ var categoryOBJ=GetCategoryOBJ(addedcategory);
+ addItem(categoryOBJ, addedItem, addedQty, checked);
+ }
+ }
+ //XXX $( "#shopcategory" ).accordion("refresh");
+ } else
+ if (data.type == API_SUCCESS_LIST_EMPTY) {
+ alert ("Empty list");
+ //TODO
+ } else {
+ alert ("Error : "+data.content);
+ }
+ } //success
+
+ }); //ajax
+
+ if($(this).blur) $(this).blur();
+ }
+
+ function removechecked(){
+ var checked = $(".itemCheckedTD");
+ if (checked.length>0) {
+
+ var data = '[';
+ for (index = 0; index < checked.length; index++) {
+ data = data + '{"itemTitle":"'+$(checked[index]).parent().find(".itemName").html()+'"},';
+ }
+ data = data.substr(0,data.length-1) + ']';
+
+ $.ajax({
+ data: {
+ auth: "none",
+ function: "deleteMultiple",
+ jsonArray: data
+ },
+ success: function (data) {
+ if (data.type == API_SUCCESS_DELETE) {
+ refresh();
+ }
+ }
+ });
+
+ }
+ $(this).blur();
+ }
+
+ function share(){
+ alert('Share the following URL: '+"\n"+$(this).attr("data"));
+ }
+
+ function getItemValues(itemOBJ) {
+ var retVal = new Array();
+ retVal['checked']=itemOBJ.find(".itemCheck").hasClass("itemCheckedTD");
+ retVal['amount']=itemOBJ.find("#itemQtyValue").val();
+ retVal['name']=itemOBJ.find(".itemName").html();
+ retVal['category']=itemOBJ.closest("div[id*='cat_']").attr("id");
+ retVal['category'] = retVal['category'].substr(4);
+ return retVal;
+ }
+
+ // Item is dropped in another category.Only used by drop event of category
+ function categoryMove(event, ui, category) {
+ //alert("Dropped");
+ var itemValues = new Array ();
+ itemValues = getItemValues(ui.draggable);
+ var catID = category.attr("id").substr(6); // Remove this
+ var newCategory = $("#cat_"+catID);
+ ui.draggable.remove();
+ addItem(newCategory,itemValues['name'],itemValues['amount'],itemValues['checked']);
+ }
+
+ // Doc ready functions
+ $(function() {
+
+ // for test cat
+ $( ".itemDelete" ).click(deleteItem);
+ $( "#shopItems" ).sortable({ items: "li" });
+ $( "#addItemButton" ).button().click(addItemClick);
+ $( ".itemCheck" ).click(checkItemToggle);
+
+ // Main page
+ $( "#refresh" ).button().click(refresh);
+ $( "#checked" ).button().click(removechecked);
+ $( "#share" ).button().click(share);
+ $( "#itemQtyValue" ).change(updateItem);
+
+ /* XXX
+ // Categories accordeon
+ $( "#shopcategory" ).accordion({
+ heightStyle: content,collapsible: true
+ });
+ */
+
+ if ($("#shopcategory").length) refresh();
+ });
+
diff --git a/lib/phpqrcode-git b/lib/phpqrcode-git
new file mode 160000
index 0000000..863ffff
--- /dev/null
+++ b/lib/phpqrcode-git
@@ -0,0 +1 @@
+Subproject commit 863ffffac4c9d22e522464e325cbcbadfbb26470
diff --git a/mysql_connector.php b/mysql_connector.php
deleted file mode 100644
index 7894908..0000000
--- a/mysql_connector.php
+++ /dev/null
@@ -1,276 +0,0 @@
- server = $mysql_config['host'];
- $this->database = $mysql_config['db'];
- $this->username = $mysql_config['user'];
- $this->password = $mysql_config['password'];
- }
-
- function save($itemName, $itemCount)
- {
- if(empty($itemName)||empty($itemCount)) {
- die(json_encode(array('type' => API_ERROR_MISSING_PARAMETER, 'content' => 'parameter missing')));
- }
- //connect to db
- $handler = new mysqli($this->server, $this->username, $this->password, $this->database);
-
- //check if connection successful
- if ($handler->connect_error) {
- die(json_encode(array('type' => API_ERROR_DATABASE_CONNECT, 'content' => $handler->connect_error)));
- }
-
- //prepare query
- $stmt = $handler->prepare("INSERT into ShoppingList(item,count) VALUES(?,?)");
- $stmt->bind_param('ss', $itemName, $itemCount);
-
- //execute query and check if successful
- if ($stmt->execute()){
- $result = json_encode(array('type' => API_SUCCESS_SAVE, 'content' => $itemName.' saved'));
- } else {
- $result = json_encode(array('type' => API_ERROR_SAVE, 'content' => $stmt->error));
- }
-
- //close connection
- $stmt->close();
-
- //return result
- return $result;
- }
-
- function saveMultiple($jsonData)
- {
- if(empty($jsonData)) {
- die(json_encode(array('type' => API_ERROR_MISSING_PARAMETER, 'content' => 'parameter missing for saveMultiple')));
- }
- //connect to db
- $handler = new mysqli($this->server, $this->username, $this->password, $this->database);
-
- //check if connection successful
- if ($handler->connect_error) {
- die(json_encode(array('type' => API_ERROR_DATABASE_CONNECT, 'content' => $handler->connect_error)));
- }
- //iterate over all items in json array
- $array = json_decode( $jsonData, true );
- foreach($array as $item)
- {
- //prepare query
- $stmt = $handler->prepare("INSERT into ShoppingList(item,count) VALUES(?,?)");
- $stmt->bind_param('ss', $item['itemTitle'], $item['itemCount']);
-
- //execute query and check if successful
- if ($stmt->execute()){
- $result = json_encode(array('type' => API_SUCCESS_SAVE, 'content' => ' Multiple items saved'));
- } else {
- $result = json_encode(array('type' => API_ERROR_SAVE, 'content' => $stmt->error));
- }
- }
-
- //close connection
- $stmt->close();
-
- //return result
- return $result;
- }
-
- function deleteMultiple($jsonData)
- {
- if(empty($jsonData)) {
- die(json_encode(array('type' => API_ERROR_MISSING_PARAMETER, 'content' => 'parameter missing for deleteMultiple')));
- }
- //connect to db
- $handler = new mysqli($this->server, $this->username, $this->password, $this->database);
-
- //check if connection successful
- if ($handler->connect_error) {
- die(json_encode(array('type' => API_ERROR_DATABASE_CONNECT, 'content' => $handler->connect_error)));
- }
- //iterate over all items in json array
- $array = json_decode( $jsonData, true );
- foreach($array as $item)
- {
- //prepare query
- $stmt = $handler->prepare("DELETE from ShoppingList WHERE item = ?");
- $stmt->bind_param('s', $item['itemTitle']);
-
- //execute query and check if successful
- if (!$stmt->execute()){
- $result = json_encode(array('type' => API_SUCCESS_DELETE, 'content' => ' Multiple items deleted'));
- } else {
- $result = json_encode(array('type' => API_ERROR_DELETE, 'content' => $stmt->error));
- }
- }
-
- //close connection
- $stmt->close();
-
- //return result
- return $result;
- }
-
- function update($itemName, $itemCount)
- {
- if(empty($itemName)||empty($itemCount)){
- die(json_encode(array('type' => API_ERROR_MISSING_PARAMETER, 'content' => 'parameter missing')));
- }
- //connect to db
- $handler = new mysqli($this->server, $this->username, $this->password, $this->database);
-
- //check if connection successful
- if ($handler->connect_error) {
- die(json_encode(array('type' => API_ERROR_DATABASE_CONNECT, 'content' => $handler->connect_error)));
- }
-
- //prepare query
- $stmt = $handler->prepare("UPDATE ShoppingList SET count = ? WHERE item = ?");
- $stmt->bind_param('ss', $itemCount, $itemName);
-
- //execute query and check if successful
- if ($stmt->execute()){
- $result = json_encode(array('type' => API_SUCCESS_UPDATE, 'content' => $itemName.' updated'));
- } else {
- $result = json_encode(array('type' => API_ERROR_UPDATE_, 'content' => $stmt->error));
- }
-
- //close connection
- $stmt->close();
-
- //return result
- return $result;
- }
-
- function delete($itemName)
- {
- if(empty($itemName)){
- die(json_encode(array('type' => API_ERROR_MISSING_PARAMETER, 'content' => 'parameter missing')));
- }
- //connect to db
- $handler = new mysqli($this->server, $this->username, $this->password, $this->database);
-
- //check if connection successful
- if ($handler->connect_error) {
- die(json_encode(array('type' => API_ERROR_DATABASE_CONNECT, 'content' => $handler->connect_error)));
- }
-
- //prepare query
- $stmt = $handler->prepare("DELETE FROM ShoppingList WHERE item = ?");
- $stmt->bind_param('s', $itemName);
-
- //execute query and check if successful
- if ($stmt->execute()){
- $result = json_encode(array('type' => API_SUCCESS_DELETE, 'content' => $itemName.' deleted'));
- } else {
- $result = json_encode(array('type' => API_ERROR_DELETE, 'content' => $stmt->error));
- }
-
- //close connection
- $stmt->close();
-
- //return result
- return $result;
- }
-
- function exists($itemName)
- {
- if(empty($itemName)){
- die(json_encode(array('type' => API_ERROR_MISSING_PARAMETER, 'content' => 'parameter missing')));
- }
- //connect to db
- $handler = new mysqli($this->server, $this->username, $this->password, $this->database);
-
- //check if connection successful
- if ($handler->connect_error) {
- die(json_encode(array('type' => API_ERROR_DATABASE_CONNECT, 'content' => $handler->connect_error)));
- }
-
- //prepare query
- $stmt = $handler->prepare("SELECT item FROM ShoppingList WHERE item = ?");
- $stmt->bind_param('s', $itemName);
- //execute query
- $stmt->execute();
-
- //bind the result
- $stmt->store_result();
- if ($stmt->num_rows > 0){
- $itemExists = true;
- } else {
- $itemExists = false;
- }
-
- //close connection
- $stmt->close();
- return $itemExists;
-
- }
-
- function listall()
- {
- //connect to db
- $handler = new mysqli($this->server, $this->username, $this->password, $this->database);
-
- //check if connection successful
- if ($handler->connect_error) {
- die(json_encode(array('type' => API_ERROR_DATABASE_CONNECT, 'content' => $handler->connect_error)));
- }
-
- //prepare query
- $stmt = $handler->prepare("SELECT item, count FROM ShoppingList ORDER BY item ASC");
- //execute query
- $stmt->execute();
- $stmt->store_result();
- //bind the result
- $stmt->bind_result($item_name, $item_count);
-
- //create array
- $stack = array();
-
- if($stmt->num_rows > 0) {
- //put all rows into array
- while ($stmt->fetch()) {
- $listdata = array('itemTitle' => $item_name, 'itemCount' => $item_count, 'checked' => false);
- array_push($stack, $listdata);
- }
- return json_encode(array('type' => API_SUCCESS_LIST, 'items' => $stack));
- } else {
- return json_encode(array('type' => API_SUCCESS_LIST_EMPTY));
- }
-
- //close connection
- $stmt->close();
- }
-
- function clear()
- {
- //connect to db
- $handler = new mysqli($this->server, $this->username, $this->password, $this->database);
-
- //check if connection successful
- if ($handler->connect_error) {
- die(json_encode(array('type' => API_ERROR_DATABASE_CONNECT, 'content' => $handler->connect_error)));
- }
-
- //prepare query
- $stmt = $handler->prepare("TRUNCATE ShoppingList");
-
- //execute query and check if successful
- if ($stmt->execute()){
- $result = json_encode(array('type' => API_SUCCESS_CLEAR, 'content' => 'list cleared'));
- } else {
- $result = json_encode(array('type' => API_ERROR_CLEAR, 'content' => $stmt->error));
- }
-
- //close connection
- $stmt->close();
-
- //return result
- return $result;
- }
-
- }
-
-?>
diff --git a/postinst.sh b/postinst.sh
new file mode 100644
index 0000000..c7b386e
--- /dev/null
+++ b/postinst.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+echo -n "Press Enter to update the ShoppingList database"
+read
+BASE=`dirname $BASH_SOURCE`
+HOST=`cat $BASE/config.php | grep host | cut -d\" -f2`
+USER=`cat $BASE/config.php | grep user | cut -d\" -f2`
+PASS=`cat $BASE/config.php | grep password | cut -d\" -f2`
+DB=`cat $BASE/config.php | grep db | cut -d\" -f2`
+echo "Starting the update. Please wait..."
+SQLSTATUS=`mysql -h $HOST -u $USER -p$PASS $DB < $BASE/update.sql 2>&1`
+
+if [ $? -eq 0 ]
+then
+ echo "The database has been updated successfully!"
+else
+ echo "Error updating the database:"
+ echo "$SQLSTATUS"
+fi
diff --git a/sqlite_connector.php b/sqlite_connector.php
deleted file mode 100644
index 6ba6cce..0000000
--- a/sqlite_connector.php
+++ /dev/null
@@ -1,136 +0,0 @@
-db = new SQLite3($dbfile, SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE);
- }catch(Exception $e){
- die(json_encode(array('type' => API_ERROR_DATABASE_CONNECT, 'content' => $e->getMessage())));
- }
- $resultQuery = $this->db->query("SELECT COUNT(*) as count FROM sqlite_master WHERE type='table' AND name='itemlist'");
- $row = $resultQuery->fetchArray();
- if($row['count'] == 0){
- $this->db->exec("CREATE TABLE itemlist(ITEM TEXT PRIMARY KEY NOT NULL, COUNT INT NOT NULL);");
- }
- }
-
- function __destructor(){
- $this->db->close();
- }
-
- function listall(){
- $resultQuery = $this->db->query("SELECT ITEM, COUNT FROM itemlist ORDER BY ITEM ASC;");
- $stack = array();
- if(!$resultQuery){
- return json_encode(array('type' => API_SUCCESS_LIST_EMPTY));
- }
- while($item = $resultQuery->fetchArray()){
- $itemData = array(
- 'itemTitle' => $item['ITEM'],
- 'itemCount' => $item['COUNT'],
- 'checked' => false
- );
- array_push($stack, $itemData);
- }
- if(count($stack) == 0){
- return json_encode(array('type' => API_SUCCESS_LIST_EMPTY));
- }else{
- return json_encode(array('type' => API_SUCCESS_LIST, 'items' => $stack));
- }
- }
-
- function exists($item){
- $resultQuery = $this->db->query("SELECT COUNT(*) as count FROM itemlist WHERE ITEM = '".$item."';");
- $row = $resultQuery->fetchArray();
- if($row['count'] > 0){
- return True;
- }else{
- return False;
- }
- }
-
- function save($item, $count){
- $resultQuery = $this->db->query("INSERT INTO itemlist (ITEM, COUNT) VALUES('".$item."', ".$count.");");
- if($resultQuery){
- $result = json_encode(array('type' => API_SUCCESS_SAVE, 'content' => $item.' saved.'));
- }else{
- $result = json_encode(array('type' => API_ERROR_SAVE, 'content' => 'Saving failed'));
- }
- return $result;
- }
-
- function saveMultiple($jsonData){
- if(empty($jsonData)) {
- die(json_encode(array('type' => API_ERROR_MISSING_PARAMETER, 'content' => 'parameter missing for saveMultiple')));
- }
- //iterate over all items in json array
- $array = json_decode( $jsonData, true );
- foreach($array as $item)
- {
- $resultQuery = $this->db->query("INSERT INTO itemlist (ITEM, COUNT) VALUES('".$item['itemTitle']."', ".$item['itemCount'].");");
- }
- if($resultQuery){
- $result = json_encode(array('type' => API_SUCCESS_SAVE, 'content' => 'Multiple items saved'));
- }else{
- $result = json_encode(array('type' => API_ERROR_SAVE, 'content' => 'Saving failed'));
- }
- return $result;
- }
-
- function update($item, $count){
- $resultQuery = $this->db->query("UPDATE itemlist SET COUNT = ".$count." WHERE ITEM = '".$item."';");
- if($resultQuery){
- $result = json_encode(array('type' => API_SUCCESS_UPDATE, 'content' => $item.' updated.'));
- }else{
- $result = json_encode(array('type' => API_ERROR_UPDATE_, 'content' => 'Updating failed'));
- }
- return $result;
- }
-
- function deleteMultiple($jsonData){
- if(empty($jsonData)) {
- die(json_encode(array('type' => API_ERROR_MISSING_PARAMETER, 'content' => 'parameter missing for deleteMultiple')));
- }
- //iterate over all items in json array
- $array = json_decode( $jsonData, true );
- foreach($array as $item)
- {
- $resultQuery = $this->db->query("DELETE FROM itemlist WHERE ITEM = '".$item['itemTitle']."';");
- }
- if($resultQuery){
- $result = json_encode(array('type' => API_SUCCESS_DELETE, 'content' => 'Multiple items deleted'));
- }else{
- $result = json_encode(array('type' => API_ERROR_DELETE, 'content' => 'Deleting failed'));
- }
- return $result;
- }
-
- function delete($item){
- $resultQuery = $this->db->query("DELETE FROM itemlist WHERE ITEM = '".$item."';");
- if($resultQuery){
- $result = json_encode(array('type' => API_SUCCESS_DELETE, 'content' => $item.' deleted.'));
- }else{
- $result = json_encode(array('type' => API_ERROR_DELETE, 'content' => 'Deleting failed'));
- }
- return $result;
- }
-
- function clear(){
- $resultQuery = $this->db->query("DELETE FROM itemlist;");
- $this->db->exec("VACUUM;");
- if($resultQuery){
- $result = json_encode(array('type' => API_SUCCESS_CLEAR, 'content' => 'List cleared'));
- }else{
- $result = json_encode(array('type' => API_ERROR_CLEAR, 'content' => 'Clearing failed'));
- }
- return $result;
- }
-
-
- }
-
-?>
diff --git a/update.sql b/update.sql
new file mode 100644
index 0000000..c3a1a7a
--- /dev/null
+++ b/update.sql
@@ -0,0 +1 @@
+ALTER TABLE ShoppingList ADD COLUMN checked INT NOT NULL;
diff --git a/view.php b/view.php
new file mode 100644
index 0000000..8645f1c
--- /dev/null
+++ b/view.php
@@ -0,0 +1,52 @@
+
+
+
+
+
+ Shopping List View
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Shopping List View
+
+ if ( isset ($_GET['key']) && $_GET['key'] == substr(crypt($authKey, 'share'), -16) )
+ {
+ $_SESSION['user_read']=1;
+?>
+
Refresh data
+
+
+
+ }
+?>
+
+
+
+
+
+