From 9a5aefbeb59955d10da7112d75c38f16a877188e Mon Sep 17 00:00:00 2001 From: Skylar Ittner Date: Sat, 22 Sep 2018 02:41:38 -0600 Subject: [PATCH] Add image uploading for items (close #8) --- .gitignore | 2 + action.php | 123 ++++++++++++++++++++++++++++++++++ composer.lock | 15 ++--- database.mwb | Bin 12039 -> 12032 bytes database.sql | 78 ++++++++++++++------- database_upgrade/v1.1_1.2.sql | 10 ++- image.php | 62 +++++++++++++++++ images/.htaccess | 1 + langs/en/images.json | 10 +++ pages.php | 10 +++ pages/editimages.php | 87 ++++++++++++++++++++++++ pages/edititem.php | 14 +++- settings.template.php | 5 ++ static/css/files.css | 24 +++++++ static/js/files.js | 19 ++++++ 15 files changed, 426 insertions(+), 34 deletions(-) create mode 100644 image.php create mode 100755 images/.htaccess create mode 100644 langs/en/images.json create mode 100644 pages/editimages.php create mode 100644 static/css/files.css create mode 100644 static/js/files.js diff --git a/.gitignore b/.gitignore index c583050..3836c57 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ settings.php nbproject/private database.mwb.bak *.sync-conflict* +images/ +!images/.htaccess \ No newline at end of file diff --git a/action.php b/action.php index 76636bd..72f6b23 100644 --- a/action.php +++ b/action.php @@ -229,6 +229,129 @@ switch ($VARS['action']) { exit("[]"); } break; + case "imageupload": + $destpath = FILE_UPLOAD_PATH; + if (!is_writable($destpath)) { + returnToSender("unwritable_folder", "&id=$VARS[itemid]"); + } + + if (empty($VARS['itemid']) || !$database->has('items', ['itemid' => $VARS['itemid']])) { + returnToSender("invalid_itemid", "&id=$VARS[itemid]"); + } + + $files = []; + foreach ($_FILES['files'] as $key => $all) { + foreach ($all as $i => $val) { + $files[$i][$key] = $val; + } + } + + $errors = []; + foreach ($files as $f) { + if ($f['error'] !== UPLOAD_ERR_OK) { + $err = "could not be uploaded."; + switch ($f['error']) { + case UPLOAD_ERR_INI_SIZE: + case UPLOAD_ERR_FORM_SIZE: + $err = "is too big."; + break; + case UPLOAD_ERR_CANT_WRITE: + $err = "could not be saved to disk."; + break; + case UPLOAD_ERR_NO_FILE: + $err = "was not actually sent."; + break; + case UPLOAD_ERR_PARTIAL: + $err = "was only partially sent."; + break; + default: + $err = "could not be uploaded."; + } + $errors[] = htmlspecialchars($f['name']) . " $err"; + continue; + } + + if (filesize($f['tmp_name']) > 11) { + $imagetype = exif_imagetype($f['tmp_name']); + } else { + $imagetype = false; + } + + switch ($imagetype) { + case IMAGETYPE_JPEG: + case IMAGETYPE_GIF: + case IMAGETYPE_PNG: + case IMAGETYPE_WEBP: + $imagevalid = true; + break; + default: + $imagevalid = false; + } + + if (!$imagevalid) { + $errors[] = htmlspecialchars($f['name']) . " is not a supported image type (JPEG, GIF, PNG, WEBP)."; + continue; + } + + $filename = basename($f['name']); + $filename = preg_replace("/[^a-z0-9\._\-]/", "_", strtolower($filename)); + $n = 1; + if (file_exists($destpath . "/" . $filename)) { + while (file_exists($destpath . '/' . $n . '_' . $filename)) { + $n++; + } + $filename = $n . '_' . $filename; + } + + $finalpath = $destpath . "/" . $filename; + + if (move_uploaded_file($f['tmp_name'], $finalpath)) { + $primary = false; + if (!$database->has('images', ['AND' => ['itemid' => $VARS['itemid'], 'primary' => true]])) { + $primary = true; + } + $database->insert('images', ['itemid' => $VARS['itemid'], 'imagename' => $filename, 'primary' => $primary]); + } else { + $errors[] = htmlspecialchars($f['name']) . " could not be uploaded."; + } + } + + if (count($errors) > 0) { + returnToSender("upload_warning", implode("
", $errors) . "&id=$VARS[itemid]"); + } + returnToSender("upload_success", "&id=$VARS[itemid]"); + break; + case "promoteimage": + if (empty($VARS['itemid']) || !$database->has('items', ['itemid' => $VARS['itemid']])) { + returnToSender("invalid_itemid", "&id=$VARS[itemid]"); + } + if (empty($VARS['imageid']) || !$database->has('images', ['AND' => ['itemid' => $VARS['itemid'], 'imageid' => $VARS['imageid']]])) { + returnToSender("invalid_imageid", "&id=$VARS[itemid]"); + } + + $database->update('images', ['primary' => false], ['itemid' => $VARS['itemid']]); + $database->update('images', ['primary' => true], ['AND' => ['itemid' => $VARS['itemid'], 'imageid' => $VARS['imageid']]]); + returnToSender("image_promoted", "&id=$VARS[itemid]"); + break; + case "deleteimage": + if (empty($VARS['itemid']) || !$database->has('items', ['itemid' => $VARS['itemid']])) { + returnToSender("invalid_itemid", "&id=$VARS[itemid]"); + } + if (empty($VARS['imageid']) || !$database->has('images', ['AND' => ['itemid' => $VARS['itemid'], 'imageid' => $VARS['imageid']]])) { + returnToSender("invalid_imageid", "&id=$VARS[itemid]"); + } + + $imagename = $database->get('images', 'imagename', ['imageid' => $VARS['imageid']]); + if ($database->count('images', ['imagename' => $imagename]) <= 1) { + unlink(FILE_UPLOAD_PATH . "/" . $imagename); + } + $database->delete('images', ['AND' => ['itemid' => $VARS['itemid'], 'imageid' => $VARS['imageid']]]); + + if (!$database->has('images', ['AND' => ['itemid' => $VARS['itemid'], 'primary' => true]])) { + $database->update('images', ['primary' => true], ['itemid' => $VARS['itemid'], 'LIMIT' => 1]); + } + + returnToSender("image_deleted", "&id=$VARS[itemid]"); case "signout": session_destroy(); header('Location: index.php'); diff --git a/composer.lock b/composer.lock index d07df16..cb6b6d1 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,6 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "831ae7560f5a64a68c52c69b772a8fc5", "content-hash": "a7d5f4f0d86fc84b983b1a3095ba3288", "packages": [ { @@ -64,7 +63,7 @@ "sql", "sqlite" ], - "time": "2018-06-14 18:59:08" + "time": "2018-06-14T18:59:08+00:00" }, { "name": "guzzlehttp/guzzle", @@ -129,7 +128,7 @@ "rest", "web service" ], - "time": "2018-04-22 15:46:56" + "time": "2018-04-22T15:46:56+00:00" }, { "name": "guzzlehttp/promises", @@ -180,7 +179,7 @@ "keywords": [ "promise" ], - "time": "2016-12-20 10:07:11" + "time": "2016-12-20T10:07:11+00:00" }, { "name": "guzzlehttp/psr7", @@ -245,7 +244,7 @@ "uri", "url" ], - "time": "2017-03-20 17:10:46" + "time": "2017-03-20T17:10:46+00:00" }, { "name": "lapinator/ods-php-generator", @@ -288,7 +287,7 @@ "LibreOffice", "ods" ], - "time": "2016-04-14 21:51:27" + "time": "2016-04-14T21:51:27+00:00" }, { "name": "league/csv", @@ -355,7 +354,7 @@ "read", "write" ], - "time": "2018-05-01 18:32:48" + "time": "2018-05-01T18:32:48+00:00" }, { "name": "psr/http-message", @@ -405,7 +404,7 @@ "request", "response" ], - "time": "2016-08-06 14:39:51" + "time": "2016-08-06T14:39:51+00:00" } ], "packages-dev": [], diff --git a/database.mwb b/database.mwb index de28faa7b3fadd17dac0499a905b18b0dd36941e..5808d6d8e08379a89f5cfb987122b8fe10173d96 100644 GIT binary patch literal 12032 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 p+|Zn0LexrkfpnQAH98KNr%^lnr?LAEx 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 diff --git a/database.sql b/database.sql index b4bf013..5cac0af 100644 --- a/database.sql +++ b/database.sql @@ -1,5 +1,5 @@ -- MySQL Script generated by MySQL Workbench --- Tue 26 Dec 2017 04:44:44 PM MST +-- Sat 22 Sep 2018 02:40:11 AM MDT -- Model: New Model Version: 1.0 -- MySQL Workbench Forward Engineering @@ -12,15 +12,9 @@ SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='TRADITIONAL,ALLOW_INVALID_DATES'; -- ----------------------------------------------------- -- ----------------------------------------------------- --- Schema inventory --- ----------------------------------------------------- -CREATE SCHEMA IF NOT EXISTS `inventory` DEFAULT CHARACTER SET utf8 ; -USE `inventory` ; - +-- Table `categories` -- ----------------------------------------------------- --- Table `inventory`.`categories` --- ----------------------------------------------------- -CREATE TABLE IF NOT EXISTS `inventory`.`categories` ( +CREATE TABLE IF NOT EXISTS `categories` ( `catid` INT NOT NULL AUTO_INCREMENT, `catname` VARCHAR(45) NOT NULL, PRIMARY KEY (`catid`), @@ -29,9 +23,9 @@ ENGINE = InnoDB; -- ----------------------------------------------------- --- Table `inventory`.`locations` +-- Table `locations` -- ----------------------------------------------------- -CREATE TABLE IF NOT EXISTS `inventory`.`locations` ( +CREATE TABLE IF NOT EXISTS `locations` ( `locid` INT NOT NULL AUTO_INCREMENT, `locname` VARCHAR(100) NOT NULL, `loccode` VARCHAR(45) NOT NULL, @@ -42,9 +36,9 @@ ENGINE = InnoDB; -- ----------------------------------------------------- --- Table `inventory`.`items` +-- Table `items` -- ----------------------------------------------------- -CREATE TABLE IF NOT EXISTS `inventory`.`items` ( +CREATE TABLE IF NOT EXISTS `items` ( `itemid` INT NOT NULL AUTO_INCREMENT, `catid` INT NOT NULL, `locid` INT NOT NULL, @@ -57,27 +51,29 @@ CREATE TABLE IF NOT EXISTS `inventory`.`items` ( `qty` INT NOT NULL DEFAULT 1, `want` INT NOT NULL DEFAULT 0, `userid` INT NULL, - PRIMARY KEY (`itemid`, `catid`, `locid`), + `cost` DECIMAL(10,2) NULL, + `price` DECIMAL(10,2) NULL, + PRIMARY KEY (`itemid`), INDEX `fk_items_categories_idx` (`catid` ASC), INDEX `fk_items_locations1_idx` (`locid` ASC), UNIQUE INDEX `itemid_UNIQUE` (`itemid` ASC), CONSTRAINT `fk_items_categories` FOREIGN KEY (`catid`) - REFERENCES `inventory`.`categories` (`catid`) + REFERENCES `categories` (`catid`) ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT `fk_items_locations1` FOREIGN KEY (`locid`) - REFERENCES `inventory`.`locations` (`locid`) + REFERENCES `locations` (`locid`) ON DELETE NO ACTION ON UPDATE NO ACTION) ENGINE = InnoDB; -- ----------------------------------------------------- --- Table `inventory`.`labels` +-- Table `labels` -- ----------------------------------------------------- -CREATE TABLE IF NOT EXISTS `inventory`.`labels` ( +CREATE TABLE IF NOT EXISTS `labels` ( `rowid` INT NOT NULL AUTO_INCREMENT, `name` VARCHAR(50) NOT NULL, `value` VARCHAR(100) NOT NULL, @@ -86,9 +82,9 @@ ENGINE = InnoDB; -- ----------------------------------------------------- --- Table `inventory`.`permissions` +-- Table `permissions` -- ----------------------------------------------------- -CREATE TABLE IF NOT EXISTS `inventory`.`permissions` ( +CREATE TABLE IF NOT EXISTS `permissions` ( `userid` INT NOT NULL, `itemid` INT NOT NULL, `canedit` TINYINT(1) NOT NULL DEFAULT 0, @@ -96,16 +92,16 @@ CREATE TABLE IF NOT EXISTS `inventory`.`permissions` ( INDEX `fk_permissions_items1_idx` (`itemid` ASC), CONSTRAINT `fk_permissions_items1` FOREIGN KEY (`itemid`) - REFERENCES `inventory`.`items` (`itemid`) + REFERENCES `items` (`itemid`) ON DELETE NO ACTION ON UPDATE NO ACTION) ENGINE = InnoDB; -- ----------------------------------------------------- --- Table `inventory`.`report_access_codes` +-- Table `report_access_codes` -- ----------------------------------------------------- -CREATE TABLE IF NOT EXISTS `inventory`.`report_access_codes` ( +CREATE TABLE IF NOT EXISTS `report_access_codes` ( `id` INT NOT NULL AUTO_INCREMENT, `code` VARCHAR(45) NULL, `expires` DATETIME NULL, @@ -114,6 +110,42 @@ CREATE TABLE IF NOT EXISTS `inventory`.`report_access_codes` ( ENGINE = InnoDB; +-- ----------------------------------------------------- +-- Table `images` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `images` ( + `imageid` INT NOT NULL AUTO_INCREMENT, + `itemid` INT NOT NULL, + `imagename` VARCHAR(255) NOT NULL, + `primary` TINYINT(1) NOT NULL DEFAULT 0, + PRIMARY KEY (`imageid`, `itemid`), + UNIQUE INDEX `imageid_UNIQUE` (`imageid` ASC), + INDEX `fk_images_items1_idx` (`itemid` ASC), + CONSTRAINT `fk_images_items1` + FOREIGN KEY (`itemid`) + REFERENCES `items` (`itemid`) + ON DELETE NO ACTION + ON UPDATE NO ACTION) +ENGINE = InnoDB; + + SET SQL_MODE=@OLD_SQL_MODE; SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS; SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS; + +-- ----------------------------------------------------- +-- Data for table `labels` +-- ----------------------------------------------------- +START TRANSACTION; +INSERT INTO `labels` (`rowid`, `name`, `value`) VALUES (1, 'itemid', 'Item ID'); +INSERT INTO `labels` (`rowid`, `name`, `value`) VALUES (2, 'catid', 'Category ID'); +INSERT INTO `labels` (`rowid`, `name`, `value`) VALUES (3, 'itemname', 'Item Name'); +INSERT INTO `labels` (`rowid`, `name`, `value`) VALUES (4, 'itemnumber1', 'Number Value 1'); +INSERT INTO `labels` (`rowid`, `name`, `value`) VALUES (5, 'itemnumber2', 'Number Value 2'); +INSERT INTO `labels` (`rowid`, `name`, `value`) VALUES (6, 'itemtext1', 'Text Value 1'); +INSERT INTO `labels` (`rowid`, `name`, `value`) VALUES (7, 'itemtext2', 'Text Value 2'); +INSERT INTO `labels` (`rowid`, `name`, `value`) VALUES (8, 'itemtext3', 'Text Value 3'); +INSERT INTO `labels` (`rowid`, `name`, `value`) VALUES (9, 'catname', 'Category Name'); + +COMMIT; + diff --git a/database_upgrade/v1.1_1.2.sql b/database_upgrade/v1.1_1.2.sql index dddaad0..6e9f631 100644 --- a/database_upgrade/v1.1_1.2.sql +++ b/database_upgrade/v1.1_1.2.sql @@ -5,12 +5,18 @@ */ ALTER TABLE `items` ADD COLUMN `cost` DECIMAL(10,2) NULL DEFAULT NULL AFTER `userid`, -ADD COLUMN `price` DECIMAL(10,2) NULL DEFAULT NULL AFTER `cost`; +ADD COLUMN `price` DECIMAL(10,2) NULL DEFAULT NULL AFTER `cost`;\ + +ALTER TABLE `items` +DROP PRIMARY KEY, +ADD PRIMARY KEY (`itemid`); + CREATE TABLE IF NOT EXISTS `images` ( `imageid` INT(11) NOT NULL AUTO_INCREMENT, `itemid` INT(11) NOT NULL, `imagename` VARCHAR(255) NOT NULL, + `primary` TINYINT(1) NOT NULL DEFAULT 0, PRIMARY KEY (`imageid`, `itemid`), UNIQUE INDEX `imageid_UNIQUE` (`imageid` ASC), INDEX `fk_images_items1_idx` (`itemid` ASC), @@ -20,4 +26,4 @@ CREATE TABLE IF NOT EXISTS `images` ( ON DELETE NO ACTION ON UPDATE NO ACTION) ENGINE = InnoDB -DEFAULT CHARACTER SET = utf8 +DEFAULT CHARACTER SET = utf8 \ No newline at end of file diff --git a/image.php b/image.php new file mode 100644 index 0000000..dc2341d --- /dev/null +++ b/image.php @@ -0,0 +1,62 @@ + 11) { + $imagetype = exif_imagetype($filepath); +} else { + $imagetype = false; +} + +switch ($imagetype) { + case IMAGETYPE_JPEG: + $mimetype = "image/jpeg"; + break; + case IMAGETYPE_GIF: + $mimetype = "image/gif"; + break; + case IMAGETYPE_PNG: + $mimetype = "image/png"; + break; + case IMAGETYPE_WEBP: + $mimetype = "image/webp"; + break; + default: + $mimetype = "application/octet-stream"; +} + +header("Content-Type: $mimetype"); +header('Content-Length: ' . filesize($filepath)); +header("X-Content-Type-Options: nosniff"); +$seconds_to_cache = 60 * 60 * 12; // 12 hours +$ts = gmdate("D, d M Y H:i:s", time() + $seconds_to_cache) . " GMT"; +header("Expires: $ts"); +header("Pragma: cache"); +header("Cache-Control: max-age=$seconds_to_cache"); + +ob_end_flush(); + +readfile($filepath); diff --git a/images/.htaccess b/images/.htaccess new file mode 100755 index 0000000..14249c5 --- /dev/null +++ b/images/.htaccess @@ -0,0 +1 @@ +Deny from all \ No newline at end of file diff --git a/langs/en/images.json b/langs/en/images.json new file mode 100644 index 0000000..bb9a405 --- /dev/null +++ b/langs/en/images.json @@ -0,0 +1,10 @@ +{ + "Editing images for item": "Editing images for {item}", + "Edit Images": "Edit Images", + "Browse": "Browse", + "Upload": "Upload", + "Promoted": "Promoted", + "Promote": "Promote", + "Delete": "Delete", + "Back": "Back" +} diff --git a/pages.php b/pages.php index 2d50c5f..c5b92e5 100644 --- a/pages.php +++ b/pages.php @@ -61,6 +61,16 @@ define("PAGES", [ "static/js/edititem.js" ], ], + "editimages" => [ + "title" => "Edit item images", + "navbar" => false, + "styles" => [ + "static/css/files.css" + ], + "scripts" => [ + "static/js/files.js" + ] + ], "editcat" => [ "title" => "Edit category", "navbar" => false, diff --git a/pages/editimages.php b/pages/editimages.php new file mode 100644 index 0000000..0e98ddf --- /dev/null +++ b/pages/editimages.php @@ -0,0 +1,87 @@ +has('items', ['itemid' => $VARS['id']])) { + $images = $database->select('images', ['imageid', 'imagename', 'primary'], ['itemid' => $VARS['id']]); +} else { + header('Location: app.php?page=items&msg=invalid_itemid'); + die(); +} +?> + +
+

+ build("Editing images for item", ['item' => "" . htmlspecialchars($database->get('items', 'name', ['itemid' => $VARS['id']])) . ""]); ?> +

+
+
+
+
+ +
+ + get("Browse"); ?> + + +
+
+ + + +
+
+
+ +
+
+ +
+ + get("Promoted"); ?> + +
+ + + + + +
+ +
+ + + + + +
+
+
+
+ +
+
+ + +
\ No newline at end of file diff --git a/pages/edititem.php b/pages/edititem.php index 636557a..825db81 100644 --- a/pages/edititem.php +++ b/pages/edititem.php @@ -214,14 +214,26 @@ if (!empty($VARS['id'])) { ?>" /> + + + diff --git a/settings.template.php b/settings.template.php index 26da672..0a00a4d 100644 --- a/settings.template.php +++ b/settings.template.php @@ -34,6 +34,11 @@ define("TIMEZONE", "America/Denver"); // Base URL for site links. define('URL', '.'); +// Folder for item images +// If in the webroot, verify that the contents of the folder are not accessible +// from a client (web browser). +define('FILE_UPLOAD_PATH', __DIR__ . '/images'); + // Use Captcheck on login screen // https://captcheck.netsyms.com define("CAPTCHA_ENABLED", FALSE); diff --git a/static/css/files.css b/static/css/files.css new file mode 100644 index 0000000..99485dc --- /dev/null +++ b/static/css/files.css @@ -0,0 +1,24 @@ +/* +This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this +file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ +.btn-file { + position: relative; + overflow: hidden; +} +.btn-file input[type=file] { + position: absolute; + top: 0; + right: 0; + min-width: 100%; + min-height: 100%; + font-size: 100px; + text-align: right; + filter: alpha(opacity=0); + opacity: 0; + outline: none; + background: white; + cursor: inherit; + display: block; +} diff --git a/static/js/files.js b/static/js/files.js new file mode 100644 index 0000000..23557f7 --- /dev/null +++ b/static/js/files.js @@ -0,0 +1,19 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +$(document).on('change', ':file', function () { + var input = $(this), + numFiles = input.get(0).files ? input.get(0).files.length : 1, + label = input.val().replace(/\\/g, '/').replace(/.*\//, ''); + input.trigger('fileselect', [numFiles, label]); +}); + +$(':file').on('fileselect', function (event, numFiles, label) { + var message = numFiles > 1 ? numFiles + ' files selected' : label; + $("#uploadstatus").val(message); +}); + +$(function () { + $('[data-toggle="tooltip"]').tooltip(); +}) \ No newline at end of file