|null $unlockedArticleIds session unlock list; null = read from session */ public function resolve(Article $article, ?User $user = null, ?array $unlockedArticleIds = null): AccessDecision { if ($user !== null && $this->isPrivileged($article, $user)) { return AccessDecision::allow(); } if (filled($article->read_password)) { $unlocked = $unlockedArticleIds ?? (array) Session::get('unlocked_articles', []); if (! in_array($article->id, $unlocked, true)) { return AccessDecision::needPassword(); } } $decision = AccessDecision::allow(); $context = [ 'article' => $article, 'user' => $user, ]; // Fold every listener result instead of letting the last one win, so a // plugin can only tighten access and a broken listener cannot re-open it. foreach (Hook::listeners('article.access') as $listener) { $result = $listener($decision, $context); if ($result instanceof AccessDecision) { $decision = $decision->tightenWith($result); } } return $decision; } /** * HTML safe to expose for the given decision (full body or teaser/description). */ public function publicHtml(Article $article, AccessDecision $decision): string { if ($decision->isAllow()) { return $article->renderedHtml(); } if (filled($decision->teaserHtml)) { return $decision->teaserHtml; } return e((string) ($article->description ?: '')); } protected function isPrivileged(Article $article, User $user): bool { if ((int) $article->user_id === (int) $user->id) { return true; } return $user->hasRole('admin'); } }