Initial import of animal_management module
This commit is contained in:
213
models/Animal.php
Normal file
213
models/Animal.php
Normal file
@@ -0,0 +1,213 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management\models;
|
||||
|
||||
use humhub\components\ActiveRecord;
|
||||
use Yii;
|
||||
use yii\helpers\Json;
|
||||
|
||||
class Animal extends ActiveRecord
|
||||
{
|
||||
public const STATUS_INTAKE = 'intake';
|
||||
public const STATUS_ACTIVE = 'active';
|
||||
public const STATUS_TRANSFER_PENDING = 'transfer_pending';
|
||||
public const STATUS_TRANSFERRED = 'transferred';
|
||||
public const STATUS_ADOPTED = 'adopted';
|
||||
|
||||
public static function tableName()
|
||||
{
|
||||
return 'rescue_animal';
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
[['contentcontainer_id', 'animal_uid'], 'required'],
|
||||
[['contentcontainer_id', 'previous_owner_user_id', 'in_possession'], 'integer'],
|
||||
[['public_summary', 'medical_notes'], 'string'],
|
||||
[['name', 'animal_uid'], 'string', 'max' => 190],
|
||||
[['species', 'breed', 'location_name'], 'string', 'max' => 120],
|
||||
[['sex', 'status'], 'string', 'max' => 32],
|
||||
[['city'], 'string', 'max' => 120],
|
||||
[['state'], 'string', 'max' => 2],
|
||||
[['zip'], 'string', 'max' => 10],
|
||||
[['animal_uid'], 'unique'],
|
||||
[['status'], 'in', 'range' => array_keys(self::statusOptions())],
|
||||
];
|
||||
}
|
||||
|
||||
public function beforeValidate()
|
||||
{
|
||||
if (!parent::beforeValidate()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->isNewRecord && empty($this->animal_uid)) {
|
||||
$this->animal_uid = $this->generateAnimalUid();
|
||||
}
|
||||
|
||||
if (empty($this->status)) {
|
||||
$this->status = self::STATUS_INTAKE;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function beforeSave($insert)
|
||||
{
|
||||
if (!parent::beforeSave($insert)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$now = date('Y-m-d H:i:s');
|
||||
if ($insert && empty($this->created_at)) {
|
||||
$this->created_at = $now;
|
||||
}
|
||||
|
||||
$this->updated_at = $now;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function statusOptions(): array
|
||||
{
|
||||
return [
|
||||
self::STATUS_INTAKE => Yii::t('AnimalManagementModule.base', 'Intake'),
|
||||
self::STATUS_ACTIVE => Yii::t('AnimalManagementModule.base', 'Active'),
|
||||
self::STATUS_TRANSFER_PENDING => Yii::t('AnimalManagementModule.base', 'Transfer Pending'),
|
||||
self::STATUS_TRANSFERRED => Yii::t('AnimalManagementModule.base', 'Transferred'),
|
||||
self::STATUS_ADOPTED => Yii::t('AnimalManagementModule.base', 'Adopted'),
|
||||
];
|
||||
}
|
||||
|
||||
public function getDisplayName(): string
|
||||
{
|
||||
$name = trim((string)$this->name);
|
||||
return $name !== '' ? $name : (string)$this->animal_uid;
|
||||
}
|
||||
|
||||
public function getTransfers()
|
||||
{
|
||||
return $this->hasMany(AnimalTransfer::class, ['animal_id' => 'id']);
|
||||
}
|
||||
|
||||
public function getMedicalVisits()
|
||||
{
|
||||
return $this->hasMany(AnimalMedicalVisit::class, ['animal_id' => 'id']);
|
||||
}
|
||||
|
||||
public function getProgressUpdates()
|
||||
{
|
||||
return $this->hasMany(AnimalProgressUpdate::class, ['animal_id' => 'id']);
|
||||
}
|
||||
|
||||
public function getTransferEvents()
|
||||
{
|
||||
return $this->hasMany(AnimalTransferEvent::class, ['animal_id' => 'id']);
|
||||
}
|
||||
|
||||
public function getFieldValues()
|
||||
{
|
||||
return $this->hasMany(AnimalFieldValue::class, ['animal_id' => 'id']);
|
||||
}
|
||||
|
||||
public function getGalleryItems()
|
||||
{
|
||||
return $this->hasMany(AnimalGalleryItem::class, ['animal_id' => 'id']);
|
||||
}
|
||||
|
||||
public function getCustomFieldDisplayValues(bool $includeRestricted = false): array
|
||||
{
|
||||
$values = [];
|
||||
|
||||
foreach ($this->getFieldValues()->with('fieldDefinition')->all() as $fieldValue) {
|
||||
if (!$fieldValue instanceof AnimalFieldValue) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$definition = $fieldValue->fieldDefinition;
|
||||
if ($definition === null || (string)$definition->module_id !== 'animal_management') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((int)$definition->is_active !== 1 || (int)$definition->is_core === 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((string)$definition->group_key !== 'animal_profile') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_array((string)$definition->field_key, [
|
||||
'cover_image_url',
|
||||
'profile_image_url',
|
||||
'photo_url',
|
||||
'image_url',
|
||||
'profile_image',
|
||||
'photo',
|
||||
'tile_display_fields',
|
||||
'hero_display_fields',
|
||||
], true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$visibility = (string)$definition->visibility;
|
||||
if (!$includeRestricted && $visibility !== 'public') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$raw = trim((string)$fieldValue->value_text);
|
||||
if ($raw === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$display = $raw;
|
||||
$inputType = (string)$definition->input_type;
|
||||
|
||||
if ($inputType === 'boolean') {
|
||||
$display = in_array(strtolower($raw), ['1', 'true', 'yes', 'on'], true)
|
||||
? Yii::t('AnimalManagementModule.base', 'Yes')
|
||||
: Yii::t('AnimalManagementModule.base', 'No');
|
||||
} elseif ($inputType === 'select') {
|
||||
$display = $this->mapSelectDisplayValue((string)$definition->options, $raw);
|
||||
}
|
||||
|
||||
$values[] = [
|
||||
'field_key' => (string)$definition->field_key,
|
||||
'label' => trim((string)$definition->label) !== '' ? (string)$definition->label : (string)$definition->field_key,
|
||||
'value' => $display,
|
||||
'visibility' => $visibility,
|
||||
];
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
private function mapSelectDisplayValue(string $options, string $raw): string
|
||||
{
|
||||
$options = trim($options);
|
||||
if ($options === '') {
|
||||
return $raw;
|
||||
}
|
||||
|
||||
$decoded = Json::decode($options, true);
|
||||
if (!is_array($decoded)) {
|
||||
return $raw;
|
||||
}
|
||||
|
||||
if (array_values($decoded) === $decoded) {
|
||||
return in_array($raw, $decoded, true) ? $raw : $raw;
|
||||
}
|
||||
|
||||
return isset($decoded[$raw]) ? (string)$decoded[$raw] : $raw;
|
||||
}
|
||||
|
||||
private function generateAnimalUid(): string
|
||||
{
|
||||
do {
|
||||
$candidate = 'ANI-' . date('Ymd') . '-' . strtoupper(substr(Yii::$app->security->generateRandomString(8), 0, 6));
|
||||
$exists = static::find()->where(['animal_uid' => $candidate])->exists();
|
||||
} while ($exists);
|
||||
|
||||
return $candidate;
|
||||
}
|
||||
}
|
||||
49
models/AnimalFieldValue.php
Normal file
49
models/AnimalFieldValue.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management\models;
|
||||
|
||||
use humhub\components\ActiveRecord;
|
||||
use humhub\modules\rescue_foundation\models\RescueFieldDefinition;
|
||||
|
||||
class AnimalFieldValue extends ActiveRecord
|
||||
{
|
||||
public static function tableName()
|
||||
{
|
||||
return 'rescue_animal_field_value';
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
[['animal_id', 'field_definition_id'], 'required'],
|
||||
[['animal_id', 'field_definition_id'], 'integer'],
|
||||
[['value_text', 'value_json'], 'string'],
|
||||
[['animal_id', 'field_definition_id'], 'unique', 'targetAttribute' => ['animal_id', 'field_definition_id']],
|
||||
];
|
||||
}
|
||||
|
||||
public function beforeSave($insert)
|
||||
{
|
||||
if (!parent::beforeSave($insert)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$now = date('Y-m-d H:i:s');
|
||||
if ($insert && empty($this->created_at)) {
|
||||
$this->created_at = $now;
|
||||
}
|
||||
|
||||
$this->updated_at = $now;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getAnimal()
|
||||
{
|
||||
return $this->hasOne(Animal::class, ['id' => 'animal_id']);
|
||||
}
|
||||
|
||||
public function getFieldDefinition()
|
||||
{
|
||||
return $this->hasOne(RescueFieldDefinition::class, ['id' => 'field_definition_id']);
|
||||
}
|
||||
}
|
||||
75
models/AnimalGalleryItem.php
Normal file
75
models/AnimalGalleryItem.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management\models;
|
||||
|
||||
use humhub\components\ActiveRecord;
|
||||
use humhub\modules\file\models\File;
|
||||
|
||||
class AnimalGalleryItem extends ActiveRecord
|
||||
{
|
||||
public static function tableName()
|
||||
{
|
||||
return 'rescue_animal_gallery_item';
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
[['animal_id'], 'required'],
|
||||
[['animal_id', 'file_id', 'source_post_id', 'created_by'], 'integer'],
|
||||
[['caption'], 'string'],
|
||||
[['source_type'], 'string', 'max' => 32],
|
||||
[['file_path'], 'string', 'max' => 500],
|
||||
];
|
||||
}
|
||||
|
||||
public function beforeSave($insert)
|
||||
{
|
||||
if (!parent::beforeSave($insert)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$now = date('Y-m-d H:i:s');
|
||||
if ($insert && empty($this->created_at)) {
|
||||
$this->created_at = $now;
|
||||
}
|
||||
|
||||
if (empty($this->source_type)) {
|
||||
$this->source_type = 'upload';
|
||||
}
|
||||
|
||||
$this->updated_at = $now;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getAnimal()
|
||||
{
|
||||
return $this->hasOne(Animal::class, ['id' => 'animal_id']);
|
||||
}
|
||||
|
||||
public function getFile()
|
||||
{
|
||||
return $this->hasOne(File::class, ['id' => 'file_id']);
|
||||
}
|
||||
|
||||
public function getImageUrl(): string
|
||||
{
|
||||
$path = trim((string)$this->file_path);
|
||||
if ($path !== '') {
|
||||
if (preg_match('/^https?:\/\//i', $path) || substr($path, 0, 1) === '/') {
|
||||
return $path;
|
||||
}
|
||||
|
||||
return '/' . ltrim($path, '/');
|
||||
}
|
||||
|
||||
if ($this->file_id) {
|
||||
$file = $this->file;
|
||||
if ($file instanceof File) {
|
||||
return (string)$file->getUrl([], false);
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
43
models/AnimalGalleryLink.php
Normal file
43
models/AnimalGalleryLink.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management\models;
|
||||
|
||||
use humhub\components\ActiveRecord;
|
||||
|
||||
class AnimalGalleryLink extends ActiveRecord
|
||||
{
|
||||
public static function tableName()
|
||||
{
|
||||
return 'rescue_animal_gallery_link';
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
[['animal_id', 'gallery_id', 'contentcontainer_id'], 'required'],
|
||||
[['animal_id', 'gallery_id', 'contentcontainer_id'], 'integer'],
|
||||
[['animal_id'], 'unique'],
|
||||
[['gallery_id'], 'unique'],
|
||||
];
|
||||
}
|
||||
|
||||
public function beforeSave($insert)
|
||||
{
|
||||
if (!parent::beforeSave($insert)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$now = date('Y-m-d H:i:s');
|
||||
if ($insert && empty($this->created_at)) {
|
||||
$this->created_at = $now;
|
||||
}
|
||||
|
||||
$this->updated_at = $now;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getAnimal()
|
||||
{
|
||||
return $this->hasOne(Animal::class, ['id' => 'animal_id']);
|
||||
}
|
||||
}
|
||||
94
models/AnimalMedicalVisit.php
Normal file
94
models/AnimalMedicalVisit.php
Normal file
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management\models;
|
||||
|
||||
use humhub\components\ActiveRecord;
|
||||
use Yii;
|
||||
|
||||
class AnimalMedicalVisit extends ActiveRecord
|
||||
{
|
||||
public static function tableName()
|
||||
{
|
||||
return 'rescue_animal_medical_visit';
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
$rules = [
|
||||
[['animal_id'], 'required'],
|
||||
[['animal_id', 'created_by'], 'integer'],
|
||||
[['visit_at'], 'safe'],
|
||||
[['notes', 'recommendations'], 'string'],
|
||||
[['provider_name'], 'string', 'max' => 190],
|
||||
];
|
||||
|
||||
$table = Yii::$app->db->schema->getTableSchema(static::tableName(), true);
|
||||
if ($table !== null && isset($table->columns['post_to_space_feed']) && isset($table->columns['post_to_animal_feed'])) {
|
||||
$rules[] = [['post_to_space_feed', 'post_to_animal_feed'], 'integer'];
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
public function beforeSave($insert)
|
||||
{
|
||||
if (!parent::beforeSave($insert)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$now = date('Y-m-d H:i:s');
|
||||
if ($insert && empty($this->created_at)) {
|
||||
$this->created_at = $now;
|
||||
}
|
||||
|
||||
$this->updated_at = $now;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getFieldValues()
|
||||
{
|
||||
return $this->hasMany(AnimalMedicalVisitFieldValue::class, ['medical_visit_id' => 'id']);
|
||||
}
|
||||
|
||||
public function getCustomFieldDisplayValues(bool $includeRestricted = false): array
|
||||
{
|
||||
$values = [];
|
||||
|
||||
foreach ($this->getFieldValues()->with('fieldDefinition')->all() as $fieldValue) {
|
||||
$definition = $fieldValue->fieldDefinition;
|
||||
if ($definition === null || (string)$definition->module_id !== 'animal_management') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((int)$definition->is_active !== 1 || (string)$definition->group_key !== 'animal_medical_visit') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$visibility = (string)$definition->visibility;
|
||||
if (!$includeRestricted && $visibility !== 'public') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$raw = trim((string)$fieldValue->value_text);
|
||||
if ($raw === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$display = $raw;
|
||||
$inputType = (string)$definition->input_type;
|
||||
if ($inputType === 'boolean') {
|
||||
$display = in_array(strtolower($raw), ['1', 'true', 'yes', 'on'], true)
|
||||
? Yii::t('AnimalManagementModule.base', 'Yes')
|
||||
: Yii::t('AnimalManagementModule.base', 'No');
|
||||
}
|
||||
|
||||
$values[] = [
|
||||
'field_key' => (string)$definition->field_key,
|
||||
'label' => trim((string)$definition->label) !== '' ? (string)$definition->label : (string)$definition->field_key,
|
||||
'value' => $display,
|
||||
];
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
}
|
||||
49
models/AnimalMedicalVisitFieldValue.php
Normal file
49
models/AnimalMedicalVisitFieldValue.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management\models;
|
||||
|
||||
use humhub\components\ActiveRecord;
|
||||
use humhub\modules\rescue_foundation\models\RescueFieldDefinition;
|
||||
|
||||
class AnimalMedicalVisitFieldValue extends ActiveRecord
|
||||
{
|
||||
public static function tableName()
|
||||
{
|
||||
return 'rescue_animal_medical_visit_field_value';
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
[['medical_visit_id', 'field_definition_id'], 'required'],
|
||||
[['medical_visit_id', 'field_definition_id'], 'integer'],
|
||||
[['value_text', 'value_json'], 'string'],
|
||||
[['medical_visit_id', 'field_definition_id'], 'unique', 'targetAttribute' => ['medical_visit_id', 'field_definition_id']],
|
||||
];
|
||||
}
|
||||
|
||||
public function beforeSave($insert)
|
||||
{
|
||||
if (!parent::beforeSave($insert)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$now = date('Y-m-d H:i:s');
|
||||
if ($insert && empty($this->created_at)) {
|
||||
$this->created_at = $now;
|
||||
}
|
||||
|
||||
$this->updated_at = $now;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getMedicalVisit()
|
||||
{
|
||||
return $this->hasOne(AnimalMedicalVisit::class, ['id' => 'medical_visit_id']);
|
||||
}
|
||||
|
||||
public function getFieldDefinition()
|
||||
{
|
||||
return $this->hasOne(RescueFieldDefinition::class, ['id' => 'field_definition_id']);
|
||||
}
|
||||
}
|
||||
87
models/AnimalProgressUpdate.php
Normal file
87
models/AnimalProgressUpdate.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management\models;
|
||||
|
||||
use humhub\components\ActiveRecord;
|
||||
use Yii;
|
||||
|
||||
class AnimalProgressUpdate extends ActiveRecord
|
||||
{
|
||||
public static function tableName()
|
||||
{
|
||||
return 'rescue_animal_progress_update';
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
[['animal_id'], 'required'],
|
||||
[['animal_id', 'created_by', 'post_to_space_feed', 'post_to_animal_feed'], 'integer'],
|
||||
[['update_at'], 'safe'],
|
||||
[['vitals', 'behavior_notes', 'meal_plan_changes', 'housing_changes', 'medical_concerns'], 'string'],
|
||||
[['weight'], 'string', 'max' => 32],
|
||||
];
|
||||
}
|
||||
|
||||
public function beforeSave($insert)
|
||||
{
|
||||
if (!parent::beforeSave($insert)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$now = date('Y-m-d H:i:s');
|
||||
if ($insert && empty($this->created_at)) {
|
||||
$this->created_at = $now;
|
||||
}
|
||||
|
||||
$this->updated_at = $now;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getFieldValues()
|
||||
{
|
||||
return $this->hasMany(AnimalProgressUpdateFieldValue::class, ['progress_update_id' => 'id']);
|
||||
}
|
||||
|
||||
public function getCustomFieldDisplayValues(bool $includeRestricted = false): array
|
||||
{
|
||||
$values = [];
|
||||
|
||||
foreach ($this->getFieldValues()->with('fieldDefinition')->all() as $fieldValue) {
|
||||
$definition = $fieldValue->fieldDefinition;
|
||||
if ($definition === null || (string)$definition->module_id !== 'animal_management') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((int)$definition->is_active !== 1 || (string)$definition->group_key !== 'animal_progress_update') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$visibility = (string)$definition->visibility;
|
||||
if (!$includeRestricted && $visibility !== 'public') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$raw = trim((string)$fieldValue->value_text);
|
||||
if ($raw === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$display = $raw;
|
||||
$inputType = (string)$definition->input_type;
|
||||
if ($inputType === 'boolean') {
|
||||
$display = in_array(strtolower($raw), ['1', 'true', 'yes', 'on'], true)
|
||||
? Yii::t('AnimalManagementModule.base', 'Yes')
|
||||
: Yii::t('AnimalManagementModule.base', 'No');
|
||||
}
|
||||
|
||||
$values[] = [
|
||||
'field_key' => (string)$definition->field_key,
|
||||
'label' => trim((string)$definition->label) !== '' ? (string)$definition->label : (string)$definition->field_key,
|
||||
'value' => $display,
|
||||
];
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
}
|
||||
49
models/AnimalProgressUpdateFieldValue.php
Normal file
49
models/AnimalProgressUpdateFieldValue.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management\models;
|
||||
|
||||
use humhub\components\ActiveRecord;
|
||||
use humhub\modules\rescue_foundation\models\RescueFieldDefinition;
|
||||
|
||||
class AnimalProgressUpdateFieldValue extends ActiveRecord
|
||||
{
|
||||
public static function tableName()
|
||||
{
|
||||
return 'rescue_animal_progress_update_field_value';
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
[['progress_update_id', 'field_definition_id'], 'required'],
|
||||
[['progress_update_id', 'field_definition_id'], 'integer'],
|
||||
[['value_text', 'value_json'], 'string'],
|
||||
[['progress_update_id', 'field_definition_id'], 'unique', 'targetAttribute' => ['progress_update_id', 'field_definition_id']],
|
||||
];
|
||||
}
|
||||
|
||||
public function beforeSave($insert)
|
||||
{
|
||||
if (!parent::beforeSave($insert)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$now = date('Y-m-d H:i:s');
|
||||
if ($insert && empty($this->created_at)) {
|
||||
$this->created_at = $now;
|
||||
}
|
||||
|
||||
$this->updated_at = $now;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getProgressUpdate()
|
||||
{
|
||||
return $this->hasOne(AnimalProgressUpdate::class, ['id' => 'progress_update_id']);
|
||||
}
|
||||
|
||||
public function getFieldDefinition()
|
||||
{
|
||||
return $this->hasOne(RescueFieldDefinition::class, ['id' => 'field_definition_id']);
|
||||
}
|
||||
}
|
||||
101
models/AnimalStreamEntry.php
Normal file
101
models/AnimalStreamEntry.php
Normal file
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management\models;
|
||||
|
||||
use humhub\modules\animal_management\widgets\stream\AnimalStreamEntryWallEntry;
|
||||
use humhub\modules\content\components\ContentActiveRecord;
|
||||
use Yii;
|
||||
|
||||
class AnimalStreamEntry extends ContentActiveRecord
|
||||
{
|
||||
public const TYPE_MEDICAL = 'medical';
|
||||
public const TYPE_PROGRESS = 'progress';
|
||||
|
||||
public $wallEntryClass = AnimalStreamEntryWallEntry::class;
|
||||
public $moduleId = 'animal_management';
|
||||
|
||||
public static function tableName()
|
||||
{
|
||||
return 'rescue_animal_stream_entry';
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
[['animal_id', 'entry_type'], 'required'],
|
||||
[['animal_id', 'medical_visit_id', 'progress_update_id'], 'integer'],
|
||||
[['entry_type'], 'string', 'max' => 32],
|
||||
[['entry_type'], 'in', 'range' => [self::TYPE_MEDICAL, self::TYPE_PROGRESS]],
|
||||
[['medical_visit_id', 'progress_update_id'], 'validateLinkedRecord'],
|
||||
];
|
||||
}
|
||||
|
||||
public function beforeSave($insert)
|
||||
{
|
||||
if (!parent::beforeSave($insert)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$now = date('Y-m-d H:i:s');
|
||||
if ($insert && empty($this->created_at)) {
|
||||
$this->created_at = $now;
|
||||
}
|
||||
|
||||
$this->updated_at = $now;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function validateLinkedRecord(string $attribute, $params): void
|
||||
{
|
||||
if ((string)$this->entry_type === self::TYPE_MEDICAL) {
|
||||
if ((int)$this->medical_visit_id <= 0) {
|
||||
$this->addError('medical_visit_id', Yii::t('AnimalManagementModule.base', 'Medical visit reference is required.'));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ((string)$this->entry_type === self::TYPE_PROGRESS) {
|
||||
if ((int)$this->progress_update_id <= 0) {
|
||||
$this->addError('progress_update_id', Yii::t('AnimalManagementModule.base', 'Progress update reference is required.'));
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public function getAnimal()
|
||||
{
|
||||
return $this->hasOne(Animal::class, ['id' => 'animal_id']);
|
||||
}
|
||||
|
||||
public function getMedicalVisit()
|
||||
{
|
||||
return $this->hasOne(AnimalMedicalVisit::class, ['id' => 'medical_visit_id']);
|
||||
}
|
||||
|
||||
public function getProgressUpdate()
|
||||
{
|
||||
return $this->hasOne(AnimalProgressUpdate::class, ['id' => 'progress_update_id']);
|
||||
}
|
||||
|
||||
public function getIcon()
|
||||
{
|
||||
return 'fa-paw';
|
||||
}
|
||||
|
||||
public function getContentName()
|
||||
{
|
||||
return Yii::t('AnimalManagementModule.base', 'Animal stream update');
|
||||
}
|
||||
|
||||
public function getContentDescription()
|
||||
{
|
||||
$animal = $this->animal;
|
||||
$animalName = $animal instanceof Animal ? $animal->getDisplayName() : Yii::t('AnimalManagementModule.base', 'Animal');
|
||||
|
||||
if ((string)$this->entry_type === self::TYPE_MEDICAL) {
|
||||
return Yii::t('AnimalManagementModule.base', 'Medical visit for {animal}', ['{animal}' => $animalName]);
|
||||
}
|
||||
|
||||
return Yii::t('AnimalManagementModule.base', 'Progress update for {animal}', ['{animal}' => $animalName]);
|
||||
}
|
||||
}
|
||||
158
models/AnimalTransfer.php
Normal file
158
models/AnimalTransfer.php
Normal file
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management\models;
|
||||
|
||||
use humhub\components\ActiveRecord;
|
||||
use humhub\modules\space\models\Space;
|
||||
use Yii;
|
||||
|
||||
class AnimalTransfer extends ActiveRecord
|
||||
{
|
||||
public const STATUS_REQUESTED = 'requested';
|
||||
public const STATUS_ACCEPTED = 'accepted';
|
||||
public const STATUS_DECLINED = 'declined';
|
||||
public const STATUS_COMPLETED = 'completed';
|
||||
public const STATUS_CANCELLED = 'cancelled';
|
||||
|
||||
public static function tableName()
|
||||
{
|
||||
return 'rescue_animal_transfer';
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
[['animal_id', 'from_contentcontainer_id', 'to_contentcontainer_id', 'status'], 'required'],
|
||||
[['animal_id', 'from_contentcontainer_id', 'to_contentcontainer_id', 'requested_by'], 'integer'],
|
||||
[['request_message', 'conditions_text'], 'string'],
|
||||
[['status'], 'string', 'max' => 32],
|
||||
[['status'], 'in', 'range' => array_keys(self::statusOptions())],
|
||||
];
|
||||
}
|
||||
|
||||
public function beforeSave($insert)
|
||||
{
|
||||
if (!parent::beforeSave($insert)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$now = date('Y-m-d H:i:s');
|
||||
if ($insert && empty($this->created_at)) {
|
||||
$this->created_at = $now;
|
||||
}
|
||||
|
||||
$this->updated_at = $now;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function statusOptions(): array
|
||||
{
|
||||
return [
|
||||
self::STATUS_REQUESTED => Yii::t('AnimalManagementModule.base', 'Requested'),
|
||||
self::STATUS_ACCEPTED => Yii::t('AnimalManagementModule.base', 'Accepted'),
|
||||
self::STATUS_DECLINED => Yii::t('AnimalManagementModule.base', 'Declined'),
|
||||
self::STATUS_COMPLETED => Yii::t('AnimalManagementModule.base', 'Completed'),
|
||||
self::STATUS_CANCELLED => Yii::t('AnimalManagementModule.base', 'Cancelled'),
|
||||
];
|
||||
}
|
||||
|
||||
public function getAnimal()
|
||||
{
|
||||
return $this->hasOne(Animal::class, ['id' => 'animal_id']);
|
||||
}
|
||||
|
||||
public function getFromSpace(): ?Space
|
||||
{
|
||||
return Space::findOne(['contentcontainer_id' => $this->from_contentcontainer_id]);
|
||||
}
|
||||
|
||||
public function getToSpace(): ?Space
|
||||
{
|
||||
return Space::findOne(['contentcontainer_id' => $this->to_contentcontainer_id]);
|
||||
}
|
||||
|
||||
public function getAuditEvents()
|
||||
{
|
||||
return $this->hasMany(AnimalTransferEvent::class, ['transfer_id' => 'id']);
|
||||
}
|
||||
|
||||
public function markAccepted(): bool
|
||||
{
|
||||
$fromStatus = $this->status;
|
||||
$this->status = self::STATUS_ACCEPTED;
|
||||
$this->responded_at = date('Y-m-d H:i:s');
|
||||
if (!$this->save(false, ['status', 'responded_at', 'updated_at'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AnimalTransferEvent::log(
|
||||
$this,
|
||||
AnimalTransferEvent::EVENT_ACCEPTED,
|
||||
$fromStatus,
|
||||
$this->status,
|
||||
Yii::t('AnimalManagementModule.base', 'Transfer request accepted.')
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function markDeclined(): bool
|
||||
{
|
||||
$fromStatus = $this->status;
|
||||
$this->status = self::STATUS_DECLINED;
|
||||
$this->responded_at = date('Y-m-d H:i:s');
|
||||
if (!$this->save(false, ['status', 'responded_at', 'updated_at'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AnimalTransferEvent::log(
|
||||
$this,
|
||||
AnimalTransferEvent::EVENT_DECLINED,
|
||||
$fromStatus,
|
||||
$this->status,
|
||||
Yii::t('AnimalManagementModule.base', 'Transfer request declined.')
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function markCompleted(): bool
|
||||
{
|
||||
$fromStatus = $this->status;
|
||||
$this->status = self::STATUS_COMPLETED;
|
||||
$this->completed_at = date('Y-m-d H:i:s');
|
||||
if (!$this->save(false, ['status', 'completed_at', 'updated_at'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AnimalTransferEvent::log(
|
||||
$this,
|
||||
AnimalTransferEvent::EVENT_COMPLETED,
|
||||
$fromStatus,
|
||||
$this->status,
|
||||
Yii::t('AnimalManagementModule.base', 'Transfer completed.')
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function markCancelled(string $message = ''): bool
|
||||
{
|
||||
$fromStatus = $this->status;
|
||||
$this->status = self::STATUS_CANCELLED;
|
||||
|
||||
if (!$this->save(false, ['status', 'updated_at'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AnimalTransferEvent::log(
|
||||
$this,
|
||||
AnimalTransferEvent::EVENT_CANCELLED,
|
||||
$fromStatus,
|
||||
$this->status,
|
||||
$message !== '' ? $message : Yii::t('AnimalManagementModule.base', 'Transfer cancelled.')
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
82
models/AnimalTransferEvent.php
Normal file
82
models/AnimalTransferEvent.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management\models;
|
||||
|
||||
use humhub\components\ActiveRecord;
|
||||
use humhub\modules\user\models\User;
|
||||
use Yii;
|
||||
use yii\helpers\Json;
|
||||
|
||||
class AnimalTransferEvent extends ActiveRecord
|
||||
{
|
||||
public const EVENT_REQUESTED = 'requested';
|
||||
public const EVENT_ACCEPTED = 'accepted';
|
||||
public const EVENT_DECLINED = 'declined';
|
||||
public const EVENT_COMPLETED = 'completed';
|
||||
public const EVENT_CANCELLED = 'cancelled';
|
||||
|
||||
public static function tableName()
|
||||
{
|
||||
return 'rescue_animal_transfer_event';
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
[['transfer_id', 'animal_id', 'event_type'], 'required'],
|
||||
[['transfer_id', 'animal_id', 'created_by'], 'integer'],
|
||||
[['message', 'metadata_json'], 'string'],
|
||||
[['event_type', 'from_status', 'to_status'], 'string', 'max' => 32],
|
||||
[['event_type'], 'in', 'range' => [
|
||||
self::EVENT_REQUESTED,
|
||||
self::EVENT_ACCEPTED,
|
||||
self::EVENT_DECLINED,
|
||||
self::EVENT_COMPLETED,
|
||||
self::EVENT_CANCELLED,
|
||||
]],
|
||||
];
|
||||
}
|
||||
|
||||
public function beforeSave($insert)
|
||||
{
|
||||
if (!$insert) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!parent::beforeSave($insert)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (empty($this->created_at)) {
|
||||
$this->created_at = date('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function log(
|
||||
AnimalTransfer $transfer,
|
||||
string $eventType,
|
||||
?string $fromStatus,
|
||||
?string $toStatus,
|
||||
string $message = '',
|
||||
array $metadata = []
|
||||
): bool {
|
||||
$event = new self();
|
||||
$event->transfer_id = (int)$transfer->id;
|
||||
$event->animal_id = (int)$transfer->animal_id;
|
||||
$event->event_type = $eventType;
|
||||
$event->from_status = $fromStatus;
|
||||
$event->to_status = $toStatus;
|
||||
$event->message = $message;
|
||||
$event->metadata_json = empty($metadata) ? null : Json::encode($metadata);
|
||||
$event->created_by = Yii::$app->user->isGuest ? null : (int)Yii::$app->user->id;
|
||||
|
||||
return $event->save(false);
|
||||
}
|
||||
|
||||
public function getCreatedByUser()
|
||||
{
|
||||
return $this->hasOne(User::class, ['id' => 'created_by']);
|
||||
}
|
||||
}
|
||||
1024
models/forms/AnimalForm.php
Normal file
1024
models/forms/AnimalForm.php
Normal file
File diff suppressed because it is too large
Load Diff
346
models/forms/AnimalMedicalVisitForm.php
Normal file
346
models/forms/AnimalMedicalVisitForm.php
Normal file
@@ -0,0 +1,346 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management\models\forms;
|
||||
|
||||
use humhub\modules\animal_management\models\Animal;
|
||||
use humhub\modules\animal_management\models\AnimalMedicalVisit;
|
||||
use humhub\modules\animal_management\models\AnimalMedicalVisitFieldValue;
|
||||
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 AnimalMedicalVisitForm extends Model
|
||||
{
|
||||
public const MODULE_ID = 'animal_management';
|
||||
|
||||
public Animal $animal;
|
||||
|
||||
public string $visit_at = '';
|
||||
public string $provider_name = '';
|
||||
public string $notes = '';
|
||||
public string $recommendations = '';
|
||||
public bool $post_to_space_feed = false;
|
||||
public bool $post_to_animal_feed = true;
|
||||
public array $customFields = [];
|
||||
public ?AnimalMedicalVisit $medicalVisit = null;
|
||||
|
||||
private array $customFieldDefinitions = [];
|
||||
|
||||
public function init()
|
||||
{
|
||||
parent::init();
|
||||
$this->bootstrapCustomFieldDefinitions();
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
[['visit_at', 'provider_name', 'notes', 'recommendations'], 'safe'],
|
||||
[['provider_name'], 'string', 'max' => 190],
|
||||
[['notes', 'recommendations'], 'string'],
|
||||
[['post_to_space_feed', 'post_to_animal_feed'], 'boolean'],
|
||||
[['customFields'], 'safe'],
|
||||
[['customFields'], 'validateCustomFieldValues'],
|
||||
];
|
||||
}
|
||||
|
||||
public function save(): bool
|
||||
{
|
||||
if (!$this->validate()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$record = $this->medicalVisit instanceof AnimalMedicalVisit
|
||||
? $this->medicalVisit
|
||||
: new AnimalMedicalVisit();
|
||||
|
||||
$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->visit_at = $this->normalizeDateTime($this->visit_at);
|
||||
$record->provider_name = trim($this->provider_name);
|
||||
$record->notes = trim($this->notes);
|
||||
$record->recommendations = trim($this->recommendations);
|
||||
if ($record->hasAttribute('post_to_space_feed') && $record->hasAttribute('post_to_animal_feed')) {
|
||||
$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->medicalVisit = $record;
|
||||
|
||||
if (!$this->saveCustomFieldValues($record)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->post_to_space_feed) {
|
||||
AnimalStreamPublisherService::publishMedicalVisit($this->animal, $record);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function setMedicalVisit(AnimalMedicalVisit $medicalVisit): void
|
||||
{
|
||||
$this->medicalVisit = $medicalVisit;
|
||||
$this->visit_at = (string)$medicalVisit->visit_at;
|
||||
$this->provider_name = (string)$medicalVisit->provider_name;
|
||||
$this->notes = (string)$medicalVisit->notes;
|
||||
$this->recommendations = (string)$medicalVisit->recommendations;
|
||||
if ($medicalVisit->hasAttribute('post_to_space_feed') && $medicalVisit->hasAttribute('post_to_animal_feed')) {
|
||||
$this->post_to_space_feed = ((int)$medicalVisit->post_to_space_feed) === 1;
|
||||
$this->post_to_animal_feed = ((int)$medicalVisit->post_to_animal_feed) === 1;
|
||||
} else {
|
||||
$this->post_to_space_feed = false;
|
||||
$this->post_to_animal_feed = true;
|
||||
}
|
||||
|
||||
$this->loadCustomFieldValues($medicalVisit);
|
||||
}
|
||||
|
||||
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_medical_visit', '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(AnimalMedicalVisit $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 = AnimalMedicalVisitFieldValue::findOne([
|
||||
'medical_visit_id' => (int)$record->id,
|
||||
'field_definition_id' => $fieldDefinitionId,
|
||||
]);
|
||||
|
||||
if ($this->isCustomValueEmpty($valueText, $inputType)) {
|
||||
if ($valueRecord instanceof AnimalMedicalVisitFieldValue) {
|
||||
$valueRecord->delete();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$valueRecord instanceof AnimalMedicalVisitFieldValue) {
|
||||
$valueRecord = new AnimalMedicalVisitFieldValue();
|
||||
$valueRecord->medical_visit_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 medical 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(AnimalMedicalVisit $record): void
|
||||
{
|
||||
foreach ($this->customFieldDefinitions as $fieldKey => $definition) {
|
||||
$value = AnimalMedicalVisitFieldValue::findOne([
|
||||
'medical_visit_id' => (int)$record->id,
|
||||
'field_definition_id' => (int)$definition['id'],
|
||||
]);
|
||||
|
||||
if (!$value instanceof AnimalMedicalVisitFieldValue) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->customFields[$fieldKey] = (string)$value->value_text;
|
||||
}
|
||||
}
|
||||
}
|
||||
348
models/forms/AnimalProgressUpdateForm.php
Normal file
348
models/forms/AnimalProgressUpdateForm.php
Normal file
@@ -0,0 +1,348 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
}
|
||||
155
models/forms/DisplaySettingsForm.php
Normal file
155
models/forms/DisplaySettingsForm.php
Normal file
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management\models\forms;
|
||||
|
||||
use humhub\modules\content\components\ContentContainerActiveRecord;
|
||||
use Yii;
|
||||
use yii\base\Model;
|
||||
use yii\helpers\Json;
|
||||
|
||||
class DisplaySettingsForm extends Model
|
||||
{
|
||||
public const DEFAULT_SEARCH_BLOCK_HEADING = 'Search Animal Profiles';
|
||||
|
||||
public const DEFAULT_TILE_FIELDS = [
|
||||
'name',
|
||||
'species',
|
||||
'status',
|
||||
];
|
||||
|
||||
public const DEFAULT_DETAIL_FIELDS = [
|
||||
'species',
|
||||
'breed',
|
||||
'sex',
|
||||
'status',
|
||||
'location_name',
|
||||
];
|
||||
|
||||
public ?ContentContainerActiveRecord $contentContainer = null;
|
||||
|
||||
public string $search_block_heading = self::DEFAULT_SEARCH_BLOCK_HEADING;
|
||||
public array $tile_fields = self::DEFAULT_TILE_FIELDS;
|
||||
public array $detail_fields = self::DEFAULT_DETAIL_FIELDS;
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
[['search_block_heading'], 'string', 'max' => 190],
|
||||
[['tile_fields', 'detail_fields'], 'each', 'rule' => ['in', 'range' => array_keys(self::fieldOptions())]],
|
||||
[['tile_fields', 'detail_fields'], 'default', 'value' => []],
|
||||
];
|
||||
}
|
||||
|
||||
public function attributeLabels()
|
||||
{
|
||||
return [
|
||||
'search_block_heading' => Yii::t('AnimalManagementModule.base', 'Search Block Heading'),
|
||||
'tile_fields' => Yii::t('AnimalManagementModule.base', 'Animal Tile Fields'),
|
||||
'detail_fields' => Yii::t('AnimalManagementModule.base', 'Animal Detail Hero Fields'),
|
||||
];
|
||||
}
|
||||
|
||||
public function loadValues(): void
|
||||
{
|
||||
if ($this->contentContainer === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$settings = Yii::$app->getModule('animal_management')->settings->contentContainer($this->contentContainer);
|
||||
|
||||
$heading = trim((string)$settings->get('searchBlockHeading', self::DEFAULT_SEARCH_BLOCK_HEADING));
|
||||
$this->search_block_heading = $heading !== '' ? $heading : self::DEFAULT_SEARCH_BLOCK_HEADING;
|
||||
|
||||
$this->tile_fields = $this->normalizeFields(
|
||||
$settings->get('tileFields', Json::encode(self::DEFAULT_TILE_FIELDS)),
|
||||
self::DEFAULT_TILE_FIELDS
|
||||
);
|
||||
|
||||
$this->detail_fields = $this->normalizeFields(
|
||||
$settings->get('detailHeroFields', Json::encode(self::DEFAULT_DETAIL_FIELDS)),
|
||||
self::DEFAULT_DETAIL_FIELDS
|
||||
);
|
||||
}
|
||||
|
||||
public function save(): bool
|
||||
{
|
||||
if ($this->contentContainer === null) {
|
||||
$this->addError('search_block_heading', Yii::t('AnimalManagementModule.base', 'Missing content container.'));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$this->validate()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$settings = Yii::$app->getModule('animal_management')->settings->contentContainer($this->contentContainer);
|
||||
|
||||
$heading = trim($this->search_block_heading);
|
||||
$settings->set('searchBlockHeading', $heading !== '' ? $heading : self::DEFAULT_SEARCH_BLOCK_HEADING);
|
||||
$settings->set('tileFields', Json::encode($this->normalizeFieldArray($this->tile_fields, self::DEFAULT_TILE_FIELDS)));
|
||||
$settings->set('detailHeroFields', Json::encode($this->normalizeFieldArray($this->detail_fields, self::DEFAULT_DETAIL_FIELDS)));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function fieldOptions(): array
|
||||
{
|
||||
return [
|
||||
'name' => Yii::t('AnimalManagementModule.base', 'Name'),
|
||||
'species' => Yii::t('AnimalManagementModule.base', 'Species'),
|
||||
'breed' => Yii::t('AnimalManagementModule.base', 'Breed'),
|
||||
'sex' => Yii::t('AnimalManagementModule.base', 'Sex'),
|
||||
'status' => Yii::t('AnimalManagementModule.base', 'Status'),
|
||||
'location_name' => Yii::t('AnimalManagementModule.base', 'Location'),
|
||||
'animal_uid' => Yii::t('AnimalManagementModule.base', 'ID'),
|
||||
'last_medical' => Yii::t('AnimalManagementModule.base', 'Last Medical Visit'),
|
||||
'public_summary' => Yii::t('AnimalManagementModule.base', 'Public Summary'),
|
||||
];
|
||||
}
|
||||
|
||||
private function normalizeFields($raw, array $default): array
|
||||
{
|
||||
$decoded = [];
|
||||
if (is_string($raw)) {
|
||||
$raw = trim($raw);
|
||||
if ($raw !== '') {
|
||||
try {
|
||||
$decoded = Json::decode($raw);
|
||||
} catch (\Throwable $e) {
|
||||
$decoded = [];
|
||||
}
|
||||
}
|
||||
} elseif (is_array($raw)) {
|
||||
$decoded = $raw;
|
||||
}
|
||||
|
||||
if (!is_array($decoded)) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
return $this->normalizeFieldArray($decoded, $default);
|
||||
}
|
||||
|
||||
private function normalizeFieldArray(array $fields, array $default): array
|
||||
{
|
||||
$normalized = [];
|
||||
$options = self::fieldOptions();
|
||||
|
||||
foreach ($fields as $field) {
|
||||
$field = trim((string)$field);
|
||||
if ($field === '' || !isset($options[$field])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!in_array($field, $normalized, true)) {
|
||||
$normalized[] = $field;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($normalized)) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
return $normalized;
|
||||
}
|
||||
}
|
||||
335
models/forms/FieldDefinitionSettingsForm.php
Normal file
335
models/forms/FieldDefinitionSettingsForm.php
Normal file
@@ -0,0 +1,335 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management\models\forms;
|
||||
|
||||
use humhub\modules\rescue_foundation\models\RescueFieldDefinition;
|
||||
use Yii;
|
||||
use yii\base\Model;
|
||||
|
||||
class FieldDefinitionSettingsForm extends Model
|
||||
{
|
||||
public const MODULE_ID = 'animal_management';
|
||||
|
||||
public array $rows = [];
|
||||
|
||||
public string $new_field_key = '';
|
||||
public string $new_label = '';
|
||||
public string $new_input_type = 'text';
|
||||
public string $new_group_key = 'animal_profile';
|
||||
public string $new_visibility = 'public';
|
||||
public int $new_required = 0;
|
||||
public int $new_sort_order = 500;
|
||||
public string $new_options = '';
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
[['rows'], 'safe'],
|
||||
[['new_field_key', 'new_label', 'new_group_key', 'new_visibility', 'new_input_type', 'new_options'], 'string'],
|
||||
[['new_required', 'new_sort_order'], 'integer'],
|
||||
];
|
||||
}
|
||||
|
||||
public static function inputTypeOptions(): array
|
||||
{
|
||||
return [
|
||||
'text' => 'Text',
|
||||
'textarea' => 'Textarea',
|
||||
'boolean' => 'Boolean',
|
||||
'select' => 'Select',
|
||||
'number' => 'Number',
|
||||
'date' => 'Date',
|
||||
'datetime' => 'Date/Time',
|
||||
];
|
||||
}
|
||||
|
||||
public static function visibilityOptions(): array
|
||||
{
|
||||
return [
|
||||
'public' => 'Public',
|
||||
'private' => 'Private',
|
||||
'internal' => 'Internal',
|
||||
];
|
||||
}
|
||||
|
||||
public function loadRows(): void
|
||||
{
|
||||
if (!$this->canUseFieldDefinition()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->rows = [];
|
||||
|
||||
$definitions = RescueFieldDefinition::find()
|
||||
->where(['module_id' => self::MODULE_ID])
|
||||
->orderBy(['group_key' => SORT_ASC, 'sort_order' => SORT_ASC, 'id' => SORT_ASC])
|
||||
->all();
|
||||
|
||||
foreach ($definitions as $definition) {
|
||||
$this->rows[] = [
|
||||
'id' => (int)$definition->id,
|
||||
'field_key' => (string)$definition->field_key,
|
||||
'label' => (string)$definition->label,
|
||||
'group_key' => (string)$definition->group_key,
|
||||
'input_type' => (string)$definition->input_type,
|
||||
'required' => (int)$definition->required,
|
||||
'is_core' => (int)$definition->is_core,
|
||||
'is_active' => (int)$definition->is_active,
|
||||
'visibility' => (string)$definition->visibility,
|
||||
'sort_order' => (int)$definition->sort_order,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
public function save(): bool
|
||||
{
|
||||
if (!$this->canUseFieldDefinition()) {
|
||||
$this->addError('rows', Yii::t('AnimalManagementModule.base', 'Field definition storage is unavailable.'));
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->clearValidationErrors();
|
||||
|
||||
$byId = [];
|
||||
foreach (RescueFieldDefinition::find()->where(['module_id' => self::MODULE_ID])->all() as $definition) {
|
||||
$byId[(int)$definition->id] = $definition;
|
||||
}
|
||||
|
||||
$transaction = Yii::$app->db->beginTransaction();
|
||||
|
||||
try {
|
||||
$now = date('Y-m-d H:i:s');
|
||||
|
||||
foreach ($this->rows as $index => $row) {
|
||||
$id = (int)($row['id'] ?? 0);
|
||||
if ($id === 0 || !isset($byId[$id])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$definition = $byId[$id];
|
||||
$isCore = ((int)$definition->is_core) === 1;
|
||||
$remove = !empty($row['remove']);
|
||||
|
||||
if ($remove && !$isCore) {
|
||||
if (!$definition->delete()) {
|
||||
$this->addError('rows', Yii::t('AnimalManagementModule.base', 'Could not remove field {field}.', [
|
||||
'{field}' => (string)$definition->field_key,
|
||||
]));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$label = trim((string)($row['label'] ?? ''));
|
||||
$groupKey = trim((string)($row['group_key'] ?? $definition->group_key));
|
||||
$visibility = trim((string)($row['visibility'] ?? $definition->visibility));
|
||||
$sortOrder = (int)($row['sort_order'] ?? $definition->sort_order);
|
||||
$required = !empty($row['required']) ? 1 : 0;
|
||||
$isActive = !empty($row['is_active']) ? 1 : 0;
|
||||
$inputType = trim((string)($row['input_type'] ?? $definition->input_type));
|
||||
$options = trim((string)$definition->options);
|
||||
|
||||
if ($label === '') {
|
||||
$this->addError('rows', Yii::t('AnimalManagementModule.base', 'Field label cannot be empty for {field}.', [
|
||||
'{field}' => (string)$definition->field_key,
|
||||
]));
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($groupKey === '') {
|
||||
$this->addError('rows', Yii::t('AnimalManagementModule.base', 'Group key cannot be empty for {field}.', [
|
||||
'{field}' => (string)$definition->field_key,
|
||||
]));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!array_key_exists($visibility, self::visibilityOptions())) {
|
||||
$this->addError('rows', Yii::t('AnimalManagementModule.base', 'Invalid visibility for {field}.', [
|
||||
'{field}' => (string)$definition->field_key,
|
||||
]));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$isCore && !$this->validateSelectOptions($inputType, $options, (string)$definition->field_key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$definition->label = $label;
|
||||
$definition->group_key = $groupKey;
|
||||
$definition->visibility = $visibility;
|
||||
$definition->sort_order = $sortOrder;
|
||||
$definition->required = $isCore ? (int)$definition->required : $required;
|
||||
$definition->is_active = $isCore ? (int)$definition->is_active : $isActive;
|
||||
$definition->updated_at = $now;
|
||||
|
||||
if (!$definition->save()) {
|
||||
$this->addError('rows', Yii::t('AnimalManagementModule.base', 'Could not save field {field}.', [
|
||||
'{field}' => (string)$definition->field_key,
|
||||
]));
|
||||
foreach ($definition->getFirstErrors() as $error) {
|
||||
$this->addError('rows', $error);
|
||||
}
|
||||
}
|
||||
|
||||
$this->rows[$index]['is_core'] = (int)$definition->is_core;
|
||||
}
|
||||
|
||||
if ($this->hasErrors()) {
|
||||
$transaction->rollBack();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$this->saveNewField($now)) {
|
||||
$transaction->rollBack();
|
||||
return false;
|
||||
}
|
||||
|
||||
$transaction->commit();
|
||||
return true;
|
||||
} catch (\Throwable $e) {
|
||||
$transaction->rollBack();
|
||||
Yii::error($e, 'animal_management.field_definition_settings');
|
||||
$this->addError('rows', Yii::t('AnimalManagementModule.base', 'Unexpected error while saving field settings.'));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function canUseFieldDefinition(): bool
|
||||
{
|
||||
if (!class_exists(RescueFieldDefinition::class)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Yii::$app->db->schema->getTableSchema('rescue_field_definition', true) !== null;
|
||||
}
|
||||
|
||||
private function saveNewField(string $now): bool
|
||||
{
|
||||
$newFieldKey = strtolower(trim($this->new_field_key));
|
||||
$newLabel = trim($this->new_label);
|
||||
|
||||
if ($newFieldKey === '' && $newLabel === '') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($newFieldKey === '' || $newLabel === '') {
|
||||
$this->addError('rows', Yii::t('AnimalManagementModule.base', 'Both new field key and label are required.'));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!preg_match('/^[a-z0-9_]+$/', $newFieldKey)) {
|
||||
$this->addError('rows', Yii::t('AnimalManagementModule.base', 'New field key must contain only lowercase letters, numbers, and underscores.'));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!array_key_exists($this->new_input_type, self::inputTypeOptions())) {
|
||||
$this->addError('rows', Yii::t('AnimalManagementModule.base', 'Invalid input type for new field.'));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!array_key_exists($this->new_visibility, self::visibilityOptions())) {
|
||||
$this->addError('rows', Yii::t('AnimalManagementModule.base', 'Invalid visibility for new field.'));
|
||||
return false;
|
||||
}
|
||||
|
||||
$groupKey = trim($this->new_group_key);
|
||||
if ($groupKey === '') {
|
||||
$this->addError('rows', Yii::t('AnimalManagementModule.base', 'New field group key is required.'));
|
||||
return false;
|
||||
}
|
||||
|
||||
$options = trim($this->new_options);
|
||||
if (!$this->validateSelectOptions($this->new_input_type, $options, $newFieldKey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$exists = RescueFieldDefinition::find()
|
||||
->where(['module_id' => self::MODULE_ID, 'field_key' => $newFieldKey])
|
||||
->exists();
|
||||
|
||||
if ($exists) {
|
||||
$this->addError('rows', Yii::t('AnimalManagementModule.base', 'Field key {key} already exists.', ['{key}' => $newFieldKey]));
|
||||
return false;
|
||||
}
|
||||
|
||||
$field = new RescueFieldDefinition();
|
||||
$field->module_id = self::MODULE_ID;
|
||||
$field->group_key = $groupKey;
|
||||
$field->field_key = $newFieldKey;
|
||||
$field->label = $newLabel;
|
||||
$field->input_type = $this->new_input_type;
|
||||
$field->required = $this->new_required ? 1 : 0;
|
||||
$field->is_core = 0;
|
||||
$field->is_active = 1;
|
||||
$field->visibility = $this->new_visibility;
|
||||
$field->sort_order = (int)$this->new_sort_order;
|
||||
$field->options = $options !== '' ? $options : '{}';
|
||||
$field->created_at = $now;
|
||||
$field->updated_at = $now;
|
||||
|
||||
if (!$field->save()) {
|
||||
$this->addError('rows', Yii::t('AnimalManagementModule.base', 'Could not create new field.'));
|
||||
foreach ($field->getFirstErrors() as $error) {
|
||||
$this->addError('rows', $error);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->new_field_key = '';
|
||||
$this->new_label = '';
|
||||
$this->new_input_type = 'text';
|
||||
$this->new_group_key = 'animal_profile';
|
||||
$this->new_visibility = 'public';
|
||||
$this->new_required = 0;
|
||||
$this->new_sort_order = 500;
|
||||
$this->new_options = '';
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function clearValidationErrors(): void
|
||||
{
|
||||
foreach (array_keys($this->errors) as $attribute) {
|
||||
$this->clearErrors($attribute);
|
||||
}
|
||||
}
|
||||
|
||||
private function validateSelectOptions(string $inputType, string $options, string $fieldKey): bool
|
||||
{
|
||||
if ($inputType !== 'select') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($options === '' || $options === '{}') {
|
||||
$this->addError('rows', Yii::t('AnimalManagementModule.base', 'Select field {field} requires options JSON or comma/newline list.', [
|
||||
'{field}' => $fieldKey,
|
||||
]));
|
||||
return false;
|
||||
}
|
||||
|
||||
$decoded = json_decode($options, true);
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
if (!is_array($decoded) || empty($decoded)) {
|
||||
$this->addError('rows', Yii::t('AnimalManagementModule.base', 'Select field {field} options JSON must be a non-empty array/object.', [
|
||||
'{field}' => $fieldKey,
|
||||
]));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$parts = preg_split('/[\r\n,]+/', $options);
|
||||
$parts = array_filter(array_map(static function ($item) {
|
||||
return trim((string)$item);
|
||||
}, $parts));
|
||||
|
||||
if (empty($parts)) {
|
||||
$this->addError('rows', Yii::t('AnimalManagementModule.base', 'Select field {field} requires at least one option.', [
|
||||
'{field}' => $fieldKey,
|
||||
]));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
165
models/forms/TransferRequestForm.php
Normal file
165
models/forms/TransferRequestForm.php
Normal file
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management\models\forms;
|
||||
|
||||
use humhub\modules\animal_management\models\Animal;
|
||||
use humhub\modules\animal_management\models\AnimalTransfer;
|
||||
use humhub\modules\animal_management\models\AnimalTransferEvent;
|
||||
use humhub\modules\animal_management\notifications\TransferNotifier;
|
||||
use humhub\modules\animal_management\notifications\TransferRequestedNotification;
|
||||
use humhub\modules\space\models\Space;
|
||||
use Yii;
|
||||
use yii\base\Model;
|
||||
|
||||
class TransferRequestForm extends Model
|
||||
{
|
||||
public Animal $animal;
|
||||
public Space $sourceSpace;
|
||||
|
||||
public int $to_space_id = 0;
|
||||
public string $request_message = '';
|
||||
public string $conditions_text = '';
|
||||
|
||||
private ?AnimalTransfer $transfer = null;
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
[['to_space_id'], 'required'],
|
||||
[['to_space_id'], 'integer'],
|
||||
[['request_message', 'conditions_text'], 'string'],
|
||||
[['to_space_id'], 'validateTargetSpace'],
|
||||
];
|
||||
}
|
||||
|
||||
public function validateTargetSpace($attribute): void
|
||||
{
|
||||
if ($this->hasErrors()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->to_space_id === (int)$this->sourceSpace->id) {
|
||||
$this->addError($attribute, Yii::t('AnimalManagementModule.base', 'You must choose a different destination rescue.'));
|
||||
return;
|
||||
}
|
||||
|
||||
$target = Space::findOne(['id' => $this->to_space_id, 'status' => Space::STATUS_ENABLED]);
|
||||
if (!$target instanceof Space) {
|
||||
$this->addError($attribute, Yii::t('AnimalManagementModule.base', 'Selected destination rescue was not found.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function getTargetOptions(): array
|
||||
{
|
||||
return Space::find()
|
||||
->select(['name', 'id'])
|
||||
->where(['status' => Space::STATUS_ENABLED])
|
||||
->andWhere(['!=', 'id', (int)$this->sourceSpace->id])
|
||||
->orderBy(['name' => SORT_ASC])
|
||||
->indexBy('id')
|
||||
->column();
|
||||
}
|
||||
|
||||
public function save(): bool
|
||||
{
|
||||
if (!$this->validate()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$targetSpace = Space::findOne(['id' => $this->to_space_id, 'status' => Space::STATUS_ENABLED]);
|
||||
if (!$targetSpace instanceof Space) {
|
||||
$this->addError('to_space_id', Yii::t('AnimalManagementModule.base', 'Selected destination rescue was not found.'));
|
||||
return false;
|
||||
}
|
||||
|
||||
$transaction = Yii::$app->db->beginTransaction();
|
||||
|
||||
try {
|
||||
$transfer = new AnimalTransfer();
|
||||
$transfer->animal_id = (int)$this->animal->id;
|
||||
$transfer->from_contentcontainer_id = (int)$this->sourceSpace->contentcontainer_id;
|
||||
$transfer->to_contentcontainer_id = (int)$targetSpace->contentcontainer_id;
|
||||
$transfer->requested_by = Yii::$app->user->isGuest ? null : (int)Yii::$app->user->id;
|
||||
$transfer->status = AnimalTransfer::STATUS_REQUESTED;
|
||||
$transfer->request_message = trim($this->request_message);
|
||||
$transfer->conditions_text = trim($this->conditions_text);
|
||||
|
||||
if (!$transfer->save()) {
|
||||
$this->addErrors($transfer->getErrors());
|
||||
$transaction->rollBack();
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->animal->status = Animal::STATUS_TRANSFER_PENDING;
|
||||
if (!$this->animal->save(false, ['status', 'updated_at'])) {
|
||||
$transaction->rollBack();
|
||||
$this->addError('to_space_id', Yii::t('AnimalManagementModule.base', 'Could not update animal transfer status.'));
|
||||
return false;
|
||||
}
|
||||
|
||||
$auditSaved = AnimalTransferEvent::log(
|
||||
$transfer,
|
||||
AnimalTransferEvent::EVENT_REQUESTED,
|
||||
null,
|
||||
AnimalTransfer::STATUS_REQUESTED,
|
||||
Yii::t('AnimalManagementModule.base', 'Transfer requested from {from} to {to}.', [
|
||||
'{from}' => (string)$this->sourceSpace->name,
|
||||
'{to}' => (string)$targetSpace->name,
|
||||
]),
|
||||
[
|
||||
'from_space_id' => (int)$this->sourceSpace->id,
|
||||
'to_space_id' => (int)$targetSpace->id,
|
||||
]
|
||||
);
|
||||
|
||||
if (!$auditSaved) {
|
||||
$transaction->rollBack();
|
||||
$this->addError('to_space_id', Yii::t('AnimalManagementModule.base', 'Could not write transfer audit log.'));
|
||||
return false;
|
||||
}
|
||||
|
||||
$transaction->commit();
|
||||
} catch (\Throwable $e) {
|
||||
$transaction->rollBack();
|
||||
Yii::error($e, 'animal_management.transfer_request');
|
||||
$this->addError('to_space_id', Yii::t('AnimalManagementModule.base', 'Could not create transfer request.'));
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->transfer = $transfer;
|
||||
$this->notifyTargetManagers();
|
||||
return true;
|
||||
}
|
||||
|
||||
private function notifyTargetManagers(): void
|
||||
{
|
||||
$targetSpace = Space::findOne(['id' => $this->to_space_id]);
|
||||
if (!$targetSpace instanceof Space) {
|
||||
return;
|
||||
}
|
||||
|
||||
$recipients = TransferNotifier::privilegedUsersForSpace($targetSpace);
|
||||
|
||||
$originator = Yii::$app->user->isGuest ? null : Yii::$app->user->getIdentity();
|
||||
|
||||
foreach ($recipients as $recipient) {
|
||||
if ($originator instanceof \humhub\modules\user\models\User && (int)$recipient->id === (int)$originator->id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$notification = TransferRequestedNotification::instance();
|
||||
if ($originator) {
|
||||
$notification->from($originator);
|
||||
}
|
||||
$notification->animalName = $this->animal->getDisplayName();
|
||||
$notification->fromSpaceName = (string)$this->sourceSpace->name;
|
||||
$notification->toSpaceGuid = (string)$targetSpace->guid;
|
||||
$notification->payload([
|
||||
'animalName' => $notification->animalName,
|
||||
'fromSpaceName' => $notification->fromSpaceName,
|
||||
'toSpaceGuid' => $notification->toSpaceGuid,
|
||||
]);
|
||||
$notification->send($recipient);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user