Skip to content

Commit 98f0d20

Browse files
committed
feat: harden block inputs (batch 1)
Hardened 1st batch of five homepage blocks by normalizing config types, clamping unsafe values, and guarding bad inputs so user‑supplied params fail safe without template errors. Changes coerce_bool.html and coerce_int.html: added shared coercion helpers for consistent, safe defaults across blocks. block.html: sanitized content/design inputs, robust user_groups parsing, safer role/affiliation/interest/link handling, and CTA URL validation. block.html: guarded avatar/banner/name/headings/bio, safe button/link resolution, and resilient parsing for affiliations, education, interests, and social links. block.html: hardened filters (kinds/folders/tags), safer sort/order handling, clamped numeric params, and archive link safety/fallbacks. block.html and block.html: defensive content rendering, clamped overlay opacity, and safe button link handling. schema.json, schema.json, schema.json: added view enum entries, added new_tab, and Biome formatting applied.
1 parent 3651909 commit 98f0d20

File tree

10 files changed

+991
-301
lines changed

10 files changed

+991
-301
lines changed

modules/blox/blox/collection/block.html

Lines changed: 204 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -5,37 +5,100 @@
55
{{/* Initialise */}}
66
{{ $page := .wcPage }}
77
{{ $block := .wcBlock }}
8-
{{ $view := $block.design.view | default "card" }}
8+
{{ $content := $block.content }}
9+
{{ if not (reflect.IsMap $content) }}{{ $content = dict }}{{ end }}
10+
{{ $design := $block.design }}
11+
{{ if not (reflect.IsMap $design) }}{{ $design = dict }}{{ end }}
912

10-
{{ $items_offset := $block.content.offset | default 0 }}
11-
{{ $items_count := $block.content.count }}
13+
{{ $view := "card" }}
14+
{{ $view_raw := index $design "view" }}
15+
{{ if eq (printf "%T" $view_raw) "string" }}
16+
{{ $view = lower (strings.TrimSpace $view_raw) }}
17+
{{ end }}
18+
19+
{{ $items_offset := partial "functions/coerce_int" (dict "value" (index $content "offset") "default" 0 "min" 0) }}
20+
{{ $items_count := partial "functions/coerce_int" (dict "value" (index $content "count") "default" 5 "min" 0) }}
1221
{{ if eq $items_count 0 }}
1322
{{ $items_count = 65535 }}
14-
{{ else }}
15-
{{ $items_count = $items_count | default 5 }}
1623
{{ end }}
1724

1825
{{/* Query */}}
1926
{{ $query := site.Pages }}
2027
{{ $archive_page := "" }}
2128

2229
{{/* Filters */}}
23-
{{ $kinds := $block.content.filters.kinds | default (slice "page") }}
30+
{{ $filters := index $content "filters" }}
31+
{{ if not (reflect.IsMap $filters) }}{{ $filters = dict }}{{ end }}
32+
33+
{{ $kinds := slice }}
34+
{{ $kinds_raw := index $filters "kinds" }}
35+
{{ if reflect.IsSlice $kinds_raw }}
36+
{{ range $kinds_raw }}
37+
{{ $kind := strings.TrimSpace (printf "%v" .) }}
38+
{{ if $kind }}
39+
{{ $kinds = $kinds | append $kind }}
40+
{{ end }}
41+
{{ end }}
42+
{{ else if eq (printf "%T" $kinds_raw) "string" }}
43+
{{ $kind := strings.TrimSpace $kinds_raw }}
44+
{{ if $kind }}
45+
{{ $kinds = slice $kind }}
46+
{{ end }}
47+
{{ end }}
48+
{{ if eq (len $kinds) 0 }}
49+
{{ $kinds = slice "page" }}
50+
{{ end }}
2451
{{ $query = where $query "Kind" "in" $kinds }}
2552

