Files
animal_management/models/forms/AnimalProgressUpdateForm.php
2026-04-04 13:13:00 -04:00

349 lines
12 KiB
PHP

<?php
namespace humhub\modules\animal_management\models\forms;
use humhub\modules\animal_management\models\Animal;
use humhub\modules\animal_management\models\AnimalProgressUpdate;
use humhub\modules\animal_management\models\AnimalProgressUpdateFieldValue;
use humhub\modules\animal_management\services\AnimalStreamPublisherService;
use humhub\modules\rescue_foundation\models\RescueFieldDefinition;
use Yii;
use yii\base\Model;
use yii\helpers\Json;
class AnimalProgressUpdateForm extends Model
{
public const MODULE_ID = 'animal_management';
public Animal $animal;
public string $update_at = '';
public string $weight = '';
public string $vitals = '';
public string $behavior_notes = '';
public string $meal_plan_changes = '';
public string $housing_changes = '';
public string $medical_concerns = '';
public bool $post_to_space_feed = false;
public bool $post_to_animal_feed = true;
public array $customFields = [];
public ?AnimalProgressUpdate $progressUpdate = null;
private array $customFieldDefinitions = [];
public function init()
{
parent::init();
$this->bootstrapCustomFieldDefinitions();
}
public function rules()
{
return [
[['update_at', 'weight', 'vitals', 'behavior_notes', 'meal_plan_changes', 'housing_changes', 'medical_concerns'], 'safe'],
[['vitals', 'behavior_notes', 'meal_plan_changes', 'housing_changes', 'medical_concerns'], 'string'],
[['weight'], 'string', 'max' => 32],
[['post_to_space_feed', 'post_to_animal_feed'], 'boolean'],
[['customFields'], 'safe'],
[['customFields'], 'validateCustomFieldValues'],
];
}
public function save(): bool
{
if (!$this->validate()) {
return false;
}
$record = $this->progressUpdate instanceof AnimalProgressUpdate
? $this->progressUpdate
: new AnimalProgressUpdate();
$isNew = $record->getIsNewRecord();
if ($isNew) {
$record->animal_id = (int)$this->animal->id;
$record->created_by = Yii::$app->user->isGuest ? null : (int)Yii::$app->user->id;
}
$record->update_at = $this->normalizeDateTime($this->update_at);
$record->weight = trim($this->weight);
$record->vitals = trim($this->vitals);
$record->behavior_notes = trim($this->behavior_notes);
$record->meal_plan_changes = trim($this->meal_plan_changes);
$record->housing_changes = trim($this->housing_changes);
$record->medical_concerns = trim($this->medical_concerns);
$record->post_to_space_feed = $this->post_to_space_feed ? 1 : 0;
$record->post_to_animal_feed = $this->post_to_animal_feed ? 1 : 0;
if (!$record->save()) {
$this->addErrors($record->getErrors());
return false;
}
$this->progressUpdate = $record;
if (!$this->saveCustomFieldValues($record)) {
return false;
}
if ($record->post_to_space_feed) {
AnimalStreamPublisherService::publishProgressUpdate($this->animal, $record);
}
return true;
}
public function setProgressUpdate(AnimalProgressUpdate $progressUpdate): void
{
$this->progressUpdate = $progressUpdate;
$this->update_at = (string)$progressUpdate->update_at;
$this->weight = (string)$progressUpdate->weight;
$this->vitals = (string)$progressUpdate->vitals;
$this->behavior_notes = (string)$progressUpdate->behavior_notes;
$this->meal_plan_changes = (string)$progressUpdate->meal_plan_changes;
$this->housing_changes = (string)$progressUpdate->housing_changes;
$this->medical_concerns = (string)$progressUpdate->medical_concerns;
$this->post_to_space_feed = ((int)$progressUpdate->post_to_space_feed) === 1;
$this->post_to_animal_feed = ((int)$progressUpdate->post_to_animal_feed) === 1;
$this->loadCustomFieldValues($progressUpdate);
}
public function getCustomFieldDefinitions(): array
{
return $this->customFieldDefinitions;
}
public function getCustomFieldSelectOptions(string $fieldKey): array
{
if (!isset($this->customFieldDefinitions[$fieldKey])) {
return [];
}
return $this->parseSelectOptions((string)$this->customFieldDefinitions[$fieldKey]['options']);
}
public function validateCustomFieldValues(string $attribute): void
{
foreach ($this->customFieldDefinitions as $fieldKey => $definition) {
$value = $this->customFields[$fieldKey] ?? null;
$label = (string)$definition['label'];
$inputType = (string)$definition['input_type'];
$required = ((int)$definition['required']) === 1;
if ($required && $this->isCustomValueEmpty($value, $inputType)) {
$this->addError($attribute, Yii::t('AnimalManagementModule.base', '{field} is required.', ['{field}' => $label]));
continue;
}
if ($this->isCustomValueEmpty($value, $inputType)) {
continue;
}
$normalized = is_array($value) ? '' : trim((string)$value);
if ($inputType === 'number' && !is_numeric($normalized)) {
$this->addError($attribute, Yii::t('AnimalManagementModule.base', '{field} must be a valid number.', ['{field}' => $label]));
continue;
}
if (($inputType === 'date' || $inputType === 'datetime') && strtotime($normalized) === false) {
$this->addError($attribute, Yii::t('AnimalManagementModule.base', '{field} must be a valid date/time.', ['{field}' => $label]));
continue;
}
if ($inputType === 'select') {
$options = $this->getCustomFieldSelectOptions($fieldKey);
if (!empty($options) && !array_key_exists($normalized, $options)) {
$this->addError($attribute, Yii::t('AnimalManagementModule.base', 'Invalid selection for {field}.', ['{field}' => $label]));
}
}
}
}
private function normalizeDateTime(string $value): string
{
$value = trim($value);
if ($value === '') {
return date('Y-m-d H:i:s');
}
$timestamp = strtotime($value);
if ($timestamp === false) {
return date('Y-m-d H:i:s');
}
return date('Y-m-d H:i:s', $timestamp);
}
private function bootstrapCustomFieldDefinitions(): void
{
if (!class_exists(RescueFieldDefinition::class)) {
return;
}
if (Yii::$app->db->schema->getTableSchema('rescue_field_definition', true) === null) {
return;
}
$definitions = RescueFieldDefinition::find()
->where(['module_id' => self::MODULE_ID, 'group_key' => 'animal_progress_update', 'is_active' => 1])
->orderBy(['sort_order' => SORT_ASC, 'id' => SORT_ASC])
->all();
foreach ($definitions as $definition) {
$fieldKey = (string)$definition->field_key;
$this->customFieldDefinitions[$fieldKey] = [
'id' => (int)$definition->id,
'field_key' => $fieldKey,
'label' => trim((string)$definition->label) !== '' ? (string)$definition->label : $fieldKey,
'input_type' => (string)$definition->input_type,
'required' => (int)$definition->required,
'visibility' => (string)$definition->visibility,
'options' => (string)$definition->options,
];
}
}
private function saveCustomFieldValues(AnimalProgressUpdate $record): bool
{
foreach ($this->customFieldDefinitions as $fieldKey => $definition) {
$fieldDefinitionId = (int)$definition['id'];
if ($fieldDefinitionId === 0) {
continue;
}
$inputType = (string)$definition['input_type'];
$rawValue = $this->customFields[$fieldKey] ?? null;
$valueText = $this->normalizeCustomValue($rawValue, $inputType);
$valueRecord = AnimalProgressUpdateFieldValue::findOne([
'progress_update_id' => (int)$record->id,
'field_definition_id' => $fieldDefinitionId,
]);
if ($this->isCustomValueEmpty($valueText, $inputType)) {
if ($valueRecord instanceof AnimalProgressUpdateFieldValue) {
$valueRecord->delete();
}
continue;
}
if (!$valueRecord instanceof AnimalProgressUpdateFieldValue) {
$valueRecord = new AnimalProgressUpdateFieldValue();
$valueRecord->progress_update_id = (int)$record->id;
$valueRecord->field_definition_id = $fieldDefinitionId;
}
$valueRecord->value_text = $valueText;
$valueRecord->value_json = null;
if (!$valueRecord->save()) {
$this->addError('customFields', Yii::t('AnimalManagementModule.base', 'Could not save custom progress field {field}.', [
'{field}' => (string)$definition['label'],
]));
foreach ($valueRecord->getFirstErrors() as $error) {
$this->addError('customFields', $error);
}
}
}
return !$this->hasErrors('customFields');
}
private function normalizeCustomValue($value, string $inputType): ?string
{
if ($inputType === 'boolean') {
return !empty($value) ? '1' : '0';
}
if (is_array($value)) {
return Json::encode($value);
}
return trim((string)$value);
}
private function isCustomValueEmpty($value, string $inputType): bool
{
if ($inputType === 'boolean') {
return false;
}
if (is_array($value)) {
return empty($value);
}
return trim((string)$value) === '';
}
private function parseSelectOptions(string $options): array
{
$options = trim($options);
if ($options === '') {
return [];
}
$decoded = null;
try {
$decoded = Json::decode($options, true);
} catch (\Throwable $e) {
$decoded = null;
}
if (is_array($decoded)) {
$result = [];
if (array_values($decoded) === $decoded) {
foreach ($decoded as $item) {
$item = (string)$item;
if ($item === '') {
continue;
}
$result[$item] = $item;
}
return $result;
}
foreach ($decoded as $key => $value) {
$key = (string)$key;
if ($key === '') {
continue;
}
$result[$key] = (string)$value;
}
return $result;
}
$result = [];
foreach (preg_split('/[\r\n,]+/', $options) as $item) {
$item = trim((string)$item);
if ($item === '') {
continue;
}
$result[$item] = $item;
}
return $result;
}
private function loadCustomFieldValues(AnimalProgressUpdate $record): void
{
foreach ($this->customFieldDefinitions as $fieldKey => $definition) {
$value = AnimalProgressUpdateFieldValue::findOne([
'progress_update_id' => (int)$record->id,
'field_definition_id' => (int)$definition['id'],
]);
if (!$value instanceof AnimalProgressUpdateFieldValue) {
continue;
}
$this->customFields[$fieldKey] = (string)$value->value_text;
}
}
}