Files
sm-sitemap-plugin/console/import.go
Jakub Zych 84cb27a810 feat!: remove the PHP-parity sitemap, tiers, definitions screen and XSL
BREAKING CHANGE: /sitemap.xsl is gone (404), the definitions screen and
the Phase 17 settings tile are replaced by the settings singleton, and
priority, change frequency, priority tiers and the update manifest are
no longer written or read. The config keys golem15.sitemap.cache_seconds
and golem15.sitemap.priority_tiers are removed; site_url, lastmod_ignore
and rebuild_delay_seconds are new.

- sitemap:import and sitemap:import-winter write the additional addresses
  (and site_url, default_locale) instead of the definition
- the golem15_sitemap_definitions table stays in the database, unread
2026-11-04 11:12:51 +01:00

139 lines
5.2 KiB
Go

package console
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"strings"
"git.golem15.com/golem15/sm-sitemap-plugin/models"
"git.golem15.com/golem15/summercms/modules/backpack"
"git.golem15.com/golem15/summercms/modules/bonfire"
"gorm.io/gorm"
)
// Import is sitemap:import FILE [--append]: it reads a JSON array of URL
// entries ({"url": "/path", "lastmod": "2026-10-28"}; lastmod optional) into
// the settings' additional addresses, in one transaction. Without --append
// the imported addresses replace the additional addresses; with --append
// they are added after them, an address already listed is skipped. An
// entry whose url is not a path or an http(s) URL (models.AllowedAddress),
// or whose lastmod is not a date, stops the import before anything is
// written. The Phase 17 keys changefreq and priority are ignored, and
// --theme is accepted for the PHP command's sake. The files follow at the
// next rebuild (sitemap:rebuild).
func Import(app *backpack.App) bonfire.Command {
return bonfire.Command{
Name: "sitemap:import",
Description: "Import additional sitemap addresses from a JSON manifest file",
Long: `Reads a JSON array of URL entries ({"url": "/path", "lastmod":
"2026-10-28"}; lastmod is optional) and puts them into the sitemap's
additional addresses (Settings > Sitemaps > Additional addresses), in one
transaction. Every entry needs a url that is a path or an http(s) URL; one bad
entry stops the import before anything is written. The old changefreq and
priority keys are ignored: the sitemaps no longer write them.
By default the import replaces the additional addresses. --append adds the
addresses after them and skips those already listed. --theme is accepted for
the PHP command's sake and does nothing. The files change at the next rebuild
(sitemap:rebuild, a settings save or the nightly run). It ends with
"Imported N addresses into the sitemap's additional addresses."`,
Args: []bonfire.Arg{
{Name: "file", Description: "Path to the JSON manifest (relative to the working directory or absolute)", Required: true},
},
Flags: []bonfire.Flag{
{Name: "theme", Description: "Accepted for the PHP command; ignored"},
{Name: "append", Description: "Add the addresses instead of replacing the additional addresses", Bare: true},
},
Examples: []bonfire.Example{
{Command: "sitemap:import urls.json", Description: "Replace the additional addresses with the manifest's addresses"},
{Command: "sitemap:import urls.json --append", Description: "Add the addresses and keep the existing ones"},
},
Run: func(ctx context.Context, in bonfire.Input, out bonfire.Output) error {
file, _ := in.Argument("file")
db, done, err := lookupDB(ctx, app)
if err != nil {
return err
}
defer done()
return ImportFile(ctx, db, file, flagValue(in, "theme"), flagOn(in, "append"), out)
},
}
}
// ImportFile runs sitemap:import against db.
func ImportFile(ctx context.Context, db *gorm.DB, file, theme string, appendMode bool, out bonfire.Output) error {
raw, err := os.ReadFile(file)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("File not found: %s", file)
}
return fmt.Errorf("Cannot read %s: %w", file, err)
}
entries, labels, ok := models.LegacyEntries(raw)
if !ok {
return errors.New("Invalid JSON: expected an array of URL entries")
}
rows := make([]models.ExtraURL, 0, len(entries))
for i, entry := range entries {
var fields map[string]json.RawMessage
_ = json.Unmarshal(entry, &fields)
url, err := models.DecodeText(fields["url"])
if fields["url"] == nil || err != nil || strings.TrimSpace(models.Text(url)) == "" {
return fmt.Errorf("Entry %s is missing a 'url' field", labels[i])
}
u := strings.TrimSpace(models.Text(url))
if !models.AllowedAddress(u) {
return fmt.Errorf("Entry %s has a url that is not a path or an http(s) URL", labels[i])
}
row := models.ExtraURL{URL: u}
if raw, ok := fields["lastmod"]; ok {
mod, err := models.DecodeText(raw)
if err != nil || (strings.TrimSpace(models.Text(mod)) != "" && !models.ValidDate(models.Text(mod))) {
return fmt.Errorf("Entry %s has a lastmod that is not a date (YYYY-MM-DD)", labels[i])
}
row.LastMod = strings.TrimSpace(models.Text(mod))
}
rows = append(rows, row)
}
var kept, added int
err = db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
s, err := loadSettingsForUpdate(tx)
if err != nil {
return err
}
next := []models.ExtraURL{}
seen := map[string]bool{}
if appendMode {
for _, e := range s.Extra() {
next = append(next, e)
seen[e.URL] = true
}
kept = len(next)
}
for _, r := range rows {
if seen[r.URL] {
continue
}
seen[r.URL] = true
next = append(next, r)
added++
}
return saveExtra(tx, next)
})
if err != nil {
return err
}
out.Success(fmt.Sprintf("Imported %d addresses into the sitemap's additional addresses.", added))
if kept > 0 {
out.Info(fmt.Sprintf("Kept %d existing addresses.", kept))
}
if theme != "" {
out.Warning(fmt.Sprintf("Theme '%s' was given; the sitemap has one set of additional addresses, so the addresses were imported there.", theme))
}
out.Info("Run sitemap:rebuild (or save the sitemap settings) to update the files now.")
return nil
}