Add image uploading for items (close #8)

master
Skylar Ittner 6 years ago
parent 75d8811477
commit 9a5aefbeb5

2
.gitignore vendored

@ -3,3 +3,5 @@ settings.php
nbproject/private
database.mwb.bak
*.sync-conflict*
images/
!images/.htaccess

@ -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("<br>", $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');

15
composer.lock generated

@ -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": [],

Binary file not shown.

@ -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;

@ -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),

@ -0,0 +1,62 @@
<?php
/*
* 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/.
*/
require_once __DIR__ . "/required.php";
$base = FILE_UPLOAD_PATH . "/";
if (isset($_GET['i'])) {
$file = $_GET['i'];
$filepath = $base . $file;
if (!file_exists($filepath) || is_dir($filepath)) {
http_response_code(404);
die("404 File Not Found");
}
if (strpos(realpath($filepath), FILE_UPLOAD_PATH) !== 0) {
http_response_code(404);
die("404 File Not Found");
}
} else {
http_response_code(404);
die("404 File Not Found");
}
if (filesize($filepath) > 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);

@ -0,0 +1 @@
Deny from all

@ -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"
}

@ -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,

@ -0,0 +1,87 @@
<?php
/*
* 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/.
*/
require_once __DIR__ . '/../required.php';
redirectifnotloggedin();
$images = [];
if (!empty($VARS['id']) && $database->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();
}
?>
<div class="card border-green">
<h3 class="card-header text-green">
<i class="fas fa-images"></i> <?php $Strings->build("Editing images for item", ['item' => "<span id=\"name_title\">" . htmlspecialchars($database->get('items', 'name', ['itemid' => $VARS['id']])) . "</span>"]); ?>
</h3>
<div class="card-body">
<div class="ml-auto my-auto">
<form action="action.php" method="POST" enctype="multipart/form-data">
<div class="input-group">
<input type="text" id="uploadstatus" class="form-control" readonly />
<div class="input-group-append">
<span class="btn btn-primary btn-file">
<i class="fas fa-folder-open"></i> <?php $Strings->get("Browse"); ?> <input id="fileupload" type="file" name="files[]" accept=".png,.jpg,.jpeg,.gif,.webp,image/png,image/jpeg,image/gif,image/webp" multiple required />
</span>
<button class="btn btn-success" type="submit"><i class="fas fa-cloud-upload-alt"></i> <?php $Strings->get("Upload"); ?></button>
</div>
</div>
<input type="hidden" name="action" value="imageupload" />
<input type="hidden" name="source" value="editimages" />
<input type="hidden" name="itemid" value="<?php echo htmlspecialchars($VARS['id']); ?>" />
</form>
</div>
<div class="row">
<?php
foreach ($images as $i) {
?>
<div class="col-12 col-sm-6 col-md-4 col-lg-4 col-xl-3">
<div class="card">
<img class="card-img" src="image.php?i=<?php echo $i['imagename']; ?>" alt="">
<div class="card-img-overlay text-right">
<?php
if ($i['primary']) {
?>
<span class="btn btn-green btn-sm" data-toggle="tooltip" data-placement="bottom" title=""><i class="fas fa-check"></i> <?php $Strings->get("Promoted"); ?></span>
<?php
} else {
?>
<form action="action.php" method="POST">
<input type="hidden" name="action" value="promoteimage" />
<input type="hidden" name="itemid" value="<?php echo $VARS['id']; ?>" />
<input type="hidden" name="imageid" value="<?php echo $i['imageid']; ?>" />
<input type="hidden" name="source" value="editimages" />
<button type="submit" class="btn btn-primary btn-sm"><i class="fas fa-level-up-alt"></i> <?php $Strings->get("Promote"); ?></button>
</form>
<?php
}
?>
<form action="action.php" method="POST">
<input type="hidden" name="action" value="deleteimage" />
<input type="hidden" name="itemid" value="<?php echo $VARS['id']; ?>" />
<input type="hidden" name="imageid" value="<?php echo $i['imageid']; ?>" />
<input type="hidden" name="source" value="editimages" />
<button type="submit" class="btn btn-danger btn-sm mt-1"><i class="fas fa-trash"></i> <?php $Strings->get("Delete"); ?></button>
</form>
</div>
</div>
</div>
<?php
}
?>
</div>
</div>
<div class="card-footer d-flex">
<a href="./app.php?page=edititem&id=<?php echo $_GET['id']; ?>" class="btn btn-success mr-auto"><i class="fas fa-arrow-left"></i> <?php $Strings->get("Back"); ?></a>
</div>
</div>

@ -214,14 +214,26 @@ if (!empty($VARS['id'])) {
?>" />
<input type="hidden" name="action" value="edititem" />
<input type="hidden" name="source" value="items" />
<?php
if ($cloning) {
?>
<input type="hidden" name="cloneof" value="<?php echo $VARS['id']; ?>" />
<?php
}
?>
<div class="card-footer d-flex">
<button type="submit" class="btn btn-success mr-auto"><i class="fas fa-save"></i> <?php $Strings->get("save"); ?></button>
<?php
if ($editing && !$cloning) {
?>
<button type="submit" class="btn btn-success mr-1"><i class="fas fa-save"></i> <?php $Strings->get("save"); ?></button>
<a href="./app.php?page=editimages&id=<?php echo $_GET['id']; ?>" class="btn btn-primary mr-auto"><i class="fas fa-images"></i> <?php $Strings->get("Edit Images"); ?></a>
<a href="action.php?action=deleteitem&source=items&itemid=<?php echo htmlspecialchars($VARS['id']); ?>" class="btn btn-danger ml-auto"><i class="fas fa-times"></i> <?php $Strings->get('delete'); ?></a>
<?php
} else {
?>
<button type="submit" class="btn btn-success mr-auto"><i class="fas fa-save"></i> <?php $Strings->get("save"); ?></button>
<?php
}
?>
</div>

@ -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);

@ -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;
}

@ -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();
})
Loading…
Cancel
Save