26-
{{ if $block.content.page_type }}
27-
{{ $query = where $query "Type" $block.content.page_type }}
28-
{{ $archive_page = site.GetPage "Section" $block.content.page_type }}
53+
{{ $page_type := "" }}
54+
{{ $page_type_raw := index $content "page_type" }}
55+
{{ if eq (printf "%T" $page_type_raw) "string" }}
56+
{{ $page_type = strings.TrimSpace $page_type_raw }}
57+
{{ end }}
58+
{{ if $page_type }}
59+
{{ $query = where $query "Type" $page_type }}
60+
{{ $archive_page = site.GetPage "Section" $page_type }}
2961
{{ end }}
30-
{{ if $block.content.filters.folders }}
31-
{{ $folders := $block.content.filters.folders }}
62+
63+
{{ $folders_raw := index $filters "folders" }}
64+
{{ $folders := slice }}
65+
{{ if reflect.IsSlice $folders_raw }}
66+
{{ range $folders_raw }}
67+
{{ $folder := strings.TrimSpace (printf "%v" .) }}
68+
{{ if $folder }}
69+
{{ $folders = $folders | append $folder }}
70+
{{ end }}
71+
{{ end }}
72+
{{ else if eq (printf "%T" $folders_raw) "string" }}
73+
{{ $folder := strings.TrimSpace $folders_raw }}
74+
{{ if $folder }}
75+
{{ $folders = slice $folder }}
76+
{{ end }}
77+
{{ end }}
78+
{{ if gt (len $folders) 0 }}
3279
{{ $query = where $query "Section" "in" $folders }}
33-
{{/* Init archive page to main folder */}}
34-
{{ $main_folder := index $folders 0 }}
35-
{{ $archive_page = site.GetPage "Section" $main_folder }}
80+
{{ $main_folder := strings.TrimSpace (printf "%v" (index $folders 0)) }}
81+
{{ if $main_folder }}
82+
{{ $archive_page = site.GetPage "Section" $main_folder }}
83+
{{ end }}
84+
{{ end }}
85+
86+
{{ $tags_raw := index $filters "tags" }}
87+
{{ $tags := slice }}
88+
{{ if reflect.IsSlice $tags_raw }}
89+
{{ range $tags_raw }}
90+
{{ $tag := strings.TrimSpace (printf "%v" .) }}
91+
{{ if $tag }}
92+
{{ $tags = $tags | append $tag }}
93+
{{ end }}
94+
{{ end }}
95+
{{ else if eq (printf "%T" $tags_raw) "string" }}
96+
{{ $tag := strings.TrimSpace $tags_raw }}
97+
{{ if $tag }}
98+
{{ $tags = slice $tag }}
99+
{{ end }}
36100
{{ end }}
37-
{{ if $block.content.filters.tags }}
38-
{{ $tags := $block.content.filters.tags }}
101+
{{ if gt (len $tags) 0 }}
39102
{{ $selected := slice }}
40103
{{ range $tags }}
41104
{{ $tag_page := site.GetPage (printf "tags/%s" (urlize .)) }}
@@ -45,47 +108,110 @@
45108
{{ end }}
46109
{{ if gt (len $selected) 0 }}
47110
{{ $query = $query | intersect $selected }}
48-
{{ $archive_page = site.GetPage (printf "tags/%s" (urlize (index $tags 0))) }}
111+
{{ $first_tag := index $tags 0 }}
112+
{{ $archive_page = site.GetPage (printf "tags/%s" (urlize $first_tag)) }}
113+
{{ end }}
114+
{{ end }}
115+
116+
{{ $tag := "" }}
117+
{{ $tag_raw := index $filters "tag" }}
118+
{{ if eq (printf "%T" $tag_raw) "string" }}
119+
{{ $tag = strings.TrimSpace $tag_raw }}
120+
{{ end }}
121+
{{ if $tag }}
122+
{{ with site.GetPage (printf "tags/%s" (urlize $tag)) }}
123+
{{ $archive_page = . }}
124+
{{ $query = $query | intersect .Pages }}
49125
{{ end }}
50126
{{ end }}
51-
{{ if $block.content.filters.tag }}
52-
{{ $archive_page = site.GetPage (printf "tags/%s" (urlize $block.content.filters.tag)) }}
53-
{{ $query = $query | intersect $archive_page.Pages }}
127+
128+
{{ $category := "" }}
129+
{{ $category_raw := index $filters "category" }}
130+
{{ if eq (printf "%T" $category_raw) "string" }}
131+
{{ $category = strings.TrimSpace $category_raw }}
54132
{{ end }}
55-
{{ if $block.content.filters.category }}
56-
{{ $archive_page = site.GetPage (printf "categories/%s" (urlize $block.content.filters.category)) }}
57-
{{ $query = $query | intersect $archive_page.Pages }}
133+
{{ if $category }}
134+
{{ with site.GetPage (printf "categories/%s" (urlize $category)) }}
135+
{{ $archive_page = . }}
136+
{{ $query = $query | intersect .Pages }}
137+
{{ end }}
58138
{{ end }}
59-
{{ if $block.content.filters.publication_type }}
60-
{{ $archive_page = site.GetPage (printf "publication_types/%s" $block.content.filters.publication_type) }}
61-
{{ $query = $query | intersect $archive_page.Pages }}
139+
140+
{{ $publication_type := "" }}
141+
{{ $publication_type_raw := index $filters "publication_type" }}
142+
{{ if eq (printf "%T" $publication_type_raw) "string" }}
143+
{{ $publication_type = strings.TrimSpace $publication_type_raw }}
62144
{{ end }}
63-
{{ if $block.content.filters.exclude_publication_type }}
64-
{{ $query = $query | complement (site.GetPage (printf "publication_types/%s" $block.content.filters.exclude_publication_type)).Pages }}
145+
{{ if $publication_type }}
146+
{{ with site.GetPage (printf "publication_types/%s" $publication_type) }}
147+
{{ $archive_page = . }}
148+
{{ $query = $query | intersect .Pages }}
149+
{{ end }}
65150
{{ end }}
66-
{{ if $block.content.filters.author }}
67-
{{ $archive_page = site.GetPage (printf "authors/%s" (urlize $block.content.filters.author)) }}
68-
{{ $query = $query | intersect $archive_page.Pages }}
151+
152+
{{ $exclude_publication_type := "" }}
153+
{{ $exclude_publication_type_raw := index $filters "exclude_publication_type" }}
154+
{{ if eq (printf "%T" $exclude_publication_type_raw) "string" }}
155+
{{ $exclude_publication_type = strings.TrimSpace $exclude_publication_type_raw }}
156+
{{ end }}
157+
{{ if $exclude_publication_type }}
158+
{{ with site.GetPage (printf "publication_types/%s" $exclude_publication_type) }}
159+
{{ $query = $query | complement .Pages }}
160+
{{ end }}
69161
{{ end }}
70-
{{ if $block.content.filters.featured_only }}
162+
163+
{{ $author_filter := "" }}
164+
{{ $author_filter_raw := index $filters "author" }}
165+
{{ if eq (printf "%T" $author_filter_raw) "string" }}
166+
{{ $author_filter = strings.TrimSpace $author_filter_raw }}
167+
{{ end }}
168+
{{ if $author_filter }}
169+
{{ with site.GetPage (printf "authors/%s" (urlize $author_filter)) }}
170+
{{ $archive_page = . }}
171+
{{ $query = $query | intersect .Pages }}
172+
{{ end }}
173+
{{ end }}
174+
175+
{{ $featured_only := partial "functions/coerce_bool" (dict "value" (index $filters "featured_only") "default" false) }}
176+
{{ if $featured_only }}
71177
{{ $query = where $query "Params.featured" "==" true }}
72178
{{ end }}
73-
{{ if $block.content.filters.exclude_featured }}
179+
{{ $exclude_featured := partial "functions/coerce_bool" (dict "value" (index $filters "exclude_featured") "default" false) }}
180+
{{ if $exclude_featured }}
74181
{{ $query = where $query "Params.featured" "!=" true }}
75182
{{ end }}
76-
{{ if $block.content.filters.exclude_past }}
183+
{{ $exclude_past := partial "functions/coerce_bool" (dict "value" (index $filters "exclude_past") "default" false) }}
184+
{{ if $exclude_past }}
77185
{{ $query = where $query "Date" ">=" now }}
78186
{{ end }}
79-
{{ if $block.content.filters.exclude_future }}
187+
{{ $exclude_future := partial "functions/coerce_bool" (dict "value" (index $filters "exclude_future") "default" false) }}
188+
{{ if $exclude_future }}
80189
{{ $query = where $query "Date" "<" now }}
81190
{{ end }}
82191

