1957 lines
78 KiB
PHP
1957 lines
78 KiB
PHP
<?php
|
|
|
|
namespace humhub\modules\animal_management\controllers;
|
|
|
|
use humhub\modules\animal_management\models\Animal;
|
|
use humhub\modules\animal_management\models\AnimalFieldValue;
|
|
use humhub\modules\animal_management\models\AnimalGalleryItem;
|
|
use humhub\modules\animal_management\models\AnimalMedicalVisit;
|
|
use humhub\modules\animal_management\models\AnimalProgressUpdate;
|
|
use humhub\modules\animal_management\models\AnimalTransfer;
|
|
use humhub\modules\animal_management\models\AnimalTransferEvent;
|
|
use humhub\modules\animal_management\models\forms\DisplaySettingsForm;
|
|
use humhub\modules\animal_management\models\forms\AnimalForm;
|
|
use humhub\modules\animal_management\models\forms\AnimalMedicalVisitForm;
|
|
use humhub\modules\animal_management\models\forms\AnimalProgressUpdateForm;
|
|
use humhub\modules\animal_management\models\forms\TransferRequestForm;
|
|
use humhub\modules\animal_management\notifications\TransferNotifier;
|
|
use humhub\modules\animal_management\permissions\ManageAnimals;
|
|
use humhub\modules\animal_management\services\GalleryIntegrationService;
|
|
use humhub\modules\content\components\ContentContainerController;
|
|
use humhub\modules\content\components\ContentContainerControllerAccess;
|
|
use humhub\modules\rescue_foundation\components\UploadStandards;
|
|
use humhub\modules\rescue_foundation\models\RescueFieldDefinition;
|
|
use humhub\modules\space\models\Space;
|
|
use Yii;
|
|
use yii\helpers\FileHelper;
|
|
use yii\filters\VerbFilter;
|
|
use yii\web\ForbiddenHttpException;
|
|
use yii\web\NotFoundHttpException;
|
|
use yii\web\Response;
|
|
use yii\web\UploadedFile;
|
|
|
|
class AnimalsController extends ContentContainerController
|
|
{
|
|
public function behaviors()
|
|
{
|
|
$behaviors = parent::behaviors();
|
|
$behaviors['verbs'] = [
|
|
'class' => VerbFilter::class,
|
|
'actions' => [
|
|
'delete' => ['post'],
|
|
'add-medical-visit' => ['post'],
|
|
'add-progress-update' => ['post'],
|
|
'add-gallery-images' => ['post'],
|
|
'add-gallery-images-inline' => ['get', 'post'],
|
|
'transfer-inline' => ['get', 'post'],
|
|
'remove-gallery-image' => ['post'],
|
|
'transfer-respond' => ['post'],
|
|
'transfer-complete' => ['post'],
|
|
'transfer-cancel' => ['post'],
|
|
],
|
|
];
|
|
|
|
return $behaviors;
|
|
}
|
|
|
|
protected function getAccessRules()
|
|
{
|
|
return [
|
|
[ContentContainerControllerAccess::RULE_USER_GROUP_ONLY => [Space::USERGROUP_OWNER, Space::USERGROUP_ADMIN, Space::USERGROUP_MODERATOR], 'actions' => ['create', 'edit', 'delete', 'transfer', 'transfer-inline', 'add-medical-visit', 'add-medical-visit-inline', 'edit-medical-visit', 'add-progress-update', 'add-progress-update-inline', 'edit-progress-update', 'add-gallery-images', 'add-gallery-images-inline', 'remove-gallery-image', 'transfer-respond', 'transfer-complete', 'transfer-cancel']],
|
|
];
|
|
}
|
|
|
|
public function actionIndex()
|
|
{
|
|
$queryValue = trim((string)Yii::$app->request->get('q', ''));
|
|
$statusFilter = trim((string)Yii::$app->request->get('status', ''));
|
|
$speciesFilter = trim((string)Yii::$app->request->get('species', ''));
|
|
if ($this->contentContainer instanceof Space) {
|
|
GalleryIntegrationService::syncSpaceAnimalGalleries($this->contentContainer);
|
|
}
|
|
$viewMode = trim((string)Yii::$app->request->get('view', 'tiles'));
|
|
if (!in_array($viewMode, ['tiles', 'tiles2', 'rows', 'tablet', 'table'], true)) {
|
|
$viewMode = 'tiles';
|
|
}
|
|
$isFocusModeRequest = trim((string)Yii::$app->request->get('focus', '')) === '1';
|
|
if ($isFocusModeRequest) {
|
|
$viewMode = 'tablet';
|
|
}
|
|
|
|
$sortKey = trim((string)Yii::$app->request->get('sort', 'updated_at'));
|
|
$sortDirection = strtolower(trim((string)Yii::$app->request->get('direction', 'desc'))) === 'asc' ? 'asc' : 'desc';
|
|
|
|
$availableSorts = [
|
|
'animal_uid' => 'animal_uid',
|
|
'name' => 'name',
|
|
'species' => 'species',
|
|
'status' => 'status',
|
|
'updated_at' => 'updated_at',
|
|
'last_medical' => 'last_medical',
|
|
];
|
|
|
|
if (!isset($availableSorts[$sortKey])) {
|
|
$sortKey = 'updated_at';
|
|
}
|
|
|
|
$availableColumns = [
|
|
'animal_uid' => Yii::t('AnimalManagementModule.base', 'ID'),
|
|
'name' => Yii::t('AnimalManagementModule.base', 'Name'),
|
|
'species' => Yii::t('AnimalManagementModule.base', 'Species'),
|
|
'status' => Yii::t('AnimalManagementModule.base', 'Status'),
|
|
'last_medical' => Yii::t('AnimalManagementModule.base', 'Last Medical Visit'),
|
|
'updated_at' => Yii::t('AnimalManagementModule.base', 'Updated'),
|
|
];
|
|
|
|
$defaultColumns = ['animal_uid', 'name', 'species', 'status', 'last_medical', 'updated_at'];
|
|
$colsRaw = Yii::$app->request->get('cols', implode(',', $defaultColumns));
|
|
|
|
if (is_array($colsRaw)) {
|
|
$selectedColumns = array_values(array_intersect(array_keys($availableColumns), $colsRaw));
|
|
} else {
|
|
$selectedColumns = array_values(array_intersect(array_keys($availableColumns), array_map('trim', explode(',', (string)$colsRaw))));
|
|
}
|
|
|
|
if (empty($selectedColumns)) {
|
|
$selectedColumns = $defaultColumns;
|
|
}
|
|
|
|
if (!in_array('name', $selectedColumns, true)) {
|
|
$selectedColumns[] = 'name';
|
|
}
|
|
|
|
$query = Animal::find()
|
|
->where(['contentcontainer_id' => $this->contentContainer->contentcontainer_id]);
|
|
|
|
if ($queryValue !== '') {
|
|
$query->andWhere([
|
|
'or',
|
|
['like', 'animal_uid', $queryValue],
|
|
['like', 'name', $queryValue],
|
|
['like', 'species', $queryValue],
|
|
['like', 'breed', $queryValue],
|
|
]);
|
|
}
|
|
|
|
if ($statusFilter !== '' && isset(Animal::statusOptions()[$statusFilter])) {
|
|
$query->andWhere(['status' => $statusFilter]);
|
|
}
|
|
|
|
if ($speciesFilter !== '') {
|
|
$query->andWhere(['species' => $speciesFilter]);
|
|
}
|
|
|
|
if ($sortKey !== 'last_medical') {
|
|
$sortColumn = $availableSorts[$sortKey];
|
|
$query->orderBy([
|
|
$sortColumn => $sortDirection === 'asc' ? SORT_ASC : SORT_DESC,
|
|
'id' => SORT_DESC,
|
|
]);
|
|
} else {
|
|
$query->orderBy(['updated_at' => SORT_DESC, 'id' => SORT_DESC]);
|
|
}
|
|
|
|
$animals = $query->all();
|
|
|
|
$speciesOptions = Animal::find()
|
|
->select('species')
|
|
->where(['contentcontainer_id' => $this->contentContainer->contentcontainer_id])
|
|
->andWhere(['not', ['species' => null]])
|
|
->andWhere(['!=', 'species', ''])
|
|
->distinct()
|
|
->orderBy(['species' => SORT_ASC])
|
|
->column();
|
|
|
|
$speciesOptions = array_values(array_filter(array_map(static function ($species) {
|
|
return trim((string)$species);
|
|
}, $speciesOptions)));
|
|
|
|
$latestMedicalVisitByAnimal = [];
|
|
$animalIds = array_map(static function (Animal $animal): int {
|
|
return (int)$animal->id;
|
|
}, $animals);
|
|
|
|
$animalDonationGoalsByAnimal = [];
|
|
$donationGoalClass = 'humhub\\modules\\donations\\models\\DonationGoal';
|
|
if (!empty($animalIds)
|
|
&& $this->contentContainer instanceof Space
|
|
&& $this->contentContainer->moduleManager->isEnabled('donations')
|
|
&& class_exists($donationGoalClass)
|
|
&& Yii::$app->db->schema->getTableSchema($donationGoalClass::tableName(), true) !== null
|
|
) {
|
|
$donationGoals = $donationGoalClass::find()
|
|
->where([
|
|
'contentcontainer_id' => $this->contentContainer->contentcontainer_id,
|
|
'goal_type' => $donationGoalClass::TYPE_ANIMAL,
|
|
])
|
|
->andWhere(['target_animal_id' => $animalIds])
|
|
->orderBy(['is_active' => SORT_DESC, 'id' => SORT_DESC])
|
|
->all();
|
|
|
|
foreach ($donationGoals as $goal) {
|
|
$animalId = (int)$goal->target_animal_id;
|
|
if ($animalId <= 0 || isset($animalDonationGoalsByAnimal[$animalId])) {
|
|
continue;
|
|
}
|
|
|
|
$animalDonationGoalsByAnimal[$animalId] = $goal;
|
|
}
|
|
}
|
|
|
|
$animalImageUrls = $this->resolveAnimalImageUrls($animalIds, ['profile_image_url', 'profile_image', 'photo_url', 'image_url', 'photo'], false);
|
|
$tileFieldOverrides = $this->resolveDisplayFieldOverrides($animalIds, 'tile_display_fields');
|
|
|
|
if (!empty($animalIds)) {
|
|
$visits = AnimalMedicalVisit::find()
|
|
->where(['animal_id' => $animalIds])
|
|
->orderBy(['animal_id' => SORT_ASC, 'visit_at' => SORT_DESC, 'id' => SORT_DESC])
|
|
->all();
|
|
|
|
foreach ($visits as $visit) {
|
|
$animalId = (int)$visit->animal_id;
|
|
if (!isset($latestMedicalVisitByAnimal[$animalId])) {
|
|
$latestMedicalVisitByAnimal[$animalId] = $visit;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($sortKey === 'last_medical') {
|
|
usort($animals, static function (Animal $a, Animal $b) use ($latestMedicalVisitByAnimal, $sortDirection): int {
|
|
$aVisit = $latestMedicalVisitByAnimal[(int)$a->id] ?? null;
|
|
$bVisit = $latestMedicalVisitByAnimal[(int)$b->id] ?? null;
|
|
|
|
$aTs = $aVisit ? strtotime((string)$aVisit->visit_at) : 0;
|
|
$bTs = $bVisit ? strtotime((string)$bVisit->visit_at) : 0;
|
|
if ($aTs === false) {
|
|
$aTs = 0;
|
|
}
|
|
if ($bTs === false) {
|
|
$bTs = 0;
|
|
}
|
|
|
|
$cmp = $aTs <=> $bTs;
|
|
if ($cmp === 0) {
|
|
$cmp = strcmp((string)$a->updated_at, (string)$b->updated_at);
|
|
}
|
|
|
|
return $sortDirection === 'asc' ? $cmp : -$cmp;
|
|
});
|
|
}
|
|
|
|
$incomingTransfers = AnimalTransfer::find()
|
|
->where(['to_contentcontainer_id' => $this->contentContainer->contentcontainer_id])
|
|
->andWhere(['status' => [AnimalTransfer::STATUS_REQUESTED, AnimalTransfer::STATUS_ACCEPTED]])
|
|
->orderBy(['updated_at' => SORT_DESC, 'id' => SORT_DESC])
|
|
->all();
|
|
|
|
$outgoingTransfers = AnimalTransfer::find()
|
|
->where(['from_contentcontainer_id' => $this->contentContainer->contentcontainer_id])
|
|
->orderBy(['updated_at' => SORT_DESC, 'id' => SORT_DESC])
|
|
->limit(30)
|
|
->all();
|
|
|
|
$transferAnimalIds = [];
|
|
foreach (array_merge($incomingTransfers, $outgoingTransfers) as $transfer) {
|
|
if ($transfer instanceof AnimalTransfer) {
|
|
$transferAnimalIds[] = (int)$transfer->animal_id;
|
|
}
|
|
}
|
|
|
|
$transferAnimalImageUrls = $this->resolveAnimalImageUrls(
|
|
$transferAnimalIds,
|
|
['cover_image_url', 'profile_image_url', 'profile_image', 'photo_url', 'image_url', 'photo'],
|
|
true
|
|
);
|
|
|
|
return $this->render('index', [
|
|
'animals' => $animals,
|
|
'queryValue' => $queryValue,
|
|
'statusFilter' => $statusFilter,
|
|
'speciesFilter' => $speciesFilter,
|
|
'viewMode' => $viewMode,
|
|
'sortKey' => $sortKey,
|
|
'sortDirection' => $sortDirection,
|
|
'availableColumns' => $availableColumns,
|
|
'selectedColumns' => $selectedColumns,
|
|
'speciesOptions' => $speciesOptions,
|
|
'latestMedicalVisitByAnimal' => $latestMedicalVisitByAnimal,
|
|
'animalImageUrls' => $animalImageUrls,
|
|
'tileFields' => $this->getTileFieldSettings(),
|
|
'tileFieldOverrides' => $tileFieldOverrides,
|
|
'animalDonationGoalsByAnimal' => $animalDonationGoalsByAnimal,
|
|
'space' => $this->contentContainer,
|
|
'canManage' => $this->canManageAnimals(),
|
|
'incomingTransfers' => $incomingTransfers,
|
|
'outgoingTransfers' => $outgoingTransfers,
|
|
'transferAnimalImageUrls' => $transferAnimalImageUrls,
|
|
]);
|
|
}
|
|
|
|
public function actionView(int $id)
|
|
{
|
|
$animal = $this->findAnimal($id);
|
|
$canManage = $this->canManageAnimals();
|
|
$layoutMode = trim((string)Yii::$app->request->get('layout', 'default'));
|
|
if (!in_array($layoutMode, ['default', 'tablet'], true)) {
|
|
$layoutMode = 'default';
|
|
}
|
|
|
|
if ($this->contentContainer instanceof Space) {
|
|
GalleryIntegrationService::ensureAnimalGallery($animal, $this->contentContainer);
|
|
}
|
|
|
|
$medicalVisitsQuery = $animal->getMedicalVisits();
|
|
$medicalSchema = Yii::$app->db->schema->getTableSchema(AnimalMedicalVisit::tableName(), true);
|
|
if ($medicalSchema !== null && isset($medicalSchema->columns['post_to_animal_feed'])) {
|
|
$medicalVisitsQuery->andWhere(['post_to_animal_feed' => 1]);
|
|
}
|
|
$medicalVisits = $medicalVisitsQuery->orderBy(['visit_at' => SORT_DESC, 'id' => SORT_DESC])->all();
|
|
|
|
$progressUpdatesQuery = $animal->getProgressUpdates();
|
|
$progressSchema = Yii::$app->db->schema->getTableSchema(AnimalProgressUpdate::tableName(), true);
|
|
if ($progressSchema !== null && isset($progressSchema->columns['post_to_animal_feed'])) {
|
|
$progressUpdatesQuery->andWhere(['post_to_animal_feed' => 1]);
|
|
}
|
|
$progressUpdates = $progressUpdatesQuery->orderBy(['update_at' => SORT_DESC, 'id' => SORT_DESC])->all();
|
|
$transfers = $animal->getTransfers()->orderBy(['created_at' => SORT_DESC, 'id' => SORT_DESC])->all();
|
|
$transferEvents = $animal->getTransferEvents()->orderBy(['id' => SORT_DESC])->limit(100)->all();
|
|
$galleryItems = $animal->getGalleryItems()->orderBy(['id' => SORT_DESC])->limit(120)->all();
|
|
$customFieldValues = $animal->getCustomFieldDisplayValues($canManage);
|
|
$animalCoverImageUrls = $this->resolveAnimalImageUrls([(int)$animal->id], ['cover_image_url', 'image_url', 'photo_url', 'photo'], false);
|
|
$animalProfileImageUrls = $this->resolveAnimalImageUrls([(int)$animal->id], ['profile_image_url', 'profile_image', 'image_url', 'photo_url', 'photo'], false);
|
|
$animalCoverImageUrl = trim((string)($animalCoverImageUrls[(int)$animal->id] ?? ''));
|
|
$animalProfileImageUrl = trim((string)($animalProfileImageUrls[(int)$animal->id] ?? ''));
|
|
$detailHeroFields = $this->getDetailHeroFieldSettings();
|
|
$heroOverrides = $this->resolveDisplayFieldOverrides([(int)$animal->id], 'hero_display_fields');
|
|
if (!empty($heroOverrides[(int)$animal->id])) {
|
|
$detailHeroFields = $heroOverrides[(int)$animal->id];
|
|
}
|
|
|
|
return $this->render('view', [
|
|
'space' => $this->contentContainer,
|
|
'animal' => $animal,
|
|
'canManage' => $canManage,
|
|
'medicalVisits' => $medicalVisits,
|
|
'progressUpdates' => $progressUpdates,
|
|
'transfers' => $transfers,
|
|
'transferEvents' => $transferEvents,
|
|
'galleryItems' => $galleryItems,
|
|
'customFieldValues' => $customFieldValues,
|
|
'animalCoverImageUrl' => $animalCoverImageUrl,
|
|
'animalProfileImageUrl' => $animalProfileImageUrl,
|
|
'detailHeroFields' => $detailHeroFields,
|
|
'layoutMode' => $layoutMode,
|
|
]);
|
|
}
|
|
|
|
public function actionMedicalVisits(int $id)
|
|
{
|
|
$animal = $this->findAnimal($id);
|
|
|
|
if ($this->contentContainer instanceof Space) {
|
|
GalleryIntegrationService::ensureAnimalGallery($animal, $this->contentContainer);
|
|
}
|
|
$canManage = $this->canManageAnimals();
|
|
|
|
$this->ensureMedicalMediaFieldDefinition();
|
|
$medicalVisitForm = new AnimalMedicalVisitForm(['animal' => $animal]);
|
|
$medicalVisits = $animal->getMedicalVisits()->orderBy(['visit_at' => SORT_DESC, 'id' => SORT_DESC])->all();
|
|
$galleryItems = $animal->getGalleryItems()->orderBy(['id' => SORT_DESC])->limit(120)->all();
|
|
|
|
return $this->render('medical-visits', [
|
|
'space' => $this->contentContainer,
|
|
'animal' => $animal,
|
|
'canManage' => $canManage,
|
|
'medicalVisitForm' => $medicalVisitForm,
|
|
'medicalVisits' => $medicalVisits,
|
|
'galleryItems' => $galleryItems,
|
|
]);
|
|
}
|
|
|
|
public function actionProgressUpdates(int $id)
|
|
{
|
|
$animal = $this->findAnimal($id);
|
|
|
|
if ($this->contentContainer instanceof Space) {
|
|
GalleryIntegrationService::ensureAnimalGallery($animal, $this->contentContainer);
|
|
}
|
|
$canManage = $this->canManageAnimals();
|
|
|
|
$progressUpdateForm = new AnimalProgressUpdateForm(['animal' => $animal]);
|
|
$progressUpdates = $animal->getProgressUpdates()->orderBy(['update_at' => SORT_DESC, 'id' => SORT_DESC])->all();
|
|
$galleryItems = $animal->getGalleryItems()->orderBy(['id' => SORT_DESC])->limit(120)->all();
|
|
|
|
return $this->render('progress-updates', [
|
|
'space' => $this->contentContainer,
|
|
'animal' => $animal,
|
|
'canManage' => $canManage,
|
|
'progressUpdateForm' => $progressUpdateForm,
|
|
'progressUpdates' => $progressUpdates,
|
|
'galleryItems' => $galleryItems,
|
|
]);
|
|
}
|
|
|
|
public function actionCreate()
|
|
{
|
|
$model = new AnimalForm(['contentContainer' => $this->contentContainer]);
|
|
$intakeGoalPayload = (array)Yii::$app->request->post('IntakeGoal', []);
|
|
|
|
if (Yii::$app->request->isPost) {
|
|
$model->load(Yii::$app->request->post());
|
|
$model->coverImageFile = UploadedFile::getInstanceByName('AnimalForm[coverImageFile]')
|
|
?: UploadedFile::getInstance($model, 'coverImageFile');
|
|
$model->profileImageFile = UploadedFile::getInstanceByName('AnimalForm[profileImageFile]')
|
|
?: UploadedFile::getInstance($model, 'profileImageFile');
|
|
}
|
|
|
|
if (Yii::$app->request->isPost && $model->save()) {
|
|
$savedAnimal = $model->getAnimal();
|
|
if ($savedAnimal instanceof Animal) {
|
|
$this->syncPrimaryImagesToGallery($savedAnimal);
|
|
if ($this->contentContainer instanceof Space) {
|
|
GalleryIntegrationService::ensureAnimalGallery($savedAnimal, $this->contentContainer);
|
|
}
|
|
|
|
$goalError = $this->createIntakeAnimalGoal($savedAnimal, $intakeGoalPayload);
|
|
if ($goalError !== null) {
|
|
$this->view->error($goalError);
|
|
}
|
|
}
|
|
|
|
$this->view->success(Yii::t('AnimalManagementModule.base', 'Animal created.'));
|
|
return $this->redirect($this->contentContainer->createUrl('/animal_management/animals/index'));
|
|
}
|
|
|
|
return $this->render('create', [
|
|
'model' => $model,
|
|
'space' => $this->contentContainer,
|
|
'isEdit' => false,
|
|
'animal' => null,
|
|
'showIntakeGoalSection' => $this->canUseIntakeGoalSection(),
|
|
]);
|
|
}
|
|
|
|
public function actionEdit(int $id)
|
|
{
|
|
$animal = $this->findAnimal($id);
|
|
|
|
$model = new AnimalForm(['contentContainer' => $this->contentContainer]);
|
|
$model->setAnimal($animal);
|
|
|
|
if (Yii::$app->request->isPost) {
|
|
$model->load(Yii::$app->request->post());
|
|
$model->coverImageFile = UploadedFile::getInstanceByName('AnimalForm[coverImageFile]')
|
|
?: UploadedFile::getInstance($model, 'coverImageFile');
|
|
$model->profileImageFile = UploadedFile::getInstanceByName('AnimalForm[profileImageFile]')
|
|
?: UploadedFile::getInstance($model, 'profileImageFile');
|
|
}
|
|
|
|
if (Yii::$app->request->isPost && $model->save()) {
|
|
$savedAnimal = $model->getAnimal();
|
|
if ($savedAnimal instanceof Animal) {
|
|
$this->syncPrimaryImagesToGallery($savedAnimal);
|
|
if ($this->contentContainer instanceof Space) {
|
|
GalleryIntegrationService::ensureAnimalGallery($savedAnimal, $this->contentContainer);
|
|
}
|
|
}
|
|
|
|
$this->view->success(Yii::t('AnimalManagementModule.base', 'Animal updated.'));
|
|
return $this->redirect($this->contentContainer->createUrl('/animal_management/animals/view', ['id' => $animal->id]));
|
|
}
|
|
|
|
return $this->render('create', [
|
|
'model' => $model,
|
|
'space' => $this->contentContainer,
|
|
'isEdit' => true,
|
|
'animal' => $animal,
|
|
]);
|
|
}
|
|
|
|
public function actionDelete(int $id)
|
|
{
|
|
if (!$this->canManageAnimals()) {
|
|
throw new ForbiddenHttpException('You are not allowed to delete animals.');
|
|
}
|
|
|
|
$animal = $this->findAnimal($id);
|
|
$localGalleryPaths = $this->collectAnimalLocalGalleryImagePaths((int)$animal->id);
|
|
|
|
if ($animal->delete() === false) {
|
|
$this->view->error(Yii::t('AnimalManagementModule.base', 'Could not delete animal.'));
|
|
return $this->redirect($this->contentContainer->createUrl('/animal_management/animals/view', ['id' => $id]));
|
|
}
|
|
|
|
$deletedFiles = 0;
|
|
foreach ($localGalleryPaths as $path) {
|
|
$absolutePath = Yii::getAlias('@webroot') . $path;
|
|
if (!is_file($absolutePath)) {
|
|
continue;
|
|
}
|
|
|
|
if (@unlink($absolutePath)) {
|
|
$deletedFiles++;
|
|
}
|
|
}
|
|
|
|
if ($this->contentContainer instanceof Space) {
|
|
GalleryIntegrationService::syncSpaceAnimalGalleries($this->contentContainer);
|
|
}
|
|
|
|
if ($deletedFiles > 0) {
|
|
$this->view->success(Yii::t('AnimalManagementModule.base', 'Animal deleted and {count} gallery image(s) removed.', ['count' => $deletedFiles]));
|
|
} else {
|
|
$this->view->success(Yii::t('AnimalManagementModule.base', 'Animal deleted.'));
|
|
}
|
|
|
|
return $this->redirect($this->contentContainer->createUrl('/animal_management/animals/index'));
|
|
}
|
|
|
|
public function actionTransfer(int $id)
|
|
{
|
|
$animal = $this->findAnimal($id);
|
|
$returnTo = (string)Yii::$app->request->post('returnTo', Yii::$app->request->get('returnTo', 'view'));
|
|
|
|
$form = new TransferRequestForm([
|
|
'animal' => $animal,
|
|
'sourceSpace' => $this->contentContainer,
|
|
]);
|
|
|
|
if ($form->load(Yii::$app->request->post()) && $form->save()) {
|
|
$this->view->success(Yii::t('AnimalManagementModule.base', 'Transfer request sent.'));
|
|
return $this->redirectToAnimalPage((int)$animal->id, $returnTo);
|
|
}
|
|
|
|
return $this->render('transfer', [
|
|
'space' => $this->contentContainer,
|
|
'animal' => $animal,
|
|
'model' => $form,
|
|
'isInline' => false,
|
|
'returnTo' => $returnTo,
|
|
]);
|
|
}
|
|
|
|
public function actionTransferInline(int $id)
|
|
{
|
|
$animal = $this->findAnimal($id);
|
|
$returnTo = (string)Yii::$app->request->post('returnTo', Yii::$app->request->get('returnTo', 'view'));
|
|
$isInline = (int)Yii::$app->request->get('inline', 1) === 1;
|
|
|
|
$form = new TransferRequestForm([
|
|
'animal' => $animal,
|
|
'sourceSpace' => $this->contentContainer,
|
|
]);
|
|
|
|
if ($form->load(Yii::$app->request->post()) && $form->save()) {
|
|
$this->view->success(Yii::t('AnimalManagementModule.base', 'Transfer request sent.'));
|
|
if ($isInline) {
|
|
return $this->renderAjax('inline-edit-result', [
|
|
'collapseId' => 'transfer-add-inline',
|
|
'refreshSelectors' => ['#animal-transfer-panel'],
|
|
]);
|
|
}
|
|
return $this->redirectToAnimalPage((int)$animal->id, $returnTo);
|
|
}
|
|
|
|
$params = [
|
|
'space' => $this->contentContainer,
|
|
'animal' => $animal,
|
|
'model' => $form,
|
|
'isInline' => $isInline,
|
|
'returnTo' => $returnTo,
|
|
];
|
|
|
|
if ($isInline) {
|
|
return $this->renderAjax('transfer', $params);
|
|
}
|
|
|
|
return $this->render('transfer', $params);
|
|
}
|
|
|
|
public function actionAddGalleryImages(int $id)
|
|
{
|
|
$animal = $this->findAnimal($id);
|
|
$result = $this->processGalleryUploads($animal);
|
|
|
|
if (!$result['hadSelection']) {
|
|
$this->view->error(Yii::t('AnimalManagementModule.base', 'No gallery images were selected.'));
|
|
return $this->redirect($this->contentContainer->createUrl('/animal_management/animals/view', ['id' => $animal->id]) . '#animal-gallery');
|
|
}
|
|
|
|
if ($result['wasLimited']) {
|
|
$this->view->info(Yii::t('AnimalManagementModule.base', 'Only the first {count} selected images were processed.', ['count' => $result['maxUploadCount']]));
|
|
}
|
|
|
|
if ($result['added'] > 0) {
|
|
if ($this->contentContainer instanceof Space) {
|
|
GalleryIntegrationService::ensureAnimalGallery($animal, $this->contentContainer);
|
|
}
|
|
$this->view->success(Yii::t('AnimalManagementModule.base', '{count} image(s) added to gallery.', ['count' => $result['added']]));
|
|
} else {
|
|
$this->view->error(Yii::t('AnimalManagementModule.base', 'No gallery images were added. Check image type/size requirements.'));
|
|
}
|
|
|
|
return $this->redirect($this->contentContainer->createUrl('/animal_management/animals/view', ['id' => $animal->id]) . '#animal-gallery');
|
|
}
|
|
|
|
public function actionAddGalleryImagesInline(int $id)
|
|
{
|
|
$animal = $this->findAnimal($id);
|
|
$returnTo = (string)Yii::$app->request->post('returnTo', Yii::$app->request->get('returnTo', 'view'));
|
|
$isInline = (int)Yii::$app->request->get('inline', 1) === 1;
|
|
$maxUploadCount = 10;
|
|
$errorMessage = '';
|
|
$infoMessage = '';
|
|
|
|
if (Yii::$app->request->isPost) {
|
|
$result = $this->processGalleryUploads($animal);
|
|
|
|
if (!$result['hadSelection']) {
|
|
$errorMessage = Yii::t('AnimalManagementModule.base', 'No gallery images were selected.');
|
|
} else {
|
|
if ($result['wasLimited']) {
|
|
$infoMessage = Yii::t('AnimalManagementModule.base', 'Only the first {count} selected images were processed.', ['count' => $result['maxUploadCount']]);
|
|
}
|
|
|
|
if ($result['added'] > 0) {
|
|
if ($this->contentContainer instanceof Space) {
|
|
GalleryIntegrationService::ensureAnimalGallery($animal, $this->contentContainer);
|
|
}
|
|
$this->view->success(Yii::t('AnimalManagementModule.base', '{count} image(s) added to gallery.', ['count' => $result['added']]));
|
|
if ($isInline) {
|
|
return $this->renderAjax('inline-edit-result', [
|
|
'collapseId' => 'gallery-add-inline',
|
|
'refreshSelectors' => ['#animal-gallery-panel'],
|
|
]);
|
|
}
|
|
return $this->redirectToAnimalPage((int)$animal->id, $returnTo);
|
|
}
|
|
|
|
$errorMessage = Yii::t('AnimalManagementModule.base', 'No gallery images were added. Check image type/size requirements.');
|
|
}
|
|
}
|
|
|
|
$galleryItems = $animal->getGalleryItems()->orderBy(['id' => SORT_DESC])->limit(120)->all();
|
|
|
|
$params = [
|
|
'space' => $this->contentContainer,
|
|
'animal' => $animal,
|
|
'galleryItems' => $galleryItems,
|
|
'isInline' => $isInline,
|
|
'returnTo' => $returnTo,
|
|
'maxUploadCount' => $maxUploadCount,
|
|
'errorMessage' => $errorMessage,
|
|
'infoMessage' => $infoMessage,
|
|
];
|
|
|
|
if ($isInline) {
|
|
return $this->renderAjax('add-gallery-images-inline', $params);
|
|
}
|
|
|
|
return $this->render('add-gallery-images-inline', $params);
|
|
}
|
|
|
|
public function actionRemoveGalleryImage(int $id, int $galleryId)
|
|
{
|
|
$animal = $this->findAnimal($id);
|
|
$item = AnimalGalleryItem::findOne(['id' => $galleryId, 'animal_id' => (int)$animal->id]);
|
|
if (!$item instanceof AnimalGalleryItem) {
|
|
throw new NotFoundHttpException('Gallery image not found.');
|
|
}
|
|
|
|
$path = trim((string)$item->file_path);
|
|
$deletePhysicalFile = false;
|
|
if ($path !== '' && substr($path, 0, 1) === '/') {
|
|
$stillReferencedByGallery = AnimalGalleryItem::find()
|
|
->where(['animal_id' => (int)$animal->id, 'file_path' => $path])
|
|
->andWhere(['!=', 'id', (int)$item->id])
|
|
->exists();
|
|
|
|
$stillReferencedByPrimaryImage = $this->isPrimaryImagePathReferenced((int)$animal->id, $path);
|
|
$deletePhysicalFile = !$stillReferencedByGallery && !$stillReferencedByPrimaryImage;
|
|
}
|
|
|
|
if ($item->delete() !== false) {
|
|
if ($this->contentContainer instanceof Space) {
|
|
GalleryIntegrationService::ensureAnimalGallery($animal, $this->contentContainer);
|
|
}
|
|
if ($deletePhysicalFile) {
|
|
$absolute = Yii::getAlias('@webroot') . $path;
|
|
if (is_file($absolute)) {
|
|
@unlink($absolute);
|
|
}
|
|
}
|
|
|
|
$this->view->success(Yii::t('AnimalManagementModule.base', 'Gallery image removed.'));
|
|
} else {
|
|
$this->view->error(Yii::t('AnimalManagementModule.base', 'Could not remove gallery image.'));
|
|
}
|
|
|
|
return $this->redirect($this->contentContainer->createUrl('/animal_management/animals/view', ['id' => $animal->id]) . '#animal-gallery');
|
|
}
|
|
|
|
public function actionAddMedicalVisit(int $id)
|
|
{
|
|
$animal = $this->findAnimal($id);
|
|
$this->ensureMedicalMediaFieldDefinition();
|
|
$form = new AnimalMedicalVisitForm(['animal' => $animal]);
|
|
$returnTo = (string)Yii::$app->request->post('returnTo', Yii::$app->request->get('returnTo', 'view'));
|
|
|
|
if ($form->load(Yii::$app->request->post())) {
|
|
$this->applyMedicalMediaInput($form, $animal);
|
|
|
|
if ($form->save()) {
|
|
$this->view->success(Yii::t('AnimalManagementModule.base', 'Medical visit added.'));
|
|
} else {
|
|
$message = Yii::t('AnimalManagementModule.base', 'Could not save medical visit.');
|
|
if ($form->hasErrors()) {
|
|
$message .= ' ' . implode(' ', array_values($form->getFirstErrors()));
|
|
}
|
|
$this->view->error($message);
|
|
}
|
|
} else {
|
|
$this->view->error(Yii::t('AnimalManagementModule.base', 'Could not save medical visit.'));
|
|
}
|
|
|
|
return $this->redirectToAnimalPage((int)$animal->id, $returnTo);
|
|
}
|
|
|
|
public function actionAddMedicalVisitInline(int $id)
|
|
{
|
|
$animal = $this->findAnimal($id);
|
|
$this->ensureMedicalMediaFieldDefinition();
|
|
$form = new AnimalMedicalVisitForm(['animal' => $animal]);
|
|
if (trim((string)$form->visit_at) === '') {
|
|
$form->visit_at = date('Y-m-d\TH:i');
|
|
}
|
|
$returnTo = (string)Yii::$app->request->post('returnTo', Yii::$app->request->get('returnTo', 'medical-visits'));
|
|
$isInline = (int)Yii::$app->request->get('inline', 1) === 1;
|
|
|
|
if ($form->load(Yii::$app->request->post())) {
|
|
$this->applyMedicalMediaInput($form, $animal);
|
|
|
|
if ($form->save()) {
|
|
$this->view->success(Yii::t('AnimalManagementModule.base', 'Medical visit added.'));
|
|
if ($isInline) {
|
|
return $this->renderAjax('inline-edit-result', [
|
|
'collapseId' => 'medical-add-inline',
|
|
'refreshSelectors' => ['#animal-medical-panel', '#animal-gallery-panel'],
|
|
]);
|
|
}
|
|
return $this->redirectToAnimalPage((int)$animal->id, $returnTo);
|
|
}
|
|
}
|
|
|
|
$galleryItems = $animal->getGalleryItems()->orderBy(['id' => SORT_DESC])->limit(120)->all();
|
|
|
|
$params = [
|
|
'space' => $this->contentContainer,
|
|
'animal' => $animal,
|
|
'model' => $form,
|
|
'returnTo' => $returnTo,
|
|
'galleryItems' => $galleryItems,
|
|
'isInline' => $isInline,
|
|
];
|
|
|
|
if ($isInline) {
|
|
return $this->renderAjax('add-medical-visit', $params);
|
|
}
|
|
|
|
return $this->render('add-medical-visit', $params);
|
|
}
|
|
|
|
public function actionEditMedicalVisit(int $id, int $visitId)
|
|
{
|
|
$animal = $this->findAnimal($id);
|
|
$visit = $animal->getMedicalVisits()->andWhere(['id' => $visitId])->one();
|
|
$returnTo = (string)Yii::$app->request->post('returnTo', Yii::$app->request->get('returnTo', 'view'));
|
|
$isInline = (int)Yii::$app->request->get('inline', 0) === 1;
|
|
|
|
if (!$visit instanceof \humhub\modules\animal_management\models\AnimalMedicalVisit) {
|
|
throw new NotFoundHttpException('Medical visit not found.');
|
|
}
|
|
|
|
$this->ensureMedicalMediaFieldDefinition();
|
|
$form = new AnimalMedicalVisitForm(['animal' => $animal]);
|
|
$form->setMedicalVisit($visit);
|
|
|
|
if ($form->load(Yii::$app->request->post())) {
|
|
$this->applyMedicalMediaInput($form, $animal);
|
|
|
|
if ($form->save()) {
|
|
$this->view->success(Yii::t('AnimalManagementModule.base', 'Medical visit updated.'));
|
|
if ($isInline) {
|
|
return $this->renderAjax('inline-edit-result', [
|
|
'collapseId' => 'medical-edit-inline-' . (int)$visit->id,
|
|
'refreshSelectors' => ['#animal-medical-panel', '#animal-gallery-panel'],
|
|
]);
|
|
}
|
|
return $this->redirectToAnimalPage((int)$animal->id, $returnTo);
|
|
}
|
|
}
|
|
|
|
$galleryItems = $animal->getGalleryItems()->orderBy(['id' => SORT_DESC])->limit(120)->all();
|
|
|
|
$params = [
|
|
'space' => $this->contentContainer,
|
|
'animal' => $animal,
|
|
'model' => $form,
|
|
'medicalVisit' => $visit,
|
|
'returnTo' => $returnTo,
|
|
'galleryItems' => $galleryItems,
|
|
'isInline' => $isInline,
|
|
];
|
|
|
|
if ($isInline) {
|
|
return $this->renderAjax('edit-medical-visit', $params);
|
|
}
|
|
|
|
return $this->render('edit-medical-visit', $params);
|
|
}
|
|
|
|
public function actionAddProgressUpdate(int $id)
|
|
{
|
|
$animal = $this->findAnimal($id);
|
|
$form = new AnimalProgressUpdateForm(['animal' => $animal]);
|
|
$returnTo = (string)Yii::$app->request->post('returnTo', Yii::$app->request->get('returnTo', 'view'));
|
|
|
|
if ($form->load(Yii::$app->request->post())) {
|
|
$this->applyProgressMediaInput($form, $animal);
|
|
|
|
if ($form->save()) {
|
|
$this->view->success(Yii::t('AnimalManagementModule.base', 'Progress update added.'));
|
|
} else {
|
|
$message = Yii::t('AnimalManagementModule.base', 'Could not save progress update.');
|
|
if ($form->hasErrors()) {
|
|
$message .= ' ' . implode(' ', array_values($form->getFirstErrors()));
|
|
}
|
|
$this->view->error($message);
|
|
}
|
|
} else {
|
|
$this->view->error(Yii::t('AnimalManagementModule.base', 'Could not save progress update.'));
|
|
}
|
|
|
|
return $this->redirectToAnimalPage((int)$animal->id, $returnTo);
|
|
}
|
|
|
|
public function actionAddProgressUpdateInline(int $id)
|
|
{
|
|
$animal = $this->findAnimal($id);
|
|
$form = new AnimalProgressUpdateForm(['animal' => $animal]);
|
|
$returnTo = (string)Yii::$app->request->post('returnTo', Yii::$app->request->get('returnTo', 'progress-updates'));
|
|
$isInline = (int)Yii::$app->request->get('inline', 1) === 1;
|
|
|
|
if ($form->load(Yii::$app->request->post())) {
|
|
$this->applyProgressMediaInput($form, $animal);
|
|
|
|
if ($form->save()) {
|
|
$this->view->success(Yii::t('AnimalManagementModule.base', 'Progress update added.'));
|
|
if ($isInline) {
|
|
return $this->renderAjax('inline-edit-result', [
|
|
'collapseId' => 'progress-add-inline',
|
|
'refreshSelectors' => ['#animal-progress-panel', '#animal-gallery-panel'],
|
|
]);
|
|
}
|
|
return $this->redirectToAnimalPage((int)$animal->id, $returnTo);
|
|
}
|
|
}
|
|
|
|
$galleryItems = $animal->getGalleryItems()->orderBy(['id' => SORT_DESC])->limit(120)->all();
|
|
|
|
$params = [
|
|
'space' => $this->contentContainer,
|
|
'animal' => $animal,
|
|
'model' => $form,
|
|
'returnTo' => $returnTo,
|
|
'galleryItems' => $galleryItems,
|
|
'isInline' => $isInline,
|
|
];
|
|
|
|
if ($isInline) {
|
|
return $this->renderAjax('add-progress-update', $params);
|
|
}
|
|
|
|
return $this->render('add-progress-update', $params);
|
|
}
|
|
|
|
public function actionEditProgressUpdate(int $id, int $updateId)
|
|
{
|
|
$animal = $this->findAnimal($id);
|
|
$update = $animal->getProgressUpdates()->andWhere(['id' => $updateId])->one();
|
|
$returnTo = (string)Yii::$app->request->post('returnTo', Yii::$app->request->get('returnTo', 'view'));
|
|
$isInline = (int)Yii::$app->request->get('inline', 0) === 1;
|
|
|
|
if (!$update instanceof \humhub\modules\animal_management\models\AnimalProgressUpdate) {
|
|
throw new NotFoundHttpException('Progress update not found.');
|
|
}
|
|
|
|
$form = new AnimalProgressUpdateForm(['animal' => $animal]);
|
|
$form->setProgressUpdate($update);
|
|
|
|
if ($form->load(Yii::$app->request->post())) {
|
|
$this->applyProgressMediaInput($form, $animal);
|
|
|
|
if ($form->save()) {
|
|
$this->view->success(Yii::t('AnimalManagementModule.base', 'Progress update updated.'));
|
|
if ($isInline) {
|
|
return $this->renderAjax('inline-edit-result', [
|
|
'collapseId' => 'progress-edit-inline-' . (int)$update->id,
|
|
'refreshSelectors' => ['#animal-progress-panel', '#animal-gallery-panel'],
|
|
]);
|
|
}
|
|
return $this->redirectToAnimalPage((int)$animal->id, $returnTo);
|
|
}
|
|
|
|
Yii::warning([
|
|
'message' => 'Progress update save failed.',
|
|
'animal_id' => (int)$animal->id,
|
|
'progress_update_id' => (int)$update->id,
|
|
'is_inline' => $isInline,
|
|
'errors' => $form->getErrors(),
|
|
], 'animal_management.progress_media');
|
|
} elseif (Yii::$app->request->isPost) {
|
|
Yii::warning([
|
|
'message' => 'Progress update form load failed on POST.',
|
|
'animal_id' => (int)$animal->id,
|
|
'progress_update_id' => (int)$update->id,
|
|
'is_inline' => $isInline,
|
|
'post_keys' => array_keys((array)Yii::$app->request->post()),
|
|
], 'animal_management.progress_media');
|
|
}
|
|
|
|
$galleryItems = $animal->getGalleryItems()->orderBy(['id' => SORT_DESC])->limit(120)->all();
|
|
|
|
$params = [
|
|
'space' => $this->contentContainer,
|
|
'animal' => $animal,
|
|
'model' => $form,
|
|
'progressUpdate' => $update,
|
|
'returnTo' => $returnTo,
|
|
'galleryItems' => $galleryItems,
|
|
'isInline' => $isInline,
|
|
];
|
|
|
|
if ($isInline) {
|
|
return $this->renderAjax('edit-progress-update', $params);
|
|
}
|
|
|
|
return $this->render('edit-progress-update', $params);
|
|
}
|
|
|
|
public function actionTransferRespond(int $id, string $decision)
|
|
{
|
|
$transfer = AnimalTransfer::findOne([
|
|
'id' => $id,
|
|
'to_contentcontainer_id' => $this->contentContainer->contentcontainer_id,
|
|
]);
|
|
|
|
if (!$transfer instanceof AnimalTransfer) {
|
|
throw new NotFoundHttpException('Transfer request not found.');
|
|
}
|
|
|
|
if ($transfer->status !== AnimalTransfer::STATUS_REQUESTED) {
|
|
$this->view->error(Yii::t('AnimalManagementModule.base', 'This transfer can no longer be responded to.'));
|
|
return $this->redirect($this->contentContainer->createUrl('/animal_management/animals/index'));
|
|
}
|
|
|
|
if ($decision === 'accept') {
|
|
if ($transfer->markAccepted()) {
|
|
$originator = Yii::$app->user->getIdentity();
|
|
TransferNotifier::notifyStatusChange($transfer, AnimalTransferEvent::EVENT_ACCEPTED, $originator);
|
|
$this->view->success(Yii::t('AnimalManagementModule.base', 'Transfer request accepted.'));
|
|
} else {
|
|
$this->view->error(Yii::t('AnimalManagementModule.base', 'Could not accept transfer request.'));
|
|
}
|
|
} elseif ($decision === 'decline') {
|
|
if ($transfer->markDeclined()) {
|
|
$originator = Yii::$app->user->getIdentity();
|
|
TransferNotifier::notifyStatusChange($transfer, AnimalTransferEvent::EVENT_DECLINED, $originator);
|
|
$animal = $transfer->animal;
|
|
if ($animal instanceof Animal && $animal->status === Animal::STATUS_TRANSFER_PENDING) {
|
|
$hasOpenTransfers = AnimalTransfer::find()
|
|
->where(['animal_id' => $animal->id, 'status' => [AnimalTransfer::STATUS_REQUESTED, AnimalTransfer::STATUS_ACCEPTED]])
|
|
->exists();
|
|
|
|
if (!$hasOpenTransfers) {
|
|
$animal->status = Animal::STATUS_ACTIVE;
|
|
$animal->save(false, ['status', 'updated_at']);
|
|
}
|
|
}
|
|
$this->view->success(Yii::t('AnimalManagementModule.base', 'Transfer request declined.'));
|
|
} else {
|
|
$this->view->error(Yii::t('AnimalManagementModule.base', 'Could not decline transfer request.'));
|
|
}
|
|
} else {
|
|
$this->view->error(Yii::t('AnimalManagementModule.base', 'Unknown transfer decision.'));
|
|
}
|
|
|
|
return $this->redirect($this->contentContainer->createUrl('/animal_management/animals/index'));
|
|
}
|
|
|
|
public function actionTransferComplete(int $id)
|
|
{
|
|
$transfer = AnimalTransfer::findOne([
|
|
'id' => $id,
|
|
'to_contentcontainer_id' => $this->contentContainer->contentcontainer_id,
|
|
'status' => AnimalTransfer::STATUS_ACCEPTED,
|
|
]);
|
|
|
|
if (!$transfer instanceof AnimalTransfer) {
|
|
throw new NotFoundHttpException('Accepted transfer not found.');
|
|
}
|
|
|
|
$animal = $transfer->animal;
|
|
if (!$animal instanceof Animal) {
|
|
throw new NotFoundHttpException('Animal not found.');
|
|
}
|
|
|
|
$animal->contentcontainer_id = $this->contentContainer->contentcontainer_id;
|
|
$animal->status = Animal::STATUS_ACTIVE;
|
|
if (!$animal->save(false, ['contentcontainer_id', 'status', 'updated_at'])) {
|
|
$this->view->error(Yii::t('AnimalManagementModule.base', 'Could not move animal ownership.'));
|
|
return $this->redirect($this->contentContainer->createUrl('/animal_management/animals/index'));
|
|
}
|
|
|
|
if (!$transfer->markCompleted()) {
|
|
$this->view->error(Yii::t('AnimalManagementModule.base', 'Could not complete transfer.'));
|
|
return $this->redirect($this->contentContainer->createUrl('/animal_management/animals/index'));
|
|
}
|
|
|
|
$originator = Yii::$app->user->getIdentity();
|
|
TransferNotifier::notifyStatusChange($transfer, AnimalTransferEvent::EVENT_COMPLETED, $originator);
|
|
|
|
$openTransfers = AnimalTransfer::find()
|
|
->where(['animal_id' => $animal->id])
|
|
->andWhere(['status' => [AnimalTransfer::STATUS_REQUESTED, AnimalTransfer::STATUS_ACCEPTED]])
|
|
->andWhere(['!=', 'id', $transfer->id])
|
|
->all();
|
|
|
|
foreach ($openTransfers as $openTransfer) {
|
|
if (!$openTransfer instanceof AnimalTransfer) {
|
|
continue;
|
|
}
|
|
|
|
$openTransfer->markCancelled(Yii::t('AnimalManagementModule.base', 'Transfer cancelled because another transfer was completed.'));
|
|
}
|
|
|
|
$this->view->success(Yii::t('AnimalManagementModule.base', 'Transfer completed. Animal ownership moved to this rescue.'));
|
|
return $this->redirect($this->contentContainer->createUrl('/animal_management/animals/view', ['id' => $animal->id]));
|
|
}
|
|
|
|
public function actionTransferCancel(int $id)
|
|
{
|
|
$transfer = AnimalTransfer::findOne([
|
|
'id' => $id,
|
|
'from_contentcontainer_id' => $this->contentContainer->contentcontainer_id,
|
|
]);
|
|
|
|
if (!$transfer instanceof AnimalTransfer) {
|
|
throw new NotFoundHttpException('Transfer request not found.');
|
|
}
|
|
|
|
if (!in_array($transfer->status, [AnimalTransfer::STATUS_REQUESTED, AnimalTransfer::STATUS_ACCEPTED], true)) {
|
|
$this->view->error(Yii::t('AnimalManagementModule.base', 'This transfer can no longer be cancelled.'));
|
|
return $this->redirect($this->contentContainer->createUrl('/animal_management/animals/index'));
|
|
}
|
|
|
|
if (!$transfer->markCancelled(Yii::t('AnimalManagementModule.base', 'Transfer cancelled by source rescue.'))) {
|
|
$this->view->error(Yii::t('AnimalManagementModule.base', 'Could not cancel transfer request.'));
|
|
return $this->redirect($this->contentContainer->createUrl('/animal_management/animals/index'));
|
|
}
|
|
|
|
$originator = Yii::$app->user->getIdentity();
|
|
TransferNotifier::notifyStatusChange($transfer, AnimalTransferEvent::EVENT_CANCELLED, $originator);
|
|
|
|
$animal = $transfer->animal;
|
|
if ($animal instanceof Animal && $animal->status === Animal::STATUS_TRANSFER_PENDING) {
|
|
$hasOpenTransfers = AnimalTransfer::find()
|
|
->where(['animal_id' => $animal->id, 'status' => [AnimalTransfer::STATUS_REQUESTED, AnimalTransfer::STATUS_ACCEPTED]])
|
|
->exists();
|
|
|
|
if (!$hasOpenTransfers) {
|
|
$animal->status = Animal::STATUS_ACTIVE;
|
|
$animal->save(false, ['status', 'updated_at']);
|
|
}
|
|
}
|
|
|
|
$this->view->success(Yii::t('AnimalManagementModule.base', 'Transfer request cancelled.'));
|
|
return $this->redirect($this->contentContainer->createUrl('/animal_management/animals/index'));
|
|
}
|
|
|
|
private function findAnimal(int $id): Animal
|
|
{
|
|
$animal = Animal::findOne(['id' => $id, 'contentcontainer_id' => $this->contentContainer->contentcontainer_id]);
|
|
if (!$animal instanceof Animal) {
|
|
throw new NotFoundHttpException('Animal not found.');
|
|
}
|
|
|
|
return $animal;
|
|
}
|
|
|
|
private function collectAnimalLocalGalleryImagePaths(int $animalId): array
|
|
{
|
|
if (Yii::$app->db->schema->getTableSchema('rescue_animal_gallery_item', true) === null) {
|
|
return [];
|
|
}
|
|
|
|
$items = AnimalGalleryItem::find()->where(['animal_id' => $animalId])->all();
|
|
$paths = [];
|
|
|
|
foreach ($items as $item) {
|
|
$path = trim((string)$item->file_path);
|
|
if ($path === '' || substr($path, 0, 1) !== '/') {
|
|
continue;
|
|
}
|
|
|
|
$paths[$path] = true;
|
|
}
|
|
|
|
return array_keys($paths);
|
|
}
|
|
|
|
private function redirectToAnimalPage(int $animalId, string $returnTo): Response
|
|
{
|
|
if ($returnTo === 'medical-visits') {
|
|
return $this->redirect($this->contentContainer->createUrl('/animal_management/animals/medical-visits', ['id' => $animalId]));
|
|
}
|
|
|
|
if ($returnTo === 'progress-updates') {
|
|
return $this->redirect($this->contentContainer->createUrl('/animal_management/animals/progress-updates', ['id' => $animalId]));
|
|
}
|
|
|
|
return $this->redirect($this->contentContainer->createUrl('/animal_management/animals/view', ['id' => $animalId]));
|
|
}
|
|
|
|
private function canManageAnimals(): bool
|
|
{
|
|
if ($this->contentContainer->can(ManageAnimals::class)) {
|
|
return true;
|
|
}
|
|
|
|
if ($this->contentContainer instanceof Space && $this->contentContainer->isAdmin()) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private function getTileFieldSettings(): array
|
|
{
|
|
$settings = Yii::$app->getModule('animal_management')->settings->contentContainer($this->contentContainer);
|
|
$raw = $settings->get('tileFields', json_encode(DisplaySettingsForm::DEFAULT_TILE_FIELDS));
|
|
return $this->normalizeDisplayFields($raw, DisplaySettingsForm::DEFAULT_TILE_FIELDS);
|
|
}
|
|
|
|
private function getDetailHeroFieldSettings(): array
|
|
{
|
|
$settings = Yii::$app->getModule('animal_management')->settings->contentContainer($this->contentContainer);
|
|
$raw = $settings->get('detailHeroFields', json_encode(DisplaySettingsForm::DEFAULT_DETAIL_FIELDS));
|
|
return $this->normalizeDisplayFields($raw, DisplaySettingsForm::DEFAULT_DETAIL_FIELDS);
|
|
}
|
|
|
|
private function normalizeDisplayFields($raw, array $default): array
|
|
{
|
|
if (is_string($raw)) {
|
|
$decoded = json_decode($raw, true);
|
|
if (!is_array($decoded)) {
|
|
return $default;
|
|
}
|
|
$raw = $decoded;
|
|
}
|
|
|
|
if (!is_array($raw)) {
|
|
return $default;
|
|
}
|
|
|
|
$allowed = array_keys(DisplaySettingsForm::fieldOptions());
|
|
$normalized = [];
|
|
foreach ($raw as $field) {
|
|
$field = trim((string)$field);
|
|
if ($field === '' || !in_array($field, $allowed, true)) {
|
|
continue;
|
|
}
|
|
if (!in_array($field, $normalized, true)) {
|
|
$normalized[] = $field;
|
|
}
|
|
}
|
|
|
|
return !empty($normalized) ? $normalized : $default;
|
|
}
|
|
|
|
private function resolveAnimalImageUrls(array $animalIds, array $imageFieldOrder = [], bool $allowGalleryFallback = false): array
|
|
{
|
|
$animalIds = array_values(array_unique(array_map('intval', $animalIds)));
|
|
if (empty($animalIds)) {
|
|
return [];
|
|
}
|
|
|
|
if (!class_exists(RescueFieldDefinition::class)
|
|
|| Yii::$app->db->schema->getTableSchema('rescue_field_definition', true) === null
|
|
|| Yii::$app->db->schema->getTableSchema('rescue_animal_field_value', true) === null
|
|
) {
|
|
return [];
|
|
}
|
|
|
|
if (empty($imageFieldOrder)) {
|
|
$imageFieldOrder = ['cover_image_url', 'profile_image_url', 'photo_url', 'image_url', 'profile_image', 'photo'];
|
|
}
|
|
|
|
$definitions = RescueFieldDefinition::find()
|
|
->select(['id', 'field_key'])
|
|
->where([
|
|
'module_id' => 'animal_management',
|
|
'group_key' => 'animal_profile',
|
|
'field_key' => $imageFieldOrder,
|
|
'is_active' => 1,
|
|
])
|
|
->all();
|
|
|
|
if (empty($definitions)) {
|
|
return [];
|
|
}
|
|
|
|
$definitionPriority = [];
|
|
foreach ($definitions as $definition) {
|
|
$priority = array_search((string)$definition->field_key, $imageFieldOrder, true);
|
|
if ($priority === false) {
|
|
$priority = 999;
|
|
}
|
|
|
|
$definitionPriority[(int)$definition->id] = (int)$priority;
|
|
}
|
|
|
|
if (empty($definitionPriority)) {
|
|
return [];
|
|
}
|
|
|
|
$valueRows = AnimalFieldValue::find()
|
|
->where(['animal_id' => $animalIds, 'field_definition_id' => array_keys($definitionPriority)])
|
|
->all();
|
|
|
|
$imageUrls = [];
|
|
$chosenPriorityByAnimal = [];
|
|
foreach ($valueRows as $valueRow) {
|
|
$animalId = (int)$valueRow->animal_id;
|
|
$valueText = trim((string)$valueRow->value_text);
|
|
if ($valueText === '') {
|
|
continue;
|
|
}
|
|
|
|
$priority = $definitionPriority[(int)$valueRow->field_definition_id] ?? 999;
|
|
if (!isset($chosenPriorityByAnimal[$animalId]) || $priority < $chosenPriorityByAnimal[$animalId]) {
|
|
$chosenPriorityByAnimal[$animalId] = $priority;
|
|
$imageUrls[$animalId] = $valueText;
|
|
}
|
|
}
|
|
|
|
$missingAnimalIds = [];
|
|
foreach ($animalIds as $animalId) {
|
|
if (!isset($imageUrls[$animalId])) {
|
|
$missingAnimalIds[] = (int)$animalId;
|
|
}
|
|
}
|
|
|
|
if ($allowGalleryFallback && !empty($missingAnimalIds) && Yii::$app->db->schema->getTableSchema('rescue_animal_gallery_item', true) !== null) {
|
|
$galleryItems = AnimalGalleryItem::find()
|
|
->where(['animal_id' => $missingAnimalIds])
|
|
->orderBy(['animal_id' => SORT_ASC, 'id' => SORT_DESC])
|
|
->all();
|
|
|
|
foreach ($galleryItems as $galleryItem) {
|
|
$animalId = (int)$galleryItem->animal_id;
|
|
if (isset($imageUrls[$animalId])) {
|
|
continue;
|
|
}
|
|
|
|
$url = trim((string)$galleryItem->getImageUrl());
|
|
if ($url === '') {
|
|
continue;
|
|
}
|
|
|
|
$imageUrls[$animalId] = $url;
|
|
}
|
|
}
|
|
|
|
return $imageUrls;
|
|
}
|
|
|
|
private function resolveDisplayFieldOverrides(array $animalIds, string $fieldKey): array
|
|
{
|
|
$animalIds = array_values(array_unique(array_map('intval', $animalIds)));
|
|
if (empty($animalIds)) {
|
|
return [];
|
|
}
|
|
|
|
if (!class_exists(RescueFieldDefinition::class)
|
|
|| Yii::$app->db->schema->getTableSchema('rescue_field_definition', true) === null
|
|
|| Yii::$app->db->schema->getTableSchema('rescue_animal_field_value', true) === null
|
|
) {
|
|
return [];
|
|
}
|
|
|
|
$definition = RescueFieldDefinition::findOne([
|
|
'module_id' => 'animal_management',
|
|
'group_key' => 'animal_profile',
|
|
'field_key' => $fieldKey,
|
|
]);
|
|
|
|
if (!$definition instanceof RescueFieldDefinition) {
|
|
return [];
|
|
}
|
|
|
|
$rows = AnimalFieldValue::find()
|
|
->where(['animal_id' => $animalIds, 'field_definition_id' => (int)$definition->id])
|
|
->all();
|
|
|
|
$allowed = array_keys(DisplaySettingsForm::fieldOptions());
|
|
$result = [];
|
|
foreach ($rows as $row) {
|
|
$raw = trim((string)$row->value_text);
|
|
if ($raw === '') {
|
|
continue;
|
|
}
|
|
|
|
$decoded = json_decode($raw, true);
|
|
if (!is_array($decoded)) {
|
|
$decoded = array_map('trim', explode(',', $raw));
|
|
}
|
|
|
|
$normalized = [];
|
|
foreach ($decoded as $field) {
|
|
$field = trim((string)$field);
|
|
if ($field === '' || !in_array($field, $allowed, true)) {
|
|
continue;
|
|
}
|
|
|
|
if (!in_array($field, $normalized, true)) {
|
|
$normalized[] = $field;
|
|
}
|
|
}
|
|
|
|
if (!empty($normalized)) {
|
|
$result[(int)$row->animal_id] = $normalized;
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
private function processGalleryUploads(Animal $animal): array
|
|
{
|
|
$uploadedFiles = UploadedFile::getInstancesByName('galleryImages');
|
|
$maxUploadCount = 10;
|
|
$hadSelection = !empty($uploadedFiles);
|
|
$wasLimited = false;
|
|
|
|
if (!$hadSelection) {
|
|
return [
|
|
'hadSelection' => false,
|
|
'wasLimited' => false,
|
|
'maxUploadCount' => $maxUploadCount,
|
|
'added' => 0,
|
|
];
|
|
}
|
|
|
|
if (count($uploadedFiles) > $maxUploadCount) {
|
|
$uploadedFiles = array_slice($uploadedFiles, 0, $maxUploadCount);
|
|
$wasLimited = true;
|
|
}
|
|
|
|
$allowedExtensions = array_map('strtolower', UploadStandards::imageExtensions());
|
|
$allowedMimeTypes = array_map('strtolower', UploadStandards::imageMimeTypes());
|
|
$maxBytes = (int)UploadStandards::IMAGE_MAX_BYTES;
|
|
$existingItems = AnimalGalleryItem::find()->where(['animal_id' => (int)$animal->id])->all();
|
|
$existingHashes = [];
|
|
foreach ($existingItems as $existingItem) {
|
|
$existingUrl = trim((string)$existingItem->getImageUrl());
|
|
if ($existingUrl === '') {
|
|
continue;
|
|
}
|
|
|
|
$hash = $this->computeImageContentHash($existingUrl);
|
|
if ($hash !== null) {
|
|
$existingHashes[$hash] = true;
|
|
}
|
|
}
|
|
|
|
$added = 0;
|
|
foreach ($uploadedFiles as $uploadedFile) {
|
|
if (!$uploadedFile instanceof UploadedFile) {
|
|
continue;
|
|
}
|
|
|
|
$extension = strtolower((string)$uploadedFile->extension);
|
|
$mimeType = strtolower((string)$uploadedFile->type);
|
|
|
|
if (!in_array($extension, $allowedExtensions, true)) {
|
|
continue;
|
|
}
|
|
|
|
if ($mimeType !== '' && !in_array($mimeType, $allowedMimeTypes, true)) {
|
|
continue;
|
|
}
|
|
|
|
if ($uploadedFile->size > $maxBytes) {
|
|
continue;
|
|
}
|
|
|
|
$storedPath = $this->storeGalleryUpload($animal, $uploadedFile);
|
|
if ($storedPath === null) {
|
|
continue;
|
|
}
|
|
|
|
$storedHash = $this->computeImageContentHash($storedPath);
|
|
|
|
$exactExists = AnimalGalleryItem::find()
|
|
->where(['animal_id' => (int)$animal->id, 'file_path' => $storedPath])
|
|
->exists();
|
|
|
|
if ($exactExists || ($storedHash !== null && isset($existingHashes[$storedHash]))) {
|
|
$absolute = Yii::getAlias('@webroot') . $storedPath;
|
|
if (is_file($absolute)) {
|
|
@unlink($absolute);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
$item = new AnimalGalleryItem();
|
|
$item->animal_id = (int)$animal->id;
|
|
$item->file_path = $storedPath;
|
|
$item->source_type = 'upload';
|
|
$item->created_by = Yii::$app->user->isGuest ? null : (int)Yii::$app->user->id;
|
|
if ($item->save()) {
|
|
$added++;
|
|
if ($storedHash !== null) {
|
|
$existingHashes[$storedHash] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return [
|
|
'hadSelection' => true,
|
|
'wasLimited' => $wasLimited,
|
|
'maxUploadCount' => $maxUploadCount,
|
|
'added' => $added,
|
|
];
|
|
}
|
|
|
|
private function storeGalleryUpload(Animal $animal, UploadedFile $file): ?string
|
|
{
|
|
$random = Yii::$app->security->generateRandomString(8);
|
|
$extension = strtolower((string)$file->extension);
|
|
$fileName = 'gallery-' . time() . '-' . $random . '.' . $extension;
|
|
|
|
$candidateDirs = [
|
|
'/uploads/animal-management/gallery/' . (int)$animal->id,
|
|
'/uploads/animal-management-runtime/gallery/' . (int)$animal->id,
|
|
'/uploads/animal-media/gallery/' . (int)$animal->id,
|
|
'/assets/animal-management-runtime/gallery/' . (int)$animal->id,
|
|
];
|
|
|
|
foreach ($candidateDirs as $relativeDir) {
|
|
$absoluteDir = Yii::getAlias('@webroot') . $relativeDir;
|
|
try {
|
|
FileHelper::createDirectory($absoluteDir, 0775, true);
|
|
} catch (\Throwable $e) {
|
|
Yii::warning([
|
|
'message' => $e->getMessage(),
|
|
'relative_dir' => $relativeDir,
|
|
'absolute_dir' => $absoluteDir,
|
|
], 'animal_management.gallery_upload_dir');
|
|
continue;
|
|
}
|
|
|
|
if (!is_dir($absoluteDir) || !is_writable($absoluteDir)) {
|
|
continue;
|
|
}
|
|
|
|
$absolutePath = $absoluteDir . '/' . $fileName;
|
|
if ($file->saveAs($absolutePath)) {
|
|
return $relativeDir . '/' . $fileName;
|
|
}
|
|
}
|
|
|
|
Yii::warning([
|
|
'message' => 'Could not save uploaded gallery file in any candidate directory.',
|
|
'animal_id' => (int)$animal->id,
|
|
'file_name' => $fileName,
|
|
'candidate_dirs' => $candidateDirs,
|
|
], 'animal_management.gallery_upload_dir');
|
|
|
|
return null;
|
|
}
|
|
|
|
private function computeImageContentHash(string $pathOrUrl): ?string
|
|
{
|
|
$pathOrUrl = trim($pathOrUrl);
|
|
if ($pathOrUrl === '') {
|
|
return null;
|
|
}
|
|
|
|
$absolutePath = $pathOrUrl;
|
|
if (substr($absolutePath, 0, 1) === '/') {
|
|
$absolutePath = Yii::getAlias('@webroot') . $absolutePath;
|
|
}
|
|
|
|
if (!is_file($absolutePath) || !is_readable($absolutePath)) {
|
|
return null;
|
|
}
|
|
|
|
$hash = @hash_file('sha1', $absolutePath);
|
|
return is_string($hash) && $hash !== '' ? $hash : null;
|
|
}
|
|
|
|
private function syncPrimaryImagesToGallery(Animal $animal): void
|
|
{
|
|
if (Yii::$app->db->schema->getTableSchema('rescue_field_definition', true) === null
|
|
|| Yii::$app->db->schema->getTableSchema('rescue_animal_field_value', true) === null
|
|
|| Yii::$app->db->schema->getTableSchema('rescue_animal_gallery_item', true) === null
|
|
) {
|
|
return;
|
|
}
|
|
|
|
$definitions = RescueFieldDefinition::find()
|
|
->select(['id', 'field_key'])
|
|
->where([
|
|
'module_id' => 'animal_management',
|
|
'group_key' => 'animal_profile',
|
|
'field_key' => ['cover_image_url', 'profile_image_url'],
|
|
])
|
|
->all();
|
|
|
|
if (empty($definitions)) {
|
|
return;
|
|
}
|
|
|
|
$byDefinitionId = [];
|
|
foreach ($definitions as $definition) {
|
|
$byDefinitionId[(int)$definition->id] = (string)$definition->field_key;
|
|
}
|
|
|
|
$fieldRows = AnimalFieldValue::find()
|
|
->where([
|
|
'animal_id' => (int)$animal->id,
|
|
'field_definition_id' => array_keys($byDefinitionId),
|
|
])
|
|
->all();
|
|
|
|
if (empty($fieldRows)) {
|
|
return;
|
|
}
|
|
|
|
$existingItems = AnimalGalleryItem::find()->where(['animal_id' => (int)$animal->id])->all();
|
|
$existingByPath = [];
|
|
$existingHashes = [];
|
|
foreach ($existingItems as $existingItem) {
|
|
$path = trim((string)$existingItem->file_path);
|
|
if ($path !== '') {
|
|
$existingByPath[$path] = true;
|
|
}
|
|
|
|
$url = trim((string)$existingItem->getImageUrl());
|
|
if ($url !== '') {
|
|
$existingByPath[$url] = true;
|
|
}
|
|
|
|
$hash = $this->computeImageContentHash($url !== '' ? $url : $path);
|
|
if ($hash !== null) {
|
|
$existingHashes[$hash] = true;
|
|
}
|
|
}
|
|
|
|
foreach ($fieldRows as $fieldRow) {
|
|
$imagePath = trim((string)$fieldRow->value_text);
|
|
if ($imagePath === '') {
|
|
continue;
|
|
}
|
|
|
|
if (!$this->isResolvableImageUrl($imagePath)) {
|
|
continue;
|
|
}
|
|
|
|
if (isset($existingByPath[$imagePath])) {
|
|
continue;
|
|
}
|
|
|
|
$hash = $this->computeImageContentHash($imagePath);
|
|
if ($hash !== null && isset($existingHashes[$hash])) {
|
|
continue;
|
|
}
|
|
|
|
$fieldKey = $byDefinitionId[(int)$fieldRow->field_definition_id] ?? 'upload';
|
|
$item = new AnimalGalleryItem();
|
|
$item->animal_id = (int)$animal->id;
|
|
$item->file_path = $imagePath;
|
|
$item->source_type = $fieldKey === 'cover_image_url' ? 'cover' : 'profile';
|
|
$item->created_by = Yii::$app->user->isGuest ? null : (int)Yii::$app->user->id;
|
|
|
|
if ($item->save()) {
|
|
$existingByPath[$imagePath] = true;
|
|
if ($hash !== null) {
|
|
$existingHashes[$hash] = true;
|
|
}
|
|
} else {
|
|
Yii::warning($item->getErrors(), 'animal_management.primary_image_gallery_sync');
|
|
}
|
|
}
|
|
}
|
|
|
|
private function isPrimaryImagePathReferenced(int $animalId, string $path): bool
|
|
{
|
|
if (Yii::$app->db->schema->getTableSchema('rescue_field_definition', true) === null
|
|
|| Yii::$app->db->schema->getTableSchema('rescue_animal_field_value', true) === null
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
$definitionIds = RescueFieldDefinition::find()
|
|
->select('id')
|
|
->where([
|
|
'module_id' => 'animal_management',
|
|
'group_key' => 'animal_profile',
|
|
'field_key' => ['cover_image_url', 'profile_image_url'],
|
|
])
|
|
->column();
|
|
|
|
if (empty($definitionIds)) {
|
|
return false;
|
|
}
|
|
|
|
return AnimalFieldValue::find()
|
|
->where([
|
|
'animal_id' => $animalId,
|
|
'field_definition_id' => $definitionIds,
|
|
'value_text' => $path,
|
|
])
|
|
->exists();
|
|
}
|
|
|
|
private function isResolvableImageUrl(string $pathOrUrl): bool
|
|
{
|
|
$pathOrUrl = trim($pathOrUrl);
|
|
if ($pathOrUrl === '') {
|
|
return false;
|
|
}
|
|
|
|
if (preg_match('/^https?:\/\//i', $pathOrUrl)) {
|
|
return true;
|
|
}
|
|
|
|
if (substr($pathOrUrl, 0, 1) !== '/') {
|
|
return false;
|
|
}
|
|
|
|
$absolutePath = Yii::getAlias('@webroot') . $pathOrUrl;
|
|
return is_file($absolutePath) && is_readable($absolutePath);
|
|
}
|
|
|
|
private function applyProgressMediaInput(AnimalProgressUpdateForm $form, Animal $animal): void
|
|
{
|
|
$removeProgressMedia = (int)Yii::$app->request->post('removeProgressMedia', 0) === 1;
|
|
if ($removeProgressMedia) {
|
|
$form->customFields['media_reference'] = '';
|
|
return;
|
|
}
|
|
|
|
$selectedGalleryPath = trim((string)Yii::$app->request->post('progressMediaGalleryPath', ''));
|
|
if ($selectedGalleryPath === '') {
|
|
$selectedGalleryPath = trim((string)Yii::$app->request->post('AnimalProgressUpdateForm.progressMediaGalleryPath', ''));
|
|
}
|
|
|
|
if ($selectedGalleryPath === '') {
|
|
$formPayload = Yii::$app->request->post('AnimalProgressUpdateForm', []);
|
|
if (is_array($formPayload) && isset($formPayload['progressMediaGalleryPath'])) {
|
|
$selectedGalleryPath = trim((string)$formPayload['progressMediaGalleryPath']);
|
|
}
|
|
}
|
|
|
|
$uploadedMedia = UploadedFile::getInstanceByName('progressMediaUpload');
|
|
if (!$uploadedMedia instanceof UploadedFile) {
|
|
$uploadedMedia = UploadedFile::getInstanceByName('AnimalProgressUpdateForm[progressMediaUpload]');
|
|
}
|
|
|
|
if (!$uploadedMedia instanceof UploadedFile) {
|
|
$uploadedCandidates = UploadedFile::getInstancesByName('progressMediaUpload');
|
|
if (!empty($uploadedCandidates) && $uploadedCandidates[0] instanceof UploadedFile) {
|
|
$uploadedMedia = $uploadedCandidates[0];
|
|
}
|
|
}
|
|
|
|
if ($uploadedMedia instanceof UploadedFile) {
|
|
$allowedExtensions = array_map('strtolower', UploadStandards::imageExtensions());
|
|
$allowedMimeTypes = array_map('strtolower', UploadStandards::imageMimeTypes());
|
|
$maxBytes = (int)UploadStandards::IMAGE_MAX_BYTES;
|
|
|
|
$extension = strtolower((string)$uploadedMedia->extension);
|
|
$mimeType = strtolower((string)$uploadedMedia->type);
|
|
|
|
if (!in_array($extension, $allowedExtensions, true)
|
|
|| ($mimeType !== '' && !in_array($mimeType, $allowedMimeTypes, true))
|
|
|| $uploadedMedia->size > $maxBytes
|
|
) {
|
|
$form->addError('customFields', Yii::t('AnimalManagementModule.base', 'Progress media image must be a valid image within size limits.'));
|
|
return;
|
|
}
|
|
|
|
$storedPath = $this->storeGalleryUpload($animal, $uploadedMedia);
|
|
if ($storedPath === null) {
|
|
Yii::warning([
|
|
'message' => 'storeGalleryUpload returned null for progress media.',
|
|
'animal_id' => (int)$animal->id,
|
|
'progress_update_id' => $form->progressUpdate instanceof AnimalProgressUpdate ? (int)$form->progressUpdate->id : null,
|
|
'uploaded_media_name' => (string)$uploadedMedia->name,
|
|
], 'animal_management.progress_media');
|
|
$form->addError('customFields', Yii::t('AnimalManagementModule.base', 'Could not upload progress media image.'));
|
|
return;
|
|
}
|
|
|
|
$this->addImagePathToGalleryIfMissing($animal, $storedPath, 'progress');
|
|
$form->customFields['media_reference'] = $storedPath;
|
|
return;
|
|
}
|
|
|
|
if (
|
|
$selectedGalleryPath !== ''
|
|
&& (preg_match('/^https?:\/\//i', $selectedGalleryPath) || substr($selectedGalleryPath, 0, 1) === '/')
|
|
) {
|
|
$this->addImagePathToGalleryIfMissing($animal, $selectedGalleryPath, 'progress');
|
|
$form->customFields['media_reference'] = $selectedGalleryPath;
|
|
return;
|
|
}
|
|
|
|
// Preserve existing media reference when no explicit media action was posted.
|
|
if ($form->progressUpdate instanceof AnimalProgressUpdate) {
|
|
$existingMedia = $this->resolveProgressMediaReferenceFromRecord($form->progressUpdate);
|
|
if ($existingMedia !== '') {
|
|
$form->customFields['media_reference'] = $existingMedia;
|
|
}
|
|
}
|
|
}
|
|
|
|
private function resolveProgressMediaReferenceFromRecord(AnimalProgressUpdate $record): string
|
|
{
|
|
foreach ($record->getCustomFieldDisplayValues(true) as $customField) {
|
|
if ((string)($customField['field_key'] ?? '') !== 'media_reference') {
|
|
continue;
|
|
}
|
|
|
|
$value = trim((string)($customField['value'] ?? ''));
|
|
if ($value !== '') {
|
|
return $value;
|
|
}
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
private function applyMedicalMediaInput(AnimalMedicalVisitForm $form, Animal $animal): void
|
|
{
|
|
if ((int)Yii::$app->request->post('removeMedicalMedia', 0) === 1) {
|
|
$form->customFields['medical_media_reference'] = '';
|
|
$form->customFields['media_reference'] = '';
|
|
return;
|
|
}
|
|
|
|
$selectedGalleryPath = trim((string)Yii::$app->request->post('medicalMediaGalleryPath', ''));
|
|
$uploadedMedia = UploadedFile::getInstanceByName('medicalMediaUpload');
|
|
|
|
if ($uploadedMedia instanceof UploadedFile) {
|
|
$allowedExtensions = array_map('strtolower', UploadStandards::imageExtensions());
|
|
$allowedMimeTypes = array_map('strtolower', UploadStandards::imageMimeTypes());
|
|
$maxBytes = (int)UploadStandards::IMAGE_MAX_BYTES;
|
|
|
|
$extension = strtolower((string)$uploadedMedia->extension);
|
|
$mimeType = strtolower((string)$uploadedMedia->type);
|
|
|
|
if (!in_array($extension, $allowedExtensions, true)
|
|
|| ($mimeType !== '' && !in_array($mimeType, $allowedMimeTypes, true))
|
|
|| $uploadedMedia->size > $maxBytes
|
|
) {
|
|
$form->addError('customFields', Yii::t('AnimalManagementModule.base', 'Medical media image must be a valid image within size limits.'));
|
|
return;
|
|
}
|
|
|
|
$storedPath = $this->storeGalleryUpload($animal, $uploadedMedia);
|
|
if ($storedPath === null) {
|
|
$form->addError('customFields', Yii::t('AnimalManagementModule.base', 'Could not upload medical media image.'));
|
|
return;
|
|
}
|
|
|
|
$this->addImagePathToGalleryIfMissing($animal, $storedPath, 'medical');
|
|
$form->customFields['medical_media_reference'] = $storedPath;
|
|
$form->customFields['media_reference'] = $storedPath;
|
|
return;
|
|
}
|
|
|
|
if (
|
|
$selectedGalleryPath !== ''
|
|
&& (preg_match('/^https?:\/\//i', $selectedGalleryPath) || substr($selectedGalleryPath, 0, 1) === '/')
|
|
) {
|
|
$this->addImagePathToGalleryIfMissing($animal, $selectedGalleryPath, 'medical');
|
|
$form->customFields['medical_media_reference'] = $selectedGalleryPath;
|
|
$form->customFields['media_reference'] = $selectedGalleryPath;
|
|
}
|
|
}
|
|
|
|
private function ensureMedicalMediaFieldDefinition(): void
|
|
{
|
|
if (!class_exists(RescueFieldDefinition::class)
|
|
|| Yii::$app->db->schema->getTableSchema('rescue_field_definition', true) === null
|
|
) {
|
|
return;
|
|
}
|
|
|
|
$definition = RescueFieldDefinition::findOne([
|
|
'module_id' => 'animal_management',
|
|
'field_key' => 'medical_media_reference',
|
|
]);
|
|
|
|
if ($definition instanceof RescueFieldDefinition) {
|
|
$needsSave = false;
|
|
if ((string)$definition->group_key !== 'animal_medical_visit') {
|
|
$definition->group_key = 'animal_medical_visit';
|
|
$needsSave = true;
|
|
}
|
|
if ((int)$definition->is_active !== 1) {
|
|
$definition->is_active = 1;
|
|
$needsSave = true;
|
|
}
|
|
if ((string)$definition->visibility !== 'public') {
|
|
$definition->visibility = 'public';
|
|
$needsSave = true;
|
|
}
|
|
if ((string)$definition->label === '') {
|
|
$definition->label = 'Media';
|
|
$needsSave = true;
|
|
}
|
|
if ($needsSave) {
|
|
$definition->updated_at = date('Y-m-d H:i:s');
|
|
$definition->save();
|
|
}
|
|
return;
|
|
}
|
|
|
|
$definition = new RescueFieldDefinition();
|
|
$definition->module_id = 'animal_management';
|
|
$definition->group_key = 'animal_medical_visit';
|
|
$definition->field_key = 'medical_media_reference';
|
|
$definition->label = 'Media';
|
|
$definition->input_type = 'text';
|
|
$definition->required = 0;
|
|
$definition->is_core = 1;
|
|
$definition->is_active = 1;
|
|
$definition->visibility = 'public';
|
|
$definition->options = '{}';
|
|
$definition->sort_order = 220;
|
|
$definition->created_at = date('Y-m-d H:i:s');
|
|
$definition->updated_at = date('Y-m-d H:i:s');
|
|
$definition->save();
|
|
}
|
|
|
|
private function addImagePathToGalleryIfMissing(Animal $animal, string $imagePath, string $sourceType = 'upload'): void
|
|
{
|
|
$imagePath = trim($imagePath);
|
|
if ($imagePath === '') {
|
|
return;
|
|
}
|
|
|
|
$existingItems = AnimalGalleryItem::find()->where(['animal_id' => (int)$animal->id])->all();
|
|
$incomingHash = $this->computeImageContentHash($imagePath);
|
|
foreach ($existingItems as $existingItem) {
|
|
$existingPath = trim((string)$existingItem->file_path);
|
|
$existingUrl = trim((string)$existingItem->getImageUrl());
|
|
|
|
if ($existingPath === $imagePath || $existingUrl === $imagePath) {
|
|
return;
|
|
}
|
|
|
|
if ($incomingHash !== null) {
|
|
$existingHash = $this->computeImageContentHash($existingUrl !== '' ? $existingUrl : $existingPath);
|
|
if ($existingHash !== null && hash_equals($incomingHash, $existingHash)) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (substr($imagePath, 0, 1) !== '/') {
|
|
return;
|
|
}
|
|
|
|
$item = new AnimalGalleryItem();
|
|
$item->animal_id = (int)$animal->id;
|
|
$item->file_path = $imagePath;
|
|
$item->source_type = $sourceType;
|
|
$item->created_by = Yii::$app->user->isGuest ? null : (int)Yii::$app->user->id;
|
|
$item->save();
|
|
}
|
|
|
|
private function canUseIntakeGoalSection(): bool
|
|
{
|
|
if (!($this->contentContainer instanceof Space)) {
|
|
return false;
|
|
}
|
|
|
|
if (!$this->contentContainer->moduleManager->isEnabled('donations')) {
|
|
return false;
|
|
}
|
|
|
|
$goalFormClass = 'humhub\\modules\\donations\\models\\forms\\DonationGoalForm';
|
|
$goalClass = 'humhub\\modules\\donations\\models\\DonationGoal';
|
|
|
|
if (!class_exists($goalFormClass) || !class_exists($goalClass)) {
|
|
return false;
|
|
}
|
|
|
|
return Yii::$app->db->schema->getTableSchema($goalClass::tableName(), true) !== null;
|
|
}
|
|
|
|
private function createIntakeAnimalGoal(Animal $animal, array $payload): ?string
|
|
{
|
|
if ((int)($payload['enabled'] ?? 0) !== 1) {
|
|
return null;
|
|
}
|
|
|
|
if (!$this->canUseIntakeGoalSection()) {
|
|
return Yii::t('AnimalManagementModule.base', 'Animal was created, but donation goal setup is not available in this space.');
|
|
}
|
|
|
|
$goalFormClass = 'humhub\\modules\\donations\\models\\forms\\DonationGoalForm';
|
|
$goalClass = 'humhub\\modules\\donations\\models\\DonationGoal';
|
|
|
|
$targetAmount = max(0.0, (float)($payload['target_amount'] ?? 0));
|
|
if ($targetAmount <= 0) {
|
|
return Yii::t('AnimalManagementModule.base', 'Animal was created, but the intake donation goal target must be greater than zero.');
|
|
}
|
|
|
|
$title = trim((string)($payload['title'] ?? ''));
|
|
if ($title === '') {
|
|
$title = Yii::t('AnimalManagementModule.base', '{animalName} Care Fund', ['animalName' => $animal->getDisplayName()]);
|
|
}
|
|
|
|
$description = trim((string)($payload['description'] ?? ''));
|
|
$imagePath = trim((string)($payload['image_path'] ?? ''));
|
|
$isActive = (int)($payload['is_active'] ?? 1) === 1;
|
|
|
|
$goalForm = new $goalFormClass();
|
|
$goalForm->contentContainer = $this->contentContainer;
|
|
$goalForm->goal_type = $goalClass::TYPE_ANIMAL;
|
|
$goalForm->target_animal_id = (int)$animal->id;
|
|
$goalForm->title = $title;
|
|
$goalForm->description = $description;
|
|
$goalForm->target_amount = $targetAmount;
|
|
$goalForm->is_active = $isActive;
|
|
|
|
if ($imagePath !== '') {
|
|
$goalForm->imageGalleryPath = $imagePath;
|
|
}
|
|
|
|
$savedGoal = $goalForm->save();
|
|
if ($savedGoal === null) {
|
|
$firstErrors = $goalForm->getFirstErrors();
|
|
$errorText = !empty($firstErrors) ? implode(' ', array_values($firstErrors)) : Yii::t('AnimalManagementModule.base', 'Unknown donation goal error.');
|
|
return Yii::t('AnimalManagementModule.base', 'Animal was created, but donation goal could not be saved: {error}', ['error' => $errorText]);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|