Gebuendelt, da ueber gemeinsame lang-/Service-Dateien verschraenkt: - Kontakt-Nachrichten: contact_messages-Tabelle + Model/Enums/Factory, Admin-Queue (livewire/admin/contact-messages), ContactController persistiert das Anliegen zusaetzlich zur Brand-Mail - Page-Data-Cache (Services/Web/*PageData) + web-archive-Indizes auf press_releases - Validierungs-Feedback + Company-Portal-Restriction (Feature-Tests) - i18n-Auslagerung lang/de|en (auth, passwords, validation, static) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
96 lines
3.6 KiB
PHP
96 lines
3.6 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Web;
|
|
|
|
use App\Enums\PressReleaseStatus;
|
|
use App\Models\Company;
|
|
use App\Models\Plan;
|
|
use App\Models\PressRelease;
|
|
use App\Scopes\PortalScope;
|
|
use App\Support\EditorialClock;
|
|
use App\Support\WebPortalContext;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
use Illuminate\Support\Carbon;
|
|
|
|
/**
|
|
* Datenaufbereitung der Veröffentlichen-Marketingseite (Einreichung läuft im
|
|
* Publisher-Hub): Archiv-Kennzahlen, Beispiel-Meldung und aktive Newsrooms.
|
|
*/
|
|
class VeroeffentlichenPageData
|
|
{
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
public function build(WebPortalContext $context): array
|
|
{
|
|
$portalValues = $context->portalValues;
|
|
$language = $context->language;
|
|
$referenceNow = EditorialClock::reference($portalValues, $language);
|
|
$recentSince = $referenceNow->copy()->subDays(30);
|
|
|
|
$publishedBase = static fn (): Builder => PressRelease::query()
|
|
->withoutGlobalScope(PortalScope::class)
|
|
->forPortalValues($portalValues)
|
|
->forLanguage($language)
|
|
->visibleInArchive();
|
|
|
|
$archiveTotal = $publishedBase()->count();
|
|
$publishedToday = $publishedBase()->whereBetween('published_at', [$referenceNow->copy()->startOfDay(), $referenceNow->copy()->endOfDay()])->count();
|
|
$oldest = $publishedBase()->min('published_at');
|
|
$oldestYear = $oldest ? (int) Carbon::parse($oldest)->format('Y') : null;
|
|
|
|
$inReview = PressRelease::query()
|
|
->withoutGlobalScope(PortalScope::class)
|
|
->whereIn('portal', $portalValues)
|
|
->where('status', PressReleaseStatus::Review)
|
|
->where('language', $language)
|
|
->count();
|
|
|
|
$exampleRelease = $publishedBase()
|
|
->with([
|
|
'company',
|
|
'category.translations' => fn ($query) => $query->where('locale', $language),
|
|
])
|
|
->orderByDesc('published_at')
|
|
->first();
|
|
|
|
$newsroomFilter = static function (Builder $query) use ($portalValues, $language, $recentSince): void {
|
|
$query
|
|
->forPortalValues($portalValues)
|
|
->forLanguage($language)
|
|
->published()
|
|
->where('published_at', '>=', $recentSince);
|
|
};
|
|
|
|
$newsroomQuery = Company::query()
|
|
->withoutGlobalScope(PortalScope::class)
|
|
->whereIn('portal', $portalValues)
|
|
->where('is_active', true)
|
|
->whereHas('pressReleases', fn (Builder $query) => $newsroomFilter($query));
|
|
|
|
$activeNewsroomsTotal = (clone $newsroomQuery)->count();
|
|
$activeNewsroomNames = (clone $newsroomQuery)
|
|
->withCount(['pressReleases as recent_count' => fn (Builder $query) => $newsroomFilter($query)])
|
|
->orderByDesc('recent_count')
|
|
->limit(10)
|
|
->pluck('name')
|
|
->all();
|
|
|
|
// Preis-Anker aus denselben Quellen wie die Preisseite (PreisePageData),
|
|
// damit die Marketingseite nie von den echten Konditionen abweicht.
|
|
$cheapestPlan = Plan::query()->active()->orderBy('monthly_price_cents')->first();
|
|
|
|
return [
|
|
'referenceNow' => $referenceNow,
|
|
'archiveTotal' => $archiveTotal,
|
|
'publishedToday' => $publishedToday,
|
|
'inReview' => $inReview,
|
|
'oldestYear' => $oldestYear,
|
|
'exampleRelease' => $exampleRelease,
|
|
'activeNewsroomNames' => $activeNewsroomNames,
|
|
'activeNewsroomsTotal' => $activeNewsroomsTotal,
|
|
'singlePmPriceCents' => (int) config('billing.single_pm_price_cents'),
|
|
'cheapestPlan' => $cheapestPlan,
|
|
];
|
|
}
|
|
}
|