blob: a9e1731006030c727bb165e411597df41c30d206 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package plugin
import (
"encoding/json"
"sync"
"github.com/segmentfault/pacman/cache"
"github.com/segmentfault/pacman/i18n"
"xorm.io/xorm"
"github.com/apache/answer/internal/base/handler"
"github.com/apache/answer/internal/base/translator"
"github.com/gin-gonic/gin"
)
// Data is defined here to avoid circular dependency with internal/base/data
type Data struct {
DB *xorm.Engine
Cache cache.Cache
}
// GinContext is a wrapper of gin.Context
// We export it to make it easy to use in plugins
type GinContext = gin.Context
// StatusManager is a manager that manages the status of plugins
// Init Plugins:
// json.Unmarshal([]byte(`{"plugin1": true, "plugin2": false}`), &plugin.StatusManager)
// Dump Status:
// json.Marshal(plugin.StatusManager)
var StatusManager = statusManager{
status: make(map[string]bool),
}
// Register registers a plugin
func Register(p Base) {
registerBase(p)
if _, ok := p.(Config); ok {
registerConfig(p.(Config))
}
if _, ok := p.(UserConfig); ok {
registerUserConfig(p.(UserConfig))
}
if _, ok := p.(Connector); ok {
registerConnector(p.(Connector))
}
if _, ok := p.(Parser); ok {
registerParser(p.(Parser))
}
if _, ok := p.(Filter); ok {
registerFilter(p.(Filter))
}
if _, ok := p.(Storage); ok {
registerStorage(p.(Storage))
}
if _, ok := p.(Cache); ok {
registerCache(p.(Cache))
}
if _, ok := p.(UserCenter); ok {
registerUserCenter(p.(UserCenter))
}
if _, ok := p.(Agent); ok {
registerAgent(p.(Agent))
}
if _, ok := p.(Search); ok {
registerSearch(p.(Search))
}
if _, ok := p.(Notification); ok {
registerNotification(p.(Notification))
}
if _, ok := p.(Reviewer); ok {
registerReviewer(p.(Reviewer))
}
if _, ok := p.(Captcha); ok {
registerCaptcha(p.(Captcha))
}
if _, ok := p.(Embed); ok {
registerEmbed(p.(Embed))
}
if _, ok := p.(Render); ok {
registerRender(p.(Render))
}
if _, ok := p.(CDN); ok {
registerCDN(p.(CDN))
}
if _, ok := p.(Importer); ok {
registerImporter(p.(Importer))
}
if _, ok := p.(KVStorage); ok {
registerKVStorage(p.(KVStorage))
}
}
type Stack[T Base] struct {
plugins []T
}
type RegisterFn[T Base] func(p T)
type Caller[T Base] func(p T) error
type CallFn[T Base] func(fn Caller[T]) error
// MakePlugin creates a plugin caller and register stack manager
// The parameter super presents if the plugin can be disabled.
// It returns a register function and a caller function
// The register function is used to register a plugin, it will be called in the plugin's init function
// The caller function is used to call all registered plugins
func MakePlugin[T Base](super bool) (CallFn[T], RegisterFn[T]) {
stack := Stack[T]{}
call := func(fn Caller[T]) error {
for _, p := range stack.plugins {
// If the plugin is disabled, skip it
if !super && !StatusManager.IsEnabled(p.Info().SlugName) {
continue
}
if err := fn(p); err != nil {
return err
}
}
return nil
}
register := func(p T) {
for _, plugin := range stack.plugins {
if plugin.Info().SlugName == p.Info().SlugName {
panic("plugin " + p.Info().SlugName + " is already registered")
}
}
stack.plugins = append(stack.plugins, p)
}
return call, register
}
type statusManager struct {
lock sync.Mutex
status map[string]bool
}
func (m *statusManager) Enable(name string, enabled bool) {
m.lock.Lock()
defer m.lock.Unlock()
if !enabled {
m.status[name] = enabled
return
}
m.status[name] = enabled
for _, slugName := range coordinatedCaptchaPlugins(name) {
m.status[slugName] = false
}
for _, slugName := range coordinatedCDNPlugins(name) {
m.status[slugName] = false
}
}
func (m *statusManager) IsEnabled(name string) bool {
if status, ok := m.status[name]; ok {
return status
}
return false
}
// MarshalJSON implements the json.Marshaler interface.
func (m *statusManager) MarshalJSON() ([]byte, error) {
return json.Marshal(m.status)
}
// UnmarshalJSON implements the json.Unmarshaler interface.
func (m *statusManager) UnmarshalJSON(data []byte) error {
return json.Unmarshal(data, &m.status)
}
// Translate translates the key to the current language of the context
func Translate(ctx *GinContext, key string) string {
return translator.Tr(handler.GetLang(ctx), key)
}
// TranslateWithData translates the key to the language with data
func TranslateWithData(lang i18n.Language, key string, data any) string {
return translator.TrWithData(lang, key, data)
}
// TranslateFn presents a generator of translated string.
// We use it to delegate the translation work outside the plugin.
type TranslateFn func(ctx *GinContext) string
// Translator contains a function that translates the key to the current language of the context
type Translator struct {
Fn TranslateFn
}
// MakeTranslator generates a translator from the key
func MakeTranslator(key string) Translator {
t := func(ctx *GinContext) string {
return Translate(ctx, key)
}
return Translator{Fn: t}
}
// Translate translates the key to the current language of the context
func (t Translator) Translate(ctx *GinContext) string {
if t.Fn == nil {
return ""
}
return t.Fn(ctx)
}