WooCommerce Code Reference

Post_Template extends Abstract_Block_Renderer
in package

Renders a `core/post-template` block (the repeater inside a Query Loop) for email.

WordPress renders core/post-template as a <ul class="wp-block-post-template"> whose grid is laid out with CSS grid/flex. Email clients (Outlook especially) don't support those, so the grid collapses to a single stacked column. This renderer re-flows the already-rendered <li> items into an email-safe, table-based column layout — mirroring how the Gallery renderer arranges images (see {@see}).

The list items arrive already rendered (post-template is a dynamic block, so $block_content holds the final <ul><li>…</li></ul>), so this renderer never re-runs the query. Each item's image is extracted and rebuilt as a clean, responsive <img> sitting directly in its grid cell — the same shape the Gallery renderer emits. This is deliberate: the images WordPress renders inside a post-template <li> are wrapped in fixed-width, auto-layout tables (<td width="520">) that were never sized for email, and a nested width: 100% image inside them collapses to a few pixels in Gmail (the width has no resolvable basis). Hoisting the image into the grid cell gives it a definite basis so it fills its column and scales down on mobile. An item with no image falls back to its original markup untouched (text content stacks correctly on its own).

Table of Contents

CELL_PADDING  = 8
Per-cell padding (px) that stands in for the grid's `gap` between items.
IMAGE_STYLE  = 'border: 0; line-height: 100%; width: 100%; max-width: 100%; height: auto; display: block;'
Responsive image style applied to every rebuilt grid image. `width: 100%` fills the column, `max-width: 100%` lets it scale down on narrow viewports, and `height: auto` keeps the ratio.
MAX_COLUMNS  = 16
Upper bound on grid columns, matching the maximum the core grid layout control allows (its Columns range control tops out at 16). Honors any column count an author can pick in the editor, while still bounding an out-of-range hand-edited value so it can't emit a runaway number of cells.
$current_rendering_context  : Rendering_Context|null
Rendering context for calls using the legacy add_spacer() signature.
render()  : string
Render the block.
add_spacer()  : string
Add a spacer around the block for vertical spacing (margin-top).
add_spacer_with_context()  : string
Add a spacer around the block with rendering context.
compile_css()  : string
Compile objects containing CSS properties to a string.
get_inner_content()  : string
Extract inner content from a wrapper element.
get_styles_from_block()  : array<string|int, mixed>
Wrapper for wp_style_engine_get_styles which ensures all values are returned.
render_content()  : string
Renders the post-template block content using a table-based grid layout.
build_grid_row()  : string
Build a single grid row as its own fixed-layout table.
build_grid_table()  : string
Build the grid as one `<table>` per row wrapped in a container table.
count_media_descendants()  : int
Count the media elements (images and embeds) contained within an element.
extract_list_items()  : array<int, string>
Extract the inner HTML of each direct-child `<li>` of the post-template list.
extract_remaining_content()  : string
Read back the card content left after the images were removed, or an empty string when nothing but structural wrapper shells remain (so image-only cards stay a clean grid).
find_image_removal_target()  : DOMElement
Choose which element to strip when hoisting an image, so the preserved remainder is clean.
find_link_href()  : string
Return the href of the nearest ancestor `<a>` of the given image, or an empty string.
find_post_template_list()  : DOMElement|null
Locate the post-template list within the rendered content.
get_cell_width()  : int
Estimate the rendered pixel width of a single grid cell's content area.
get_column_count()  : int
Determine how many columns the grid should render.
normalize_image_for_email()  : string
Sanitize a raw `<img>` and normalize it for a grid cell.
prepare_item_content()  : string
Turn a rendered `<li>`'s inner HTML into email-safe cell content.
strip_unsafe_markup()  : void
Strip markup that has no place in an email from the preserved remainder: `<script>`/`<style>` elements and inline event-handler (`on*`) attributes.

Constants

IMAGE_STYLE

Responsive image style applied to every rebuilt grid image. `width: 100%` fills the column, `max-width: 100%` lets it scale down on narrow viewports, and `height: auto` keeps the ratio.

private mixed IMAGE_STYLE = 'border: 0; line-height: 100%; width: 100%; max-width: 100%; height: auto; display: block;'

The explicit width attribute (set alongside this) is the Outlook fallback, since Outlook ignores max-width.

MAX_COLUMNS

