Files
api/services/nginx/app/traits/form_t.php
T
Jepp9350 8aee687106 Add new booking handling features and improve validations
Introduced support for a `data` property in bookings, extended form handling with additional validation and input fields, and added functionality for retrieving booking counts by date and status. Improved safety seal labeling and enhanced PDF generation comments.
2025-03-27 10:53:54 +01:00

847 lines
31 KiB
PHP

<?php
namespace traits;
use classes\response;
use Exception;
use forms\objects\book_wash_f;
use objects\departments_o;
use objects\form_submissions_o;
use objects\users_o;
use routes\formRoute;
trait form_t
{
/**
* The form identifier
* @var string $form_identifier The form identifier
*/
public string $form_identifier;
/**
* The form name
* @var string $form_name The form name
*/
public string $form_name;
/**
* The form description
* @var string $form_description The form description
*/
public string $form_description;
/**
* The form sanitized data
* @var array $form_sanitized_data The form sanitized data
*/
public array $form_sanitized_data;
/**
* The department id, if applicable
* @notation This should be used to store the department id, if applicable
* @see setDepartmentId()
* @see getDepartmentId()
* @var int|null $department_id The department id
*/
public int|null $department_id = null;
/**
* The user id, if applicable
* @notation This should be used to store the user id, if applicable
* @see setUserId()
* @see getUserId()
* @var int|null $user_id The user id
*/
public int|null $user_id = null;
/**
* The customer number, if applicable
* @notation This should be used to store the customer number, if applicable
* @see setCustomerNumber()
* @see getCustomerNumber()
* @var int|null $customer_number The customer number
*/
public int|null $customer_number = null;
/**
* The form fields
* @notation This should be used to store the form fields and their validation methods
* @var array $form_fields The form fields and their validation methods (E.g. ['name' => 'self::className()::validateString', 'email' => 'self::className()::validateEmail'])
* @see defineInputFields()
* @see requireInputFields()
* @see validateInput()
*/
public array $form_fields_validations = [];
/**
* The form fields options
* @notation This should be used to store the form fields options (e.g. ['field1' => ['option1' => 'Option 1', 'option2' => 'Option 2']])
* @var array $form_fields_options The form fields options
*/
public array $form_fields_options = [];
/**
* The form raw data / unsanitized input values
* @notation This should be used to store the form raw data (Unsanitized)
* @var array $form_unsanitized_data
*/
public array $form_unsanitized_data = [];
/**
* The form submissions object (When the form is submitted)
* @var form_submissions_o $form_submissions_o The form submissions object
* @see form_submissions_o
* @see submit()
*/
public form_submissions_o $form_submissions_o;
/**
* The form fields descriptions
* @var array $form_fields_metas The form fields descriptions
* @notation This should be used to store the form fields descriptions (e.g. ['field1' => 'Field 1', 'field2' => 'Field 2'])
* @see getFormFields()
* @see defineInputFields()
*/
public array $form_fields_metas = [];
/**
* The text displayed on the submit button
* @var string $submit_button_text The text displayed on the submit button
* @notation This should be used to store the text displayed on the submit button
* @see submit()
* @see setSubmitButtonText()
* @see getSubmitButtonText()
*/
public string $submit_button_text = 'Submit';
/**
* The form constructor (Runs the setup method)
* @notation This should be used to set the form identifier, name and description
* @throws Exception If the form identifier, name or description is empty
*/
public function __construct()
{
parent::__construct();
self::setup();
}
/**
* The form setup method, this is run when the form is created
* @throws Exception If the form identifier, name or description is empty
* @throws Exception If the form identifier is not a valid format (e.g. "FORM_IDENTIFIER")
*/
abstract public function setup();
/**
* The form run method, this is run when the form is submitted
* @throws Exception If the form input is not valid
* @throws Exception If the form was not submitted successfully
*/
public function submit(): void
{
// Validate the form identifier, name and description
self::validate();
// Require the input fields to be set
self::requireInputFields();
// Validate the form input
self::validateInput();
// Sanitize the form input
self::sanitize();
// Before save function
self::beforeSave();
// Save the form input to the database
self::save();
// Run the form after successful submission
self::afterSubmit();
}
/**
* Validate the form identifier, name and description
* @throws Exception If the form identifier, name or description is empty
*/
public function validate(): void
{
if (empty($this->form_identifier)) {
throw new Exception('The form identifier is empty');
}
if (empty($this->form_name)) {
throw new Exception('The form name is empty');
}
if (empty($this->form_description)) {
throw new Exception('The form description is empty');
}
}
/**
* Require input fields to be set
* @notation This should be used to check if the fields are set and if the input is valid
* @throws Exception If the input fields callable is not callable, or if the input fields are not set
* @see defineInputFields()
* @see validateInput()
*/
public function requireInputFields(): void
{
global /** @var response $response */
$response;
$errors = [];
$missing_fields = [];
$DATA = json_decode(file_get_contents('php://input'), true);
foreach ( $this->form_fields_validations as $field => $validation_method ) {
// Check if the post field is set
if (!isset($DATA[self::getFormFieldPrefix() . $field])) {
// Check if the field is required
if (self::isFieldRequired($field)) {
// If the field is required, add it to the missing fields
$missing_fields[] = self::getFormFieldPrefix() . $field;
}
} else {
// Check if the field is valid
if (is_callable($validation_method) || method_exists($this, $validation_method)) {
$callable = is_callable($validation_method) ? $validation_method : [$this, $validation_method];
try {
// Check if the field is multiple ( e.g. ['field1', 'field2'])
if (is_array($DATA[self::getFormFieldPrefix() . $field])) {
// If there's no value, check if the field is required
if (count($DATA[self::getFormFieldPrefix() . $field]) === 0) {
// If the field is required, add it to the missing fields
if (self::isFieldRequired($field)) {
$missing_fields[] = self::getFormFieldPrefix() . $field;
}
}
// Loop through the array and check if each value is valid
foreach ( $DATA[self::getFormFieldPrefix() . $field] as $value ) {
// Call the validation method
$is_valid = call_user_func($callable, $value);
if (!$is_valid) {
// If the value is not valid, add it to the errors
$errors[self::getFormFieldPrefix() . $field] = [
'error' => 'The field is invalid, please check the format',
'field' => self::getFormFieldPrefix() . $field,
];
}
}
} else {
// Call the validation method
$is_valid = call_user_func($callable, $DATA[self::getFormFieldPrefix() . $field]);
}
} catch (Exception $e) {
// If the validation method throws an exception, add it to the errors
$errors[self::getFormFieldPrefix() . $field] = [
'error' => 'The field is invalid, please check the format',
'field' => self::getFormFieldPrefix() . $field,
];
}
if (!$is_valid) {
// Check if the value is empty, and the field isn't required
// If the field is required, we don't need to add it to the errors
// If the field is not required, we need to add it to the errors
if (self::isFieldRequired($field)) {
// If the value is not valid, add it to the errors
$errors[self::getFormFieldPrefix() . $field] = [
'error' => 'The field is invalid, please check the format',
'field' => self::getFormFieldPrefix() . $field,
];
} else if (!self::isFieldRequired($field) && empty($DATA[self::getFormFieldPrefix() . $field])) {
// If the value is empty, and the field isn't required, we don't need to add it to the errors
} else {
// If the value is not valid, add it to the errors
$errors[self::getFormFieldPrefix() . $field] = [
'error' => 'The field is invalid, please check the format',
'field' => self::getFormFieldPrefix() . $field,
];
}
}
} else {
// Throw an error if the validation method is not callable
throw new Exception('The validation method is not callable for field: ' . self::getFormFieldPrefix() . $field . ' - ' . $validation_method);
}
}
// Add the field to the form unsanitized data
$this->form_unsanitized_data[self::getFormFieldPrefix() . $field] = $DATA[self::getFormFieldPrefix() . $field];
}
// Check if any fields are missing
if (!empty($missing_fields)) {
foreach ( $missing_fields as $field ) {
$errors[$field] = [
'error' => 'The field is required',
'field' => $field,
];
}
}
// Check if there are any errors
if (empty($errors)) {
// All fields are valid
return;
}
// Return an error
$response->error(
[
'message' => 'Input validation failed',
'errors' => $errors,
],
400,
);
}
/**
* Get the form field prefix
* @return string The form field prefix
*/
public function getFormFieldPrefix(): string
{
return $this->form_identifier . '_field_';
}
/**
* Check if the field is required
* @param string $field The field to check
* @return bool True if the field is required, false otherwise
*/
public function isFieldRequired(string $field): bool
{
// Check if the field is required
if (isset($this->form_fields_metas[$field]['required'])) {
return $this->form_fields_metas[$field]['required'];
}
return false;
}
/**
* The form input validation
* @notation This should be used to check if the fields are set and if the input is valid
* @throws Exception If the input is not valid
*/
abstract public function validateInput(): void;
/**
* The form input sanitization
* @throws Exception If the input is not valid
*/
abstract public function sanitize(): void;
/**
* The form before save method, this is run before the form is saved
* @throws Exception If the form was not submitted successfully
*/
abstract public function beforeSave(): void;
/**
* Save the form input to the database
* @throws Exception If the form was not submitted successfully
*/
public function save(): void
{
// Save the form input to the database
$form_submissions_o = new form_submissions_o;
$form_submissions_o->add(
$this->form_identifier,
$this->form_sanitized_data,
self::getUserId(),
self::getDepartmentId(),
self::getCustomerNumber(),
);
// Set the form submissions object
$this->form_submissions_o = $form_submissions_o;
}
/**
* Get the user id
* @return int|null The user id
*/
public function getUserId(): int|null
{
return $this->user_id;
}
/**
* Set the user id
* @param int|null $user_id The user id
* @return form_t|book_wash_f
*/
public function setUserId(int|null $user_id): self
{
$this->user_id = $user_id;
return $this;
}
/**
* Get the department id
* @return int|null The department id
*/
public function getDepartmentId(): int|null
{
return $this->department_id;
}
/**
* Set the department id
* @param int|null $department_id The department id
* @return form_t|book_wash_f
* @throws Exception If the department does not exist
*/
public function setDepartmentId(int|null $department_id): self
{
// If the department id is not set, set it to null
if (empty($department_id)) {
$department_id = null;
}
// Check if the department exists
$department = (new departments_o())->selectId($department_id);
if (!$department->exists()) {
throw new Exception('The department does not exist');
}
// Set the department id
$this->department_id = $department_id;
return $this;
}
/**
* Get the customer number
* @return int|null The customer number
*/
public function getCustomerNumber(): int|null
{
return $this->customer_number;
}
/**
* Set the customer number
* @param int|null $customer_number The customer number
* @return form_t|book_wash_f
* @throws Exception
*/
public function setCustomerNumber(int|null $customer_number): self
{
// If the customer number is not set, set it to null
if (empty($customer_number)) {
$customer_number = null;
}
// Check if the customer number is valid
if (!is_numeric($customer_number)) {
throw new Exception('The customer number is not valid');
}
// Check if the customer number exists
$customer = (new users_o())->getUserByCustomerNumber($customer_number);
if (!$customer->exists()) {
throw new Exception('The customer number does not exist');
}
// Set the customer number
$this->customer_number = $customer_number;
return $this;
}
/**
* The form after successful submission
*/
abstract public function afterSubmit(): void;
/**
* Get the sanitized data key
* @param string $key The key to get
* @return mixed The sanitized data
* @throws Exception If the key is not valid
*/
public function getSanitizedData(string $key): mixed
{
// Check if the key is valid
if (!isset($this->form_sanitized_data[self::getFormFieldPrefix() . $key])) {
throw new Exception('The key is not valid: ' . $key);
}
// Return the sanitized data
return $this->form_sanitized_data[self::getFormFieldPrefix() . $key];
}
/**
* Define the form input fields
* @notation E.g. ['name' => 'self::className()::validateString', 'email' => 'self::className()::validateEmail']
* @throws Exception If the input fields are not valid
* @var array $input_fields The form input fields
*/
public function defineInputFields(array $input_fields): void
{
// Set the form input fields
$fields = [];
foreach ( $input_fields as $field => $validation_method ) {
// Check if the field is valid
if (is_callable($validation_method)) {
$fields[$field] = $validation_method;
} else {
// Throw an error if the validation method is not callable
throw new Exception('The validation method is not callable for field: ' . $field . ' - ' . $validation_method);
}
}
// Set the form input fields
$this->form_fields_validations = $fields;
}
/**
* Define the form fields with validation methods, descriptions and options
* @param array $input_fields The form input fields (e.g. ['name' => ['validation_method' => 'validateString', 'description' => 'Name', 'options' => ['option1' => 'Option 1', 'option2' => 'Option 2']]])
* @throws Exception If the input fields are not valid
* @throws Exception If the input fields options are not valid
* @throws Exception If the input fields metadata are not valid
*/
public function defineInputFieldsAdvanced(array $input_fields): void
{
// Set the form input fields
$tmp_validators = [];
$tmp_metas = [];
$tmp_options = [];
foreach ( $input_fields as $field => $input_field ) {
// Check if the field is valid
if (isset($input_field['validation_method'])) {
$tmp_validators[$field] = self::className() . '::' . $input_field['validation_method'];
// We need to remove the validation method from the input field, to avoid adding it to the form fields metas
unset($input_field['validation_method']);
} else {
throw new Exception('The validation method is not valid for field: ' . $field);
}
// Check if the field has options
if (isset($input_field['options'])) {
$tmp_options[$field] = $input_field['options'];
// We need to remove the options from the input field, to avoid adding it to the form fields metas
unset($input_field['options']);
}
// Add the prefix to the display condition
if (isset($input_field['display_if'])) {
$input_field['display_if'] = self::getFormFieldPrefix() . $input_field['display_if'];
}
// Loop through the input fields data
foreach ( $input_field as $key => $value ) {
// Add the field key to the form fields metas
$tmp_metas[$field][$key] = $value;
}
// Check if the field has a display condition
}
// Set the form input fields
$this->form_fields_validations = $tmp_validators;
$this->form_fields_metas = $tmp_metas;
$this->form_fields_options = $tmp_options;
}
/**
* Get the class name
* @return string The class name
*/
public function className(): string
{
return get_class($this);
}
/**
* Add options to the form input fields
* @param string $field The field to add options to
* @param array $input_fields_options The input fields options (e.g. ['option1' => 'Option 1', 'option2' => 'Option 2'])
* @throws Exception If the field is not valid
* @throws Exception If the input fields options are not valid
* @notation E.g. ['field1' => ['option1' => 'Option 1', 'option2' => 'Option 2']]
* @throws Exception
*/
public function addInputFieldOptions(string $field, array $input_fields_options): void
{
// Check if the field is valid
if (!isset($this->form_fields_validations[$field])) {
throw new Exception('The field is not valid: ' . $field);
}
// Check if the input fields options are valid
if (empty($input_fields_options)) {
throw new Exception('The input fields options are not valid: ' . $field);
}
// Check if the input fields options are valid
foreach ( $input_fields_options as $option => $value ) {
// Check if the option is valid
if (!is_string($value)) {
throw new Exception('The input fields options are not valid: ' . $field);
}
}
// Check if the field is valid
if (!isset($this->form_fields_validations[$field])) {
throw new Exception('The field is not valid: ' . $field);
}
// Set the form input fields options
$this->form_fields_options[$field] = $input_fields_options;
}
/**
* Validation method for emails
* @param string $email The email to validate
* @return bool True if the email is valid, false otherwise
*/
public function validateEmail(string $email): bool
{
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}
/**
* Validation method for phone numbers
* @param string $phone The phone number to validate
* @return bool True if the phone number is valid, false otherwise
*/
public function validatePhone(string $phone): bool
{
// Check if the phone number is valid
// This regex allows for optional '+' at the start, followed by 7 to 15 digits
return preg_match('/^\+?[0-9]{7,15}$/', $phone) === 1;
}
/**
* Validation method for customer numbers
* @param int $customer_number The customer number to validate
* @return bool True if the customer number is valid, false otherwise
*/
public function validateCustomerNumber(int|string $customer_number): bool
{
// Check if the customer number is valid
// This regex allows for 1 to 10 digits
return preg_match('/^[0-9]{1,10}$/', $customer_number) === 1;
}
/**
* Validation method for integers
* @param int $int The integer to validate
* @return bool True if the integer is valid, false otherwise
*/
public function validateInt(int $int): bool
{
// Check if the integer is valid
// This regex allows for optional '+' or '-' at the start, followed by 1 to 10 digits
return preg_match('/^[+-]?[0-9]{1,10}$/', $int) === 1;
}
/**
* Validation method for booking ids
* @param int $int The integer to validate
* @return bool True if the integer is valid, false otherwise
*/
public function validateBookingId(int $int): bool
{
// Check if the booking id is valid (A positive integer)
return preg_match('/^[1-9][0-9]*$/', $int) === 1;
}
/**
* Validation method for department ids
* @param int $int The integer to validate
* @return bool True if the integer is valid, false otherwise
*/
public function validateDepartmentId(int $int): bool
{
// Check if the department id is valid (A positive integer)
return preg_match('/^[1-9][0-9]*$/', $int) === 1;
}
/**
* Validation method for strings
* @param string $string The string to validate
* @return bool True if the string is valid, false otherwise
*/
public function validateString(string $string): bool
{
// Check if the string is valid
// This regex allows for any character, including spaces, and has a length of 1 to 255 characters
return preg_match('/^.{1,255}$/', $string) === 1;
}
/**
* Validation method for booleans
* @param bool $boolean The boolean to validate
* @return bool True if the boolean is valid, false otherwise
*/
public function validateBoolean(bool $boolean): bool
{
// Check if the boolean is valid
return true;
}
/**
* Validation method for dates
* @param string $date The date to validate
* @return bool True if the date is valid, false otherwise
*/
public function validateDate(string $date): bool
{
// Check if the date is valid
// This regex allows for YYYY-MM-DD format
return preg_match('/^\d{4}-\d{2}-\d{2}$/', $date) === 1;
}
/**
* Validation method for registration numbers
* @param string $registration_number The registration number to validate
* @return bool True if the registration number is valid, false otherwise
*/
public function validateRegistrationNumber(string $registration_number): bool
{
// Check if the registration number is valid
// This regex allows for 1 to 20 alphanumeric characters
return preg_match('/^[a-zA-Z0-9]{1,20}$/', $registration_number) === 1;
}
/**
* Get the form identifier
* @return string The form identifier
*/
public function getFormIdentifier(): string
{
return $this->form_identifier;
}
/**
* Set the form identifier (e.g. "FORM_IDENTIFIER")
* @notation This should be used to set the form identifier (e.g. "FORM_IDENTIFIER"), this is used to store the form data in the database.
* @param string $form_identifier The form identifier
* @throws Exception If the form identifier is not a valid format (e.g. "FORM_IDENTIFIER")
*/
public function setFormIdentifier(string $form_identifier): void
{
// Check if the form identifier is valid (e.g. "FORM_IDENTIFIER")
if (!preg_match('/^[A-Z_]+$/', $form_identifier)) {
throw new Exception('The form identifier is not a valid format (e.g. "FORM_IDENTIFIER")');
}
$this->form_identifier = $form_identifier;
}
/**
* Get the form name
* @return string The form name
*/
public function getFormName(): string
{
return $this->form_name;
}
/**
* Set the form name
* @param string $form_name The form name
*/
public function setFormName(string $form_name): void
{
$this->form_name = $form_name;
}
/**
* Get the form description
* @return string The form description
*/
public function getFormDescription(): string
{
return $this->form_description;
}
/**
* Set the form description
* @param string $form_description The form description
*/
public function setFormDescription(string $form_description): void
{
$this->form_description = $form_description;
}
/**
* Get the form as an array
* @notation This returns the form as an array, including the form identifier, name, description and fields
* @return array The form as an array
*/
public function asArray(): array
{
return [
'metadata' => [
'identifier' => $this->form_identifier,
'name' => $this->form_name,
'description' => $this->form_description,
'submit_button_text' => self::getSubmitButtonText(),
],
'fields' => self::getFormFields(),
];
}
/**
* Get the submit button text
* @return string The submit button text (e.g. "Submit form")
*/
public function getSubmitButtonText(): string
{
return $this->submit_button_text ?? 'Submit form';
}
/**
* Set the submit button text
* @param string $text The submit button text (e.g. "Submit form")
*/
public function setSubmitButtonText(string $text): void
{
$this->submit_button_text = $text;
}
/**
* Get the form fields
* @notation This should be used to get the form fields
* @return array The form fields and their validation methods (E.g. ['name' => 'self::className()::validateString', 'email' => 'self::className()::validateEmail'])
* @see formRoute - This is used to get the form fields, to dynamically generate the form.
* @see defineInputFields() - This is used to define the form fields and their validation methods.
* @see requireInputFields() - This is used to require the input fields to be set.
*/
public function getFormFields(): array
{
function removeMethodPrefix($method): array|string|null
{
// Remove the class name and the '::' prefix from the method name
return preg_replace('/^.*?::/', '', $method);
}
// Format the form fields with types, names and values
$formatted_fields = [];
foreach ( $this->form_fields_validations as $field => $validation_method ) {
// Check if the field is valid
$formatted_fields[$field] = [
'name' => $field,
'id' => self::getFormFieldPrefix() . $field,
'validation' => removeMethodPrefix($validation_method),
'metadata' => $this->form_fields_metas[$field] ?? null,
];
// Check if the field has options
if (count(self::getFormFieldOptions($field)) > 0) {
$formatted_fields[$field]['options'] = self::getFormFieldOptions($field);
}
}
return $formatted_fields;
}
/**
* Get the form field options (if applicable)
* @notation This should be used to get the form field options (e.g. ['option1' => 'Option 1', 'option2' => 'Option 2'])
* @param string $field The field to get the options for
* @return array The form field options
*/
public function getFormFieldOptions(string $field): array
{
// Check if the field is valid
if (!isset($this->form_fields_options[$field])) {
return [];
}
// Return the form field options
return $this->form_fields_options[$field];
}
/**
* Set the sanitized data to the form
* @param array $data The sanitized data
* @throws Exception If the data is not valid
*/
public function setSanitizedData(array $data): void
{
// Check if the data is valid
if (empty($data)) {
throw new Exception('The data is empty');
}
// Set the form sanitized data
$this->form_sanitized_data = $data;
}
/**
* Get department options
* @return array The department options
* @notation This should be used to get the department options (e.g. [1 => 'Department 1', 2 => 'Department 2'])
* @see formRoute - This is used to get the department options, to dynamically generate the form.
*/
public function getDepartmentOptions(): array
{
$departments = (new departments_o())->list(false) ?? [];
$options = [];
/** @var array $department */
foreach ( $departments as $department ) {
$options[$department['id']] = $department['name'];
}
return $options;
}
}