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 +132 -24 3.1.3 → trunk View file →
@@ -5,11 +5,10 @@
5 5 defined('ABSPATH') || die('No direct access.');
6 6
7 7 class TagBlocks
8 8 {
9 - // Loop blocks render their inner tree once per item; counting children as
10 - // separate seqs would produce N DOM ids for one parse_blocks() entry and
11 - // break source-walks that resolve clicked ids back to block code.
9 + // Counting a loop's per-item copies or a cart's drawer spends ids the save
10 + // walk can't resolve.
12 11 // Public so SaveController + WPController can share the same list.
13 12 public static $ignored = [
14 13 'core/query',
15 14 'core/post-template',
@@ -17,10 +16,69 @@
17 16 'core/comments',
18 17 'core/comment-template',
19 18 'woocommerce/product-collection',
20 19 'woocommerce/product-template',
20 + 'woocommerce/mini-cart',
21 + 'woocommerce/cart',
22 + 'woocommerce/checkout',
21 23 ];
22 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 +
23 81 public static function init()
24 82 {
25 83 \add_filter('the_content', [self::class, 'enterScope'], 0);
26 84 \add_filter('the_content', [self::class, 'leaveScope'], PHP_INT_MAX);
@@ -41,15 +99,9 @@
41 99 'frames' => [],
42 100 ];
43 101 }
44 102 $GLOBALS['extendify_agent_scope']['depth']++;
45 - // Each scope has: seq, id_stack, pushed_stack, skip_depth
46 - $GLOBALS['extendify_agent_scope']['frames'][] = [
47 - 'seq' => 0,
48 - 'id_stack' => [],
49 - 'pushed_stack' => [],
50 - 'skip_depth' => 0, // >0 while inside an ignored subtree
51 - ];
103 + $GLOBALS['extendify_agent_scope']['frames'][] = self::newFrame();
52 104 return $content;
53 105 }
54 106
55 107 public static function leaveScope($content)
@@ -80,24 +132,46 @@
80 132 $frame = $S['frames'][$i];
81 133
82 134 $name = $parsed_block['blockName'];
83 135
84 - // 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.
85 162 if (in_array($name, self::$ignored, true)) {
86 163 $frame['skip_depth']++;
87 - $frame['pushed_stack'][] = false; // we didn't assign an id to this block
88 - } elseif ($frame['skip_depth'] > 0) {
89 - // Already skipping? (we're inside an ignored subtree)
90 - $frame['pushed_stack'][] = false; // no id for anything under ignored
91 - } else {
92 - // Normal counting
93 - $frame['seq']++;
94 - $id = $frame['seq'];
95 - $frame['id_stack'][] = $id;
96 - $frame['pushed_stack'][] = true;
97 164 }
98 165
99 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 +
100 174 return $pre;
101 175 }
102 176
103 177 public static function post($content, $parsed_block)
@@ -111,16 +185,41 @@
111 185 $frame = $S['frames'][$i];
112 186
113 187 $name = is_array($parsed_block) ? ($parsed_block['blockName'] ?? null) : null;
114 188
115 - // Pop pushed flag & optional id (ALWAYS pop to stay balanced)
116 - $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'];
117 216 $id = ($pushed && !empty($frame['id_stack'])) ? array_pop($frame['id_stack']) : null;
118 217
119 218 // Inject only when: outer scope, we counted this block, html present, not admin
120 219 if (!is_admin() && ($S['depth'] ?? 0) === 1 && $pushed && $id && $content && $name) {
121 220 $tp = new \WP_HTML_Tag_Processor($content);
122 - $value = (string) (int) $id;
221 + $value = (string) $id;
123 222
124 223 // Move cursor to the first start tag in the fragment
125 224 if ($tp->next_tag()) {
126 225 $tp->set_attribute('data-extendify-agent-block-id', $value);
@@ -125,8 +224,17 @@
125 224 if ($tp->next_tag()) {
126 225 $tp->set_attribute('data-extendify-agent-block-id', $value);
127 226 $content = $tp->get_updated_html();
128 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 + );
129 237 }
130 238
131 239 // If this block ends an ignored subtree, exit skip mode
132 240 if ($name && in_array($name, self::$ignored, true) && $frame['skip_depth'] > 0) {