SEO additions index now
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\CmsPage;
|
||||
use App\Services\IndexNowService;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Throwable;
|
||||
|
||||
final class SubmitIndexNow extends Command
|
||||
{
|
||||
protected $signature = 'indexnow:submit {urls?* : Absolute URLs to submit} {--all : Submit all public indexable SonarDesk URLs}';
|
||||
protected $description = 'Submit public SonarDesk URLs to IndexNow';
|
||||
|
||||
public function handle(IndexNowService $indexNow): int
|
||||
{
|
||||
if (! config('indexnow.enabled')) {
|
||||
$this->warn('IndexNow is disabled. Set INDEXNOW_ENABLED=true to enable submissions.');
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$urls = $this->argument('urls');
|
||||
if ($this->option('all') || $urls === []) {
|
||||
$urls = $this->allPublicUrls();
|
||||
}
|
||||
|
||||
try {
|
||||
$response = $indexNow->submit($urls);
|
||||
} catch (Throwable $e) {
|
||||
$this->error($e->getMessage());
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
if ($response === null || ! $response->successful()) {
|
||||
$this->error('IndexNow rejected the submission'.($response ? ' (HTTP '.$response->status().').' : '.'));
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$this->info('Submitted '.count($urls).' URL(s) to IndexNow (HTTP '.$response->status().').');
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
/** @return array<int, string> */
|
||||
private function allPublicUrls(): array
|
||||
{
|
||||
$routes = ['home', 'features', 'buy', 'release', 'support', 'privacy', 'terms', 'refund', 'cookies'];
|
||||
$urls = collect($routes)->filter(fn (string $name) => Route::has($name))->map(fn (string $name) => route($name));
|
||||
|
||||
$pages = CmsPage::query()
|
||||
->where('is_published', true)
|
||||
->where('robots_index', true)
|
||||
->where(fn ($query) => $query->whereNull('published_at')->orWhere('published_at', '<=', now()))
|
||||
->pluck('slug')
|
||||
->map(fn (string $slug) => route('cms.show', ['slug' => $slug]));
|
||||
|
||||
return $urls->merge($pages)->unique()->values()->all();
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\CmsPage;
|
||||
use App\Services\IndexNowService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rule;
|
||||
@@ -12,11 +13,24 @@ final class CmsPageController extends Controller
|
||||
{
|
||||
public function index() { return view('admin.cms.index', ['pages' => CmsPage::latest()->paginate(25)]); }
|
||||
public function create() { return view('admin.cms.form', ['page' => new CmsPage()]); }
|
||||
public function store(Request $request) { $page = CmsPage::create($this->validated($request)); return redirect()->route('admin.cms.edit', $page)->with('status', 'Page created.'); }
|
||||
public function store(Request $request, IndexNowService $indexNow) { $page = CmsPage::create($this->validated($request)); $this->submitIfIndexable($page, $indexNow); return redirect()->route('admin.cms.edit', $page)->with('status', 'Page created.'); }
|
||||
public function edit(CmsPage $page) { return view('admin.cms.form', compact('page')); }
|
||||
public function update(Request $request, CmsPage $page) { $page->update($this->validated($request, $page)); return back()->with('status', 'Page updated.'); }
|
||||
public function update(Request $request, CmsPage $page, IndexNowService $indexNow) { $page->update($this->validated($request, $page)); $this->submitIfIndexable($page, $indexNow); return back()->with('status', 'Page updated.'); }
|
||||
public function destroy(CmsPage $page) { $page->delete(); return redirect()->route('admin.cms.index')->with('status', 'Page deleted.'); }
|
||||
|
||||
private function submitIfIndexable(CmsPage $page, IndexNowService $indexNow): void
|
||||
{
|
||||
if (! $page->is_published || ! $page->robots_index || ($page->published_at && $page->published_at->isFuture())) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$indexNow->submit([route('cms.show', ['slug' => $page->slug])]);
|
||||
} catch (\Throwable $e) {
|
||||
report($e);
|
||||
}
|
||||
}
|
||||
|
||||
private function validated(Request $request, ?CmsPage $page = null): array
|
||||
{
|
||||
$request->merge(['slug' => Str::slug($request->input('slug') ?: $request->input('title', ''))]);
|
||||
|
||||
@@ -37,6 +37,18 @@ final class SeoController extends Controller
|
||||
->header('Cache-Control', 'public, max-age=3600');
|
||||
}
|
||||
|
||||
public function indexNowKey(string $key): Response
|
||||
{
|
||||
$configured = trim((string) config('indexnow.key'));
|
||||
|
||||
abort_unless(config('indexnow.enabled') && $configured !== '' && hash_equals($configured, $key), 404);
|
||||
|
||||
return response($configured, 200, [
|
||||
'Content-Type' => 'text/plain; charset=UTF-8',
|
||||
'Cache-Control' => 'public, max-age=86400',
|
||||
]);
|
||||
}
|
||||
|
||||
public function robots(): Response
|
||||
{
|
||||
$indexable = app()->environment('production') && SiteSetting::booleanFor('seo_robots_enabled', true);
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Illuminate\Http\Client\Response;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use InvalidArgumentException;
|
||||
|
||||
final class IndexNowService
|
||||
{
|
||||
/** @param array<int, string> $urls */
|
||||
public function submit(array $urls): ?Response
|
||||
{
|
||||
if (! config('indexnow.enabled')) {
|
||||
Log::info('IndexNow submission skipped: disabled.');
|
||||
return null;
|
||||
}
|
||||
|
||||
$key = trim((string) config('indexnow.key'));
|
||||
if ($key === '') {
|
||||
throw new InvalidArgumentException('INDEXNOW_KEY is not configured.');
|
||||
}
|
||||
|
||||
$urls = collect($urls)
|
||||
->filter(fn ($url) => is_string($url) && filter_var($url, FILTER_VALIDATE_URL))
|
||||
->unique()
|
||||
->values()
|
||||
->all();
|
||||
|
||||
if ($urls === []) {
|
||||
throw new InvalidArgumentException('No valid URLs were supplied to IndexNow.');
|
||||
}
|
||||
|
||||
$host = parse_url($urls[0], PHP_URL_HOST);
|
||||
if (! is_string($host) || $host === '') {
|
||||
throw new InvalidArgumentException('Could not determine the IndexNow host.');
|
||||
}
|
||||
|
||||
foreach ($urls as $url) {
|
||||
if (parse_url($url, PHP_URL_HOST) !== $host) {
|
||||
throw new InvalidArgumentException('All IndexNow URLs must use the same host.');
|
||||
}
|
||||
}
|
||||
|
||||
$response = Http::asJson()
|
||||
->acceptJson()
|
||||
->timeout(10)
|
||||
->retry(2, 500)
|
||||
->post((string) config('indexnow.endpoint'), [
|
||||
'host' => $host,
|
||||
'key' => $key,
|
||||
'keyLocation' => 'https://'.$host.'/'.$key.'.txt',
|
||||
'urlList' => $urls,
|
||||
]);
|
||||
|
||||
if ($response->successful()) {
|
||||
Log::info('IndexNow URLs submitted.', ['host' => $host, 'count' => count($urls), 'status' => $response->status()]);
|
||||
} else {
|
||||
Log::warning('IndexNow submission failed.', ['host' => $host, 'count' => count($urls), 'status' => $response->status(), 'body' => $response->body()]);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'enabled' => (bool) env('INDEXNOW_ENABLED', false),
|
||||
'key' => env('INDEXNOW_KEY'),
|
||||
'endpoint' => env('INDEXNOW_ENDPOINT', 'https://api.indexnow.org/indexnow'),
|
||||
];
|
||||
@@ -18,6 +18,7 @@ use App\Http\Middleware\AdminOnly;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::get('/sitemap.xml', [SeoController::class, 'sitemap'])->name('sitemap');
|
||||
Route::get('/{key}.txt', [SeoController::class, 'indexNowKey'])->where('key', '[A-Fa-f0-9]{8,128}')->name('indexnow.key');
|
||||
Route::get('/robots.txt', [SeoController::class, 'robots'])->name('robots');
|
||||
|
||||
Route::get('/', fn () => view('portal.welcome', ['latestRelease' => \App\Models\SonarDeskRelease::published()->where('channel', 'stable')->latest('published_at')->first()]))->name('home');
|
||||
|
||||
Reference in New Issue
Block a user