83192
{{ $count := len $query }}
84193

85194
{{/* Sort */}}
86-
{{ $sort_by := $block.content.sort_by | default "Date" }}
195+
{{ $sort_by := "Date" }}
196+
{{ $sort_by_raw := index $content "sort_by" }}
197+
{{ if eq (printf "%T" $sort_by_raw) "string" }}
198+
{{ $sort_by_raw = strings.TrimSpace $sort_by_raw }}
199+
{{ if ne $sort_by_raw "" }}
200+
{{ $sort_by = $sort_by_raw }}
201+
{{ end }}
202+
{{ end }}
87203
{{ $sort_by = partial "functions/get_sort_by_parameter" $sort_by }}
88-
{{ $sort_ascending := $block.content.sort_ascending | default (eq $block.content.order "asc") | default false }}
204+
{{ $sort_ascending := false }}
205+
{{ if isset $content "sort_ascending" }}
206+
{{ $sort_ascending = partial "functions/coerce_bool" (dict "value" (index $content "sort_ascending") "default" false) }}
207+
{{ else }}
208+
{{ $order_raw := index $content "order" }}
209+
{{ if eq (printf "%T" $order_raw) "string" }}
210+
{{ if eq (lower (strings.TrimSpace $order_raw)) "asc" }}
211+
{{ $sort_ascending = true }}
212+
{{ end }}
213+
{{ end }}
214+
{{ end }}
89215
{{ $sort_order := cond $sort_ascending "asc" "desc" }}
90216
{{ $query = sort $query $sort_by $sort_order }}
91217

