2017-05-29 14:52:37 +00:00
/* Copyright Azareal 2016 - 2018 */
2017-01-01 15:45:43 +00:00
package main
2017-05-29 14:52:37 +00:00
import (
//"fmt"
"log"
"os"
2017-06-25 09:56:39 +00:00
"bytes"
2017-05-29 14:52:37 +00:00
"strings"
2017-08-13 11:22:34 +00:00
"errors"
2017-05-29 14:52:37 +00:00
"mime"
"io/ioutil"
"path/filepath"
"encoding/json"
"net/http"
2017-06-25 09:56:39 +00:00
"text/template"
2017-05-29 14:52:37 +00:00
)
2017-01-01 15:45:43 +00:00
var defaultTheme string
var themes map [ string ] Theme = make ( map [ string ] Theme )
//var overriden_templates map[string]interface{} = make(map[string]interface{})
var overriden_templates map [ string ] bool = make ( map [ string ] bool )
type Theme struct
{
Name string
FriendlyName string
Version string
Creator string
2017-01-07 06:31:04 +00:00
FullImage string
MobileFriendly bool
2017-01-21 18:16:27 +00:00
Disabled bool
2017-02-28 09:27:28 +00:00
HideFromThemes bool
2017-03-07 07:22:29 +00:00
ForkOf string
2017-01-21 18:16:27 +00:00
Tag string
2017-05-29 14:52:37 +00:00
URL string
2017-06-19 08:06:54 +00:00
Sidebars string // Allowed Values: left, right, both, false
2017-07-29 10:36:39 +00:00
//DisableMinifier // Is this really a good idea? I don't think themes should be fighting against the minifier
2017-01-01 15:45:43 +00:00
Settings map [ string ] ThemeSetting
Templates [ ] TemplateMapping
2017-06-19 08:06:54 +00:00
TemplatesMap map [ string ] string
2017-08-13 11:22:34 +00:00
TmplPtr map [ string ] interface { } // Coming Soon
2017-06-19 08:06:54 +00:00
Resources [ ] ThemeResource
2017-06-25 09:56:39 +00:00
ResourceTemplates * template . Template
2017-05-29 14:52:37 +00:00
2017-01-01 15:45:43 +00:00
// This variable should only be set and unset by the system, not the theme meta file
Active bool
}
type ThemeSetting struct
{
FriendlyName string
Options [ ] string
}
type TemplateMapping struct
{
Name string
Source string
//When string
}
2017-06-19 08:06:54 +00:00
type ThemeResource struct
{
Name string
Location string
}
2017-06-14 09:55:47 +00:00
func LoadThemes ( ) error {
rows , err := get_themes_stmt . 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
}
2017-06-16 10:41:30 +00:00
theme . TemplatesMap = make ( map [ string ] string )
if theme . Templates != nil {
for _ , themeTmpl := range theme . Templates {
theme . TemplatesMap [ themeTmpl . Name ] = themeTmpl . Source
}
}
2017-06-25 09:56:39 +00:00
theme . ResourceTemplates = template . New ( "" )
template . Must ( theme . ResourceTemplates . ParseGlob ( "./themes/" + uname + "/public/*.css" ) )
2017-06-14 09:55:47 +00:00
if defaultThemeSwitch {
log . Print ( "Loading the theme '" + theme . Name + "'" )
theme . Active = true
defaultTheme = uname
2017-06-25 09:56:39 +00:00
add_theme_static_files ( theme )
2017-06-14 09:55:47 +00:00
map_theme_templates ( theme )
} else {
theme . Active = false
}
themes [ uname ] = theme
}
err = rows . Err ( )
if err != nil {
return err
}
return nil
}
2017-01-01 15:45:43 +00:00
func init_themes ( ) {
themeFiles , err := ioutil . ReadDir ( "./themes" )
if err != nil {
log . Fatal ( err )
}
2017-05-29 14:52:37 +00:00
2017-01-01 15:45:43 +00:00
for _ , themeFile := range themeFiles {
if ! themeFile . IsDir ( ) {
continue
}
2017-05-29 14:52:37 +00:00
2017-01-01 15:45:43 +00:00
themeName := themeFile . Name ( )
log . Print ( "Adding theme '" + themeName + "'" )
themeFile , err := ioutil . ReadFile ( "./themes/" + themeName + "/theme.json" )
if err != nil {
log . Fatal ( err )
}
2017-05-29 14:52:37 +00:00
2017-01-01 15:45:43 +00:00
var theme Theme
2017-01-07 06:31:04 +00:00
err = json . Unmarshal ( themeFile , & theme )
if err != nil {
log . Fatal ( err )
}
2017-05-29 14:52:37 +00:00
2017-01-01 15:45:43 +00:00
theme . Active = false // Set this to false, just in case someone explicitly overrode this value in the JSON file
2017-05-29 14:52:37 +00:00
2017-01-07 06:31:04 +00:00
if theme . FullImage != "" {
2017-07-17 10:23:42 +00:00
if dev . DebugMode {
2017-05-07 08:31:41 +00:00
log . Print ( "Adding theme image" )
}
2017-01-07 06:31:04 +00:00
err = add_static_file ( "./themes/" + themeName + "/" + theme . FullImage , "./themes/" + themeName )
if err != nil {
log . Fatal ( err )
}
}
2017-05-29 14:52:37 +00:00
2017-01-01 15:45:43 +00:00
themes [ theme . Name ] = theme
}
}
2017-06-25 09:56:39 +00:00
func add_theme_static_files ( theme Theme ) {
2017-07-29 10:36:39 +00:00
// TO-DO: Use a function instead of a closure to make this more testable? What about a function call inside the closure to take the theme variable into account?
2017-06-25 09:56:39 +00:00
err := filepath . Walk ( "./themes/" + theme . Name + "/public" , func ( path string , f os . FileInfo , err error ) error {
2017-07-17 10:23:42 +00:00
if dev . DebugMode {
2017-06-25 09:56:39 +00:00
log . Print ( "Attempting to add static file '" + path + "' for default theme '" + theme . Name + "'" )
}
2017-01-01 15:45:43 +00:00
if err != nil {
return err
}
if f . IsDir ( ) {
return nil
}
2017-05-29 14:52:37 +00:00
2017-06-25 09:56:39 +00:00
path = strings . Replace ( path , "\\" , "/" , - 1 )
2017-01-01 15:45:43 +00:00
data , err := ioutil . ReadFile ( path )
if err != nil {
return err
}
2017-05-29 14:52:37 +00:00
2017-06-25 09:56:39 +00:00
var ext string = filepath . Ext ( path )
//log.Print("path ",path)
//log.Print("ext ",ext)
2017-07-17 10:23:42 +00:00
if ext == ".css" && len ( data ) != 0 {
2017-06-25 09:56:39 +00:00
var b bytes . Buffer
var pieces [ ] string = strings . Split ( path , "/" )
var filename string = pieces [ len ( pieces ) - 1 ]
//log.Print("filename ", filename)
err = theme . ResourceTemplates . ExecuteTemplate ( & b , filename , CssData { ComingSoon : "We don't have any data to pass you yet!" } )
if err != nil {
return err
}
data = b . Bytes ( )
2017-05-07 08:31:41 +00:00
}
2017-06-25 09:56:39 +00:00
path = strings . TrimPrefix ( path , "themes/" + theme . Name + "/public" )
2017-02-16 06:47:55 +00:00
gzip_data := compress_bytes_gzip ( data )
2017-06-25 09:56:39 +00:00
static_files [ "/static" + path ] = SFile { data , gzip_data , 0 , int64 ( len ( data ) ) , int64 ( len ( gzip_data ) ) , mime . TypeByExtension ( ext ) , f , f . ModTime ( ) . UTC ( ) . Format ( http . TimeFormat ) }
2017-05-29 14:52:37 +00:00
2017-07-17 10:23:42 +00:00
if dev . DebugMode {
2017-06-25 09:56:39 +00:00
log . Print ( "Added the '" + path + "' static file for default theme " + theme . Name + "." )
}
2017-01-01 15:45:43 +00:00
return nil
} )
if err != nil {
panic ( err )
}
}
func map_theme_templates ( theme Theme ) {
if theme . Templates != nil {
for _ , themeTmpl := range theme . Templates {
if themeTmpl . Name == "" {
log . Fatal ( "Invalid destination template name" )
}
if themeTmpl . Source == "" {
log . Fatal ( "Invalid source template name" )
}
2017-05-29 14:52:37 +00:00
2017-01-07 06:31:04 +00:00
// `go generate` is one possibility for letting plugins inject custom page structs, but it would simply add another step of compilation. It might be simpler than the current build process from the perspective of the administrator?
2017-05-29 14:52:37 +00:00
2017-01-01 15:45:43 +00:00
dest_tmpl_ptr , ok := tmpl_ptr_map [ themeTmpl . Name ]
if ! ok {
2017-06-16 10:41:30 +00:00
return
2017-01-01 15:45:43 +00:00
}
source_tmpl_ptr , ok := tmpl_ptr_map [ themeTmpl . Source ]
if ! ok {
log . Fatal ( "The source template doesn't exist!" )
}
2017-05-29 14:52:37 +00:00
2017-01-01 15:45:43 +00:00
switch d_tmpl_ptr := dest_tmpl_ptr . ( type ) {
2017-08-13 11:22:34 +00:00
case * func ( TopicPage , http . ResponseWriter ) :
2017-01-01 15:45:43 +00:00
switch s_tmpl_ptr := source_tmpl_ptr . ( type ) {
2017-08-13 11:22:34 +00:00
case * func ( TopicPage , http . ResponseWriter ) :
2017-01-01 15:45:43 +00:00
//overriden_templates[themeTmpl.Name] = d_tmpl_ptr
overriden_templates [ themeTmpl . Name ] = true
* d_tmpl_ptr = * s_tmpl_ptr
default :
log . Fatal ( "The source and destination templates are incompatible" )
}
2017-08-13 11:22:34 +00:00
case * func ( TopicsPage , http . ResponseWriter ) :
2017-01-01 15:45:43 +00:00
switch s_tmpl_ptr := source_tmpl_ptr . ( type ) {
2017-08-13 11:22:34 +00:00
case * func ( TopicsPage , http . ResponseWriter ) :
2017-01-01 15:45:43 +00:00
//overriden_templates[themeTmpl.Name] = d_tmpl_ptr
overriden_templates [ themeTmpl . Name ] = true
* d_tmpl_ptr = * s_tmpl_ptr
default :
log . Fatal ( "The source and destination templates are incompatible" )
}
2017-08-13 11:22:34 +00:00
case * func ( ForumPage , http . ResponseWriter ) :
2017-01-01 15:45:43 +00:00
switch s_tmpl_ptr := source_tmpl_ptr . ( type ) {
2017-08-13 11:22:34 +00:00
case * func ( ForumPage , http . ResponseWriter ) :
2017-01-01 15:45:43 +00:00
//overriden_templates[themeTmpl.Name] = d_tmpl_ptr
overriden_templates [ themeTmpl . Name ] = true
* d_tmpl_ptr = * s_tmpl_ptr
default :
log . Fatal ( "The source and destination templates are incompatible" )
}
2017-08-13 11:22:34 +00:00
case * func ( ForumsPage , http . ResponseWriter ) :
2017-01-01 15:45:43 +00:00
switch s_tmpl_ptr := source_tmpl_ptr . ( type ) {
2017-08-13 11:22:34 +00:00
case * func ( ForumsPage , http . ResponseWriter ) :
2017-01-01 15:45:43 +00:00
//overriden_templates[themeTmpl.Name] = d_tmpl_ptr
overriden_templates [ themeTmpl . Name ] = true
* d_tmpl_ptr = * s_tmpl_ptr
default :
log . Fatal ( "The source and destination templates are incompatible" )
}
2017-08-13 11:22:34 +00:00
case * func ( ProfilePage , http . ResponseWriter ) :
2017-01-01 15:45:43 +00:00
switch s_tmpl_ptr := source_tmpl_ptr . ( type ) {
2017-08-13 11:22:34 +00:00
case * func ( ProfilePage , http . ResponseWriter ) :
2017-01-01 15:45:43 +00:00
//overriden_templates[themeTmpl.Name] = d_tmpl_ptr
overriden_templates [ themeTmpl . Name ] = true
* d_tmpl_ptr = * s_tmpl_ptr
default :
log . Fatal ( "The source and destination templates are incompatible" )
}
2017-08-13 11:22:34 +00:00
case * func ( CreateTopicPage , http . ResponseWriter ) :
2017-01-01 15:45:43 +00:00
switch s_tmpl_ptr := source_tmpl_ptr . ( type ) {
2017-08-13 11:22:34 +00:00
case * func ( CreateTopicPage , http . ResponseWriter ) :
//overriden_templates[themeTmpl.Name] = d_tmpl_ptr
overriden_templates [ themeTmpl . Name ] = true
* d_tmpl_ptr = * s_tmpl_ptr
default :
log . Fatal ( "The source and destination templates are incompatible" )
}
case * func ( Page , http . ResponseWriter ) :
switch s_tmpl_ptr := source_tmpl_ptr . ( type ) {
case * func ( Page , http . ResponseWriter ) :
2017-01-01 15:45:43 +00:00
//overriden_templates[themeTmpl.Name] = d_tmpl_ptr
overriden_templates [ themeTmpl . Name ] = true
* d_tmpl_ptr = * s_tmpl_ptr
default :
log . Fatal ( "The source and destination templates are incompatible" )
}
default :
log . Fatal ( "Unknown destination template type!" )
}
}
}
}
func reset_template_overrides ( ) {
log . Print ( "Resetting the template overrides" )
2017-05-29 14:52:37 +00:00
2017-01-01 15:45:43 +00:00
for name , _ := range overriden_templates {
log . Print ( "Resetting '" + name + "' template override" )
2017-05-29 14:52:37 +00:00
2017-01-01 15:45:43 +00:00
origin_pointer , ok := tmpl_ptr_map [ "o_" + name ]
if ! ok {
//log.Fatal("The origin template doesn't exist!")
log . Print ( "The origin template doesn't exist!" )
return
}
2017-05-29 14:52:37 +00:00
2017-01-01 15:45:43 +00:00
dest_tmpl_ptr , ok := tmpl_ptr_map [ name ]
if ! ok {
//log.Fatal("The destination template doesn't exist!")
log . Print ( "The destination template doesn't exist!" )
return
}
2017-05-29 14:52:37 +00:00
2017-01-01 16:14:38 +00:00
// Not really a pointer, more of a function handle, an artifact from one of the earlier versions of themes.go
2017-01-01 15:45:43 +00:00
switch o_ptr := origin_pointer . ( type ) {
2017-08-13 11:22:34 +00:00
case func ( TopicPage , http . ResponseWriter ) :
switch d_ptr := dest_tmpl_ptr . ( type ) {
case * func ( TopicPage , http . ResponseWriter ) :
* d_ptr = o_ptr
default :
log . Fatal ( "The origin and destination templates are incompatible" )
}
case func ( TopicsPage , http . ResponseWriter ) :
2017-01-01 15:45:43 +00:00
switch d_ptr := dest_tmpl_ptr . ( type ) {
2017-08-13 11:22:34 +00:00
case * func ( TopicsPage , http . ResponseWriter ) :
2017-01-01 15:45:43 +00:00
* d_ptr = o_ptr
default :
log . Fatal ( "The origin and destination templates are incompatible" )
}
2017-08-13 11:22:34 +00:00
case func ( ForumPage , http . ResponseWriter ) :
2017-01-01 15:45:43 +00:00
switch d_ptr := dest_tmpl_ptr . ( type ) {
2017-08-13 11:22:34 +00:00
case * func ( ForumPage , http . ResponseWriter ) :
2017-01-01 15:45:43 +00:00
* d_ptr = o_ptr
default :
log . Fatal ( "The origin and destination templates are incompatible" )
}
2017-08-13 11:22:34 +00:00
case func ( ForumsPage , http . ResponseWriter ) :
2017-01-01 15:45:43 +00:00
switch d_ptr := dest_tmpl_ptr . ( type ) {
2017-08-13 11:22:34 +00:00
case * func ( ForumsPage , http . ResponseWriter ) :
2017-01-01 15:45:43 +00:00
* d_ptr = o_ptr
default :
log . Fatal ( "The origin and destination templates are incompatible" )
}
2017-08-13 11:22:34 +00:00
case func ( ProfilePage , http . ResponseWriter ) :
2017-01-01 15:45:43 +00:00
switch d_ptr := dest_tmpl_ptr . ( type ) {
2017-08-13 11:22:34 +00:00
case * func ( ProfilePage , http . ResponseWriter ) :
2017-01-01 15:45:43 +00:00
* d_ptr = o_ptr
default :
log . Fatal ( "The origin and destination templates are incompatible" )
}
2017-08-13 11:22:34 +00:00
case func ( CreateTopicPage , http . ResponseWriter ) :
2017-01-01 15:45:43 +00:00
switch d_ptr := dest_tmpl_ptr . ( type ) {
2017-08-13 11:22:34 +00:00
case * func ( CreateTopicPage , http . ResponseWriter ) :
* d_ptr = o_ptr
default :
log . Fatal ( "The origin and destination templates are incompatible" )
}
case func ( Page , http . ResponseWriter ) :
switch d_ptr := dest_tmpl_ptr . ( type ) {
case * func ( Page , http . ResponseWriter ) :
2017-01-01 15:45:43 +00:00
* d_ptr = o_ptr
default :
log . Fatal ( "The origin and destination templates are incompatible" )
}
default :
log . Fatal ( "Unknown destination template type!" )
}
log . Print ( "The template override was reset" )
}
overriden_templates = make ( map [ string ] bool )
log . Print ( "All of the template overrides have been reset" )
}
2017-08-13 11:22:34 +00:00
// NEW method of doing theme templates to allow one user to have a different theme to another. Under construction.
// TO-DO: Generate the type switch instead of writing it by hand
func RunThemeTemplate ( theme string , template string , pi interface { } , w http . ResponseWriter ) {
switch tmpl := GetThemeTemplate ( theme , template ) . ( type ) {
case func ( TopicPage , http . ResponseWriter ) : tmpl ( pi . ( TopicPage ) , w )
case func ( TopicsPage , http . ResponseWriter ) : tmpl ( pi . ( TopicsPage ) , w )
case func ( ForumPage , http . ResponseWriter ) : tmpl ( pi . ( ForumPage ) , w )
case func ( ForumsPage , http . ResponseWriter ) : tmpl ( pi . ( ForumsPage ) , w )
case func ( ProfilePage , http . ResponseWriter ) : tmpl ( pi . ( ProfilePage ) , w )
case func ( CreateTopicPage , http . ResponseWriter ) : tmpl ( pi . ( CreateTopicPage ) , w )
case func ( Page , http . ResponseWriter ) : tmpl ( pi . ( Page ) , w )
default : LogError ( errors . New ( "Unknown template type" ) )
}
}
func GetThemeTemplate ( theme string , template string ) interface { } {
tmpl , ok := themes [ theme ] . TmplPtr [ template ]
if ! ok {
return tmpl
}
return nil
}
func CreateThemeTemplate ( theme string , name string ) {
themes [ theme ] . TmplPtr [ name ] = func ( pi ProfilePage , w http . ResponseWriter ) {
mapping , ok := themes [ defaultTheme ] . TemplatesMap [ name ]
if ! ok {
mapping = name
}
err := templates . ExecuteTemplate ( w , mapping + ".html" , pi )
if err != nil {
InternalError ( err , w )
}
}
}