-
-
Notifications
You must be signed in to change notification settings - Fork 110
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
ad32932
commit b82a043
Showing
3 changed files
with
211 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
160 changes: 160 additions & 0 deletions
160
pfSense-pkg-API/files/etc/inc/api/fields/DateTimeField.inc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
<?php | ||
|
||
namespace API\Fields; | ||
|
||
require_once("api/auto_loader.inc"); | ||
|
||
use API; | ||
use API\Core\Field; | ||
use API\Responses\ValidationError; | ||
use DateTime; | ||
|
||
/** | ||
* Defines a StringField class that extends the core Field method. This Field adds validation and representation for | ||
* string Model fields. | ||
*/ | ||
class DateTimeField extends Field { | ||
public string $datetime_format; | ||
|
||
/** | ||
* Defines the StringField object and sets its options. | ||
* @param bool $required If `true`, this field is required to have a value at all times. | ||
* @param bool $unique If `true`, this field must be unique from all other parent model objects. Enabling this | ||
* option requires the Model $context to be set AND the Model $context must have a `config_path` set. | ||
* @param mixed|null $default Assign a default string value to assign this Field if no value is present. | ||
* @param array $choices An array of value choices this Field can be assigned. This can either be an indexed array | ||
* of the exact choice values, or an associative array where the array key is the exact choice value and the array | ||
* value is a verbose name for the choice. Verbose choice name are used by ModelForms when generating web pages | ||
* for a given Model. | ||
* @param string $choices_callable Assign a callable method from this Field object OR the parent Model context to | ||
* execute to populate choices for this field. This callable must be a method assigned on this Field object OR the | ||
* parent Model object that returns an array of valid choices in the same format as $choices. This is helpful when | ||
* choices are dynamic and must be populated at runtime instead of pre-determined sets of values. | ||
* @param string $datetime_format the PHP DateTime format this value should be formatted as. | ||
* https://www.php.net/manual/en/datetime.format.php | ||
* @param bool $allow_empty If `true`, empty strings will be allowed by this field. | ||
* @param bool $allow_null If `true`, null values will be allowed by this field. | ||
* @param bool $editable Set to `false` to prevent this field's value from being changed after its initial creation. | ||
* @param bool $read_only If `true`, this field can only read its value and cannot write its value to config. | ||
* @param bool $write_only Set the `true` to make this field write-only. This will prevent the field's current value | ||
* from being displayed in the representation data. This is ideal for potentially sensitive fields like passwords, | ||
* keys, and hashes. | ||
* @param bool $representation_only Set to `true` to make this field only present in its representation form. This | ||
* effectively prevents the Field from being converted to an internal value which is saved to the pfSense config. | ||
* This should only be used for fields that do not relate directly to a configuration value. | ||
* @param bool $many If `true`, the value must be an array of many strings. | ||
* @param int $many_minimum When $many is set to `true`, this sets the minimum number of array entries required. | ||
* @param int $many_maximum When $many is set to `true`, this sets the maximum number of array entries allowed. | ||
* @param string|null $delimiter Assigns the string delimiter to use when writing array values to config. | ||
* Use `null` if this field is stored as an actual array in config. This is only available if $many is set to | ||
* `true`. Defaults to `,` to store as comma-separated string. | ||
* @param string $verbose_name The detailed name for this Field. This name will be used in non-programmatic areas | ||
* like web pages and help text. This Field will default to property name assigned to the parent Model with | ||
* underscores converted to spaces. | ||
* @param string $verbose_name_plural The plural form of $verbose_name. This defaults to $verbose_name with `s` | ||
* suffixed or `es` suffixes to strings already ending with `s`. | ||
* @param string $internal_name Assign a different field name to use when referring to the internal field as it's | ||
* stored in the pfSense configuration. | ||
* @param string $internal_namespace Sets the namespace this field belongs to internally. This can be used to nest | ||
* the fields internal value under a specific namespace as an associative array. This only applies to the internal | ||
* value, not the representation value. | ||
* @param array $referenced_by An array that specifies other Models and Field's that reference this Field's parent | ||
* Model using this Field's value. This will prevent the parent Model object from being deleted while it is actively | ||
* referenced by another Model object. The array key must be the name of the Model class that references this Field, | ||
* and the value must be a Field within that Model. The framework will automatically search for any existing Model | ||
* objects that have the referenced Field assigned a value that matches this Field's value. | ||
* @param array $conditions An array of conditions the field must meet to be included. This allows you to specify | ||
* conditions of other fields within the parent Model context. For example, if the parent Model context has two | ||
* fields, one field named `type` and the other being this field; and you only want this field to be included if | ||
* `type` is equal to `type1`, you could assign ["type" => "type1"] to this parameter. | ||
* @param array $validators An array of Validator objects to run against this field. | ||
* @param string $help_text Set a description for this field. This description will be used in API documentation. | ||
*/ | ||
public function __construct( | ||
bool $required = false, | ||
bool $unique = false, | ||
mixed $default = null, | ||
array $choices = [], | ||
string $choices_callable = "", | ||
string $datetime_format = "m/d/Y", | ||
bool $allow_empty = false, | ||
bool $allow_null = false, | ||
bool $editable = true, | ||
bool $read_only = false, | ||
bool $write_only = false, | ||
bool $representation_only = false, | ||
bool $many = false, | ||
int $many_minimum = 1, | ||
int $many_maximum = 128, | ||
string|null $delimiter = ",", | ||
string $verbose_name = "", | ||
string $verbose_name_plural = "", | ||
string $internal_name = "", | ||
string $internal_namespace = "", | ||
array $referenced_by = [], | ||
array $conditions = [], | ||
array $validators = [], | ||
string $help_text = "" | ||
) | ||
{ | ||
$this->datetime_format = $datetime_format; | ||
parent::__construct( | ||
type: "string", | ||
required: $required, | ||
unique: $unique, | ||
default: $default, | ||
choices: $choices, | ||
choices_callable: $choices_callable, | ||
allow_empty: $allow_empty, | ||
allow_null: $allow_null, | ||
editable: $editable, | ||
read_only: $read_only, | ||
write_only: $write_only, | ||
representation_only: $representation_only, | ||
many: $many, | ||
many_minimum: $many_minimum, | ||
many_maximum: $many_maximum, | ||
delimiter: $delimiter, | ||
verbose_name: $verbose_name, | ||
verbose_name_plural: $verbose_name_plural, | ||
internal_name: $internal_name, | ||
internal_namespace: $internal_namespace, | ||
referenced_by: $referenced_by, | ||
conditions: $conditions, | ||
validators: $validators, | ||
help_text: $help_text | ||
); | ||
} | ||
|
||
/** | ||
* @param string $datetime The datetime string to validate. (i.e. 12/31/1999) | ||
* @param string $format The DateTime format of the $datetime string. (i.e. m/d/Y) | ||
* @return bool `true` when the datetime string is valid, `false` when it is not. | ||
*/ | ||
public static function is_valid_datetime(string $datetime, string $format) : bool { | ||
# Try to create a DateTime object with this datetime string and format, then check for errors | ||
$datetime_obj = DateTime::createFromFormat($format, $datetime); | ||
$errors = DateTime::getLastErrors(); | ||
|
||
# This is not a valid datetime string if there are warnings | ||
if ($errors["warning_count"]) { | ||
return false; | ||
} | ||
|
||
return $datetime_obj !== false; | ||
} | ||
|
||
protected function check_value_type(mixed $value) | ||
{ | ||
# Run the parent check_value_type() method to check primitive date type (i.e. string, boolean, integer) | ||
parent::check_value_type($value); | ||
|
||
# Also check to ensure that the datetime value matches this Field's DateTime format. | ||
if (!$this->is_valid_datetime($value, $this->datetime_format)) { | ||
throw new ValidationError( | ||
message: "Field `$this->name` must be a DateTime in format `$this->datetime_format`. Received `$value`", | ||
response_id: "DATETIME_FIELD_MUST_MATCH_FORMAT" | ||
); | ||
} | ||
} | ||
} |
49 changes: 49 additions & 0 deletions
49
pfSense-pkg-API/files/etc/inc/api/tests/APIFieldsDateTimeFieldTestCase.inc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
<?php | ||
|
||
namespace API\Tests; | ||
|
||
use API\Core\TestCase; | ||
use API\Fields\DateTimeField; | ||
|
||
class APIFieldsDateTimeFieldTestCase extends TestCase | ||
{ | ||
/** | ||
* Checks that the is_valid_datetime() method correctly determines whether a datetime string matches a given format. | ||
*/ | ||
public function test_is_valid_datetime() { | ||
# Ensure valid datetimes return true | ||
$this->assert_is_true(DateTimeField::is_valid_datetime("12/31/1999", "m/d/Y")); | ||
$this->assert_is_true(DateTimeField::is_valid_datetime("Dec 31, 1999", "M d, Y")); | ||
|
||
# Ensure invalid datetimes return false | ||
$this->assert_is_false(DateTimeField::is_valid_datetime("13/31/1999", "m/d/Y")); | ||
$this->assert_is_false(DateTimeField::is_valid_datetime("Test 31, 1999", "M d, Y")); | ||
$this->assert_is_false(DateTimeField::is_valid_datetime("Dec 31, 1999", "m/d/Y")); | ||
$this->assert_is_false(DateTimeField::is_valid_datetime("12/31/1999", "M d, Y")); | ||
} | ||
|
||
/** | ||
* Checks that an error is thrown if this field is validated and its value does not match is datetime format | ||
*/ | ||
public function test_validate() { | ||
$this->assert_throws_response( | ||
response_id: "DATETIME_FIELD_MUST_MATCH_FORMAT", | ||
code: 400, | ||
callable: function () { | ||
$dt = new DateTimeField(required: true, datetime_format: "m/d/Y"); | ||
$dt->name = "test_field"; | ||
$dt->value = "not a datetime"; | ||
$dt->validate(); | ||
} | ||
); | ||
|
||
$this->assert_does_not_throw( | ||
callable: function () { | ||
$dt = new DateTimeField(required: true, datetime_format: "m/d/Y"); | ||
$dt->name = "test_field"; | ||
$dt->value = "12/31/1999"; | ||
$dt->validate(); | ||
} | ||
); | ||
} | ||
} |