diff --git a/pfSense-pkg-API/files/etc/inc/api/core/Field.inc b/pfSense-pkg-API/files/etc/inc/api/core/Field.inc index da7c00be2..945cecb38 100644 --- a/pfSense-pkg-API/files/etc/inc/api/core/Field.inc +++ b/pfSense-pkg-API/files/etc/inc/api/core/Field.inc @@ -739,7 +739,7 @@ class Field * `value` property for this object. * @param string|null $internal_value The raw internal config value to convert to a representation value. */ - public function from_internal(string|null $internal_value) { + public function from_internal(mixed $internal_value) { # Keep null values for non boolean values and representation only values if ($this->representation_only or (is_null($internal_value) and $this->type !== "boolean")) { # Check if this field has a default, if so, use the default value diff --git a/pfSense-pkg-API/files/etc/inc/api/core/Model.inc b/pfSense-pkg-API/files/etc/inc/api/core/Model.inc index 300169026..0bc63202c 100644 --- a/pfSense-pkg-API/files/etc/inc/api/core/Model.inc +++ b/pfSense-pkg-API/files/etc/inc/api/core/Model.inc @@ -152,6 +152,39 @@ class Model { return (array_set_path($config, $path, $value, $default)); } + /** + * Merges changed Model field values into the config. This is similar to `set_config()` but instead of replacing + * the entire config path with a set value, it only sets Field's known to this Model. Any fields in the stored + * object that are not defined in a Field assigned to this Model will be left unchanged. + * @param $path string The Model config path including any Model ID + */ + public function merge_config(string $path) { + # Loop through each field known to this Model + foreach ($this->get_fields() as $field) { + # Determine the field path + if ($this->$field->internal_namespace) { + $field_path = "$path/{$this->$field->internal_namespace}/{$this->$field->internal_name}"; + } + else { + $field_path = "$path/{$this->$field->internal_name}"; + } + + # Do not merge `representation_only` fields + if ($this->$field->representation_only) { + continue; + } + + # Remove config values to fields that are `null` + if ($this->$field->to_internal() === null) { + $this->del_config(path: $field_path); + continue; + } + + # Otherwise, set the value normally + $this->set_config(path: $field_path, value: $this->$field->to_internal()); + } + } + /** * Delete a key from the configuration * @param $path string path with '/' separators @@ -907,12 +940,12 @@ class Model { # Merge differences between the stored object and updated internal object when update strategy is 'merge' if ($this->update_strategy === "merge") { - $stored_object = $this->get_config($update_config_path, []); - $updated_object = array_merge($stored_object, $this->to_internal()); + $this->merge_config($update_config_path); } # Replace the internal object entirely when update strategy is 'replace' elseif ($this->update_strategy === "replace") { - $updated_object = $this->to_internal(); + $this->set_config(path: $update_config_path, value: $this->to_internal()); + } # Throw an error if the update strategy is unknown else { @@ -923,7 +956,6 @@ class Model { } # Write the changes to the object in config. - $this->set_config(path: $update_config_path, value: $updated_object); $this->write_config("Modified $this->verbose_name via API"); return; } diff --git a/pfSense-pkg-API/files/etc/inc/api/endpoints/ServicesDHCPServers.inc b/pfSense-pkg-API/files/etc/inc/api/endpoints/ServicesDHCPServers.inc index 47689b8c7..4ff8de14e 100644 --- a/pfSense-pkg-API/files/etc/inc/api/endpoints/ServicesDHCPServers.inc +++ b/pfSense-pkg-API/files/etc/inc/api/endpoints/ServicesDHCPServers.inc @@ -12,7 +12,6 @@ class ServicesDHCPServers extends Endpoint # Assign the URL for this endpoint along with the Model it corresponds to. $this->url = "/api/v2/services/dhcp_servers"; $this->model_name = "DHCPServer"; - $this-> $this->many = true; # Assign allowed request methods along with the privileges required for each diff --git a/pfSense-pkg-API/files/etc/inc/api/fields/IntegerField.inc b/pfSense-pkg-API/files/etc/inc/api/fields/IntegerField.inc index ebc3e5bf2..8e5a21f5b 100644 --- a/pfSense-pkg-API/files/etc/inc/api/fields/IntegerField.inc +++ b/pfSense-pkg-API/files/etc/inc/api/fields/IntegerField.inc @@ -42,8 +42,9 @@ class IntegerField extends API\Core\Field { * @param int $many_maximum When $many is set to `true`, this sets the maximum number of array entries allowed. * @param int $minimum The minimum value this value can be. * @param int $maximum The maximum value this value can be. - * @param string $delimiter Assign the delimiter used to join the value array back to its internal string form when - * $many is enabled. + * @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. @@ -82,7 +83,7 @@ class IntegerField extends API\Core\Field { int $many_maximum = 128, int $minimum = 0, int $maximum = 99999999999999, - string $delimiter = ",", + string|null $delimiter = ",", string $verbose_name = "", string $verbose_name_plural = "", string $internal_name = "", diff --git a/pfSense-pkg-API/files/etc/inc/api/fields/InterfaceField.inc b/pfSense-pkg-API/files/etc/inc/api/fields/InterfaceField.inc index eaa31c30a..a3ce1db1d 100644 --- a/pfSense-pkg-API/files/etc/inc/api/fields/InterfaceField.inc +++ b/pfSense-pkg-API/files/etc/inc/api/fields/InterfaceField.inc @@ -46,8 +46,9 @@ class InterfaceField extends API\Fields\StringField { * @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 $delimiter Assign the delimiter used to join the value array back to its internal string form when - * $many is enabled. + * @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. @@ -88,7 +89,7 @@ class InterfaceField extends API\Fields\StringField { bool $many = false, int $many_minimum = 1, int $many_maximum = 128, - string $delimiter = ",", + string|null $delimiter = ",", string $verbose_name = "", string $verbose_name_plural = "", string $internal_name = "", diff --git a/pfSense-pkg-API/files/etc/inc/api/fields/StringField.inc b/pfSense-pkg-API/files/etc/inc/api/fields/StringField.inc index 7e710d93e..090f61501 100644 --- a/pfSense-pkg-API/files/etc/inc/api/fields/StringField.inc +++ b/pfSense-pkg-API/files/etc/inc/api/fields/StringField.inc @@ -43,8 +43,9 @@ class StringField extends API\Core\Field { * @param int $many_maximum When $many is set to `true`, this sets the maximum number of array entries allowed. * @param int $minimum_length The minimum number of characters required for this string field. * @param int $maximum_length The maximum number of characters allowed by this string field. - * @param string $delimiter Assign the delimiter used to join the value array back to its internal string form when - * $many is enabled. + * @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. @@ -84,7 +85,7 @@ class StringField extends API\Core\Field { int $many_maximum = 128, int $minimum_length = 0, int $maximum_length = 1024, - string $delimiter = ",", + string|null $delimiter = ",", string $verbose_name = "", string $verbose_name_plural = "", string $internal_name = "", diff --git a/pfSense-pkg-API/files/etc/inc/api/models/DHCPServer.inc b/pfSense-pkg-API/files/etc/inc/api/models/DHCPServer.inc index 23df02aa3..36e9af892 100644 --- a/pfSense-pkg-API/files/etc/inc/api/models/DHCPServer.inc +++ b/pfSense-pkg-API/files/etc/inc/api/models/DHCPServer.inc @@ -206,7 +206,8 @@ class DHCPServer extends Model ); $this->denyunknown = new StringField( default: null, - choices: ["enabled", "class"], + choices: ["", "enabled", "class"], + allow_empty: true, allow_null: true, help_text: "Define how to handle unknown clients requesting DHCP leases. When set to `null`, any DHCP ". "client will get an IP address within this scope/range on this interface. If set to `enabled`, ".