Skip to content

Commit 9b593f3

Browse files
committed
Merge origin/develop into v2.0.0-alpha.1
Resolved translation conflicts by keeping entries from v2.0.0-alpha.1 (preserves auth page translations) and re-extracting against merged source to pick up new msgids from develop. .js files recompiled.
2 parents 458a27c + 8691250 commit 9b593f3

81 files changed

Lines changed: 10985 additions & 19297 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
namespace HiEvents\DomainObjects\Enums;
4+
5+
enum TrackingPixelProvider: string
6+
{
7+
use BaseEnum;
8+
9+
case FACEBOOK_PIXEL = 'facebook_pixel';
10+
case GOOGLE_ANALYTICS_4 = 'google_analytics_4';
11+
case GOOGLE_TAG_MANAGER = 'google_tag_manager';
12+
case TIKTOK_PIXEL = 'tiktok_pixel';
13+
14+
public function pixelIdPattern(): string
15+
{
16+
return match ($this) {
17+
self::FACEBOOK_PIXEL => '/^\d{9,20}$/',
18+
self::GOOGLE_ANALYTICS_4 => '/^G-[a-zA-Z0-9]{6,20}$/',
19+
self::GOOGLE_TAG_MANAGER => '/^GTM-[a-zA-Z0-9]{4,20}$/',
20+
self::TIKTOK_PIXEL => '/^[a-zA-Z0-9]{6,30}$/',
21+
};
22+
}
23+
24+
public function pixelIdFormatDescription(): string
25+
{
26+
return match ($this) {
27+
self::FACEBOOK_PIXEL => __('Must be 9-20 digits (e.g., 1234567890)'),
28+
self::GOOGLE_ANALYTICS_4 => __('Must start with G- followed by 6-20 characters (e.g., G-XXXXXXXXXX)'),
29+
self::GOOGLE_TAG_MANAGER => __('Must start with GTM- followed by 4-20 characters (e.g., GTM-XXXXXXX)'),
30+
self::TIKTOK_PIXEL => __('Must be 6-30 alphanumeric characters'),
31+
};
32+
}
33+
}

backend/app/DomainObjects/Generated/OrganizerSettingDomainObjectAbstract.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ abstract class OrganizerSettingDomainObjectAbstract extends \HiEvents\DomainObje
2929
final public const DEFAULT_SHOW_MARKETING_OPT_IN = 'default_show_marketing_opt_in';
3030
final public const DEFAULT_PASS_PLATFORM_FEE_TO_BUYER = 'default_pass_platform_fee_to_buyer';
3131
final public const DEFAULT_ALLOW_ATTENDEE_SELF_EDIT = 'default_allow_attendee_self_edit';
32+
final public const TRACKING_PIXELS = 'tracking_pixels';
33+
final public const TRACKING_CONSENT_ACKNOWLEDGED = 'tracking_consent_acknowledged';
3234

3335
protected int $id;
3436
protected int $organizer_id;
@@ -49,6 +51,8 @@ abstract class OrganizerSettingDomainObjectAbstract extends \HiEvents\DomainObje
4951
protected bool $default_show_marketing_opt_in = true;
5052
protected bool $default_pass_platform_fee_to_buyer = false;
5153
protected bool $default_allow_attendee_self_edit = true;
54+
protected array|string|null $tracking_pixels = null;
55+
protected bool $tracking_consent_acknowledged = false;
5256

5357
public function toArray(): array
5458
{
@@ -72,6 +76,8 @@ public function toArray(): array
7276
'default_show_marketing_opt_in' => $this->default_show_marketing_opt_in ?? null,
7377
'default_pass_platform_fee_to_buyer' => $this->default_pass_platform_fee_to_buyer ?? null,
7478
'default_allow_attendee_self_edit' => $this->default_allow_attendee_self_edit ?? null,
79+
'tracking_pixels' => $this->tracking_pixels ?? null,
80+
'tracking_consent_acknowledged' => $this->tracking_consent_acknowledged ?? null,
7581
];
7682
}
7783

@@ -284,4 +290,26 @@ public function getDefaultAllowAttendeeSelfEdit(): bool
284290
{
285291
return $this->default_allow_attendee_self_edit;
286292
}
293+
294+
public function setTrackingPixels(array|string|null $tracking_pixels): self
295+
{
296+
$this->tracking_pixels = $tracking_pixels;
297+
return $this;
298+
}
299+
300+
public function getTrackingPixels(): array|string|null
301+
{
302+
return $this->tracking_pixels;
303+
}
304+
305+
public function setTrackingConsentAcknowledged(bool $tracking_consent_acknowledged): self
306+
{
307+
$this->tracking_consent_acknowledged = $tracking_consent_acknowledged;
308+
return $this;
309+
}
310+
311+
public function getTrackingConsentAcknowledged(): bool
312+
{
313+
return $this->tracking_consent_acknowledged;
314+
}
287315
}

