Skip to content

Commit ba01bf7

Browse files
committed
Release 1.1.0: comment protection, object access, and demo admin chrome.
1 parent 0faac53 commit ba01bf7

127 files changed

Lines changed: 5768 additions & 338 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.symfony/recipe/nowo-tech/blog-kit-bundle/1.0/config/packages/nowo_blog_kit.yaml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,33 @@ nowo_blog_kit:
88
moderate_roles: [ROLE_MODERATOR]
99
configure_roles: [ROLE_ADMIN]
1010
allow_unauthenticated: false
11+
object_access:
12+
strategy: none
13+
# strategy: owner
14+
# strategy: service
15+
# service: App\Security\BlogPublicationAccessChecker
1116
web_ui:
1217
layout_template: 'admin/layout.html.twig'
1318
public_layout_template: 'base.html.twig'
1419
css_framework: bootstrap5
1520
icon_set: bootstrap-icons
1621
row_actions_display: icon
22+
listing:
23+
mode: paginated
24+
masonry:
25+
strategy: masonry
26+
columns_mobile: 1
27+
columns_tablet: 2
28+
columns_desktop: 2
29+
comments:
30+
rate_limit:
31+
strategy: fixed_window
32+
limit: 5
33+
interval_seconds: 60
34+
captcha:
35+
strategy: honeypot
36+
html:
37+
sanitize:
38+
strategy: none
1739
doctrine:
1840
table_prefix: ''

.symfony/recipe/nowo-tech/blog-kit-bundle/1.0/config/packages/nowo_form_kit.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
# GET list filters (AbstractGetFilterType) require profile `filter`.
2+
# `entity` is required by BlogArticleType (tags). BlogKit also prepends this type_map.
23
nowo_form_kit:
34
default_profile: default
5+
type_map:
6+
entity: Symfony\Bridge\Doctrine\Form\Type\EntityType
47
profiles:
58
filter:
69
alias: filter

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,13 @@ Blog Kit Bundle gives Symfony applications a reusable blog domain backed by Doct
2020

2121
- Multilingual articles and tags with locale fallback (`es`, `en` by default)
2222
- Public index and article routes at `/blog` and `/blog/{slug}`
23-
- Moderated comments with staff replies
23+
- Moderated comments with staff replies, configurable rate limits, and CAPTCHA strategies
24+
- Optional article HTML sanitizer (`none`, `strip`, `allowlist`, or a host service)
2425
- Admin CRUD for articles, tags, comments, and singleton blog settings
2526
- Paginated or infinite-scroll listing, configurable asides, and card options
2627
- LinkedIn hashtag formatting command `nowo:blog:sync-hashtags`
2728
- `BlogArticlePublishedEvent` after an article becomes published
28-
- Configurable access: `access_roles`, manage / moderate / configure roles, custom checker, or demo-only unauthenticated mode
29+
- Configurable access: `access_roles`, manage / moderate / configure roles, custom `access_checker`, and object-level publication rules (`security.object_access`: `none` / `owner` / host `service`) via `BlogKitAccessDenied`
2930
- Host CSS stack via `web_ui.css_framework` (`bootstrap5`, `tailwind`, `foundation`, `custom`), `icon_set`, and `row_actions_display` without forking page templates
3031
- Twig namespace `NowoBlogKitBundle` with host override precedence
3132
- Symfony Flex recipe and FrankenPHP demo in `demo/symfony8`
@@ -49,6 +50,8 @@ nowo_blog_kit:
4950
moderate_roles: [ROLE_MODERATOR]
5051
configure_roles: [ROLE_ADMIN]
5152
allow_unauthenticated: false
53+
# object_access:
54+
# strategy: owner
5255
web_ui:
5356
layout_template: admin/layout.html.twig
5457
public_layout_template: base.html.twig

demo/symfony8/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ Default URL: **http://localhost:8105** (override with `PORT` in `.env`).
1515
## What to try
1616