@@ -104,28 +230,32 @@
104230
{{ end }}
105231
{{ end }}
106232

107-
{{ $columns := $block.design.columns | default "2" }}
233+
{{ $columns := partial "functions/coerce_int" (dict "value" (index $design "columns") "default" 2 "min" 1 "max" 4) }}
234+
{{ $title := "" }}
235+
{{ with index $content "title" }}{{ $title = printf "%v" . | emojify | $page.RenderString }}{{ end }}
236+
{{ $text := "" }}
237+
{{ with index $content "text" }}{{ $text = printf "%v" . | emojify | $page.RenderString }}{{ end }}
108238

109-
{{ if $block.content.title }}
239+
{{ if $title }}
110240
<div class="flex flex-col items-center max-w-prose mx-auto gap-3 justify-center px-6 md:px-0">
111241

112242
<div class="mb-6 text-3xl font-bold text-gray-900 dark:text-white">
113-
{{ $block.content.title | emojify | $page.RenderString }}
243+
{{ $title }}
114244
</div>
115245

116-
{{ with $block.content.text }}<p>{{ . | emojify | $page.RenderString }}</p>{{ end }}
246+
{{ with $text }}<p>{{ . }}</p>{{ end }}
117247
</div>
118248
{{ end }}
119249

120250
<div class="flex flex-col items-center px-6">
121251

