Initial baseline: LaraBlog core with plugin commerce surface.

Captures the current working tree after theme slots, ArticleAccess, and the payment / paid-content plugins so subsequent work has a reviewable git history.
This commit is contained in:
ak
2026-08-12 01:15:38 +08:00
commit 263b98b218
337 changed files with 31393 additions and 0 deletions
+240
View File
@@ -0,0 +1,240 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use App\Domain\Blog\AccessDecision;
use App\Domain\Blog\ArticleAccess;
use App\Domain\Seo\SeoPresenter;
use App\Models\Article;
use App\Models\Category;
use App\Models\Comment;
use App\Models\Link;
use App\Models\Tag;
use App\Settings\BlogSettings;
use App\Settings\GeneralSettings;
use Illuminate\Http\Request;
use Illuminate\View\View;
class BlogController extends Controller
{
public function index(Request $request, GeneralSettings $settings, SeoPresenter $seo): View|\Illuminate\Http\RedirectResponse
{
// /index.php is often rewritten to / by PHP built-in server / nginx try_files.
$action = $request->query('action');
if ($action === 'tags' && $request->filled('item')) {
return redirect('/tag/'.rawurlencode((string) $request->query('item')), 301);
}
if (is_string($action) && $action !== '') {
return match ($action) {
'login' => app(AuthController::class)->showLogin($settings),
'reg' => app(AuthController::class)->showRegister($settings),
'profile' => app(AuthController::class)->showProfile($settings),
'links' => $this->links(),
'comments' => $this->comments(),
'tagslist' => $this->tagsList(),
'search' => $this->search($request),
'show' => $this->show($request, (int) $request->query('id'), $settings, $seo),
'tags' => $this->tagsList(),
default => $this->indexWithoutAction($request, $settings, $seo),
};
}
return $this->indexWithoutAction($request, $settings, $seo);
}
protected function indexWithoutAction(Request $request, GeneralSettings $settings, SeoPresenter $seo): View
{
$cid = $request->integer('cid') ?: null;
$setdate = $request->query('setdate');
$perPage = max(1, (int) app(BlogSettings::class)->posts_per_page);
$query = Article::query()
->with(['category', 'user', 'tags'])
->visible()
->published()
->orderByDesc('stick')
->orderByDesc('published_at');
if ($cid) {
$query->where('category_id', $cid);
}
if (is_string($setdate) && preg_match('/^\d{6}$/', $setdate)) {
$year = (int) substr($setdate, 0, 4);
$month = (int) substr($setdate, 4, 2);
$query->whereYear('published_at', $year)->whereMonth('published_at', $month);
}
$articles = $query->paginate($perPage)->withQueryString();
return view('theme::home', [
'articles' => $articles,
'categories' => Category::query()->orderBy('display_order')->get(),
'settings' => $settings,
'seo' => $seo->forHome(),
]);
}
public function bySlug(string $slug, GeneralSettings $settings, SeoPresenter $seo): View|\Illuminate\Http\RedirectResponse
{
$article = Article::query()
->visible()
->published()
->where('slug', $slug)
->firstOrFail();
return redirect('/show-'.$article->id.'.shtml', 301);
}
public function show(Request $request, int $id, GeneralSettings $settings, SeoPresenter $seo, ArticleAccess $access): View|\Illuminate\Http\RedirectResponse
{
$article = Article::query()
->with(['category', 'user', 'tags', 'comments' => fn ($q) => $q->visible()->orderBy('published_at')])
->visible()
->published()
->findOrFail($id);
$unlocked = (array) $request->session()->get('unlocked_articles', []);
$passwordError = null;
if (filled($article->read_password) && ! in_array($article->id, $unlocked, true)) {
if ($request->isMethod('post') && hash_equals((string) $article->read_password, (string) $request->input('password'))) {
$unlocked[] = $article->id;
$request->session()->put('unlocked_articles', $unlocked);
} elseif ($request->isMethod('post')) {
$passwordError = __('frontend.article.password_error');
}
}
$decision = $access->resolve($article, $request->user(), $unlocked);
if ($decision->status === AccessDecision::NEED_PASSWORD) {
return view('theme::password', [
'article' => $article,
'categories' => Category::query()->orderBy('display_order')->get(),
'settings' => $settings,
'seo' => $seo->forArticle($article),
'error' => $passwordError,
]);
}
if (! $decision->isAllow()) {
$article->increment('views');
return view('theme::paywall', [
'article' => $article,
'access' => $decision,
'bodyHtml' => $access->publicHtml($article, $decision),
'categories' => Category::query()->orderBy('display_order')->get(),
'settings' => $settings,
'seo' => $seo->forArticle($article),
]);
}
$article->increment('views');
return view('theme::article', [
'article' => $article,
'categories' => Category::query()->orderBy('display_order')->get(),
'settings' => $settings,
'seo' => $seo->forArticle($article),
]);
}
public function archives(Request $request, ?string $date = null): View
{
$request->merge(['setdate' => $date === 'all' ? null : $date]);
return $this->index($request, app(GeneralSettings::class), app(SeoPresenter::class));
}
public function category(Request $request, int $cid): View
{
$request->merge(['cid' => $cid]);
return $this->index($request, app(GeneralSettings::class), app(SeoPresenter::class));
}
public function tagsList(): View
{
$tags = Tag::query()->orderByDesc('use_count')->paginate(100);
return view('theme::tags', [
'tags' => $tags,
'categories' => Category::query()->orderBy('display_order')->get(),
'settings' => app(GeneralSettings::class),
]);
}
public function tag(Request $request, ?string $name = null): View
{
$name = $name ?: (string) $request->query('item', '');
$tag = Tag::query()->where('name', $name)->firstOrFail();
$articles = $tag->articles()
->visible()
->published()
->orderByDesc('published_at')
->paginate(10);
return view('theme::tag', [
'tag' => $tag,
'articles' => $articles,
'categories' => Category::query()->orderBy('display_order')->get(),
'settings' => app(GeneralSettings::class),
]);
}
public function comments(): View
{
$comments = Comment::query()
->with('article')
->visible()
->orderByDesc('published_at')
->paginate(20);
return view('theme::comments', [
'comments' => $comments,
'categories' => Category::query()->orderBy('display_order')->get(),
'settings' => app(GeneralSettings::class),
]);
}
public function search(Request $request): View
{
$q = trim((string) $request->query('keywords', $request->input('keywords', '')));
$articles = Article::query()
->visible()
->published()
->when($q !== '', function ($query) use ($q) {
$query->where(function ($inner) use ($q) {
$inner->where('title', 'like', "%{$q}%")
->orWhere('content', 'like', "%{$q}%")
->orWhere('description', 'like', "%{$q}%");
});
})
->orderByDesc('published_at')
->paginate(10)
->withQueryString();
return view('theme::search', [
'articles' => $articles,
'keywords' => $q,
'categories' => Category::query()->orderBy('display_order')->get(),
'settings' => app(GeneralSettings::class),
]);
}
public function links(): View
{
return view('theme::links', [
'links' => Link::query()->where('visible', true)->orderBy('display_order')->get(),
'categories' => Category::query()->orderBy('display_order')->get(),
'settings' => app(GeneralSettings::class),
]);
}
}