Initial import of animal_management module

This commit is contained in:
Kelin Rescue Hub
2026-04-04 13:13:00 -04:00
commit 20adb1bd1e
65 changed files with 14004 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
<?php
namespace humhub\modules\animal_management\notifications;
use humhub\modules\notification\components\NotificationCategory;
use Yii;
class TransferNotificationCategory extends NotificationCategory
{
public $id = 'animal_management_transfer';
public function getTitle()
{
return Yii::t('AnimalManagementModule.base', 'Animal Transfers');
}
public function getDescription()
{
return Yii::t('AnimalManagementModule.base', 'Receive notifications about animal transfer requests and status updates.');
}
}

View File

@@ -0,0 +1,120 @@
<?php
namespace humhub\modules\animal_management\notifications;
use humhub\modules\animal_management\models\AnimalTransfer;
use humhub\modules\space\models\Space;
use humhub\modules\user\models\User;
class TransferNotifier
{
public static function notifyStatusChange(AnimalTransfer $transfer, string $eventType, ?User $originator = null, string $details = ''): int
{
$animal = $transfer->animal;
if ($animal === null) {
return 0;
}
$fromSpace = $transfer->getFromSpace();
$toSpace = $transfer->getToSpace();
if (!$fromSpace instanceof Space || !$toSpace instanceof Space) {
return 0;
}
$targetSpaces = static::targetSpacesForEvent($eventType, $fromSpace, $toSpace);
$sentCount = 0;
$seenRecipients = [];
foreach ($targetSpaces as $space) {
$recipients = static::privilegedUsersForSpace($space);
foreach ($recipients as $recipient) {
$recipientId = (int)$recipient->id;
if ($originator instanceof User && $recipientId === (int)$originator->id) {
continue;
}
if (isset($seenRecipients[$recipientId])) {
continue;
}
$seenRecipients[$recipientId] = true;
$notification = TransferStatusNotification::instance();
if ($originator instanceof User) {
$notification->from($originator);
}
$notification->animalName = $animal->getDisplayName();
$notification->fromSpaceName = (string)$fromSpace->name;
$notification->toSpaceName = (string)$toSpace->name;
$notification->spaceGuid = (string)$space->guid;
$notification->eventType = $eventType;
$notification->details = $details;
$notification->payload([
'animalName' => $notification->animalName,
'fromSpaceName' => $notification->fromSpaceName,
'toSpaceName' => $notification->toSpaceName,
'spaceGuid' => $notification->spaceGuid,
'eventType' => $notification->eventType,
'details' => $notification->details,
]);
$notification->send($recipient);
$sentCount++;
}
}
return $sentCount;
}
public static function privilegedUsersForSpace(Space $space): array
{
$recipients = [];
foreach ($space->getPrivilegedGroupUsers() as $users) {
foreach ($users as $user) {
if ($user instanceof User && (int)$user->status === User::STATUS_ENABLED) {
$recipients[(int)$user->id] = $user;
}
}
}
if (empty($recipients)) {
$owner = $space->getOwnerUser()->one();
if ($owner instanceof User && (int)$owner->status === User::STATUS_ENABLED) {
$recipients[(int)$owner->id] = $owner;
}
}
return $recipients;
}
private static function targetSpacesForEvent(string $eventType, Space $fromSpace, Space $toSpace): array
{
$spaces = [];
switch ($eventType) {
case 'accepted':
case 'declined':
$spaces[] = $fromSpace;
break;
case 'cancelled':
$spaces[] = $toSpace;
break;
case 'completed':
$spaces[] = $fromSpace;
break;
default:
$spaces[] = $fromSpace;
$spaces[] = $toSpace;
break;
}
$unique = [];
foreach ($spaces as $space) {
$key = (int)$space->contentcontainer_id;
if (!isset($unique[$key])) {
$unique[$key] = $space;
}
}
return array_values($unique);
}
}

View File

