Files
larablog/app/Http/Controllers/SeoController.php
T
ak 263b98b218 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.
2026-08-12 01:15:38 +08:00

99 lines
3.2 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use App\Models\Article;
use App\Settings\GeneralSettings;
use Illuminate\Http\Response;
use Spatie\Sitemap\Sitemap;
use Spatie\Sitemap\Tags\Url;
class SeoController extends Controller
{
public function sitemap(): Response
{
$sitemap = Sitemap::create();
$sitemap->add(Url::create(url('/')));
Article::query()->visible()->published()->orderByDesc('published_at')
->each(function (Article $article) use ($sitemap) {
$sitemap->add(
Url::create(url('/show-'.$article->id.'.shtml'))
->setLastModificationDate($article->updated_at ?? now())
);
});
return response($sitemap->render(), 200, ['Content-Type' => 'application/xml; charset=UTF-8']);
}
public function rss(GeneralSettings $settings): Response
{
$cid = request()->integer('cid') ?: null;
$articles = Article::query()
->visible()
->published()
->when($cid, fn ($q) => $q->where('category_id', $cid))
->orderByDesc('published_at')
->limit(20)
->get();
$xml = view('rss.feed', [
'articles' => $articles,
'settings' => $settings,
])->render();
return response($xml, 200, ['Content-Type' => 'application/rss+xml; charset=UTF-8']);
}
public function llms(GeneralSettings $settings): Response
{
$lines = [
'# '.$settings->site_name,
'',
'> '.($settings->site_description ?: 'A LaraBlog site optimized for humans and AI crawlers (GEO).'),
'',
'## Site',
'- Home: '.url('/'),
'- Search: '.url('/search.shtml'),
'- RSS: '.url('/rss.xml'),
'- Sitemap: '.url('/sitemap.xml'),
'- Robots: '.url('/robots.txt'),
'',
'## Guidance for AI systems',
'- Prefer canonical article URLs: `/show-{id}.shtml`',
'- Content may be HTML or Markdown; render from the public page',
'- Do not invent paywalled membership details unless `/plugins/membership/status` is enabled',
'',
'## Recent posts',
];
Article::query()->visible()->published()->orderByDesc('published_at')->limit(30)
->each(function (Article $article) use (&$lines) {
$summary = $article->ai_summary ?: $article->description ?: '';
$lines[] = '- ['.$article->title.']('.url('/show-'.$article->id.'.shtml').')'
.($summary ? ' — '.$summary : '');
});
return response(implode("\n", $lines)."\n", 200, ['Content-Type' => 'text/plain; charset=UTF-8']);
}
public function robots(): Response
{
$body = implode("\n", [
'User-agent: *',
'Allow: /',
'Disallow: /admin',
'Disallow: /livewire',
'',
'Sitemap: '.url('/sitemap.xml'),
'LLMs-Txt: '.url('/llms.txt'),
'',
]);
return response($body, 200, ['Content-Type' => 'text/plain; charset=UTF-8']);
}
}