Add public endpoint for guest time bookings entries with validation and addon support
This commit is contained in:
@@ -71,6 +71,7 @@ class department_time_bookings_entries_o extends db
|
||||
int $phone,
|
||||
?string $note = null,
|
||||
?string $reg = null,
|
||||
?array $addons = null,
|
||||
): void
|
||||
{
|
||||
self::preChecks(...func_get_args());
|
||||
@@ -83,6 +84,7 @@ class department_time_bookings_entries_o extends db
|
||||
'reg' => $reg,
|
||||
'phone' => $phone,
|
||||
'phone_country_code' => $phone_country_code,
|
||||
'addons' => $addons,
|
||||
]);
|
||||
$this->id = $tmp_id;
|
||||
self::getObjectProperties();
|
||||
@@ -113,6 +115,7 @@ class department_time_bookings_entries_o extends db
|
||||
int $phone,
|
||||
?string $note = null,
|
||||
?string $reg = null,
|
||||
?array $addons = null,
|
||||
): void
|
||||
{
|
||||
// Check if the type_id exists
|
||||
@@ -139,6 +142,14 @@ class department_time_bookings_entries_o extends db
|
||||
if (count($bookings_overlapping) > 0) {
|
||||
throw new Exception('The time slot is already booked');
|
||||
}
|
||||
// Validate phone number
|
||||
if (!is_numeric($phone) || $phone <= 0) {
|
||||
throw new Exception('Invalid phone number');
|
||||
}
|
||||
// Validate phone country code
|
||||
if (!is_numeric($phone_country_code) || $phone_country_code <= 0) {
|
||||
throw new Exception('Invalid phone country code');
|
||||
}
|
||||
}
|
||||
|
||||
private function getBookingsByDepartmentAndTime(int $department_id, string $start, string $end): array
|
||||
|
||||
@@ -187,4 +187,14 @@ class product_options_o extends db
|
||||
{
|
||||
return self::getFieldsWhere(['option_id' => $product_id], ['product_id']);
|
||||
}
|
||||
|
||||
public function isOptionAllowedOnType(int $option_id, int $primary_type_id): bool
|
||||
{
|
||||
// Check if the option is allowed on the primary type
|
||||
$allowed_options = self::getFieldsWhere(
|
||||
['option_id' => $option_id, 'product_id' => $primary_type_id],
|
||||
['id']
|
||||
);
|
||||
return !empty($allowed_options);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,8 @@ use classes\router;
|
||||
use objects\department_time_bookings_entries_o;
|
||||
use objects\department_time_bookings_opening_hours_o;
|
||||
use objects\department_time_bookings_types_o;
|
||||
use objects\departments_o;
|
||||
use objects\product_options_o;
|
||||
use traits\route_t;
|
||||
|
||||
class customerTimeBookingsRoute
|
||||
@@ -172,5 +174,99 @@ class customerTimeBookingsRoute
|
||||
// No permissions required for this endpoint, as it is for guests
|
||||
]
|
||||
);
|
||||
|
||||
/** Guest Time Bookings -> Entries -> Add */
|
||||
$this->post('/department/timebookings/entries/public', function () {
|
||||
global $response;
|
||||
self::requireParameters(['department', 'type', 'start']);
|
||||
self::requireType((int)self::getParameter('department'), self::type_int());
|
||||
self::requireMinValue((int)self::getParameter('department'), 1);
|
||||
self::requireSameLength(self::getParameter('department'), (int)self::getParameter('department'));
|
||||
|
||||
// Get the type
|
||||
self::requireType((int)self::getParameter('type'), self::type_int());
|
||||
self::requireMinValue((int)self::getParameter('type'), 1);
|
||||
self::requireSameLength(self::getParameter('type'), (int)self::getParameter('type'));
|
||||
|
||||
// Get the start time
|
||||
self::requireType((string)self::getParameter('start'), self::type_string());
|
||||
self::requireMinLength('start', 1);
|
||||
self::requireMaxLength('start', 255);
|
||||
self::requireSameLength(self::getParameter('start'), (string)self::getParameter('start'));
|
||||
self::requireDateFormat((string)self::getParameter('start'), 'Y-m-d H:i:s');
|
||||
|
||||
// Check if the department exists
|
||||
$department = new departments_o();
|
||||
$department->select((int)self::getParameter('department'));
|
||||
if (!$department->exists()) {
|
||||
$response->error('Department not found', 404);
|
||||
}
|
||||
// Check if the department has time bookings enabled
|
||||
$variable = $department->isModuleTimeBookingsEnabled();
|
||||
if (!$variable) {
|
||||
$response->error('Department time bookings are not enabled', 404);
|
||||
}
|
||||
// Check if the type exists
|
||||
$department_time_bookings_types = new department_time_bookings_types_o();
|
||||
$department_time_bookings_types->select((int)self::getParameter('type'));
|
||||
if (!$department_time_bookings_types->exists()) {
|
||||
$response->error('Department time bookings type not found', 404);
|
||||
}
|
||||
// Check if the type belongs to the department
|
||||
if ((int)$department_time_bookings_types->department->value() !== (int)$department->id) {
|
||||
$response->error('Department time bookings type does not belong to the department', 404);
|
||||
}
|
||||
// Calculate the end time
|
||||
$start_time = \DateTime::createFromFormat('Y-m-d H:i:s', (string)self::getParameter('start'));
|
||||
if (!$start_time) {
|
||||
$response->error('Invalid start time format', 400);
|
||||
}
|
||||
$duration = (int)$department_time_bookings_types->duration->value();
|
||||
if ($duration <= 0) {
|
||||
$response->error('Invalid duration for department time bookings type', 400);
|
||||
}
|
||||
$end_time = clone $start_time;
|
||||
$end_time->modify("+{$duration} minutes");
|
||||
|
||||
// Check if the addons parameter is set
|
||||
$addons = [];
|
||||
if (self::isParametersSet(['addons'])) {
|
||||
self::requireType((array)self::getParameter('addons'), self::type_array());
|
||||
$addons = (array)self::getParameter('addons');
|
||||
// Validate each addon
|
||||
foreach ( $addons as $addon ) {
|
||||
self::requireType((int)$addon, self::type_int());
|
||||
self::requireMinValue((int)$addon, 1);
|
||||
self::requireSameLength($addon, (int)$addon);
|
||||
// Check if the addon is allowed on the primary type
|
||||
$product_options = new product_options_o();
|
||||
if (!$product_options->isOptionAllowedOnType(
|
||||
(int)$addon,
|
||||
(int)$department_time_bookings_types->product->value()
|
||||
)) {
|
||||
$response->error('This product option is not allowed on the primary product type', 400);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Add the department time bookings entry
|
||||
$department_time_bookings_entries = new department_time_bookings_entries_o();
|
||||
$department_time_bookings_entries->add(
|
||||
(int)self::getParameter('department'),
|
||||
(int)self::getParameter('type'),
|
||||
(string)$start_time->format('Y-m-d H:i:s'),
|
||||
(string)$end_time->format('Y-m-d H:i:s'),
|
||||
(int)(self::isParametersSet(['phone_country_code']) ? self::getParameter('phone_country_code') : 0),
|
||||
(int)(self::isParametersSet(['phone']) ? self::getParameter('phone') : 0),
|
||||
(self::isParametersSet(['note']) ? (string)self::getParameter('note') : null),
|
||||
(self::isParametersSet(['reg']) ? (string)self::getParameter('reg') : null),
|
||||
(array)$addons,
|
||||
);
|
||||
// Return success
|
||||
$response->success($department_time_bookings_entries->asArray());
|
||||
},
|
||||
[
|
||||
// No permissions required for this endpoint, as it is for guests
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ use objects\department_time_bookings_entries_o;
|
||||
use objects\department_time_bookings_opening_hours_o;
|
||||
use objects\department_time_bookings_types_o;
|
||||
use objects\logs_o;
|
||||
use objects\product_options_o;
|
||||
use traits\route_t;
|
||||
|
||||
class departmentTimeBookingsRoute
|
||||
@@ -384,6 +385,28 @@ class departmentTimeBookingsRoute
|
||||
$end_time = clone $start_time;
|
||||
$end_time->modify("+{$duration} minutes");
|
||||
|
||||
// Check if the addons parameter is set
|
||||
$addons = [];
|
||||
if (self::isParametersSet(['addons'])) {
|
||||
self::requireType((array)self::getParameter('addons'), self::type_array());
|
||||
$addons = (array)self::getParameter('addons');
|
||||
// Validate each addon
|
||||
foreach ( $addons as $addon ) {
|
||||
self::requireType((int)$addon, self::type_int());
|
||||
self::requireMinValue((int)$addon, 1);
|
||||
self::requireSameLength($addon, (int)$addon);
|
||||
// Check if the addon is allowed on the primary type
|
||||
$product_options = new product_options_o();
|
||||
if (!$product_options->isOptionAllowedOnType(
|
||||
(int)$addon,
|
||||
(int)$department_time_bookings_types->product->value()
|
||||
)) {
|
||||
(new logs_o())->add('department_time_bookings_entries', 'global', 0, $user->id, 'DEPARTMENT_TIME_BOOKINGS_ENTRIES_POST', 'This product option is not allowed on the primary product type');
|
||||
$response->error('This product option is not allowed on the primary product type', 400);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Add the department time bookings entry
|
||||
$department_time_bookings_entries = new department_time_bookings_entries_o();
|
||||
@@ -395,7 +418,8 @@ class departmentTimeBookingsRoute
|
||||
(int)(self::isParametersSet(['phone_country_code']) ? self::getParameter('phone_country_code') : 0),
|
||||
(int)(self::isParametersSet(['phone']) ? self::getParameter('phone') : 0),
|
||||
(self::isParametersSet(['note']) ? (string)self::getParameter('note') : null),
|
||||
(self::isParametersSet(['reg']) ? (string)self::getParameter('reg') : null)
|
||||
(self::isParametersSet(['reg']) ? (string)self::getParameter('reg') : null),
|
||||
(array)$addons,
|
||||
);
|
||||
(new logs_o())->add('department_time_bookings_entries', 'global', 0, $user->id, 'DEPARTMENT_TIME_BOOKINGS_ENTRIES_POST', 'Add department time bookings entries');
|
||||
$response->success($department_time_bookings_entries->asArray());
|
||||
|
||||
@@ -923,6 +923,33 @@ trait db_object_t
|
||||
global /** @var db $db */
|
||||
$db;
|
||||
try {
|
||||
// Sanitize the data
|
||||
foreach ( $data as $key => $value ) {
|
||||
// If the value is an object or an array, convert it to a JSON string
|
||||
if (is_object($value) || is_array($value)) {
|
||||
$data[$key] = json_encode($value);
|
||||
if ($data[$key] === false) {
|
||||
throw new Exception('Failed to encode value for key: ' . $key . ' - ' . json_last_error_msg());
|
||||
}
|
||||
}
|
||||
// If the value is null, set it to null
|
||||
if ($value === null || (is_string($value) && strtolower($value) === 'null')) {
|
||||
$data[$key] = 'NULL';
|
||||
continue;
|
||||
}
|
||||
// Escape the value to prevent SQL injection (this is important for strings)
|
||||
if (is_string($value)) {
|
||||
// If the value is a string, escape it
|
||||
$data[$key] = $db->escape_string($value);
|
||||
} elseif (is_numeric($value)) {
|
||||
// If the value is numeric, cast it to a string
|
||||
$data[$key] = (string)$value;
|
||||
} elseif (is_bool($value)) {
|
||||
// If the value is a boolean, convert it to an integer
|
||||
$data[$key] = (int)$value;
|
||||
}
|
||||
}
|
||||
// Prepare the SQL query to insert the data
|
||||
$columns = implode(', ', array_keys($data));
|
||||
$values = implode("', '", array_values($data));
|
||||
$sql = "INSERT INTO $this->table ($columns) VALUES ('$values')";
|
||||
|
||||
Reference in New Issue
Block a user