# b-blocks/2.1.7/build/timeline-item/render.php

bBlocks – Essential Gutenberg Blocks &amp; Patterns Collection, version 2.1.7. 145 lines.

- Page: https://pluginprobe.com/plugins/b-blocks/2.1.7/code/build/timeline-item/render.php
- Raw: https://pluginprobe.com/plugins/b-blocks/2.1.7/raw/build/timeline-item/render.php
- Modified: 2026-09-22T11:27:38+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/b-blocks/2.1.7/code/build/timeline-item/render.php#L10-L20`.

```php
<?php
/**
 * Timeline Item Block server-side render.
 *
 * One entry on a Timeline: the card holding the inner blocks, the SVG marker
 * that sits on the line, and the date beside it. Like its parent the markup is
 * rendered here rather than on the client — the card's content is InnerBlocks
 * — and view.js only mounts the generated <style> into the empty host below.
 *
 * The side an entry sits on is normally decided in CSS from its position in
 * the list, so nothing about ordering needs to be known at render time. A
 * `side` set in the sidebar adds a class that outranks that rule; it mirrors
 * utils/functions.js.
 *
 * @package bBlocks
 *
 * @var array  $attributes Block attributes.
 * @var string $content    Rendered inner blocks.
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

$id = wp_unique_id( 'timeline-item-' );

$item = isset( $attributes['item'] ) && is_array( $attributes['item'] ) ? $attributes['item'] : array();

$side = isset( $item['side'] ) ? sanitize_key( (string) $item['side'] ) : '';
$side = in_array( $side, array( 'left', 'right' ), true ) ? $side : '';

// Empty means follow the parent Timeline, so only an explicit choice emits a
// class. Mirrors getTimelineItemClasses in the parent's utils/functions.js.
$arrowAlign = isset( $item['arrowAlign'] ) ? sanitize_key( (string) $item['arrowAlign'] ) : '';
$arrowAlign = in_array( $arrowAlign, array( 'start', 'center', 'end' ), true ) ? $arrowAlign : '';

// The date is a RichText field, so it can carry inline formatting — bold,
// links, colour. wp_kses_post keeps exactly what a post is allowed to hold
// and strips the rest, matching how every other RichText in the plugin is
// rendered.
$date = isset( $attributes['date'] ) ? wp_kses_post( (string) $attributes['date'] ) : '';

/**
 * The marker icon is an inline SVG from the bpl-tools icon library rather
 * than a font class, so it carries no webfont dependency — but it also means
 * raw markup reaches the page and has to be filtered.
 *
 * The allowlist below is not guesswork: it is every element and attribute
 * that actually occurs across all 4,354 icons in the three bundled libraries
 * (Font Awesome, Bootstrap, Lucide). Anything outside it — script, style, on*
 * handlers, foreignObject — is stripped by wp_kses.
 */
$svgAttr = array(
	'xmlns'            => true,
	'viewbox'          => true,
	'width'            => true,
	'height'           => true,
	'fill'             => true,
	'fill-rule'        => true,
	'fill-opacity'     => true,
	'stroke'           => true,
	'stroke-width'     => true,
	'stroke-linecap'   => true,
	'stroke-linejoin'  => true,
	'class'            => true,
	'id'               => true,
	'transform'        => true,
	'aria-hidden'      => true,
	'focusable'        => true,
);

$shapeAttr = array(
	'd'                => true,
	'points'           => true,
	'cx'               => true,
	'cy'               => true,
	'r'                => true,
	'rx'               => true,
	'ry'               => true,
	'x'                => true,
	'y'                => true,
	'x1'               => true,
	'y1'               => true,
	'x2'               => true,
	'y2'               => true,
	'width'            => true,
	'height'           => true,
	'fill'             => true,
	'fill-rule'        => true,
	'fill-opacity'     => true,
	'stroke'           => true,
	'stroke-width'     => true,
	'stroke-linecap'   => true,
	'stroke-linejoin'  => true,
	'transform'        => true,
);

$iconAllowed = array(
	'svg'      => $svgAttr,
	'g'        => $shapeAttr,
	'path'     => $shapeAttr,
	'circle'   => $shapeAttr,
	'ellipse'  => $shapeAttr,
	'rect'     => $shapeAttr,
	'line'     => $shapeAttr,
	'polyline' => $shapeAttr,
	'polygon'  => $shapeAttr,
);

$icon = isset( $attributes['icon'] ) ? wp_kses( (string) $attributes['icon'], $iconAllowed ) : '';

$itemClasses = trim(
	'b-blocks-timeline-item'
	. ( $side ? ' b-blocks-timeline-side-' . $side : '' )
	. ( $arrowAlign ? ' b-blocks-timeline-item-arrow-' . $arrowAlign : '' )
);
?>
<div
	<?php // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- get_block_wrapper_attributes() is properly escaped ?>
	<?php echo get_block_wrapper_attributes( array( 'class' => $itemClasses ) ); ?>
	id='<?php echo esc_attr( $id ); ?>'
	data-attributes='<?php echo esc_attr( wp_json_encode( $attributes ) ); ?>'
>
	<div class='b-blocks-timeline-item-style'></div>

	<div class='b-blocks-timeline-content'>
		<div class='b-blocks-timeline-card'>
			<?php
			// $content is this entry's inner blocks, each of which rendered
			// and escaped its own markup.
			echo $content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- inner blocks render their own escaped markup
			?>
		</div>
	</div>

	<div class='b-blocks-timeline-marker' aria-hidden='true'>
		<?php
		// Sanitized with wp_kses against the icon allowlist above.
		echo $icon; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- filtered by wp_kses above
		?>
	</div>

	<div class='b-blocks-timeline-date'><?php echo $date; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- sanitized with wp_kses_post above ?></div>
</div>

```
