Files
api/services/nginx/app/traits/form_t.php
T
Jepp9350 7976b89fff Add form field options handling and invoice discount logic
Added support for defining and retrieving form field options, along with methods to manage field-specific configurations. Enhanced invoice draft generation to handle discounts, including the ability to calculate total discounts and display them as separate lines. These updates improve flexibility in forms and invoice processing.
2025-03-24 10:27:11 +01:00

589 lines
20 KiB
PHP

<?php
namespace traits;
use classes\response;
use Exception;
use forms\book_wash_f;
use objects\departments_o;
use objects\form_submissions_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 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 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()
{
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();
// 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])) {
// If the field is not a boolean, 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];
$is_valid = call_user_func($callable, $DATA[self::getFormFieldPrefix() . $field]);
if (!$is_valid) {
$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_';
}
/**
* 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;
/**
* 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(),
);
}
/**
* 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
*/
public function setDepartmentId(int|null $department_id): self
{
$this->department_id = $department_id;
return $this;
}
/**
* The form after successful submission
*/
abstract public function afterSubmit(): void;
/**
* 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;
}
/**
* 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 $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 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 class name
* @return string The class name
*/
public function className(): string
{
return get_class($this);
}
/**
* 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,
],
'fields' => self::getFormFields(),
];
}
/**
* 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),
];
// 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;
}
}