PluginProbe
Extendify / trunk
Extendify vtrunk
3.2.1 3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 0.6.0 All 127 releases
← All changes | app/Agent/TagBlocks.php +133 -24 3.0.6 → trunk View file →
@@ -5,11 +5,11 @@
5 5 defined('ABSPATH') || die('No direct access.');
6 6
7 7 class TagBlocks
8 8 {
9 - // Iterating blocks: their inner template renders once per item, so tagging
10 - // children would assign N rendered ids to one parsed block. Skip the subtree.
11 - // Public so WPController::getBlockCode walks the same list.
9 + // Counting a loop's per-item copies or a cart's drawer spends ids the save
10 + // walk can't resolve.
11 + // Public so SaveController + WPController can share the same list.
12 12 public static $ignored = [
13 13 'core/query',
14 14 'core/post-template',
15 15 'core/post-content',
@@ -16,10 +16,69 @@
16 16 'core/comments',
17 17 'core/comment-template',
18 18 'woocommerce/product-collection',
19 19 'woocommerce/product-template',
20 + 'woocommerce/mini-cart',
21 + 'woocommerce/cart',
22 + 'woocommerce/checkout',
20 23 ];
21 24
25 + // Shared with PostBlockFinder so the save walk skips what this declined to count.
26 + public static $refContainers = [
27 + 'core/block' => 'wp_block',
28 + ];
29 +
30 + // TagTemplateParts numbers the interior from 1 under the part's own slug.
31 + private static function partPrefix(array $block): string
32 + {
33 + return 'part:' . (string) ($block['attrs']['slug'] ?? '') . ':';
34 + }
35 +
36 + private static function isTemplatePart(array $block): bool
37 + {
38 + return ($block['blockName'] ?? '') === 'core/template-part';
39 + }
40 +
41 + // Blocks whose interior belongs to another post's id space.
42 + private static function scopePrefix(array $block): string
43 + {
44 + if (TemplatePartBlockFinder::isRefNav($block)) {
45 + return TemplatePartBlockFinder::refPrefix('navigation', $block);
46 + }
47 + return self::isTemplatePart($block) ? self::partPrefix($block) : '';
48 + }
49 +
50 + // `prefix` names the post an id belongs to; empty means this post.
51 + // A `foreign` frame is numbered by another tagger, so nothing in it counts.
52 + // A `ref` frame's ids are only ever resolved by TemplatePartBlockFinder.
53 + private static function newFrame(
54 + string $prefix = '',
55 + bool $foreign = false,
56 + bool $ref = false
57 + ): array {
58 + return [
59 + 'seq' => 0,
60 + 'id_stack' => [],
61 + 'pushed_stack' => [],
62 + 'skip_depth' => 0, // >0 while inside an ignored subtree
63 + 'prefix' => $prefix,
64 + 'foreign' => $foreign,
65 + 'ref' => $ref,
66 + ];
67 + }
68 +
69 + // Diverging from TemplatePartBlockFinder here drifts every later id.
70 + private static function counts(array $frame, string $name, array $block): bool
71 + {
72 + if ($frame['skip_depth'] !== 0) {
73 + return false;
74 + }
75 + if (empty($frame['ref'])) {
76 + return !in_array($name, self::$ignored, true);
77 + }
78 + return !self::isTemplatePart($block);
79 + }
80 +
22 81 public static function init()
23 82 {
24 83 \add_filter('the_content', [self::class, 'enterScope'], 0);
25 84 \add_filter('the_content', [self::class, 'leaveScope'], PHP_INT_MAX);
@@ -40,15 +99,9 @@
40 99 'frames' => [],
41 100 ];
42 101 }
43 102 $GLOBALS['extendify_agent_scope']['depth']++;
44 - // Each scope has: seq, id_stack, pushed_stack, skip_depth
45 - $GLOBALS['extendify_agent_scope']['frames'][] = [
46 - 'seq' => 0,
47 - 'id_stack' => [],
48 - 'pushed_stack' => [],
49 - 'skip_depth' => 0, // >0 while inside an ignored subtree
50 - ];
103 + $GLOBALS['extendify_agent_scope']['frames'][] = self::newFrame();
51 104 return $content;
52 105 }
53 106
54 107 public static function leaveScope($content)
@@ -79,24 +132,46 @@
79 132 $frame = $S['frames'][$i];
80 133
81 134 $name = $parsed_block['blockName'];
82 135
83 - // If this block starts an ignored subtree, enter skip mode
136 + // Another tagger owns this interior's numbering.
137 + if (!empty($frame['foreign'])) {
138 + $frame['pushed_stack'][] = ['counts' => false, 'name' => $name];
139 + $GLOBALS['extendify_agent_scope']['frames'][$i] = $frame;
140 + return $pre;
141 + }
142 +
143 + // The pattern's blocks render inline but live in another post, so they
144 + // are numbered off that post instead of counted here.
145 + if (isset(self::$refContainers[$name]) && $frame['skip_depth'] === 0) {
146 + $GLOBALS['extendify_agent_scope']['frames'][] = self::newFrame(
147 + TemplatePartBlockFinder::refPrefix('block', $parsed_block),
148 + false,
149 + true
150 + );
151 + return $pre;
152 + }
153 +
154 + $counts = self::counts($frame, $name, $parsed_block);
155 + if ($counts) {
156 + $frame['seq']++;
157 + $frame['id_stack'][] = $frame['prefix'] . $frame['seq'];
158 + }
159 + $frame['pushed_stack'][] = ['counts' => $counts, 'name' => $name];
160 +
161 + // Raised after counting so a counted leaf still hides its rendered subtree.
84 162 if (in_array($name, self::$ignored, true)) {
85 163 $frame['skip_depth']++;
86 - $frame['pushed_stack'][] = false; // we didn't assign an id to this block
87 - } elseif ($frame['skip_depth'] > 0) {
88 - // Already skipping? (we're inside an ignored subtree)
89 - $frame['pushed_stack'][] = false; // no id for anything under ignored
90 - } else {
91 - // Normal counting
92 - $frame['seq']++;
93 - $id = $frame['seq'];
94 - $frame['id_stack'][] = $id;
95 - $frame['pushed_stack'][] = true;
96 164 }
97 165
98 166 $GLOBALS['extendify_agent_scope']['frames'][$i] = $frame;
167 +
168 + // Pops from the interior must not reach the page's stack.
169 + $scope = self::scopePrefix($parsed_block);
170 + if ($scope !== '' && $frame['skip_depth'] === 0) {
171 + $GLOBALS['extendify_agent_scope']['frames'][] = self::newFrame($scope, true);
172 + }
173 +
99 174 return $pre;
100 175 }
101 176
102 177 public static function post($content, $parsed_block)
@@ -110,16 +185,41 @@
110 185 $frame = $S['frames'][$i];
111 186
112 187 $name = is_array($parsed_block) ? ($parsed_block['blockName'] ?? null) : null;
113 188
114 - // Pop pushed flag & optional id (ALWAYS pop to stay balanced)
115 - $pushed = !empty($frame['pushed_stack']) ? array_pop($frame['pushed_stack']) : false;
189 + // core/block renders through a nested WP_Block::render, so this fires twice.
190 + if ($name !== null && isset(self::$refContainers[$name])) {
191 + if ($frame['prefix'] === TemplatePartBlockFinder::refPrefix('block', $parsed_block)) {
192 + array_pop($GLOBALS['extendify_agent_scope']['frames']);
193 + return $content;
194 + }
195 + }
196 +
197 + $scope = is_array($parsed_block) ? self::scopePrefix($parsed_block) : '';
198 + if ($scope !== '' && $frame['prefix'] === $scope) {
199 + array_pop($GLOBALS['extendify_agent_scope']['frames']);
200 + $i = count($GLOBALS['extendify_agent_scope']['frames']) - 1;
201 + $frame = $GLOBALS['extendify_agent_scope']['frames'][$i];
202 + }
203 + $navPrefix = (is_array($parsed_block) && TemplatePartBlockFinder::isRefNav($parsed_block))
204 + ? TemplatePartBlockFinder::refPrefix('navigation', $parsed_block)
205 + : '';
206 +
207 + // A loop item's wrapper hits render_block alone, so popping spends the
208 + // container's id on the item.
209 + $top = $frame['pushed_stack'] ? $frame['pushed_stack'][count($frame['pushed_stack']) - 1] : null;
210 + if ($top === null || $top['name'] !== $name) {
211 + return $content;
212 + }
213 +
214 + array_pop($frame['pushed_stack']);
215 + $pushed = $top['counts'];
116 216 $id = ($pushed && !empty($frame['id_stack'])) ? array_pop($frame['id_stack']) : null;
117 217
118 218 // Inject only when: outer scope, we counted this block, html present, not admin
119 219 if (!is_admin() && ($S['depth'] ?? 0) === 1 && $pushed && $id && $content && $name) {
120 220 $tp = new \WP_HTML_Tag_Processor($content);
121 - $value = (string) (int) $id;
221 + $value = (string) $id;
122 222
123 223 // Move cursor to the first start tag in the fragment
124 224 if ($tp->next_tag()) {
125 225 $tp->set_attribute('data-extendify-agent-block-id', $value);
@@ -124,8 +224,17 @@
124 224 if ($tp->next_tag()) {
125 225 $tp->set_attribute('data-extendify-agent-block-id', $value);
126 226 $content = $tp->get_updated_html();
127 227 }
228 + }
229 +
230 + if ($navPrefix && $content) {
231 + $content = TagTemplateParts::stampNavItems(
232 + $content,
233 + $navPrefix,
234 + (int) $parsed_block['attrs']['ref'],
235 + 'data-extendify-agent-block-id'
236 + );
128 237 }
129 238
130 239 // If this block ends an ignored subtree, exit skip mode
131 240 if ($name && in_array($name, self::$ignored, true) && $frame['skip_depth'] > 0) {