1717
1. Open `/` — landing page with links to the public blog and admin.
18-
2. Open `/blog` — 32 published lorem ipsum articles with 12 distinct tags (paginated), wrapped in the demo layout (`css_framework: bootstrap5`).
19-
3. Open `/admin/blog` — admin UI (HTTP Basic: `admin` / `admin`).
18+
2. Open `/blog` — 32 published lorem ipsum articles with 12 distinct tags (infinite scroll, masonry), wrapped in the demo layout (`css_framework: bootstrap5`). Switch `listing.mode` to `paginated` or `listing.masonry.strategy` to `grid` / `list` in `config/packages/nowo_blog_kit.yaml`.
19+
3. Open `/admin/blog` — admin UI in the demo masthead (HTTP Basic: `admin` / `admin`). Bootstrap 5 + Icons, FormKit `blog_kit` / `filter` profiles, and `bootstrap_5_layout`. Optional `security.object_access.strategy: owner` in `nowo_blog_kit.yaml`.
2020

2121
Demo content is seeded on `make up` (`app:load-demo-blog`). Recreate with:
2222

demo/symfony8/composer.lock

Lines changed: 9 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

demo/symfony8/config/packages/nowo_blog_kit.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,30 @@ nowo_blog_kit:
88
moderate_roles: [ROLE_ADMIN]
99
configure_roles: [ROLE_ADMIN]
1010
allow_unauthenticated: false
11+
# object_access:
12+
# strategy: owner
1113
web_ui:
14+
layout_template: admin/layout.html.twig
1215
public_layout_template: base.html.twig
1316
css_framework: bootstrap5
1417
icon_set: bootstrap-icons
1518
row_actions_display: icon
19+
listing:
20+
mode: infinite
21+
masonry:
22+
strategy: masonry
23+
columns_mobile: 1
24+
columns_tablet: 2
25+
columns_desktop: 2
26+
comments:
27+
rate_limit:
28+
strategy: fixed_window
29+
limit: 5
30+
interval_seconds: 60
31+
captcha:
32+
strategy: honeypot
33+
html:
34+
sanitize:
35+
strategy: allowlist
1636
doctrine:
1737
table_prefix: ''

demo/symfony8/config/packages/nowo_form_kit.yaml

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
# FormKit profiles used by BlogKit admin (GET list filters use profile `filter`).
2-
# `blog_kit` is also prepended by NowoBlogKitExtension; this file keeps `filter` in the host app.
1+
# FormKit: css_framework is bootstrap|tailwind|foundation|none (no bootstrap5).
2+
# UiKit / BlogKit use bootstrap5. Pair this with twig.form_themes → bootstrap_5_layout.
3+
# Profiles `blog_kit` (CRUD) and `filter` (GET lists) skip NowoBlogKitExtension prepends when defined here.
34
nowo_form_kit:
45
default_profile: default
56
css_framework: bootstrap
7+
type_map:
8+
entity: Symfony\Bridge\Doctrine\Form\Type\EntityType
69
profiles:
710
default:
811
alias: default
@@ -12,6 +15,39 @@ nowo_form_kit:
1215
class: form-control
1316
row_attr:
1417
class: mb-3
18+
field_types:
19+
checkbox:
20+
attr:
21+
class: form-check-input
22+
row_attr:
23+
class: form-check mb-3
24+
choice:
25+
attr:
26+
class: form-select
27+
blog_kit:
28+
alias: blog_kit
29+
translation_domain: NowoBlogKitBundle
30+
defaults:
31+
attr:
32+
class: nowo-ui-input form-control
33+
row_attr:
34+
class: mb-3
35+
field_types:
36+
checkbox:
37+
attr:
38+
class: form-check-input
39+
row_attr:
40+
class: form-check mb-3
41+
choice:
42+
attr:
43+
class: form-select
44+
entity:
45+
attr:
46+
class: form-select
47+
textarea:
48+
attr:
49+
class: nowo-ui-input form-control
50+
rows: '6'
1551
filter:
1652
alias: filter
1753
translation_domain: NowoBlogKitBundle
@@ -20,3 +56,16 @@ nowo_form_kit:
2056
defaults:
2157
label: false
2258
required: false
59+
attr:
60+
class: form-control form-control-sm
61+
row_attr:
62+
class: mb-0
63+
field_types:
64+
checkbox:
65+
attr:
66+
class: form-check-input
67+
row_attr:
68+
class: form-check mb-0
69+
choice:
70+
attr:
71+
class: form-select form-select-sm
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
nowo_ui_kit:
22
css_framework: bootstrap5
33
icon_set: bootstrap-icons
4+
row_actions_display: icon
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,7 @@
11
twig:
22
default_path: '%kernel.project_dir%/templates'
3+
form_themes:
4+
# FormKit static blocks first; Bootstrap 5 must win for checkbox / radio / select.
5+
- '@NowoFormKitBundle/form/static_blocks.html.twig'
6+
- '@NowoBlogKitBundle/admin/form_theme.html.twig'
7+
- bootstrap_5_layout.html.twig

