cc1d0f089a
The client templates load earlier now for a smoother user experience. Added a setting for setting a Google Site Verification meta tag without editing header.html Added support for favicons. /static/favicon.ico will be mapped to favicon.ico, if it exists. The parent forum is now visible on the topics list for Nox. Language headers which contain the wildcard character are no longer considered unknowns. Meta descriptions and open graph descriptions are no longer emitted for logged in users. Slimmed down topics_topic slightly for Nox. Pre-parsed widgets are now minified. Stale WebSockets connections should be cleaned up far quicker now. Template generation is now logged separately. Commented out some obsolete template logic. Marked a few template generator fields as unexported. Fixed the styling for the ban page in the profile for Nox. Fixed the styling for colline for Cosora and Tempra Simple. Fixed the sidebar overflowing outside of the box on Nox. Fixed the meta description text overflowing the box in the Setting Manager on Nox. Fixed excessive padding in the Page Manager. Fixed a few missing border on the profiles for Tempra Simple. Fixed the sidebar appearing in places it shouldn't on Tempra Simple. Fixed the status code emitted by NotFoundJS Fixed a bug where Gosora kept falling back to interpreted templates. Fixed a bug where WebSockets connections weren't getting closed properly if the user cache overflowed. Fixed a bug where WebSocket connections weren't getting initialised for guests. Fixed a bug where template overrides weren't always getting applied. Fixed a bug where root template overrides weren't always getting applied. Added the google_site_verify setting. Added the google_site_verify phrase. You will need to run the patcher or updater for this commit.
299 lines
7.9 KiB
Go
299 lines
7.9 KiB
Go
package common
|
|
|
|
import (
|
|
"database/sql"
|
|
"encoding/json"
|
|
"errors"
|
|
"html/template"
|
|
"io"
|
|
"io/ioutil"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
|
|
"github.com/Azareal/Gosora/query_gen"
|
|
)
|
|
|
|
// TODO: Something more thread-safe
|
|
type ThemeList map[string]*Theme
|
|
|
|
var Themes ThemeList = make(map[string]*Theme) // ? Refactor this into a store?
|
|
var DefaultThemeBox atomic.Value
|
|
var ChangeDefaultThemeMutex sync.Mutex
|
|
|
|
// TODO: Fallback to a random theme if this doesn't exist, so admins can remove themes they don't use
|
|
// TODO: Use this when the default theme doesn't exist
|
|
var fallbackTheme = "cosora"
|
|
var overridenTemplates = make(map[string]bool) // ? What is this used for?
|
|
|
|
type ThemeStmts struct {
|
|
getAll *sql.Stmt
|
|
isDefault *sql.Stmt
|
|
update *sql.Stmt
|
|
add *sql.Stmt
|
|
}
|
|
|
|
var themeStmts ThemeStmts
|
|
|
|
func init() {
|
|
DbInits.Add(func(acc *qgen.Accumulator) error {
|
|
themeStmts = ThemeStmts{
|
|
getAll: acc.Select("themes").Columns("uname, default").Prepare(),
|
|
isDefault: acc.Select("themes").Columns("default").Where("uname = ?").Prepare(),
|
|
update: acc.Update("themes").Set("default = ?").Where("uname = ?").Prepare(),
|
|
add: acc.Insert("themes").Columns("uname, default").Fields("?,?").Prepare(),
|
|
}
|
|
return acc.FirstError()
|
|
})
|
|
}
|
|
|
|
func NewThemeList() (themes ThemeList, err error) {
|
|
themes = make(map[string]*Theme)
|
|
|
|
themeFiles, err := ioutil.ReadDir("./themes")
|
|
if err != nil {
|
|
return themes, err
|
|
}
|
|
if len(themeFiles) == 0 {
|
|
return themes, errors.New("You don't have any themes")
|
|
}
|
|
|
|
var lastTheme, defaultTheme string
|
|
for _, themeFile := range themeFiles {
|
|
if !themeFile.IsDir() {
|
|
continue
|
|
}
|
|
|
|
themeName := themeFile.Name()
|
|
log.Printf("Adding theme '%s'", themeName)
|
|
themePath := "./themes/" + themeName
|
|
themeFile, err := ioutil.ReadFile(themePath + "/theme.json")
|
|
if err != nil {
|
|
return themes, err
|
|
}
|
|
|
|
var theme = &Theme{Name: ""}
|
|
err = json.Unmarshal(themeFile, theme)
|
|
if err != nil {
|
|
return themes, err
|
|
}
|
|
|
|
if theme.Name == "" {
|
|
return themes, errors.New("Theme " + themePath + " doesn't have a name set in theme.json")
|
|
}
|
|
if theme.Name == fallbackTheme {
|
|
defaultTheme = fallbackTheme
|
|
}
|
|
lastTheme = theme.Name
|
|
|
|
// TODO: Implement the static file part of this and fsnotify
|
|
if theme.Path != "" {
|
|
log.Print("Resolving redirect to " + theme.Path)
|
|
themeFile, err := ioutil.ReadFile(theme.Path + "/theme.json")
|
|
if err != nil {
|
|
return themes, err
|
|
}
|
|
theme = &Theme{Name: "", Path: theme.Path}
|
|
err = json.Unmarshal(themeFile, theme)
|
|
if err != nil {
|
|
return themes, err
|
|
}
|
|
} else {
|
|
theme.Path = themePath
|
|
}
|
|
|
|
theme.Active = false // Set this to false, just in case someone explicitly overrode this value in the JSON file
|
|
|
|
// TODO: Let the theme specify where it's resources are via the JSON file?
|
|
// TODO: Let the theme inherit CSS from another theme?
|
|
// ? - This might not be too helpful, as it only searches for /public/ and not if /public/ is empty. Still, it might help some people with a slightly less cryptic error
|
|
log.Print(theme.Path + "/public/")
|
|
_, err = os.Stat(theme.Path + "/public/")
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return themes, errors.New("We couldn't find this theme's resources. E.g. the /public/ folder.")
|
|
} else {
|
|
log.Print("We weren't able to access this theme's resources due to a permissions issue or some other problem")
|
|
return themes, err
|
|
}
|
|
}
|
|
|
|
if theme.FullImage != "" {
|
|
DebugLog("Adding theme image")
|
|
err = StaticFiles.Add(theme.Path+"/"+theme.FullImage, themePath)
|
|
if err != nil {
|
|
return themes, err
|
|
}
|
|
}
|
|
|
|
theme.TemplatesMap = make(map[string]string)
|
|
theme.TmplPtr = make(map[string]interface{})
|
|
if theme.Templates != nil {
|
|
for _, themeTmpl := range theme.Templates {
|
|
theme.TemplatesMap[themeTmpl.Name] = themeTmpl.Source
|
|
theme.TmplPtr[themeTmpl.Name] = TmplPtrMap["o_"+themeTmpl.Source]
|
|
}
|
|
}
|
|
|
|
theme.IntTmplHandle = DefaultTemplates
|
|
overrides, err := ioutil.ReadDir(theme.Path + "/overrides/")
|
|
if err != nil && !os.IsNotExist(err) {
|
|
return themes, err
|
|
}
|
|
if len(overrides) > 0 {
|
|
var overCount = 0
|
|
theme.OverridenMap = make(map[string]bool)
|
|
for _, override := range overrides {
|
|
if override.IsDir() {
|
|
continue
|
|
}
|
|
var ext = filepath.Ext(themePath + "/overrides/" + override.Name())
|
|
log.Print("attempting to add " + themePath + "/overrides/" + override.Name())
|
|
if ext != ".html" {
|
|
log.Print("not a html file")
|
|
continue
|
|
}
|
|
overCount++
|
|
nosuf := strings.TrimSuffix(override.Name(), ext)
|
|
theme.OverridenTemplates = append(theme.OverridenTemplates, nosuf)
|
|
theme.OverridenMap[nosuf] = true
|
|
//theme.TmplPtr[nosuf] = TmplPtrMap["o_"+nosuf]
|
|
log.Print("succeeded")
|
|
}
|
|
|
|
localTmpls := template.New("")
|
|
err = loadTemplates(localTmpls, theme.Name)
|
|
if err != nil {
|
|
return themes, err
|
|
}
|
|
theme.IntTmplHandle = localTmpls
|
|
log.Printf("theme.OverridenTemplates: %+v\n", theme.OverridenTemplates)
|
|
log.Printf("theme.IntTmplHandle: %+v\n", theme.IntTmplHandle)
|
|
} else {
|
|
log.Print("no overrides for " + theme.Name)
|
|
}
|
|
|
|
// TODO: Bind the built template, or an interpreted one for any dock overrides this theme has
|
|
|
|
themes[theme.Name] = theme
|
|
}
|
|
|
|
if defaultTheme == "" {
|
|
defaultTheme = lastTheme
|
|
}
|
|
DefaultThemeBox.Store(defaultTheme)
|
|
|
|
return themes, nil
|
|
}
|
|
|
|
// TODO: Make the initThemes and LoadThemes functions less confusing
|
|
// ? - Delete themes which no longer exist in the themes folder from the database?
|
|
func (themes ThemeList) LoadActiveStatus() error {
|
|
ChangeDefaultThemeMutex.Lock()
|
|
defer ChangeDefaultThemeMutex.Unlock()
|
|
|
|
rows, err := themeStmts.getAll.Query()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var uname string
|
|
var defaultThemeSwitch bool
|
|
for rows.Next() {
|
|
err = rows.Scan(&uname, &defaultThemeSwitch)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Was the theme deleted at some point?
|
|
theme, ok := themes[uname]
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
if defaultThemeSwitch {
|
|
DebugLogf("Loading the default theme '%s'", theme.Name)
|
|
theme.Active = true
|
|
DefaultThemeBox.Store(theme.Name)
|
|
theme.MapTemplates()
|
|
} else {
|
|
DebugLogf("Loading the theme '%s'", theme.Name)
|
|
theme.Active = false
|
|
}
|
|
|
|
themes[uname] = theme
|
|
}
|
|
return rows.Err()
|
|
}
|
|
|
|
func (themes ThemeList) LoadStaticFiles() error {
|
|
for _, theme := range themes {
|
|
err := theme.LoadStaticFiles()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ResetTemplateOverrides() {
|
|
log.Print("Resetting the template overrides")
|
|
for name := range overridenTemplates {
|
|
log.Print("Resetting '" + name + "' template override")
|
|
|
|
originPointer, ok := TmplPtrMap["o_"+name]
|
|
if !ok {
|
|
log.Print("The origin template doesn't exist!")
|
|
return
|
|
}
|
|
destTmplPtr, ok := TmplPtrMap[name]
|
|
if !ok {
|
|
log.Print("The destination template doesn't exist!")
|
|
return
|
|
}
|
|
|
|
// Not really a pointer, more of a function handle, an artifact from one of the earlier versions of themes.go
|
|
oPtr, ok := originPointer.(func(interface{}, io.Writer) error)
|
|
if !ok {
|
|
log.Print("name: ", name)
|
|
LogError(errors.New("Unknown destination template type!"))
|
|
return
|
|
}
|
|
|
|
dPtr, ok := destTmplPtr.(*func(interface{}, io.Writer) error)
|
|
if !ok {
|
|
LogError(errors.New("The source and destination templates are incompatible"))
|
|
return
|
|
}
|
|
|
|
*dPtr = oPtr
|
|
log.Print("The template override was reset")
|
|
}
|
|
overridenTemplates = make(map[string]bool)
|
|
log.Print("All of the template overrides have been reset")
|
|
}
|
|
|
|
// CreateThemeTemplate creates a theme template on the current default theme
|
|
func CreateThemeTemplate(theme string, name string) {
|
|
Themes[theme].TmplPtr[name] = func(pi Page, w http.ResponseWriter) error {
|
|
mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap[name]
|
|
if !ok {
|
|
mapping = name
|
|
}
|
|
return DefaultTemplates.ExecuteTemplate(w, mapping+".html", pi)
|
|
}
|
|
}
|
|
|
|
func GetDefaultThemeName() string {
|
|
return DefaultThemeBox.Load().(string)
|
|
}
|
|
|
|
func SetDefaultThemeName(name string) {
|
|
DefaultThemeBox.Store(name)
|
|
}
|