php-tenant/Concerns/HasOrderRelationships.php
Claude 275cba29d7
refactor: guard external relationships in User model with class_exists()
Moves all external module relationships (Page, Project, Domain, Pixel,
AnalyticsWebsite, AnalyticsGoal, PushWebsite, PushCampaign, PushSegment,
PushFlow, TrustCampaign, TrustNotification, Order) out of the User model
into dedicated opt-in traits that resolve model classes at runtime via
class_exists(). This prevents fatal errors when consuming apps do not
have those modules installed.

New traits:
- HasPageRelationships (pages, projects, domains, pixels, bio/sub-page entitlements)
- HasAnalyticsRelationships (analytics websites, goals)
- HasPushRelationships (push websites, campaigns, segments, flows)
- HasTrustRelationships (trust campaigns, notifications)
- HasOrderRelationships (orders, vanity URL entitlement)

The User model still uses all five traits so existing consumers see no
breaking changes — the relationships now return null instead of throwing
a class-not-found error when the backing module is absent.

Fixes #6

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 13:22:36 +00:00

91 lines
2.2 KiB
PHP

<?php
declare(strict_types=1);
namespace Core\Tenant\Concerns;
use Core\Tenant\Models\Boost;
use Illuminate\Database\Eloquent\Relations\HasMany;
/**
* Order-related relationships for the User model.
*
* Requires the Order/Commerce module to be installed. All relationships
* are guarded with class_exists() so the tenant package remains functional
* even when the Commerce module is absent.
*
* @mixin \Core\Tenant\Models\User
*/
trait HasOrderRelationships
{
/**
* Get all orders placed by this user.
*/
public function orders(): ?HasMany
{
$class = $this->resolveOrderModel('Order');
if (! $class) {
return null;
}
return $this->hasMany($class);
}
/**
* Check if user can claim a vanity URL.
*
* Requires either:
* - A paid subscription (Creator/Agency package)
* - A one-time vanity URL boost purchase
*/
public function canClaimVanityUrl(): bool
{
// Check for vanity URL boost
$hasBoost = $this->boosts()
->where('feature_code', 'bio.vanity_url')
->where('status', Boost::STATUS_ACTIVE)
->exists();
if ($hasBoost) {
return true;
}
// Check for paid subscription (Creator or Agency package)
$orders = $this->orders();
if (! $orders) {
return false;
}
$hasPaidSubscription = $orders
->where('status', 'paid')
->where('total', '>', 0)
->whereHas('items', function ($query) {
$query->whereIn('item_code', ['creator', 'agency']);
})
->exists();
return $hasPaidSubscription;
}
/**
* Resolve an Order model class, checking common namespaces.
*/
protected function resolveOrderModel(string $model): ?string
{
$candidates = [
"Core\\Mod\\Commerce\\Models\\{$model}",
"Core\\Mod\\Social\\Models\\{$model}",
"App\\Models\\{$model}",
];
foreach ($candidates as $candidate) {
if (class_exists($candidate)) {
return $candidate;
}
}
return null;
}
}