github.com/kotovmak/go-admin@v1.1.1/plugins/plugins.go (about) 1 // Copyright 2019 GoAdmin Core Team. All rights reserved. 2 // Use of this source code is governed by a Apache-2.0 style 3 // license that can be found in the LICENSE file. 4 5 package plugins 6 7 import ( 8 "bytes" 9 "encoding/json" 10 "errors" 11 template2 "html/template" 12 "net/http" 13 "plugin" 14 "time" 15 16 "github.com/kotovmak/go-admin/template/icon" 17 "github.com/kotovmak/go-admin/template/types/action" 18 19 "github.com/kotovmak/go-admin/context" 20 "github.com/kotovmak/go-admin/modules/auth" 21 "github.com/kotovmak/go-admin/modules/config" 22 "github.com/kotovmak/go-admin/modules/db" 23 "github.com/kotovmak/go-admin/modules/language" 24 "github.com/kotovmak/go-admin/modules/logger" 25 "github.com/kotovmak/go-admin/modules/menu" 26 "github.com/kotovmak/go-admin/modules/remote_server" 27 "github.com/kotovmak/go-admin/modules/service" 28 "github.com/kotovmak/go-admin/modules/ui" 29 "github.com/kotovmak/go-admin/modules/utils" 30 "github.com/kotovmak/go-admin/plugins/admin/models" 31 "github.com/kotovmak/go-admin/plugins/admin/modules/table" 32 "github.com/kotovmak/go-admin/template" 33 "github.com/kotovmak/go-admin/template/types" 34 ) 35 36 // Plugin as one of the key components of goAdmin has three 37 // methods. GetRequest return all the path registered in the 38 // plugin. GetHandler according the url and method return the 39 // corresponding handler. InitPlugin init the plugin which do 40 // something like init the database and set the config and register 41 // the routes. The Plugin must implement the three methods. 42 type Plugin interface { 43 GetHandler() context.HandlerMap 44 InitPlugin(services service.List) 45 GetGenerators() table.GeneratorList 46 Name() string 47 Prefix() string 48 GetInfo() Info 49 GetIndexURL() string 50 GetSettingPage() table.Generator 51 IsInstalled() bool 52 Uninstall() error 53 Upgrade() error 54 } 55 56 type Info struct { 57 Title string `json:"title" yaml:"title" ini:"title"` 58 Description string `json:"description" yaml:"description" ini:"description"` 59 OldVersion string `json:"old_version" yaml:"old_version" ini:"old_version"` 60 Version string `json:"version" yaml:"version" ini:"version"` 61 Author string `json:"author" yaml:"author" ini:"author"` 62 Banners []string `json:"banners" yaml:"banners" ini:"banners"` 63 Url string `json:"url" yaml:"url" ini:"url"` 64 Cover string `json:"cover" yaml:"cover" ini:"cover"` 65 MiniCover string `json:"mini_cover" yaml:"mini_cover" ini:"mini_cover"` 66 Website string `json:"website" yaml:"website" ini:"website"` 67 Agreement string `json:"agreement" yaml:"agreement" ini:"agreement"` 68 CreateDate time.Time `json:"create_date" yaml:"create_date" ini:"create_date"` 69 UpdateDate time.Time `json:"update_date" yaml:"update_date" ini:"update_date"` 70 ModulePath string `json:"module_path" yaml:"module_path" ini:"module_path"` 71 Name string `json:"name" yaml:"name" ini:"name"` 72 Uuid string `json:"uuid" yaml:"uuid" ini:"uuid"` 73 Downloaded bool `json:"downloaded" yaml:"downloaded" ini:"downloaded"` 74 ExtraDownloadUrl string `json:"extra_download_url" yaml:"extra_download_url" ini:"extra_download_url"` 75 Price []string `json:"price" yaml:"price" ini:"price"` 76 GoodUUIDs []string `json:"good_uuids" yaml:"good_uuids" ini:"good_uuids"` 77 GoodNum int64 `json:"good_num" yaml:"good_num" ini:"good_num"` 78 CommentNum int64 `json:"comment_num" yaml:"comment_num" ini:"comment_num"` 79 Order int64 `json:"order" yaml:"order" ini:"order"` 80 Features string `json:"features" yaml:"features" ini:"features"` 81 Questions []string `json:"questions" yaml:"questions" ini:"questions"` 82 HasBought bool `json:"has_bought" yaml:"has_bought" ini:"has_bought"` 83 CanUpdate bool `json:"can_update" yaml:"can_update" ini:"can_update"` 84 Legal bool `json:"legal" yaml:"legal" ini:"legal"` 85 SkipInstallation bool `json:"skip_installation" yaml:"skip_installation" ini:"skip_installation"` 86 } 87 88 func (i Info) IsFree() bool { 89 return len(i.Price) == 0 90 } 91 92 type Base struct { 93 App *context.App 94 Services service.List 95 Conn db.Connection 96 UI *ui.Service 97 PlugName string 98 URLPrefix string 99 Info Info 100 } 101 102 func (b *Base) InitPlugin(services service.List) {} 103 func (b *Base) GetGenerators() table.GeneratorList { return make(table.GeneratorList) } 104 func (b *Base) GetHandler() context.HandlerMap { return b.App.Handlers } 105 func (b *Base) Name() string { return b.PlugName } 106 func (b *Base) GetInfo() Info { return b.Info } 107 func (b *Base) Prefix() string { return b.URLPrefix } 108 func (b *Base) IsInstalled() bool { return false } 109 func (b *Base) Uninstall() error { return nil } 110 func (b *Base) Upgrade() error { return nil } 111 func (b *Base) GetIndexURL() string { return "" } 112 func (b *Base) GetSettingPage() table.Generator { return nil } 113 114 func (b *Base) InitBase(srv service.List, prefix string) { 115 b.Services = srv 116 b.Conn = db.GetConnection(b.Services) 117 b.UI = ui.GetService(b.Services) 118 b.URLPrefix = prefix 119 } 120 121 func (b *Base) SetInfo(info Info) { 122 b.Info = info 123 } 124 125 func (b *Base) Title() string { 126 return language.GetWithScope(b.Info.Title, b.Name()) 127 } 128 129 func (b *Base) ExecuteTmpl(ctx *context.Context, panel types.Panel, options template.ExecuteOptions) *bytes.Buffer { 130 return Execute(ctx, b.Conn, *b.UI.NavButtons, auth.Auth(ctx), panel, options) 131 } 132 133 func (b *Base) ExecuteTmplWithNavButtons(ctx *context.Context, panel types.Panel, btns types.Buttons, 134 options template.ExecuteOptions) *bytes.Buffer { 135 return Execute(ctx, b.Conn, btns, auth.Auth(ctx), panel, options) 136 } 137 138 func (b *Base) ExecuteTmplWithMenu(ctx *context.Context, panel types.Panel, options template.ExecuteOptions) *bytes.Buffer { 139 return ExecuteWithMenu(ctx, b.Conn, *b.UI.NavButtons, auth.Auth(ctx), panel, b.Name(), b.Title(), options) 140 } 141 142 func (b *Base) ExecuteTmplWithCustomMenu(ctx *context.Context, panel types.Panel, menu *menu.Menu, options template.ExecuteOptions) *bytes.Buffer { 143 return ExecuteWithCustomMenu(ctx, *b.UI.NavButtons, auth.Auth(ctx), panel, menu, b.Title(), options) 144 } 145 146 func (b *Base) ExecuteTmplWithMenuAndNavButtons(ctx *context.Context, panel types.Panel, menu *menu.Menu, 147 btns types.Buttons, options template.ExecuteOptions) *bytes.Buffer { 148 return ExecuteWithMenu(ctx, b.Conn, btns, auth.Auth(ctx), panel, b.Name(), b.Title(), options) 149 } 150 151 func (b *Base) NewMenu(data menu.NewMenuData) (int64, error) { 152 return menu.NewMenu(b.Conn, data) 153 } 154 155 func (b *Base) HTML(ctx *context.Context, panel types.Panel, options ...template.ExecuteOptions) { 156 buf := b.ExecuteTmpl(ctx, panel, template.GetExecuteOptions(options)) 157 ctx.HTMLByte(http.StatusOK, buf.Bytes()) 158 } 159 160 func (b *Base) HTMLCustomMenu(ctx *context.Context, panel types.Panel, menu *menu.Menu, options ...template.ExecuteOptions) { 161 buf := b.ExecuteTmplWithCustomMenu(ctx, panel, menu, template.GetExecuteOptions(options)) 162 ctx.HTMLByte(http.StatusOK, buf.Bytes()) 163 } 164 165 func (b *Base) HTMLMenu(ctx *context.Context, panel types.Panel, options ...template.ExecuteOptions) { 166 buf := b.ExecuteTmplWithMenu(ctx, panel, template.GetExecuteOptions(options)) 167 ctx.HTMLByte(http.StatusOK, buf.Bytes()) 168 } 169 170 func (b *Base) HTMLBtns(ctx *context.Context, panel types.Panel, btns types.Buttons, options ...template.ExecuteOptions) { 171 buf := b.ExecuteTmplWithNavButtons(ctx, panel, btns, template.GetExecuteOptions(options)) 172 ctx.HTMLByte(http.StatusOK, buf.Bytes()) 173 } 174 175 func (b *Base) HTMLMenuWithBtns(ctx *context.Context, panel types.Panel, menu *menu.Menu, btns types.Buttons, options ...template.ExecuteOptions) { 176 buf := b.ExecuteTmplWithMenuAndNavButtons(ctx, panel, menu, btns, template.GetExecuteOptions(options)) 177 ctx.HTMLByte(http.StatusOK, buf.Bytes()) 178 } 179 180 func (b *Base) HTMLFile(ctx *context.Context, path string, data map[string]interface{}, options ...template.ExecuteOptions) { 181 182 buf := new(bytes.Buffer) 183 var panel types.Panel 184 185 t, err := template2.ParseFiles(path) 186 if err != nil { 187 panel = template.WarningPanel(err.Error()).GetContent(config.IsProductionEnvironment()) 188 } else { 189 if err := t.Execute(buf, data); err != nil { 190 panel = template.WarningPanel(err.Error()).GetContent(config.IsProductionEnvironment()) 191 } else { 192 panel = types.Panel{ 193 Content: template.HTML(buf.String()), 194 } 195 } 196 } 197 198 b.HTML(ctx, panel, options...) 199 } 200 201 func (b *Base) HTMLFiles(ctx *context.Context, data map[string]interface{}, files []string, options ...template.ExecuteOptions) { 202 buf := new(bytes.Buffer) 203 var panel types.Panel 204 205 t, err := template2.ParseFiles(files...) 206 if err != nil { 207 panel = template.WarningPanel(err.Error()).GetContent(config.IsProductionEnvironment()) 208 } else { 209 if err := t.Execute(buf, data); err != nil { 210 panel = template.WarningPanel(err.Error()).GetContent(config.IsProductionEnvironment()) 211 } else { 212 panel = types.Panel{ 213 Content: template.HTML(buf.String()), 214 } 215 } 216 } 217 218 b.HTML(ctx, panel, options...) 219 } 220 221 type BasePlugin struct { 222 Base 223 Info Info 224 IndexURL string 225 Installed bool 226 } 227 228 func (b *BasePlugin) GetInfo() Info { return b.Info } 229 func (b *BasePlugin) Name() string { return b.Info.Name } 230 func (b *BasePlugin) GetIndexURL() string { return b.IndexURL } 231 func (b *BasePlugin) IsInstalled() bool { return b.Installed } 232 233 func NewBasePluginWithInfo(info Info) Plugin { 234 return &BasePlugin{Info: info} 235 } 236 237 func NewBasePluginWithInfoAndIndexURL(info Info, u string, installed bool) Plugin { 238 return &BasePlugin{Info: info, IndexURL: u, Installed: installed} 239 } 240 241 func GetPluginsWithInfos(info []Info) Plugins { 242 p := make(Plugins, len(info)) 243 for k, i := range info { 244 p[k] = NewBasePluginWithInfo(i) 245 } 246 return p 247 } 248 249 func LoadFromPlugin(mod string) Plugin { 250 251 plug, err := plugin.Open(mod) 252 if err != nil { 253 logger.Error("LoadFromPlugin err", err) 254 panic(err) 255 } 256 257 symPlugin, err := plug.Lookup("Plugin") 258 if err != nil { 259 logger.Error("LoadFromPlugin err", err) 260 panic(err) 261 } 262 263 var p Plugin 264 p, ok := symPlugin.(Plugin) 265 if !ok { 266 logger.Error("LoadFromPlugin err: unexpected type from module symbol") 267 panic(errors.New("LoadFromPlugin err: unexpected type from module symbol")) 268 } 269 270 return p 271 } 272 273 // GetHandler is a help method for Plugin GetHandler. 274 func GetHandler(app *context.App) context.HandlerMap { return app.Handlers } 275 276 func Execute(ctx *context.Context, conn db.Connection, navButtons types.Buttons, user models.UserModel, 277 panel types.Panel, options template.ExecuteOptions) *bytes.Buffer { 278 tmpl, tmplName := template.Get(config.GetTheme()).GetTemplate(ctx.IsPjax()) 279 280 return template.Execute(&template.ExecuteParam{ 281 User: user, 282 TmplName: tmplName, 283 Tmpl: tmpl, 284 Panel: panel, 285 Config: config.Get(), 286 Menu: menu.GetGlobalMenu(user, conn, ctx.Lang()).SetActiveClass(config.URLRemovePrefix(ctx.Path())), 287 Animation: options.Animation, 288 Buttons: navButtons.CheckPermission(user), 289 NoCompress: options.NoCompress, 290 IsPjax: ctx.IsPjax(), 291 Iframe: ctx.IsIframe(), 292 }) 293 } 294 295 func ExecuteWithCustomMenu(ctx *context.Context, 296 navButtons types.Buttons, 297 user models.UserModel, 298 panel types.Panel, 299 menu *menu.Menu, logo string, options template.ExecuteOptions) *bytes.Buffer { 300 301 tmpl, tmplName := template.Get(config.GetTheme()).GetTemplate(ctx.IsPjax()) 302 303 return template.Execute(&template.ExecuteParam{ 304 User: user, 305 TmplName: tmplName, 306 Tmpl: tmpl, 307 Panel: panel, 308 Config: config.Get(), 309 Menu: menu, 310 Animation: options.Animation, 311 Buttons: navButtons.CheckPermission(user), 312 NoCompress: options.NoCompress, 313 Logo: template2.HTML(logo), 314 IsPjax: ctx.IsPjax(), 315 Iframe: ctx.IsIframe(), 316 }) 317 } 318 319 func ExecuteWithMenu(ctx *context.Context, 320 conn db.Connection, 321 navButtons types.Buttons, 322 user models.UserModel, 323 panel types.Panel, 324 name, logo string, options template.ExecuteOptions) *bytes.Buffer { 325 326 tmpl, tmplName := template.Get(config.GetTheme()).GetTemplate(ctx.IsPjax()) 327 328 btns := options.NavDropDownButton 329 if btns == nil { 330 btns = []*types.NavDropDownItemButton{ 331 types.GetDropDownItemButton(language.GetFromHtml("plugin setting"), 332 action.Jump(config.Url("/info/plugin_"+name+"/edit"))), 333 types.GetDropDownItemButton(language.GetFromHtml("menus manage"), 334 action.Jump(config.Url("/menu?__plugin_name="+name))), 335 } 336 } else { 337 btns = append(btns, []*types.NavDropDownItemButton{ 338 types.GetDropDownItemButton(language.GetFromHtml("plugin setting"), 339 action.Jump(config.Url("/info/plugin_"+name+"/edit"))), 340 types.GetDropDownItemButton(language.GetFromHtml("menus manage"), 341 action.Jump(config.Url("/menu?__plugin_name="+name))), 342 }...) 343 } 344 345 return template.Execute(&template.ExecuteParam{ 346 User: user, 347 TmplName: tmplName, 348 Tmpl: tmpl, 349 Panel: panel, 350 Config: config.Get(), 351 Menu: menu.GetGlobalMenu(user, conn, ctx.Lang(), name).SetActiveClass(config.URLRemovePrefix(ctx.Path())), 352 Animation: options.Animation, 353 Buttons: navButtons.Copy(). 354 RemoveInfoNavButton(). 355 RemoveSiteNavButton(). 356 RemoveToolNavButton(). 357 Add(types.GetDropDownButton("", icon.Gear, btns)).CheckPermission(user), 358 NoCompress: options.NoCompress, 359 Logo: template2.HTML(logo), 360 IsPjax: ctx.IsPjax(), 361 Iframe: ctx.IsIframe(), 362 }) 363 } 364 365 type Plugins []Plugin 366 367 func (pp Plugins) Add(p Plugin) Plugins { 368 if !pp.Exist(p) { 369 pp = append(pp, p) 370 } 371 return pp 372 } 373 374 func (pp Plugins) Exist(p Plugin) bool { 375 for _, v := range pp { 376 if v.Name() == p.Name() { 377 return true 378 } 379 } 380 return false 381 } 382 383 func FindByName(name string) (Plugin, bool) { 384 for _, v := range pluginList { 385 if v.Name() == name { 386 return v, true 387 } 388 } 389 return nil, false 390 } 391 392 func FindByNameAll(name string) (Plugin, bool) { 393 for _, v := range allPluginList { 394 if v.Name() == name { 395 return v, true 396 } 397 } 398 return nil, false 399 } 400 401 var ( 402 pluginList = make(Plugins, 0) 403 allPluginList = make(Plugins, 0) 404 ) 405 406 func Exist(p Plugin) bool { 407 return pluginList.Exist(p) 408 } 409 410 func Add(p Plugin) { 411 // TODO: 验证插件合法性 412 pluginList = pluginList.Add(p) 413 } 414 415 func GetAll(req remote_server.GetOnlineReq, token string) (Plugins, Page) { 416 417 plugs := make(Plugins, 0) 418 page := Page{} 419 420 res, err := remote_server.GetOnline(req, token) 421 422 if err != nil { 423 return plugs, page 424 } 425 426 var data GetOnlineRes 427 err = json.Unmarshal(res, &data) 428 if err != nil { 429 return plugs, page 430 } 431 432 if data.Code != 0 { 433 return plugs, page 434 } 435 436 plugs = GetPluginsWithInfos(data.Data.List) 437 page = data.Data.Page 438 439 for index, p := range plugs { 440 for key, value := range pluginList { 441 if value.Name() == p.Name() { 442 info := pluginList[key].GetInfo() 443 info.CanUpdate = utils.CompareVersion(info.Version, plugs[index].GetInfo().Version) 444 info.OldVersion = info.Version 445 info.Downloaded = true 446 info.Description = language.GetWithScope(info.Description, info.Name) 447 info.Title = language.GetWithScope(info.Title, info.Name) 448 info.Version = plugs[index].GetInfo().Version 449 plugs[index] = NewBasePluginWithInfoAndIndexURL(info, value.GetIndexURL(), value.IsInstalled()) 450 break 451 } 452 } 453 } 454 455 for _, p := range plugs { 456 exist := false 457 for _, pp := range allPluginList { 458 if pp.Name() == p.Name() { 459 exist = true 460 break 461 } 462 } 463 if !exist { 464 allPluginList = append(allPluginList, p) 465 } 466 } 467 468 return plugs, page 469 } 470 471 func Get() Plugins { 472 var plugs = make(Plugins, len(pluginList)) 473 copy(plugs, pluginList) 474 return plugs 475 } 476 477 type GetOnlineRes struct { 478 Code int `json:"code"` 479 Msg string `json:"msg"` 480 Data GetOnlineResData `json:"data"` 481 } 482 483 type GetOnlineResData struct { 484 List []Info `json:"list"` 485 Count int `json:"count"` 486 HasMore bool `json:"has_more"` 487 Page Page `json:"page"` 488 } 489 490 type Page struct { 491 CSS string `json:"css"` 492 HTML string `json:"html"` 493 JS string `json:"js"` 494 }