diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0cf2c685..4a74cade 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -50,5 +50,12 @@ jobs: - name: Build Assets run: npm run build + # Vor den Tests: Die statische Analyse ist ohne Gate binnen weniger Wochen + # abgedriftet (Baseline zuletzt 12.05.2026, danach 390 neue Meldungen). + # Neue Meldungen gehören in den Code oder — begründet — in die Baseline, + # nicht ins Rauschen. + - name: Run Static Analysis + run: ./vendor/bin/phpstan analyse --memory-limit=2G --no-progress + - name: Run Tests run: ./vendor/bin/pest \ No newline at end of file diff --git a/app/Console/Commands/MigrateLegacyMedia.php b/app/Console/Commands/MigrateLegacyMedia.php index a312056c..cda68d35 100644 --- a/app/Console/Commands/MigrateLegacyMedia.php +++ b/app/Console/Commands/MigrateLegacyMedia.php @@ -342,6 +342,9 @@ class MigrateLegacyMedia extends Command /** * @return list */ + /** + * @return list<'company-logos'|'press-release-images'> + */ private function selectedTypes(): array { $type = (string) $this->option('type'); diff --git a/app/Console/Commands/RetireAgencyCompanyType.php b/app/Console/Commands/RetireAgencyCompanyType.php index 1243958e..df5debdc 100644 --- a/app/Console/Commands/RetireAgencyCompanyType.php +++ b/app/Console/Commands/RetireAgencyCompanyType.php @@ -84,7 +84,7 @@ class RetireAgencyCompanyType extends Command continue; } - if (! $isNewsroom && $isProtected) { + if (! $isNewsroom) { $report['protected'][] = $entry + [ 'third_party_members' => (bool) $agency->has_third_party_members, 'payment_option_links' => (bool) $agency->has_payment_option_links, diff --git a/app/Http/Controllers/PressReleasePreviewController.php b/app/Http/Controllers/PressReleasePreviewController.php index cf53a331..ce3995e2 100644 --- a/app/Http/Controllers/PressReleasePreviewController.php +++ b/app/Http/Controllers/PressReleasePreviewController.php @@ -23,7 +23,7 @@ class PressReleasePreviewController extends Controller return $this->renderError(__('Der Vorschau-Link ist ungültig.'), 404); } - if ($magicLink->expires_at && $magicLink->expires_at->isPast()) { + if ($magicLink->expires_at->isPast()) { return $this->renderError(__('Der Vorschau-Link ist abgelaufen.'), 410); } diff --git a/app/Listeners/RecordMailActivity.php b/app/Listeners/RecordMailActivity.php index 66d09bae..502b7db0 100644 --- a/app/Listeners/RecordMailActivity.php +++ b/app/Listeners/RecordMailActivity.php @@ -27,7 +27,7 @@ class RecordMailActivity { $recipients = array_values(array_map( fn ($address): string => $address->getAddress(), - $event->message->getTo() ?? [], + $event->message->getTo(), )); if ($recipients === []) { diff --git a/app/Models/ActivityEvent.php b/app/Models/ActivityEvent.php index 09e56125..276bf3b1 100644 --- a/app/Models/ActivityEvent.php +++ b/app/Models/ActivityEvent.php @@ -35,11 +35,17 @@ class ActivityEvent extends Model ]; } + /** + * @return BelongsTo + */ public function user(): BelongsTo { return $this->belongsTo(User::class); } + /** + * @return MorphTo + */ public function subject(): MorphTo { return $this->morphTo(); diff --git a/app/Models/ApiUsageLog.php b/app/Models/ApiUsageLog.php index cbc9f435..d6d9f07c 100644 --- a/app/Models/ApiUsageLog.php +++ b/app/Models/ApiUsageLog.php @@ -30,11 +30,17 @@ class ApiUsageLog extends Model ]; } + /** + * @return BelongsTo + */ public function user(): BelongsTo { return $this->belongsTo(User::class); } + /** + * @return BelongsTo + */ public function personalAccessToken(): BelongsTo { return $this->belongsTo(PersonalAccessToken::class); diff --git a/app/Models/BillingAddress.php b/app/Models/BillingAddress.php index a64627ec..3c7d24d1 100644 --- a/app/Models/BillingAddress.php +++ b/app/Models/BillingAddress.php @@ -26,6 +26,9 @@ class BillingAddress extends Model 'vat_id', ]; + /** + * @return BelongsTo + */ public function user(): BelongsTo { return $this->belongsTo(User::class); diff --git a/app/Models/BookingConfirmation.php b/app/Models/BookingConfirmation.php index 40263f0d..bbac223b 100644 --- a/app/Models/BookingConfirmation.php +++ b/app/Models/BookingConfirmation.php @@ -38,6 +38,9 @@ class BookingConfirmation extends Model ]; } + /** + * @return BelongsTo + */ public function user(): BelongsTo { return $this->belongsTo(User::class); @@ -46,6 +49,8 @@ class BookingConfirmation extends Model /** * Die akzeptierte AGB-Fassung (E3: "agb_version_id" am * Buchungsdatensatz); `agb_version` hält den Klartext redundant fest. + * + * @return BelongsTo */ public function legalDocument(): BelongsTo { diff --git a/app/Models/Boost.php b/app/Models/Boost.php index 849bde9d..5543dabe 100644 --- a/app/Models/Boost.php +++ b/app/Models/Boost.php @@ -40,11 +40,17 @@ class Boost extends Model ]; } + /** + * @return BelongsTo + */ public function pressRelease(): BelongsTo { return $this->belongsTo(PressRelease::class); } + /** + * @return BelongsTo + */ public function user(): BelongsTo { return $this->belongsTo(User::class); diff --git a/app/Models/Category.php b/app/Models/Category.php index 865247a7..1c264cdb 100644 --- a/app/Models/Category.php +++ b/app/Models/Category.php @@ -31,16 +31,25 @@ class Category extends Model ]; } + /** + * @return BelongsTo + */ public function parent(): BelongsTo { return $this->belongsTo(self::class, 'parent_id'); } + /** + * @return HasMany + */ public function children(): HasMany { return $this->hasMany(self::class, 'parent_id'); } + /** + * @return HasMany + */ public function translations(): HasMany { return $this->hasMany(CategoryTranslation::class); @@ -56,11 +65,17 @@ class Category extends Model return $this->translations->first()?->name ?? '#'.$this->id; } + /** + * @return HasMany + */ public function pressReleases(): HasMany { return $this->hasMany(PressRelease::class); } + /** + * @return BelongsToMany + */ public function footerCodes(): BelongsToMany { return $this->belongsToMany(FooterCode::class, 'category_footer_code'); diff --git a/app/Models/CategoryTranslation.php b/app/Models/CategoryTranslation.php index 65fcd238..29176e3b 100644 --- a/app/Models/CategoryTranslation.php +++ b/app/Models/CategoryTranslation.php @@ -16,6 +16,9 @@ class CategoryTranslation extends Model 'description', ]; + /** + * @return BelongsTo + */ public function category(): BelongsTo { return $this->belongsTo(Category::class); diff --git a/app/Models/Company.php b/app/Models/Company.php index a9b6b91c..f31f576c 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -131,11 +131,17 @@ class Company extends Model return $this->gone_at !== null; } + /** + * @return BelongsTo + */ public function owner(): BelongsTo { return $this->belongsTo(User::class, 'owner_user_id'); } + /** + * @return BelongsToMany + */ public function users(): BelongsToMany { return $this->belongsToMany(User::class) @@ -143,16 +149,25 @@ class Company extends Model ->withTimestamps(); } + /** + * @return HasMany + */ public function contacts(): HasMany { return $this->hasMany(Contact::class); } + /** + * @return HasMany + */ public function pressReleases(): HasMany { return $this->hasMany(PressRelease::class); } + /** + * @return BelongsToMany + */ public function userPaymentOptions(): BelongsToMany { return $this->belongsToMany(UserPaymentOption::class, 'user_payment_option_company') @@ -177,7 +192,7 @@ class Company extends Model $candidates[] = ltrim($logoPath, '/'); } - if ($legacyPortal && filled($logoFilename)) { + if (filled($logoFilename)) { $candidates[] = "company-logos/{$legacyPortal}/{$this->id}/{$logoFilename}"; $candidates[] = "{$legacyPortal}/company/{$logoFilename}"; } diff --git a/app/Models/Contact.php b/app/Models/Contact.php index 1493fa87..e1f385cc 100644 --- a/app/Models/Contact.php +++ b/app/Models/Contact.php @@ -58,6 +58,9 @@ class Contact extends Model ]; } + /** + * @return BelongsTo + */ public function company(): BelongsTo { return $this->belongsTo(Company::class); @@ -74,11 +77,17 @@ class Contact extends Model return $name !== '' ? $name : __('Kontakt ohne Name'); } + /** + * @return BelongsToMany + */ public function pressReleases(): BelongsToMany { return $this->belongsToMany(PressRelease::class, 'press_release_contact'); } + /** + * @return BelongsToMany + */ public function users(): BelongsToMany { return $this->belongsToMany(User::class) diff --git a/app/Models/ContactMessage.php b/app/Models/ContactMessage.php index 349c8a53..3aa9d7ea 100644 --- a/app/Models/ContactMessage.php +++ b/app/Models/ContactMessage.php @@ -55,11 +55,17 @@ class ContactMessage extends Model return 'KT-'.$this->id; } + /** + * @return BelongsTo + */ public function user(): BelongsTo { return $this->belongsTo(User::class); } + /** + * @return BelongsTo + */ public function resolver(): BelongsTo { return $this->belongsTo(User::class, 'resolved_by_user_id'); diff --git a/app/Models/CreditTopup.php b/app/Models/CreditTopup.php index 85567956..40286bc9 100644 --- a/app/Models/CreditTopup.php +++ b/app/Models/CreditTopup.php @@ -37,6 +37,9 @@ class CreditTopup extends Model ]; } + /** + * @return BelongsTo + */ public function user(): BelongsTo { return $this->belongsTo(User::class); diff --git a/app/Models/CreditTransaction.php b/app/Models/CreditTransaction.php index 53f5e421..889a0b4b 100644 --- a/app/Models/CreditTransaction.php +++ b/app/Models/CreditTransaction.php @@ -38,16 +38,25 @@ class CreditTransaction extends Model ]; } + /** + * @return BelongsTo + */ public function wallet(): BelongsTo { return $this->belongsTo(CreditWallet::class, 'credit_wallet_id'); } + /** + * @return BelongsTo + */ public function user(): BelongsTo { return $this->belongsTo(User::class); } + /** + * @return MorphTo + */ public function reference(): MorphTo { return $this->morphTo(); diff --git a/app/Models/CreditWallet.php b/app/Models/CreditWallet.php index 114a5277..4a87fee6 100644 --- a/app/Models/CreditWallet.php +++ b/app/Models/CreditWallet.php @@ -32,11 +32,17 @@ class CreditWallet extends Model ]; } + /** + * @return BelongsTo + */ public function user(): BelongsTo { return $this->belongsTo(User::class); } + /** + * @return HasMany + */ public function transactions(): HasMany { return $this->hasMany(CreditTransaction::class)->orderByDesc('created_at'); diff --git a/app/Models/EmailNotificationOptOut.php b/app/Models/EmailNotificationOptOut.php index 34195bf5..88775077 100644 --- a/app/Models/EmailNotificationOptOut.php +++ b/app/Models/EmailNotificationOptOut.php @@ -33,6 +33,9 @@ class EmailNotificationOptOut extends Model ]; } + /** + * @return BelongsTo + */ public function user(): BelongsTo { return $this->belongsTo(User::class); diff --git a/app/Models/FooterCode.php b/app/Models/FooterCode.php index becf5e71..be669081 100644 --- a/app/Models/FooterCode.php +++ b/app/Models/FooterCode.php @@ -36,6 +36,9 @@ class FooterCode extends Model ]; } + /** + * @return BelongsToMany + */ public function categories(): BelongsToMany { return $this->belongsToMany(Category::class, 'category_footer_code'); diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index 8bcfb710..b8d65652 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -46,16 +46,25 @@ class Invoice extends Model ]; } + /** + * @return BelongsTo + */ public function user(): BelongsTo { return $this->belongsTo(User::class); } + /** + * @return BelongsTo + */ public function userPayment(): BelongsTo { return $this->belongsTo(UserPayment::class, 'user_payment_id'); } + /** + * @return BelongsTo + */ public function invoiceBillingAddress(): BelongsTo { return $this->belongsTo(InvoiceBillingAddress::class); diff --git a/app/Models/InvoiceBillingAddress.php b/app/Models/InvoiceBillingAddress.php index 1680fe87..e2d090a6 100644 --- a/app/Models/InvoiceBillingAddress.php +++ b/app/Models/InvoiceBillingAddress.php @@ -23,6 +23,9 @@ class InvoiceBillingAddress extends Model 'vat_id', ]; + /** + * @return HasMany + */ public function invoices(): HasMany { return $this->hasMany(Invoice::class); diff --git a/app/Models/KiAudit.php b/app/Models/KiAudit.php index cbb051e2..ccf5755e 100644 --- a/app/Models/KiAudit.php +++ b/app/Models/KiAudit.php @@ -44,6 +44,9 @@ class KiAudit extends Model ]; } + /** + * @return BelongsTo + */ public function pressRelease(): BelongsTo { return $this->belongsTo(PressRelease::class); diff --git a/app/Models/LegacyInvoice.php b/app/Models/LegacyInvoice.php index 545df100..eb1e9814 100644 --- a/app/Models/LegacyInvoice.php +++ b/app/Models/LegacyInvoice.php @@ -49,6 +49,9 @@ class LegacyInvoice extends Model ]; } + /** + * @return BelongsTo + */ public function user(): BelongsTo { return $this->belongsTo(User::class); diff --git a/app/Models/LegalRequest.php b/app/Models/LegalRequest.php index 9932c675..d5285dcd 100644 --- a/app/Models/LegalRequest.php +++ b/app/Models/LegalRequest.php @@ -79,11 +79,17 @@ class LegalRequest extends Model return $this->payload['reported_url'] ?? null; } + /** + * @return BelongsTo + */ public function pressRelease(): BelongsTo { return $this->belongsTo(PressRelease::class); } + /** + * @return BelongsTo + */ public function resolver(): BelongsTo { return $this->belongsTo(User::class, 'resolved_by_user_id'); @@ -91,6 +97,8 @@ class LegalRequest extends Model /** * Bezahlvorgang der kostenpflichtigen Spuren (Depublizierung/Änderung). + * + * @return BelongsTo */ public function singlePurchase(): BelongsTo { @@ -100,6 +108,8 @@ class LegalRequest extends Model /** * Verschlüsselte Vorher-Snapshots ausgeführter Mutationen (Korrektur/ * Anonymisierung) — Grundlage der Rückgängig-Option bis zur Löschfrist. + * + * @return HasMany */ public function redactionSnapshots(): HasMany { diff --git a/app/Models/MagicLink.php b/app/Models/MagicLink.php index b68fb287..2931b6c8 100644 --- a/app/Models/MagicLink.php +++ b/app/Models/MagicLink.php @@ -27,6 +27,9 @@ class MagicLink extends Model ]; } + /** + * @return BelongsTo + */ public function user(): BelongsTo { return $this->belongsTo(User::class); diff --git a/app/Models/NewsletterCampaign.php b/app/Models/NewsletterCampaign.php index ea66aefb..dd21d920 100644 --- a/app/Models/NewsletterCampaign.php +++ b/app/Models/NewsletterCampaign.php @@ -45,11 +45,17 @@ class NewsletterCampaign extends Model ]; } + /** + * @return BelongsTo + */ public function createdBy(): BelongsTo { return $this->belongsTo(User::class, 'created_by_user_id'); } + /** + * @return HasMany + */ public function recipients(): HasMany { return $this->hasMany(NewsletterCampaignRecipient::class); diff --git a/app/Models/NewsletterCampaignRecipient.php b/app/Models/NewsletterCampaignRecipient.php index a5301713..a84e16c6 100644 --- a/app/Models/NewsletterCampaignRecipient.php +++ b/app/Models/NewsletterCampaignRecipient.php @@ -30,11 +30,17 @@ class NewsletterCampaignRecipient extends Model ]; } + /** + * @return BelongsTo + */ public function campaign(): BelongsTo { return $this->belongsTo(NewsletterCampaign::class, 'newsletter_campaign_id'); } + /** + * @return BelongsTo + */ public function user(): BelongsTo { return $this->belongsTo(User::class); diff --git a/app/Models/NewsletterSubscription.php b/app/Models/NewsletterSubscription.php index 5aa2b25d..2fbb9d94 100644 --- a/app/Models/NewsletterSubscription.php +++ b/app/Models/NewsletterSubscription.php @@ -40,6 +40,9 @@ class NewsletterSubscription extends Model ]; } + /** + * @return BelongsTo + */ public function user(): BelongsTo { return $this->belongsTo(User::class); diff --git a/app/Models/PaymentOption.php b/app/Models/PaymentOption.php index ae173ff6..c90ee044 100644 --- a/app/Models/PaymentOption.php +++ b/app/Models/PaymentOption.php @@ -33,11 +33,17 @@ class PaymentOption extends Model ]; } + /** + * @return HasMany + */ public function translations(): HasMany { return $this->hasMany(PaymentOptionTranslation::class); } + /** + * @return HasMany + */ public function userPaymentOptions(): HasMany { return $this->hasMany(UserPaymentOption::class); diff --git a/app/Models/PaymentOptionTranslation.php b/app/Models/PaymentOptionTranslation.php index a6d53b9c..f458a9e5 100644 --- a/app/Models/PaymentOptionTranslation.php +++ b/app/Models/PaymentOptionTranslation.php @@ -17,6 +17,9 @@ class PaymentOptionTranslation extends Model 'description', ]; + /** + * @return BelongsTo + */ public function paymentOption(): BelongsTo { return $this->belongsTo(PaymentOption::class); diff --git a/app/Models/PressRelease.php b/app/Models/PressRelease.php index 70de3bef..b8354258 100644 --- a/app/Models/PressRelease.php +++ b/app/Models/PressRelease.php @@ -57,7 +57,7 @@ class PressRelease extends Model static::creating(function (self $pressRelease): void { if (blank($pressRelease->placeholder_variant)) { $seed = $pressRelease->uuid ?? $pressRelease->title ?? (string) now()->timestamp; - $pressRelease->placeholder_variant = PressReleasePlaceholder::fromSeed($seed)->value; + $pressRelease->placeholder_variant = PressReleasePlaceholder::fromSeed($seed); } }); } @@ -309,36 +309,57 @@ class PressRelease extends Model return $publishedAt->isoFormat('D. MMM'); } + /** + * @return BelongsTo + */ public function user(): BelongsTo { return $this->belongsTo(User::class); } + /** + * @return BelongsTo + */ public function company(): BelongsTo { return $this->belongsTo(Company::class); } + /** + * @return BelongsTo + */ public function category(): BelongsTo { return $this->belongsTo(Category::class); } + /** + * @return HasMany + */ public function images(): HasMany { return $this->hasMany(PressReleaseImage::class); } + /** + * @return HasMany + */ public function attachments(): HasMany { return $this->hasMany(PressReleaseAttachment::class); } + /** + * @return BelongsToMany + */ public function contacts(): BelongsToMany { return $this->belongsToMany(Contact::class, 'press_release_contact'); } + /** + * @return HasMany + */ public function statusLogs(): HasMany { return $this->hasMany(PressReleaseStatusLog::class)->orderByDesc('created_at'); @@ -346,6 +367,8 @@ class PressRelease extends Model /** * Ereignis-Log (Aufrufe/Shares) — Aufruf-Gesamtzähler bleibt `hits`. + * + * @return HasMany */ public function events(): HasMany { @@ -383,6 +406,8 @@ class PressRelease extends Model /** * Versionierter Audit-Trail der KI-Deklarationen (Art. 50 KI-VO) — * jeder Submit erzeugt einen neuen, unveränderlichen Eintrag. + * + * @return HasMany */ public function aiDeclarations(): HasMany { @@ -436,11 +461,17 @@ class PressRelease extends Model ], $portals); } + /** + * @return HasMany + */ public function kiAudits(): HasMany { return $this->hasMany(KiAudit::class)->orderByDesc('created_at'); } + /** + * @return HasMany + */ public function boosts(): HasMany { return $this->hasMany(Boost::class); diff --git a/app/Models/PressReleaseAiDeclaration.php b/app/Models/PressReleaseAiDeclaration.php index cdeccaae..f5e6253f 100644 --- a/app/Models/PressReleaseAiDeclaration.php +++ b/app/Models/PressReleaseAiDeclaration.php @@ -38,16 +38,25 @@ class PressReleaseAiDeclaration extends Model ]; } + /** + * @return BelongsTo + */ public function pressRelease(): BelongsTo { return $this->belongsTo(PressRelease::class); } + /** + * @return BelongsTo + */ public function user(): BelongsTo { return $this->belongsTo(User::class); } + /** + * @return BelongsTo + */ public function company(): BelongsTo { return $this->belongsTo(Company::class); diff --git a/app/Models/PressReleaseAttachment.php b/app/Models/PressReleaseAttachment.php index 7f4d3670..5b952ad2 100644 --- a/app/Models/PressReleaseAttachment.php +++ b/app/Models/PressReleaseAttachment.php @@ -36,6 +36,9 @@ class PressReleaseAttachment extends Model ]; } + /** + * @return BelongsTo + */ public function pressRelease(): BelongsTo { return $this->belongsTo(PressRelease::class); diff --git a/app/Models/PressReleaseImage.php b/app/Models/PressReleaseImage.php index 1fd1dd22..58364c7c 100644 --- a/app/Models/PressReleaseImage.php +++ b/app/Models/PressReleaseImage.php @@ -59,6 +59,9 @@ class PressReleaseImage extends Model ]; } + /** + * @return BelongsTo + */ public function pressRelease(): BelongsTo { return $this->belongsTo(PressRelease::class); diff --git a/app/Models/PressReleaseRedactionSnapshot.php b/app/Models/PressReleaseRedactionSnapshot.php index e20e9fe4..5373d2c2 100644 --- a/app/Models/PressReleaseRedactionSnapshot.php +++ b/app/Models/PressReleaseRedactionSnapshot.php @@ -52,11 +52,17 @@ class PressReleaseRedactionSnapshot extends Model ]); } + /** + * @return BelongsTo + */ public function legalRequest(): BelongsTo { return $this->belongsTo(LegalRequest::class); } + /** + * @return BelongsTo + */ public function pressRelease(): BelongsTo { return $this->belongsTo(PressRelease::class); diff --git a/app/Models/PressReleaseStatusLog.php b/app/Models/PressReleaseStatusLog.php index 15fbfcc0..00c2c100 100644 --- a/app/Models/PressReleaseStatusLog.php +++ b/app/Models/PressReleaseStatusLog.php @@ -29,11 +29,17 @@ class PressReleaseStatusLog extends Model ]; } + /** + * @return BelongsTo + */ public function pressRelease(): BelongsTo { return $this->belongsTo(PressRelease::class); } + /** + * @return BelongsTo + */ public function changedBy(): BelongsTo { return $this->belongsTo(User::class, 'changed_by_user_id'); diff --git a/app/Models/Profile.php b/app/Models/Profile.php index a439c12c..fca390d6 100644 --- a/app/Models/Profile.php +++ b/app/Models/Profile.php @@ -45,6 +45,9 @@ class Profile extends Model ]; } + /** + * @return BelongsTo + */ public function user(): BelongsTo { return $this->belongsTo(User::class); diff --git a/app/Models/ReviewCheck.php b/app/Models/ReviewCheck.php index e5199458..5cca1d0e 100644 --- a/app/Models/ReviewCheck.php +++ b/app/Models/ReviewCheck.php @@ -34,11 +34,17 @@ class ReviewCheck extends Model ]; } + /** + * @return BelongsTo + */ public function user(): BelongsTo { return $this->belongsTo(User::class); } + /** + * @return BelongsTo + */ public function pressRelease(): BelongsTo { return $this->belongsTo(PressRelease::class); diff --git a/app/Models/SinglePurchase.php b/app/Models/SinglePurchase.php index 6fb079ba..bec9aa22 100644 --- a/app/Models/SinglePurchase.php +++ b/app/Models/SinglePurchase.php @@ -43,11 +43,17 @@ class SinglePurchase extends Model ]; } + /** + * @return BelongsTo + */ public function user(): BelongsTo { return $this->belongsTo(User::class); } + /** + * @return BelongsTo + */ public function pressRelease(): BelongsTo { return $this->belongsTo(PressRelease::class); diff --git a/app/Models/User.php b/app/Models/User.php index 9609486c..b9addd2e 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -343,21 +343,33 @@ class User extends Authenticatable implements MustVerifyEmail ->implode(''); } + /** + * @return HasOne + */ public function profile(): HasOne { return $this->hasOne(Profile::class); } + /** + * @return HasMany + */ public function magicLinks(): HasMany { return $this->hasMany(MagicLink::class); } + /** + * @return HasMany + */ public function ownedCompanies(): HasMany { return $this->hasMany(Company::class, 'owner_user_id'); } + /** + * @return BelongsToMany + */ public function companies(): BelongsToMany { return $this->belongsToMany(Company::class) @@ -365,22 +377,34 @@ class User extends Authenticatable implements MustVerifyEmail ->withTimestamps(); } + /** + * @return BelongsToMany + */ public function contacts(): BelongsToMany { return $this->belongsToMany(Contact::class) ->withTimestamps(); } + /** + * @return HasMany + */ public function pressReleases(): HasMany { return $this->hasMany(PressRelease::class); } + /** + * @return HasMany + */ public function newsletterSubscriptions(): HasMany { return $this->hasMany(NewsletterSubscription::class); } + /** + * @return HasMany + */ public function emailNotificationOptOuts(): HasMany { return $this->hasMany(EmailNotificationOptOut::class); @@ -408,36 +432,57 @@ class User extends Authenticatable implements MustVerifyEmail $this->emailNotificationOptOuts()->where('type', $type->value)->delete(); } + /** + * @return HasOne + */ public function billingAddress(): HasOne { return $this->hasOne(BillingAddress::class); } + /** + * @return HasMany + */ public function userPaymentOptions(): HasMany { return $this->hasMany(UserPaymentOption::class); } + /** + * @return HasMany + */ public function singlePurchases(): HasMany { return $this->hasMany(SinglePurchase::class); } + /** + * @return HasOne + */ public function creditWallet(): HasOne { return $this->hasOne(CreditWallet::class); } + /** + * @return HasMany + */ public function creditTransactions(): HasMany { return $this->hasMany(CreditTransaction::class); } + /** + * @return HasMany + */ public function reviewChecks(): HasMany { return $this->hasMany(ReviewCheck::class); } + /** + * @return HasMany + */ public function creditTopups(): HasMany { return $this->hasMany(CreditTopup::class); @@ -456,12 +501,17 @@ class User extends Authenticatable implements MustVerifyEmail * Lokale Rechnungen (STR- und MAN-Kreis). Überschreibt bewusst die * gleichnamige Cashier-Methode — Stripe-Rechnungen werden beim * Webhook-Sync (9E) in diese Tabelle gespiegelt. + * + * @return HasMany */ public function invoices(): HasMany { return $this->hasMany(Invoice::class); } + /** + * @return HasMany + */ public function legacyInvoices(): HasMany { return $this->hasMany(LegacyInvoice::class); @@ -476,6 +526,9 @@ class User extends Authenticatable implements MustVerifyEmail return $this->legacyInvoices()->latestFirst()->first(); } + /** + * @return HasMany + */ public function filterPresets(): HasMany { return $this->hasMany(UserFilterPreset::class); diff --git a/app/Models/UserFilterPreset.php b/app/Models/UserFilterPreset.php index 2cab8652..c9b09afa 100644 --- a/app/Models/UserFilterPreset.php +++ b/app/Models/UserFilterPreset.php @@ -25,6 +25,9 @@ class UserFilterPreset extends Model ]; } + /** + * @return BelongsTo + */ public function user(): BelongsTo { return $this->belongsTo(User::class); diff --git a/app/Models/UserPayment.php b/app/Models/UserPayment.php index 6961de30..4d47b34b 100644 --- a/app/Models/UserPayment.php +++ b/app/Models/UserPayment.php @@ -29,11 +29,17 @@ class UserPayment extends Model ]; } + /** + * @return BelongsTo + */ public function userPaymentOption(): BelongsTo { return $this->belongsTo(UserPaymentOption::class); } + /** + * @return HasMany + */ public function invoices(): HasMany { return $this->hasMany(Invoice::class, 'user_payment_id'); diff --git a/app/Models/UserPaymentOption.php b/app/Models/UserPaymentOption.php index 4e29bd45..87ed0708 100644 --- a/app/Models/UserPaymentOption.php +++ b/app/Models/UserPaymentOption.php @@ -38,16 +38,25 @@ class UserPaymentOption extends Model ]; } + /** + * @return BelongsTo + */ public function user(): BelongsTo { return $this->belongsTo(User::class); } + /** + * @return BelongsTo + */ public function paymentOption(): BelongsTo { return $this->belongsTo(PaymentOption::class); } + /** + * @return BelongsToMany + */ public function companies(): BelongsToMany { return $this->belongsToMany(Company::class, 'user_payment_option_company') @@ -55,6 +64,9 @@ class UserPaymentOption extends Model ->withTimestamps(); } + /** + * @return HasMany + */ public function payments(): HasMany { return $this->hasMany(UserPayment::class); diff --git a/app/Services/Billing/ManualInvoiceService.php b/app/Services/Billing/ManualInvoiceService.php index 74f1b545..a5cd29f8 100644 --- a/app/Services/Billing/ManualInvoiceService.php +++ b/app/Services/Billing/ManualInvoiceService.php @@ -75,7 +75,6 @@ class ManualInvoiceService UserPaymentOptionStatus::Grandfathered, ], true) || $lockedOption->stripe_subscription_id !== null - || $lockedOption->current_period_end === null || $lockedOption->current_period_end->isAfter($asOf) ) { return null; diff --git a/app/Services/PressRelease/ProofPdfRenderer.php b/app/Services/PressRelease/ProofPdfRenderer.php index 37f2054f..9616ef87 100644 --- a/app/Services/PressRelease/ProofPdfRenderer.php +++ b/app/Services/PressRelease/ProofPdfRenderer.php @@ -208,7 +208,7 @@ class ProofPdfRenderer $image = $this->coverImage($pressRelease); $embed = $image ? $this->loadCoverImage($image) : null; - if ($embed === null || $image === null) { + if ($embed === null) { $this->sectionHeading('Angaben zur Veröffentlichung'); $lastIndex = count($rows) - 1; diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index c796d620..a731c9d7 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,11 +1,29 @@ parameters: ignoreErrors: + - + message: '#^Instanceof between App\\Models\\User and Illuminate\\Contracts\\Auth\\MustVerifyEmail will always evaluate to true\.$#' + identifier: instanceof.alwaysTrue + count: 1 + path: app/Actions/Fortify/UpdateUserProfileInformation.php + - message: '#^Expression on left side of \?\? is not nullable\.$#' identifier: nullCoalesce.expr count: 1 path: app/Console/Commands/FixLegacyTimestamps.php + - + message: '#^Using nullsafe property access on non\-nullable type App\\Enums\\Portal\. Use \-\> instead\.$#' + identifier: nullsafe.neverNull + count: 1 + path: app/Console/Commands/MigrateLegacyAgencyNames.php + + - + message: '#^Offset ''priority'' on array\{path\: string, priority\: 0\|10\|20\|30\|40\|50\} on left side of \?\? always exists and is not nullable\.$#' + identifier: nullCoalesce.offset + count: 1 + path: app/Console/Commands/MigrateLegacyMedia.php + - message: '#^Call to static method chunk\(\) on an unknown class App\\Models\\Legacy\\Presseecho\\Article\.$#' identifier: class.notFound @@ -43,88 +61,94 @@ parameters: path: app/Console/Commands/MigratePresseData.php - - message: '#^Call to function is_array\(\) with string\|null will always evaluate to false\.$#' - identifier: function.impossibleType + message: '#^Access to an undefined property App\\Models\\Company\:\:\$has_contacts\.$#' + identifier: property.notFound count: 1 - path: app/Console/Commands/SyncPressReleaseImages.php + path: app/Console/Commands/RetireAgencyCompanyType.php - - message: '#^Result of \|\| is always true\.$#' - identifier: booleanOr.alwaysTrue + message: '#^Access to an undefined property App\\Models\\Company\:\:\$has_payment_option_links\.$#' + identifier: property.notFound + count: 2 + path: app/Console/Commands/RetireAgencyCompanyType.php + + - + message: '#^Access to an undefined property App\\Models\\Company\:\:\$has_press_releases\.$#' + identifier: property.notFound count: 1 - path: app/Console/Commands/SyncPressReleaseImages.php + path: app/Console/Commands/RetireAgencyCompanyType.php - - message: '#^Strict comparison using \=\=\= between \*NEVER\* and array\{\} will always evaluate to false\.$#' - identifier: identical.alwaysFalse + message: '#^Access to an undefined property App\\Models\\Company\:\:\$has_third_party_members\.$#' + identifier: property.notFound + count: 2 + path: app/Console/Commands/RetireAgencyCompanyType.php + + - + message: '#^Using nullsafe property access on non\-nullable type App\\Enums\\Portal\. Use \-\> instead\.$#' + identifier: nullsafe.neverNull count: 1 - path: app/Console/Commands/SyncPressReleaseImages.php + path: app/Console/Commands/RetireAgencyCompanyType.php - - message: '#^Cannot access property \$value on string\.$#' - identifier: property.nonObject + message: '#^Using nullsafe property access "\?\-\>legacy_id" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' + identifier: nullsafe.neverNull count: 1 - path: app/Http/Controllers/Api/V1/NewsletterSubscriptionController.php + path: app/Console/Commands/SeedLegacyApiTestdata.php - - message: '#^Cannot access property \$value on string\.$#' - identifier: property.nonObject - count: 5 - path: app/Http/Controllers/Api/V1/PressReleaseController.php + message: '#^Using nullsafe property access on non\-nullable type App\\Enums\\Portal\. Use \-\> instead\.$#' + identifier: nullsafe.neverNull + count: 1 + path: app/Http/Controllers/Api/Legacy/LegacyContactController.php - - message: '#^Instanceof between string and App\\Enums\\PressReleaseStatus will always evaluate to false\.$#' - identifier: instanceof.alwaysFalse + message: '#^Using nullsafe property access on non\-nullable type App\\Enums\\Portal\. Use \-\> instead\.$#' + identifier: nullsafe.neverNull + count: 1 + path: app/Http/Controllers/Api/Legacy/LegacyPressReleaseController.php + + - + message: '#^Instanceof between App\\Enums\\PressReleaseStatus and App\\Enums\\PressReleaseStatus will always evaluate to true\.$#' + identifier: instanceof.alwaysTrue count: 1 path: app/Http/Controllers/Api/V1/PressReleaseImageController.php - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$is_active\.$#' + message: '#^Method App\\Http\\Controllers\\Auth\\GoogleController\:\:redirect\(\) should return Illuminate\\Http\\RedirectResponse but returns Symfony\\Component\\HttpFoundation\\RedirectResponse\.$#' + identifier: return.type + count: 1 + path: app/Http/Controllers/Auth/GoogleController.php + + - + message: '#^Access to an undefined property Laravel\\Cashier\\Payment\:\:\$id\.$#' identifier: property.notFound count: 1 - path: app/Http/Controllers/Auth/MagicLinkConsumeController.php + path: app/Http/Controllers/CheckoutController.php - - message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Model\:\:canAccessAdmin\(\)\.$#' + message: '#^Using nullsafe property access on non\-nullable type App\\Enums\\Portal\. Use \-\> instead\.$#' + identifier: nullsafe.neverNull + count: 1 + path: app/Http/Controllers/LegalRequestController.php + + - + message: '#^Using nullsafe property access on non\-nullable type App\\Enums\\Portal\. Use \-\> instead\.$#' + identifier: nullsafe.neverNull + count: 1 + path: app/Http/Controllers/Web/PressReleaseReportController.php + + - + message: '#^Using nullsafe property access "\?\-\>value" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' + identifier: nullsafe.neverNull + count: 1 + path: app/Http/Controllers/Web/ReportFormController.php + + - + message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Builder\\:\:visibleInArchive\(\)\.$#' identifier: method.notFound count: 1 - path: app/Http/Controllers/Auth/MagicLinkConsumeController.php - - - - message: '#^Cannot call method isPast\(\) on string\.$#' - identifier: method.nonObject - count: 1 - path: app/Http/Controllers/Auth/MagicLinkConsumeController.php - - - - message: '#^Cannot call method isPast\(\) on string\.$#' - identifier: method.nonObject - count: 1 - path: app/Http/Controllers/PressReleasePreviewController.php - - - - message: '#^Negated boolean expression is always true\.$#' - identifier: booleanNot.alwaysTrue - count: 1 - path: app/Http/Controllers/PressReleasePreviewController.php - - - - message: '#^Offset ''press_release_id'' on string\|null on left side of \?\? does not exist\.$#' - identifier: nullCoalesce.offset - count: 1 - path: app/Http/Controllers/PressReleasePreviewController.php - - - - message: '#^Ternary operator condition is always false\.$#' - identifier: ternary.alwaysFalse - count: 1 - path: app/Http/Controllers/PressReleasePreviewController.php - - - - message: '#^Unreachable statement \- code above always terminates\.$#' - identifier: deadCode.unreachable - count: 1 - path: app/Http/Controllers/PressReleasePreviewController.php + path: app/Http/Controllers/Web/SitemapController.php - message: '#^Call to function method_exists\(\) with Laravel\\Sanctum\\PersonalAccessToken and ''getKey'' will always evaluate to true\.$#' @@ -348,6 +372,12 @@ parameters: count: 2 path: app/Http/Resources/PressReleaseImageResource.php + - + message: '#^Call to an undefined method App\\Http\\Resources\\PressReleaseImageResource\:\:variantUrl\(\)\.$#' + identifier: method.notFound + count: 1 + path: app/Http/Resources/PressReleaseImageResource.php + - message: '#^Access to an undefined property App\\Http\\Resources\\PressReleaseResource\:\:\$backlink_url\.$#' identifier: property.notFound @@ -469,10 +499,142 @@ parameters: path: app/Http/Responses/RoleAwareLoginResponse.php - - message: '#^Cannot access property \$value on string\.$#' - identifier: property.nonObject + message: '#^Instanceof between Laravel\\Cashier\\Billable\|null and App\\Models\\User will always evaluate to false\.$#' + identifier: instanceof.alwaysFalse + count: 3 + path: app/Listeners/ProcessStripeWebhook.php + + - + message: '#^Method App\\Listeners\\ProcessStripeWebhook\:\:localSubscription\(\) is unused\.$#' + identifier: method.unused count: 1 - path: app/Models/Company.php + path: app/Listeners/ProcessStripeWebhook.php + + - + message: '#^Method App\\Listeners\\ProcessStripeWebhook\:\:snapshotAddress\(\) is unused\.$#' + identifier: method.unused + count: 1 + path: app/Listeners/ProcessStripeWebhook.php + + - + message: '#^Property App\\Listeners\\ProcessStripeWebhook\:\:\$activityLog is never read, only written\.$#' + identifier: property.onlyWritten + count: 1 + path: app/Listeners/ProcessStripeWebhook.php + + - + message: '#^Property App\\Listeners\\ProcessStripeWebhook\:\:\$numbers is never read, only written\.$#' + identifier: property.onlyWritten + count: 1 + path: app/Listeners/ProcessStripeWebhook.php + + - + message: '#^Unreachable statement \- code above always terminates\.$#' + identifier: deadCode.unreachable + count: 3 + path: app/Listeners/ProcessStripeWebhook.php + + - + message: '#^Using nullsafe property access "\?\-\>address1" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' + identifier: nullsafe.neverNull + count: 1 + path: app/Listeners/ProcessStripeWebhook.php + + - + message: '#^Using nullsafe property access "\?\-\>city" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' + identifier: nullsafe.neverNull + count: 1 + path: app/Listeners/ProcessStripeWebhook.php + + - + message: '#^Using nullsafe property access "\?\-\>country_code" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' + identifier: nullsafe.neverNull + count: 1 + path: app/Listeners/ProcessStripeWebhook.php + + - + message: '#^Using nullsafe property access "\?\-\>name" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' + identifier: nullsafe.neverNull + count: 1 + path: app/Listeners/ProcessStripeWebhook.php + + - + message: '#^Using nullsafe property access "\?\-\>postal_code" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' + identifier: nullsafe.neverNull + count: 1 + path: app/Listeners/ProcessStripeWebhook.php + + - + message: '#^Using nullsafe property access "\?\-\>name" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' + identifier: nullsafe.neverNull + count: 1 + path: app/Listeners/RecordMailActivity.php + + - + message: '#^Trait App\\Livewire\\Concerns\\HandlesAdminPressReleaseActions is used zero times and is not analysed\.$#' + identifier: trait.unused + count: 1 + path: app/Livewire/Concerns/HandlesAdminPressReleaseActions.php + + - + message: '#^Trait App\\Livewire\\Concerns\\HasFullTextSearch is used zero times and is not analysed\.$#' + identifier: trait.unused + count: 1 + path: app/Livewire/Concerns/HasFullTextSearch.php + + - + message: '#^Trait App\\Livewire\\Concerns\\HasSortableTable is used zero times and is not analysed\.$#' + identifier: trait.unused + count: 1 + path: app/Livewire/Concerns/HasSortableTable.php + + - + message: '#^Trait App\\Livewire\\Concerns\\InteractsWithAiDeclaration is used zero times and is not analysed\.$#' + identifier: trait.unused + count: 1 + path: app/Livewire/Concerns/InteractsWithAiDeclaration.php + + - + message: '#^Trait App\\Livewire\\Concerns\\InteractsWithPressReleaseContacts is used zero times and is not analysed\.$#' + identifier: trait.unused + count: 1 + path: app/Livewire/Concerns/InteractsWithPressReleaseContacts.php + + - + message: '#^Trait App\\Livewire\\Concerns\\InteractsWithPressReleaseScheduling is used zero times and is not analysed\.$#' + identifier: trait.unused + count: 1 + path: app/Livewire/Concerns/InteractsWithPressReleaseScheduling.php + + - + message: '#^Trait App\\Livewire\\Concerns\\InteractsWithPressReleaseTags is used zero times and is not analysed\.$#' + identifier: trait.unused + count: 1 + path: app/Livewire/Concerns/InteractsWithPressReleaseTags.php + + - + message: '#^Trait App\\Livewire\\Concerns\\ProvidesPressReleaseFormRules is used zero times and is not analysed\.$#' + identifier: trait.unused + count: 1 + path: app/Livewire/Concerns/ProvidesPressReleaseFormRules.php + + - + message: '#^Trait App\\Livewire\\Concerns\\ProvidesPressReleasePresubmitChecks is used zero times and is not analysed\.$#' + identifier: trait.unused + count: 1 + path: app/Livewire/Concerns/ProvidesPressReleasePresubmitChecks.php + + - + message: '#^Trait App\\Livewire\\Concerns\\ProvidesUserFormRules is used zero times and is not analysed\.$#' + identifier: trait.unused + count: 1 + path: app/Livewire/Concerns/ProvidesUserFormRules.php + + - + message: '#^Using nullsafe property access "\?\-\>name" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' + identifier: nullsafe.neverNull + count: 1 + path: app/Models/Category.php - message: '#^PHPDoc tag @var with type Illuminate\\Database\\Eloquent\\Model is not subtype of native type \$this\(App\\Models\\Company\)\.$#' @@ -481,11 +643,47 @@ parameters: path: app/Models/Company.php - - message: '#^Using nullsafe property access on non\-nullable type string\. Use \-\> instead\.$#' + message: '#^Using nullsafe property access on non\-nullable type App\\Enums\\Portal\. Use \-\> instead\.$#' identifier: nullsafe.neverNull count: 1 path: app/Models/Company.php + - + message: '#^Using nullsafe property access "\?\-\>version" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' + identifier: nullsafe.neverNull + count: 1 + path: app/Models/LegalDocument.php + + - + message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Builder\:\:forPlacement\(\)\.$#' + identifier: method.notFound + count: 1 + path: app/Models/PressRelease.php + + - + message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Builder\:\:published\(\)\.$#' + identifier: method.notFound + count: 1 + path: app/Models/PressRelease.php + + - + message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Builder\\:\:active\(\)\.$#' + identifier: method.notFound + count: 1 + path: app/Models/PressRelease.php + + - + message: '#^Method App\\Models\\PressRelease\:\:embargoAtLocal\(\) should return Illuminate\\Support\\Carbon\|null but returns Carbon\\Carbon\|null\.$#' + identifier: return.type + count: 1 + path: app/Models/PressRelease.php + + - + message: '#^Method App\\Models\\PressRelease\:\:scheduledAtLocal\(\) should return Illuminate\\Support\\Carbon\|null but returns Carbon\\Carbon\|null\.$#' + identifier: return.type + count: 1 + path: app/Models/PressRelease.php + - message: '#^PHPDoc tag @var with type Illuminate\\Database\\Eloquent\\Model is not subtype of native type \$this\(App\\Models\\PressRelease\)\.$#' identifier: varTag.nativeType @@ -493,64 +691,28 @@ parameters: path: app/Models/PressRelease.php - - message: '#^Call to function is_string\(\) with null will always evaluate to false\.$#' - identifier: function.impossibleType - count: 1 - path: app/Models/PressReleaseImage.php + message: '#^Using nullsafe method call on non\-nullable type Illuminate\\Database\\Eloquent\\Collection\\. Use \-\> instead\.$#' + identifier: nullsafe.neverNull + count: 2 + path: app/Models/PressRelease.php - - message: '#^Call to function method_exists\(\) with Illuminate\\Filesystem\\FilesystemAdapter and ''url'' will always evaluate to true\.$#' - identifier: function.alreadyNarrowedType + message: '#^Using nullsafe property access "\?\-\>balance_credits" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' + identifier: nullsafe.neverNull count: 1 - path: app/Models/PressReleaseImage.php + path: app/Models/User.php - - message: '#^Offset string on string\|null on left side of \?\? does not exist\.$#' - identifier: nullCoalesce.offset - count: 1 - path: app/Models/PressReleaseImage.php + message: '#^Using nullsafe property access "\?\-\>press_release_quota" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' + identifier: nullsafe.neverNull + count: 2 + path: app/Models/User.php - - message: '#^Result of \|\| is always true\.$#' - identifier: booleanOr.alwaysTrue + message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Builder\:\:grandfatheredNow\(\)\.$#' + identifier: method.notFound count: 1 - path: app/Models/PressReleaseImage.php - - - - message: '#^Unreachable statement \- code above always terminates\.$#' - identifier: deadCode.unreachable - count: 1 - path: app/Models/PressReleaseImage.php - - - - message: '#^Call to function in_array\(\) with arguments string, array\{App\\Enums\\PressReleaseStatus\:\:Draft, App\\Enums\\PressReleaseStatus\:\:Rejected, App\\Enums\\PressReleaseStatus\:\:Review\} and true will always evaluate to false\.$#' - identifier: function.impossibleType - count: 1 - path: app/Policies/PressReleasePolicy.php - - - - message: '#^Call to function in_array\(\) with arguments string, array\{App\\Enums\\PressReleaseStatus\:\:Draft, App\\Enums\\PressReleaseStatus\:\:Rejected\} and true will always evaluate to false\.$#' - identifier: function.impossibleType - count: 1 - path: app/Policies/PressReleasePolicy.php - - - - message: '#^Result of && is always false\.$#' - identifier: booleanAnd.alwaysFalse - count: 1 - path: app/Policies/PressReleasePolicy.php - - - - message: '#^Strict comparison using \!\=\= between string and App\\Enums\\PressReleaseStatus\:\:Published will always evaluate to true\.$#' - identifier: notIdentical.alwaysTrue - count: 1 - path: app/Policies/PressReleasePolicy.php - - - - message: '#^Called ''env'' outside of the config directory which returns null when the config is cached, use ''config''\.$#' - identifier: larastan.noEnvCallsOutsideOfConfig - count: 1 - path: app/Providers/ThemeServiceProvider.php + path: app/Models/UserPaymentOption.php - message: '#^Method App\\Services\\Admin\\AdminSlowRequestReporter\:\:entries\(\) should return Illuminate\\Support\\Collection\\> but returns Illuminate\\Support\\Collection\\>\.$#' @@ -558,12 +720,6 @@ parameters: count: 1 path: app/Services/Admin/AdminSlowRequestReporter.php - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$status\.$#' - identifier: property.notFound - count: 1 - path: app/Services/Api/ApiAccessEligibilityService.php - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$requests\.$#' identifier: property.notFound @@ -577,13 +733,7 @@ parameters: path: app/Services/Api/ApiUsageReporter.php - - message: '#^Cannot call method toIso8601String\(\) on string\.$#' - identifier: method.nonObject - count: 1 - path: app/Services/Api/ApiUsageReporter.php - - - - message: '#^Using nullsafe method call on non\-nullable type string\. Use \-\> instead\.$#' + message: '#^Using nullsafe method call on non\-nullable type Carbon\\Carbon\. Use \-\> instead\.$#' identifier: nullsafe.neverNull count: 1 path: app/Services/Api/ApiUsageReporter.php @@ -595,76 +745,22 @@ parameters: path: app/Services/Api/LegacyApiAccessLogAnalyzer.php - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$id\.$#' - identifier: property.notFound - count: 1 - path: app/Services/Api/LegacyApiCustomerReporter.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$invoice_date\.$#' - identifier: property.notFound - count: 1 - path: app/Services/Api/LegacyApiCustomerReporter.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$legacy_id\.$#' - identifier: property.notFound - count: 1 - path: app/Services/Api/LegacyApiCustomerReporter.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$legacy_portal\.$#' - identifier: property.notFound - count: 1 - path: app/Services/Api/LegacyApiCustomerReporter.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$number\.$#' - identifier: property.notFound - count: 1 - path: app/Services/Api/LegacyApiCustomerReporter.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$paid_at\.$#' - identifier: property.notFound - count: 1 - path: app/Services/Api/LegacyApiCustomerReporter.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$status\.$#' - identifier: property.notFound - count: 1 - path: app/Services/Api/LegacyApiCustomerReporter.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$total_cents\.$#' - identifier: property.notFound - count: 1 - path: app/Services/Api/LegacyApiCustomerReporter.php - - - - message: '#^Cannot access property \$value on string\.$#' - identifier: property.nonObject - count: 2 - path: app/Services/Api/LegacyApiCustomerReporter.php - - - - message: '#^Cannot call method toDateTimeString\(\) on string\.$#' - identifier: method.nonObject - count: 1 - path: app/Services/Api/LegacyApiCustomerReporter.php - - - - message: '#^Using nullsafe property access on non\-nullable type string\. Use \-\> instead\.$#' + message: '#^Using nullsafe property access on non\-nullable type App\\Enums\\Portal\. Use \-\> instead\.$#' identifier: nullsafe.neverNull count: 2 path: app/Services/Api/LegacyApiCustomerReporter.php - - message: '#^Method App\\Services\\Auth\\MagicLinkGenerator\:\:createLoginLink\(\) should return array\{magic_link\: App\\Models\\MagicLink, plain_token\: string, expires_at\: Illuminate\\Support\\Carbon\} but returns array\{magic_link\: Illuminate\\Database\\Eloquent\\Model, plain_token\: string, expires_at\: Illuminate\\Support\\Carbon\}\.$#' - identifier: return.type + message: '#^Using nullsafe property access on non\-nullable type App\\Enums\\RegistrationType\. Use \-\> instead\.$#' + identifier: nullsafe.neverNull count: 1 - path: app/Services/Auth/MagicLinkGenerator.php + path: app/Services/Api/LegacyApiCustomerReporter.php + + - + message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Relations\\Pivot\:\:\$role\.$#' + identifier: property.notFound + count: 1 + path: app/Services/Auth/ContactAccessService.php - message: '#^Using nullsafe property access "\?\-\>id" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' @@ -673,28 +769,40 @@ parameters: path: app/Services/Auth/MagicLinkGenerator.php - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$profile\.$#' - identifier: property.notFound + message: '#^Using nullsafe property access "\?\-\>currency" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' + identifier: nullsafe.neverNull count: 1 - path: app/Services/Billing/LegacyInvoicePdfRenderer.php + path: app/Services/Billing/ManualInvoiceService.php - - message: '#^Cannot call method betweenIncluded\(\) on string\.$#' - identifier: method.nonObject + message: '#^Using nullsafe property access "\?\-\>price_cents" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' + identifier: nullsafe.neverNull count: 1 - path: app/Services/Billing/LegacyInvoicePdfRenderer.php + path: app/Services/Billing/ManualInvoiceService.php - - message: '#^Cannot call method format\(\) on string\.$#' - identifier: method.nonObject - count: 4 - path: app/Services/Billing/LegacyInvoicePdfRenderer.php + message: '#^Access to property \$id on an unknown class Laravel\\Cashier\\Concerns\\Stripe\\TaxId\.$#' + identifier: class.notFound + count: 1 + path: app/Services/Billing/StripeCheckoutService.php - - message: '#^Cannot call method label\(\) on string\.$#' - identifier: method.nonObject - count: 3 - path: app/Services/Billing/LegacyInvoicePdfRenderer.php + message: '#^Access to property \$type on an unknown class Laravel\\Cashier\\Concerns\\Stripe\\TaxId\.$#' + identifier: class.notFound + count: 1 + path: app/Services/Billing/StripeCheckoutService.php + + - + message: '#^Access to property \$value on an unknown class Laravel\\Cashier\\Concerns\\Stripe\\TaxId\.$#' + identifier: class.notFound + count: 1 + path: app/Services/Billing/StripeCheckoutService.php + + - + message: '#^Using nullsafe property access "\?\-\>timestamp" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' + identifier: nullsafe.neverNull + count: 1 + path: app/Services/Billing/SubscriptionPeriodResolver.php - message: '#^Unsafe access to private property App\\Services\\CurrentPortalContext\:\:\$portal through static\:\:\.$#' @@ -702,6 +810,12 @@ parameters: count: 4 path: app/Services/CurrentPortalContext.php + - + message: '#^Using nullsafe property access "\?\-\>role" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' + identifier: nullsafe.neverNull + count: 1 + path: app/Services/Customer/CustomerCompanyContext.php + - message: '#^Offset ''filename'' on array\{dirname\?\: string, basename\: string, extension\?\: string, filename\: string\} on left side of \?\? always exists and is not nullable\.$#' identifier: nullCoalesce.offset @@ -715,40 +829,160 @@ parameters: path: app/Services/Import/PressReleaseImporter.php - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$email\.$#' + message: '#^Using nullsafe property access "\?\-\>uuid" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' + identifier: nullsafe.neverNull + count: 1 + path: app/Services/Import/PressReleaseImporter.php + + - + message: '#^Using nullsafe property access "\?\-\>translations" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' + identifier: nullsafe.neverNull + count: 2 + path: app/Services/PressRelease/BoostService.php + + - + message: '#^Using nullsafe property access on non\-nullable type App\\Enums\\Portal\. Use \-\> instead\.$#' + identifier: nullsafe.neverNull + count: 1 + path: app/Services/PressRelease/CorrectionService.php + + - + message: '#^Instanceof between App\\Enums\\PressReleaseStatus and App\\Enums\\PressReleaseStatus will always evaluate to true\.$#' + identifier: instanceof.alwaysTrue + count: 1 + path: app/Services/PressRelease/PressReleaseService.php + + - + message: '#^Method App\\Services\\PressRelease\\PressReleaseService\:\:resolvePublishedAt\(\) should return Illuminate\\Support\\Carbon but returns Carbon\\Carbon\.$#' + identifier: return.type + count: 3 + path: app/Services/PressRelease/PressReleaseService.php + + - + message: '#^Using nullsafe property access "\?\-\>name" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' + identifier: nullsafe.neverNull + count: 1 + path: app/Services/PressRelease/PressReleaseService.php + + - + message: '#^Comparison operation "\<" between int\<1, max\> and 1 is always false\.$#' + identifier: smaller.alwaysFalse + count: 2 + path: app/Services/PressRelease/ProofPdfRenderer.php + + - + message: '#^Instanceof between App\\Enums\\Portal and App\\Enums\\Portal will always evaluate to true\.$#' + identifier: instanceof.alwaysTrue + count: 1 + path: app/Services/PressRelease/ProofPdfRenderer.php + + - + message: '#^Result of \|\| is always false\.$#' + identifier: booleanOr.alwaysFalse + count: 1 + path: app/Services/PressRelease/ProofPdfRenderer.php + + - + message: '#^Access to an undefined property App\\Models\\Company\:\:\$recent_releases_count\.$#' identifier: property.notFound count: 1 - path: app/Services/PressRelease/PressReleaseService.php + path: app/Services/Web/CategoryPageData.php - - message: '#^Call to function in_array\(\) with arguments string, array\ and true will always evaluate to false\.$#' - identifier: function.impossibleType + message: '#^Access to an undefined property App\\Models\\Company\:\:\$today_releases_count\.$#' + identifier: property.notFound count: 1 - path: app/Services/PressRelease/PressReleaseService.php + path: app/Services/Web/CategoryPageData.php - - message: '#^Instanceof between string and App\\Enums\\PressReleaseStatus will always evaluate to false\.$#' - identifier: instanceof.alwaysFalse - count: 1 - path: app/Services/PressRelease/PressReleaseService.php + message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Builder\:\:forPortalValues\(\)\.$#' + identifier: method.notFound + count: 2 + path: app/Services/Web/CategoryPageData.php - - message: '#^Strict comparison using \!\=\= between string and App\\Enums\\PressReleaseStatus will always evaluate to true\.$#' - identifier: notIdentical.alwaysTrue + message: '#^Access to an undefined property App\\Models\\Company\:\:\$recent_releases_count\.$#' + identifier: property.notFound count: 1 - path: app/Services/PressRelease/PressReleaseService.php + path: app/Services/Web/HomePageData.php - - message: '#^Strict comparison using \=\=\= between string and App\\Enums\\PressReleaseStatus\:\:Published will always evaluate to false\.$#' - identifier: identical.alwaysFalse + message: '#^Access to an undefined property App\\Models\\Company\:\:\$today_releases_count\.$#' + identifier: property.notFound count: 1 - path: app/Services/PressRelease/PressReleaseService.php + path: app/Services/Web/HomePageData.php - - message: '#^Called ''env'' outside of the config directory which returns null when the config is cached, use ''config''\.$#' - identifier: larastan.noEnvCallsOutsideOfConfig + message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Builder\:\:forPortalValues\(\)\.$#' + identifier: method.notFound + count: 5 + path: app/Services/Web/HomePageData.php + + - + message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Builder\\:\:forPortalValues\(\)\.$#' + identifier: method.notFound count: 1 - path: bootstrap/app.php + path: app/Services/Web/HomePageData.php + + - + message: '#^Using nullsafe property access "\?\-\>name" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' + identifier: nullsafe.neverNull + count: 1 + path: app/Services/Web/HomePageData.php + + - + message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Builder\\:\:visibleInArchive\(\)\.$#' + identifier: method.notFound + count: 1 + path: app/Services/Web/HubPageData.php + + - + message: '#^Call to function is_array\(\) with list\ will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType + count: 1 + path: app/Services/Web/MarketDataService.php + + - + message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Builder\:\:forPortalValues\(\)\.$#' + identifier: method.notFound + count: 1 + path: app/Services/Web/NewsroomDetailPageData.php + + - + message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Builder\:\:forPortalValues\(\)\.$#' + identifier: method.notFound + count: 1 + path: app/Services/Web/NewsroomsPageData.php + + - + message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Builder\:\:forPortalValues\(\)\.$#' + identifier: method.notFound + count: 1 + path: app/Services/Web/ReleaseDetailPageData.php + + - + message: '#^Call to an undefined method Illuminate\\Support\\Collection\<\(int\|string\), mixed\>\:\:modelKeys\(\)\.$#' + identifier: method.notFound + count: 1 + path: app/Services/Web/ReleaseDetailPageData.php + + - + message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Builder\:\:forPortalValues\(\)\.$#' + identifier: method.notFound + count: 1 + path: app/Services/Web/VeroeffentlichenPageData.php + + - + message: '#^Method App\\Support\\CategoryNavigation\:\:query\(\) should return Illuminate\\Support\\Collection\ but returns Illuminate\\Support\\Collection\\.$#' + identifier: return.type + count: 1 + path: app/Support/CategoryNavigation.php + + - + message: '#^Call to function is_array\(\) with array\ will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType + count: 1 + path: app/Support/DomainAssetContext.php - message: '#^Call to an undefined method Faker\\Generator\:\:secondaryAddress\(\)\.$#' diff --git a/phpstan.neon b/phpstan.neon index 1c746f47..751fa2e0 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -4,6 +4,15 @@ includes: - phpstan-baseline.neon parameters: + # ZWINGEND für dieses Projekt: Alle Models deklarieren ihre Casts über die + # `casts()`-METHODE (Laravel-11-Stil). Laravel führt sie erst zur Laufzeit + # mit `$this->casts` zusammen, Larastan instanziiert Models aber ohne + # Konstruktor und sieht sie deshalb nicht — ohne diesen Schalter gilt + # `$pressRelease->status` als `string` statt als `PressReleaseStatus` und + # jeder Enum-Vergleich wird als „immer falsch" gemeldet. Larastans Default + # ist `false`. + parseModelCastsMethod: true + level: 4 paths: - app