admin: Indexierungsstatus aus der Search Console, Kundenansicht vorbereitet und zu (Anhang 1: 1c.4, E8)
press-releases:check-index-status fragt täglich je Portal ein Kontingent veröffentlichter Meldungen über die URL Inspection API ab, nach Alter gestaffelt, nie geprüfte zuerst. Jede Antwort wird als Momentaufnahme in press_release_index_checks gespeichert; der reduzierte Befund (IndexState) trennt „noch nicht gecrawlt" von „gecrawlt, nicht indexiert". Client authentifiziert sich als Service-Konto über ein selbst signiertes JWT — kein Google-SDK. Admin ▸ Reports ▸ Indexierung zeigt die Verteilung je Portal getrennt nach Direkteinreichung und Syndikation (Status-Log source=api; Altbestand ohne Log zählt als direkt). Das Kundenpanel heißt jetzt „Auslieferung", zeigt Sitemap-Datum und die Abrufe durch Such- und KI-Dienste mit dem Satz „Ein Abruf ist kein Zitat"; der Google-Index-Block hängt an GSC_CUSTOMER_VISIBLE (false). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7088d092d8
commit
6c4618afed
17 changed files with 1324 additions and 30 deletions
62
app/Enums/IndexState.php
Normal file
62
app/Enums/IndexState.php
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
/**
|
||||
* Befund einer URL-Inspektion, auf das reduziert, was in jeder Anzeige
|
||||
* unterschieden werden muss (Anhang 17.09.2026, 1c.4): „noch nicht
|
||||
* gecrawlt" und „gecrawlt, aber nicht indexiert" sind verschiedene Befunde.
|
||||
*/
|
||||
enum IndexState: string
|
||||
{
|
||||
case Indexed = 'indexed';
|
||||
case CrawledNotIndexed = 'crawled_not_indexed';
|
||||
case NotCrawled = 'not_crawled';
|
||||
case Excluded = 'excluded';
|
||||
case Unknown = 'unknown';
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $indexStatus `inspectionResult.indexStatusResult` der URL Inspection API
|
||||
*/
|
||||
public static function fromIndexStatus(array $indexStatus): self
|
||||
{
|
||||
$verdict = strtoupper((string) ($indexStatus['verdict'] ?? ''));
|
||||
$coverage = mb_strtolower((string) ($indexStatus['coverageState'] ?? ''));
|
||||
$indexing = strtoupper((string) ($indexStatus['indexingState'] ?? ''));
|
||||
$robots = strtoupper((string) ($indexStatus['robotsTxtState'] ?? ''));
|
||||
$fetch = strtoupper((string) ($indexStatus['pageFetchState'] ?? ''));
|
||||
$crawled = ! empty($indexStatus['lastCrawlTime']);
|
||||
|
||||
if ($verdict === 'PASS' || (str_contains($coverage, 'indexed') && ! str_contains($coverage, 'not indexed'))) {
|
||||
return self::Indexed;
|
||||
}
|
||||
|
||||
if ($robots === 'DISALLOWED'
|
||||
|| in_array($indexing, ['BLOCKED_BY_META_TAG', 'BLOCKED_BY_HTTP_HEADER', 'BLOCKED_BY_ROBOTS_TXT'], true)
|
||||
|| ($fetch !== '' && $fetch !== 'SUCCESSFUL' && $crawled)
|
||||
|| str_contains($coverage, 'duplicate')
|
||||
|| str_contains($coverage, 'canonical')
|
||||
|| str_contains($coverage, 'redirect')
|
||||
|| str_contains($coverage, 'noindex')
|
||||
|| str_contains($coverage, 'blocked')) {
|
||||
return self::Excluded;
|
||||
}
|
||||
|
||||
if (! $crawled) {
|
||||
return $verdict === '' && $coverage === '' ? self::Unknown : self::NotCrawled;
|
||||
}
|
||||
|
||||
return self::CrawledNotIndexed;
|
||||
}
|
||||
|
||||
public function label(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::Indexed => 'indexiert',
|
||||
self::CrawledNotIndexed => 'gecrawlt, nicht indexiert',
|
||||
self::NotCrawled => 'noch nicht gecrawlt',
|
||||
self::Excluded => 'ausgeschlossen',
|
||||
self::Unknown => 'unbekannt',
|
||||
};
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue