package crawl import ( "crypto/sha256" "encoding/hex" "strings" "git.golem15.com/golem15/sm-seo-plugin/classes" "git.golem15.com/golem15/sm-seo-plugin/models" "git.golem15.com/golem15/summercms/modules/pact" ) // The SEO tools an issue may name (pier prefixes them with seo_). const ( ToolSetURLSEO = "seo_set_url_seo" ToolUpdateRobots = "seo_update_robots" ToolCreateRedirect = "seo_create_redirect" ToolUpdatePage = "seo_update_page" ToolCreatePage = "seo_create_page" ToolUpdateSettings = "seo_update_settings" // ToolSitemapSettings is the sitemap plugin's settings tool, which // edits its Additional addresses. ToolSitemapSettings = "sitemap_update_settings" ) // Record is the CMS record behind a crawled page: its content type, id // and locale, the descriptor facts the checks compare the page with // (title, word count, headings, images, the highest heading level) and the // fixing tools its type names (pact.ContentDescriptor.Tools). type Record struct { Type string ID string Locale string Title string Words *int Headings []pact.ContentHeading Images []pact.ContentImage TopHeading int // Links are the record's CMS-known links: the address of its primary // parent, which the page's breadcrumb trail links to. Links []string Tools map[string]string // CustomJSONLD is true when the record's SEO tab stores custom JSON-LD. CustomJSONLD bool } // tool returns the record's tool for the first operation of ops it // names, "" when it names none. func (r *Record) tool(ops ...string) string { if r == nil { return "" } for _, op := range ops { if t := strings.TrimSpace(r.Tools[op]); t != "" { return t } } return "" } // hasHeading reports whether the record's own headings hold one of level // (any level when level is 0) whose text is text (any text when empty). func (r *Record) hasHeading(level int, text string) bool { if r == nil { return false } for _, h := range r.Headings { if level != 0 && h.Level != level { continue } if text == "" || normal(h.Text) == normal(text) { return true } } return false } // Finding is one problem a check found on a page, before it is stored: // the kind, the address and locale, the details of the kind, and how it // is fixed (P33-38). Tool is a fixed CMS tool (seo_*); Content lists the // record operations (pact.ContentTool*) that fix a finding inside the // record's own content, tried in order; What names the template element // when it is fixed in code. type Finding struct { Kind string URL string Locale string Details map[string]any Tool string Content []string Record *Record What string // Check tells the two content_js findings apart (cms, rendered). Check string } // Route applies P33-38: a finding inside the record's own content names // the record type's fitting tool (H1 findings prefer Tools[headings], // other content findings Tools[edit]); else a fixed CMS tool; else fix // code with the template element in details.what. Every finding gets one // of the two. func Route(f Finding) (fix, tool, what string) { if len(f.Content) > 0 { if t := f.Record.tool(f.Content...); t != "" { return models.FixTool, t, "" } } if f.Tool != "" { return models.FixTool, f.Tool, "" } what = f.What if what == "" { what = defaultWhat[f.Kind] } if what == "" { what = "the page template" } return models.FixCode, "", what } // defaultWhat names the template element of a kind fixed in code. var defaultWhat = map[string]string{ KindCanonicalMissing: ` in the page head`, KindH1Missing: "the

of the page template", KindH1Multiple: "the

headings of the page template", KindHeadingSkipped: "the heading levels of the page template", KindHeadingEmpty: "the empty heading of the page template", KindJSONLDInvalid: `the