File: //home/swifizcd/buyease.com.ng/wp-content/plugins/extendify/app/Agent/TagTemplateParts.php
<?php
namespace Extendify\Agent;
defined('ABSPATH') || die('No direct access.');
/**
* Tag blocks inside template parts (header/footer/etc.) with deterministic IDs.
* - Only runs inside core/template-part scopes.
* - Preorder numbering per template-part.
*/
class TagTemplateParts
{
private static $frames = [];
private static $blockStack = [];
public static function init()
{
// Reset per-request state
add_action('template_redirect', function () {
self::$frames = [];
self::$blockStack = [];
});
// Assign IDs at the block-data layer (pre-order, stable)
add_filter('render_block_data', [self::class, 'onRenderBlockData'], 10, 2);
// Turn those IDs into DOM attributes & manage scope lifecycle
add_filter('render_block', [self::class, 'onRenderBlock'], 10, 2);
}
private static function isTemplatePart(array $b): bool
{
return (($b['blockName'] ?? '') === 'core/template-part');
}
private static function currentFrame()
{
return self::$frames ? self::$frames[count(self::$frames) - 1] : null;
}
private static function setCurrentFrame(array $frame)
{
self::$frames[count(self::$frames) - 1] = $frame;
}
private static function labelForPart(array $b): string
{
if (!empty($b['attrs']['area'])) {
return (string) $b['attrs']['area'];
}
if (!empty($b['attrs']['slug'])) {
return (string) $b['attrs']['slug'];
}
return 'template-part';
}
/** Assign IDs/labels into attrs before render (pre-order) */
public static function onRenderBlockData(array $block, array $source): array
{
$name = $block['blockName'] ?? '';
if ($name === '') {
return $block;
}
// OPEN a template-part scope (do not number the wrapper itself)
if (self::isTemplatePart($block)) {
$label = self::labelForPart($block);
self::$frames[] = [
'label' => $label,
'seq' => 0, // per-part counter
];
// mark so we know to pop when this wrapper finishes rendering
$block['attrs']['__extendify_scope_open'] = 1;
return $block;
}
// Not inside a template-part? Ignore (prevents tagging post content).
if (empty(self::$frames)) {
return $block;
}
// Inside a template part
$frame = self::currentFrame();
// Any other block inside the part → number it
if ($frame) {
$frame['seq']++;
self::$blockStack[] = [
'id' => $frame['seq'],
'label' => $frame['label'],
];
self::setCurrentFrame($frame);
}
return $block;
}
/** Inject DOM attributes and close scopes after render */
public static function onRenderBlock(string $html, array $block): string
{
$name = $block['blockName'] ?? '';
if ($name === '') {
return $html;
}
// When the template-part wrapper finishes rendering, POP the frame
if (self::isTemplatePart($block) && !empty($block['attrs']['__extendify_scope_open'])) {
// wrapper itself isn’t tagged; just close the scope
array_pop(self::$frames);
return $html;
}
// If we’re not inside a template part, do nothing
if (empty(self::$frames)) {
return $html;
}
// Inject attributes for blocks we numbered
$info = !empty(self::$blockStack)
? array_pop(self::$blockStack)
: null;
if ($info && $html) {
$tp = new \WP_HTML_Tag_Processor($html);
if ($tp->next_tag()) {
$tp->set_attribute('data-extendify-part-block-id', (string) (int) $info['id']);
$tp->set_attribute('data-extendify-part', $info['label']);
$html = $tp->get_updated_html();
}
}
return $html;
}
}