demo/symfony8/config/reference.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1306,6 +1306,10 @@
13061306
* configure_roles?: list<scalar|Param|null>,
13071307
* access_checker?: scalar|Param|null, // Default: null
13081308
* allow_unauthenticated?: bool|Param, // Default: false
1309+
* object_access?: array{ // Object-level checks after roles (e.g. editors only manage their publications).
1310+
* strategy?: "none"|"owner"|"service"|Param, // none (roles only), owner (createdBy), or service (host checker). // Default: "none"
1311+
* service?: scalar|Param|null, // Service id implementing BlogKitResourceAccessCheckerInterface when strategy=service. // Default: null
1312+
* },
13091313
* },
13101314
* web_ui?: array{
13111315
* layout_template?: scalar|Param|null, // Default: "@NowoBlogKitBundle/admin/layout.html.twig"
@@ -1320,6 +1324,37 @@
13201324
* table_prefix?: scalar|Param|null, // Default: ""
13211325
* connection?: scalar|Param|null, // Default: "default"
13221326
* },
1327+
* comments?: array{
1328+
* rate_limit?: array{ // Public comment POST rate limiting. Requires cache.app except strategy none/service.
1329+
* strategy?: "none"|"fixed_window"|"per_ip_article"|"sliding_window"|"service"|Param, // Default: "fixed_window"
1330+
* limit?: int|Param, // Max submissions per interval. 0 disables the limiter. // Default: 5
1331+
* interval_seconds?: int|Param, // Default: 60
1332+
* service?: scalar|Param|null, // Service id implementing BlogCommentRateLimiterInterface when strategy=service. // Default: null
1333+
* },
1334+
* captcha?: array{ // Public comment bot mitigation. Secrets stay in YAML, not BlogSettings.
1335+
* strategy?: "none"|"honeypot"|"recaptcha_v2"|"recaptcha_v3"|"hcaptcha"|"turnstile"|"service"|Param, // Default: "honeypot"
1336+
* site_key?: scalar|Param|null, // Default: ""
1337+
* secret_key?: scalar|Param|null, // Default: ""
1338+
* min_score?: float|Param, // Minimum reCAPTCHA v3 score. // Default: 0.5
1339+
* honeypot_field?: scalar|Param|null, // Default: "website"
1340+
* service?: scalar|Param|null, // Service id implementing BlogCommentCaptchaStrategyInterface when strategy=service. // Default: null
1341+
* },
1342+
* },
1343+
* listing?: array{ // Public /blog index: numbered pagination or infinite scroll.
1344+
* mode?: "paginated"|"infinite"|Param, // paginated (numbered pages) or infinite (IntersectionObserver + ?partial=1). // Default: "paginated"
1345+
* masonry?: array{ // Public index card layout. Admin inherit / 0 keeps these YAML values.
1346+
* strategy?: "masonry"|"grid"|"list"|Param, // masonry (packed columns), grid (uniform rows), or list (single column). // Default: "masonry"
1347+
* columns_mobile?: int|Param, // Default: 1
1348+
* columns_tablet?: int|Param, // Default: 2
1349+
* columns_desktop?: int|Param, // Default: 2
1350+
* },
1351+
* },
1352+
* html?: array{
1353+
* sanitize?: array{ // Sanitize article HTML on persist and public render. Default none keeps trusted-editor |raw.
1354+
* strategy?: "none"|"strip"|"allowlist"|"service"|Param, // Default: "none"
1355+
* service?: scalar|Param|null, // Service id implementing BlogHtmlSanitizerInterface when strategy=service. // Default: null
1356+
* },
1357+
* },
13231358
* }
13241359
* @psalm-type NowoUiKitConfig = array{
13251360
* css_framework?: "bootstrap"|"bootstrap5"|"bootstrap4"|"tailwind"|"foundation"|"custom"|"tabler"|"none"|Param, // Host CSS stack: bootstrap5|bootstrap4|tailwind|foundation|custom|none|tabler (bootstrap alias → bootstrap5). // Default: "bootstrap5"

0 commit comments

Comments
 (0)