# Changelog All notable changes to this project will be documented in this file. ## [1.1.3] - 2026-03-07 ### Security - **Template output escaping**: Added `|esc_url` filter to all unescaped URL outputs across 8 Twig template partials — `header.html.twig`, `header-offcanvas.html.twig`, `header-transparent.html.twig`, `header-centered.html.twig`, `footer.html.twig`, `footer-columns.html.twig`, `search-form.html.twig`, `comment-item.html.twig`. Covers `site.url`, `item.url`, `child.url`, `user.account_url`, `comment.author_url`, and `comment.edit_url`. ### Added - **Twig escape filters** (`TwigService.php`): Registered `esc_html`, `esc_attr`, and `esc_url` as Twig filters with `['is_safe' => ['html']]` to prevent double-encoding. Complements existing `wpautop` and `wp_kses_post` filters. ## [1.1.2] - 2026-03-01 ### Security - **WidgetRenderer regex hardening**: Combined two separate `preg_replace` calls for h2→h4 heading downgrade into a single regex that only matches `

` elements with the `wp-block-heading` class. The previous approach replaced all `

` tags unconditionally, risking mismatched tags if a widget contained non-block h2 elements. ### Performance - **O(n) comment tree building** (`ContextBuilder`): Replaced O(n²) recursive scan with a parent-indexed lookup map built in a single pass. Each recursion level now iterates only direct children instead of all comments. - **Consolidated sidebar queries** (`ContextBuilder`): Merged three separate sidebar detection branches (`is_home`, `is_page`+sidebar, `is_singular` post) into a single boolean check with one `getSidebarData()` call, eliminating up to 2 redundant calls per request. - **Transient caching for sidebar data** (`ContextBuilder`): `getSidebarRecentPosts()` and `getSidebarTags()` results cached in WordPress transients (1 hour TTL). Invalidation hooks on `save_post` (recent posts) and `create/edit/delete_post_tag` (tags). ### Changed - **Hex-to-RGB consolidation** (`functions.php`): `wp_bootstrap_hex_to_rgb()` now delegates to `wp_bootstrap_hex_to_rgb_array()` instead of duplicating hex parsing logic. Added `ctype_xdigit()` validation and return type hints to all color utility functions. ## [1.1.1] - 2026-02-28 ### Added - **PHPUnit test suite**: 64 unit tests covering `BlockRenderer`, `WidgetRenderer`, `NavWalker`, and `TemplateController` classes with 107 assertions. Uses PHPUnit 11 and Brain\Monkey for WordPress function mocking. - **Test infrastructure**: `WP_HTML_Tag_Processor` functional stub using DOMDocument for testing block renderer HTML manipulation outside WordPress. Empty stubs for `WP_Block` and `WP_Widget` type hints. - **Build pipeline integration**: Tests run automatically before every `npm run build` via `prebuild` hook (`composer exec -- phpunit`). - **CI test job**: New PHPUnit test step in Gitea CI workflow between lint and build-release. Tests must pass before release packages are built. - **Release package exclusions**: `tests/`, `phpunit.xml.dist`, and `.phpunit.cache/` excluded from release ZIP packages with verification step. ## [1.1.0] - 2026-02-28 ### Added - **Block Renderer** (`inc/Block/BlockRenderer.php`): New class that injects Bootstrap 5 classes into WordPress core block HTML output on the frontend via per-block `render_block_{$name}` filters. Handles 8 block types: - `core/table` — `.table` on ``, `.table-striped` when stripes style is active - `core/button` — `.btn` + `.btn-{variant}` or `.btn-outline-{variant}` mapped from WP preset color slugs - `core/buttons` — `.d-flex .flex-wrap .gap-2` on button group wrapper - `core/image` — `.img-fluid` on `` for responsive images - `core/search` — `.input-group` on inner wrapper, `.form-control` on input, `.btn .btn-primary` on button - `core/quote` — `.blockquote` on `
`, `.blockquote-footer` on `` - `core/pullquote` — Same blockquote treatment inside `
` - `core/list` — `.list-group` + `.list-group-item` when `is-style-list-group` block style is selected - **Widget Renderer** (`inc/Block/WidgetRenderer.php`): New class that transforms sidebar widgets into Bootstrap 5 card components via `dynamic_sidebar_params` and `widget_block_content` filters. Wraps each widget in a `.card > .card-body` structure with `.card-title` headings. Downgrades block widget `

` headings to `

