Files
donations/Events.php

436 lines
26 KiB
PHP

<?php
namespace humhub\modules\donations;
use humhub\modules\donations\events\DonationSettlementEvent;
use humhub\modules\donations\models\DonationGoal;
use humhub\modules\donations\models\DonationTransaction;
use humhub\modules\donations\services\DonationAnimalIntegrationService;
use humhub\modules\donations\services\DonationNotificationService;
use humhub\modules\space\models\Space;
use humhub\modules\user\models\User;
use Yii;
use yii\helpers\Html;
class Events
{
public static function onSpaceMenuInit($event): void
{
$space = $event->sender->space ?? null;
if ($space === null || !$space->moduleManager->isEnabled('donations')) {
return;
}
$event->sender->addItem([
'label' => Yii::t('DonationsModule.base', 'My Donations'),
'group' => 'modules',
'url' => $space->createUrl('/donations/donations/index'),
'icon' => '<i class="fa fa-heart"></i>',
'sortOrder' => 120,
'isActive' => (
Yii::$app->controller
&& Yii::$app->controller->module
&& Yii::$app->controller->module->id === 'donations'
&& Yii::$app->controller->id === 'donations'
),
]);
}
public static function onRescueSettingsMenuInit($event): void
{
$space = $event->sender->space ?? null;
if ($space === null || !$space->moduleManager->isEnabled('donations')) {
return;
}
$event->sender->addItem([
'label' => Yii::t('DonationsModule.base', 'Donations'),
'url' => $space->createUrl('/donations/settings'),
'sortOrder' => 400,
'isActive' => (
Yii::$app->controller
&& Yii::$app->controller->module
&& Yii::$app->controller->module->id === 'donations'
&& Yii::$app->controller->id === 'settings'
),
]);
}
public static function onSpaceAdminMenuInitFallback($event): void
{
$space = $event->sender->space ?? null;
if ($space === null || !$space->moduleManager->isEnabled('donations')) {
return;
}
if ($space->moduleManager->isEnabled('rescue_foundation') || !$space->isAdmin()) {
return;
}
$event->sender->addItem([
'label' => Yii::t('DonationsModule.base', 'Donations'),
'group' => 'admin',
'url' => $space->createUrl('/donations/settings'),
'icon' => '<i class="fa fa-heart"></i>',
'sortOrder' => 620,
'isActive' => (
Yii::$app->controller
&& Yii::$app->controller->module
&& Yii::$app->controller->module->id === 'donations'
&& Yii::$app->controller->id === 'settings'
),
]);
}
public static function onDonationTransactionSucceeded($event): void
{
if (!$event instanceof DonationSettlementEvent) {
return;
}
$payload = is_array($event->payload) ? $event->payload : [];
$animalIntegration = new DonationAnimalIntegrationService();
$payload = $animalIntegration->applySucceededIntegration($event->transaction, $event->goal, $payload);
$notificationIntegration = new DonationNotificationService();
$payload = $notificationIntegration->applySucceededNotifications($event->transaction, $event->goal, $payload);
$event->payload = $payload;
}
public static function onDonationTransactionRefunded($event): void
{
if (!$event instanceof DonationSettlementEvent) {
return;
}
$payload = is_array($event->payload) ? $event->payload : [];
$animalIntegration = new DonationAnimalIntegrationService();
$payload = $animalIntegration->applyRefundedIntegration($event->transaction, $event->goal, $payload);
$notificationIntegration = new DonationNotificationService();
$payload = $notificationIntegration->applyRefundedNotifications($event->transaction, $event->goal, $payload);
$event->payload = $payload;
}
public static function onAnimalTileOverlayRender($event): void
{
$hookClass = 'humhub\\modules\\animal_management\\events\\AnimalTileRenderEvent';
if (!class_exists($hookClass) || !$event instanceof $hookClass) {
return;
}
$space = $event->contentContainer ?? null;
if (!$space instanceof Space || !$space->moduleManager->isEnabled('donations')) {
return;
}
if (Yii::$app->db->schema->getTableSchema(DonationGoal::tableName(), true) === null) {
return;
}
$goal = $event->existingDonationGoal ?? null;
if (!$goal instanceof DonationGoal) {
$goal = DonationGoal::find()
->where([
'contentcontainer_id' => (int)$space->contentcontainer_id,
'goal_type' => DonationGoal::TYPE_ANIMAL,
'target_animal_id' => (int)($event->animal->id ?? 0),
])
->orderBy(['is_active' => SORT_DESC, 'id' => SORT_DESC])
->one();
}
if (!$goal instanceof DonationGoal) {
return;
}
$target = (float)$goal->target_amount;
$current = max(0.0, (float)$goal->current_amount);
$isFunded = $target > 0 && $current >= $target;
$percent = $target > 0 ? min(100.0, round(($current / $target) * 100, 1)) : 0.0;
$percentLabel = rtrim(rtrim(number_format($percent, 1, '.', ''), '0'), '.') . '%';
$targetLabel = '$' . number_format(round(max(0.0, $target)), 0);
$currentLabel = '$' . number_format(round($current), 0);
$statusColor = $isFunded ? '#4ce083' : '#ff6666';
$goalToggleInputId = 'donation-goal-toggle-' . (int)($event->animal->id ?? 0);
$quickDonateToggleInputId = 'donation-goal-quick-toggle-' . (int)($event->animal->id ?? 0);
$goalCardId = 'donation-goal-card-' . (int)($event->animal->id ?? 0);
$donorPanelId = 'donation-goal-donors-panel-' . (int)($event->animal->id ?? 0);
$quickDonatePanelId = 'donation-goal-quick-panel-' . (int)($event->animal->id ?? 0);
$donorPagerWrapId = 'donation-goal-donors-pager-' . (int)($event->animal->id ?? 0);
$donorPagerName = 'donation-goal-donors-page-' . (int)($event->animal->id ?? 0);
$donorRows = [];
if (Yii::$app->db->schema->getTableSchema(DonationTransaction::tableName(), true) !== null) {
$transactions = DonationTransaction::find()
->where([
'contentcontainer_id' => (int)$space->contentcontainer_id,
'goal_id' => (int)$goal->id,
'status' => DonationTransaction::STATUS_SUCCEEDED,
])
->orderBy(['id' => SORT_DESC])
->limit(200)
->all();
$totalsByUser = [];
$anonymousTotal = 0.0;
foreach ($transactions as $transaction) {
$amount = max(0.0, (float)$transaction->amount);
if ($amount <= 0) {
continue;
}
if ((int)$transaction->is_anonymous === 1 || (int)$transaction->donor_user_id <= 0) {
$anonymousTotal += $amount;
continue;
}
$userId = (int)$transaction->donor_user_id;
$totalsByUser[$userId] = ($totalsByUser[$userId] ?? 0.0) + $amount;
}
if (!empty($totalsByUser)) {
arsort($totalsByUser);
$users = User::find()
->where(['id' => array_keys($totalsByUser)])
->indexBy('id')
->all();
foreach ($totalsByUser as $userId => $totalAmount) {
$user = $users[$userId] ?? null;
if (!$user instanceof User) {
continue;
}
$avatarUrl = (string)$user->getProfileImage()->getUrl();
$profileUrl = (string)$user->getUrl();
$displayName = (string)$user->getDisplayName();
$amountText = '$' . number_format(round($totalAmount), 0);
$donorRows[] =
'<a href="' . Html::encode($profileUrl) . '" style="display:flex;align-items:center;gap:8px;padding:6px 0;color:#fff;text-decoration:none;">'
. '<img src="' . Html::encode($avatarUrl) . '" alt="" style="width:22px;height:22px;border-radius:999px;border:1px solid rgba(255,255,255,0.35);object-fit:cover;">'
. '<span style="font-size:14px;opacity:0.95;">' . Html::encode($displayName) . '</span>'
. '<span style="margin-left:auto;font-size:14px;font-weight:700;white-space:nowrap;">' . Html::encode($amountText) . '</span>'
. '</a>';
}
}
if ($anonymousTotal > 0) {
$donorRows[] =
'<div style="display:flex;align-items:center;gap:8px;padding:6px 0;color:#fff;">'
. '<span style="width:22px;height:22px;border-radius:999px;border:1px solid rgba(255,255,255,0.35);display:inline-flex;align-items:center;justify-content:center;background:rgba(255,255,255,0.12);">'
. '<i class="fa fa-user" style="font-size:11px;"></i>'
. '</span>'
. '<span style="font-size:14px;opacity:0.95;">' . Html::encode(Yii::t('DonationsModule.base', 'Anonymous')) . '</span>'
. '<span style="margin-left:auto;font-size:14px;font-weight:700;white-space:nowrap;">$' . Html::encode(number_format(round($anonymousTotal), 0)) . '</span>'
. '</div>';
}
}
$donorPageRows = empty($donorRows)
? ['<div style="font-size:13px;color:rgba(255,255,255,0.9);">' . Html::encode(Yii::t('DonationsModule.base', 'No donations yet.')) . '</div>']
: $donorRows;
$donorPageSize = 25;
$donorPages = array_chunk($donorPageRows, $donorPageSize);
$donorTotalPages = max(1, count($donorPages));
$donorPagesHtml = '';
foreach ($donorPages as $pageIndex => $pageRows) {
$pageNumber = $pageIndex + 1;
$donorPagesHtml .= '<div data-donor-page="' . (int)$pageNumber . '" style="display:' . ($pageNumber === 1 ? 'block' : 'none') . ';">'
. implode('', $pageRows)
. '</div>';
}
$donorPagerCss = '#'. Html::encode($donorPagerWrapId) . ' [data-donor-page],#' . Html::encode($donorPagerWrapId) . ' [data-donor-controls-page]{display:none;}';
$donorPagerRadioInputsHtml = '';
$donorPagerTopControlsHtml = '';
$donorPagerBottomControlsHtml = '';
foreach ($donorPages as $pageIndex => $pageRows) {
$pageNumber = $pageIndex + 1;
$radioId = $donorPagerName . '-' . (int)$pageNumber;
$prevRadioId = $donorPagerName . '-' . (int)max(1, $pageNumber - 1);
$nextRadioId = $donorPagerName . '-' . (int)min($donorTotalPages, $pageNumber + 1);
$donorPagerRadioInputsHtml .= '<input type="radio" id="' . Html::encode($radioId) . '" name="' . Html::encode($donorPagerName) . '" style="display:none;" ' . ($pageNumber === 1 ? 'checked' : '') . '>';
$prevControl = $pageNumber > 1
? '<label for="' . Html::encode($prevRadioId) . '" style="display:inline-block;border:1px solid rgba(255,255,255,0.25);background:rgba(255,255,255,0.08);color:#fff;border-radius:4px;padding:2px 7px;font-size:12px;cursor:pointer;">' . Html::encode(Yii::t('DonationsModule.base', 'Previous')) . '</label>'
: '<span style="display:inline-block;border:1px solid rgba(255,255,255,0.2);background:rgba(255,255,255,0.05);color:rgba(255,255,255,0.45);border-radius:4px;padding:2px 7px;font-size:12px;">' . Html::encode(Yii::t('DonationsModule.base', 'Previous')) . '</span>';
$nextControl = $pageNumber < $donorTotalPages
? '<label for="' . Html::encode($nextRadioId) . '" style="display:inline-block;border:1px solid rgba(255,255,255,0.25);background:rgba(255,255,255,0.08);color:#fff;border-radius:4px;padding:2px 7px;font-size:12px;cursor:pointer;">' . Html::encode(Yii::t('DonationsModule.base', 'Next')) . '</label>'
: '<span style="display:inline-block;border:1px solid rgba(255,255,255,0.2);background:rgba(255,255,255,0.05);color:rgba(255,255,255,0.45);border-radius:4px;padding:2px 7px;font-size:12px;">' . Html::encode(Yii::t('DonationsModule.base', 'Next')) . '</span>';
$pageLabel = Html::encode('Page ' . $pageNumber . ' / ' . $donorTotalPages);
$controlsHtml = '<div data-donor-controls-page="' . (int)$pageNumber . '" style="align-items:center;justify-content:space-between;gap:6px;">'
. $prevControl
. '<span style="font-size:12px;color:rgba(255,255,255,0.9);">' . $pageLabel . '</span>'
. $nextControl
. '</div>';
$donorPagerTopControlsHtml .= $controlsHtml;
$donorPagerBottomControlsHtml .= $controlsHtml;
$donorPagerCss .= '#' . Html::encode($radioId) . ':checked ~ [data-donor-top-controls] [data-donor-controls-page="' . (int)$pageNumber . '"]{display:flex;}';
$donorPagerCss .= '#' . Html::encode($radioId) . ':checked ~ [data-donor-pages] [data-donor-page="' . (int)$pageNumber . '"]{display:block;}';
$donorPagerCss .= '#' . Html::encode($radioId) . ':checked ~ [data-donor-bottom-controls] [data-donor-controls-page="' . (int)$pageNumber . '"]{display:flex;}';
}
$goalToggleCss = '<style>'
. '#' . Html::encode($goalToggleInputId) . ':checked ~ #' . Html::encode($donorPanelId) . '{display:block !important;opacity:1 !important;pointer-events:auto !important;}'
. '#' . Html::encode($quickDonateToggleInputId) . ':checked ~ #' . Html::encode($quickDonatePanelId) . '{display:flex !important;opacity:1 !important;pointer-events:auto !important;}'
. '#' . Html::encode($quickDonatePanelId) . ' input[name="amount"]::-webkit-outer-spin-button,#' . Html::encode($quickDonatePanelId) . ' input[name="amount"]::-webkit-inner-spin-button{-webkit-appearance:none;margin:0;}'
. '[data-animal-tile-overlay-stack]:has(#' . Html::encode($goalToggleInputId) . ':checked),'
. '[data-animal-tile-overlay-stack]:has(#' . Html::encode($quickDonateToggleInputId) . ':checked)'
. '{top:12px !important;bottom:12px !important;display:flex !important;flex-direction:column !important;justify-content:flex-start !important;}'
. '[data-animal-tile-overlay-stack]:has(#' . Html::encode($goalToggleInputId) . ':checked) #' . Html::encode($goalCardId) . ','
. '[data-animal-tile-overlay-stack]:has(#' . Html::encode($quickDonateToggleInputId) . ':checked) #' . Html::encode($goalCardId)
. '{background:rgba(7,10,16,0.34) !important;border-color:rgba(255,255,255,0.24) !important;}'
. $donorPagerCss
. '</style>';
$quickDonateHeaderTemplate = (string)Yii::$app->getModule('donations')->settings->contentContainer($space)
->get('animal_donation_form_header', Yii::t('DonationsModule.base', 'Your Donation directly supports [animal-name]'));
$quickDonateHeaderTemplate = trim($quickDonateHeaderTemplate);
if ($quickDonateHeaderTemplate === '') {
$quickDonateHeaderTemplate = Yii::t('DonationsModule.base', 'Your Donation directly supports [animal-name]');
}
$quickDonateHeaderText = str_replace('[animal-name]', (string)$event->animal->getDisplayName(), $quickDonateHeaderTemplate);
$quickDonateActionUrl = $space->createUrl('/donations/donations/donate');
$quickDonatePanelHtml = '<div id="' . Html::encode($quickDonatePanelId) . '" style="display:none;opacity:0;pointer-events:none;margin:8px 12px 0;padding:20px 20px;border-radius:7px;background:rgba(0,0,0,0.28);border:1px solid rgba(255,255,255,0.15);overflow:hidden;max-width:fit-content;width:fit-content;justify-self:center;align-self:center;">'
. '<form method="post" action="' . Html::encode($quickDonateActionUrl) . '" style="margin:0;display:flex;flex-direction:column;align-items:center;text-align:center;">'
. Html::hiddenInput(Yii::$app->request->csrfParam, Yii::$app->request->getCsrfToken())
. Html::hiddenInput('goal_id', (int)$goal->id)
. '<div style="margin:0 0 8px 0;font-size:13px;font-weight:700;color:#fff;text-align:center;hyphens:none;word-break:normal;overflow-wrap:normal;white-space:normal;">' . Html::encode($quickDonateHeaderText) . '</div>'
. '<div style="display:flex;align-items:center;justify-content:center;gap:6px;margin-bottom:8px;">'
. '<label style="margin:0;font-size:24px;font-weight:700;color:#fff;line-height:1;">$</label>'
. '<input type="number" step="0.01" min="0.01" name="amount" required class="form-control input-sm" style="width:100px;max-width:100px;min-width:100px;background:rgba(54,209,124,0.32) !important;border:1px solid rgba(54,209,124,0.72) !important;border-radius:999px !important;color:#f8fafc !important;-webkit-text-fill-color:#f8fafc;box-shadow:none !important;font-size:24px;font-weight:700;line-height:1.15;text-align:center;padding:4px 10px;appearance:textfield;-moz-appearance:textfield;overflow:hidden;">'
. '</div>'
. '<div style="display:flex;align-items:center;justify-content:center;gap:14px;flex-wrap:wrap;margin-bottom:8px;">'
. '<label style="margin:0;display:flex;align-items:center;gap:6px;color:#fff;font-size:13px;cursor:pointer;">'
. '<input type="radio" name="mode" value="one_time" checked>'
. Html::encode(Yii::t('DonationsModule.base', 'One Time'))
. '</label>'
. '<label style="margin:0;display:flex;align-items:center;gap:6px;color:#fff;font-size:13px;cursor:pointer;">'
. '<input type="radio" name="mode" value="recurring">'
. Html::encode(Yii::t('DonationsModule.base', 'Recurring'))
. '</label>'
. '</div>'
. '<label style="margin:0 0 10px 0;display:flex;align-items:center;justify-content:center;gap:6px;color:#fff;font-size:13px;cursor:pointer;">'
. '<input type="checkbox" name="is_anonymous" value="1">'
. Html::encode(Yii::t('DonationsModule.base', 'Donate anonymously'))
. '</label>'
. '<div style="display:flex;align-items:center;gap:8px;justify-content:center;">'
. '<button type="submit" name="provider" value="paypal" class="btn btn-sm" style="min-width:92px;border:1px solid rgba(255,255,255,0.28);background:rgba(255,255,255,0.12);color:#fff;">PayPal</button>'
. '<button type="submit" name="provider" value="stripe" class="btn btn-sm" style="min-width:92px;border:1px solid rgba(255,255,255,0.28);background:rgba(255,255,255,0.12);color:#fff;">Stripe</button>'
. '</div>'
. '</form>'
. '</div>';
$donorPanelHtml = $goalToggleCss
. '<input type="checkbox" id="' . Html::encode($goalToggleInputId) . '" style="display:none;" onchange="if(this.checked){var q=document.getElementById(\'' . Html::encode($quickDonateToggleInputId) . '\');if(q){q.checked=false;}}">'
. '<input type="checkbox" id="' . Html::encode($quickDonateToggleInputId) . '" style="display:none;" onchange="if(this.checked){var g=document.getElementById(\'' . Html::encode($goalToggleInputId) . '\');if(g){g.checked=false;}}">'
. '<div id="' . Html::encode($donorPanelId) . '" style="display:none;opacity:0;pointer-events:none;margin-top:8px;padding:8px 10px;border-radius:7px;background:rgba(0,0,0,0.28);border:1px solid rgba(255,255,255,0.15);overflow:hidden;max-height:calc(100% - 98px);">'
. '<div id="' . Html::encode($donorPagerWrapId) . '">'
. $donorPagerRadioInputsHtml
. '<div data-donor-top-controls style="' . ($donorTotalPages > 1 ? 'margin-bottom:6px;' : 'display:none;') . '">' . $donorPagerTopControlsHtml . '</div>'
. '<div data-donor-pages data-donor-list-scroll style="overflow-y:auto;max-height:calc(100% - 72px);">' . $donorPagesHtml . '</div>'
. '<div data-donor-bottom-controls style="' . ($donorTotalPages > 1 ? 'margin-top:6px;' : 'display:none;') . '">' . $donorPagerBottomControlsHtml . '</div>'
. '</div>'
. '</div>';
$goalLabelHtml = '<label for="' . Html::encode($goalToggleInputId) . '" style="margin:0;cursor:pointer;font-weight:700;color:rgba(255,255,255,0.98);pointer-events:auto;position:relative;z-index:4;">'
. Html::encode(Yii::t('DonationsModule.base', 'Goal'))
. '</label>';
$percentColor = $percent >= 100.0 ? '#4ce083' : '#ffffff';
$currentAmountHtml = '<span style="font-weight:700;color:#ffffff;white-space:nowrap;">' . Html::encode($currentLabel) . '</span>';
$targetAmountNumberLabel = ltrim($targetLabel, '$');
if ($targetAmountNumberLabel === '') {
$targetAmountNumberLabel = $targetLabel;
}
$targetAmountInnerHtml = '<span style="display:inline-flex;align-items:flex-end;gap:2px;line-height:1;">'
. '<span style="font-size:13px;line-height:1;transform:translateY(-0.36em);">$</span>'
. '<span style="font-size:21px;line-height:1;">' . Html::encode($targetAmountNumberLabel) . '</span>'
. '</span>';
$donateCtaHtml = '<span style="display:inline-flex;align-items:center;justify-content:center;height:16px;padding:4px 14px;border-radius:999px;border:1px solid rgba(30,164,95,0.62);background:linear-gradient(90deg,#36d17c,#1ea45f);color:#000000;font-size:12px;font-weight:800;letter-spacing:.08em;line-height:1;text-transform:uppercase;box-shadow:0 0 0 1px rgba(255,255,255,0.2),0 0 10px rgba(54,209,124,0.62),0 0 18px rgba(30,164,95,0.46);text-shadow:0 0 2px rgba(255,255,255,0.28);">'
. Html::encode(Yii::t('DonationsModule.base', 'Donate'))
. '</span>';
$targetAmountHtml = '<span style="margin-left:auto;font-weight:700;color:' . $statusColor . ';white-space:nowrap;display:inline-flex;align-items:flex-end;">' . $targetAmountInnerHtml . '</span>';
if (!empty($event->showDonationSettingsButton) && !empty($event->donationToggleInputId)) {
$toggleInputId = (string)$event->donationToggleInputId;
$inlineFormId = (string)($event->donationInlineFormId ?? '');
$targetOnClick = '';
if ($inlineFormId !== '') {
$targetOnClick = ' onclick="var c=document.getElementById(\'' . Html::encode($toggleInputId) . '\');var f=document.getElementById(\'' . Html::encode($inlineFormId) . '\');if(c&&f){window.setTimeout(function(){f.style.display=c.checked?\'block\':\'none\';},0);}"';
}
$targetAmountHtml = '<label for="' . Html::encode($toggleInputId) . '" class="js-animal-donation-inline-toggle"'
. ' data-toggle-id="#' . Html::encode($toggleInputId) . '"'
. ($inlineFormId !== '' ? ' data-form-id="#' . Html::encode($inlineFormId) . '"' : '')
. $targetOnClick
. ' style="margin:0 0 0 auto;cursor:pointer;font-weight:700;color:' . $statusColor . ';pointer-events:auto;position:relative;z-index:4;text-decoration:none;white-space:nowrap;display:inline-flex;align-items:flex-end;">'
. $targetAmountInnerHtml
. '</label>';
}
$event->addHtml(
'<div id="' . Html::encode($goalCardId) . '" style="display:block;position:relative;overflow:visible;width:100%;padding:7px 8px;border-radius:8px;background:rgba(7,10,16,0.16);border:1px solid rgba(255,255,255,0.14);box-sizing:border-box;transition:background 140ms ease,border-color 140ms ease;">'
. '<div style="position:relative;">'
. '<label for="' . Html::encode($quickDonateToggleInputId) . '" style="position:absolute;inset:0;z-index:2;cursor:pointer;"></label>'
. '<div style="position:relative;z-index:3;display:flex;gap:8px;align-items:flex-end;font-size:16.5px;color:rgba(255,255,255,0.95);pointer-events:none;">'
. $goalLabelHtml
. $targetAmountHtml
. '</div>'
. '<div style="margin-top:5px;height:7px;border-radius:999px;overflow:hidden;background:rgba(255,255,255,0.22);pointer-events:none;">'
. '<span style="display:block;height:100%;width:' . Html::encode((string)$percent) . '%;background:linear-gradient(90deg,#36d17c,#1ea45f);"></span>'
. '</div>'
. '<div style="margin-top:4px;display:grid;grid-template-columns:minmax(0,1fr) auto minmax(0,1fr);align-items:end;column-gap:10px;font-size:16.5px;font-weight:700;pointer-events:none;">'
. $currentAmountHtml
. $donateCtaHtml
. '<span style="justify-self:end;white-space:nowrap;text-align:right;color:' . $percentColor . ';">' . Html::encode($percentLabel) . '</span>'
. '</div>'
. '</div>'
. $donorPanelHtml
. $quickDonatePanelHtml
. '</div>'
);
}
public static function onAnimalTileSizeResolve($event): void
{
$hookClass = 'humhub\\modules\\animal_management\\events\\AnimalTileSizeEvent';
if (!class_exists($hookClass) || !$event instanceof $hookClass) {
return;
}
$space = $event->contentContainer ?? null;
if (!$space instanceof Space || !$space->moduleManager->isEnabled('donations')) {
return;
}
$settings = Yii::$app->getModule('donations')->settings->contentContainer($space);
$extraHeight = max(0, (int)$settings->get('animal_tile_extra_height_px', 0));
if ($extraHeight <= 0) {
return;
}
$event->addAdditionalHeightPx($extraHeight);
}
}