I’ll build this as a reusable advanced WordPress theme sidebar builder file for inc/builders/sidebar-builder.php, with registration, dynamic sidebars, layout helpers, widget rendering, fallback content, schema-friendly markup, and theme hooks.
Below is an advanced, complete inc/builders/sidebar-builder.php file for your rx theme. It includes dynamic sidebar registration, multiple sidebar areas, layout helpers, sticky sidebar support, WooCommerce sidebars, BuddyPress/bbPress support, fallback widgets, conditional sidebar logic, schema markup, sanitization, hooks, filters, CSS class builders, and helper functions.
Create this file:
mkdir -p inc/builders
touch inc/builders/sidebar-builder.php
Then paste this full code inside:
<?php
/**
* RX Theme Sidebar Builder
*
* File: inc/builders/sidebar-builder.php
*
* This file controls all sidebar-related functionality for the RX Theme.
* It registers sidebar/widget areas, renders sidebars, builds sidebar layouts,
* supports conditional sidebars, sticky sidebars, WooCommerce sidebars,
* footer widget columns, fallback content, schema-ready markup, and developer hooks.
*
* @package RX_Theme
*/
defined( 'ABSPATH' ) || exit;
if ( ! class_exists( 'RX_Sidebar_Builder' ) ) :
/**
* Main RX Sidebar Builder Class.
*/
final class RX_Sidebar_Builder {
/**
* Class instance.
*
* @var RX_Sidebar_Builder|null
*/
private static $instance = null;
/**
* Theme prefix.
*
* @var string
*/
private $prefix = 'rx';
/**
* Default sidebar layout.
*
* Available values:
* - right-sidebar
* - left-sidebar
* - no-sidebar
* - full-width
*
* @var string
*/
private $default_layout = 'right-sidebar';
/**
* Registered sidebar definitions.
*
* @var array
*/
private $sidebars = array();
/**
* Get instance.
*
* @return RX_Sidebar_Builder
*/
public static function instance() {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Constructor.
*/
private function __construct() {
$this->sidebars = $this->get_default_sidebars();
add_action( 'widgets_init', array( $this, 'register_sidebars' ) );
add_filter( 'body_class', array( $this, 'body_classes' ) );
add_filter( 'rx_theme_layout', array( $this, 'filter_theme_layout' ) );
add_action( 'rx_before_sidebar', array( $this, 'before_sidebar_hook' ), 10, 1 );
add_action( 'rx_after_sidebar', array( $this, 'after_sidebar_hook' ), 10, 1 );
}
/**
* Get default sidebars.
*
* @return array
*/
private function get_default_sidebars() {
$sidebars = array(
'primary-sidebar' => array(
'name' => esc_html__( 'Primary Sidebar', 'rx-theme' ),
'description' => esc_html__( 'Main sidebar used on posts, pages, archive pages, and blog layouts.', 'rx-theme' ),
'before_widget' => '<section id="%1$s" class="widget rx-widget %2$s">',
'after_widget' => '</section>',
'before_title' => '<h2 class="widget-title rx-widget-title">',
'after_title' => '</h2>',
),
'blog-sidebar' => array(
'name' => esc_html__( 'Blog Sidebar', 'rx-theme' ),
'description' => esc_html__( 'Sidebar for blog index, category, tag, author, date, and search archive pages.', 'rx-theme' ),
'before_widget' => '<section id="%1$s" class="widget rx-widget rx-blog-widget %2$s">',
'after_widget' => '</section>',
'before_title' => '<h2 class="widget-title rx-widget-title">',
'after_title' => '</h2>',
),
'single-post-sidebar' => array(
'name' => esc_html__( 'Single Post Sidebar', 'rx-theme' ),
'description' => esc_html__( 'Sidebar displayed on single blog posts.', 'rx-theme' ),
'before_widget' => '<section id="%1$s" class="widget rx-widget rx-single-widget %2$s">',
'after_widget' => '</section>',
'before_title' => '<h2 class="widget-title rx-widget-title">',
'after_title' => '</h2>',
),
'page-sidebar' => array(
'name' => esc_html__( 'Page Sidebar', 'rx-theme' ),
'description' => esc_html__( 'Sidebar displayed on static pages.', 'rx-theme' ),
'before_widget' => '<section id="%1$s" class="widget rx-widget rx-page-widget %2$s">',
'after_widget' => '</section>',
'before_title' => '<h2 class="widget-title rx-widget-title">',
'after_title' => '</h2>',
),
'home-sidebar' => array(
'name' => esc_html__( 'Home Sidebar', 'rx-theme' ),
'description' => esc_html__( 'Sidebar displayed on the homepage or front page.', 'rx-theme' ),
'before_widget' => '<section id="%1$s" class="widget rx-widget rx-home-widget %2$s">',
'after_widget' => '</section>',
'before_title' => '<h2 class="widget-title rx-widget-title">',
'after_title' => '</h2>',
),
'search-sidebar' => array(
'name' => esc_html__( 'Search Sidebar', 'rx-theme' ),
'description' => esc_html__( 'Sidebar displayed on search result pages.', 'rx-theme' ),
'before_widget' => '<section id="%1$s" class="widget rx-widget rx-search-widget %2$s">',
'after_widget' => '</section>',
'before_title' => '<h2 class="widget-title rx-widget-title">',
'after_title' => '</h2>',
),
'404-sidebar' => array(
'name' => esc_html__( '404 Sidebar', 'rx-theme' ),
'description' => esc_html__( 'Sidebar displayed on 404 error pages.', 'rx-theme' ),
'before_widget' => '<section id="%1$s" class="widget rx-widget rx-404-widget %2$s">',
'after_widget' => '</section>',
'before_title' => '<h2 class="widget-title rx-widget-title">',
'after_title' => '</h2>',
),
'header-widget-area' => array(
'name' => esc_html__( 'Header Widget Area', 'rx-theme' ),
'description' => esc_html__( 'Widget area displayed inside or near the header.', 'rx-theme' ),
'before_widget' => '<div id="%1$s" class="widget rx-widget rx-header-widget %2$s">',
'after_widget' => '</div>',
'before_title' => '<h2 class="widget-title rx-widget-title">',
'after_title' => '</h2>',
),
'above-content' => array(
'name' => esc_html__( 'Above Content Widget Area', 'rx-theme' ),
'description' => esc_html__( 'Widget area displayed above the main content.', 'rx-theme' ),
'before_widget' => '<section id="%1$s" class="widget rx-widget rx-above-content-widget %2$s">',
'after_widget' => '</section>',
'before_title' => '<h2 class="widget-title rx-widget-title">',
'after_title' => '</h2>',
),
'below-content' => array(
'name' => esc_html__( 'Below Content Widget Area', 'rx-theme' ),
'description' => esc_html__( 'Widget area displayed below the main content.', 'rx-theme' ),
'before_widget' => '<section id="%1$s" class="widget rx-widget rx-below-content-widget %2$s">',
'after_widget' => '</section>',
'before_title' => '<h2 class="widget-title rx-widget-title">',
'after_title' => '</h2>',
),
'after-post-content' => array(
'name' => esc_html__( 'After Post Content', 'rx-theme' ),
'description' => esc_html__( 'Widget area displayed after single post content.', 'rx-theme' ),
'before_widget' => '<section id="%1$s" class="widget rx-widget rx-after-post-widget %2$s">',
'after_widget' => '</section>',
'before_title' => '<h2 class="widget-title rx-widget-title">',
'after_title' => '</h2>',
),
'mobile-sidebar' => array(
'name' => esc_html__( 'Mobile Sidebar / Off Canvas', 'rx-theme' ),
'description' => esc_html__( 'Widget area for mobile menu, off-canvas panel, or mobile-only sidebar.', 'rx-theme' ),
'before_widget' => '<section id="%1$s" class="widget rx-widget rx-mobile-widget %2$s">',
'after_widget' => '</section>',
'before_title' => '<h2 class="widget-title rx-widget-title">',
'after_title' => '</h2>',
),
'footer-1' => array(
'name' => esc_html__( 'Footer Column 1', 'rx-theme' ),
'description' => esc_html__( 'First footer widget column.', 'rx-theme' ),
'before_widget' => '<section id="%1$s" class="widget rx-widget rx-footer-widget %2$s">',
'after_widget' => '</section>',
'before_title' => '<h2 class="widget-title rx-widget-title">',
'after_title' => '</h2>',
),
'footer-2' => array(
'name' => esc_html__( 'Footer Column 2', 'rx-theme' ),
'description' => esc_html__( 'Second footer widget column.', 'rx-theme' ),
'before_widget' => '<section id="%1$s" class="widget rx-widget rx-footer-widget %2$s">',
'after_widget' => '</section>',
'before_title' => '<h2 class="widget-title rx-widget-title">',
'after_title' => '</h2>',
),
'footer-3' => array(
'name' => esc_html__( 'Footer Column 3', 'rx-theme' ),
'description' => esc_html__( 'Third footer widget column.', 'rx-theme' ),
'before_widget' => '<section id="%1$s" class="widget rx-widget rx-footer-widget %2$s">',
'after_widget' => '</section>',
'before_title' => '<h2 class="widget-title rx-widget-title">',
'after_title' => '</h2>',
),
'footer-4' => array(
'name' => esc_html__( 'Footer Column 4', 'rx-theme' ),
'description' => esc_html__( 'Fourth footer widget column.', 'rx-theme' ),
'before_widget' => '<section id="%1$s" class="widget rx-widget rx-footer-widget %2$s">',
'after_widget' => '</section>',
'before_title' => '<h2 class="widget-title rx-widget-title">',
'after_title' => '</h2>',
),
);
if ( class_exists( 'WooCommerce' ) ) {
$sidebars['shop-sidebar'] = array(
'name' => esc_html__( 'WooCommerce Shop Sidebar', 'rx-theme' ),
'description' => esc_html__( 'Sidebar displayed on WooCommerce shop, product category, product tag, and product archive pages.', 'rx-theme' ),
'before_widget' => '<section id="%1$s" class="widget rx-widget rx-shop-widget %2$s">',
'after_widget' => '</section>',
'before_title' => '<h2 class="widget-title rx-widget-title">',
'after_title' => '</h2>',
);
$sidebars['single-product-sidebar'] = array(
'name' => esc_html__( 'Single Product Sidebar', 'rx-theme' ),
'description' => esc_html__( 'Sidebar displayed on single WooCommerce product pages.', 'rx-theme' ),
'before_widget' => '<section id="%1$s" class="widget rx-widget rx-product-widget %2$s">',
'after_widget' => '</section>',
'before_title' => '<h2 class="widget-title rx-widget-title">',
'after_title' => '</h2>',
);
}
if ( function_exists( 'bp_is_active' ) ) {
$sidebars['buddypress-sidebar'] = array(
'name' => esc_html__( 'BuddyPress Sidebar', 'rx-theme' ),
'description' => esc_html__( 'Sidebar displayed on BuddyPress pages.', 'rx-theme' ),
'before_widget' => '<section id="%1$s" class="widget rx-widget rx-buddypress-widget %2$s">',
'after_widget' => '</section>',
'before_title' => '<h2 class="widget-title rx-widget-title">',
'after_title' => '</h2>',
);
}
if ( class_exists( 'bbPress' ) ) {
$sidebars['bbpress-sidebar'] = array(
'name' => esc_html__( 'bbPress Sidebar', 'rx-theme' ),
'description' => esc_html__( 'Sidebar displayed on bbPress forum pages.', 'rx-theme' ),
'before_widget' => '<section id="%1$s" class="widget rx-widget rx-bbpress-widget %2$s">',
'after_widget' => '</section>',
'before_title' => '<h2 class="widget-title rx-widget-title">',
'after_title' => '</h2>',
);
}
/**
* Filter sidebar definitions.
*
* Example:
* add_filter( 'rx_sidebar_definitions', function( $sidebars ) {
* $sidebars['custom-sidebar'] = array(
* 'name' => 'Custom Sidebar',
* 'description' => 'My custom sidebar',
* );
* return $sidebars;
* } );
*/
return apply_filters( 'rx_sidebar_definitions', $sidebars );
}
/**
* Register all sidebars.
*
* @return void
*/
public function register_sidebars() {
foreach ( $this->sidebars as $id => $args ) {
$defaults = array(
'id' => sanitize_key( $id ),
'name' => ucwords( str_replace( '-', ' ', $id ) ),
'description' => '',
'before_widget' => '<section id="%1$s" class="widget rx-widget %2$s">',
'after_widget' => '</section>',
'before_title' => '<h2 class="widget-title rx-widget-title">',
'after_title' => '</h2>',
);
$args = wp_parse_args( $args, $defaults );
$args['id'] = sanitize_key( $id );
register_sidebar( $args );
}
}
/**
* Get all registered RX sidebar IDs.
*
* @return array
*/
public function get_sidebar_ids() {
return array_keys( $this->sidebars );
}
/**
* Check if a sidebar exists in RX sidebar list.
*
* @param string $sidebar_id Sidebar ID.
* @return bool
*/
public function sidebar_exists( $sidebar_id ) {
$sidebar_id = sanitize_key( $sidebar_id );
return isset( $this->sidebars[ $sidebar_id ] );
}
/**
* Get the best sidebar ID for the current request.
*
* @return string
*/
public function get_current_sidebar_id() {
$sidebar_id = 'primary-sidebar';
if ( is_front_page() || is_home() ) {
$sidebar_id = is_home() ? 'blog-sidebar' : 'home-sidebar';
}
if ( is_single() && 'post' === get_post_type() ) {
$sidebar_id = 'single-post-sidebar';
}
if ( is_page() ) {
$sidebar_id = 'page-sidebar';
}
if ( is_archive() ) {
$sidebar_id = 'blog-sidebar';
}
if ( is_search() ) {
$sidebar_id = 'search-sidebar';
}
if ( is_404() ) {
$sidebar_id = '404-sidebar';
}
if ( class_exists( 'WooCommerce' ) ) {
if ( function_exists( 'is_shop' ) && ( is_shop() || is_product_taxonomy() ) ) {
$sidebar_id = 'shop-sidebar';
}
if ( function_exists( 'is_product' ) && is_product() ) {
$sidebar_id = 'single-product-sidebar';
}
}
if ( function_exists( 'bp_is_active' ) && function_exists( 'is_buddypress' ) && is_buddypress() ) {
$sidebar_id = 'buddypress-sidebar';
}
if ( class_exists( 'bbPress' ) && function_exists( 'is_bbpress' ) && is_bbpress() ) {
$sidebar_id = 'bbpress-sidebar';
}
/**
* Filter current sidebar ID.
*/
return sanitize_key( apply_filters( 'rx_current_sidebar_id', $sidebar_id ) );
}
/**
* Get sidebar layout.
*
* @return string
*/
public function get_layout() {
$layout = get_theme_mod( 'rx_sidebar_layout', $this->default_layout );
if ( is_singular() ) {
$post_id = get_queried_object_id();
if ( $post_id ) {
$post_layout = get_post_meta( $post_id, '_rx_sidebar_layout', true );
if ( ! empty( $post_layout ) ) {
$layout = $post_layout;
}
}
}
if ( is_page_template( 'templates/full-width.php' ) ) {
$layout = 'full-width';
}
if ( is_page_template( 'templates/no-sidebar.php' ) ) {
$layout = 'no-sidebar';
}
if ( is_attachment() ) {
$layout = 'no-sidebar';
}
if ( is_404() ) {
$layout = get_theme_mod( 'rx_404_sidebar_layout', 'no-sidebar' );
}
if ( is_search() ) {
$layout = get_theme_mod( 'rx_search_sidebar_layout', $layout );
}
if ( class_exists( 'WooCommerce' ) ) {
if ( function_exists( 'is_cart' ) && ( is_cart() || is_checkout() || is_account_page() ) ) {
$layout = 'no-sidebar';
}
}
$allowed = array(
'right-sidebar',
'left-sidebar',
'no-sidebar',
'full-width',
);
if ( ! in_array( $layout, $allowed, true ) ) {
$layout = $this->default_layout;
}
return apply_filters( 'rx_sidebar_layout', $layout );
}
/**
* Filter theme layout.
*
* @param string $layout Existing layout.
* @return string
*/
public function filter_theme_layout( $layout ) {
$current = $this->get_layout();
return ! empty( $current ) ? $current : $layout;
}
/**
* Whether current page should show sidebar.
*
* @return bool
*/
public function should_show_sidebar() {
$layout = $this->get_layout();
if ( in_array( $layout, array( 'no-sidebar', 'full-width' ), true ) ) {
return false;
}
$sidebar_id = $this->get_current_sidebar_id();
if ( ! is_active_sidebar( $sidebar_id ) && ! $this->fallback_enabled() ) {
return false;
}
return (bool) apply_filters( 'rx_should_show_sidebar', true, $layout, $sidebar_id );
}
/**
* Is fallback sidebar enabled?
*
* @return bool
*/
public function fallback_enabled() {
return (bool) apply_filters(
'rx_sidebar_fallback_enabled',
get_theme_mod( 'rx_sidebar_fallback_enabled', true )
);
}
/**
* Render current sidebar.
*
* @param string $sidebar_id Optional sidebar ID.
* @param array $args Optional args.
* @return void
*/
public function render_sidebar( $sidebar_id = '', $args = array() ) {
if ( empty( $sidebar_id ) ) {
$sidebar_id = $this->get_current_sidebar_id();
}
$sidebar_id = sanitize_key( $sidebar_id );
$defaults = array(
'echo' => true,
'wrapper' => true,
'fallback' => true,
'sticky' => get_theme_mod( 'rx_sticky_sidebar', true ),
'aria_label' => esc_html__( 'Sidebar', 'rx-theme' ),
'schema' => true,
'custom_class' => '',
'inner_class' => '',
'container_tag' => 'aside',
'inner_tag' => 'div',
);
$args = wp_parse_args( $args, $defaults );
if ( ! $this->should_render_sidebar_id( $sidebar_id, $args ) ) {
return;
}
$output = '';
if ( $args['wrapper'] ) {
$output .= $this->get_sidebar_open_markup( $sidebar_id, $args );
}
if ( is_active_sidebar( $sidebar_id ) ) {
ob_start();
dynamic_sidebar( $sidebar_id );
$output .= ob_get_clean();
} elseif ( true === $args['fallback'] && $this->fallback_enabled() ) {
$output .= $this->get_fallback_sidebar_content( $sidebar_id );
}
if ( $args['wrapper'] ) {
$output .= $this->get_sidebar_close_markup( $args );
}
$output = apply_filters( 'rx_render_sidebar_output', $output, $sidebar_id, $args );
if ( $args['echo'] ) {
echo $output; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
return;
}
return $output;
}
/**
* Check whether a specific sidebar should render.
*
* @param string $sidebar_id Sidebar ID.
* @param array $args Render args.
* @return bool
*/
private function should_render_sidebar_id( $sidebar_id, $args ) {
if ( empty( $sidebar_id ) ) {
return false;
}
if ( ! is_active_sidebar( $sidebar_id ) && empty( $args['fallback'] ) ) {
return false;
}
if ( ! is_active_sidebar( $sidebar_id ) && ! $this->fallback_enabled() ) {
return false;
}
return (bool) apply_filters( 'rx_should_render_sidebar_id', true, $sidebar_id, $args );
}
/**
* Get sidebar open markup.
*
* @param string $sidebar_id Sidebar ID.
* @param array $args Args.
* @return string
*/
private function get_sidebar_open_markup( $sidebar_id, $args ) {
$tag = tag_escape( $args['container_tag'] );
$classes = $this->get_sidebar_classes(
$sidebar_id,
array(
'sticky' => $args['sticky'],
'custom_class' => $args['custom_class'],
)
);
$attributes = array(
'id' => 'secondary',
'class' => implode( ' ', array_map( 'sanitize_html_class', $classes ) ),
'aria-label' => $args['aria_label'],
);
if ( $args['schema'] ) {
$attributes['itemscope'] = 'itemscope';
$attributes['itemtype'] = 'https://schema.org/WPSideBar';
}
$attributes = apply_filters( 'rx_sidebar_wrapper_attributes', $attributes, $sidebar_id, $args );
$output = '';
$output .= sprintf( '<%1$s %2$s>', $tag, $this->build_attributes( $attributes ) );
$output .= "\n";
do_action( 'rx_before_sidebar', $sidebar_id );
$inner_tag = tag_escape( $args['inner_tag'] );
$inner_classes = array(
'rx-sidebar-inner',
);
if ( ! empty( $args['inner_class'] ) ) {
$inner_classes[] = sanitize_html_class( $args['inner_class'] );
}
$output .= sprintf(
'<%1$s class="%2$s">',
$inner_tag,
esc_attr( implode( ' ', $inner_classes ) )
);
return $output;
}
/**
* Get sidebar close markup.
*
* @param array $args Args.
* @return string
*/
private function get_sidebar_close_markup( $args ) {
$tag = tag_escape( $args['container_tag'] );
$inner_tag = tag_escape( $args['inner_tag'] );
$output = sprintf( '</%s>', $inner_tag );
$output .= "\n";
do_action( 'rx_after_sidebar', $this->get_current_sidebar_id() );
$output .= sprintf( '</%s>', $tag );
$output .= "\n";
return $output;
}
/**
* Build sidebar CSS classes.
*
* @param string $sidebar_id Sidebar ID.
* @param array $args Args.
* @return array
*/
public function get_sidebar_classes( $sidebar_id = '', $args = array() ) {
if ( empty( $sidebar_id ) ) {
$sidebar_id = $this->get_current_sidebar_id();
}
$layout = $this->get_layout();
$classes = array(
'widget-area',
'rx-sidebar',
'rx-sidebar-' . sanitize_html_class( $sidebar_id ),
'rx-layout-' . sanitize_html_class( $layout ),
);
if ( ! empty( $args['sticky'] ) ) {
$classes[] = 'rx-sidebar-sticky';
}
if ( is_active_sidebar( $sidebar_id ) ) {
$classes[] = 'rx-sidebar-has-widgets';
} else {
$classes[] = 'rx-sidebar-empty';
}
if ( is_rtl() ) {
$classes[] = 'rx-sidebar-rtl';
}
if ( ! empty( $args['custom_class'] ) ) {
$custom_classes = explode( ' ', $args['custom_class'] );
foreach ( $custom_classes as $custom_class ) {
if ( ! empty( $custom_class ) ) {
$classes[] = sanitize_html_class( $custom_class );
}
}
}
return apply_filters( 'rx_sidebar_classes', array_unique( $classes ), $sidebar_id, $args );
}
/**
* Render above content widget area.
*
* @return void
*/
public function render_above_content() {
$this->render_widget_area(
'above-content',
array(
'wrapper_class' => 'rx-above-content-area',
'aria_label' => esc_html__( 'Above content widgets', 'rx-theme' ),
)
);
}
/**
* Render below content widget area.
*
* @return void
*/
public function render_below_content() {
$this->render_widget_area(
'below-content',
array(
'wrapper_class' => 'rx-below-content-area',
'aria_label' => esc_html__( 'Below content widgets', 'rx-theme' ),
)
);
}
/**
* Render after post content widget area.
*
* @return void
*/
public function render_after_post_content() {
if ( ! is_single() ) {
return;
}
$this->render_widget_area(
'after-post-content',
array(
'wrapper_class' => 'rx-after-post-content-area',
'aria_label' => esc_html__( 'After post content widgets', 'rx-theme' ),
)
);
}
/**
* Render header widget area.
*
* @return void
*/
public function render_header_widget_area() {
$this->render_widget_area(
'header-widget-area',
array(
'wrapper_class' => 'rx-header-widget-area',
'aria_label' => esc_html__( 'Header widgets', 'rx-theme' ),
'tag' => 'div',
)
);
}
/**
* Render mobile sidebar area.
*
* @return void
*/
public function render_mobile_sidebar() {
$this->render_widget_area(
'mobile-sidebar',
array(
'wrapper_class' => 'rx-mobile-sidebar-area',
'aria_label' => esc_html__( 'Mobile sidebar widgets', 'rx-theme' ),
'tag' => 'aside',
)
);
}
/**
* Render a generic widget area.
*
* @param string $sidebar_id Sidebar ID.
* @param array $args Args.
* @return void
*/
public function render_widget_area( $sidebar_id, $args = array() ) {
$sidebar_id = sanitize_key( $sidebar_id );
if ( ! is_active_sidebar( $sidebar_id ) ) {
return;
}
$defaults = array(
'tag' => 'section',
'wrapper_class' => 'rx-widget-area',
'aria_label' => esc_html__( 'Widget area', 'rx-theme' ),
'inner_class' => 'rx-widget-area-inner',
);
$args = wp_parse_args( $args, $defaults );
$tag = tag_escape( $args['tag'] );
printf(
'<%1$s class="%2$s" aria-label="%3$s">',
$tag,
esc_attr( $args['wrapper_class'] ),
esc_attr( $args['aria_label'] )
);
printf(
'<div class="%s">',
esc_attr( $args['inner_class'] )
);
dynamic_sidebar( $sidebar_id );
echo '</div>';
printf(
'</%s>',
$tag
);
}
/**
* Render footer widgets.
*
* @param int $columns Number of columns.
* @return void
*/
public function render_footer_widgets( $columns = 4 ) {
$columns = absint( $columns );
if ( $columns < 1 ) {
$columns = 1;
}
if ( $columns > 4 ) {
$columns = 4;
}
$active = false;
for ( $i = 1; $i <= $columns; $i++ ) {
if ( is_active_sidebar( 'footer-' . $i ) ) {
$active = true;
break;
}
}
if ( ! $active ) {
return;
}
$classes = array(
'rx-footer-widgets',
'rx-footer-widgets-columns-' . $columns,
);
$classes = apply_filters( 'rx_footer_widgets_classes', $classes, $columns );
echo '<section class="' . esc_attr( implode( ' ', $classes ) ) . '" aria-label="' . esc_attr__( 'Footer widgets', 'rx-theme' ) . '">';
echo '<div class="rx-container rx-footer-widgets-container">';
for ( $i = 1; $i <= $columns; $i++ ) {
$sidebar_id = 'footer-' . $i;
if ( ! is_active_sidebar( $sidebar_id ) ) {
continue;
}
echo '<div class="rx-footer-widget-column rx-footer-widget-column-' . esc_attr( $i ) . '">';
dynamic_sidebar( $sidebar_id );
echo '</div>';
}
echo '</div>';
echo '</section>';
}
/**
* Fallback sidebar content.
*
* @param string $sidebar_id Sidebar ID.
* @return string
*/
private function get_fallback_sidebar_content( $sidebar_id ) {
ob_start();
/**
* Before fallback sidebar content.
*/
do_action( 'rx_before_fallback_sidebar_content', $sidebar_id );
?>
<section class="widget rx-widget rx-fallback-widget rx-fallback-search">
<h2 class="widget-title rx-widget-title">
<?php esc_html_e( 'Search', 'rx-theme' ); ?>
</h2>
<?php get_search_form(); ?>
</section>
<?php if ( ! is_404() ) : ?>
<section class="widget rx-widget rx-fallback-widget rx-fallback-recent-posts">
<h2 class="widget-title rx-widget-title">
<?php esc_html_e( 'Recent Posts', 'rx-theme' ); ?>
</h2>
<ul>
<?php
$recent_posts = wp_get_recent_posts(
array(
'numberposts' => 5,
'post_status' => 'publish',
'suppress_filters' => false,
)
);
if ( ! empty( $recent_posts ) ) :
foreach ( $recent_posts as $recent_post ) :
?>
<li>
<a href="<?php echo esc_url( get_permalink( $recent_post['ID'] ) ); ?>">
<?php echo esc_html( get_the_title( $recent_post['ID'] ) ); ?>
</a>
</li>
<?php
endforeach;
else :
?>
<li><?php esc_html_e( 'No recent posts found.', 'rx-theme' ); ?></li>
<?php
endif;
?>
</ul>
</section>
<?php endif; ?>
<?php if ( ! is_page() ) : ?>
<section class="widget rx-widget rx-fallback-widget rx-fallback-categories">
<h2 class="widget-title rx-widget-title">
<?php esc_html_e( 'Categories', 'rx-theme' ); ?>
</h2>
<ul>
<?php
wp_list_categories(
array(
'title_li' => '',
'number' => 10,
'hide_empty' => true,
'show_count' => true,
'orderby' => 'count',
'order' => 'DESC',
)
);
?>
</ul>
</section>
<?php endif; ?>
<?php
/**
* After fallback sidebar content.
*/
do_action( 'rx_after_fallback_sidebar_content', $sidebar_id );
$content = ob_get_clean();
return apply_filters( 'rx_fallback_sidebar_content', $content, $sidebar_id );
}
/**
* Add body classes.
*
* @param array $classes Body classes.
* @return array
*/
public function body_classes( $classes ) {
$layout = $this->get_layout();
$sidebar_id = $this->get_current_sidebar_id();
$classes[] = 'rx-sidebar-layout-' . sanitize_html_class( $layout );
$classes[] = 'rx-current-sidebar-' . sanitize_html_class( $sidebar_id );
if ( $this->should_show_sidebar() ) {
$classes[] = 'rx-has-sidebar';
} else {
$classes[] = 'rx-no-sidebar';
}
if ( get_theme_mod( 'rx_sticky_sidebar', true ) ) {
$classes[] = 'rx-has-sticky-sidebar';
}
return array_unique( $classes );
}
/**
* Build content wrapper classes.
*
* @return string
*/
public function get_content_area_classes() {
$layout = $this->get_layout();
$classes = array(
'rx-content-area',
'rx-content-layout-' . sanitize_html_class( $layout ),
);
if ( $this->should_show_sidebar() ) {
$classes[] = 'rx-content-with-sidebar';
} else {
$classes[] = 'rx-content-without-sidebar';
}
return esc_attr( implode( ' ', apply_filters( 'rx_content_area_classes', $classes, $layout ) ) );
}
/**
* Build main content classes.
*
* @return string
*/
public function get_main_content_classes() {
$layout = $this->get_layout();
$classes = array(
'rx-main-content',
'rx-main-content-' . sanitize_html_class( $layout ),
);
if ( $this->should_show_sidebar() ) {
$classes[] = 'rx-main-has-sidebar';
} else {
$classes[] = 'rx-main-no-sidebar';
}
return esc_attr( implode( ' ', apply_filters( 'rx_main_content_classes', $classes, $layout ) ) );
}
/**
* Build HTML attributes.
*
* @param array $attributes Attributes.
* @return string
*/
private function build_attributes( $attributes ) {
$output = array();
foreach ( $attributes as $name => $value ) {
if ( false === $value || null === $value || '' === $value ) {
continue;
}
$name = sanitize_key( $name );
if ( true === $value ) {
$output[] = esc_attr( $name );
continue;
}
if ( 'itemscope' === $name && 'itemscope' === $value ) {
$output[] = 'itemscope';
continue;
}
$output[] = sprintf(
'%1$s="%2$s"',
esc_attr( $name ),
esc_attr( $value )
);
}
return implode( ' ', $output );
}
/**
* Before sidebar hook.
*
* @param string $sidebar_id Sidebar ID.
* @return void
*/
public function before_sidebar_hook( $sidebar_id ) {
/**
* This method intentionally exists as a public hook point.
* Add content before sidebar with:
*
* add_action( 'rx_before_sidebar', 'your_function' );
*/
}
/**
* After sidebar hook.
*
* @param string $sidebar_id Sidebar ID.
* @return void
*/
public function after_sidebar_hook( $sidebar_id ) {
/**
* This method intentionally exists as a public hook point.
* Add content after sidebar with:
*
* add_action( 'rx_after_sidebar', 'your_function' );
*/
}
}
endif;
/**
* Initialize sidebar builder.
*
* @return RX_Sidebar_Builder
*/
function rx_sidebar_builder() {
return RX_Sidebar_Builder::instance();
}
rx_sidebar_builder();
/**
* Render current sidebar.
*
* Usage:
* rx_render_sidebar();
*
* @param string $sidebar_id Sidebar ID.
* @param array $args Args.
* @return void
*/
function rx_render_sidebar( $sidebar_id = '', $args = array() ) {
rx_sidebar_builder()->render_sidebar( $sidebar_id, $args );
}
/**
* Get current RX sidebar ID.
*
* @return string
*/
function rx_get_current_sidebar_id() {
return rx_sidebar_builder()->get_current_sidebar_id();
}
/**
* Get RX sidebar layout.
*
* @return string
*/
function rx_get_sidebar_layout() {
return rx_sidebar_builder()->get_layout();
}
/**
* Check whether current request should show sidebar.
*
* @return bool
*/
function rx_should_show_sidebar() {
return rx_sidebar_builder()->should_show_sidebar();
}
/**
* Render above content widget area.
*
* @return void
*/
function rx_render_above_content_widgets() {
rx_sidebar_builder()->render_above_content();
}
/**
* Render below content widget area.
*
* @return void
*/
function rx_render_below_content_widgets() {
rx_sidebar_builder()->render_below_content();
}
/**
* Render after post widget area.
*
* @return void
*/
function rx_render_after_post_widgets() {
rx_sidebar_builder()->render_after_post_content();
}
/**
* Render header widget area.
*
* @return void
*/
function rx_render_header_widgets() {
rx_sidebar_builder()->render_header_widget_area();
}
/**
* Render mobile sidebar widget area.
*
* @return void
*/
function rx_render_mobile_sidebar_widgets() {
rx_sidebar_builder()->render_mobile_sidebar();
}
/**
* Render footer widgets.
*
* Usage:
* rx_render_footer_widgets( 4 );
*
* @param int $columns Footer columns.
* @return void
*/
function rx_render_footer_widgets( $columns = 4 ) {
rx_sidebar_builder()->render_footer_widgets( $columns );
}
/**
* Get content area classes.
*
* @return string
*/
function rx_get_content_area_classes() {
return rx_sidebar_builder()->get_content_area_classes();
}
/**
* Get main content classes.
*
* @return string
*/
function rx_get_main_content_classes() {
return rx_sidebar_builder()->get_main_content_classes();
}
/**
* Open content-sidebar layout wrapper.
*
* Usage in templates:
* rx_content_sidebar_open();
*
* @return void
*/
function rx_content_sidebar_open() {
$layout = rx_get_sidebar_layout();
$classes = array(
'rx-layout-wrap',
'rx-layout-wrap-' . sanitize_html_class( $layout ),
);
if ( rx_should_show_sidebar() ) {
$classes[] = 'rx-layout-has-sidebar';
} else {
$classes[] = 'rx-layout-no-sidebar';
}
echo '<div class="' . esc_attr( implode( ' ', $classes ) ) . '">';
}
/**
* Close content-sidebar layout wrapper.
*
* @return void
*/
function rx_content_sidebar_close() {
echo '</div>';
}
/**
* Open main content area.
*
* @return void
*/
function rx_main_content_open() {
echo '<main id="primary" class="' . esc_attr( rx_get_main_content_classes() ) . '">';
}
/**
* Close main content area.
*
* @return void
*/
function rx_main_content_close() {
echo '</main>';
}
/**
* Render sidebar only if available.
*
* Useful in index.php, single.php, page.php, archive.php.
*
* @return void
*/
function rx_maybe_render_sidebar() {
if ( rx_should_show_sidebar() ) {
rx_render_sidebar();
}
}
/**
* Add optional layout customizer settings.
*
* This gives your theme sidebar controls in WordPress Customizer.
*
* @param WP_Customize_Manager $wp_customize Customizer object.
* @return void
*/
function rx_sidebar_customizer_settings( $wp_customize ) {
$wp_customize->add_section(
'rx_sidebar_section',
array(
'title' => esc_html__( 'RX Sidebar Settings', 'rx-theme' ),
'description' => esc_html__( 'Control sidebar layout, sticky sidebar, and fallback widgets.', 'rx-theme' ),
'priority' => 160,
)
);
$wp_customize->add_setting(
'rx_sidebar_layout',
array(
'default' => 'right-sidebar',
'sanitize_callback' => 'rx_sanitize_sidebar_layout',
'transport' => 'refresh',
)
);
$wp_customize->add_control(
'rx_sidebar_layout',
array(
'label' => esc_html__( 'Default Sidebar Layout', 'rx-theme' ),
'section' => 'rx_sidebar_section',
'type' => 'select',
'choices' => array(
'right-sidebar' => esc_html__( 'Right Sidebar', 'rx-theme' ),
'left-sidebar' => esc_html__( 'Left Sidebar', 'rx-theme' ),
'no-sidebar' => esc_html__( 'No Sidebar', 'rx-theme' ),
'full-width' => esc_html__( 'Full Width', 'rx-theme' ),
),
)
);
$wp_customize->add_setting(
'rx_sticky_sidebar',
array(
'default' => true,
'sanitize_callback' => 'wp_validate_boolean',
'transport' => 'refresh',
)
);
$wp_customize->add_control(
'rx_sticky_sidebar',
array(
'label' => esc_html__( 'Enable Sticky Sidebar', 'rx-theme' ),
'section' => 'rx_sidebar_section',
'type' => 'checkbox',
)
);
$wp_customize->add_setting(
'rx_sidebar_fallback_enabled',
array(
'default' => true,
'sanitize_callback' => 'wp_validate_boolean',
'transport' => 'refresh',
)
);
$wp_customize->add_control(
'rx_sidebar_fallback_enabled',
array(
'label' => esc_html__( 'Enable Fallback Sidebar Content', 'rx-theme' ),
'description' => esc_html__( 'Show search, recent posts, and categories when no widgets are added.', 'rx-theme' ),
'section' => 'rx_sidebar_section',
'type' => 'checkbox',
)
);
$wp_customize->add_setting(
'rx_search_sidebar_layout',
array(
'default' => 'right-sidebar',
'sanitize_callback' => 'rx_sanitize_sidebar_layout',
'transport' => 'refresh',
)
);
$wp_customize->add_control(
'rx_search_sidebar_layout',
array(
'label' => esc_html__( 'Search Page Sidebar Layout', 'rx-theme' ),
'section' => 'rx_sidebar_section',
'type' => 'select',
'choices' => array(
'right-sidebar' => esc_html__( 'Right Sidebar', 'rx-theme' ),
'left-sidebar' => esc_html__( 'Left Sidebar', 'rx-theme' ),
'no-sidebar' => esc_html__( 'No Sidebar', 'rx-theme' ),
'full-width' => esc_html__( 'Full Width', 'rx-theme' ),
),
)
);
$wp_customize->add_setting(
'rx_404_sidebar_layout',
array(
'default' => 'no-sidebar',
'sanitize_callback' => 'rx_sanitize_sidebar_layout',
'transport' => 'refresh',
)
);
$wp_customize->add_control(
'rx_404_sidebar_layout',
array(
'label' => esc_html__( '404 Page Sidebar Layout', 'rx-theme' ),
'section' => 'rx_sidebar_section',
'type' => 'select',
'choices' => array(
'right-sidebar' => esc_html__( 'Right Sidebar', 'rx-theme' ),
'left-sidebar' => esc_html__( 'Left Sidebar', 'rx-theme' ),
'no-sidebar' => esc_html__( 'No Sidebar', 'rx-theme' ),
'full-width' => esc_html__( 'Full Width', 'rx-theme' ),
),
)
);
}
add_action( 'customize_register', 'rx_sidebar_customizer_settings' );
/**
* Sanitize sidebar layout.
*
* @param string $layout Layout value.
* @return string
*/
function rx_sanitize_sidebar_layout( $layout ) {
$allowed = array(
'right-sidebar',
'left-sidebar',
'no-sidebar',
'full-width',
);
if ( in_array( $layout, $allowed, true ) ) {
return $layout;
}
return 'right-sidebar';
}
/**
* Add post/page sidebar layout meta box.
*
* @return void
*/
function rx_sidebar_layout_meta_box() {
$post_types = apply_filters(
'rx_sidebar_layout_meta_box_post_types',
array( 'post', 'page' )
);
foreach ( $post_types as $post_type ) {
add_meta_box(
'rx-sidebar-layout-meta-box',
esc_html__( 'RX Sidebar Layout', 'rx-theme' ),
'rx_sidebar_layout_meta_box_callback',
$post_type,
'side',
'default'
);
}
}
add_action( 'add_meta_boxes', 'rx_sidebar_layout_meta_box' );
/**
* Sidebar layout meta box callback.
*
* @param WP_Post $post Post object.
* @return void
*/
function rx_sidebar_layout_meta_box_callback( $post ) {
wp_nonce_field( 'rx_save_sidebar_layout_meta', 'rx_sidebar_layout_nonce' );
$current = get_post_meta( $post->ID, '_rx_sidebar_layout', true );
if ( empty( $current ) ) {
$current = '';
}
?>
<p>
<label for="rx-sidebar-layout">
<?php esc_html_e( 'Select sidebar layout for this content.', 'rx-theme' ); ?>
</label>
</p>
<select name="rx_sidebar_layout" id="rx-sidebar-layout" class="widefat">
<option value="" <?php selected( $current, '' ); ?>>
<?php esc_html_e( 'Default Customizer Setting', 'rx-theme' ); ?>
</option>
<option value="right-sidebar" <?php selected( $current, 'right-sidebar' ); ?>>
<?php esc_html_e( 'Right Sidebar', 'rx-theme' ); ?>
</option>
<option value="left-sidebar" <?php selected( $current, 'left-sidebar' ); ?>>
<?php esc_html_e( 'Left Sidebar', 'rx-theme' ); ?>
</option>
<option value="no-sidebar" <?php selected( $current, 'no-sidebar' ); ?>>
<?php esc_html_e( 'No Sidebar', 'rx-theme' ); ?>
</option>
<option value="full-width" <?php selected( $current, 'full-width' ); ?>>
<?php esc_html_e( 'Full Width', 'rx-theme' ); ?>
</option>
</select>
<?php
}
/**
* Save sidebar layout meta.
*
* @param int $post_id Post ID.
* @return void
*/
function rx_save_sidebar_layout_meta( $post_id ) {
if ( ! isset( $_POST['rx_sidebar_layout_nonce'] ) ) {
return;
}
if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['rx_sidebar_layout_nonce'] ) ), 'rx_save_sidebar_layout_meta' ) ) {
return;
}
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
}
if ( isset( $_POST['post_type'] ) && 'page' === $_POST['post_type'] ) {
if ( ! current_user_can( 'edit_page', $post_id ) ) {
return;
}
} else {
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return;
}
}
$layout = '';
if ( isset( $_POST['rx_sidebar_layout'] ) ) {
$layout = sanitize_text_field( wp_unslash( $_POST['rx_sidebar_layout'] ) );
}
if ( empty( $layout ) ) {
delete_post_meta( $post_id, '_rx_sidebar_layout' );
return;
}
$layout = rx_sanitize_sidebar_layout( $layout );
update_post_meta( $post_id, '_rx_sidebar_layout', $layout );
}
add_action( 'save_post', 'rx_save_sidebar_layout_meta' );
/**
* Optional helper: register an extra custom sidebar from child theme or plugin.
*
* Usage:
* rx_register_custom_sidebar(
* 'my-sidebar',
* 'My Sidebar',
* 'This is my custom sidebar.'
* );
*
* @param string $id Sidebar ID.
* @param string $name Sidebar name.
* @param string $description Sidebar description.
* @return void
*/
function rx_register_custom_sidebar( $id, $name, $description = '' ) {
$id = sanitize_key( $id );
if ( empty( $id ) || empty( $name ) ) {
return;
}
register_sidebar(
array(
'id' => $id,
'name' => esc_html( $name ),
'description' => esc_html( $description ),
'before_widget' => '<section id="%1$s" class="widget rx-widget rx-custom-widget %2$s">',
'after_widget' => '</section>',
'before_title' => '<h2 class="widget-title rx-widget-title">',
'after_title' => '</h2>',
)
);
}
/**
* Optional CSS for sidebar layout.
*
* You can move this CSS to style.css later.
*
* @return void
*/
function rx_sidebar_inline_css() {
$enable_inline_css = apply_filters( 'rx_sidebar_enable_inline_css', true );
if ( ! $enable_inline_css ) {
return;
}
$css = '
.rx-layout-wrap {
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: var(--rx-sidebar-gap, 32px);
width: 100%;
}
.rx-layout-wrap-right-sidebar.rx-layout-has-sidebar {
grid-template-columns: minmax(0, 1fr) minmax(260px, 340px);
}
.rx-layout-wrap-left-sidebar.rx-layout-has-sidebar {
grid-template-columns: minmax(260px, 340px) minmax(0, 1fr);
}
.rx-layout-wrap-left-sidebar.rx-layout-has-sidebar .rx-sidebar {
order: -1;
}
.rx-main-content {
min-width: 0;
}
.rx-sidebar {
min-width: 0;
}
.rx-sidebar-inner {
width: 100%;
}
.rx-sidebar-sticky .rx-sidebar-inner {
position: sticky;
top: var(--rx-sticky-sidebar-offset, 32px);
}
.rx-widget {
margin-bottom: 28px;
overflow-wrap: break-word;
}
.rx-widget-title {
margin-top: 0;
margin-bottom: 16px;
font-size: 1.125rem;
line-height: 1.3;
}
.rx-footer-widgets {
width: 100%;
padding-top: 48px;
padding-bottom: 48px;
}
.rx-footer-widgets-container {
display: grid;
gap: 32px;
}
.rx-footer-widgets-columns-1 .rx-footer-widgets-container {
grid-template-columns: 1fr;
}
.rx-footer-widgets-columns-2 .rx-footer-widgets-container {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.rx-footer-widgets-columns-3 .rx-footer-widgets-container {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.rx-footer-widgets-columns-4 .rx-footer-widgets-container {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.rx-above-content-area,
.rx-below-content-area,
.rx-after-post-content-area {
margin-top: 32px;
margin-bottom: 32px;
}
.rx-header-widget-area {
display: flex;
align-items: center;
gap: 16px;
}
@media (max-width: 991px) {
.rx-layout-wrap-right-sidebar.rx-layout-has-sidebar,
.rx-layout-wrap-left-sidebar.rx-layout-has-sidebar {
grid-template-columns: 1fr;
}
.rx-layout-wrap-left-sidebar.rx-layout-has-sidebar .rx-sidebar {
order: initial;
}
.rx-sidebar-sticky .rx-sidebar-inner {
position: static;
}
.rx-footer-widgets-columns-3 .rx-footer-widgets-container,
.rx-footer-widgets-columns-4 .rx-footer-widgets-container {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 640px) {
.rx-footer-widgets-columns-2 .rx-footer-widgets-container,
.rx-footer-widgets-columns-3 .rx-footer-widgets-container,
.rx-footer-widgets-columns-4 .rx-footer-widgets-container {
grid-template-columns: 1fr;
}
}
';
wp_register_style( 'rx-sidebar-inline-style', false, array(), defined( 'RX_THEME_VERSION' ) ? RX_THEME_VERSION : '1.0.0' );
wp_enqueue_style( 'rx-sidebar-inline-style' );
wp_add_inline_style( 'rx-sidebar-inline-style', $css );
}
add_action( 'wp_enqueue_scripts', 'rx_sidebar_inline_css', 20 );
Now include it in your functions.php:
/**
* Sidebar Builder.
*/
require_once get_template_directory() . '/inc/builders/sidebar-builder.php';
Use it in your template files like this:
<?php rx_content_sidebar_open(); ?>
<?php rx_main_content_open(); ?>
<?php
if ( have_posts() ) :
while ( have_posts() ) :
the_post();
the_content();
endwhile;
endif;
?>
<?php rx_main_content_close(); ?>
<?php rx_maybe_render_sidebar(); ?>
<?php rx_content_sidebar_close(); ?>
For your footer.php, use:
<?php rx_render_footer_widgets( 4 ); ?>
For your header.php, use:
<?php rx_render_header_widgets(); ?>
For after single post content, use inside single.php:
<?php rx_render_after_post_widgets(); ?>
This file will give your RX Theme a strong sidebar system with many future expansion options.