package crawl import ( "git.golem15.com/golem15/sm-seo-plugin/models" "git.golem15.com/golem15/summercms/modules/pact" ) // The crawl kinds of UI-SPEC C23. const ( KindPageStatus = "page_status" KindSitemapError = "sitemap_error" KindSitemapNoindex = "sitemap_noindex" KindSitemapBlocked = "sitemap_blocked" KindSitemapMissing = "sitemap_missing" KindBrokenLink = "broken_link" KindRedirectedLink = "redirected_link" KindCanonicalMissing = "canonical_missing" KindCanonicalError = "canonical_error" KindMissingTitle = "missing_title" KindDuplicateTitle = "duplicate_title" KindMissingDesc = "missing_description" KindDuplicateDesc = "duplicate_description" KindH1Missing = "h1_missing" KindH1Multiple = "h1_multiple" KindHeadingSkipped = "heading_skipped" KindHeadingEmpty = "heading_empty" KindH1TitleMismatch = "h1_title_mismatch" KindJSONLDInvalid = "jsonld_invalid" KindJSONLDMultiple = "jsonld_multiple" KindJSONLDRequired = "jsonld_required" KindJSONLDRecommended = "jsonld_recommended" KindHreflangRecip = "hreflang_reciprocal" KindHreflangError = "hreflang_error" KindGenericAnchor = "generic_anchor" KindImageAlt = "image_alt" KindImageSize = "image_size" KindFeaturedLazy = "featured_lazy" KindContentJS = "content_js" KindOrphanPage = "orphan_page" // KindH1InContent is the CMS-data check of a record whose body holds // an H1 although its page renders the title as the H1. KindH1InContent = "h1_in_content" ) // severities are the default severities of the kinds table (UI-SPEC C23), // the Phase 32 CMS kinds included. var severities = map[string]string{ KindPageStatus: models.SeverityError, KindSitemapError: models.SeverityError, KindSitemapNoindex: models.SeverityError, KindSitemapBlocked: models.SeverityError, KindSitemapMissing: models.SeverityNotice, KindBrokenLink: models.SeverityError, KindRedirectedLink: models.SeverityNotice, KindCanonicalMissing: models.SeverityWarning, KindCanonicalError: models.SeverityError, "canonical_noindex": models.SeverityError, "canonical_redirected": models.SeverityWarning, KindMissingTitle: models.SeverityWarning, KindDuplicateTitle: models.SeverityWarning, KindMissingDesc: models.SeverityNotice, KindDuplicateDesc: models.SeverityNotice, "rule_overrides_record": models.SeverityNotice, "redirect_chain": models.SeverityWarning, "noindex_blocked": models.SeverityError, KindH1Missing: models.SeverityError, KindH1Multiple: models.SeverityError, KindH1InContent: models.SeverityWarning, KindHeadingSkipped: models.SeverityWarning, KindHeadingEmpty: models.SeverityWarning, KindH1TitleMismatch: models.SeverityNotice, KindJSONLDInvalid: models.SeverityError, KindJSONLDMultiple: models.SeverityWarning, KindJSONLDRequired: models.SeverityError, KindJSONLDRecommended: models.SeverityNotice, KindHreflangRecip: models.SeverityWarning, KindHreflangError: models.SeverityError, KindGenericAnchor: models.SeverityWarning, KindImageAlt: models.SeverityWarning, KindImageSize: models.SeverityNotice, KindFeaturedLazy: models.SeverityNotice, KindContentJS: models.SeverityWarning, KindOrphanPage: models.SeverityWarning, } // Severity returns a kind's severity (warning for an unknown kind). func Severity(kind string) string { if s, ok := severities[kind]; ok { return s } return models.SeverityWarning } // Kinds lists every kind the audit stores, in the kinds table order. func Kinds() []string { return []string{ KindPageStatus, KindSitemapError, KindSitemapNoindex, KindSitemapBlocked, KindSitemapMissing, KindBrokenLink, KindRedirectedLink, KindCanonicalMissing, KindCanonicalError, "canonical_noindex", "canonical_redirected", KindMissingTitle, KindDuplicateTitle, KindMissingDesc, KindDuplicateDesc, "rule_overrides_record", "redirect_chain", "noindex_blocked", KindH1Missing, KindH1Multiple, KindH1InContent, KindHeadingSkipped, KindHeadingEmpty, KindH1TitleMismatch, KindJSONLDInvalid, KindJSONLDMultiple, KindJSONLDRequired, KindJSONLDRecommended, KindHreflangRecip, KindHreflangError, KindGenericAnchor, KindImageAlt, KindImageSize, KindFeaturedLazy, KindContentJS, KindOrphanPage, } } // Env is what the checks know besides the crawl: the record behind an // address. type Env struct { // Lookup returns the CMS record of an address, nil when none. Lookup func(key string) *Record } func (e *Env) record(key string) *Record { if e == nil || e.Lookup == nil { return nil } return e.Lookup(key) } // Check runs the per-page checks over a crawl's pages. func Check(res *Result, env *Env) []Finding { var out []Finding for _, key := range res.Order { p := res.Pages[key] if !p.OK() { continue } out = append(out, checkHeadings(p, env.record(key))...) } return out } // checkHeadings reports h1_missing and h1_multiple. An H1 inside the // record's own content is fixed through the record's headings tool. func checkHeadings(p *Page, rec *Record) []Finding { h1 := p.Facts.H1() switch { case len(h1) == 0: return []Finding{{Kind: KindH1Missing, URL: p.Key, Record: rec}} case len(h1) > 1: f := Finding{Kind: KindH1Multiple, URL: p.Key, Record: rec, Details: map[string]any{"count": len(h1), "h1": h1}} if rec.hasHeading(1, "") { f.Content = []string{pact.ContentToolHeadings, pact.ContentToolEdit} } return []Finding{f} } return nil }