PluginProbe
Extendify / 3.2.1
Extendify v3.2.1
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.63.2.1 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) {