resolvePageModel(); if (! $class) { return null; } return $this->hasMany($class); } /** * Get all page projects (folders) owned by this user. */ public function pageProjects(): ?HasMany { $class = $this->resolvePageProjectModel(); if (! $class) { return null; } return $this->hasMany($class); } /** * Get all custom domains owned by this user. */ public function pageDomains(): ?HasMany { $class = $this->resolvePageDomainModel(); if (! $class) { return null; } return $this->hasMany($class); } /** * Get all tracking pixels owned by this user. */ public function pagePixels(): ?HasMany { $class = $this->resolvePagePixelModel(); if (! $class) { return null; } return $this->hasMany($class); } /** * Get the user's bio.pages entitlement (base + boosts). */ public function getBioPagesLimit(): int { // Base: 1 page for all tiers $base = 1; // Add from boosts $boostPages = $this->boosts() ->where('feature_code', 'bio.pages') ->where('status', Boost::STATUS_ACTIVE) ->sum('limit_value'); return $base + (int) $boostPages; } /** * Check if user can create more bio pages. */ public function canCreateBioPage(): bool { $pages = $this->pages(); if (! $pages) { return false; } return $pages->rootPages()->count() < $this->getBioPagesLimit(); } /** * Get remaining bio page slots. */ public function remainingBioPageSlots(): int { $pages = $this->pages(); if (! $pages) { return 0; } return max(0, $this->getBioPagesLimit() - $pages->rootPages()->count()); } /** * Get the user's sub-page limit (0 base + boosts). */ public function getSubPagesLimit(): int { // Base: 0 sub-pages (free tier) $base = 0; // Add from boosts $boostPages = $this->boosts() ->where('feature_code', 'webpage.sub_pages') ->where('status', Boost::STATUS_ACTIVE) ->sum('limit_value'); return $base + (int) $boostPages; } /** * Get the total sub-pages count across all root pages. */ public function getSubPagesCount(): int { $pages = $this->pages(); if (! $pages) { return 0; } return $pages->subPages()->count(); } /** * Check if user can create more sub-pages. */ public function canCreateSubPage(): bool { return $this->getSubPagesCount() < $this->getSubPagesLimit(); } /** * Get remaining sub-page slots. */ public function remainingSubPageSlots(): int { return max(0, $this->getSubPagesLimit() - $this->getSubPagesCount()); } /** * Resolve the Page model class, checking common namespaces. */ protected function resolvePageModel(): ?string { foreach ($this->getPageModelCandidates('Page') as $candidate) { if (class_exists($candidate)) { return $candidate; } } return null; } /** * Resolve the Project model class, checking common namespaces. */ protected function resolvePageProjectModel(): ?string { foreach ($this->getPageModelCandidates('Project') as $candidate) { if (class_exists($candidate)) { return $candidate; } } return null; } /** * Resolve the Domain model class, checking common namespaces. */ protected function resolvePageDomainModel(): ?string { foreach ($this->getPageModelCandidates('Domain') as $candidate) { if (class_exists($candidate)) { return $candidate; } } return null; } /** * Resolve the Pixel model class, checking common namespaces. */ protected function resolvePagePixelModel(): ?string { foreach ($this->getPageModelCandidates('Pixel') as $candidate) { if (class_exists($candidate)) { return $candidate; } } return null; } /** * Get candidate class names for a Page module model. * * @return list */ protected function getPageModelCandidates(string $model): array { return [ "Core\\Mod\\Social\\Models\\{$model}", "App\\Models\\{$model}", ]; } }