status === PressReleaseStatus::Published && $pressRelease->classification === PressReleaseClassification::Green; } public function isBoosted(PressRelease $pressRelease): bool { return $pressRelease->boosts()->active()->exists(); } /** * Ende des aktuell laufenden Boosts (das späteste zukünftige `ends_at`) * oder null, wenn die PM gerade nicht geboostet ist. */ public function activeUntil(PressRelease $pressRelease): ?Carbon { $endsAt = $pressRelease->boosts()->active()->max('ends_at'); return $endsAt ? Carbon::parse($endsAt) : null; } /** * Bucht einen Boost. Wirft BoostNotAllowedException am Gate und * InvalidArgumentException bei unbekannter Laufzeit; die Wallet-Belastung * wirft InsufficientCreditsException, falls das Guthaben nicht reicht. */ public function boost(User $user, PressRelease $pressRelease, int $days): Boost { if (! $this->canBoost($pressRelease)) { throw BoostNotAllowedException::notBoostable(); } $credits = $this->pricing->boostCredits($days); $boost = DB::transaction(function () use ($user, $pressRelease, $days, $credits): Boost { $startsAt = $this->activeUntil($pressRelease) ?? now(); $boost = $pressRelease->boosts()->create([ 'user_id' => $user->id, 'days' => $days, 'credits_charged' => $credits, 'starts_at' => $startsAt, 'ends_at' => $startsAt->copy()->addDays($days), ]); $this->wallet->debit( $user, $credits, "Boost {$days} Tage – PM #{$pressRelease->id}", $boost, ); return $boost; }); $this->forgetPlacementCaches($pressRelease); return $boost; } /** * Gecachte Start-/Branchenseiten der betroffenen Ausgaben verwerfen, * damit die bezahlte Platzierung sofort sichtbar wird statt erst nach * Ablauf der Cache-TTL (HomePageData/CategoryPageData, 300 s). */ private function forgetPlacementCaches(PressRelease $pressRelease): void { $portals = $pressRelease->portal === Portal::Both ? [Portal::Presseecho->value, Portal::Businessportal24->value] : [$pressRelease->portal->value]; // Branchenseite der eigenen Rubrik plus Eltern-Rubrik (deren Seite // schließt Unterkategorien mit ein), je hinterlegter Sprache. $category = $pressRelease->category() ->with(['translations', 'parent.translations']) ->first(); $translations = collect($category?->translations ?? []) ->merge($category?->parent?->translations ?? []); foreach ($portals as $portal) { Cache::forget(sprintf('web.home.%s.%s', $portal, $pressRelease->language)); foreach ($translations as $translation) { if (filled($translation->slug)) { Cache::forget(sprintf('web.category.%s.%s.%s', $portal, $translation->locale, $translation->slug)); } } } } }