Upper bound on grid columns, matching the maximum the core grid layout control allows (its Columns range control tops out at 16). Honors any column count an author can pick in the editor, while still bounding an out-of-range hand-edited value so it can't emit a runaway number of cells.

private mixed MAX_COLUMNS = 16

Properties

Methods

add_spacer()

Add a spacer around the block for vertical spacing (margin-top).

protected add_spacer(string $content, array<string|int, mixed> $email_attrs) : string

Horizontal root padding is applied uniformly by Content_Renderer::render_block() so that all blocks — including those using render_email_callback without Abstract_Block_Renderer — receive consistent padding.

Parameters
$content : string

The block content.

$email_attrs : array<string|int, mixed>

The email attributes.

Return values
string

add_spacer_with_context()

Add a spacer around the block with rendering context.

protected add_spacer_with_context(string $content, array<string|int, mixed> $email_attrs[, Rendering_Context|null $rendering_context = null ]) : string
Parameters
$content : string

The block content.

$email_attrs : array<string|int, mixed>

The email attributes.

$rendering_context : Rendering_Context|null = null

Rendering context.

Return values
string

get_inner_content()

Extract inner content from a wrapper element.

protected get_inner_content(string $block_content[, string $tag_name = 'div' ]) : string

Removes the outer wrapper element (e.g., div) and returns only the inner HTML content. This is useful when you need to strip the wrapper and use only the inner content.

Parameters
$block_content : string

Block content with wrapper element.

$tag_name : string = 'div'

Tag name of the wrapper element (default: 'div').

Return values
stringInner content without the wrapper element, or original content if wrapper not found.

get_styles_from_block()

Wrapper for wp_style_engine_get_styles which ensures all values are returned.

protected get_styles_from_block(array<string|int, mixed> $block_styles[, bool $skip_convert_vars = false ]) : array<string|int, mixed>
Parameters
$block_styles : array<string|int, mixed>

Array of block styles.

$skip_convert_vars : bool = false

If true, --wp_preset--spacing--x type values will be left in the original var:preset:spacing:x format.

Return values
array<string|int, mixed>

render_content()

Renders the post-template block content using a table-based grid layout.

protected render_content(string $block_content, array<string|int, mixed> $parsed_block, Rendering_Context $rendering_context) : string
Parameters
$block_content : string

Block content.

$parsed_block : array<string|int, mixed>

Parsed block.

$rendering_context : Rendering_Context

Rendering context.

Return values
string

build_grid_row()

Build a single grid row as its own fixed-layout table.

private build_grid_row(array<int, string> $row_items, int $columns, int $cell_width) : string

Every cell is a fixed 100 / $columns percent wide and a partial final row is padded with empty cells, so items stay aligned to their column and keep a uniform width across rows (unlike the gallery, which stretches a partial row to fill the width). This matches how a CSS grid keeps column tracks consistent — important when the items are logos that shouldn't change size row to row.

Parameters
$row_items : array<int, string>

Inner HTML of the items in this row.

$columns : int

Total number of columns.

$cell_width : int

Cell content width in px.

Return values
stringRow table HTML.

build_grid_table()

Build the grid as one `<table>` per row wrapped in a container table.

private build_grid_table(array<int, string> $items, int $columns, Dom_Document_Helper $dom, DOMElement $list_element, int $layout_width) : string

Follows the tiled-gallery pattern ({@see}): items are chunked into rows of $columns and each row is its own fixed-layout table, so every cell keeps a consistent width regardless of how many items the final (possibly partial) row holds.

Parameters
$items : array<int, string>

Inner HTML of each list item.

$columns : int

Number of columns.

$dom : Dom_Document_Helper

Parsed block content.

$list_element : DOMElement

The post-template list element (for wrapper classes).

$layout_width : int

Available layout width in px.

Return values
stringGrid table HTML.

extract_list_items()

Extract the inner HTML of each direct-child `<li>` of the post-template list.

private extract_list_items(Dom_Document_Helper $dom, DOMElement $list_element) : array<int, string>

Only direct children are collected, so a nested list inside a post's content (e.g. a core/list in an excerpt) contributes its markup to the item it lives in rather than being mistaken for additional repeater items.

Parameters
$dom : Dom_Document_Helper

Parsed block content.

$list_element : DOMElement

The post-template list element.

Return values
array<int, string>Inner HTML of each list item, in document order.

find_image_removal_target()

Choose which element to strip when hoisting an image, so the preserved remainder is clean.

private find_image_removal_target(DOMElement $img_element) : DOMElement