@@ -0,0 +1,109 @@
<?php
namespace humhub\modules\animal_management\notifications;
use humhub\libs\Html;
use humhub\modules\notification\components\BaseNotification;
use humhub\modules\space\models\Space;
use Yii;
use yii\helpers\Json;
use yii\helpers\Url;
class TransferRequestedNotification extends BaseNotification
{
public $moduleId = 'animal_management';
public $requireSource = false;
public $requireOriginator = false;
public string $animalName = '';
public string $fromSpaceName = '';
public string $toSpaceGuid = '';
public function category()
{
return new TransferNotificationCategory();
}
public function getUrl()
{
$toSpaceGuid = $this->payloadString('toSpaceGuid', $this->toSpaceGuid);
$space = Space::findOne(['guid' => $toSpaceGuid]);
if ($space instanceof Space) {
return $space->createUrl('/animal_management/animals/index') . '#incoming-transfers';
}
return Url::to(['/animal_management/animals/index', 'sguid' => $toSpaceGuid]) . '#incoming-transfers';
}
public function html()
{
$animalName = $this->payloadString('animalName', $this->animalName);
if ($this->originator) {
return Yii::t('AnimalManagementModule.base', '{displayName} requested to transfer {animalName}.', [
'displayName' => Html::tag('strong', Html::encode($this->originator->displayName)),
'animalName' => Html::tag('strong', Html::encode($animalName)),
]);
}
return Yii::t('AnimalManagementModule.base', 'A transfer was requested for {animalName}.', [
'animalName' => Html::tag('strong', Html::encode($animalName)),
]);
}
public function getMailSubject()
{
$animalName = $this->payloadString('animalName', $this->animalName);
return Yii::t('AnimalManagementModule.base', 'Animal transfer request: {animalName}', [
'animalName' => $animalName,
]);
}
public function __serialize(): array
{
$data = parent::__serialize();
$data['animalName'] = $this->animalName;
$data['fromSpaceName'] = $this->fromSpaceName;
$data['toSpaceGuid'] = $this->toSpaceGuid;
$data['payload'] = $this->payload;
return $data;
}
public function __unserialize($unserializedArr)
{
parent::__unserialize($unserializedArr);
$this->animalName = (string)($unserializedArr['animalName'] ?? '');
$this->fromSpaceName = (string)($unserializedArr['fromSpaceName'] ?? '');
$this->toSpaceGuid = (string)($unserializedArr['toSpaceGuid'] ?? '');
if (isset($unserializedArr['payload']) && is_array($unserializedArr['payload'])) {
$this->payload = $unserializedArr['payload'];
}
}
private function payloadString(string $key, string $fallback = ''): string
{
if (is_array($this->payload) && array_key_exists($key, $this->payload)) {
return trim((string)$this->payload[$key]);
}
if ($this->record !== null && !empty($this->record->payload)) {
try {
$decoded = Json::decode((string)$this->record->payload);
if (is_array($decoded)) {
$this->payload = $decoded;
if (array_key_exists($key, $decoded)) {
return trim((string)$decoded[$key]);
}
}
} catch (\Throwable $e) {
// Fall back to explicit property values when payload is unavailable.
}
}
return trim($fallback);
}
}

View File

