diff --git a/.travis.yml b/.travis.yml index 36c5ac90..4a16e25b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: go go: - - 1.10 + - "1.10" - master before_install: - cd $HOME diff --git a/README.md b/README.md index d66a3de7..904d8b0f 100644 --- a/README.md +++ b/README.md @@ -49,11 +49,6 @@ It's entirely possible that your host might already have MySQL, so you might be At some point, we'll have releases which you can download, but right now, you'll have to use the `git clone` command as mentioned down in the advanced setup section to download a copy of Gosora. -# Updating - -The update system is currently under development, however if you have Git installed, then you can run `dev-update.bat` or `dev-update-linux` to update your instance to the latest commit and to update the associated database schema, etc. - - # Installation Instructions *Linux* @@ -96,9 +91,9 @@ Several important features for saving memory in the templates system may have to # Advanced Installation -An example of running the commands directly on Windows. We're looking into reducing the number of commands you need to type, for instance, you could invoke the update-deps batch or shell files to install / update all of the dependencies instead of typing each `get get -u` +This section explains how to set things up without running the batch or shell files. For Windows, you will likely have to open up cmd.exe (the app called Command Prompt in Win10) to run these commands inside or something similar, while with Linux you would likely use the Terminal or console. -Linux is similar, however you might need to use cd and mv a bit more like in the shell files due to the differences in go build across platforms. Additionally, Linux doesn't require `StackExchange/wmi` or ``/x/sys/windows` +Linux is similar, however you might need to use cd and mv a bit more like in the shell files due to the differences in go build across platforms. Additionally, Linux doesn't require `StackExchange/wmi` or `/x/sys/windows` You also need to substitute the `gosora.exe` bits for `./Gosora` on Linux. For more info, you might want to take a gander inside the `./run-linux` and `./install-linux` shell files to see how they're implemented. @@ -154,6 +149,13 @@ gosora.exe I'm looking into minimising the number of go gets for the advanced build and to maybe remove the platform and database engine specific dependencies if possible for those who don't need them. +# Updating + +The update system is currently under development, however if you have Git installed, then you can run `dev-update.bat` or `dev-update-linux` to update your instance to the latest commit and to update the associated database schema, etc. + +In addition to this, you can update the dependencies without updating Gosora by running `update-deps.bat` or `./update-deps-linux` (.bat is for Windows, the other for Linux as the names would suggest). + + # How do I install plugins? For the default plugins like Markdown and Helloworld, you can find them in the Plugin Manager of your Control Panel. For ones which aren't included by default, you will need to drag them from your /extend/ directory and into the / directory (the root directory of your Gosora installation, where the executable and most of the main Go files are). diff --git a/client/main.go b/client/main.go index b59edd2c..6257c7dd 100644 --- a/client/main.go +++ b/client/main.go @@ -1,7 +1,10 @@ package main import ( + "bytes" + "../common" + "../tmpl_gen/client" "github.com/gopherjs/gopherjs/js" ) @@ -13,4 +16,14 @@ func main() { } return "" }) + + js.Global.Set("renderAlert", func(asid int, path string, msg string, avatar string) string { + var buf bytes.Buffer + alertItem := common.AlertItem{asid, path, msg, avatar} + err := tmpl.Template_alert(alertItem, &buf) + if err != nil { + println(err.Error()) + } + return string(buf.Bytes()) + }) } diff --git a/common/errors.go b/common/errors.go index cd6b7cf1..dc329c56 100644 --- a/common/errors.go +++ b/common/errors.go @@ -8,7 +8,7 @@ import ( ) // TODO: Use the error_buffer variable to construct the system log in the Control Panel. Should we log errors caused by users too? Or just collect statistics on those or do nothing? Intercept recover()? Could we intercept the logger instead here? We might get too much information, if we intercept the logger, maybe make it part of the Debug page? -// ? - Should we pass HeaderVars / HeaderLite rather than forcing the errors to pull the global HeaderVars instance? +// ? - Should we pass Header / HeaderLite rather than forcing the errors to pull the global Header instance? var errorBufferMutex sync.RWMutex var errorBuffer []error @@ -77,7 +77,7 @@ func LogWarning(err error) { // InternalError is the main function for handling internal errors, while simultaneously printing out a page for the end-user to let them know that *something* has gone wrong // ? - Add a user parameter? func InternalError(err error, w http.ResponseWriter, r *http.Request) RouteError { - pi := Page{"Internal Server Error", GuestUser, DefaultHeaderVar(), tList, "A problem has occurred in the system."} + pi := Page{"Internal Server Error", GuestUser, DefaultHeader(w), tList, "A problem has occurred in the system."} handleErrorTemplate(w, r, pi) LogError(err) return HandledRouteError() @@ -122,9 +122,8 @@ func SilentInternalErrorXML(err error, w http.ResponseWriter, r *http.Request) R } func PreError(errmsg string, w http.ResponseWriter, r *http.Request) RouteError { - //LogError(errors.New(errmsg)) w.WriteHeader(500) - pi := Page{"Error", GuestUser, DefaultHeaderVar(), tList, errmsg} + pi := Page{"Error", GuestUser, DefaultHeader(w), tList, errmsg} handleErrorTemplate(w, r, pi) return HandledRouteError() } @@ -144,9 +143,8 @@ func PreErrorJSQ(errmsg string, w http.ResponseWriter, r *http.Request, isJs boo // LocalError is an error shown to the end-user when something goes wrong and it's not the software's fault func LocalError(errmsg string, w http.ResponseWriter, r *http.Request, user User) RouteError { - //LogError(errors.New(errmsg)) w.WriteHeader(500) - pi := Page{"Local Error", user, DefaultHeaderVar(), tList, errmsg} + pi := Page{"Local Error", user, DefaultHeader(w), tList, errmsg} handleErrorTemplate(w, r, pi) return HandledRouteError() } @@ -168,7 +166,7 @@ func LocalErrorJS(errmsg string, w http.ResponseWriter, r *http.Request) RouteEr // NoPermissions is an error shown to the end-user when they try to access an area which they aren't authorised to access func NoPermissions(w http.ResponseWriter, r *http.Request, user User) RouteError { w.WriteHeader(403) - pi := Page{"Local Error", user, DefaultHeaderVar(), tList, "You don't have permission to do that."} + pi := Page{"Local Error", user, DefaultHeader(w), tList, "You don't have permission to do that."} handleErrorTemplate(w, r, pi) return HandledRouteError() } @@ -189,7 +187,7 @@ func NoPermissionsJS(w http.ResponseWriter, r *http.Request, user User) RouteErr // ? - Is this actually used? Should it be used? A ban in Gosora should be more of a permission revocation to stop them posting rather than something which spits up an error page, right? func Banned(w http.ResponseWriter, r *http.Request, user User) RouteError { w.WriteHeader(403) - pi := Page{"Banned", user, DefaultHeaderVar(), tList, "You have been banned from this site."} + pi := Page{"Banned", user, DefaultHeader(w), tList, "You have been banned from this site."} handleErrorTemplate(w, r, pi) return HandledRouteError() } @@ -221,7 +219,7 @@ func LoginRequiredJSQ(w http.ResponseWriter, r *http.Request, user User, isJs bo // LoginRequired is an error shown to the end-user when they try to access an area which requires them to login func LoginRequired(w http.ResponseWriter, r *http.Request, user User) RouteError { w.WriteHeader(401) - pi := Page{"Local Error", user, DefaultHeaderVar(), tList, "You need to login to do that."} + pi := Page{"Local Error", user, DefaultHeader(w), tList, "You need to login to do that."} handleErrorTemplate(w, r, pi) return HandledRouteError() } @@ -237,7 +235,7 @@ func LoginRequiredJS(w http.ResponseWriter, r *http.Request, user User) RouteErr // ? - Should we add JS and JSQ versions of this? func SecurityError(w http.ResponseWriter, r *http.Request, user User) RouteError { w.WriteHeader(403) - pi := Page{"Security Error", user, DefaultHeaderVar(), tList, "There was a security issue with your request."} + pi := Page{"Security Error", user, DefaultHeader(w), tList, "There was a security issue with your request."} if RunPreRenderHook("pre_render_security_error", w, r, &user, &pi) { return nil } @@ -251,28 +249,28 @@ func SecurityError(w http.ResponseWriter, r *http.Request, user User) RouteError // NotFound is used when the requested page doesn't exist // ? - Add a JSQ and JS version of this? // ? - Add a user parameter? -func NotFound(w http.ResponseWriter, r *http.Request, headerVars *HeaderVars) RouteError { - return CustomError("The requested page doesn't exist.", 404, "Not Found", w, r, headerVars, GuestUser) +func NotFound(w http.ResponseWriter, r *http.Request, header *Header) RouteError { + return CustomError("The requested page doesn't exist.", 404, "Not Found", w, r, header, GuestUser) } // CustomError lets us make custom error types which aren't covered by the generic functions above -func CustomError(errmsg string, errcode int, errtitle string, w http.ResponseWriter, r *http.Request, headerVars *HeaderVars, user User) RouteError { - if headerVars == nil { - headerVars = DefaultHeaderVar() +func CustomError(errmsg string, errcode int, errtitle string, w http.ResponseWriter, r *http.Request, header *Header, user User) RouteError { + if header == nil { + header = DefaultHeader(w) } w.WriteHeader(errcode) - pi := Page{errtitle, user, headerVars, tList, errmsg} + pi := Page{errtitle, user, header, tList, errmsg} handleErrorTemplate(w, r, pi) return HandledRouteError() } // CustomErrorJSQ is a version of CustomError which lets us handle both JSON and regular pages depending on how it's being accessed -func CustomErrorJSQ(errmsg string, errcode int, errtitle string, w http.ResponseWriter, r *http.Request, headerVars *HeaderVars, user User, isJs bool) RouteError { +func CustomErrorJSQ(errmsg string, errcode int, errtitle string, w http.ResponseWriter, r *http.Request, header *Header, user User, isJs bool) RouteError { if !isJs { - if headerVars == nil { - headerVars = DefaultHeaderVar() + if header == nil { + header = DefaultHeader(w) } - return CustomError(errmsg, errcode, errtitle, w, r, headerVars, user) + return CustomError(errmsg, errcode, errtitle, w, r, header, user) } return CustomErrorJS(errmsg, errcode, w, r, user) } @@ -285,9 +283,8 @@ func CustomErrorJS(errmsg string, errcode int, w http.ResponseWriter, r *http.Re } func handleErrorTemplate(w http.ResponseWriter, r *http.Request, pi Page) { - //LogError(errors.New("error happened")) // TODO: What to do about this hook? - if RunPreRenderHook("pre_render_error", w, r, &pi.CurrentUser, &pi) { + if RunPreRenderHook("pre_render_error", w, r, &pi.Header.CurrentUser, &pi) { return } err := RunThemeTemplate(pi.Header.Theme.Name, "error", pi, w) diff --git a/common/extend.go b/common/extend.go index 221aa582..d9ba9c1a 100644 --- a/common/extend.go +++ b/common/extend.go @@ -62,7 +62,7 @@ type Message interface { // While the idea is nice, this might result in too much code duplication, as we have seventy billion page structs, what else could we do to get static typing with these in plugins? type PageInt interface { Title() string - HeaderVars() *HeaderVars + Header() *Header CurrentUser() *User GetExtData(name string) interface{} SetExtData(name string, contents interface{}) diff --git a/common/menus.go b/common/menus.go new file mode 100644 index 00000000..2dffd39d --- /dev/null +++ b/common/menus.go @@ -0,0 +1,408 @@ +package common + +import ( + "bytes" + "database/sql" + "fmt" + "io" + "io/ioutil" + "strconv" + "sync/atomic" + + "../query_gen/lib" +) + +var Menus *DefaultMenuStore + +type MenuItemList []MenuItem + +type DefaultMenuStore struct { + menus map[int]*atomic.Value +} + +func NewDefaultMenuStore() *DefaultMenuStore { + return &DefaultMenuStore{make(map[int]*atomic.Value)} +} + +func (store *DefaultMenuStore) Get(mid int) *MenuListHolder { + aStore, ok := store.menus[mid] + if ok { + return aStore.Load().(*MenuListHolder) + } + return nil +} + +type MenuListHolder struct { + List MenuItemList + Variations map[int]menuTmpl // 0 = Guest Menu, 1 = Member Menu, 2 = Super Mod Menu, 3 = Admin Menu +} + +type menuTmpl struct { + RenderBuffer [][]byte + VariableIndices []int +} + +type MenuItem struct { + ID int + HTMLID string + CSSClass string + Position string + Path string + Aria string + Tooltip string + Order int + TmplName string + + GuestOnly bool + MemberOnly bool + SuperModOnly bool + AdminOnly bool +} + +func (store *DefaultMenuStore) Load(mid int) error { + var mlist MenuItemList + acc := qgen.Builder.Accumulator() + err := acc.Select("menu_items").Columns("htmlID, cssClass, position, path, aria, tooltip, order, tmplName, guestOnly, memberOnly, staffOnly, adminOnly").Where("mid = " + strconv.Itoa(mid)).Orderby("order ASC").Each(func(rows *sql.Rows) error { + var mitem = MenuItem{ID: 1} + err := rows.Scan(&mitem.HTMLID, &mitem.CSSClass, &mitem.Position, &mitem.Path, &mitem.Aria, &mitem.Tooltip, &mitem.Order, &mitem.TmplName, &mitem.GuestOnly, &mitem.MemberOnly, &mitem.SuperModOnly, &mitem.AdminOnly) + if err != nil { + return err + } + mlist = append(mlist, mitem) + return nil + }) + if err != nil { + return err + } + + hold := &MenuListHolder{mlist, make(map[int]menuTmpl)} + err = hold.Preparse() + if err != nil { + return err + } + + var aStore = &atomic.Value{} + aStore.Store(hold) + store.menus[mid] = aStore + return nil +} + +// TODO: Run this in main, sync ticks, when the phrase file changes (need to implement the sync for that first), and when the settings are changed +func (hold *MenuListHolder) Preparse() error { + var tmpls = make(map[string]MenuTmpl) + var loadTmpl = func(name string) error { + data, err := ioutil.ReadFile("./templates/" + name + ".html") + if err != nil { + return err + } + tmpls[name] = hold.Parse(name, data) + return nil + } + err := loadTmpl("menu_item") + if err != nil { + return err + } + err = loadTmpl("menu_alerts") + if err != nil { + return err + } + + var addVariation = func(index int, callback func(mitem MenuItem) bool) { + renderBuffer, variableIndices := hold.Scan(tmpls, callback) + hold.Variations[index] = menuTmpl{renderBuffer, variableIndices} + fmt.Print("renderBuffer: ") + menuDumpSlice(renderBuffer) + fmt.Printf("\nvariableIndices: %+v\n", variableIndices) + } + + // Guest Menu + addVariation(0, func(mitem MenuItem) bool { + return !mitem.MemberOnly + }) + // Member Menu + addVariation(1, func(mitem MenuItem) bool { + return !mitem.SuperModOnly && !mitem.GuestOnly + }) + // Super Mod Menu + addVariation(2, func(mitem MenuItem) bool { + return !mitem.AdminOnly && !mitem.GuestOnly + }) + // Admin Menu + addVariation(3, func(mitem MenuItem) bool { + return !mitem.GuestOnly + }) + return nil +} + +func nextCharIs(tmplData []byte, i int, expects byte) bool { + if len(tmplData) <= (i + 1) { + return false + } + return tmplData[i+1] == expects +} + +func skipUntilIfExists(tmplData []byte, i int, expects byte) (newI int, hasIt bool) { + j := i + for ; j < len(tmplData); j++ { + if tmplData[j] == expects { + return j, true + } + } + return j, false +} + +func skipUntilCharsExist(tmplData []byte, i int, expects []byte) (newI int, hasIt bool) { + j := i + expectIndex := 0 + for ; j < len(tmplData) && expectIndex < len(expects); j++ { + if tmplData[j] != expects[expectIndex] { + return j, false + } + expectIndex++ + } + return j, true +} + +type menuRenderItem struct { + Type int // 0: text, 1: variable + Index int +} + +type MenuTmpl struct { + Name string + TextBuffer [][]byte + VariableBuffer [][]byte + RenderList []menuRenderItem +} + +func menuDumpSlice(outerSlice [][]byte) { + for sliceID, slice := range outerSlice { + fmt.Print(strconv.Itoa(sliceID) + ":[") + for _, char := range slice { + fmt.Print(string(char)) + } + fmt.Print("] ") + } +} + +func (hold *MenuListHolder) Parse(name string, tmplData []byte) (menuTmpl MenuTmpl) { + //fmt.Println("tmplData: ", string(tmplData)) + var textBuffer, variableBuffer [][]byte + var renderList []menuRenderItem + var subBuffer []byte + + // ? We only support simple properties on MenuItem right now + var addVariable = func(name []byte) { + //fmt.Println("appending subBuffer: ", string(subBuffer)) + textBuffer = append(textBuffer, subBuffer) + subBuffer = nil + + //fmt.Println("adding variable: ", string(name)) + variableBuffer = append(variableBuffer, name) + renderList = append(renderList, menuRenderItem{0, len(textBuffer) - 1}) + renderList = append(renderList, menuRenderItem{1, len(variableBuffer) - 1}) + } + + for i := 0; i < len(tmplData); i++ { + char := tmplData[i] + if char == '{' && nextCharIs(tmplData, i, '{') { + dotIndex, hasDot := skipUntilIfExists(tmplData, i, '.') + if !hasDot { + // Template function style + langIndex, hasChars := skipUntilCharsExist(tmplData, i+2, []byte("lang")) + if hasChars { + startIndex, hasStart := skipUntilIfExists(tmplData, langIndex, '"') + endIndex, hasEnd := skipUntilIfExists(tmplData, startIndex+1, '"') + if hasStart && hasEnd { + fenceIndex, hasFence := skipUntilIfExists(tmplData, endIndex, '}') + if !hasFence || !nextCharIs(tmplData, fenceIndex, '}') { + break + } + //fmt.Println("tmplData[startIndex:endIndex]: ", tmplData[startIndex+1:endIndex]) + prefix := []byte("lang.") + addVariable(append(prefix, tmplData[startIndex+1:endIndex]...)) + i = fenceIndex + 1 + continue + } + } + break + } + fenceIndex, hasFence := skipUntilIfExists(tmplData, dotIndex, '}') + if !hasFence || !nextCharIs(tmplData, fenceIndex, '}') { + break + } + addVariable(tmplData[dotIndex:fenceIndex]) + i = fenceIndex + 1 + continue + } + subBuffer = append(subBuffer, char) + } + if len(subBuffer) > 0 { + // TODO: Have a property in renderList which holds the byte slice since variableBuffers and textBuffers have the same underlying implementation? + textBuffer = append(textBuffer, subBuffer) + renderList = append(renderList, menuRenderItem{0, len(textBuffer) - 1}) + } + + fmt.Println("name: ", name) + fmt.Print("textBuffer: ") + menuDumpSlice(textBuffer) + fmt.Print("\nvariableBuffer: ") + menuDumpSlice(variableBuffer) + fmt.Printf("\nrenderList: %+v\n", renderList) + return MenuTmpl{name, textBuffer, variableBuffer, renderList} +} + +func (hold *MenuListHolder) Scan(menuTmpls map[string]MenuTmpl, showItem func(mitem MenuItem) bool) (renderBuffer [][]byte, variableIndices []int) { + for _, mitem := range hold.List { + // Do we want this item in this variation of the menu? + if !showItem(mitem) { + continue + } + + menuTmpl, ok := menuTmpls[mitem.TmplName] + if !ok { + menuTmpl = menuTmpls["menu_item"] + } + fmt.Println("menuTmpl: ", menuTmpl) + for _, renderItem := range menuTmpl.RenderList { + if renderItem.Type == 0 { + renderBuffer = append(renderBuffer, menuTmpl.TextBuffer[renderItem.Index]) + continue + } + + variable := menuTmpl.VariableBuffer[renderItem.Index] + fmt.Println("initial variable: ", string(variable)) + dotAt, hasDot := skipUntilIfExists(variable, 0, '.') + if !hasDot { + fmt.Println("no dot") + continue + } + + if bytes.Equal(variable[:dotAt], []byte("lang")) { + fmt.Println("lang: ", string(bytes.TrimPrefix(variable[dotAt:], []byte(".")))) + renderBuffer = append(renderBuffer, []byte(GetTmplPhrase(string(bytes.TrimPrefix(variable[dotAt:], []byte(".")))))) + } else { + var renderItem []byte + switch string(variable) { + case ".HTMLID": + renderItem = []byte(mitem.HTMLID) + case ".CSSClass": + renderItem = []byte(mitem.CSSClass) + case ".Position": + renderItem = []byte(mitem.Position) + case ".Path": + renderItem = []byte(mitem.Path) + case ".Aria": + renderItem = []byte(mitem.Aria) + case ".Tooltip": + renderItem = []byte(mitem.Tooltip) + } + + _, hasInnerVar := skipUntilIfExists(renderItem, 0, '{') + if hasInnerVar { + fmt.Println("inner var: ", string(renderItem)) + dotAt, hasDot := skipUntilIfExists(renderItem, 0, '.') + endFence, hasEndFence := skipUntilIfExists(renderItem, dotAt, '}') + if !hasDot || !hasEndFence || (endFence-dotAt) <= 1 { + renderBuffer = append(renderBuffer, renderItem) + variableIndices = append(variableIndices, len(renderBuffer)-1) + continue + } + + if bytes.Equal(renderItem[1:dotAt], []byte("lang")) { + fmt.Println("lang var: ", string(renderItem[dotAt+1:endFence])) + renderBuffer = append(renderBuffer, []byte(GetTmplPhrase(string(renderItem[dotAt+1:endFence])))) + } else { + fmt.Println("other var: ", string(variable[:dotAt])) + if len(renderItem) > 0 { + renderBuffer = append(renderBuffer, renderItem) + variableIndices = append(variableIndices, len(renderBuffer)-1) + } + } + continue + } + + fmt.Println("normal var: ", string(variable[:dotAt])) + if len(renderItem) > 0 { + renderBuffer = append(renderBuffer, renderItem) + } + } + } + } + // TODO: Need more coalescing in the renderBuffer + return renderBuffer, variableIndices +} + +// TODO: Pre-render the lang stuff +func (hold *MenuListHolder) Build(w io.Writer, user *User) error { + var mTmpl menuTmpl + if !user.Loggedin { + mTmpl = hold.Variations[0] + } else if user.IsAdmin { + mTmpl = hold.Variations[3] + } else if user.IsSuperMod { + mTmpl = hold.Variations[2] + } else { + mTmpl = hold.Variations[1] + } + + if len(mTmpl.VariableIndices) == 0 { + fmt.Println("no variable indices") + for _, renderItem := range mTmpl.RenderBuffer { + fmt.Printf("renderItem: %+v\n", renderItem) + w.Write(renderItem) + } + return nil + } + + var nearIndex = 0 + for index, renderItem := range mTmpl.RenderBuffer { + if index != mTmpl.VariableIndices[nearIndex] { + fmt.Println("wrote text: ", string(renderItem)) + w.Write(renderItem) + continue + } + + fmt.Println("variable: ", string(renderItem)) + variable := renderItem + // ? - I can probably remove this check now that I've kicked it upstream, or we could keep it here for safety's sake? + if len(variable) == 0 { + continue + } + prevIndex := 0 + for i := 0; i < len(renderItem); i++ { + fenceStart, hasFence := skipUntilIfExists(variable, i, '{') + if !hasFence { + continue + } + i = fenceStart + fenceEnd, hasFence := skipUntilIfExists(variable, fenceStart, '}') + if !hasFence { + continue + } + i = fenceEnd + dotAt, hasDot := skipUntilIfExists(variable, fenceStart, '.') + if !hasDot { + continue + } + if bytes.Equal(variable[fenceStart:dotAt], []byte("me")) { + fmt.Println("maybe me variable") + w.Write(variable[prevIndex:fenceStart]) + switch string(variable[dotAt:fenceEnd]) { + case "Link": + w.Write([]byte(user.Link)) + case "Session": + w.Write([]byte(user.Session)) + } + prevIndex = fenceEnd + } + } + fmt.Println("prevIndex: ", prevIndex) + fmt.Println("len(variable)-1: ", len(variable)-1) + w.Write(variable[prevIndex : len(variable)-1]) + if len(mTmpl.VariableIndices) > (nearIndex + 1) { + nearIndex++ + } + } + return nil +} diff --git a/common/pages.go b/common/pages.go index 7ca0ed84..55cedb00 100644 --- a/common/pages.go +++ b/common/pages.go @@ -9,7 +9,8 @@ import ( // TODO: Allow resources in spots other than /static/ and possibly even external domains (e.g. CDNs) // TODO: Preload Trumboyg on Cosora on the forum list -type HeaderVars struct { +type Header struct { + Title string NoticeList []string Scripts []string //PreloadScripts []string @@ -20,21 +21,22 @@ type HeaderVars struct { Themes map[string]*Theme // TODO: Use a slice containing every theme instead of the main map for speed? Theme *Theme //TemplateName string // TODO: Use this to move template calls to the router rather than duplicating them over and over and over? - Zone string - MetaDesc string - Writer http.ResponseWriter - ExtData ExtData + CurrentUser User // TODO: Deprecate CurrentUser on the page structs + Zone string + MetaDesc string + Writer http.ResponseWriter + ExtData ExtData } -func (header *HeaderVars) AddScript(name string) { +func (header *Header) AddScript(name string) { header.Scripts = append(header.Scripts, name) } -/*func (header *HeaderVars) PreloadScript(name string) { +/*func (header *Header) PreloadScript(name string) { header.PreloadScripts = append(header.PreloadScripts, name) }*/ -func (header *HeaderVars) AddSheet(name string) { +func (header *Header) AddSheet(name string) { header.Stylesheets = append(header.Stylesheets, name) } @@ -57,18 +59,31 @@ type ExtData struct { sync.RWMutex } +type AlertItem struct { + ASID int + Path string + Message string + Avatar string +} + type Page struct { Title string CurrentUser User - Header *HeaderVars + Header *Header ItemList []interface{} Something interface{} } +type Paginator struct { + PageList []int + Page int + LastPage int +} + type TopicPage struct { Title string CurrentUser User - Header *HeaderVars + Header *Header ItemList []ReplyUser Topic TopicUser Poll Poll @@ -76,40 +91,34 @@ type TopicPage struct { LastPage int } -type TopicsPage struct { - Title string - CurrentUser User - Header *HeaderVars +type TopicListPage struct { + *Header TopicList []*TopicsRow ForumList []Forum DefaultForum int - PageList []int - Page int - LastPage int + Paginator } type ForumPage struct { Title string CurrentUser User - Header *HeaderVars + Header *Header ItemList []*TopicsRow Forum *Forum - PageList []int - Page int - LastPage int + Paginator } type ForumsPage struct { Title string CurrentUser User - Header *HeaderVars + Header *Header ItemList []Forum } type ProfilePage struct { Title string CurrentUser User - Header *HeaderVars + Header *Header ItemList []ReplyUser ProfileOwner User } @@ -117,7 +126,7 @@ type ProfilePage struct { type CreateTopicPage struct { Title string CurrentUser User - Header *HeaderVars + Header *Header ItemList []Forum FID int } @@ -125,7 +134,7 @@ type CreateTopicPage struct { type IPSearchPage struct { Title string CurrentUser User - Header *HeaderVars + Header *Header ItemList map[int]*User IP string } @@ -143,7 +152,7 @@ type PanelStats struct { type PanelPage struct { Title string CurrentUser User - Header *HeaderVars + Header *Header Stats PanelStats Zone string ItemList []interface{} @@ -163,7 +172,7 @@ type GridElement struct { type PanelDashboardPage struct { Title string CurrentUser User - Header *HeaderVars + Header *Header Stats PanelStats Zone string GridItems []GridElement @@ -182,7 +191,7 @@ type PanelAnalyticsItem struct { type PanelAnalyticsPage struct { Title string CurrentUser User - Header *HeaderVars + Header *Header Stats PanelStats Zone string PrimaryGraph PanelTimeGraph @@ -198,7 +207,7 @@ type PanelAnalyticsRoutesItem struct { type PanelAnalyticsRoutesPage struct { Title string CurrentUser User - Header *HeaderVars + Header *Header Stats PanelStats Zone string ItemList []PanelAnalyticsRoutesItem @@ -214,7 +223,7 @@ type PanelAnalyticsAgentsItem struct { type PanelAnalyticsAgentsPage struct { Title string CurrentUser User - Header *HeaderVars + Header *Header Stats PanelStats Zone string ItemList []PanelAnalyticsAgentsItem @@ -224,7 +233,7 @@ type PanelAnalyticsAgentsPage struct { type PanelAnalyticsRoutePage struct { Title string CurrentUser User - Header *HeaderVars + Header *Header Stats PanelStats Zone string Route string @@ -236,7 +245,7 @@ type PanelAnalyticsRoutePage struct { type PanelAnalyticsAgentPage struct { Title string CurrentUser User - Header *HeaderVars + Header *Header Stats PanelStats Zone string Agent string @@ -248,7 +257,7 @@ type PanelAnalyticsAgentPage struct { type PanelThemesPage struct { Title string CurrentUser User - Header *HeaderVars + Header *Header Stats PanelStats Zone string PrimaryThemes []*Theme @@ -258,31 +267,27 @@ type PanelThemesPage struct { type PanelUserPage struct { Title string CurrentUser User - Header *HeaderVars + Header *Header Stats PanelStats Zone string ItemList []User - PageList []int - Page int - LastPage int + Paginator } type PanelGroupPage struct { Title string CurrentUser User - Header *HeaderVars + Header *Header Stats PanelStats Zone string ItemList []GroupAdmin - PageList []int - Page int - LastPage int + Paginator } type PanelEditGroupPage struct { Title string CurrentUser User - Header *HeaderVars + Header *Header Stats PanelStats Zone string ID int @@ -300,7 +305,7 @@ type GroupForumPermPreset struct { type PanelEditForumPage struct { Title string CurrentUser User - Header *HeaderVars + Header *Header Stats PanelStats Zone string ID int @@ -320,7 +325,7 @@ type NameLangToggle struct { type PanelEditForumGroupPage struct { Title string CurrentUser User - Header *HeaderVars + Header *Header Stats PanelStats Zone string ForumID int @@ -335,7 +340,7 @@ type PanelEditForumGroupPage struct { type PanelEditGroupPermsPage struct { Title string CurrentUser User - Header *HeaderVars + Header *Header Stats PanelStats Zone string ID int @@ -355,7 +360,7 @@ type BackupItem struct { type PanelBackupPage struct { Title string CurrentUser User - Header *HeaderVars + Header *Header Stats PanelStats Zone string Backups []BackupItem @@ -370,21 +375,21 @@ type LogItem struct { type PanelLogsPage struct { Title string CurrentUser User - Header *HeaderVars + Header *Header Stats PanelStats Zone string Logs []LogItem - PageList []int - Page int - LastPage int + Paginator } type PanelDebugPage struct { Title string CurrentUser User - Header *HeaderVars + Header *Header Stats PanelStats Zone string + GoVersion string + DBVersion string Uptime string OpenConns int DBAdapter string @@ -400,8 +405,7 @@ type AreYouSure struct { Message string } -// This is mostly for errors.go, please create *HeaderVars on the spot instead of relying on this or the atomic store underlying it, if possible // TODO: Write a test for this -func DefaultHeaderVar() *HeaderVars { - return &HeaderVars{Site: Site, Theme: Themes[fallbackTheme]} +func DefaultHeader(w http.ResponseWriter) *Header { + return &Header{Site: Site, Theme: Themes[fallbackTheme], CurrentUser: GuestUser, Writer: w} } diff --git a/common/routes_common.go b/common/routes_common.go index e20fa4df..178d5693 100644 --- a/common/routes_common.go +++ b/common/routes_common.go @@ -14,13 +14,13 @@ var PreRoute func(http.ResponseWriter, *http.Request) (User, bool) = preRoute // TODO: Come up with a better middleware solution // nolint We need these types so people can tell what they are without scrolling to the bottom of the file -var PanelUserCheck func(http.ResponseWriter, *http.Request, *User) (*HeaderVars, PanelStats, RouteError) = panelUserCheck +var PanelUserCheck func(http.ResponseWriter, *http.Request, *User) (*Header, PanelStats, RouteError) = panelUserCheck var SimplePanelUserCheck func(http.ResponseWriter, *http.Request, *User) (*HeaderLite, RouteError) = simplePanelUserCheck var SimpleForumUserCheck func(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerLite *HeaderLite, err RouteError) = simpleForumUserCheck -var ForumUserCheck func(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerVars *HeaderVars, err RouteError) = forumUserCheck -var MemberCheck func(w http.ResponseWriter, r *http.Request, user *User) (headerVars *HeaderVars, err RouteError) = memberCheck +var ForumUserCheck func(w http.ResponseWriter, r *http.Request, user *User, fid int) (header *Header, err RouteError) = forumUserCheck +var MemberCheck func(w http.ResponseWriter, r *http.Request, user *User) (header *Header, err RouteError) = memberCheck var SimpleUserCheck func(w http.ResponseWriter, r *http.Request, user *User) (headerLite *HeaderLite, err RouteError) = simpleUserCheck -var UserCheck func(w http.ResponseWriter, r *http.Request, user *User) (headerVars *HeaderVars, err RouteError) = userCheck +var UserCheck func(w http.ResponseWriter, r *http.Request, user *User) (header *Header, err RouteError) = userCheck func simpleForumUserCheck(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerLite *HeaderLite, rerr RouteError) { if !Forums.Exists(fid) { @@ -46,20 +46,20 @@ func simpleForumUserCheck(w http.ResponseWriter, r *http.Request, user *User, fi return headerLite, nil } -func forumUserCheck(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerVars *HeaderVars, rerr RouteError) { - headerVars, rerr = UserCheck(w, r, user) +func forumUserCheck(w http.ResponseWriter, r *http.Request, user *User, fid int) (header *Header, rerr RouteError) { + header, rerr = UserCheck(w, r, user) if rerr != nil { - return headerVars, rerr + return header, rerr } if !Forums.Exists(fid) { - return headerVars, NotFound(w, r, headerVars) + return header, NotFound(w, r, header) } if VhookSkippable["forum_check_pre_perms"] != nil { var skip bool - skip, rerr = RunVhookSkippable("forum_check_pre_perms", w, r, user, &fid, &headerVars) + skip, rerr = RunVhookSkippable("forum_check_pre_perms", w, r, user, &fid, &header) if skip || rerr != nil { - return headerVars, rerr + return header, rerr } } @@ -70,7 +70,7 @@ func forumUserCheck(w http.ResponseWriter, r *http.Request, user *User, fid int) return nil, PreError("Something weird happened", w, r) } cascadeForumPerms(fperms, user) - return headerVars, rerr + return header, rerr } // TODO: Put this on the user instance? Do we really want forum specific logic in there? Maybe, a method which spits a new pointer with the same contents as user? @@ -98,7 +98,7 @@ func cascadeForumPerms(fperms *ForumPerms, user *User) { // Even if they have the right permissions, the control panel is only open to supermods+. There are many areas without subpermissions which assume that the current user is a supermod+ and admins are extremely unlikely to give these permissions to someone who isn't at-least a supermod to begin with // TODO: Do a panel specific theme? -func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (headerVars *HeaderVars, stats PanelStats, rerr RouteError) { +func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (header *Header, stats PanelStats, rerr RouteError) { var theme = &Theme{Name: ""} cookie, err := r.Cookie("current_theme") @@ -112,17 +112,18 @@ func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (headerV theme = Themes[DefaultThemeBox.Load().(string)] } - headerVars = &HeaderVars{ - Site: Site, - Settings: SettingBox.Load().(SettingMap), - Themes: Themes, - Theme: theme, - Zone: "panel", - Writer: w, + header = &Header{ + Site: Site, + Settings: SettingBox.Load().(SettingMap), + Themes: Themes, + Theme: theme, + CurrentUser: *user, + Zone: "panel", + Writer: w, } - // TODO: We should probably initialise headerVars.ExtData + // TODO: We should probably initialise header.ExtData - headerVars.AddSheet(theme.Name + "/panel.css") + header.AddSheet(theme.Name + "/panel.css") if len(theme.Resources) > 0 { rlist := theme.Resources for _, resource := range rlist { @@ -130,9 +131,9 @@ func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (headerV extarr := strings.Split(resource.Name, ".") ext := extarr[len(extarr)-1] if ext == "css" { - headerVars.AddSheet(resource.Name) + header.AddSheet(resource.Name) } else if ext == "js" { - headerVars.AddScript(resource.Name) + header.AddScript(resource.Name) } } } @@ -141,7 +142,7 @@ func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (headerV stats.Users = Users.GlobalCount() stats.Groups = Groups.GlobalCount() stats.Forums = Forums.GlobalCount() // TODO: Stop it from showing the blanked forums - stats.Settings = len(headerVars.Settings) + stats.Settings = len(header.Settings) stats.WordFilters = len(WordFilterBox.Load().(WordFilterMap)) stats.Themes = len(Themes) stats.Reports = 0 // TODO: Do the report count. Only show open threads? @@ -153,16 +154,16 @@ func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (headerV pusher.Push("/static/global.js", nil) pusher.Push("/static/jquery-3.1.1.min.js", nil) // TODO: Test these - for _, sheet := range headerVars.Stylesheets { + for _, sheet := range header.Stylesheets { pusher.Push("/static/"+sheet, nil) } - for _, script := range headerVars.Scripts { + for _, script := range header.Scripts { pusher.Push("/static/"+script, nil) } // TODO: Push avatars? } - return headerVars, stats, nil + return header, stats, nil } func simplePanelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (headerLite *HeaderLite, rerr RouteError) { @@ -173,12 +174,12 @@ func simplePanelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (h } // TODO: Add this to the member routes -func memberCheck(w http.ResponseWriter, r *http.Request, user *User) (headerVars *HeaderVars, rerr RouteError) { - headerVars, rerr = UserCheck(w, r, user) +func memberCheck(w http.ResponseWriter, r *http.Request, user *User) (header *Header, rerr RouteError) { + header, rerr = UserCheck(w, r, user) if !user.Loggedin { - return headerVars, NoPermissions(w, r, *user) + return header, NoPermissions(w, r, *user) } - return headerVars, rerr + return header, rerr } // SimpleUserCheck is back from the grave, yay :D @@ -191,7 +192,7 @@ func simpleUserCheck(w http.ResponseWriter, r *http.Request, user *User) (header } // TODO: Add the ability for admins to restrict certain themes to certain groups? -func userCheck(w http.ResponseWriter, r *http.Request, user *User) (headerVars *HeaderVars, rerr RouteError) { +func userCheck(w http.ResponseWriter, r *http.Request, user *User) (header *Header, rerr RouteError) { var theme = &Theme{Name: ""} cookie, err := r.Cookie("current_theme") @@ -205,20 +206,21 @@ func userCheck(w http.ResponseWriter, r *http.Request, user *User) (headerVars * theme = Themes[DefaultThemeBox.Load().(string)] } - headerVars = &HeaderVars{ - Site: Site, - Settings: SettingBox.Load().(SettingMap), - Themes: Themes, - Theme: theme, - Zone: "frontend", - Writer: w, + header = &Header{ + Site: Site, + Settings: SettingBox.Load().(SettingMap), + Themes: Themes, + Theme: theme, + CurrentUser: *user, + Zone: "frontend", + Writer: w, } if user.IsBanned { - headerVars.NoticeList = append(headerVars.NoticeList, GetNoticePhrase("account_banned")) + header.NoticeList = append(header.NoticeList, GetNoticePhrase("account_banned")) } if user.Loggedin && !user.Active { - headerVars.NoticeList = append(headerVars.NoticeList, GetNoticePhrase("account_inactive")) + header.NoticeList = append(header.NoticeList, GetNoticePhrase("account_inactive")) } if len(theme.Resources) > 0 { @@ -231,9 +233,9 @@ func userCheck(w http.ResponseWriter, r *http.Request, user *User) (headerVars * extarr := strings.Split(resource.Name, ".") ext := extarr[len(extarr)-1] if ext == "css" { - headerVars.AddSheet(resource.Name) + header.AddSheet(resource.Name) } else if ext == "js" { - headerVars.AddScript(resource.Name) + header.AddScript(resource.Name) } } } @@ -245,16 +247,16 @@ func userCheck(w http.ResponseWriter, r *http.Request, user *User) (headerVars * pusher.Push("/static/global.js", nil) pusher.Push("/static/jquery-3.1.1.min.js", nil) // TODO: Test these - for _, sheet := range headerVars.Stylesheets { + for _, sheet := range header.Stylesheets { pusher.Push("/static/"+sheet, nil) } - for _, script := range headerVars.Scripts { + for _, script := range header.Scripts { pusher.Push("/static/"+script, nil) } // TODO: Push avatars? } - return headerVars, nil + return header, nil } func preRoute(w http.ResponseWriter, r *http.Request) (User, bool) { diff --git a/common/template_init.go b/common/template_init.go index 89eb020e..fe46407d 100644 --- a/common/template_init.go +++ b/common/template_init.go @@ -2,8 +2,8 @@ package common import ( "html/template" + "io" "log" - "net/http" "path/filepath" "strconv" "strings" @@ -15,7 +15,7 @@ import ( var Ctemplates []string var Templates = template.New("") -var PrebuildTmplList []func(User, *HeaderVars) CTmpl +var PrebuildTmplList []func(User, *Header) CTmpl type CTmpl struct { Name string @@ -28,7 +28,7 @@ type CTmpl struct { // TODO: Stop duplicating these bits of code // nolint -func interpreted_topic_template(pi TopicPage, w http.ResponseWriter) error { +func interpretedTopicTemplate(pi TopicPage, w io.Writer) error { mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["topic"] if !ok { mapping = "topic" @@ -37,11 +37,11 @@ func interpreted_topic_template(pi TopicPage, w http.ResponseWriter) error { } // nolint -var Template_topic_handle = interpreted_topic_template -var Template_topic_alt_handle = interpreted_topic_template +var Template_topic_handle = interpretedTopicTemplate +var Template_topic_alt_handle = interpretedTopicTemplate // nolint -var Template_topics_handle = func(pi TopicsPage, w http.ResponseWriter) error { +var Template_topics_handle = func(pi TopicListPage, w io.Writer) error { mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["topics"] if !ok { mapping = "topics" @@ -50,7 +50,7 @@ var Template_topics_handle = func(pi TopicsPage, w http.ResponseWriter) error { } // nolint -var Template_forum_handle = func(pi ForumPage, w http.ResponseWriter) error { +var Template_forum_handle = func(pi ForumPage, w io.Writer) error { mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["forum"] if !ok { mapping = "forum" @@ -59,7 +59,7 @@ var Template_forum_handle = func(pi ForumPage, w http.ResponseWriter) error { } // nolint -var Template_forums_handle = func(pi ForumsPage, w http.ResponseWriter) error { +var Template_forums_handle = func(pi ForumsPage, w io.Writer) error { mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["forums"] if !ok { mapping = "forums" @@ -68,7 +68,7 @@ var Template_forums_handle = func(pi ForumsPage, w http.ResponseWriter) error { } // nolint -var Template_profile_handle = func(pi ProfilePage, w http.ResponseWriter) error { +var Template_profile_handle = func(pi ProfilePage, w io.Writer) error { mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["profile"] if !ok { mapping = "profile" @@ -77,7 +77,7 @@ var Template_profile_handle = func(pi ProfilePage, w http.ResponseWriter) error } // nolint -var Template_create_topic_handle = func(pi CreateTopicPage, w http.ResponseWriter) error { +var Template_create_topic_handle = func(pi CreateTopicPage, w io.Writer) error { mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["create_topic"] if !ok { mapping = "create_topic" @@ -86,7 +86,7 @@ var Template_create_topic_handle = func(pi CreateTopicPage, w http.ResponseWrite } // nolint -var Template_login_handle = func(pi Page, w http.ResponseWriter) error { +var Template_login_handle = func(pi Page, w io.Writer) error { mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["login"] if !ok { mapping = "login" @@ -95,7 +95,7 @@ var Template_login_handle = func(pi Page, w http.ResponseWriter) error { } // nolint -var Template_register_handle = func(pi Page, w http.ResponseWriter) error { +var Template_register_handle = func(pi Page, w io.Writer) error { mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["register"] if !ok { mapping = "register" @@ -104,7 +104,7 @@ var Template_register_handle = func(pi Page, w http.ResponseWriter) error { } // nolint -var Template_error_handle = func(pi Page, w http.ResponseWriter) error { +var Template_error_handle = func(pi Page, w io.Writer) error { mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["error"] if !ok { mapping = "error" @@ -113,7 +113,7 @@ var Template_error_handle = func(pi Page, w http.ResponseWriter) error { } // nolint -var Template_ip_search_handle = func(pi IPSearchPage, w http.ResponseWriter) error { +var Template_ip_search_handle = func(pi IPSearchPage, w io.Writer) error { mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["ip_search"] if !ok { mapping = "ip_search" @@ -127,8 +127,12 @@ func CompileTemplates() error { config.Minify = Config.MinifyTemplates config.SuperDebug = Dev.TemplateDebug - var c tmpl.CTemplateSet + c := tmpl.NewCTemplateSet() c.SetConfig(config) + c.SetBaseImportMap(map[string]string{ + "io": "io", + "./common": "./common", + }) // Schemas to train the template compiler on what to expect // TODO: Add support for interface{}s @@ -136,11 +140,12 @@ func CompileTemplates() error { // TODO: Do a more accurate level calculation for this? user2 := User{1, BuildProfileURL("admin-alice", 1), "Admin Alice", "alice@localhost", 1, true, true, true, true, false, false, AllPerms, make(map[string]bool), "", true, BuildAvatar(1, ""), "", "", "", "", 58, 1000, 0, "127.0.0.1", 0} user3 := User{2, BuildProfileURL("admin-fred", 62), "Admin Fred", "fred@localhost", 1, true, true, true, true, false, false, AllPerms, make(map[string]bool), "", true, BuildAvatar(2, ""), "", "", "", "", 42, 900, 0, "::1", 0} - headerVars := &HeaderVars{ + header := &Header{ Site: Site, Settings: SettingBox.Load().(SettingMap), Themes: Themes, Theme: Themes[DefaultThemeBox.Load().(string)], + CurrentUser: user, NoticeList: []string{"test"}, Stylesheets: []string{"panel"}, Scripts: []string{"whatever"}, @@ -161,7 +166,7 @@ func CompileTemplates() error { replyList = append(replyList, ReplyUser{0, 0, "Yo!", "Yo!", 0, "alice", "Alice", Config.DefaultGroup, now, RelativeTime(now), 0, 0, "", "", 0, "", "", "", "", 0, "127.0.0.1", false, 1, "", ""}) var varList = make(map[string]tmpl.VarItem) - tpage := TopicPage{"Title", user, headerVars, replyList, topic, poll, 1, 1} + tpage := TopicPage{"Title", user, header, replyList, topic, poll, 1, 1} topicIDTmpl, err := c.Compile("topic.html", "templates/", "common.TopicPage", tpage, varList) if err != nil { return err @@ -172,7 +177,7 @@ func CompileTemplates() error { } varList = make(map[string]tmpl.VarItem) - ppage := ProfilePage{"User 526", user, headerVars, replyList, user} + ppage := ProfilePage{"User 526", user, header, replyList, user} profileTmpl, err := c.Compile("profile.html", "templates/", "common.ProfilePage", ppage, varList) if err != nil { return err @@ -189,7 +194,7 @@ func CompileTemplates() error { forumList = append(forumList, *forum) } varList = make(map[string]tmpl.VarItem) - forumsPage := ForumsPage{"Forum List", user, headerVars, forumList} + forumsPage := ForumsPage{"Forum List", user, header, forumList} forumsTmpl, err := c.Compile("forums.html", "templates/", "common.ForumsPage", forumsPage, varList) if err != nil { return err @@ -197,8 +202,9 @@ func CompileTemplates() error { var topicsList []*TopicsRow topicsList = append(topicsList, &TopicsRow{1, "topic-title", "Topic Title", "The topic content.", 1, false, false, "Date", time.Now(), "Date", user3.ID, 1, "", "127.0.0.1", 0, 1, "classname", "", &user2, "", 0, &user3, "General", "/forum/general.2"}) - topicsPage := TopicsPage{"Topic List", user, headerVars, topicsList, forumList, Config.DefaultForum, []int{1}, 1, 1} - topicsTmpl, err := c.Compile("topics.html", "templates/", "common.TopicsPage", topicsPage, varList) + header.Title = "Topic List" + topicListPage := TopicListPage{header, topicsList, forumList, Config.DefaultForum, Paginator{[]int{1}, 1, 1}} + topicListTmpl, err := c.Compile("topics.html", "templates/", "common.TopicListPage", topicListPage, varList) if err != nil { return err } @@ -206,25 +212,25 @@ func CompileTemplates() error { //var topicList []TopicUser //topicList = append(topicList,TopicUser{1,"topic-title","Topic Title","The topic content.",1,false,false,"Date","Date",1,"","127.0.0.1",0,1,"classname","","admin-fred","Admin Fred",config.DefaultGroup,"",0,"","","","",58,false}) forumItem := BlankForum(1, "general-forum.1", "General Forum", "Where the general stuff happens", true, "all", 0, "", 0) - forumPage := ForumPage{"General Forum", user, headerVars, topicsList, forumItem, []int{1}, 1, 1} + forumPage := ForumPage{"General Forum", user, header, topicsList, forumItem, Paginator{[]int{1}, 1, 1}} forumTmpl, err := c.Compile("forum.html", "templates/", "common.ForumPage", forumPage, varList) if err != nil { return err } - loginPage := Page{"Login Page", user, headerVars, tList, nil} + loginPage := Page{"Login Page", user, header, tList, nil} loginTmpl, err := c.Compile("login.html", "templates/", "common.Page", loginPage, varList) if err != nil { return err } - registerPage := Page{"Registration Page", user, headerVars, tList, nil} + registerPage := Page{"Registration Page", user, header, tList, nil} registerTmpl, err := c.Compile("register.html", "templates/", "common.Page", registerPage, varList) if err != nil { return err } - errorPage := Page{"Error", user, headerVars, tList, "A problem has occurred in the system."} + errorPage := Page{"Error", user, header, tList, "A problem has occurred in the system."} errorTmpl, err := c.Compile("error.html", "templates/", "common.Page", errorPage, varList) if err != nil { return err @@ -232,7 +238,7 @@ func CompileTemplates() error { var ipUserList = make(map[int]*User) ipUserList[1] = &user2 - ipSearchPage := IPSearchPage{"IP Search", user2, headerVars, ipUserList, "::1"} + ipSearchPage := IPSearchPage{"IP Search", user2, header, ipUserList, "::1"} ipSearchTmpl, err := c.Compile("ip_search.html", "templates/", "common.IPSearchPage", ipSearchPage, varList) if err != nil { return err @@ -261,7 +267,7 @@ func CompileTemplates() error { config.SkipHandles = true c.SetConfig(config) for _, tmplfunc := range PrebuildTmplList { - tmplItem := tmplfunc(user, headerVars) + tmplItem := tmplfunc(user, header) varList = make(map[string]tmpl.VarItem) compiledTmpl, err := c.Compile(tmplItem.Filename, tmplItem.Path, tmplItem.StructName, tmplItem.Data, varList, tmplItem.Imports...) if err != nil { @@ -275,94 +281,132 @@ func CompileTemplates() error { writeTemplate("topic_alt", topicIDAltTmpl) writeTemplate("profile", profileTmpl) writeTemplate("forums", forumsTmpl) - writeTemplate("topics", topicsTmpl) + writeTemplate("topics", topicListTmpl) writeTemplate("forum", forumTmpl) writeTemplate("login", loginTmpl) writeTemplate("register", registerTmpl) writeTemplate("ip_search", ipSearchTmpl) writeTemplate("error", errorTmpl) + writeTemplateList(c, &wg, "./") + return nil +} +func CompileJSTemplates() error { + log.Print("Compiling the JS templates") + var config tmpl.CTemplateConfig + config.Minify = Config.MinifyTemplates + config.SuperDebug = Dev.TemplateDebug + config.SkipHandles = true + config.PackageName = "tmpl" + + c := tmpl.NewCTemplateSet() + c.SetConfig(config) + c.SetBaseImportMap(map[string]string{ + "io": "io", + "../../common": "../../common", + }) + var varList = make(map[string]tmpl.VarItem) + + // TODO: Check what sort of path is sent exactly and use it here + alertItem := AlertItem{Avatar: "", ASID: 1, Path: "/", Message: "uh oh, something happened"} + alertTmpl, err := c.Compile("alert.html", "templates/", "common.AlertItem", alertItem, varList) + if err != nil { + return err + } + + var dirPrefix = "./tmpl_gen/client/" + var wg sync.WaitGroup + var writeTemplate = func(name string, content string) { + log.Print("Writing template '" + name + "'") + if content == "" { + log.Fatal("No content body") + } + + wg.Add(1) + go func() { + err := writeFile(dirPrefix+"template_"+name+".go", content) + if err != nil { + log.Fatal(err) + } + wg.Done() + }() + } + writeTemplate("alert", alertTmpl) + writeTemplateList(c, &wg, dirPrefix) + return nil +} + +func writeTemplateList(c *tmpl.CTemplateSet, wg *sync.WaitGroup, prefix string) { + log.Print("Writing template list") wg.Add(1) go func() { - out := "package main\n\n" + out := "package " + c.GetConfig().PackageName + "\n\n" for templateName, count := range c.TemplateFragmentCount { out += "var " + templateName + "_frags = make([][]byte," + strconv.Itoa(count) + ")\n" } out += "\n// nolint\nfunc init() {\n" + c.FragOut + "}\n" - err := writeFile("./template_list.go", out) + err := writeFile(prefix+"template_list.go", out) if err != nil { log.Fatal(err) } wg.Done() }() - wg.Wait() - return nil +} + +func arithToInt64(in interface{}) (out int64) { + switch in := in.(type) { + case int64: + out = in + case int32: + out = int64(in) + case int: + out = int64(in) + case uint32: + out = int64(in) + case uint16: + out = int64(in) + case uint8: + out = int64(in) + case uint: + out = int64(in) + } + return out +} + +func arithDuoToInt64(left interface{}, right interface{}) (leftInt int64, rightInt int64) { + return arithToInt64(left), arithToInt64(right) } func InitTemplates() error { DebugLog("Initialising the template system") - - // TODO: Add support for 64-bit integers // TODO: Add support for floats fmap := make(map[string]interface{}) fmap["add"] = func(left interface{}, right interface{}) interface{} { - var leftInt, rightInt int - switch left := left.(type) { - case uint, uint8, uint16, int, int32: - leftInt = left.(int) - } - switch right := right.(type) { - case uint, uint8, uint16, int, int32: - rightInt = right.(int) - } + leftInt, rightInt := arithDuoToInt64(left, right) return leftInt + rightInt } fmap["subtract"] = func(left interface{}, right interface{}) interface{} { - var leftInt, rightInt int - switch left := left.(type) { - case uint, uint8, uint16, int, int32: - leftInt = left.(int) - } - switch right := right.(type) { - case uint, uint8, uint16, int, int32: - rightInt = right.(int) - } + leftInt, rightInt := arithDuoToInt64(left, right) return leftInt - rightInt } fmap["multiply"] = func(left interface{}, right interface{}) interface{} { - var leftInt, rightInt int - switch left := left.(type) { - case uint, uint8, uint16, int, int32: - leftInt = left.(int) - } - switch right := right.(type) { - case uint, uint8, uint16, int, int32: - rightInt = right.(int) - } + leftInt, rightInt := arithDuoToInt64(left, right) return leftInt * rightInt } fmap["divide"] = func(left interface{}, right interface{}) interface{} { - var leftInt, rightInt int - switch left := left.(type) { - case uint, uint8, uint16, int, int32: - leftInt = left.(int) - } - switch right := right.(type) { - case uint, uint8, uint16, int, int32: - rightInt = right.(int) - } + leftInt, rightInt := arithDuoToInt64(left, right) if leftInt == 0 || rightInt == 0 { return 0 } return leftInt / rightInt } - fmap["dock"] = func(dock interface{}, headerVarInt interface{}) interface{} { - return template.HTML(BuildWidget(dock.(string), headerVarInt.(*HeaderVars))) + fmap["dock"] = func(dock interface{}, headerInt interface{}) interface{} { + return template.HTML(BuildWidget(dock.(string), headerInt.(*Header))) } fmap["lang"] = func(phraseNameInt interface{}) interface{} { diff --git a/common/templates/templates.go b/common/templates/templates.go index 07434510..54c11a33 100644 --- a/common/templates/templates.go +++ b/common/templates/templates.go @@ -28,10 +28,12 @@ type VarItemReflect struct { } type CTemplateConfig struct { - Minify bool - Debug bool - SuperDebug bool - SkipHandles bool + Minify bool + Debug bool + SuperDebug bool + SkipHandles bool + SkipInitBlock bool + PackageName string } // nolint @@ -54,12 +56,42 @@ type CTemplateSet struct { currentNode parse.NodeType nextNode parse.NodeType //tempVars map[string]string - config CTemplateConfig - doImports bool - expectsInt interface{} + config CTemplateConfig + baseImportMap map[string]string + expectsInt interface{} +} + +func NewCTemplateSet() *CTemplateSet { + return &CTemplateSet{ + config: CTemplateConfig{ + PackageName: "main", + }, + baseImportMap: map[string]string{}, + funcMap: map[string]interface{}{ + "and": "&&", + "not": "!", + "or": "||", + "eq": true, + "ge": true, + "gt": true, + "le": true, + "lt": true, + "ne": true, + "add": true, + "subtract": true, + "multiply": true, + "divide": true, + "dock": true, + "lang": true, + "scope": true, + }, + } } func (c *CTemplateSet) SetConfig(config CTemplateConfig) { + if config.PackageName == "" { + config.PackageName = "main" + } c.config = config } @@ -67,35 +99,18 @@ func (c *CTemplateSet) GetConfig() CTemplateConfig { return c.config } +func (c *CTemplateSet) SetBaseImportMap(importMap map[string]string) { + c.baseImportMap = importMap +} + func (c *CTemplateSet) Compile(name string, fileDir string, expects string, expectsInt interface{}, varList map[string]VarItem, imports ...string) (out string, err error) { if c.config.Debug { fmt.Println("Compiling template '" + name + "'") } - c.fileDir = fileDir - c.doImports = true - c.funcMap = map[string]interface{}{ - "and": "&&", - "not": "!", - "or": "||", - "eq": true, - "ge": true, - "gt": true, - "le": true, - "lt": true, - "ne": true, - "add": true, - "subtract": true, - "multiply": true, - "divide": true, - "dock": true, - "lang": true, - "scope": true, - } - - c.importMap = map[string]string{ - "net/http": "net/http", - "./common": "./common", + c.importMap = map[string]string{} + for index, item := range c.baseImportMap { + c.importMap[index] = item } if len(imports) > 0 { for _, importItem := range imports { @@ -103,6 +118,7 @@ func (c *CTemplateSet) Compile(name string, fileDir string, expects string, expe } } + c.fileDir = fileDir c.varList = varList c.hasDispInt = false c.localDispStructIndex = 0 @@ -157,10 +173,8 @@ func (c *CTemplateSet) Compile(name string, fileDir string, expects string, expe c.TemplateFragmentCount[fname] = c.fragmentCursor[fname] + 1 var importList string - if c.doImports { - for _, item := range c.importMap { - importList += "import \"" + item + "\"\n" - } + for _, item := range c.importMap { + importList += "import \"" + item + "\"\n" } var varString string @@ -169,28 +183,32 @@ func (c *CTemplateSet) Compile(name string, fileDir string, expects string, expe } fout := "// +build !no_templategen\n\n// Code generated by Gosora. More below:\n/* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */\n" + fout += "package " + c.config.PackageName + "\n" + importList + "\n" - fout += "package main\n" + importList + "\n" - fout += "var " + fname + "_tmpl_phrase_id int\n\n" - fout += "// nolint\nfunc init() {\n" - - if !c.config.SkipHandles { - fout += "\tcommon.Template_" + fname + "_handle = Template_" + fname + "\n" - - fout += "\tcommon.Ctemplates = append(common.Ctemplates,\"" + fname + "\")\n\tcommon.TmplPtrMap[\"" + fname + "\"] = &common.Template_" + fname + "_handle\n" - } - - fout += "\tcommon.TmplPtrMap[\"o_" + fname + "\"] = Template_" + fname + "\n" - if len(c.langIndexToName) > 0 { - fout += "\t" + fname + "_tmpl_phrase_id = common.RegisterTmplPhraseNames([]string{\n" - for _, name := range c.langIndexToName { - fout += "\t\t" + `"` + name + `"` + ",\n" + if !c.config.SkipInitBlock { + if len(c.langIndexToName) > 0 { + fout += "var " + fname + "_tmpl_phrase_id int\n\n" } - fout += "\t})\n" - } - fout += "}\n\n" + fout += "// nolint\nfunc init() {\n" - fout += "// nolint\nfunc Template_" + fname + "(tmpl_" + fname + "_vars " + expects + ", w http.ResponseWriter) error {\n" + if !c.config.SkipHandles { + fout += "\tcommon.Template_" + fname + "_handle = Template_" + fname + "\n" + + fout += "\tcommon.Ctemplates = append(common.Ctemplates,\"" + fname + "\")\n\tcommon.TmplPtrMap[\"" + fname + "\"] = &common.Template_" + fname + "_handle\n" + } + + fout += "\tcommon.TmplPtrMap[\"o_" + fname + "\"] = Template_" + fname + "\n" + if len(c.langIndexToName) > 0 { + fout += "\t" + fname + "_tmpl_phrase_id = common.RegisterTmplPhraseNames([]string{\n" + for _, name := range c.langIndexToName { + fout += "\t\t" + `"` + name + `"` + ",\n" + } + fout += "\t})\n" + } + fout += "}\n\n" + } + + fout += "// nolint\nfunc Template_" + fname + "(tmpl_" + fname + "_vars " + expects + ", w io.Writer) error {\n" if len(c.langIndexToName) > 0 { fout += "var phrases = common.GetTmplPhrasesBytes(" + fname + "_tmpl_phrase_id)\n" } diff --git a/common/themes.go b/common/themes.go index ae3d4563..00591bcb 100644 --- a/common/themes.go +++ b/common/themes.go @@ -7,6 +7,7 @@ import ( "database/sql" "encoding/json" "errors" + "io" "io/ioutil" "log" "mime" @@ -271,72 +272,72 @@ func (theme *Theme) MapTemplates() { } switch dTmplPtr := destTmplPtr.(type) { - case *func(TopicPage, http.ResponseWriter) error: + case *func(TopicPage, io.Writer) error: switch sTmplPtr := sourceTmplPtr.(type) { - case *func(TopicPage, http.ResponseWriter) error: + case *func(TopicPage, io.Writer) error: //overridenTemplates[themeTmpl.Name] = d_tmpl_ptr overridenTemplates[themeTmpl.Name] = true *dTmplPtr = *sTmplPtr default: LogError(errors.New("The source and destination templates are incompatible")) } - case *func(TopicsPage, http.ResponseWriter) error: + case *func(TopicListPage, io.Writer) error: switch sTmplPtr := sourceTmplPtr.(type) { - case *func(TopicsPage, http.ResponseWriter) error: + case *func(TopicListPage, io.Writer) error: //overridenTemplates[themeTmpl.Name] = d_tmpl_ptr overridenTemplates[themeTmpl.Name] = true *dTmplPtr = *sTmplPtr default: LogError(errors.New("The source and destination templates are incompatible")) } - case *func(ForumPage, http.ResponseWriter) error: + case *func(ForumPage, io.Writer) error: switch sTmplPtr := sourceTmplPtr.(type) { - case *func(ForumPage, http.ResponseWriter) error: + case *func(ForumPage, io.Writer) error: //overridenTemplates[themeTmpl.Name] = d_tmpl_ptr overridenTemplates[themeTmpl.Name] = true *dTmplPtr = *sTmplPtr default: LogError(errors.New("The source and destination templates are incompatible")) } - case *func(ForumsPage, http.ResponseWriter) error: + case *func(ForumsPage, io.Writer) error: switch sTmplPtr := sourceTmplPtr.(type) { - case *func(ForumsPage, http.ResponseWriter) error: + case *func(ForumsPage, io.Writer) error: //overridenTemplates[themeTmpl.Name] = d_tmpl_ptr overridenTemplates[themeTmpl.Name] = true *dTmplPtr = *sTmplPtr default: LogError(errors.New("The source and destination templates are incompatible")) } - case *func(ProfilePage, http.ResponseWriter) error: + case *func(ProfilePage, io.Writer) error: switch sTmplPtr := sourceTmplPtr.(type) { - case *func(ProfilePage, http.ResponseWriter) error: + case *func(ProfilePage, io.Writer) error: //overridenTemplates[themeTmpl.Name] = d_tmpl_ptr overridenTemplates[themeTmpl.Name] = true *dTmplPtr = *sTmplPtr default: LogError(errors.New("The source and destination templates are incompatible")) } - case *func(CreateTopicPage, http.ResponseWriter) error: + case *func(CreateTopicPage, io.Writer) error: switch sTmplPtr := sourceTmplPtr.(type) { - case *func(CreateTopicPage, http.ResponseWriter) error: + case *func(CreateTopicPage, io.Writer) error: //overridenTemplates[themeTmpl.Name] = d_tmpl_ptr overridenTemplates[themeTmpl.Name] = true *dTmplPtr = *sTmplPtr default: LogError(errors.New("The source and destination templates are incompatible")) } - case *func(IPSearchPage, http.ResponseWriter) error: + case *func(IPSearchPage, io.Writer) error: switch sTmplPtr := sourceTmplPtr.(type) { - case *func(IPSearchPage, http.ResponseWriter) error: + case *func(IPSearchPage, io.Writer) error: //overridenTemplates[themeTmpl.Name] = d_tmpl_ptr overridenTemplates[themeTmpl.Name] = true *dTmplPtr = *sTmplPtr default: LogError(errors.New("The source and destination templates are incompatible")) } - case *func(Page, http.ResponseWriter) error: + case *func(Page, io.Writer) error: switch sTmplPtr := sourceTmplPtr.(type) { - case *func(Page, http.ResponseWriter) error: + case *func(Page, io.Writer) error: //overridenTemplates[themeTmpl.Name] = d_tmpl_ptr overridenTemplates[themeTmpl.Name] = true *dTmplPtr = *sTmplPtr @@ -372,58 +373,58 @@ func ResetTemplateOverrides() { // Not really a pointer, more of a function handle, an artifact from one of the earlier versions of themes.go switch oPtr := originPointer.(type) { - case func(TopicPage, http.ResponseWriter) error: + case func(TopicPage, io.Writer) error: switch dPtr := destTmplPtr.(type) { - case *func(TopicPage, http.ResponseWriter) error: + case *func(TopicPage, io.Writer) error: *dPtr = oPtr default: LogError(errors.New("The source and destination templates are incompatible")) } - case func(TopicsPage, http.ResponseWriter) error: + case func(TopicListPage, io.Writer) error: switch dPtr := destTmplPtr.(type) { - case *func(TopicsPage, http.ResponseWriter) error: + case *func(TopicListPage, io.Writer) error: *dPtr = oPtr default: LogError(errors.New("The source and destination templates are incompatible")) } - case func(ForumPage, http.ResponseWriter) error: + case func(ForumPage, io.Writer) error: switch dPtr := destTmplPtr.(type) { - case *func(ForumPage, http.ResponseWriter) error: + case *func(ForumPage, io.Writer) error: *dPtr = oPtr default: LogError(errors.New("The source and destination templates are incompatible")) } - case func(ForumsPage, http.ResponseWriter) error: + case func(ForumsPage, io.Writer) error: switch dPtr := destTmplPtr.(type) { - case *func(ForumsPage, http.ResponseWriter) error: + case *func(ForumsPage, io.Writer) error: *dPtr = oPtr default: LogError(errors.New("The source and destination templates are incompatible")) } - case func(ProfilePage, http.ResponseWriter) error: + case func(ProfilePage, io.Writer) error: switch dPtr := destTmplPtr.(type) { - case *func(ProfilePage, http.ResponseWriter) error: + case *func(ProfilePage, io.Writer) error: *dPtr = oPtr default: LogError(errors.New("The source and destination templates are incompatible")) } - case func(CreateTopicPage, http.ResponseWriter) error: + case func(CreateTopicPage, io.Writer) error: switch dPtr := destTmplPtr.(type) { - case *func(CreateTopicPage, http.ResponseWriter) error: + case *func(CreateTopicPage, io.Writer) error: *dPtr = oPtr default: LogError(errors.New("The source and destination templates are incompatible")) } - case func(IPSearchPage, http.ResponseWriter) error: + case func(IPSearchPage, io.Writer) error: switch dPtr := destTmplPtr.(type) { - case *func(IPSearchPage, http.ResponseWriter) error: + case *func(IPSearchPage, io.Writer) error: *dPtr = oPtr default: LogError(errors.New("The source and destination templates are incompatible")) } - case func(Page, http.ResponseWriter) error: + case func(Page, io.Writer) error: switch dPtr := destTmplPtr.(type) { - case *func(Page, http.ResponseWriter) error: + case *func(Page, io.Writer) error: *dPtr = oPtr default: LogError(errors.New("The source and destination templates are incompatible")) @@ -441,48 +442,48 @@ func ResetTemplateOverrides() { // NEW method of doing theme templates to allow one user to have a different theme to another. Under construction. // TODO: Generate the type switch instead of writing it by hand // TODO: Cut the number of types in half -func RunThemeTemplate(theme string, template string, pi interface{}, w http.ResponseWriter) error { +func RunThemeTemplate(theme string, template string, pi interface{}, w io.Writer) error { var getTmpl = GetThemeTemplate(theme, template) switch tmplO := getTmpl.(type) { - case *func(TopicPage, http.ResponseWriter) error: + case *func(TopicPage, io.Writer) error: var tmpl = *tmplO return tmpl(pi.(TopicPage), w) - case *func(TopicsPage, http.ResponseWriter) error: + case *func(TopicListPage, io.Writer) error: var tmpl = *tmplO - return tmpl(pi.(TopicsPage), w) - case *func(ForumPage, http.ResponseWriter) error: + return tmpl(pi.(TopicListPage), w) + case *func(ForumPage, io.Writer) error: var tmpl = *tmplO return tmpl(pi.(ForumPage), w) - case *func(ForumsPage, http.ResponseWriter) error: + case *func(ForumsPage, io.Writer) error: var tmpl = *tmplO return tmpl(pi.(ForumsPage), w) - case *func(ProfilePage, http.ResponseWriter) error: + case *func(ProfilePage, io.Writer) error: var tmpl = *tmplO return tmpl(pi.(ProfilePage), w) - case *func(CreateTopicPage, http.ResponseWriter) error: + case *func(CreateTopicPage, io.Writer) error: var tmpl = *tmplO return tmpl(pi.(CreateTopicPage), w) - case *func(IPSearchPage, http.ResponseWriter) error: + case *func(IPSearchPage, io.Writer) error: var tmpl = *tmplO return tmpl(pi.(IPSearchPage), w) - case *func(Page, http.ResponseWriter) error: + case *func(Page, io.Writer) error: var tmpl = *tmplO return tmpl(pi.(Page), w) - case func(TopicPage, http.ResponseWriter) error: + case func(TopicPage, io.Writer) error: return tmplO(pi.(TopicPage), w) - case func(TopicsPage, http.ResponseWriter) error: - return tmplO(pi.(TopicsPage), w) - case func(ForumPage, http.ResponseWriter) error: + case func(TopicListPage, io.Writer) error: + return tmplO(pi.(TopicListPage), w) + case func(ForumPage, io.Writer) error: return tmplO(pi.(ForumPage), w) - case func(ForumsPage, http.ResponseWriter) error: + case func(ForumsPage, io.Writer) error: return tmplO(pi.(ForumsPage), w) - case func(ProfilePage, http.ResponseWriter) error: + case func(ProfilePage, io.Writer) error: return tmplO(pi.(ProfilePage), w) - case func(CreateTopicPage, http.ResponseWriter) error: + case func(CreateTopicPage, io.Writer) error: return tmplO(pi.(CreateTopicPage), w) - case func(IPSearchPage, http.ResponseWriter) error: + case func(IPSearchPage, io.Writer) error: return tmplO(pi.(IPSearchPage), w) - case func(Page, http.ResponseWriter) error: + case func(Page, io.Writer) error: return tmplO(pi.(Page), w) case string: mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap[template] diff --git a/common/topic_list.go b/common/topic_list.go index 53787933..436adfcc 100644 --- a/common/topic_list.go +++ b/common/topic_list.go @@ -12,14 +12,12 @@ var TopicList TopicListInt type TopicListHolder struct { List []*TopicsRow ForumList []Forum - PageList []int - Page int - LastPage int + Paginator Paginator } type TopicListInt interface { - GetListByGroup(group *Group, page int) (topicList []*TopicsRow, forumList []Forum, pageList []int, outPage int, lastPage int, err error) - GetList(page int) (topicList []*TopicsRow, forumList []Forum, pageList []int, outPage int, lastPage int, err error) + GetListByGroup(group *Group, page int) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error) + GetList(page int) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error) } type DefaultTopicList struct { @@ -99,11 +97,11 @@ func (tList *DefaultTopicList) Tick() error { var oddLists = make(map[int]*TopicListHolder) var evenLists = make(map[int]*TopicListHolder) - var addList = func(gid int, topicList []*TopicsRow, forumList []Forum, pageList []int, page int, lastPage int) { + var addList = func(gid int, topicList []*TopicsRow, forumList []Forum, paginator Paginator) { if gid%2 == 0 { - evenLists[gid] = &TopicListHolder{topicList, forumList, pageList, page, lastPage} + evenLists[gid] = &TopicListHolder{topicList, forumList, paginator} } else { - oddLists[gid] = &TopicListHolder{topicList, forumList, pageList, page, lastPage} + oddLists[gid] = &TopicListHolder{topicList, forumList, paginator} } } @@ -111,11 +109,11 @@ func (tList *DefaultTopicList) Tick() error { if err != nil { return err } - topicList, forumList, pageList, page, lastPage, err := tList.getListByGroup(guestGroup, 1) + topicList, forumList, paginator, err := tList.getListByGroup(guestGroup, 1) if err != nil { return err } - addList(guestGroup.ID, topicList, forumList, pageList, page, lastPage) + addList(guestGroup.ID, topicList, forumList, paginator) for _, gid := range tList.groupList { group, err := Groups.Get(gid) // TODO: Bulk load the groups? @@ -126,11 +124,11 @@ func (tList *DefaultTopicList) Tick() error { continue } - topicList, forumList, pageList, page, lastPage, err := tList.getListByGroup(group, 1) + topicList, forumList, paginator, err := tList.getListByGroup(group, 1) if err != nil { return err } - addList(group.ID, topicList, forumList, pageList, page, lastPage) + addList(group.ID, topicList, forumList, paginator) } tList.oddLock.Lock() @@ -144,7 +142,7 @@ func (tList *DefaultTopicList) Tick() error { return nil } -func (tList *DefaultTopicList) GetListByGroup(group *Group, page int) (topicList []*TopicsRow, forumList []Forum, pageList []int, outPage int, lastPage int, err error) { +func (tList *DefaultTopicList) GetListByGroup(group *Group, page int) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error) { // TODO: Cache the first three pages not just the first along with all the topics on this beaten track if page == 1 { var holder *TopicListHolder @@ -159,14 +157,14 @@ func (tList *DefaultTopicList) GetListByGroup(group *Group, page int) (topicList tList.oddLock.RUnlock() } if ok { - return holder.List, holder.ForumList, holder.PageList, holder.Page, holder.LastPage, nil + return holder.List, holder.ForumList, holder.Paginator, nil } } return tList.getListByGroup(group, page) } -func (tList *DefaultTopicList) getListByGroup(group *Group, page int) (topicList []*TopicsRow, forumList []Forum, pageList []int, outPage int, lastPage int, err error) { +func (tList *DefaultTopicList) getListByGroup(group *Group, page int) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error) { // TODO: Make CanSee a method on *Group with a canSee field? Have a CanSee method on *User to cover the case of superadmins? canSee := group.CanSee @@ -185,19 +183,19 @@ func (tList *DefaultTopicList) getListByGroup(group *Group, page int) (topicList argList, qlist := ForumListToArgQ(forumList) if qlist == "" { // We don't want to kill the page, so pass an empty slice and nil error - return topicList, forumList, pageList, 1, 1, nil + return topicList, forumList, Paginator{[]int{}, 1, 1}, nil } - topicList, pageList, page, lastPage, err = tList.getList(page, argList, qlist) - return topicList, forumList, pageList, page, lastPage, err + topicList, paginator, err = tList.getList(page, argList, qlist) + return topicList, forumList, paginator, err } // TODO: Reduce the number of returns -func (tList *DefaultTopicList) GetList(page int) (topicList []*TopicsRow, forumList []Forum, pageList []int, outPage int, lastPage int, err error) { +func (tList *DefaultTopicList) GetList(page int) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error) { // TODO: Make CanSee a method on *Group with a canSee field? Have a CanSee method on *User to cover the case of superadmins? canSee, err := Forums.GetAllVisibleIDs() if err != nil { - return nil, nil, nil, 1, 1, err + return nil, nil, Paginator{nil, 1, 1}, err } // We need a list of the visible forums for Quick Topic @@ -215,24 +213,24 @@ func (tList *DefaultTopicList) GetList(page int) (topicList []*TopicsRow, forumL argList, qlist := ForumListToArgQ(forumList) if qlist == "" { // If the super admin can't see anything, then things have gone terribly wrong - return topicList, forumList, pageList, 1, 1, err + return topicList, forumList, Paginator{[]int{}, 1, 1}, err } - topicList, pageList, outPage, lastPage, err = tList.getList(page, argList, qlist) - return topicList, forumList, pageList, outPage, lastPage, err + topicList, paginator, err = tList.getList(page, argList, qlist) + return topicList, forumList, paginator, err } // TODO: Rename this to TopicListStore and pass back a TopicList instance holding the pagination data and topic list rather than passing them back one argument at a time -func (tList *DefaultTopicList) getList(page int, argList []interface{}, qlist string) (topicList []*TopicsRow, pageList []int, outPage int, lastPage int, err error) { +func (tList *DefaultTopicList) getList(page int, argList []interface{}, qlist string) (topicList []*TopicsRow, paginator Paginator, err error) { topicCount, err := ArgQToTopicCount(argList, qlist) if err != nil { - return nil, nil, 1, 1, err + return nil, Paginator{nil, 1, 1}, err } offset, page, lastPage := PageOffset(topicCount, page, Config.ItemsPerPage) stmt, err := qgen.Builder.SimpleSelect("topics", "tid, title, content, createdBy, is_closed, sticky, createdAt, lastReplyAt, lastReplyBy, parentID, postCount, likeCount", "parentID IN("+qlist+")", "sticky DESC, lastReplyAt DESC, createdBy DESC", "?,?") if err != nil { - return nil, nil, 1, 1, err + return nil, Paginator{nil, 1, 1}, err } defer stmt.Close() @@ -241,7 +239,7 @@ func (tList *DefaultTopicList) getList(page int, argList []interface{}, qlist st rows, err := stmt.Query(argList...) if err != nil { - return nil, nil, 1, 1, err + return nil, Paginator{nil, 1, 1}, err } defer rows.Close() @@ -250,7 +248,7 @@ func (tList *DefaultTopicList) getList(page int, argList []interface{}, qlist st topicItem := TopicsRow{ID: 0} err := rows.Scan(&topicItem.ID, &topicItem.Title, &topicItem.Content, &topicItem.CreatedBy, &topicItem.IsClosed, &topicItem.Sticky, &topicItem.CreatedAt, &topicItem.LastReplyAt, &topicItem.LastReplyBy, &topicItem.ParentID, &topicItem.PostCount, &topicItem.LikeCount) if err != nil { - return nil, nil, 1, 1, err + return nil, Paginator{nil, 1, 1}, err } topicItem.Link = BuildTopicURL(NameToSlug(topicItem.Title), topicItem.ID) @@ -269,7 +267,7 @@ func (tList *DefaultTopicList) getList(page int, argList []interface{}, qlist st } err = rows.Err() if err != nil { - return nil, nil, 1, 1, err + return nil, Paginator{nil, 1, 1}, err } // Convert the user ID map to a slice, then bulk load the users @@ -283,7 +281,7 @@ func (tList *DefaultTopicList) getList(page int, argList []interface{}, qlist st // TODO: What if a user is deleted via the Control Panel? userList, err := Users.BulkGetMap(idSlice) if err != nil { - return nil, nil, 1, 1, err + return nil, Paginator{nil, 1, 1}, err } // Second pass to the add the user data @@ -293,8 +291,8 @@ func (tList *DefaultTopicList) getList(page int, argList []interface{}, qlist st topicItem.LastUser = userList[topicItem.LastReplyBy] } - pageList = Paginate(topicCount, Config.ItemsPerPage, 5) - return topicList, pageList, page, lastPage, nil + pageList := Paginate(topicCount, Config.ItemsPerPage, 5) + return topicList, Paginator{pageList, page, lastPage}, nil } // Internal. Don't rely on it. diff --git a/common/widgets.go b/common/widgets.go index c3ab3d79..55d6f854 100644 --- a/common/widgets.go +++ b/common/widgets.go @@ -5,6 +5,7 @@ import ( "bytes" "database/sql" "encoding/json" + "fmt" "html/template" "strings" "sync" @@ -80,7 +81,6 @@ func preparseWidget(widget *Widget, wdata string) (err error) { if err != nil { return err } - widget.Literal = true widget.Body, err = prebuildWidget("widget_simple", tmp) case "about": var tmp NameTextPair @@ -88,12 +88,11 @@ func preparseWidget(widget *Widget, wdata string) (err error) { if err != nil { return err } - widget.Literal = true widget.Body, err = prebuildWidget("widget_about", tmp) default: - widget.Literal = true widget.Body = wdata } + widget.Literal = true // TODO: Test this // TODO: Should we toss this through a proper parser rather than crudely replacing it? @@ -117,23 +116,36 @@ func preparseWidget(widget *Widget, wdata string) (err error) { return err } -func BuildWidget(dock string, headerVars *HeaderVars) (sbody string) { +func BuildWidget(dock string, header *Header) (sbody string) { var widgets []*Widget - if !headerVars.Theme.HasDock(dock) { + if !header.Theme.HasDock(dock) { return "" } // Let themes forcibly override this slot - sbody = headerVars.Theme.BuildDock(dock) + sbody = header.Theme.BuildDock(dock) if sbody != "" { return sbody } + fmt.Println("dock: ", dock) switch dock { case "leftOfNav": widgets = Docks.LeftOfNav case "rightOfNav": widgets = Docks.RightOfNav + case "topMenu": + fmt.Println("topMenu") + // 1 = id for the default menu + mhold := Menus.Get(1) + if mhold != nil { + fmt.Println("header.Writer: ", header.Writer) + err := mhold.Build(header.Writer, &header.CurrentUser) + if err != nil { + LogError(err) + } + } + return "" case "rightSidebar": widgets = Docks.RightSidebar case "footer": @@ -144,8 +156,8 @@ func BuildWidget(dock string, headerVars *HeaderVars) (sbody string) { if !widget.Enabled { continue } - if widget.Allowed(headerVars.Zone) { - item, err := widget.Build(headerVars) + if widget.Allowed(header.Zone) { + item, err := widget.Build(header) if err != nil { LogError(err) } @@ -156,7 +168,7 @@ func BuildWidget(dock string, headerVars *HeaderVars) (sbody string) { } // TODO: Test this -// TODO: Add support for zone:id. Perhaps, carry a ZoneID property around in headerVars? It might allow some weirdness like frontend[5] which matches any zone with an ID of 5 but it would be a tad faster than verifying each zone, although it might be problematic if users end up relying on this behaviour for areas which don't pass IDs to the widgets system but *probably* should +// TODO: Add support for zone:id. Perhaps, carry a ZoneID property around in *Header? It might allow some weirdness like frontend[5] which matches any zone with an ID of 5 but it would be a tad faster than verifying each zone, although it might be problematic if users end up relying on this behaviour for areas which don't pass IDs to the widgets system but *probably* should func (widget *Widget) Allowed(zone string) bool { for _, loc := range strings.Split(widget.Location, "|") { if loc == "global" || loc == zone { @@ -177,8 +189,8 @@ func (widget *Widget) Build(hvars interface{}) (string, error) { return widget.Body, nil } - var headerVars = hvars.(*HeaderVars) - err := RunThemeTemplate(headerVars.Theme.Name, widget.Body, hvars, headerVars.Writer) + var header = hvars.(*Header) + err := RunThemeTemplate(header.Theme.Name, widget.Body, hvars, header.Writer) return "", err } diff --git a/dev-update-linux b/dev-update-linux index fddfb172..84eabc6a 100644 --- a/dev-update-linux +++ b/dev-update-linux @@ -35,6 +35,8 @@ echo "Updating GopherJS" go get -u github.com/gopherjs/gopherjs echo "Updating Gosora" +rm ./schema/lastSchema.json +cp ./schema/schema.json ./schema/lastSchema.json git pull origin master echo "Patching Gosora" diff --git a/dev-update.bat b/dev-update.bat index b61969bb..ef4ebb5e 100644 --- a/dev-update.bat +++ b/dev-update.bat @@ -101,6 +101,7 @@ if %errorlevel% neq 0 ( echo Updating Gosora +copy ./schema/schema.json ./schema/lastSchema.json git pull origin master if %errorlevel% neq 0 ( pause diff --git a/extend/guilds/lib/guilds.go b/extend/guilds/lib/guilds.go index bd660271..a3758b93 100644 --- a/extend/guilds/lib/guilds.go +++ b/extend/guilds/lib/guilds.go @@ -52,7 +52,7 @@ type Guild struct { type Page struct { Title string CurrentUser common.User - Header *common.HeaderVars + Header *common.Header ItemList []*common.TopicsRow Forum *common.Forum Guild *Guild @@ -64,14 +64,14 @@ type Page struct { type ListPage struct { Title string CurrentUser common.User - Header *common.HeaderVars + Header *common.Header GuildList []*Guild } type MemberListPage struct { Title string CurrentUser common.User - Header *common.HeaderVars + Header *common.Header ItemList []Member Guild *Guild Page int @@ -90,7 +90,7 @@ type Member struct { User common.User } -func PrebuildTmplList(user common.User, headerVars *common.HeaderVars) common.CTmpl { +func PrebuildTmplList(user common.User, header *common.Header) common.CTmpl { var guildList = []*Guild{ &Guild{ ID: 1, @@ -107,13 +107,13 @@ func PrebuildTmplList(user common.User, headerVars *common.HeaderVars) common.CT Forums: []*common.Forum{common.Forums.DirtyGet(1)}, }, } - listPage := ListPage{"Guild List", user, headerVars, guildList} + listPage := ListPage{"Guild List", user, header, guildList} return common.CTmpl{"guilds_guild_list", "guilds_guild_list.html", "templates/", "guilds.ListPage", listPage, []string{"./extend/guilds/lib"}} } // TODO: Do this properly via the widget system // TODO: REWRITE THIS -func CommonAreaWidgets(headerVars *common.HeaderVars) { +func CommonAreaWidgets(header *common.Header) { // TODO: Hot Groups? Featured Groups? Official Groups? var b bytes.Buffer var menu = common.WidgetMenu{"Guilds", []common.WidgetMenuItem{ @@ -126,16 +126,16 @@ func CommonAreaWidgets(headerVars *common.HeaderVars) { return } - if headerVars.Theme.HasDock("leftSidebar") { - headerVars.Widgets.LeftSidebar = template.HTML(string(b.Bytes())) - } else if headerVars.Theme.HasDock("rightSidebar") { - headerVars.Widgets.RightSidebar = template.HTML(string(b.Bytes())) + if header.Theme.HasDock("leftSidebar") { + header.Widgets.LeftSidebar = template.HTML(string(b.Bytes())) + } else if header.Theme.HasDock("rightSidebar") { + header.Widgets.RightSidebar = template.HTML(string(b.Bytes())) } } // TODO: Do this properly via the widget system // TODO: Make a better more customisable group widget system -func GuildWidgets(headerVars *common.HeaderVars, guildItem *Guild) (success bool) { +func GuildWidgets(header *common.Header, guildItem *Guild) (success bool) { return false // Disabled until the next commit /*var b bytes.Buffer @@ -150,10 +150,10 @@ func GuildWidgets(headerVars *common.HeaderVars, guildItem *Guild) (success bool return false } - if themes[headerVars.Theme.Name].Sidebars == "left" { - headerVars.Widgets.LeftSidebar = template.HTML(string(b.Bytes())) - } else if themes[headerVars.Theme.Name].Sidebars == "right" || themes[headerVars.Theme.Name].Sidebars == "both" { - headerVars.Widgets.RightSidebar = template.HTML(string(b.Bytes())) + if themes[header.Theme.Name].Sidebars == "left" { + header.Widgets.LeftSidebar = template.HTML(string(b.Bytes())) + } else if themes[header.Theme.Name].Sidebars == "right" || themes[header.Theme.Name].Sidebars == "both" { + header.Widgets.RightSidebar = template.HTML(string(b.Bytes())) } else { return false } @@ -165,11 +165,11 @@ func GuildWidgets(headerVars *common.HeaderVars, guildItem *Guild) (success bool */ func RouteGuildList(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { - headerVars, ferr := common.UserCheck(w, r, &user) + header, ferr := common.UserCheck(w, r, &user) if ferr != nil { return ferr } - CommonAreaWidgets(headerVars) + CommonAreaWidgets(header) rows, err := ListStmt.Query() if err != nil && err != common.ErrNoRows { @@ -192,8 +192,8 @@ func RouteGuildList(w http.ResponseWriter, r *http.Request, user common.User) co return common.InternalError(err, w, r) } - pi := ListPage{"Guild List", user, headerVars, guildList} - err = common.RunThemeTemplate(headerVars.Theme.Name, "guilds_guild_list", pi, w) + pi := ListPage{"Guild List", user, header, guildList} + err = common.RunThemeTemplate(header.Theme.Name, "guilds_guild_list", pi, w) if err != nil { return common.InternalError(err, w, r) } @@ -215,7 +215,7 @@ func MiddleViewGuild(w http.ResponseWriter, r *http.Request, user common.User) c if err != nil { return common.LocalError("Bad guild", w, r, user) } - // TODO: Build and pass headerVars + // TODO: Build and pass header if !guildItem.Active { return common.NotFound(w, r, nil) } @@ -229,7 +229,7 @@ func MiddleViewGuild(w http.ResponseWriter, r *http.Request, user common.User) c } func RouteCreateGuild(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { - headerVars, ferr := common.UserCheck(w, r, &user) + header, ferr := common.UserCheck(w, r, &user) if ferr != nil { return ferr } @@ -237,9 +237,9 @@ func RouteCreateGuild(w http.ResponseWriter, r *http.Request, user common.User) if !user.Loggedin || !user.PluginPerms["CreateGuild"] { return common.NoPermissions(w, r, user) } - CommonAreaWidgets(headerVars) + CommonAreaWidgets(header) - pi := common.Page{"Create Guild", user, headerVars, tList, nil} + pi := common.Page{"Create Guild", user, header, tList, nil} err := common.Templates.ExecuteTemplate(w, "guilds_create_guild.html", pi) if err != nil { return common.InternalError(err, w, r) @@ -298,7 +298,7 @@ func RouteCreateGuildSubmit(w http.ResponseWriter, r *http.Request, user common. } func RouteMemberList(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { - headerVars, ferr := common.UserCheck(w, r, &user) + header, ferr := common.UserCheck(w, r, &user) if ferr != nil { return ferr } @@ -319,7 +319,7 @@ func RouteMemberList(w http.ResponseWriter, r *http.Request, user common.User) c } guildItem.Link = BuildGuildURL(common.NameToSlug(guildItem.Name), guildItem.ID) - GuildWidgets(headerVars, guildItem) + GuildWidgets(header, guildItem) rows, err := MemberListJoinStmt.Query(guildID) if err != nil && err != common.ErrNoRows { @@ -356,12 +356,12 @@ func RouteMemberList(w http.ResponseWriter, r *http.Request, user common.User) c } rows.Close() - pi := MemberListPage{"Guild Member List", user, headerVars, guildMembers, guildItem, 0, 0} + pi := MemberListPage{"Guild Member List", user, header, guildMembers, guildItem, 0, 0} // A plugin with plugins. Pluginception! if common.RunPreRenderHook("pre_render_guilds_member_list", w, r, &user, &pi) { return nil } - err = common.RunThemeTemplate(headerVars.Theme.Name, "guilds_member_list", pi, w) + err = common.RunThemeTemplate(header.Theme.Name, "guilds_member_list", pi, w) if err != nil { return common.InternalError(err, w, r) } @@ -445,7 +445,7 @@ func ForumCheck(args ...interface{}) (skip bool, rerr common.RouteError) { return true, common.InternalError(errors.New("Unable to find the parent group for a forum"), w, r) } if !guildItem.Active { - return true, common.NotFound(w, r, nil) // TODO: Can we pull headerVars out of args? + return true, common.NotFound(w, r, nil) // TODO: Can we pull header out of args? } r = r.WithContext(context.WithValue(r.Context(), "guilds_current_group", guildItem)) } @@ -496,7 +496,7 @@ func ForumCheck(args ...interface{}) (skip bool, rerr common.RouteError) { func Widgets(args ...interface{}) interface{} { var zone = args[0].(string) - var headerVars = args[2].(*common.HeaderVars) + var header = args[2].(*common.Header) var request = args[3].(*http.Request) if zone != "view_forum" { @@ -512,12 +512,12 @@ func Widgets(args ...interface{}) interface{} { return false } - if headerVars.ExtData.Items == nil { - headerVars.ExtData.Items = make(map[string]interface{}) + if header.ExtData.Items == nil { + header.ExtData.Items = make(map[string]interface{}) } - headerVars.ExtData.Items["guilds_current_group"] = guildItem + header.ExtData.Items["guilds_current_group"] = guildItem - return GuildWidgets(headerVars, guildItem) + return GuildWidgets(header, guildItem) } return false } diff --git a/gen_mysql.go b/gen_mysql.go index 08d2a325..b0c40975 100644 --- a/gen_mysql.go +++ b/gen_mysql.go @@ -76,7 +76,7 @@ func _gen_mysql() (err error) { } common.DebugLog("Preparing getUsersOffset statement.") - stmts.getUsersOffset, err = db.Prepare("SELECT `uid`,`name`,`group`,`active`,`is_super_admin`,`avatar` FROM `users` ORDER BY uid ASC LIMIT ?,?") + stmts.getUsersOffset, err = db.Prepare("SELECT `uid`,`name`,`group`,`active`,`is_super_admin`,`avatar` FROM `users` ORDER BY `uid` ASC LIMIT ?,?") if err != nil { log.Print("Error in getUsersOffset statement.") return err @@ -97,14 +97,14 @@ func _gen_mysql() (err error) { } common.DebugLog("Preparing getModlogsOffset statement.") - stmts.getModlogsOffset, err = db.Prepare("SELECT `action`,`elementID`,`elementType`,`ipaddress`,`actorID`,`doneAt` FROM `moderation_logs` ORDER BY doneAt DESC LIMIT ?,?") + stmts.getModlogsOffset, err = db.Prepare("SELECT `action`,`elementID`,`elementType`,`ipaddress`,`actorID`,`doneAt` FROM `moderation_logs` ORDER BY `doneAt` DESC LIMIT ?,?") if err != nil { log.Print("Error in getModlogsOffset statement.") return err } common.DebugLog("Preparing getAdminlogsOffset statement.") - stmts.getAdminlogsOffset, err = db.Prepare("SELECT `action`,`elementID`,`elementType`,`ipaddress`,`actorID`,`doneAt` FROM `administration_logs` ORDER BY doneAt DESC LIMIT ?,?") + stmts.getAdminlogsOffset, err = db.Prepare("SELECT `action`,`elementID`,`elementType`,`ipaddress`,`actorID`,`doneAt` FROM `administration_logs` ORDER BY `doneAt` DESC LIMIT ?,?") if err != nil { log.Print("Error in getAdminlogsOffset statement.") return err @@ -139,14 +139,14 @@ func _gen_mysql() (err error) { } common.DebugLog("Preparing forumEntryExists statement.") - stmts.forumEntryExists, err = db.Prepare("SELECT `fid` FROM `forums` WHERE `name` = '' ORDER BY fid ASC LIMIT 0,1") + stmts.forumEntryExists, err = db.Prepare("SELECT `fid` FROM `forums` WHERE `name` = '' ORDER BY `fid` ASC LIMIT 0,1") if err != nil { log.Print("Error in forumEntryExists statement.") return err } common.DebugLog("Preparing groupEntryExists statement.") - stmts.groupEntryExists, err = db.Prepare("SELECT `gid` FROM `users_groups` WHERE `name` = '' ORDER BY gid ASC LIMIT 0,1") + stmts.groupEntryExists, err = db.Prepare("SELECT `gid` FROM `users_groups` WHERE `name` = '' ORDER BY `gid` ASC LIMIT 0,1") if err != nil { log.Print("Error in groupEntryExists statement.") return err @@ -160,7 +160,7 @@ func _gen_mysql() (err error) { } common.DebugLog("Preparing getForumTopics statement.") - stmts.getForumTopics, err = db.Prepare("SELECT `topics`.`tid`, `topics`.`title`, `topics`.`content`, `topics`.`createdBy`, `topics`.`is_closed`, `topics`.`sticky`, `topics`.`createdAt`, `topics`.`lastReplyAt`, `topics`.`parentID`, `users`.`name`, `users`.`avatar` FROM `topics` LEFT JOIN `users` ON `topics`.`createdBy` = `users`.`uid` WHERE `topics`.`parentID` = ? ORDER BY topics.sticky DESC,topics.lastReplyAt DESC,topics.createdBy DESC") + stmts.getForumTopics, err = db.Prepare("SELECT `topics`.`tid`, `topics`.`title`, `topics`.`content`, `topics`.`createdBy`, `topics`.`is_closed`, `topics`.`sticky`, `topics`.`createdAt`, `topics`.`lastReplyAt`, `topics`.`parentID`, `users`.`name`, `users`.`avatar` FROM `topics` LEFT JOIN `users` ON `topics`.`createdBy` = `users`.`uid` WHERE `topics`.`parentID` = ? ORDER BY `topics`.`sticky` DESC,`topics`.`lastReplyAt` DESC,`topics`.`createdBy` DESC") if err != nil { log.Print("Error in getForumTopics statement.") return err diff --git a/langs/english.json b/langs/english.json index 20dad4af..32e9a9c6 100644 --- a/langs/english.json +++ b/langs/english.json @@ -649,6 +649,8 @@ "panel_backups_download":"Download", "panel_backups_no_backups":"There aren't any backups available at this time.", "panel_debug_head":"Debug", + "panel_debug_go_version_label":"Go Version", + "panel_debug_database_version_label":"DB Version", "panel_debug_uptime_label":"Uptime", "panel_debug_open_database_connections_label":"Open DB Conns", "panel_debug_adapter_label":"Adapter" diff --git a/main.go b/main.go index 60599447..b0aed2bd 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,7 @@ package main import ( + "bytes" "flag" "fmt" "io" @@ -70,6 +71,18 @@ func afterDBInit() (err error) { return err } + log.Print("Initialising the menu item list") + common.Menus = common.NewDefaultMenuStore() + err = common.Menus.Load(1) // 1 = the default menu + if err != nil { + return err + } + menuHold := common.Menus.Get(1) + fmt.Println("menuHold: %+v", menuHold) + var b bytes.Buffer + menuHold.Build(&b, &common.GuestUser) + fmt.Println("menuHold output: ", string(b.Bytes())) + log.Print("Initialising the authentication system") common.Auth, err = common.NewDefaultAuth() if err != nil { @@ -225,6 +238,10 @@ func main() { if err != nil { log.Fatal(err) } + err = common.CompileJSTemplates() + if err != nil { + log.Fatal(err) + } return } diff --git a/panel_routes.go b/panel_routes.go index ce840949..6d9263da 100644 --- a/panel_routes.go +++ b/panel_routes.go @@ -12,6 +12,7 @@ import ( "net/http" "os" "path/filepath" + "runtime" "strconv" "strings" "time" @@ -1747,7 +1748,7 @@ func routePanelUsers(w http.ResponseWriter, r *http.Request, user common.User) c } pageList := common.Paginate(stats.Users, perPage, 5) - pi := common.PanelUserPage{common.GetTitlePhrase("panel_users"), user, headerVars, stats, "users", userList, pageList, page, lastPage} + pi := common.PanelUserPage{common.GetTitlePhrase("panel_users"), user, headerVars, stats, "users", userList, common.Paginator{pageList, page, lastPage}} return panelRenderTemplate("panel_users", w, r, user, &pi) } @@ -1940,7 +1941,7 @@ func routePanelGroups(w http.ResponseWriter, r *http.Request, user common.User) //log.Printf("groupList: %+v\n", groupList) pageList := common.Paginate(stats.Groups, perPage, 5) - pi := common.PanelGroupPage{common.GetTitlePhrase("panel_groups"), user, headerVars, stats, "groups", groupList, pageList, page, lastPage} + pi := common.PanelGroupPage{common.GetTitlePhrase("panel_groups"), user, headerVars, stats, "groups", groupList, common.Paginator{pageList, page, lastPage}} return panelRenderTemplate("panel_groups", w, r, user, &pi) } @@ -2517,7 +2518,7 @@ func routePanelLogsMod(w http.ResponseWriter, r *http.Request, user common.User) } pageList := common.Paginate(logCount, perPage, 5) - pi := common.PanelLogsPage{common.GetTitlePhrase("panel_mod_logs"), user, headerVars, stats, "logs", logs, pageList, page, lastPage} + pi := common.PanelLogsPage{common.GetTitlePhrase("panel_mod_logs"), user, headerVars, stats, "logs", logs, common.Paginator{pageList, page, lastPage}} return panelRenderTemplate("panel_modlogs", w, r, user, &pi) } @@ -2557,7 +2558,7 @@ func routePanelLogsAdmin(w http.ResponseWriter, r *http.Request, user common.Use } pageList := common.Paginate(logCount, perPage, 5) - pi := common.PanelLogsPage{common.GetTitlePhrase("panel_admin_logs"), user, headerVars, stats, "logs", logs, pageList, page, lastPage} + pi := common.PanelLogsPage{common.GetTitlePhrase("panel_admin_logs"), user, headerVars, stats, "logs", logs, common.Paginator{pageList, page, lastPage}} return panelRenderTemplate("panel_adminlogs", w, r, user, &pi) } @@ -2567,6 +2568,8 @@ func routePanelDebug(w http.ResponseWriter, r *http.Request, user common.User) c return ferr } + goVersion := runtime.Version() + dbVersion := qgen.Builder.DbVersion() var uptime string upDuration := time.Since(startTime) hours := int(upDuration.Hours()) @@ -2586,6 +2589,6 @@ func routePanelDebug(w http.ResponseWriter, r *http.Request, user common.User) c // Disk I/O? // TODO: Fetch the adapter from Builder rather than getting it from a global? - pi := common.PanelDebugPage{common.GetTitlePhrase("panel_debug"), user, headerVars, stats, "debug", uptime, openConnCount, dbAdapter} + pi := common.PanelDebugPage{common.GetTitlePhrase("panel_debug"), user, headerVars, stats, "debug", goVersion, dbVersion, uptime, openConnCount, dbAdapter} return panelRenderTemplate("panel_debug", w, r, user, &pi) } diff --git a/patcher/main.go b/patcher/main.go index 5400e677..1c79a93c 100644 --- a/patcher/main.go +++ b/patcher/main.go @@ -2,6 +2,7 @@ package main import ( "bufio" + "database/sql" "fmt" "os" "runtime/debug" @@ -63,28 +64,13 @@ func patcher(scanner *bufio.Scanner) error { func eachUser(handle func(int) error) error { acc := qgen.Builder.Accumulator() - stmt := acc.Select("users").Prepare() - err := acc.FirstError() - if err != nil { - return err - } - - rows, err := stmt.Query() - if err != nil { - return err - } - defer rows.Close() - - for rows.Next() { + err := acc.Select("users").Each(func(rows *sql.Rows) error { var uid int err := rows.Scan(&uid) if err != nil { return err } - err = handle(uid) - if err != nil { - return err - } - } - return rows.Err() + return handle(uid) + }) + return err } diff --git a/plugin_heythere.go b/plugin_heythere.go index abb00cc8..393c5e5e 100644 --- a/plugin_heythere.go +++ b/plugin_heythere.go @@ -17,7 +17,7 @@ func deactivateHeythere() { } func heythereReply(data ...interface{}) interface{} { - currentUser := data[0].(*common.TopicPage).CurrentUser + currentUser := data[0].(*common.TopicPage).Header.CurrentUser reply := data[1].(*common.ReplyUser) reply.Content = "Hey there, " + currentUser.Name + "!" reply.ContentHtml = "Hey there, " + currentUser.Name + "!" diff --git a/query_gen/lib/acc_builders.go b/query_gen/lib/acc_builders.go index 467b2fe8..179baebf 100644 --- a/query_gen/lib/acc_builders.go +++ b/query_gen/lib/acc_builders.go @@ -49,25 +49,25 @@ func (update *accUpdateBuilder) Prepare() *sql.Stmt { return update.build.SimpleUpdate(update.table, update.set, update.where) } -type accSelectBuilder struct { +type AccSelectBuilder struct { table string columns string where string orderby string limit string dateCutoff *dateCutoff // We might want to do this in a slightly less hacky way - inChain *accSelectBuilder + inChain *AccSelectBuilder inColumn string build *Accumulator } -func (selectItem *accSelectBuilder) Columns(columns string) *accSelectBuilder { +func (selectItem *AccSelectBuilder) Columns(columns string) *AccSelectBuilder { selectItem.columns = columns return selectItem } -func (selectItem *accSelectBuilder) Where(where string) *accSelectBuilder { +func (selectItem *AccSelectBuilder) Where(where string) *AccSelectBuilder { if selectItem.where != "" { selectItem.where += " AND " } @@ -76,7 +76,7 @@ func (selectItem *accSelectBuilder) Where(where string) *accSelectBuilder { } // TODO: Don't implement the SQL at the accumulator level but the adapter level -func (selectItem *accSelectBuilder) In(column string, inList []int) *accSelectBuilder { +func (selectItem *AccSelectBuilder) In(column string, inList []int) *AccSelectBuilder { if len(inList) == 0 { return selectItem } @@ -94,28 +94,28 @@ func (selectItem *accSelectBuilder) In(column string, inList []int) *accSelectBu return selectItem } -func (selectItem *accSelectBuilder) InQ(column string, subBuilder *accSelectBuilder) *accSelectBuilder { +func (selectItem *AccSelectBuilder) InQ(column string, subBuilder *AccSelectBuilder) *AccSelectBuilder { selectItem.inChain = subBuilder selectItem.inColumn = column return selectItem } -func (selectItem *accSelectBuilder) DateCutoff(column string, quantity int, unit string) *accSelectBuilder { +func (selectItem *AccSelectBuilder) DateCutoff(column string, quantity int, unit string) *AccSelectBuilder { selectItem.dateCutoff = &dateCutoff{column, quantity, unit} return selectItem } -func (selectItem *accSelectBuilder) Orderby(orderby string) *accSelectBuilder { +func (selectItem *AccSelectBuilder) Orderby(orderby string) *AccSelectBuilder { selectItem.orderby = orderby return selectItem } -func (selectItem *accSelectBuilder) Limit(limit string) *accSelectBuilder { +func (selectItem *AccSelectBuilder) Limit(limit string) *AccSelectBuilder { selectItem.limit = limit return selectItem } -func (selectItem *accSelectBuilder) Prepare() *sql.Stmt { +func (selectItem *AccSelectBuilder) Prepare() *sql.Stmt { // TODO: Phase out the procedural API and use the adapter's OO API? The OO API might need a bit more work before we do that and it needs to be rolled out to MSSQL. if selectItem.dateCutoff != nil || selectItem.inChain != nil { selectBuilder := selectItem.build.GetAdapter().Builder().Select().FromAcc(selectItem) @@ -124,7 +124,7 @@ func (selectItem *accSelectBuilder) Prepare() *sql.Stmt { return selectItem.build.SimpleSelect(selectItem.table, selectItem.columns, selectItem.where, selectItem.orderby, selectItem.limit) } -func (selectItem *accSelectBuilder) Query(args ...interface{}) (*sql.Rows, error) { +func (selectItem *AccSelectBuilder) Query(args ...interface{}) (*sql.Rows, error) { stmt := selectItem.Prepare() if stmt != nil { return stmt.Query(args...) @@ -132,6 +132,23 @@ func (selectItem *accSelectBuilder) Query(args ...interface{}) (*sql.Rows, error return nil, selectItem.build.FirstError() } +// Experimental, reduces lines +func (selectItem *AccSelectBuilder) Each(handle func(*sql.Rows) error) error { + rows, err := selectItem.Query() + if err != nil { + return err + } + defer rows.Close() + + for rows.Next() { + err = handle(rows) + if err != nil { + return err + } + } + return rows.Err() +} + type accInsertBuilder struct { table string columns string diff --git a/query_gen/lib/accumulator.go b/query_gen/lib/accumulator.go index 9479d8b2..11770ace 100644 --- a/query_gen/lib/accumulator.go +++ b/query_gen/lib/accumulator.go @@ -197,8 +197,8 @@ func (build *Accumulator) Update(table string) *accUpdateBuilder { return &accUpdateBuilder{table, "", "", build} } -func (build *Accumulator) Select(table string) *accSelectBuilder { - return &accSelectBuilder{table, "", "", "", "", nil, nil, "", build} +func (build *Accumulator) Select(table string) *AccSelectBuilder { + return &AccSelectBuilder{table, "", "", "", "", nil, nil, "", build} } func (build *Accumulator) Insert(table string) *accInsertBuilder { diff --git a/query_gen/lib/micro_builders.go b/query_gen/lib/micro_builders.go index 7c35e1a8..28cc498a 100644 --- a/query_gen/lib/micro_builders.go +++ b/query_gen/lib/micro_builders.go @@ -142,7 +142,7 @@ func (selectItem *selectPrebuilder) Limit(limit string) *selectPrebuilder { } // TODO: We probably want to avoid the double allocation of two builders somehow -func (selectItem *selectPrebuilder) FromAcc(accBuilder *accSelectBuilder) *selectPrebuilder { +func (selectItem *selectPrebuilder) FromAcc(accBuilder *AccSelectBuilder) *selectPrebuilder { selectItem.table = accBuilder.table selectItem.columns = accBuilder.columns selectItem.where = accBuilder.where diff --git a/query_gen/lib/mssql.go b/query_gen/lib/mssql.go index af09c147..1d5e960f 100644 --- a/query_gen/lib/mssql.go +++ b/query_gen/lib/mssql.go @@ -135,14 +135,13 @@ func (adapter *MssqlAdapter) SimpleInsert(name string, table string, columns str if table == "" { return "", errors.New("You need a name for this table") } - if len(columns) == 0 { - return "", errors.New("No columns found for SimpleInsert") - } - if len(fields) == 0 { - return "", errors.New("No input data found for SimpleInsert") - } - var querystr = "INSERT INTO [" + table + "] (" + var querystr = "INSERT INTO [" + table + "]" + if columns == "" { + adapter.pushStatement(name, "insert", querystr) + return querystr, nil + } + querystr += " (" // Escape the column names, just in case we've used a reserved keyword for _, column := range processColumns(columns) { diff --git a/query_gen/lib/mysql.go b/query_gen/lib/mysql.go index cc66f6b3..f6ca1dcb 100644 --- a/query_gen/lib/mysql.go +++ b/query_gen/lib/mysql.go @@ -143,31 +143,31 @@ func (adapter *MysqlAdapter) SimpleInsert(name string, table string, columns str if table == "" { return "", errors.New("You need a name for this table") } - if len(columns) == 0 { - return "", errors.New("No columns found for SimpleInsert") - } - if len(fields) == 0 { - return "", errors.New("No input data found for SimpleInsert") - } - var querystr = "INSERT INTO `" + table + "`(" + adapter.buildColumns(columns) + ") VALUES (" - for _, field := range processFields(fields) { - nameLen := len(field.Name) - if field.Name[0] == '"' && field.Name[nameLen-1] == '"' && nameLen >= 3 { - field.Name = "'" + field.Name[1:nameLen-1] + "'" + var querystr = "INSERT INTO `" + table + "`" + if columns != "" { + querystr += "(" + adapter.buildColumns(columns) + ") VALUES (" + for _, field := range processFields(fields) { + nameLen := len(field.Name) + if field.Name[0] == '"' && field.Name[nameLen-1] == '"' && nameLen >= 3 { + field.Name = "'" + field.Name[1:nameLen-1] + "'" + } + if field.Name[0] == '\'' && field.Name[nameLen-1] == '\'' && nameLen >= 3 { + field.Name = "'" + strings.Replace(field.Name[1:nameLen-1], "'", "''", -1) + "'" + } + querystr += field.Name + "," } - if field.Name[0] == '\'' && field.Name[nameLen-1] == '\'' && nameLen >= 3 { - field.Name = "'" + strings.Replace(field.Name[1:nameLen-1], "'", "''", -1) + "'" - } - querystr += field.Name + "," + querystr = querystr[0:len(querystr)-1] + ")" } - querystr = querystr[0:len(querystr)-1] + ")" adapter.pushStatement(name, "insert", querystr) return querystr, nil } func (adapter *MysqlAdapter) buildColumns(columns string) (querystr string) { + if columns == "" { + return "" + } // Escape the column names, just in case we've used a reserved keyword for _, column := range processColumns(columns) { if column.Type == "function" { @@ -393,7 +393,7 @@ func (adapter *MysqlAdapter) buildOrderby(orderby string) (querystr string) { querystr = " ORDER BY " for _, column := range processOrderby(orderby) { // TODO: We might want to escape this column - querystr += column.Column + " " + strings.ToUpper(column.Order) + "," + querystr += "`" + strings.Replace(column.Column, ".", "`.`", -1) + "` " + strings.ToUpper(column.Order) + "," } querystr = querystr[0 : len(querystr)-1] } diff --git a/query_gen/lib/pgsql.go b/query_gen/lib/pgsql.go index edcae3cc..a5af9fdd 100644 --- a/query_gen/lib/pgsql.go +++ b/query_gen/lib/pgsql.go @@ -115,12 +115,6 @@ func (adapter *PgsqlAdapter) SimpleInsert(name string, table string, columns str if table == "" { return "", errors.New("You need a name for this table") } - if len(columns) == 0 { - return "", errors.New("No columns found for SimpleInsert") - } - if len(fields) == 0 { - return "", errors.New("No input data found for SimpleInsert") - } return "", nil } diff --git a/query_gen/main.go b/query_gen/main.go index aa61a1f5..49595b7e 100644 --- a/query_gen/main.go +++ b/query_gen/main.go @@ -209,6 +209,32 @@ func seedTables(adapter qgen.Adapter) error { qgen.Install.SimpleInsert("replies", "tid, content, parsed_content, createdAt, createdBy, lastUpdated, lastEdit, lastEditBy, ipaddress", "1,'A reply!','A reply!',UTC_TIMESTAMP(),1,UTC_TIMESTAMP(),0,0,'::1'") + /* + {{range .MenuItems}} +