package cabana import ( "fmt" "strings" "git.golem15.com/golem15/summercms/modules/pact" "git.golem15.com/golem15/summercms/modules/party" ) // compileActionExtensions merges the record actions plugins add to admin // controllers through pact.HasAdminActionExtensions (D-14) into the compiled // controllers of reg, in plugin slice (activation) order. A contributed // action is appended to the target form's offered record actions and to its // RecordActions map, so the show response, the record action route and the // permission validation of compileContributions treat it like the owner's. // It must run after every controller is compiled and before // compileContributions, which validates the actions' permissions. Every // failure stops boot: an unknown controller ID, a target without a form // preview block (record actions are offered on the preview screen), a name // that is not an identifier, a reserved name, a name the target already // registers, an action without Run or without a label. func compileActionExtensions(reg *Registry, plugins []party.Plugin) error { if reg == nil { return fmt.Errorf("cabana: registry is nil") } for _, plugin := range plugins { if plugin == nil { continue } src, ok := plugin.(pact.HasAdminActionExtensions) if !ok || src == nil { continue } pluginID := plugin.ID() for _, ext := range src.AdminActionExtensions() { if len(ext.RecordActions) == 0 { continue } cc, ok := reg.byID[ext.Controller] if !ok || cc == nil { return fmt.Errorf("cabana: plugin %s adds record actions to unknown admin controller %q", pluginID, ext.Controller) } if cc.Form == nil || cc.Form.preview == nil { return fmt.Errorf("cabana: plugin %s adds record actions to admin controller %s, which has no preview block (record actions are offered on the preview screen)", pluginID, ext.Controller) } for _, action := range ext.RecordActions { if err := checkContributedAction(cc, pluginID, ext.Controller, action); err != nil { return err } // A new slice and map: the compiled backing array may be // shared with another schema value. names := make([]string, 0, len(cc.Form.recordActions)+1) names = append(names, cc.Form.recordActions...) cc.Form.recordActions = append(names, action.Name) actions := make(map[string]pact.AdminRecordAction, len(cc.RecordActions)+1) for name, registered := range cc.RecordActions { actions[name] = registered } actions[action.Name] = action cc.RecordActions = actions if cc.contributed == nil { cc.contributed = map[string]string{} } cc.contributed[action.Name] = pluginID } } } return nil } // checkContributedAction applies compileRecordActions' checks to one // contributed record action, plus a label and a name the target does not // register yet (the owner's or an earlier contribution). func checkContributedAction(cc *CompiledController, pluginID, controller string, action pact.AdminRecordAction) error { prefix := fmt.Sprintf("cabana: plugin %s adds record action %q to admin controller %s", pluginID, action.Name, controller) if !identifier(action.Name) { return fmt.Errorf("%s: the name is not an identifier", prefix) } if builtinToolbarActions[action.Name] { return fmt.Errorf("%s: the name is reserved (create, delete)", prefix) } if action.Run == nil { return fmt.Errorf("%s: it has no Run function", prefix) } if strings.TrimSpace(action.Label) == "" { return fmt.Errorf("%s: it needs a label", prefix) } if _, taken := cc.RecordActions[action.Name]; taken { owner := cc.PluginID if contributor, ok := cc.contributed[action.Name]; ok { owner = contributor } return fmt.Errorf("%s: plugin %s already registers a record action with that name", prefix, owner) } return nil }