Implemented attachments for products
This commit is contained in:
parent
3cc2b91ed4
commit
a3656604c0
@ -18,4 +18,11 @@ $error_address = 'root@example.com';
|
||||
#$notify_discard = 'inventory-tracking@example.com';
|
||||
$discard_notify = false;
|
||||
|
||||
# Directory to save attachments to
|
||||
# Interpreted as absolute path if beginning with /,
|
||||
# otherwise assumed to be relative to the application root.
|
||||
# Must be writable by the web server.
|
||||
# Does not need to be within the web root.
|
||||
$files_dir = 'attachments';
|
||||
|
||||
?>
|
||||
|
@ -280,12 +280,6 @@
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<div id="product-history"
|
||||
class="¤hidden¤">
|
||||
<h2>Artikelhistorik</h2>
|
||||
¤history¤
|
||||
</div>
|
||||
<!--
|
||||
<div id="product-attachments"
|
||||
class="¤hidden¤">
|
||||
<h2>Bilagor</h2>
|
||||
@ -298,17 +292,21 @@
|
||||
<input id="uploadfile"
|
||||
name="uploadfile"
|
||||
type="file"
|
||||
onchange="show_file(event)"/>
|
||||
onchange="showFile(event)"/>
|
||||
<input id="filename"
|
||||
name="filename"
|
||||
type="text"
|
||||
placeholder="Välj en fil..."
|
||||
onclick="select_file(event)"
|
||||
onclick="selectFile(event)"
|
||||
readonly />
|
||||
<button>Ladda upp</button>
|
||||
</form>
|
||||
</div>
|
||||
-->
|
||||
<div id="product-history"
|
||||
class="¤hidden¤">
|
||||
<h2>Artikelhistorik</h2>
|
||||
¤history¤
|
||||
</div>
|
||||
<div id="product-label"
|
||||
class="¤hidden¤">
|
||||
<h2>Etikett</h2>
|
||||
@ -316,13 +314,23 @@
|
||||
</div>
|
||||
|
||||
¤¤ attachment_list ¤¤
|
||||
<ul>
|
||||
<ul class="attachment-list">
|
||||
¤attachments¤
|
||||
</ul>
|
||||
|
||||
¤¤ attachment ¤¤
|
||||
<li>
|
||||
<strong>¤name¤</strong>: <a href="./?page=dl&id=¤id¤">Ladda ner</a>
|
||||
<strong>¤name¤</strong> (¤date¤): <a href="./?page=dl&id=¤id¤">Ladda ner</a>
|
||||
<br/>
|
||||
<form onSubmit="JavaScript:deleteAttachment(event)">
|
||||
<input type="hidden"
|
||||
name="id"
|
||||
value="¤id¤" />
|
||||
<input type="hidden"
|
||||
name="name"
|
||||
value="¤name¤" />
|
||||
<button>Ta bort</button>
|
||||
</form>
|
||||
</li>
|
||||
|
||||
¤¤ product_label ¤¤
|
||||
|
@ -59,6 +59,13 @@ class Ajax extends Responder {
|
||||
break;
|
||||
case 'toggleservice':
|
||||
$out = $this->toggle_service();
|
||||
break;
|
||||
case 'addattachment':
|
||||
$out = $this->add_attachment();
|
||||
break;
|
||||
case 'deleteattachment':
|
||||
$out = $this->delete_attachment();
|
||||
break;
|
||||
}
|
||||
print($out->toJson());
|
||||
}
|
||||
@ -381,5 +388,31 @@ class Ajax extends Responder {
|
||||
.'på den här artikeln nu.');
|
||||
}
|
||||
}
|
||||
|
||||
private function add_attachment() {
|
||||
try {
|
||||
$product = new Product($_POST['id']);
|
||||
$uploadfile = $_FILES['uploadfile'];
|
||||
$attach = Attachment::create($uploadfile, $product->get_id());
|
||||
$date = format_date($attach->get_uploadtime());
|
||||
$fragment = replace(array('name' => $attach->get_filename(),
|
||||
'id' => $attach->get_id(),
|
||||
'date' => $date),
|
||||
$this->fragments['attachment']);
|
||||
return new Success($fragment);
|
||||
} catch(Exception $e) {
|
||||
return new Failure($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function delete_attachment() {
|
||||
$attach = new Attachment($_POST['id']);
|
||||
try {
|
||||
$attach->delete();
|
||||
return new Success('');
|
||||
} catch(Exception $e) {
|
||||
return new Failure($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
109
include/Attachment.php
Normal file
109
include/Attachment.php
Normal file
@ -0,0 +1,109 @@
|
||||
<?php
|
||||
class Attachment {
|
||||
private $id;
|
||||
private $product;
|
||||
private $filename;
|
||||
private $uploadtime;
|
||||
|
||||
public static function create($file, $prodid) {
|
||||
begin_trans();
|
||||
try {
|
||||
if(!isset($file['error']) || is_array($file['error'])) {
|
||||
throw new Exception('Ogiltigt anrop.');
|
||||
}
|
||||
switch($file['error']) {
|
||||
case UPLOAD_ERR_OK:
|
||||
break;
|
||||
case UPLOAD_ERR_NO_FILE:
|
||||
throw new Exception('Ingen fil skickades.');
|
||||
case UPLOAD_ERR_INI_SIZE:
|
||||
case UPLOAD_ERR_FORM_SIZE:
|
||||
throw new Exception('Filen är för stor.');
|
||||
default:
|
||||
throw new Exception('Ett okänt fel har inträffat.');
|
||||
}
|
||||
$filename = $file['name'];
|
||||
$insert = prepare('insert into `attachment`
|
||||
(`product`, `filename`, `uploadtime`)
|
||||
values (?, ?, ?)');
|
||||
bind($insert, 'isi', $prodid, $filename, time());
|
||||
execute($insert);
|
||||
$attachid = $insert->insert_id;
|
||||
global $files_dir;
|
||||
$savepath = $files_dir;
|
||||
if(substr($savepath, -1) !== '/') {
|
||||
$savepath .= '/';
|
||||
}
|
||||
$savepath .= $attachid;
|
||||
$tmp_name = $file['tmp_name'];
|
||||
if(file_exists($savepath)) {
|
||||
throw new Exception('Filens plats är upptagen. '
|
||||
.'Det här borde aldrig inträffa.');
|
||||
}
|
||||
if(!move_uploaded_file($tmp_name, $savepath)) {
|
||||
throw new Exception('Filen kunde inte sparas.');
|
||||
}
|
||||
commit_trans();
|
||||
return new Attachment($attachid);
|
||||
} catch(Exception $e) {
|
||||
revert_trans();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public function __construct($id) {
|
||||
$search = prepare('select * from `attachment` where `id`=?');
|
||||
bind($search, 'i', $id);
|
||||
execute($search);
|
||||
$result = result_single($search);
|
||||
if($result === null) {
|
||||
throw new Exception('Attachment does not exist.');
|
||||
}
|
||||
$this->id = $result['id'];
|
||||
$this->product = $result['product'];
|
||||
$this->filename = $result['filename'];
|
||||
$this->uploadtime = $result['uploadtime'];
|
||||
}
|
||||
|
||||
public function delete() {
|
||||
$delete = prepare('update `attachment` set `deletetime`=?
|
||||
where `id`=?');
|
||||
bind($delete, 'ii', time(), $this->get_id());
|
||||
execute($delete);
|
||||
$path = $this->get_filepath();
|
||||
if(file_exists($path)) {
|
||||
unlink($path);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function get_id() {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function get_product() {
|
||||
return new Product($this->product);
|
||||
}
|
||||
|
||||
public function get_filename() {
|
||||
return $this->filename;
|
||||
}
|
||||
|
||||
public function get_uploadtime() {
|
||||
return $this->uploadtime;
|
||||
}
|
||||
|
||||
public function get_filepath() {
|
||||
global $files_dir;
|
||||
$path = $files_dir;
|
||||
if(substr($path, -1) !== '/') {
|
||||
$path .= '/';
|
||||
}
|
||||
$path .= $this->get_id();
|
||||
if(!file_exists($path)) {
|
||||
throw new Exception('Filen har försvunnit.');
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
?>
|
25
include/Download.php
Normal file
25
include/Download.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
class Download extends Responder {
|
||||
private $attachment;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
if(isset($_GET['id'])) {
|
||||
$this->attachment = new Attachment($_GET['id']);
|
||||
}
|
||||
}
|
||||
|
||||
public function render() {
|
||||
$filename = $this->attachment->get_filename();
|
||||
$filepath = $this->attachment->get_filepath();
|
||||
header('Content-Description: File Transfer');
|
||||
header('Content-Type: application/octet-stream');
|
||||
header('Content-Disposition: attachment; filename="'.$filename.'"');
|
||||
header('Expires: 0');
|
||||
header('Cache-Control: no-cache');
|
||||
header('Content-length: '.filesize($filepath));
|
||||
readfile($filepath);
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
?>
|
@ -21,8 +21,8 @@ class Product {
|
||||
$now = time();
|
||||
begin_trans();
|
||||
try {
|
||||
$stmt = 'insert into
|
||||
`product`(`brand`, `name`, `invoice`, `serial`, `createtime`)
|
||||
$stmt = 'insert into `product`
|
||||
(`brand`, `name`, `invoice`, `serial`, `createtime`)
|
||||
values (?, ?, ?, ?, ?)';
|
||||
$ins_prod = prepare($stmt);
|
||||
bind($ins_prod, 'ssssi', $brand, $name, $invoice, $serial, $now);
|
||||
@ -403,7 +403,17 @@ class Product {
|
||||
}
|
||||
|
||||
public function get_attachments() {
|
||||
return array();
|
||||
$out = array();
|
||||
$find = prepare('select `id` from `attachment`
|
||||
where `product`=? and `deletetime` is NULL
|
||||
order by `uploadtime` asc');
|
||||
bind($find, 'i', $this->id);
|
||||
execute($find);
|
||||
$items = result_list($find);
|
||||
foreach($items as $item) {
|
||||
$out[] = new Attachment($item['id']);
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
@ -89,7 +89,6 @@ class ProductPage extends Page {
|
||||
'service' => 'Starta service',
|
||||
'history' => $history,
|
||||
'attachments' => $attachments);
|
||||
$attachments = $this->product->get_attachments();
|
||||
if(class_exists('QRcode')) {
|
||||
$fields['label'] = replace($fields,
|
||||
$this->fragments['product_label']);
|
||||
@ -144,12 +143,14 @@ class ProductPage extends Page {
|
||||
|
||||
private function build_attachment_list($attachments) {
|
||||
if(!$attachments) {
|
||||
return 'Inga bilagor.';
|
||||
return '<p>Inga bilagor.</p>';
|
||||
}
|
||||
$items = '';
|
||||
foreach($attachments as $attachment) {
|
||||
$items .= replace(array('name' => $attachment->get_name(),
|
||||
'id' => $attachments->get_id()),
|
||||
$date = format_date($attachment->get_uploadtime());
|
||||
$items .= replace(array('name' => $attachment->get_filename(),
|
||||
'id' => $attachment->get_id(),
|
||||
'date' => $date),
|
||||
$this->fragments['attachment']);
|
||||
}
|
||||
return replace(array('attachments' => $items),
|
||||
|
@ -5,27 +5,35 @@ abstract class Responder {
|
||||
public function __construct() {
|
||||
$this->fragments = get_fragments('./html/fragments.html');
|
||||
}
|
||||
|
||||
|
||||
final protected function escape_tags($tags) {
|
||||
foreach($tags as $key => $tag) {
|
||||
$tags[$key] = str_replace(array("'",
|
||||
'"'),
|
||||
array(''',
|
||||
'"'),
|
||||
strtolower($tag));
|
||||
$tags[$key] = $this->escape_string(strtolower($tag));
|
||||
}
|
||||
return $tags;
|
||||
}
|
||||
|
||||
final protected function unescape_tags($tags) {
|
||||
foreach($tags as $key => $tag) {
|
||||
$tags[$key] = str_replace(array(''',
|
||||
'"'),
|
||||
array("'",
|
||||
'"'),
|
||||
strtolower($tag));
|
||||
$tags[$key] = $this->unescape_string(strtolower($tag));
|
||||
}
|
||||
return $tags;
|
||||
}
|
||||
|
||||
final protected function escape_string($string) {
|
||||
return str_replace(array("'",
|
||||
'"'),
|
||||
array(''',
|
||||
'"'),
|
||||
$string);
|
||||
}
|
||||
|
||||
final protected function unescape_string($string) {
|
||||
return str_replace(array(''',
|
||||
'"'),
|
||||
array("'",
|
||||
'"'),
|
||||
$string);
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
@ -97,6 +97,8 @@ function make_page($page) {
|
||||
return new QR();
|
||||
case 'print':
|
||||
return new Printer();
|
||||
case 'dl':
|
||||
return new Download();
|
||||
}
|
||||
}
|
||||
|
||||
|
100
script.js
100
script.js
@ -2,18 +2,7 @@ function ajaxRequest(action, datalist, callback) {
|
||||
var request = false
|
||||
request = new XMLHttpRequest()
|
||||
request.open('POST', "./?page=ajax&action=" + action, true)
|
||||
request.setRequestHeader('Content-Type',
|
||||
'application/x-www-form-urlencoded')
|
||||
var datastring = ''
|
||||
var first = true
|
||||
for(let [key, value] of datalist) {
|
||||
if(!first) {
|
||||
datastring += '&'
|
||||
}
|
||||
datastring += key + '=' + encodeURIComponent(value)
|
||||
first = false
|
||||
}
|
||||
request.send(datastring)
|
||||
request.send(datalist)
|
||||
|
||||
request.onreadystatechange = function() {
|
||||
if (request.readyState == 4) {
|
||||
@ -83,12 +72,18 @@ function fixDuplicateInputNames(fields) {
|
||||
}
|
||||
|
||||
function dataListFromForm(form, filter = function(field) {return true}) {
|
||||
var out = []
|
||||
var out = new FormData()
|
||||
var fields = form.querySelectorAll('input,textarea')
|
||||
fixDuplicateInputNames(fields)
|
||||
for(var i = 0; i < fields.length; i++) {
|
||||
if(filter(fields[i])) {
|
||||
out.push([fields[i].name, fields[i].value])
|
||||
var field = fields[i]
|
||||
if(filter(field)) {
|
||||
if(field.type == 'file') {
|
||||
var file = field.files[0]
|
||||
out.append(field.name, file, file.name)
|
||||
} else {
|
||||
out.append(field.name, field.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
@ -103,9 +98,9 @@ function getFragment(name, callback) {
|
||||
}
|
||||
}
|
||||
|
||||
ajaxRequest('getfragment',
|
||||
[['fragment', name]],
|
||||
unpack)
|
||||
var data = new FormData()
|
||||
data.append('fragment', name)
|
||||
ajaxRequest('getfragment', data, unpack)
|
||||
}
|
||||
|
||||
function replace(fragment, replacements) {
|
||||
@ -158,12 +153,12 @@ function extendLoan(event) {
|
||||
|
||||
function startInventory(event) {
|
||||
event.preventDefault()
|
||||
ajaxRequest('startinventory', [], reloadOrError)
|
||||
ajaxRequest('startinventory', new FormData(), reloadOrError)
|
||||
}
|
||||
|
||||
function endInventory(event) {
|
||||
event.preventDefault()
|
||||
ajaxRequest('endinventory', [], reloadOrError)
|
||||
ajaxRequest('endinventory', new FormData(), reloadOrError)
|
||||
}
|
||||
|
||||
function inventoryProduct(event) {
|
||||
@ -220,7 +215,9 @@ function suggest(input, type) {
|
||||
suggestlist.appendChild(next)
|
||||
}
|
||||
}
|
||||
ajaxRequest('suggest', [['type', type]], render)
|
||||
data = new FormData()
|
||||
data.append('type', type)
|
||||
ajaxRequest('suggest', data, render)
|
||||
}
|
||||
|
||||
function suggestContent(input) {
|
||||
@ -236,7 +233,9 @@ function suggestContent(input) {
|
||||
suggestlist.appendChild(next)
|
||||
}
|
||||
}
|
||||
ajaxRequest('suggestcontent', [['fieldname', input.name]], render)
|
||||
data = new FormData()
|
||||
data.append('fieldname', input.name)
|
||||
ajaxRequest('suggestcontent', data, render)
|
||||
}
|
||||
|
||||
function addField(event) {
|
||||
@ -414,6 +413,59 @@ function updateUser(event) {
|
||||
ajaxRequest('updateuser', dataListFromForm(form), reloadOrError)
|
||||
}
|
||||
|
||||
function uploadAttachment(event) {
|
||||
event.preventDefault()
|
||||
var form = event.currentTarget
|
||||
var render = function(result) {
|
||||
if(result.type != 'success') {
|
||||
showResult(result)
|
||||
return
|
||||
}
|
||||
var classvalue = 'attachment-list'
|
||||
var list = form.parentNode.querySelector('.'+classvalue)
|
||||
if(list == null) {
|
||||
list = document.createElement('ul')
|
||||
list.classList.add(classvalue)
|
||||
var p = form.parentNode.querySelector('p')
|
||||
p.replaceWith(list)
|
||||
}
|
||||
var temp = document.createElement('template')
|
||||
temp.innerHTML = result.message
|
||||
list.appendChild(temp.content.firstChild)
|
||||
}
|
||||
var filter = function(input) {
|
||||
if(input.name == 'filename') {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
ajaxRequest('addattachment', dataListFromForm(form, filter), render)
|
||||
}
|
||||
|
||||
function deleteAttachment(event) {
|
||||
event.preventDefault()
|
||||
var form = event.currentTarget
|
||||
var node = form.parentNode
|
||||
var name = form.name.value
|
||||
if(window.confirm("Är du säker på att du vill ta bort bilagan '"
|
||||
+name+"'?")) {
|
||||
var render = function(result) {
|
||||
if(result.type == 'success') {
|
||||
var list = node.parentNode
|
||||
list.removeChild(node)
|
||||
if(list.childElementCount == 0) {
|
||||
var p = document.createElement('p')
|
||||
p.append('Inga bilagor.')
|
||||
list.replaceWith(p)
|
||||
}
|
||||
} else {
|
||||
showResult(result);
|
||||
}
|
||||
}
|
||||
ajaxRequest('deleteattachment', dataListFromForm(form), render)
|
||||
}
|
||||
}
|
||||
|
||||
function productDataList(form) {
|
||||
var filter = function(input) {
|
||||
var name = input.name
|
||||
@ -513,11 +565,11 @@ function removeTerm(event) {
|
||||
}
|
||||
|
||||
function selectFile(event) {
|
||||
var fileinput = document.getElementById("uploadfile")
|
||||
var fileinput = event.currentTarget.parentNode.uploadfile
|
||||
fileinput.click()
|
||||
}
|
||||
|
||||
function showFile(event) {
|
||||
var filefield = document.getElementById("filename")
|
||||
var filefield = event.currentTarget.parentNode.filename
|
||||
filefield.value = event.currentTarget.files[0].name
|
||||
}
|
||||
|
@ -2,6 +2,10 @@ textarea {
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
#message {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
|
Loading…
x
Reference in New Issue
Block a user