backend/app/Http/Actions/Events/Stats/GetEventStatsAction.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22

33
namespace HiEvents\Http\Actions\Events\Stats;
44

5-
use Carbon\Carbon;
65
use HiEvents\DomainObjects\EventDomainObject;
76
use HiEvents\Http\Actions\BaseAction;
87
use HiEvents\Services\Application\Handlers\Event\DTO\EventStatsRequestDTO;
98
use HiEvents\Services\Application\Handlers\Event\GetEventStatsHandler;
109
use Illuminate\Http\JsonResponse;
10+
use Illuminate\Http\Request;
1111
use Illuminate\Http\Resources\Json\JsonResource;
1212

1313
class GetEventStatsAction extends BaseAction
@@ -18,14 +18,15 @@ public function __construct(
1818
{
1919
}
2020

21-
public function __invoke(int $eventId): JsonResponse
21+
public function __invoke(int $eventId, Request $request): JsonResponse
2222
{
2323
$this->isActionAuthorized($eventId, EventDomainObject::class);
2424

25+
$dateRangePreset = $request->query('date_range', 'month');
26+
2527
$stats = $this->eventStatsHandler->handle(EventStatsRequestDTO::fromArray([
2628
'event_id' => $eventId,
27-
'start_date' => Carbon::now()->subDays(7)->format('Y-m-d H:i:s'),
28-
'end_date' => Carbon::now()->format('Y-m-d H:i:s')
29+
'date_range_preset' => $dateRangePreset,
2930
]));
3031

3132
return $this->resourceResponse(JsonResource::class, $stats);

backend/app/Http/Request/Organizer/Settings/PartialUpdateOrganizerSettingsRequest.php

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,58 @@
55
use HiEvents\DomainObjects\Enums\AttendeeDetailsCollectionMethod;
66
use HiEvents\DomainObjects\Enums\HomepageBackgroundType;
77
use HiEvents\DomainObjects\Enums\OrganizerHomepageVisibility;
8+
use HiEvents\DomainObjects\Enums\TrackingPixelProvider;
89
use HiEvents\Http\Request\BaseRequest;
910
use HiEvents\Validators\Rules\RulesHelper;
1011
use Illuminate\Validation\Rule;
1112

1213
class PartialUpdateOrganizerSettingsRequest extends BaseRequest
1314
{
15+
public function after(): array
16+
{
17+
return [
18+
function ($validator) {
19+
$pixels = $this->input('tracking_pixels', []);
20+
if (!is_array($pixels)) {
21+
return;
22+
}
23+
24+
$isSaasMode = config('app.saas_mode_enabled');
25+
26+
foreach ($pixels as $index => $pixel) {
27+
$providerValue = $pixel['provider'] ?? null;
28+
$pixelId = $pixel['pixel_id'] ?? '';
29+
$provider = TrackingPixelProvider::tryFrom($providerValue);
30+
31+
if ($isSaasMode && $provider === TrackingPixelProvider::GOOGLE_TAG_MANAGER) {
32+
$validator->errors()->add(
33+
"tracking_pixels.{$index}.provider",
34+
__('Google Tag Manager is not available on hosted plans for security reasons.')
35+
);
36+
continue;
37+
}
38+
39+
if ($provider && $pixelId !== '') {
40+
if (!preg_match($provider->pixelIdPattern(), $pixelId)) {
41+
$validator->errors()->add(
42+
"tracking_pixels.{$index}.pixel_id",
43+
$provider->pixelIdFormatDescription()
44+
);
45+
}
46+
}
47+
}
48+
49+
$enabledPixels = collect($pixels)->filter(fn ($p) => !empty($p['enabled']));
50+
if ($enabledPixels->isNotEmpty() && !$this->input('tracking_consent_acknowledged')) {
51+
$validator->errors()->add(
52+
'tracking_consent_acknowledged',
53+
__('You must acknowledge your data controller responsibilities before enabling tracking pixels.')
54+
);
55+
}
56+
},
57+
];
58+
}
59+
1460
public static function rules(): array
1561
{
1662
return [
@@ -73,6 +119,13 @@ public static function rules(): array
73119

74120
// Password
75121
'homepage_password' => ['sometimes', 'nullable', 'string', 'max:100'],
122+
123+
// Tracking pixels
124+
'tracking_pixels' => ['sometimes', 'nullable', 'array', 'max:10'],
125+
'tracking_pixels.*.provider' => ['required', 'string', Rule::in(TrackingPixelProvider::valuesArray())],
126+
'tracking_pixels.*.pixel_id' => ['required', 'string', 'max:50', 'regex:/^[a-zA-Z0-9\-_]+$/'],
127+
'tracking_pixels.*.enabled' => ['required', 'boolean'],
128+
'tracking_consent_acknowledged' => ['sometimes', 'nullable', 'boolean'],
76129
];
77130
}
78131
}

backend/app/Models/OrganizerSetting.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public function getCastMap(): array
1414
'social_media_handles' => 'array',
1515
'homepage_theme_settings' => 'array',
1616
'location_details' => 'array',
17+
'tracking_pixels' => 'array',
1718
];
1819
}
1920
}