Climbs from the image through every ancestor that wraps nothing but that single image — no text, no other media — and returns the outermost such wrapper. This removes the whole empty <figure>/<a>/layout-table shell WordPress renders around a featured image in one go, rather than leaving hollow, padded cells behind. Climbing stops as soon as an ancestor holds real content, so a sibling title/date (outside the image's wrapper) and a <figcaption> (whose text lives on the figure) are both preserved.

Parameters
$img_element : DOMElement

The image element being hoisted.

Return values
DOMElementThe element to remove from the item.

find_post_template_list()

Locate the post-template list within the rendered content.

private find_post_template_list(Dom_Document_Helper $dom) : DOMElement|null

The list is matched by its wp-block-post-template class rather than by being the first <ul>, so a sibling list that happens to appear earlier in the markup can't be mistaken for the repeater. Returns null when no such list is present.

Parameters
$dom : Dom_Document_Helper

Parsed block content.

Return values
DOMElement|null

get_cell_width()

Estimate the rendered pixel width of a single grid cell's content area.

private get_cell_width(int $layout_width, int $columns) : int

The layout width is split evenly across the columns and the per-cell padding is removed from both sides. Used to give each rebuilt image a concrete width attribute (the Outlook fallback) instead of the intrinsic file width, which Outlook would otherwise honor literally and blow the cell open.

Parameters
$layout_width : int

Available layout width in px.

$columns : int

Number of columns (>= 2; a grid is only built for multi-column layouts).

Return values
intCell content width in px (at least 1).

get_column_count()

Determine how many columns the grid should render.

private get_column_count(array<string|int, mixed> $parsed_block, Dom_Document_Helper $dom, DOMElement $list_element) : int

Prefers the block's own layout.columnCount attribute and falls back to the columns-N class WordPress core stamps on the rendered list, so it still works when the parsed attributes are sparse. Non-grid/flex layouts always resolve to a single (stacked) column.

Parameters
$parsed_block : array<string|int, mixed>

Parsed block data.

$dom : Dom_Document_Helper

Parsed block content.

$list_element : DOMElement

The post-template list element.

Return values
intColumn count (at least 1).

normalize_image_for_email()

Sanitize a raw `<img>` and normalize it for a grid cell.

private normalize_image_for_email(string $img_html, int $cell_width) : string

Reuses {@see} for the security pass (attribute allowlist, URL/style sanitizing), then pins the display width to the cell and replaces the web-only styling with the responsive email style. WordPress stores the intrinsic file width (e.g. width="1024") which Outlook honors literally, and the core web style carries a width: 100% that collapses once the image is out of a CSS grid — so both are overwritten with a concrete cell width plus {@see}.

Parameters
$img_html : string

Raw <img> HTML.

$cell_width : int

Cell content width in px.

Return values
stringNormalized `` HTML, or an empty string when the image has no usable src.

prepare_item_content()

Turn a rendered `<li>`'s inner HTML into email-safe cell content.

private prepare_item_content(string $item_html, int $cell_width) : string

Each image in the item is rebuilt as a clean, responsive <img> (preserving its link) sitting directly in the cell, so its width resolves against the grid column instead of collapsing inside the fixed-width wrapper tables WordPress renders around it. Any non-image content the card holds (post title, date, excerpt) is kept below the image, so a post grid isn't reduced to bare images.

When the card is image-only (e.g. a featured-image sponsor grid) the leftover is nothing but the now-empty wrapper shells, which are dropped so the output stays a clean logo grid. An item with no image at all is returned unchanged — its text stacks correctly without intervention.

Parameters
$item_html : string

Inner HTML of a single list item.

$cell_width : int

Cell content width in px.

Return values
stringCell content HTML.

strip_unsafe_markup()

Strip markup that has no place in an email from the preserved remainder: `<script>`/`<style>` elements and inline event-handler (`on*`) attributes.

private strip_unsafe_markup(Dom_Document_Helper $item_dom) : void

The images beside this content are already sanitized when they're rebuilt, so this keeps the reconstructed cell internally consistent. It intentionally leaves style attributes and all structural markup in place, so legitimate card content (title/date/excerpt) renders unchanged — core never emits scripts or handlers there, making this a no-op for real content. Scoped to this renderer's grid path only; it operates on the local item DOM and touches no shared helper.

Parameters
$item_dom : Dom_Document_Helper

The item DOM to clean in place.

Return values
void