Add order attachments module
- Implemented CRUD operations for order attachments, including adding, listing, downloading, and deleting. - Updated `db_object_t` and `attachments` to enhance object-attachment interactions with new methods for formatting, creating, and managing attachments. - Added new routes (`/orders/attachments` and `/attachments/upload`) for attachment-related functionality. - Adjusted `file_server.php` to handle temp file downloads and attachment storage. - Improved typing and error handling across attachment helper methods and classes.
This commit is contained in:
@@ -49,12 +49,27 @@ class attachments implements attachments_i
|
||||
{
|
||||
return (new object_attachments_o())->select($attachment_id) ?: null;
|
||||
}
|
||||
/**
|
||||
* Format an object_attachments_o to an attachment object
|
||||
* @param object_attachments_o $object_attachments_o The object to format
|
||||
* @return attachment The formatted attachment
|
||||
* @throws Exception If the attachment cannot be formatted
|
||||
*/
|
||||
public function format(object_attachments_o $object_attachments_o): attachment
|
||||
{
|
||||
$data = (object)$object_attachments_o->toArray();
|
||||
// If content is JSON, decode it
|
||||
if (is_string($data->content) && $data->content !== '' && $data->content[0] === '{') {
|
||||
$data->content = json_decode($data->content, true);
|
||||
}
|
||||
return (new attachment())->populate($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @throws Exception If the attachment cannot be created
|
||||
*/
|
||||
public function create(string $type, int $object_id, attachment_content $attachment_content): bool
|
||||
public function create(string $type, int $object_id, attachment_content $attachment_content): int|bool
|
||||
{
|
||||
$object_attachments_o = new object_attachments_o();
|
||||
$object_attachments_o->add([
|
||||
@@ -62,7 +77,7 @@ class attachments implements attachments_i
|
||||
'object_id' => $object_id,
|
||||
'content' => json_encode($attachment_content->toArray()), // Store as JSON
|
||||
]);
|
||||
return (bool)$object_attachments_o->id;
|
||||
return $object_attachments_o->id ?: false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,11 +2,19 @@
|
||||
// Get the file name from the URL
|
||||
$file = $_SERVER['REQUEST_URI'];
|
||||
$isPDF = false;
|
||||
$isPDFStore = false;
|
||||
$isAttachment = false;
|
||||
// Check if the filetype is .pdf
|
||||
if (preg_match('/\.pdf$/', $file)) {
|
||||
$isPDF = true;
|
||||
// Check if the file name contains "temp_file_"
|
||||
if (!str_contains($file, 'temp_file_')) {
|
||||
$isPDFStore = true;
|
||||
} else {
|
||||
$isAttachment = true;
|
||||
}
|
||||
}
|
||||
if ($isPDF) {
|
||||
if ($isPDF && $isPDFStore) {
|
||||
$file = str_replace('/modules/washcertificates/output/certificates/', '', $file);
|
||||
|
||||
// Download the file from minio, and send it to the client
|
||||
@@ -27,6 +35,29 @@ if ($isPDF) {
|
||||
header('Content-Disposition: inline; filename="' . $file . '"');
|
||||
header('Content-Length: ' . filesize($certificate_path));
|
||||
readfile($certificate_path);
|
||||
// Delete the certificate from /tmp after sending it
|
||||
unlink($certificate_path);
|
||||
exit;
|
||||
}
|
||||
|
||||
// If the file is an attachment, serve it as a download
|
||||
if ($isAttachment) {
|
||||
$attachment_store = new \classes\attachment_store();
|
||||
$file = str_replace('/files/', '', $file);
|
||||
// Check if the file exists in the attachment store
|
||||
if (!$attachment_store->isFileInStore($file)) {
|
||||
echo 'Attachment not found in store';
|
||||
exit;
|
||||
}
|
||||
// Download the file from the store to /tmp
|
||||
$file_path = $attachment_store->download($file);
|
||||
// Send the file to the client
|
||||
header('Content-Type: ' . mime_content_type($file_path));
|
||||
header('Content-Disposition: attachment; filename="' . basename($file_path) . '"');
|
||||
header('Content-Length: ' . filesize($file_path));
|
||||
readfile($file_path);
|
||||
// Delete the file from /tmp after sending it
|
||||
unlink($file_path);
|
||||
exit;
|
||||
}
|
||||
|
||||
@@ -47,5 +78,7 @@ if (!$isPDF) {
|
||||
header('Content-Disposition: inline; filename="' . basename($file_path) . '"');
|
||||
header('Content-Length: ' . filesize($file_path));
|
||||
readfile($file_path);
|
||||
// Delete the file from /tmp after sending it
|
||||
unlink($file_path);
|
||||
exit;
|
||||
}
|
||||
@@ -26,10 +26,10 @@ interface attachments_i
|
||||
* @param string $type The type of the entity parent. (E.g. users_o, groups_o, etc.)
|
||||
* @param int $object_id The ID of the entity.
|
||||
* @param attachment_content $attachment_content The content of the attachment to be added.
|
||||
* @return bool True on success, false on failure.
|
||||
* @return int|bool The ID of the newly created attachment on success, or false on failure.
|
||||
* @see attachment_content
|
||||
*/
|
||||
public function create(string $type, int $object_id, attachment_content $attachment_content): bool;
|
||||
public function create(string $type, int $object_id, attachment_content $attachment_content): int|bool;
|
||||
/**
|
||||
* Delete an attachment by its ID.
|
||||
* @param int $attachment_id The ID of the attachment to be deleted.
|
||||
|
||||
@@ -43,7 +43,7 @@ class object_attachments_o extends db
|
||||
public function getObjectProperties(): void
|
||||
{
|
||||
$this->object_type = new object_property($this->table, $this->id, 'object_type', 'string', false);
|
||||
$this->object_id = new object_property($this->table, $this->id, 'object_id', 'number', false);
|
||||
$this->object_id = new object_property($this->table, $this->id, 'object_id', 'int', false);
|
||||
$this->content = new object_property($this->table, $this->id, 'content', 'string', false);
|
||||
$this->created_at = new object_property($this->table, $this->id, 'created_at', 'string', false);
|
||||
$this->updated_at = new object_property($this->table, $this->id, 'updated_at', 'string', false);
|
||||
@@ -54,4 +54,17 @@ class object_attachments_o extends db
|
||||
{
|
||||
//TODO: Add cache invalidation
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'id' => (int)$this->id,
|
||||
'object_type' => (string)$this->object_type->value(),
|
||||
'object_id' => (int)$this->object_id->value(),
|
||||
'content' => json_decode($this->content->value(), true),
|
||||
'created_at' => $this->created_at->value(),
|
||||
'updated_at' => $this->updated_at->value(),
|
||||
'deleted_at' => $this->deleted_at->value(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,9 @@ namespace routes;
|
||||
|
||||
use attachments\helpers\attachment;
|
||||
use attachments\helpers\attachment_content;
|
||||
use classes\attachment_store;
|
||||
use classes\attachments;
|
||||
use classes\response;
|
||||
use objects\orders_o;
|
||||
use traits\route_t;
|
||||
|
||||
@@ -23,5 +25,20 @@ class attachmentsRoute
|
||||
$orders_o->addAttachment((new attachment_content())->setOther('Hello World!'));
|
||||
$response->success($orders_o->listAttachments());
|
||||
});
|
||||
$this->post('/attachments/upload', function () {
|
||||
global $response;
|
||||
self::requireParameters(['base64_image']);
|
||||
$base64_image = self::getParameter('base64_image');
|
||||
//self::requirePermission('modules_scanner_lpr');
|
||||
if (empty($base64_image)) {
|
||||
$response->error('Base64 image is required.');
|
||||
}
|
||||
$uploads = new attachment_store();
|
||||
$object_name = $uploads->storeTempImageFromBase64(
|
||||
$base64_image
|
||||
);
|
||||
echo $object_name;
|
||||
$response->success(['object_name' => $object_name]);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
namespace routes;
|
||||
|
||||
use attachments\helpers\attachment_content;
|
||||
use classes\attachment_store;
|
||||
use classes\attachments;
|
||||
use classes\authentication;
|
||||
use classes\response;
|
||||
use classes\stripe;
|
||||
@@ -314,6 +317,198 @@ class ordersRoute
|
||||
]
|
||||
);
|
||||
|
||||
$this->get('/orders/attachments/download', function () {
|
||||
// Require the user to be logged in
|
||||
global $response;
|
||||
$this->requirePermission('download_order_attachments');
|
||||
// Get the user object
|
||||
$user = (new authentication())->get_user();
|
||||
// Check if the request was successful
|
||||
if ($user) {
|
||||
// Get the order ID and attachment ID from the request
|
||||
self::requireParameters([
|
||||
'order_id',
|
||||
'attachment_id'
|
||||
]);
|
||||
$order_id = self::getParameter('order_id');
|
||||
$attachment_id = self::getParameter('attachment_id');
|
||||
if (!is_numeric($order_id) || (int)$order_id < 1) {
|
||||
$response->error('Invalid order ID', 400);
|
||||
}
|
||||
if (!is_numeric($attachment_id) || (int)$attachment_id < 1) {
|
||||
$response->error('Invalid attachment ID', 400);
|
||||
}
|
||||
// Get the current order
|
||||
$order = (new orders_o())->getOrderById((int)$order_id);
|
||||
// Check if the order exists
|
||||
if (!$order->exists()) {
|
||||
$response->error('Order not found', 400);
|
||||
}
|
||||
// Get the attachment
|
||||
$attachment = $order->getAttachment((int)$attachment_id);
|
||||
if (!$attachment->exists()) {
|
||||
$response->error('Attachment not found', 400);
|
||||
}
|
||||
// Create a download link
|
||||
$attachment_store = new attachment_store();
|
||||
$attachments = new attachments();
|
||||
$attachment_formatted = $attachments->format($attachment);
|
||||
$download_link = $attachment_store->generateDirectDownloadUrl(
|
||||
$attachment_formatted->content->document
|
||||
);
|
||||
// Log the incident
|
||||
(new logs_o())->add('orders', $order->department_id->value(), 1, $user->id, 'DOWNLOAD_ORDER_ATTACHMENT', 'Successfully downloaded an attachment for an order (Order ID: ' . $order_id . ', Attachment ID: ' . $attachment_id . ')');
|
||||
// Return the download link
|
||||
$response->success(['download_link' => $download_link]);
|
||||
|
||||
} else {
|
||||
// Log the incident
|
||||
(new logs_o())->add('orders', 'global', 1, 0, 'DOWNLOAD_ORDER_ATTACHMENT', 'No user found, or invalid session');
|
||||
// Return an error
|
||||
$response->error('Invalid session', 400);
|
||||
}
|
||||
},
|
||||
[
|
||||
'download_order_attachments' => 'Download attachments for an order'
|
||||
]
|
||||
);
|
||||
|
||||
$this->get('/orders/attachments', function () {
|
||||
// Require the user to be logged in
|
||||
global $response;
|
||||
$this->requirePermission('list_order_attachments');
|
||||
// Get the user object
|
||||
$user = (new authentication())->get_user();
|
||||
// Check if the request was successful
|
||||
if ($user) {
|
||||
// Get the order ID from the request
|
||||
self::requireParameters([
|
||||
'id'
|
||||
]);
|
||||
$order_id = self::getParameter('id');
|
||||
if (!is_numeric($order_id) || (int)$order_id < 1) {
|
||||
$response->error('Invalid order ID', 400);
|
||||
}
|
||||
// Get the current order
|
||||
$order = (new orders_o())->getOrderById((int)$order_id);
|
||||
// Check if the order exists
|
||||
if (!$order->exists()) {
|
||||
$response->error('Order not found', 400);
|
||||
}
|
||||
// Get the attachments
|
||||
$attachments = $order->listAttachments();
|
||||
// Log the incident
|
||||
(new logs_o())->add('orders', $order->department_id->value(), 1, $user->id, 'LIST_ORDER_ATTACHMENTS', 'Successfully listed attachments for an order (ID: ' . $order_id . ')');
|
||||
// Return the attachments
|
||||
$response->success($attachments);
|
||||
} else {
|
||||
// Log the incident
|
||||
(new logs_o())->add('orders', 'global', 1, 0, 'LIST_ORDER_ATTACHMENTS', 'No user found, or invalid session');
|
||||
// Return an error
|
||||
$response->error('Invalid session', 400);
|
||||
}
|
||||
},
|
||||
[
|
||||
'list_order_attachments' => 'List attachments for an order'
|
||||
]
|
||||
);
|
||||
|
||||
$this->post('/orders/attachments/upload', function () {
|
||||
// Require the user to be logged in
|
||||
global $response;
|
||||
$this->requirePermission('add_order_attachments');
|
||||
// Get the user object
|
||||
$user = (new authentication())->get_user();
|
||||
// Check if the request was successful
|
||||
if ($user) {
|
||||
// Check if the order ID and file are set
|
||||
$this->requireParameters(['order_id', 'base64_file', 'file_name']);
|
||||
$order_id = (int)$this->getParameter('order_id');
|
||||
if (!is_numeric($order_id) || (int)$order_id < 1) {
|
||||
$response->error('Invalid order ID', 400);
|
||||
}
|
||||
// Get the current order
|
||||
$order = (new orders_o())->getOrderById((int)$order_id);
|
||||
// Check if the order exists
|
||||
if (!$order->exists()) {
|
||||
$response->error('Order not found', 400);
|
||||
}
|
||||
// Get the base64 file
|
||||
$base64_file = (string)$this->getParameter('base64_file');
|
||||
$attachment_store = new attachment_store();
|
||||
// Determine the file extension from the file name
|
||||
$file_name = (string)$this->getParameter('file_name');
|
||||
$extension = pathinfo($file_name, PATHINFO_EXTENSION);
|
||||
$object_name = $attachment_store->storeTempFileFromBase64(
|
||||
$base64_file,
|
||||
$extension
|
||||
);
|
||||
if ($object_name === false) {
|
||||
$response->error('Failed to store the attachment file.', 500);
|
||||
}
|
||||
$object_attachment = $order->addAttachment((new attachment_content())->setDocument($object_name)->setOther((string)self::getParameter('file_name')));
|
||||
// Log the incident
|
||||
(new logs_o())->add('orders', $order->department_id->value(), 1, $user->id, 'ADD_ORDER_ATTACHMENT', 'Successfully added an attachment for an order (Order ID: ' . $order_id . ')');
|
||||
// Return a success message
|
||||
$attachments = new attachments();
|
||||
$response->success($attachments->format($attachments->get($object_attachment->id)));
|
||||
} else {
|
||||
// Log the incident
|
||||
(new logs_o())->add('orders', 'global', 1, 0, 'ADD_ORDER_ATTACHMENT', 'No user found, or invalid session');
|
||||
// Return an error
|
||||
$response->error('Invalid session', 400);
|
||||
}
|
||||
},
|
||||
[
|
||||
'add_order_attachments' => 'Add attachments for an order'
|
||||
]
|
||||
);
|
||||
|
||||
$this->delete('/orders/attachments', function () {
|
||||
// Require the user to be logged in
|
||||
global $response;
|
||||
$this->requirePermission('delete_order_attachments');
|
||||
// Get the user object
|
||||
$user = (new authentication())->get_user();
|
||||
// Check if the request was successful
|
||||
if ($user) {
|
||||
// Get the order ID and attachment ID from the request
|
||||
self::requireParameters([
|
||||
'order_id',
|
||||
'attachment_id'
|
||||
]);
|
||||
$order_id = self::getParameter('order_id');
|
||||
$attachment_id = self::getParameter('attachment_id');
|
||||
if (!is_numeric($order_id) || (int)$order_id < 1) {
|
||||
$response->error('Invalid order ID', 400);
|
||||
}
|
||||
if (!is_numeric($attachment_id) || (int)$attachment_id < 1) {
|
||||
$response->error('Invalid attachment ID', 400);
|
||||
}
|
||||
// Get the current order
|
||||
$order = (new orders_o())->getOrderById((int)$order_id);
|
||||
// Check if the order exists
|
||||
if (!$order->exists()) {
|
||||
$response->error('Order not found', 400);
|
||||
}
|
||||
// Delete the attachment
|
||||
$order->removeAttachment((int)$attachment_id);
|
||||
// Log the incident
|
||||
(new logs_o())->add('orders', $order->department_id->value(), 1, $user->id, 'DELETE_ORDER_ATTACHMENT', 'Successfully deleted an attachment for an order (Order ID: ' . $order_id . ', Attachment ID: ' . $attachment_id . ')');
|
||||
// Return a success message
|
||||
$response->success(['message' => 'Attachment deleted successfully']);
|
||||
} else {
|
||||
// Log the incident
|
||||
(new logs_o())->add('orders', 'global', 1, 0, 'DELETE_ORDER_ATTACHMENT', 'No user found, or invalid session');
|
||||
// Return an error
|
||||
$response->error('Invalid session', 400);
|
||||
}
|
||||
},
|
||||
[
|
||||
'delete_order_attachments' => 'Delete attachments for an order'
|
||||
]
|
||||
);
|
||||
|
||||
$this->post('/orders/mark_as_completed', function () {
|
||||
// Require the user to be logged in
|
||||
global $response;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace traits;
|
||||
|
||||
use attachments\helpers\attachment;
|
||||
use attachments\helpers\attachment_content;
|
||||
use classes\attachments;
|
||||
use classes\db;
|
||||
@@ -1151,14 +1152,16 @@ trait db_object_t
|
||||
/**
|
||||
* Add an attachment to the (current) object
|
||||
* @param attachment_content $content The content of the attachment
|
||||
* @return object_attachments_o The attachment object
|
||||
* @throws Exception If the object is not selected, it throws an exception
|
||||
*/
|
||||
public function addAttachment(attachment_content $content): void
|
||||
public function addAttachment(attachment_content $content): object_attachments_o
|
||||
{
|
||||
self::requireSelected();
|
||||
// Add the attachment to the object
|
||||
$attachment = new attachments();
|
||||
$attachment->create($this->table, $this->id, $content);
|
||||
$id = $attachment->create($this->table, $this->id, $content);
|
||||
return (new object_attachments_o())->select($id);
|
||||
}
|
||||
/**
|
||||
* List attachments of the (current) object
|
||||
|
||||
@@ -189,6 +189,45 @@ trait minio_t
|
||||
return self::getS3Client()->doesObjectExist(self::getBucket(), $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store temp file from base64 string
|
||||
* @param string $base64 The base64 encoded string
|
||||
* @param string $fileExtension The file extension (e.g., 'txt', 'jpg')
|
||||
* @return string|bool The path to the temporary file if successful, false otherwise
|
||||
*/
|
||||
public function storeTempFileFromBase64(string $base64, string $fileExtension = 'txt'): string|bool
|
||||
{
|
||||
// Check if the base64 string is valid
|
||||
if (empty($base64) || !preg_match('/^data:.*;base64,/', $base64)) {
|
||||
return false; // Invalid base64 string
|
||||
}
|
||||
// Decode the base64 data
|
||||
$fileData = base64_decode(preg_replace('/^data:.*;base64,/', '', $base64));
|
||||
if ($fileData === false) {
|
||||
return false; // Failed to decode base64 data
|
||||
}
|
||||
// Temporary file path
|
||||
$tempFileName = uniqid('temp_file_', true) . '.' . $fileExtension;
|
||||
$tempFilePath = '/tmp/' . $tempFileName;
|
||||
// Create a temporary file
|
||||
$tempFile = fopen($tempFilePath, 'wb');
|
||||
if ($tempFile === false) {
|
||||
return false; // Failed to create temp file
|
||||
}
|
||||
|
||||
// Write the file data to the temporary file
|
||||
if (fwrite($tempFile, $fileData) === false) {
|
||||
fclose($tempFile);
|
||||
return false; // Failed to write to temp file
|
||||
}
|
||||
fclose($tempFile);
|
||||
// Upload the temporary file to the bucket
|
||||
$result = self::uploadFile($tempFileName, $tempFilePath);
|
||||
// Clean up the temporary file
|
||||
unlink($tempFilePath);
|
||||
return $result ? $tempFileName : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store temp image file from base64 string
|
||||
* @param string $base64 The base64 encoded image string (E.g. data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAA)
|
||||
|
||||
Reference in New Issue
Block a user