feat: Bootstrap 5 block renderer, widget cards, and sidebar post layout (v1.1.0)
Add BlockRenderer class injecting Bootstrap classes into 8 core block types
(table, button, buttons, image, search, quote, pullquote, list) via per-block
render_block filters using WP_HTML_Tag_Processor.
Add WidgetRenderer class wrapping sidebar widgets in Bootstrap card components
with h4 heading hierarchy via dynamic_sidebar_params and widget_block_content
filters.
Add widget SCSS stylesheet for list styling, search input-group, tag cloud
pills, and card-flush list positioning.
Add single-sidebar.html.twig as the default post template with two-column
Bootstrap layout (col-lg-8 content, col-lg-4 sidebar). Full-width available
via template selection.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 23:43:43 +01:00
|
|
|
<?php
|
|
|
|
|
/**
|
|
|
|
|
* Widget Renderer.
|
|
|
|
|
*
|
|
|
|
|
* Transforms sidebar widget wrappers into Bootstrap 5 card components
|
|
|
|
|
* and adjusts block widget content (headings, lists) for Bootstrap styling.
|
|
|
|
|
*
|
|
|
|
|
* Card structure:
|
|
|
|
|
* With title: card → card-body → h4.card-title → content
|
|
|
|
|
* Without title: card → card-body → content
|
|
|
|
|
*
|
|
|
|
|
* The title is placed inside card-body as a card-title. This avoids
|
|
|
|
|
* broken HTML when widgets omit the title (WordPress skips before_title
|
|
|
|
|
* and after_title entirely when there is no title to output).
|
|
|
|
|
*
|
|
|
|
|
* @package WPBootstrap\Block
|
|
|
|
|
* @since 1.1.0
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
namespace WPBootstrap\Block;
|
|
|
|
|
|
|
|
|
|
use WP_HTML_Tag_Processor;
|
|
|
|
|
|
|
|
|
|
class WidgetRenderer
|
|
|
|
|
{
|
|
|
|
|
/**
|
|
|
|
|
* Register filters for widget output transformation.
|
|
|
|
|
*/
|
|
|
|
|
public function __construct()
|
|
|
|
|
{
|
|
|
|
|
if ( is_admin() || wp_doing_ajax() ) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
add_filter( 'dynamic_sidebar_params', [ $this, 'wrapWidgetInCard' ] );
|
|
|
|
|
add_filter( 'widget_block_content', [ $this, 'processBlockWidgetContent' ], 10, 3 );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Restructure widget wrapper as a Bootstrap card.
|
|
|
|
|
*
|
|
|
|
|
* Uses card-body for all content with card-title for the heading.
|
|
|
|
|
* This structure works correctly whether or not the widget outputs a title.
|
|
|
|
|
*
|
|
|
|
|
* Note: WordPress runs sprintf on before_widget BEFORE this filter,
|
|
|
|
|
* so %1$s/%2$s are already replaced. We must use the processed values.
|
|
|
|
|
*
|
|
|
|
|
* @param array $params Sidebar parameters.
|
|
|
|
|
* @return array Modified parameters.
|
|
|
|
|
*/
|
|
|
|
|
public function wrapWidgetInCard( array $params ): array
|
|
|
|
|
{
|
|
|
|
|
$widgetId = $params[0]['widget_id'] ?? '';
|
|
|
|
|
$beforeWidget = $params[0]['before_widget'] ?? '';
|
|
|
|
|
|
|
|
|
|
// Extract widget-type classes (e.g. widget_block, widget_search)
|
|
|
|
|
// from the already-processed before_widget, skipping generic
|
|
|
|
|
// wrapper classes that we're replacing.
|
|
|
|
|
$widgetClasses = '';
|
|
|
|
|
if ( preg_match( '/class="([^"]*)"/', $beforeWidget, $matches ) ) {
|
|
|
|
|
$original = array_filter( explode( ' ', $matches[1] ) );
|
|
|
|
|
$skip = [ 'widget', 'mb-4' ];
|
|
|
|
|
$kept = array_diff( $original, $skip );
|
|
|
|
|
$widgetClasses = implode( ' ', $kept );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$params[0]['before_widget'] = sprintf(
|
|
|
|
|
'<div id="%s" class="card mb-3 widget %s"><div class="card-body">',
|
|
|
|
|
esc_attr( $widgetId ),
|
|
|
|
|
esc_attr( $widgetClasses )
|
|
|
|
|
);
|
|
|
|
|
$params[0]['after_widget'] = '</div></div>';
|
|
|
|
|
$params[0]['before_title'] = '<h4 class="card-title h6 text-uppercase fw-semibold">';
|
|
|
|
|
$params[0]['after_title'] = '</h4>';
|
|
|
|
|
|
|
|
|
|
return $params;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Process block widget content to downgrade h2 headings to h4.
|
|
|
|
|
*
|
|
|
|
|
* Block widgets render their headings as <h2 class="wp-block-heading">.
|
|
|
|
|
* Inside a sidebar card, h2 is too large — replace with h4 for proper
|
|
|
|
|
* visual hierarchy.
|
|
|
|
|
*
|
|
|
|
|
* @param string $content Widget block content.
|
|
|
|
|
* @param array $instance Widget instance data.
|
|
|
|
|
* @param \WP_Widget $widget Widget object.
|
|
|
|
|
* @return string Modified content.
|
|
|
|
|
*/
|
|
|
|
|
public function processBlockWidgetContent( string $content, array $instance, \WP_Widget $widget ): string
|
|
|
|
|
{
|
|
|
|
|
if ( empty( $content ) ) {
|
|
|
|
|
return $content;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-01 01:02:12 +01:00
|
|
|
// Replace <h2 ... wp-block-heading ...>...</h2> with <h4> pairs.
|
|
|
|
|
// Single regex ensures only headings with wp-block-heading class are
|
|
|
|
|
// downgraded, preventing mismatched tags if a widget contains other h2s.
|
feat: Bootstrap 5 block renderer, widget cards, and sidebar post layout (v1.1.0)
Add BlockRenderer class injecting Bootstrap classes into 8 core block types
(table, button, buttons, image, search, quote, pullquote, list) via per-block
render_block filters using WP_HTML_Tag_Processor.
Add WidgetRenderer class wrapping sidebar widgets in Bootstrap card components
with h4 heading hierarchy via dynamic_sidebar_params and widget_block_content
filters.
Add widget SCSS stylesheet for list styling, search input-group, tag cloud
pills, and card-flush list positioning.
Add single-sidebar.html.twig as the default post template with two-column
Bootstrap layout (col-lg-8 content, col-lg-4 sidebar). Full-width available
via template selection.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 23:43:43 +01:00
|
|
|
$content = preg_replace(
|
2026-03-01 01:02:12 +01:00
|
|
|
'/<h2(\s+class="[^"]*wp-block-heading[^"]*"[^>]*)>(.*?)<\/h2>/s',
|
|
|
|
|
'<h4$1>$2</h4>',
|
feat: Bootstrap 5 block renderer, widget cards, and sidebar post layout (v1.1.0)
Add BlockRenderer class injecting Bootstrap classes into 8 core block types
(table, button, buttons, image, search, quote, pullquote, list) via per-block
render_block filters using WP_HTML_Tag_Processor.
Add WidgetRenderer class wrapping sidebar widgets in Bootstrap card components
with h4 heading hierarchy via dynamic_sidebar_params and widget_block_content
filters.
Add widget SCSS stylesheet for list styling, search input-group, tag cloud
pills, and card-flush list positioning.
Add single-sidebar.html.twig as the default post template with two-column
Bootstrap layout (col-lg-8 content, col-lg-4 sidebar). Full-width available
via template selection.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 23:43:43 +01:00
|
|
|
$content
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return $content;
|
|
|
|
|
}
|
|
|
|
|
}
|