clearlove2.1
1// 把 handlers 的模板与会话能力注入插件运行时。
2//
3// 依赖方向是 handlers -> plugin,因此由 handlers 实现 plugin.ViewProvider
4// 并反向注册,避免 plugin 包 import handlers 造成循环依赖。
5package handlers
6
7import (
8 "bytes"
9 "html/template"
10 "io/fs"
11 "net/http"
12
13 "clearlove/internal/middleware"
14 "clearlove/internal/plugin"
15)
16
17type pluginViewProvider struct{}
18
19// RenderTemplate 渲染插件目录下的模板文件
20func (pluginViewProvider) RenderTemplate(fsys fs.FS, name string, data any) (string, error) {
21 t, err := template.New(name).Funcs(tmplFuncs).ParseFS(fsys, name)
22 if err != nil {
23 return "", err
24 }
25 var buf bytes.Buffer
26 if err := t.ExecuteTemplate(&buf, name, data); err != nil {
27 return "", err
28 }
29 return buf.String(), nil
30}
31
32func (pluginViewProvider) CSRF(r *http.Request) string { return middleware.CSRFToken(r) }
33
34func (pluginViewProvider) UserID(r *http.Request) int64 {
35 if u := CurrentUser(r); u != nil {
36 return u.ID
37 }
38 return 0
39}
40
41func (pluginViewProvider) AdminID(r *http.Request) int64 {
42 if a := CurrentAdmin(r); a != nil {
43 return a.ID
44 }
45 return 0
46}
47
48func (pluginViewProvider) AdminPerm(r *http.Request, perm string) bool {
49 a := CurrentAdmin(r)
50 return a != nil && a.HasPerm(perm)
51}
52
53// pluginSlotData 构造传给插件 Slot 处理的轻量数据(避免整页数据序列化开销)
54func pluginSlotData(r *http.Request, page string, data map[string]any) map[string]any {
55 out := map[string]any{"page": page, "path": r.URL.Path}
56 for _, k := range []string{"site", "topic", "notice"} {
57 if v, ok := data[k]; ok {
58 out[k] = v
59 }
60 }
61 if u := CurrentUser(r); u != nil {
62 out["user"] = map[string]any{"id": u.ID, "username": u.Username}
63 }
64 if a := CurrentAdmin(r); a != nil {
65 out["admin"] = map[string]any{"id": a.ID, "username": a.Username, "role": a.Role}
66 }
67 // 详情页/个人主页把当前帖子(或列表)的轻量信息交给插件 Slot
68 switch p := data["post"].(type) {
69 case Card:
70 out["post"] = map[string]any{"id": p.ID, "user_id": p.UserID, "nickname": p.Nickname}
71 case *Card:
72 if p != nil {
73 out["post"] = map[string]any{"id": p.ID, "user_id": p.UserID, "nickname": p.Nickname}
74 }
75 }
76 return out
77}
78
79var _ plugin.ViewProvider = pluginViewProvider{}
80
81// ---------- 过滤器接入辅助 ----------
82
83func strOfAny(v any) string {
84 if s, ok := v.(string); ok {
85 return s
86 }
87 return ""
88}
89
90func boolOfAny(v any) bool {
91 switch t := v.(type) {
92 case bool:
93 return t
94 case string:
95 return t == "1" || t == "true"
96 case int64:
97 return t != 0
98 case float64:
99 return t != 0
100 default:
101 return false
102 }
103}
104
105func intOfAny(v any) int64 {
106 switch t := v.(type) {
107 case int64:
108 return t
109 case int:
110 return int64(t)
111 case float64:
112 return int64(t)
113 default:
114 return 0
115 }
116}
117
118func anyToStrings(v any) []string {
119 switch list := v.(type) {
120 case []string:
121 return list
122 case []any:
123 out := make([]string, 0, len(list))
124 for _, item := range list {
125 if s, ok := item.(string); ok {
126 out = append(out, s)
127 }
128 }
129 return out
130 default:
131 return nil
132 }
133}
134
135// cardToMap 把帖子卡片转成插件可读写的普通对象
136func cardToMap(c *Card) map[string]any {
137 return map[string]any{
138 "id": c.ID,
139 "user_id": c.UserID,
140 "nickname": c.Nickname,
141 "content": c.Content,
142 "topic": c.Topic,
143 "images": c.Images,
144 "videos": c.Videos,
145 "likes": c.Likes,
146 "comment_count": c.CommentCount,
147 "is_admin": c.IsAdmin,
148 "badges": c.Badges,
149 "created_at": c.CreatedAt,
150 }
151}
152
153// mapToCard 把插件改写后的字段写回卡片(id/user_id 不允许改)
154func mapToCard(m map[string]any, c *Card) {
155 if v, ok := m["nickname"].(string); ok && v != "" {
156 c.Nickname = v
157 }
158 if v, ok := m["content"].(string); ok {
159 c.Content = v
160 }
161 if v, ok := m["topic"].(string); ok {
162 c.Topic = v
163 }
164 if v, ok := m["likes"]; ok {
165 c.Likes = intOfAny(v)
166 }
167 if v, ok := m["comment_count"]; ok {
168 c.CommentCount = intOfAny(v)
169 }
170 if v, ok := m["images"]; ok {
171 c.Images = anyToStrings(v)
172 }
173 if v, ok := m["videos"]; ok {
174 c.Videos = anyToStrings(v)
175 }
176 if v, ok := m["badges"]; ok {
177 if list := anyToStrings(v); list != nil {
178 c.Badges = list
179 }
180 }
181}
182
183// applyCardFilter 让插件改写卡片数据(filter.card)
184func applyCardFilter(c *Card) {
185 if !plugin.HasApps() || c == nil {
186 return
187 }
188 out := plugin.ApplyFilterValue("filter.card", cardToMap(c), nil)
189 if m, ok := out.(map[string]any); ok {
190 mapToCard(m, c)
191 }
192}