122252
{{ $config := dict
123-
"columns" ($block.design.columns | default 2)
253+
"columns" $columns
124254
"len" (len $query)
125-
"fill_image" ($block.design.fill_image | default true)
126-
"show_date" ($block.design.show_date | default true)
127-
"show_read_time" ($block.design.show_read_time | default true)
128-
"show_read_more" ($block.design.show_read_more | default true)
255+
"fill_image" (partial "functions/coerce_bool" (dict "value" (index $design "fill_image") "default" true))
256+
"show_date" (partial "functions/coerce_bool" (dict "value" (index $design "show_date") "default" true))
257+
"show_read_time" (partial "functions/coerce_bool" (dict "value" (index $design "show_read_time") "default" true))
258+
"show_read_more" (partial "functions/coerce_bool" (dict "value" (index $design "show_read_more") "default" true))
129259
}}
130260
{{ partial "functions/render_view" (dict "fragment" "start" "page" $block "item" . "view" $view "config" $config) }}
131261

@@ -138,15 +268,30 @@
138268
</div>
139269

140270
{{/* Archive link */}}
141-
{{ $show_archive_link := $block.content.archive.enable | default (gt $count $items_count) }}
271+
{{ $archive := index $content "archive" }}
272+
{{ if not (reflect.IsMap $archive) }}{{ $archive = dict }}{{ end }}
273+
{{ $show_archive_link := gt $count $items_count }}
274+
{{ if isset $archive "enable" }}
275+
{{ $show_archive_link = partial "functions/coerce_bool" (dict "value" (index $archive "enable") "default" false) }}
276+
{{ end }}
142277
{{ if $show_archive_link | and $archive_page }}
143278

144279
{{ $archive_link := "" }}
145-
{{ if $block.content.archive.link }}
146-
{{ $archive_link = $block.content.archive.link | relLangURL }}
147-
{{ else }}
280+
{{ $archive_link_raw := index $archive "link" }}
281+
{{ if eq (printf "%T" $archive_link_raw) "string" }}
282+
{{ $archive_link = strings.TrimSpace $archive_link_raw }}
283+
{{ end }}
284+
{{ if not $archive_link }}
148285
{{ $archive_link = $archive_page.RelPermalink }}
149286
{{ end }}
287+
{{ if $archive_link }}
288+
{{ $scheme := (urls.Parse $archive_link).Scheme }}
289+
{{ if not $scheme }}
290+
{{ if not (hasPrefix $archive_link "#") }}
291+
{{ $archive_link = $archive_link | relLangURL }}
292+
{{ end }}
293+
{{ end }}
294+
{{ end }}
150295

151296
{{/* Localisation */}}
152297
{{ $items_type := $archive_page.Type }}
@@ -161,13 +306,20 @@
161306
{{ $i18n = "more_pages" }}
162307
{{ end }}
163308

164-
{{ $archive_text := $block.content.archive.text | default (i18n $i18n) | default "See all" }}
309+
{{ $archive_text := "" }}
310+
{{ $archive_text_raw := index $archive "text" }}
311+
{{ if eq (printf "%T" $archive_text_raw) "string" }}
312+
{{ $archive_text = strings.TrimSpace $archive_text_raw }}
313+
{{ end }}
314+
{{ if not $archive_text }}
315+
{{ $archive_text = i18n $i18n | default "See all" }}
316+
{{ end }}
165317

166318
<div class="container mx-auto max-w-screen-lg px-8 xl:px-5 pb-5 lg:pb-8">
167319
<div class="mt-10 flex justify-center">
168320
<a
169321
class="relative inline-flex items-center gap-1 rounded-md border border-gray-300 bg-white px-3 py-2 pl-4 text-sm font-medium text-gray-500 hover:bg-gray-50 focus:z-20 dark:border-gray-500 dark:bg-gray-800 dark:text-gray-300"
170-
href="{{ $archive_link }}">
322+
href="{{ $archive_link | safeURL }}">
171323
<span>{{ $archive_text | emojify }}</span>
172324
</a>
173325
</div>

modules/blox/blox/collection/schema.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@
154154
"properties": {
155155
"view": {
156156
"type": "string",
157-
"enum": ["card", "compact", "showcase", "citation", "list", "masonry"],
157+
"enum": ["card", "compact", "showcase", "citation", "list", "masonry", "article-grid", "date-title-summary", "slides-gallery"],
158158
"description": "Display view type",
159159
"default": "card"
160160
},

0 commit comments

Comments
 (0)