backend/app/Resources/Organizer/OrganizerSettingsPublicResource.php

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,30 @@
22

33
namespace HiEvents\Resources\Organizer;
44

5+
use HiEvents\DomainObjects\Enums\TrackingPixelProvider;
56
use HiEvents\DomainObjects\OrganizerSettingDomainObject;
67

78
/**
8-
* We can extend the OrganizerSettingsResource for now
9-
*
109
* @mixin OrganizerSettingDomainObject
1110
*/
1211
class OrganizerSettingsPublicResource extends OrganizerSettingsResource
1312
{
13+
public function toArray($request): array
14+
{
15+
$data = parent::toArray($request);
1416

17+
unset(
18+
$data['tracking_consent_acknowledged'],
19+
$data['homepage_password'],
20+
);
21+
22+
if (config('app.saas_mode_enabled') && !empty($data['tracking_pixels'])) {
23+
$data['tracking_pixels'] = array_values(array_filter(
24+
$data['tracking_pixels'],
25+
fn($pixel) => ($pixel['provider'] ?? null) !== TrackingPixelProvider::GOOGLE_TAG_MANAGER->value,
26+
));
27+
}
28+
29+
return $data;
30+
}
1531
}

backend/app/Resources/Organizer/OrganizerSettingsResource.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ public function toArray($request): array
2929
'seo_description' => $this->getSeoDescription(),
3030
'allow_search_engine_indexing' => $this->getAllowSearchEngineIndexing(),
3131
'location_details' => $this->getLocationDetails(),
32+
'tracking_pixels' => $this->getTrackingPixels(),
33+
'tracking_consent_acknowledged' => $this->getTrackingConsentAcknowledged(),
3234
];
3335
}
3436
}

backend/app/Services/Application/Handlers/Event/DTO/EventStatsRequestDTO.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@
77
class EventStatsRequestDTO extends BaseDTO
88
{
99
public function __construct(
10-
public int $event_id,
11-
public string $start_date,
12-
public string $end_date,
10+
public int $event_id,
11+
public ?string $start_date = null,
12+
public ?string $end_date = null,
13+
public string $date_range_preset = 'month',
1314
)
1415
{
1516
}

backend/app/Services/Application/Handlers/Order/Payment/Stripe/IncomingWebhookHandler.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use Illuminate\Cache\Repository;
1414
use Illuminate\Log\Logger;
1515
use JsonException;
16+
use Stripe\Charge;
1617
use Stripe\Event;
1718
use Stripe\Exception\SignatureVerificationException;
1819
use Stripe\Webhook;
@@ -27,6 +28,8 @@ class IncomingWebhookHandler
2728
Event::PAYMENT_INTENT_PAYMENT_FAILED,
2829
Event::ACCOUNT_UPDATED,
2930
Event::REFUND_UPDATED,
31+
Event::REFUND_CREATED,
32+
Event::CHARGE_REFUNDED,
3033
Event::CHARGE_SUCCEEDED,
3134
Event::CHARGE_UPDATED,
3235
Event::PAYOUT_PAID,
@@ -92,8 +95,12 @@ public function handle(StripeWebhookDTO $webhookDTO): void
9295
$this->chargeSucceededHandler->handleEvent($event->data->object);
9396
break;
9497
case Event::REFUND_UPDATED:
98+
case Event::REFUND_CREATED:
9599
$this->refundEventHandlerService->handleEvent($event->data->object);
96100
break;
101+
case Event::CHARGE_REFUNDED:
102+
$this->handleChargeRefunded($event->data->object);
103+
break;
97104
case Event::ACCOUNT_UPDATED:
98105
$this->accountUpdateHandler->handleEvent($event->data->object);
99106
break;
@@ -170,6 +177,15 @@ private function hasEventBeenHandled(Event $event): bool
170177
return $this->cache->has('stripe_event_' . $event->id);
171178
}
172179

180+
private function handleChargeRefunded(Charge $charge): void
181+
{
182+
$refunds = $charge->refunds->data ?? [];
183+
184+
foreach ($refunds as $refund) {
185+
$this->refundEventHandlerService->handleEvent($refund);
186+
}
187+
}
188+
173189
private function markEventAsHandled(Event $event): void
174190
{
175191
$this->logger->info('Marking Stripe event as handled', [

backend/app/Services/Application/Handlers/Organizer/DTO/PartialUpdateOrganizerSettingsDTO.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ public function __construct(
6969

7070
// Password
7171
public readonly string|Optional|null $homepagePassword,
72+
73+
// Tracking pixels
74+
public readonly array|Optional|null $trackingPixels,
75+
public readonly bool|Optional|null $trackingConsentAcknowledged,
7276
)
7377
{
7478
}

0 commit comments

Comments
 (0)