` for proper sidebar visual hierarchy. - **Widget SCSS** (`src/scss/_widgets.scss`): New stylesheet for sidebar widget Bootstrap styling — list-group-style list items with border separators, flush-to-card-edge list positioning, Bootstrap form-control styling for select dropdowns, search form input-group layout, tag cloud with pill badges, and secondary-color post dates. - **List Group block style**: New "List Group" style registered for `core/list` blocks — applies Bootstrap `.list-group` and `.list-group-item` classes when selected in the editor. - **Single post sidebar template** (`views/pages/single-sidebar.html.twig`): New two-column layout for blog posts with `col-lg-8` content area and `col-lg-4` sidebar. Includes all single post features (meta, thumbnail, tags, post navigation, comments, more posts). "More posts" section uses `row-cols-md-2` to fit the narrower column. - **Extensibility**: `wp_bootstrap_block_renderer_blocks` filter allows child themes to add/remove block handler mappings. ### Changed - **Post template default** (`inc/Template/TemplateController.php`): Blog posts now render with the sidebar layout by default (`single-sidebar.html.twig`). Posts assigned the "Full Width" template use `single.html.twig` instead. Template selection uses `get_page_template_slug()` with a `match` expression. - **Sidebar data for posts** (`inc/Template/ContextBuilder.php`): Posts always receive sidebar data (recent posts, tags, widgets) regardless of template selection, ensuring the sidebar partial always has data available. - **Widget SCSS import** (`src/scss/style.scss`): Added `_widgets` partial import between Bootstrap Icons and custom styles. ## [1.0.12] - 2026-02-28 ### Fixed - **Admin bar offcanvas padding on desktop** (`functions.php`): Scoped the admin bar offcanvas padding fix to mobile viewports only (`max-width: 991.98px`) so the extra padding does not appear on wide screens where the offcanvas renders inline as a regular navbar. ## [1.0.11] - 2026-02-28 ### Changed - **Offcanvas mobile navigation**: Default header now uses `header-offcanvas.html.twig` instead of `header.html.twig`. Mobile navigation slides in as an offcanvas panel from the right instead of collapsing downward. - **User avatar in offcanvas header**: When logged in, the offcanvas header displays the user's Gravatar and display name linking to the WooCommerce My Account page (or WP admin profile as fallback). Falls back to the site name when logged out. - **Dark mode toggle repositioned**: Moved from the offcanvas body to the offcanvas footer on mobile. Desktop toggle remains in the navbar. ### Added - **User context data** (`inc/Template/ContextBuilder.php`): New `getUserData()` method exposing `user.logged_in`, `user.display_name`, `user.avatar`, and `user.account_url` to all Twig templates. ### Fixed - **Admin bar overlapping offcanvas** (`functions.php`): Inline CSS via `wp_add_inline_style()` adds `padding-top` matching the admin bar height to `.offcanvas` when the admin bar is visible, preventing content overlap. ## [1.0.10] - 2026-02-25 ### Fixed - **Title double-encoding in Twig templates** (`inc/Template/ContextBuilder.php`): WordPress's `get_the_title()` pre-encodes `&` as `&`. When passed to Twig with autoescape enabled, the `&` in `&` was escaped again to `&`, rendering as literal `&` in the browser (e.g. "Bewerbungen & Nachrichten" instead of "Bewerbungen & Nachrichten"). Fixed by wrapping all 6 `get_the_title()` calls with `wp_specialchars_decode()` to decode WordPress entities before Twig. Twig autoescape then properly re-encodes `&` → `&`. This is XSS-safe because Twig still escapes all output. ## [1.0.9] - 2026-02-19 ### Performance - **Color variation CSS transient caching** (`functions.php`): `wp_bootstrap_variation_colors()` now caches the generated inline CSS in a 24-hour WordPress transient keyed by `wp_bootstrap_variation_css_` + an MD5 of the active stylesheet slug. Previously the palette iteration and CSS string building ran on every frontend page load. The transient is immediately invalidated on `switch_theme` and `save_post_wp_global_styles`, so changes made via the Design Editor are reflected instantly. - **Twig template recompilation gated behind `WP_DEBUG`** (`inc/Twig/TwigService.php`): `auto_reload` in the Twig `Environment` constructor was hardcoded to `true`, causing Twig to stat every compiled template file on every request to check for source changes. Changed to `WP_DEBUG` so template recompilation only occurs during development. In production (`WP_DEBUG = false`) compiled Twig templates are served from cache without filesystem mtime checks. ## [1.0.8] - 2026-02-19 ### Security - **Archive XSS hardening**: `ContextBuilder::getArchiveData()` now wraps `get_the_archive_title()` and `get_the_archive_description()` with `wp_kses_post()`. Term descriptions are user-editable by Editors and above; without sanitization an injected `