- SEO › Auto-links (golem15.seo.autolinks, after Pages): list by phrase
with locale, origin and active filters, the disabled row state, the
form with its refusals, history through a field-less extension
- toolbar Add focus keyphrases (from_keyphrases) and
POST /autolinks/from-keyphrases
- GET/POST /autolinks, PUT /autolinks/{id}, DELETE /autolinks and the
tools seo_list_autolinks, seo_create_autolink, seo_update_autolink,
seo_delete_autolinks, seo_autolinks_from_keyphrases
- the settings tile description and the permission comment name
auto-links and the site audit
237 lines
7.8 KiB
Go
237 lines
7.8 KiB
Go
package controllers
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"git.golem15.com/golem15/sm-seo-plugin/classes"
|
|
"git.golem15.com/golem15/sm-seo-plugin/models"
|
|
"git.golem15.com/golem15/summercms/modules/backpack"
|
|
"git.golem15.com/golem15/summercms/modules/lagoon"
|
|
"git.golem15.com/golem15/summercms/modules/pact"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// AutolinksControllerID is the Auto-links screen (UI-SPEC C22).
|
|
const AutolinksControllerID = "golem15.seo.autolinks"
|
|
|
|
// ActionFromKeyphrases is the Auto-links toolbar action that adds the
|
|
// records' focus keyphrases (UI-SPEC C22).
|
|
const ActionFromKeyphrases = "from_keyphrases"
|
|
|
|
// autolinksAdminController lists and edits the auto-link phrases (D-82).
|
|
// History of a save comes from the plugin's own field-less form extension
|
|
// on this controller, which runs inside the save transaction once the row
|
|
// is written.
|
|
type autolinksAdminController struct {
|
|
app func() *backpack.App
|
|
}
|
|
|
|
func (autolinksAdminController) ID() string { return AutolinksControllerID }
|
|
func (autolinksAdminController) ModelName() string { return `Golem15\SEO\Models\Autolink` }
|
|
func (autolinksAdminController) ConfigDir() string { return "controllers/autolinks" }
|
|
func (autolinksAdminController) RequiredPermissions() []string {
|
|
return []string{PermissionManage}
|
|
}
|
|
|
|
// NewRecord is a phrase with the form's defaults: every locale, active,
|
|
// entered by hand.
|
|
func (autolinksAdminController) NewRecord() any {
|
|
a := models.NewAutolink()
|
|
return &a
|
|
}
|
|
|
|
// FormBeforeCreate refuses a short phrase, an invalid address and a
|
|
// phrase the locale already has (UI-SPEC C22); a create is manual.
|
|
func (c autolinksAdminController) FormBeforeCreate(ctx context.Context, model any) error {
|
|
if a, ok := model.(*models.Autolink); ok && a != nil {
|
|
a.Origin = models.AutolinkOriginManual
|
|
}
|
|
return c.check(ctx, model)
|
|
}
|
|
|
|
// FormBeforeUpdate checks an update like a create.
|
|
func (c autolinksAdminController) FormBeforeUpdate(ctx context.Context, model any) error {
|
|
return c.check(ctx, model)
|
|
}
|
|
|
|
func (c autolinksAdminController) check(ctx context.Context, model any) error {
|
|
a, ok := model.(*models.Autolink)
|
|
if !ok || a == nil {
|
|
return nil
|
|
}
|
|
svc, err := classes.NewService(appOf(c.app))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
db, err := database(ctx, c.app)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
problems, err := svc.AutolinkProblems(ctx, db, a)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return problemsError(ctx, translator(c.app), problems)
|
|
}
|
|
|
|
// FormAfterDelete records the deleted phrase in history inside the
|
|
// delete's transaction.
|
|
func (c autolinksAdminController) FormAfterDelete(ctx context.Context, model any) error {
|
|
a, ok := model.(*models.Autolink)
|
|
if !ok || a == nil {
|
|
return nil
|
|
}
|
|
svc, err := classes.NewService(appOf(c.app))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
db, err := database(ctx, c.app)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return svc.RecordAutolink(ctx, db, classes.AutolinkValues(*a), *a, true)
|
|
}
|
|
|
|
// ListRowStates marks an inactive phrase disabled (autolinks.state_off).
|
|
func (autolinksAdminController) ListRowStates(_ context.Context, _ *gorm.DB, records []any) ([][]pact.RowState, error) {
|
|
out := make([][]pact.RowState, len(records))
|
|
for i, record := range records {
|
|
if a, ok := record.(*models.Autolink); ok && a != nil && !a.Enabled {
|
|
out[i] = []pact.RowState{pact.RowStateDisabled}
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// autolinkOriginLabels are the phrase keys of the origins.
|
|
var autolinkOriginLabels = map[string]string{
|
|
models.AutolinkOriginManual: "golem15.seo::lang.autolinks.origin_manual",
|
|
models.AutolinkOriginKeyphrase: "golem15.seo::lang.autolinks.origin_keyphrase",
|
|
models.AutolinkOriginMCP: "golem15.seo::lang.autolinks.origin_mcp",
|
|
}
|
|
|
|
// locales returns the site's content locales, the default first.
|
|
func (c autolinksAdminController) locales(ctx context.Context) []string {
|
|
svc, err := classes.NewService(appOf(c.app))
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
db, err := database(ctx, c.app)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return svc.Locales(ctx, db)
|
|
}
|
|
|
|
// FilterOptions serves the choices of the locale and origin filters.
|
|
func (c autolinksAdminController) FilterOptions(scope string) []pact.Option {
|
|
switch scope {
|
|
case models.FilterAutolinkLocale:
|
|
out := []pact.Option{{Value: models.AutolinkLocaleAll, Label: "golem15.seo::lang.autolinks.locale_all"}}
|
|
for _, code := range c.locales(context.Background()) {
|
|
out = append(out, pact.Option{Value: code, Label: strings.ToUpper(code)})
|
|
}
|
|
return out
|
|
case models.FilterAutolinkOrigin:
|
|
out := make([]pact.Option, 0, len(models.AutolinkOrigins))
|
|
for _, o := range models.AutolinkOrigins {
|
|
out = append(out, pact.Option{Value: o, Label: autolinkOriginLabels[o]})
|
|
}
|
|
return out
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// DropdownOptions serves the locale dropdown: each content locale (the
|
|
// empty option is every locale).
|
|
func (c autolinksAdminController) DropdownOptions(field string) []pact.Option {
|
|
if field != "locale" {
|
|
return nil
|
|
}
|
|
codes := c.locales(context.Background())
|
|
out := make([]pact.Option, 0, len(codes))
|
|
for _, code := range codes {
|
|
out = append(out, pact.Option{Value: code, Label: strings.ToUpper(code)})
|
|
}
|
|
return out
|
|
}
|
|
|
|
// ListExtendRecords shows the locale (all languages when empty) and the
|
|
// origin of the list's rows as their labels in the request locale. The
|
|
// rows are list values only and never saved.
|
|
func (c autolinksAdminController) ListExtendRecords(ctx context.Context, _ *gorm.DB, records []any) error {
|
|
tr := translator(c.app)
|
|
for _, record := range records {
|
|
a, ok := record.(*models.Autolink)
|
|
if !ok || a == nil {
|
|
continue
|
|
}
|
|
if a.Locale == "" {
|
|
a.Locale = Phrase(ctx, tr, "golem15.seo::lang.autolinks.locale_all", nil)
|
|
} else {
|
|
a.Locale = strings.ToUpper(a.Locale)
|
|
}
|
|
if key, ok := autolinkOriginLabels[a.Origin]; ok {
|
|
a.Origin = Phrase(ctx, tr, key, nil)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// AdminActions is the toolbar's Add focus keyphrases.
|
|
func (c autolinksAdminController) AdminActions() []pact.AdminAction {
|
|
return []pact.AdminAction{{
|
|
Name: ActionFromKeyphrases,
|
|
Label: "golem15.seo::lang.autolinks.from_keyphrases",
|
|
Permissions: []string{PermissionManage},
|
|
Run: func(ctx context.Context, _ pact.AdminActionInput) (pact.AdminActionResult, error) {
|
|
svc, err := classes.NewService(appOf(c.app))
|
|
if err != nil {
|
|
return pact.AdminActionResult{}, err
|
|
}
|
|
db, err := database(ctx, c.app)
|
|
if err != nil {
|
|
return pact.AdminActionResult{}, err
|
|
}
|
|
added := 0
|
|
err = lagoon.Transaction(ctx, db, func(ctx context.Context, tx *gorm.DB) error {
|
|
var err error
|
|
added, err = svc.FromKeyphrases(classes.WithTx(ctx, tx), tx)
|
|
return err
|
|
})
|
|
if err != nil {
|
|
return pact.AdminActionResult{}, err
|
|
}
|
|
return pact.AdminActionResult{Message: KeyphrasesMessage(ctx, c.app, added)}, nil
|
|
},
|
|
}}
|
|
}
|
|
|
|
// KeyphrasesMessage is the Add focus keyphrases toast: the count added,
|
|
// or that there was nothing to add.
|
|
func KeyphrasesMessage(ctx context.Context, app func() *backpack.App, added int) string {
|
|
tr := translator(app)
|
|
if added == 0 {
|
|
return Phrase(ctx, tr, "golem15.seo::lang.autolinks.keyphrases_none", nil)
|
|
}
|
|
if tr == nil {
|
|
return "golem15.seo::lang.autolinks.keyphrases_added"
|
|
}
|
|
return tr.Choice(ctx, "golem15.seo::lang.autolinks.keyphrases_added", added, nil)
|
|
}
|
|
|
|
var (
|
|
_ pact.AdminController = autolinksAdminController{}
|
|
_ pact.AdminPermissioned = autolinksAdminController{}
|
|
_ pact.AdminRecordSource = autolinksAdminController{}
|
|
_ pact.FormBeforeCreate = autolinksAdminController{}
|
|
_ pact.FormBeforeUpdate = autolinksAdminController{}
|
|
_ pact.FormAfterDelete = autolinksAdminController{}
|
|
_ pact.ListRowStates = autolinksAdminController{}
|
|
_ pact.FilterOptions = autolinksAdminController{}
|
|
_ pact.ListExtendRecords = autolinksAdminController{}
|
|
_ pact.DropdownOptionsProvider = autolinksAdminController{}
|
|
_ pact.HasAdminActions = autolinksAdminController{}
|
|
)
|