Files
Jakub Zych 4a82240429 feat(17-03): full sitemap generation, settings, hooks and import commands
- generator in Definition::addItemToSet order: 50000 URL cap, lastmod from
  the record or the updates manifest (regular file, 5 MiB cap, fail safe),
  BeforeAddItem (skip) and AddItem (extra XML) festival events, tier or item
  priority, per-alternate expansion of list type children
- priority tiers from golem15.sitemap.priority_tiers, compiled at boot,
  defaults equal to the PHP getPriorityForUrl rules
- frontend_url rewrite of loc, xhtml:link href and the stylesheet href;
  GET /sitemap.xsl serves the PHP stylesheet byte for byte
- golem15_sitemap_settings singleton and Settings model
- sitemap:import (PHP ImportCommand) and sitemap:import-winter with the
  read-only Winter export SQL
- urlto and full goldens generated from PHP; TestSitemapImportSmoke
2026-10-12 17:07:39 +02:00

99 lines
3.3 KiB
Go

package classes
import (
"fmt"
"regexp"
"slices"
"strings"
"git.golem15.com/golem15/sm-sitemap-plugin/models"
"git.golem15.com/golem15/summercms/modules/compass"
)
// TiersConfigKey is the config key of the priority tier rules. The plugin's
// config/config.yaml ships the rules of the PHP getPriorityForUrl; a host
// replaces the whole list in its own config.
const TiersConfigKey = "golem15.sitemap.priority_tiers"
// TierConfig is one configured tier rule: Setting names the priority setting
// whose value applies (priority_homepage, priority_services, priority_blog,
// priority_categories or priority_legal), and exactly one of Exact (paths
// that match as they are) and Pattern (a regular expression, RE2 syntax)
// selects the URLs.
type TierConfig struct {
Setting string `koanf:"setting"`
Exact []string `koanf:"exact"`
Pattern string `koanf:"pattern"`
}
// TierRule is a compiled tier rule.
type TierRule struct {
Setting string
Exact []string
Pattern *regexp.Regexp
}
// Tiers is the ordered tier rule list; the first matching rule wins.
type Tiers []TierRule
// CompileTiers checks and compiles tier rules. Every error names the rule:
// an unknown setting, neither or both of exact and pattern, or a pattern
// that does not compile.
func CompileTiers(rules []TierConfig) (Tiers, error) {
out := make(Tiers, 0, len(rules))
for i, r := range rules {
if _, ok := models.PriorityDefaults[r.Setting]; !ok {
return nil, fmt.Errorf("%s[%d]: setting %q is not one of priority_homepage, priority_services, priority_blog, priority_categories, priority_legal", TiersConfigKey, i, r.Setting)
}
hasPattern := strings.TrimSpace(r.Pattern) != ""
if hasPattern == (len(r.Exact) > 0) {
return nil, fmt.Errorf("%s[%d]: set exactly one of exact and pattern", TiersConfigKey, i)
}
rule := TierRule{Setting: r.Setting, Exact: append([]string(nil), r.Exact...)}
if hasPattern {
re, err := regexp.Compile(r.Pattern)
if err != nil {
return nil, fmt.Errorf("%s[%d]: pattern: %w", TiersConfigKey, i, err)
}
rule.Pattern = re
}
out = append(out, rule)
}
return out, nil
}
// LoadTiers reads and compiles the rules at TiersConfigKey; an absent key
// is no rules.
func LoadTiers(cfg *compass.Config) (Tiers, error) {
if cfg == nil || !cfg.Has(TiersConfigKey) {
return Tiers{}, nil
}
var rules []TierConfig
if err := cfg.LoadSection(TiersConfigKey, &rules); err != nil {
return nil, fmt.Errorf("%s must be a list of {setting, exact} or {setting, pattern} rules: %w", TiersConfigKey, err)
}
return CompileTiers(rules)
}
// Priority is PHP getPriorityForUrl with configured rules: when the
// settings enable tiers, the URL's path (parse_url's, "/" when it has none)
// right-trimmed of slashes is matched against the rules in order and the
// first match's setting value is returned. ok is false when tiers are off or
// no rule matches, so the item's own priority applies.
func (t Tiers) Priority(url string, s models.Settings) (string, bool) {
if !s.EnablePriorityTiers {
return "", false
}
path := URLPath(url)
if path == "" {
path = "/"
}
path = strings.TrimRight(path, "/")
for _, r := range t {
if r.Pattern != nil && r.Pattern.MatchString(path) || r.Pattern == nil && slices.Contains(r.Exact, path) {
return s.Priority(r.Setting), true
}
}
return "", false
}