Add email configuration module and enable invoice PDF retrieval

Introduce an email configuration module to manage SMTP settings, including encryption, host, username, and more. Implement functionality for retrieving invoice PDFs, storing them, and providing secure download links. Added relevant endpoint, routes, and helper methods to support these features.
This commit is contained in:
Jepp9350
2025-02-10 18:07:51 +01:00
parent 172e37d3a2
commit 61de9cdb45
21 changed files with 621 additions and 1 deletions
+21
View File
@@ -0,0 +1,21 @@
<?php
namespace classes;
require_once WD . '/modules/email/email_c.php';
use email\email_c;
use interfaces\email_i;
class email implements email_i
{
/**
* Configuration for the email service
* @var email_c
*/
public email_c $config;
public function __construct()
{
$this->config = new email_c();
}
}
@@ -0,0 +1,54 @@
<?php
namespace classes;
use interfaces\minio_invoices_i;
use traits\minio_t;
class invoice_store implements minio_invoices_i
{
use minio_t;
public function __construct()
{
self::setBucket('invoices'); // Change the bucket to invoices
}
/**
* @inheritDoc
*/
public function invoice_exists(int $invoice_id): bool
{
// Check if the file exists
$objects = self::getS3Client()->listObjects([
'Bucket' => self::getBucket(),
'Prefix' => 'invoice_' . $invoice_id . '.pdf'
]);
return count($objects['Contents'] ?? []) > 0;
}
public function getInvoiceDownloadUrl(int $id): string
{
return self::getPresignedUrl('invoice_' . $id . '.pdf');
}
/**
* @param string $file
* @return string The path to the downloaded file
*/
public function download(string $file): string
{
$path = '/tmp/' . $file;
$result = self::getS3Client()->getObject([
'Bucket' => self::getBucket(),
'Key' => $file,
'SaveAs' => $path
]);
return $path;
}
public function isFileInStore(string $file): bool
{
return self::getS3Client()->doesObjectExist(self::getBucket(), $file);
}
}
+24
View File
@@ -163,4 +163,28 @@ class response implements response_i
{
$this->add_data($list_name, [$key => $value]);
}
#[NoReturn] public function displayPDF($raw_pdf): void
{
self::disableAll();
$virtual_file = fopen('php://temp', 'r+');
fwrite($virtual_file, $raw_pdf);
fseek($virtual_file, 0);
file_put_contents('/tmp/debug_output.pdf', stream_get_contents($virtual_file));
// Return the PDF /tmp/debug_output.pdf
header('Content-Type: application/pdf');
header('Content-Disposition: inline; filename="debug_output.pdf"');
header('Content-Length: ' . filesize('/tmp/debug_output.pdf'));
header('Content-Transfer-Encoding: binary');
header('Accept-Ranges: bytes');
readfile('/tmp/debug_output.pdf');
exit;
}
private static function disableAll(): void
{
error_reporting(0);
ini_set('display_errors', '0');
}
}
+5
View File
@@ -1,5 +1,8 @@
<?php global /** @var response $response */
$DEBUG, $CONFIG_DB, $ENCRYPTION_KEY, $CORS, $WD, $db, $response, $request, $router, $redis;
ini_set('output_buffering', 'off');
ini_set('zlib.output_compression', false);
/**
* This is the main entry point to the Truck Wash API.
*/
@@ -45,6 +48,7 @@ require_once 'classes/response.php';
require_once 'classes/request.php';
require_once 'classes/ratelimit.php';
require_once 'classes/wash_certificate_store.php';
require_once 'classes/invoice_store.php';
require_once 'classes/redis.php';
require_once 'classes/slack.php';
require_once 'classes/wordpress_bookings_remote.php';
@@ -52,6 +56,7 @@ require_once 'classes/language_packs.php';
require_once 'classes/economic.php';
require_once 'classes/statistics.php';
require_once 'classes/recaptcha.php';
require_once 'classes/email.php';
/**
* Modules
@@ -0,0 +1,8 @@
<?php
namespace interfaces;
interface email_i
{
}
@@ -0,0 +1,13 @@
<?php
namespace interfaces;
interface minio_invoices_i
{
/**
* Check if an invoice exists in the Minio bucket
* @param int $invoice_id
* @return bool
*/
public function invoice_exists(int $invoice_id): bool;
}
@@ -6,10 +6,12 @@ require_once WD . '/modules/economic/endpoints/invoices/economic_invoices_sent_e
require_once WD . '/modules/economic/endpoints/invoices/economic_invoices_booked_endpoint.php';
require_once WD . '/modules/economic/endpoints/invoices/economic_invoices_drafts_endpoint.php';
require_once WD . '/modules/economic/endpoints/invoices/draft/economic_invoices_draft_endpoint.php';
require_once WD . '/modules/economic/endpoints/invoices/economic_invoices_pdf_endpoint.php';
use endpoints\orders\economic_invoices_booked_endpoint;
use endpoints\orders\economic_invoices_draft_endpoint;
use endpoints\orders\economic_invoices_drafts_endpoint;
use endpoints\orders\economic_invoices_pdf_endpoint;
use endpoints\orders\economic_invoices_sent_endpoint;
use endpoints\orders\economic_invoices_totals_endpoint;
use traits\economic_endpoint_t;
@@ -44,6 +46,11 @@ class economic_invoices_endpoint
* @var economic_invoices_draft_endpoint
*/
public economic_invoices_draft_endpoint $draft;
/**
* Any endpoints reached by the /invoices/booked/{id}/pdf endpoint
* @var economic_invoices_pdf_endpoint
*/
public economic_invoices_pdf_endpoint $pdf;
public function __construct()
{
@@ -52,5 +59,6 @@ class economic_invoices_endpoint
$this->booked = new economic_invoices_booked_endpoint();
$this->drafts = new economic_invoices_drafts_endpoint();
$this->draft = new economic_invoices_draft_endpoint();
$this->pdf = new economic_invoices_pdf_endpoint();
}
}
@@ -0,0 +1,29 @@
<?php
namespace endpoints\orders;
use traits\economic_endpoint_t;
class economic_invoices_pdf_endpoint
{
use economic_endpoint_t;
/**
* Get a PDF of a booked invoice
* @param int $id
* @return string
*/
public function get(int $id): string
{
$unique_id = uniqid();
$this->send_file_download_request(
'/invoices/booked/' . $id . '/pdf',
'GET',
'',
false,
'/tmp/invoice_' . $unique_id . '.pdf'
);
return '/tmp/invoice_' . $unique_id . '.pdf';
}
}
@@ -0,0 +1,29 @@
<?php
namespace email\config;
use Exception;
use traits\module_config_variable;
class email_enabled_c
{
use module_config_variable;
/**
* @throws Exception
*/
public function __construct()
{
self::setupConfigVariable(
'Email',
'enabled',
'bool',
true,
null,
'Whether the email service is enabled or not',
'1',
false,
'false'
);
}
}
@@ -0,0 +1,29 @@
<?php
namespace email\config;
use Exception;
use traits\module_config_variable;
class email_smtp_encryption_c
{
use module_config_variable;
/**
* @throws Exception
*/
public function __construct()
{
self::setupConfigVariable(
'Email',
'smtp_encryption',
'string',
true,
null,
'The SMTP encryption for the email service',
'tls',
false,
'tls'
);
}
}
@@ -0,0 +1,29 @@
<?php
namespace email\config;
use Exception;
use traits\module_config_variable;
class email_smtp_from_c
{
use module_config_variable;
/**
* @throws Exception
*/
public function __construct()
{
self::setupConfigVariable(
'Email',
'smtp_from',
'string',
true,
null,
'The SMTP from email address for the email service',
'user@example.com',
false,
''
);
}
}
@@ -0,0 +1,29 @@
<?php
namespace email\config;
use Exception;
use traits\module_config_variable;
class email_smtp_from_name_c
{
use module_config_variable;
/**
* @throws Exception
*/
public function __construct()
{
self::setupConfigVariable(
'Email',
'smtp_from_name',
'string',
true,
null,
'The SMTP from name for the email service',
'User',
false,
'Mail Service'
);
}
}
@@ -0,0 +1,29 @@
<?php
namespace email\config;
use Exception;
use traits\module_config_variable;
class email_smtp_host_c
{
use module_config_variable;
/**
* @throws Exception
*/
public function __construct()
{
self::setupConfigVariable(
'Email',
'smtp_host',
'string',
true,
null,
'The SMTP host for the email service',
'localhost',
false,
'localhost'
);
}
}
@@ -0,0 +1,29 @@
<?php
namespace email\config;
use Exception;
use traits\module_config_variable;
class email_smtp_password_c
{
use module_config_variable;
/**
* @throws Exception
*/
public function __construct()
{
self::setupConfigVariable(
'Email',
'smtp_password',
'string',
true,
null,
'The SMTP password for the email service',
'password123',
true,
''
);
}
}
@@ -0,0 +1,29 @@
<?php
namespace email\config;
use Exception;
use traits\module_config_variable;
class email_smtp_port_c
{
use module_config_variable;
/**
* @throws Exception
*/
public function __construct()
{
self::setupConfigVariable(
'Email',
'smtp_port',
'integer',
true,
null,
'The SMTP port for the email service',
'25',
false,
'25'
);
}
}
@@ -0,0 +1,29 @@
<?php
namespace email\config;
use Exception;
use traits\module_config_variable;
class email_smtp_username_c
{
use module_config_variable;
/**
* @throws Exception
*/
public function __construct()
{
self::setupConfigVariable(
'Email',
'smtp_username',
'string',
true,
null,
'The SMTP username for the email service',
'user@example.com',
false,
''
);
}
}
@@ -0,0 +1,98 @@
<?php
namespace email;
require_once WD . '/modules/email/config/email_enabled_c.php';
require_once WD . '/modules/email/config/email_smtp_encryption_c.php';
require_once WD . '/modules/email/config/email_smtp_host_c.php';
require_once WD . '/modules/email/config/email_smtp_password_c.php';
require_once WD . '/modules/email/config/email_smtp_port_c.php';
require_once WD . '/modules/email/config/email_smtp_username_c.php';
require_once WD . '/modules/email/config/email_smtp_from_c.php';
require_once WD . '/modules/email/config/email_smtp_from_name_c.php';
use email\config\email_enabled_c;
use email\config\email_smtp_encryption_c;
use email\config\email_smtp_from_c;
use email\config\email_smtp_from_name_c;
use email\config\email_smtp_host_c;
use email\config\email_smtp_password_c;
use email\config\email_smtp_port_c;
use email\config\email_smtp_username_c;
use traits\module_config_t;
class email_c
{
use module_config_t;
/**
* The status of reCAPTCHA, whether it is enabled or not
* @var email_enabled_c
*/
public email_enabled_c $enabled;
/**
* The SMTP host for the email service
* @var email_smtp_host_c
*/
public email_smtp_host_c $smtp_host;
/**
* The SMTP port for the email service
* @var email_smtp_port_c
*/
public email_smtp_port_c $smtp_port;
/**
* The SMTP username for the email service
* @var email_smtp_username_c
*/
public email_smtp_username_c $smtp_username;
/**
* The SMTP password for the email service
* @var email_smtp_password_c
*/
public email_smtp_password_c $smtp_password;
/**
* The SMTP encryption for the email service
* @var email_smtp_encryption_c
*/
public email_smtp_encryption_c $smtp_encryption;
/**
* The SMTP from email address for the email service
* @var email_smtp_from_c
*/
public email_smtp_from_c $smtp_from;
/**
* The SMTP from name for the email service
* @var email_smtp_from_name_c
*/
public email_smtp_from_name_c $smtp_from_name;
public function __construct()
{
$this->setupConfig('Email');
$this->allowUpdate([
email_enabled_c::class,
email_smtp_host_c::class,
email_smtp_port_c::class,
email_smtp_username_c::class,
email_smtp_password_c::class,
email_smtp_encryption_c::class,
email_smtp_from_c::class,
email_smtp_from_name_c::class
]);
$this->enabled = new email_enabled_c();
$this->smtp_host = new email_smtp_host_c();
$this->smtp_port = new email_smtp_port_c();
$this->smtp_username = new email_smtp_username_c();
$this->smtp_password = new email_smtp_password_c();
$this->smtp_encryption = new email_smtp_encryption_c();
$this->smtp_from = new email_smtp_from_c();
$this->smtp_from_name = new email_smtp_from_name_c();
}
}
+12
View File
@@ -303,5 +303,17 @@ class orders_o extends db
return $db->fetch_all($result);
}
public function getOrderByInvoiceId(int $invoiceId): orders_o
{
global $db;
$sql = "SELECT id FROM economic_module_orders WHERE invoice_id = $invoiceId";
$result = $db->query($sql);
if ($result->num_rows > 0) {
$row = $result->fetch_assoc();
return $this->getOrderById($row['id']);
}
throw new \Exception('Order not found');
}
}
@@ -4,8 +4,10 @@ namespace routes;
use classes\authentication;
use classes\economic;
use classes\invoice_store;
use objects\economic_module_orders;
use objects\logs_o;
use objects\orders_o;
use objects\users_o;
use traits\route_t;
@@ -81,5 +83,46 @@ class invoicesRoute
$response->error('Invalid session', 400);
}
});
$this->get('/invoices/pdf', function () {
// Require the user to be logged in
global $response;
$this->requirePermission('get_invoice_pdf');
// Get the user object
$user = (new authentication())->get_user();
// Check if the request was successful
if ($user) {
// Check if the invoice id is set
if (!$this->fromRequest('id')) {
$response->error('id parameter is required', 400);
}
$order_id = (new orders_o())->getOrderByInvoiceId($this->fromRequest('id'))->id;
// Check if the user has permission to view the invoice
if (!$user->hasAccessToOrder($order_id)) {
$response->error('User does not have access to this invoice', 403);
}
// Create economic object
$economic = new economic();
// Get the draft invoice
$invoicePathFile = $economic->invoices->pdf->get($this->fromRequest('id'));
// Add the pdf to the invoice store
$invoice_store = new invoice_store();
$invoice_store->uploadFile('invoice_' . $this->fromRequest('id') . '.pdf', $invoicePathFile);
// Log the incident
(new logs_o())->add('invoices', 'global', 1, $user->id, 'GET_INVOICE_PDF', 'Successfully retrieved invoice pdf');
// Remove the file
unlink($invoicePathFile);
// Return the download link
$response->success(
['message' => 'Invoice PDF retrieved', 'url' => $invoice_store->getInvoiceDownloadUrl($this->fromRequest('id'))]
);
} else {
// Log the incident
(new logs_o())->add('orders', 'global', 1, 0, 'LIST_ORDERS', 'No user found, or invalid session');
// Return an error
$response->error('Invalid session', 400);
}
});
}
}
@@ -4,6 +4,7 @@ namespace routes;
use classes\authentication;
use classes\economic;
use classes\email;
use classes\recaptcha;
use classes\response;
use classes\router;
@@ -84,5 +85,38 @@ class moduleConfigRoute
$response->error('Invalid session', 400);
}
});
/** Email config > GET */
$this->get('/email/config', function () {
global $response;
$this->requirePermission('email_config');
$user = (new authentication())->get_user();
if ($user) {
(new logs_o())->add('email_config', 'global', 1, $user->id, 'EMAIL_CONFIG', 'Successfully fetched email config');
$response->success(
(new email())->config->getConfigRequest()
);
} else {
(new logs_o())->add('email_config', 'global', 1, 0, 'EMAIL_CONFIG', 'No user found, or invalid session');
$response->error('Invalid session', 400);
}
});
/** Email config > POST */
$this->post('/email/config', function () {
global $response;
$this->requirePermission('email_config');
$user = (new authentication())->get_user();
if ($user) {
(new logs_o())->add('email_config', 'global', 1, $user->id, 'EMAIL_CONFIG', 'Successfully updated email config');
$response->success(
(new email())->config->postConfigRequest()
);
} else {
(new logs_o())->add('email_config', 'global', 1, 0, 'EMAIL_CONFIG', 'No user found, or invalid session');
$response->error('Invalid session', 400);
}
});
}
}
@@ -70,7 +70,7 @@ trait economic_endpoint_t
curl_setopt_array($curl, array(
CURLOPT_URL => $this->api_url . $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
//CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
@@ -96,4 +96,43 @@ trait economic_endpoint_t
curl_close($curl);
return $response;
}
public function send_file_download_request($url, $method, $data = '', bool $authToken2 = false, $outputFile = null)
{
// Initialize cURL session
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => $this->api_url . $url,
CURLOPT_RETURNTRANSFER => true, // Get the response as a string
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_HTTPHEADER => array(
'X-AppSecretToken: ' . $this->app_token,
'X-AgreementGrantToken: ' . ($authToken2 ? $this->appAccessGrant2 : $this->appAccessGrant),
'Content-Type: application/json'
),
));
// Execute the request
$response = curl_exec($curl);
// Handle errors
if (curl_errno($curl)) {
echo 'Curl error: ' . curl_error($curl);
return curl_error($curl);
}
// Close the cURL session
curl_close($curl);
// If a file path is provided, save the response to the file
if ($outputFile) {
// Write file contents
file_put_contents($outputFile, $response);
return "File saved to: " . $outputFile;
}
// Otherwise, return the raw content (e.g., for inline use)
return $response;
}
}