@@ -0,0 +1,162 @@
<?php
namespace humhub\modules\animal_management\notifications;
use humhub\libs\Html;
use humhub\modules\notification\components\BaseNotification;
use humhub\modules\space\models\Space;
use Yii;
use yii\helpers\Json;
use yii\helpers\Url;
class TransferStatusNotification extends BaseNotification
{
public $moduleId = 'animal_management';
public $requireSource = false;
public $requireOriginator = false;
public string $animalName = '';
public string $fromSpaceName = '';
public string $toSpaceName = '';
public string $spaceGuid = '';
public string $eventType = '';
public string $details = '';
public function category()
{
return new TransferNotificationCategory();
}
public function getUrl()
{
$eventType = $this->payloadString('eventType', $this->eventType);
$spaceGuid = $this->payloadString('spaceGuid', $this->spaceGuid);
$anchor = static::eventAnchor($eventType);
$space = Space::findOne(['guid' => $spaceGuid]);
if ($space instanceof Space) {
return $space->createUrl('/animal_management/animals/index') . '#' . $anchor;
}
return Url::to(['/animal_management/animals/index', 'sguid' => $spaceGuid]) . '#' . $anchor;
}
public function html()
{
$animalName = $this->payloadString('animalName', $this->animalName);
$eventType = $this->payloadString('eventType', $this->eventType);
$details = $this->payloadString('details', $this->details);
$params = [
'animalName' => Html::tag('strong', Html::encode($animalName)),
'status' => Html::encode(static::statusLabel($eventType)),
];
if ($this->originator) {
$params['displayName'] = Html::tag('strong', Html::encode($this->originator->displayName));
$message = Yii::t('AnimalManagementModule.base', '{displayName} {status} the transfer of {animalName}.', $params);
} else {
$message = Yii::t('AnimalManagementModule.base', '{status} the transfer of {animalName}.', $params);
}
if ($details !== '') {
$message .= ' ' . Html::encode($details);
}
return $message;
}
public function getMailSubject()
{
$animalName = $this->payloadString('animalName', $this->animalName);
$eventType = $this->payloadString('eventType', $this->eventType);
return Yii::t('AnimalManagementModule.base', 'Animal transfer update: {animalName} ({status})', [
'animalName' => $animalName,
'status' => static::statusLabel($eventType),
]);
}
public function __serialize(): array
{
$data = parent::__serialize();
$data['animalName'] = $this->animalName;
$data['fromSpaceName'] = $this->fromSpaceName;
$data['toSpaceName'] = $this->toSpaceName;
$data['spaceGuid'] = $this->spaceGuid;
$data['eventType'] = $this->eventType;
$data['details'] = $this->details;
$data['payload'] = $this->payload;
return $data;
}
public function __unserialize($unserializedArr)
{
parent::__unserialize($unserializedArr);
$this->animalName = (string)($unserializedArr['animalName'] ?? '');
$this->fromSpaceName = (string)($unserializedArr['fromSpaceName'] ?? '');
$this->toSpaceName = (string)($unserializedArr['toSpaceName'] ?? '');
$this->spaceGuid = (string)($unserializedArr['spaceGuid'] ?? '');
$this->eventType = (string)($unserializedArr['eventType'] ?? '');
$this->details = (string)($unserializedArr['details'] ?? '');
if (isset($unserializedArr['payload']) && is_array($unserializedArr['payload'])) {
$this->payload = $unserializedArr['payload'];
}
}
public static function statusLabel(string $eventType): string
{
switch ($eventType) {
case 'accepted':
return Yii::t('AnimalManagementModule.base', 'Accepted');
case 'declined':
return Yii::t('AnimalManagementModule.base', 'Declined');
case 'completed':
return Yii::t('AnimalManagementModule.base', 'Completed');
case 'cancelled':
return Yii::t('AnimalManagementModule.base', 'Cancelled');
default:
return Yii::t('AnimalManagementModule.base', 'Updated');
}
}
private static function eventAnchor(string $eventType): string
{
switch ($eventType) {
case 'requested':
case 'cancelled':
return 'incoming-transfers';
case 'accepted':
case 'declined':
case 'completed':
return 'outgoing-transfers';
default:
return 'outgoing-transfers';
}
}
private function payloadString(string $key, string $fallback = ''): string
{
if (is_array($this->payload) && array_key_exists($key, $this->payload)) {
return trim((string)$this->payload[$key]);
}
if ($this->record !== null && !empty($this->record->payload)) {
try {
$decoded = Json::decode((string)$this->record->payload);
if (is_array($decoded)) {
$this->payload = $decoded;
if (array_key_exists($key, $decoded)) {
return trim((string)$decoded[$key]);
}
}
} catch (\Throwable $e) {
// Fall back to explicit property values when payload is unavailable.
}
}
return trim($fallback);
}
}