- docs/backend: admin controllers, forms, lists and filters, relation manager, users and permissions, settings, partials and widgets, admin SPA - docs/services: storage, outbound HTTP, realtime, Web Push, search, parity testing and the Frontend and AJAX (not provided) page - Examples for cabana (with testdata/docs YAML), fetchguard, lighthouse and its centrifugo driver, flare, beachcomber and typesense, tide; lighthouse and beachcomber TestDocs* regions run on their Postgres harnesses - concept map rows link their guide pages and the not-provided rows the Frontend and AJAX page; index lists Backend, Database and Services - TestDocsRequiredPages asserts the D-08 section order
3.8 KiB
title, description, section, order
| title | description | section | order |
|---|---|---|---|
| Settings | Declare singleton settings pages with pact.SettingsItem, backed by a model, a fields.yaml form and validation rules, and read them from plugin code. | backend | 60 |
Settings
A WinterCMS settings model extends SettingModel, stores its values in system_settings and registers its page with registerSettings. In SummerCMS a settings page is a singleton row of the plugin's own table, edited through a form described in fields.yaml, and declared with pact.HasSettings.
Declaring a settings page
pact.HasSettings returns pact.SettingsItem entries. Each names the page's code, label, category and icon for the settings index, the permissions it needs, the form file inside the plugin's embedded tree, and a factory for its model:
// Settings replaces registerSettings().
func (p *BlogPlugin) Settings() []pact.SettingsItem {
return []pact.SettingsItem{{
Code: "blog",
Label: "acme.blog::lang.settings.label",
Description: "acme.blog::lang.settings.description",
Category: "acme.blog::lang.plugin.name",
Icon: "icon-pencil",
Model: "BlogSettings",
Permissions: []string{"acme.blog.access_settings"},
Form: "models/settings/fields.yaml",
NewModel: func() any { return &BlogSettings{} },
}}
}
The model is a GORM struct with a Fillable list and a Rules method; cabana refuses at start-up a settings model without either:
// BlogSettings is the singleton row behind the plugin's settings page.
type BlogSettings struct {
ID uint `gorm:"column:id;primaryKey"`
PostsPerPage int `gorm:"column:posts_per_page"`
CommentsEnabled bool `gorm:"column:comments_enabled"`
}
// Rules validates a settings save, as a WinterCMS settings model's $rules.
func (BlogSettings) Rules() map[string]string {
return map[string]string{"posts_per_page": "required|integer|between:1,100"}
}
The form uses the same field types and options as a controller form; see Forms:
fields:
posts_per_page:
label: acme.blog::lang.settings.posts_per_page
type: number
span: left
default: 10
comments_enabled:
label: acme.blog::lang.settings.comments_enabled
type: switch
span: right
Create the table in a migration like any other; see Migrations.
How values are stored
The settings row is the one with ID 1. Before it exists, the page shows the fields' default values and the API reports that the row does not exist yet; the first save creates it. A save writes only fillable fields, validates them with the model's rules and the form's required flags, and runs in a transaction, as a controller form save does.
The admin API serves the page at <prefix>/api/v1/settings/{code} (values) and .../settings/{code}/schema (the form), and GET .../settings lists the pages the administrator may open.
Reading settings in plugin code
Settings are an ordinary table, so plugin code reads them with GORM:
svc.SetGate(beachcomber.GateFunc(func(ctx context.Context, db *gorm.DB) bool {
var enabled bool
err := db.WithContext(ctx).Raw(`SELECT search_enabled FROM acme_blog_settings WHERE id = 1`).Scan(&enabled).Error
return err == nil && enabled
}))
That example reads a flag from a settings row inside a search gate, treating a failed read as off. A setting that changes rarely and that operators, not administrators, own belongs in configuration instead; see Configuration.