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 +142 -23 3.0.4 → trunk View file →
@@ -5,11 +5,80 @@
5 5 defined('ABSPATH') || die('No direct access.');
6 6
7 7 class TagBlocks
8 8 {
9 - // Blocks to ignore completely (block + its children)
10 - private static $ignored = ['core/query', 'core/post-template', 'core/post-content'];
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 + public static $ignored = [
13 + 'core/query',
14 + 'core/post-template',
15 + 'core/post-content',
16 + 'core/comments',
17 + 'core/comment-template',
18 + 'woocommerce/product-collection',
19 + 'woocommerce/product-template',
20 + 'woocommerce/mini-cart',
21 + 'woocommerce/cart',
22 + 'woocommerce/checkout',
23 + ];
11 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 +
12 81 public static function init()
13 82 {
14 83 \add_filter('the_content', [self::class, 'enterScope'], 0);
15 84 \add_filter('the_content', [self::class, 'leaveScope'], PHP_INT_MAX);
@@ -30,15 +99,9 @@
30 99 'frames' => [],
31 100 ];
32 101 }
33 102 $GLOBALS['extendify_agent_scope']['depth']++;
34 - // Each scope has: seq, id_stack, pushed_stack, skip_depth
35 - $GLOBALS['extendify_agent_scope']['frames'][] = [
36 - 'seq' => 0,
37 - 'id_stack' => [],
38 - 'pushed_stack' => [],
39 - 'skip_depth' => 0, // >0 while inside an ignored subtree
40 - ];
103 + $GLOBALS['extendify_agent_scope']['frames'][] = self::newFrame();
41 104 return $content;
42 105 }
43 106
44 107 public static function leaveScope($content)
@@ -69,24 +132,46 @@
69 132 $frame = $S['frames'][$i];
70 133
71 134 $name = $parsed_block['blockName'];
72 135
73 - // 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.
74 162 if (in_array($name, self::$ignored, true)) {
75 163 $frame['skip_depth']++;
76 - $frame['pushed_stack'][] = false; // we didn't assign an id to this block
77 - } elseif ($frame['skip_depth'] > 0) {
78 - // Already skipping? (we're inside an ignored subtree)
79 - $frame['pushed_stack'][] = false; // no id for anything under ignored
80 - } else {
81 - // Normal counting
82 - $frame['seq']++;
83 - $id = $frame['seq'];
84 - $frame['id_stack'][] = $id;
85 - $frame['pushed_stack'][] = true;
86 164 }
87 165
88 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 +
89 174 return $pre;
90 175 }
91 176
92 177 public static function post($content, $parsed_block)
@@ -100,16 +185,41 @@
100 185 $frame = $S['frames'][$i];
101 186
102 187 $name = is_array($parsed_block) ? ($parsed_block['blockName'] ?? null) : null;
103 188
104 - // Pop pushed flag & optional id (ALWAYS pop to stay balanced)
105 - $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'];
106 216 $id = ($pushed && !empty($frame['id_stack'])) ? array_pop($frame['id_stack']) : null;
107 217
108 218 // Inject only when: outer scope, we counted this block, html present, not admin
109 219 if (!is_admin() && ($S['depth'] ?? 0) === 1 && $pushed && $id && $content && $name) {
110 220 $tp = new \WP_HTML_Tag_Processor($content);
111 - $value = (string) (int) $id;
221 + $value = (string) $id;
112 222
113 223 // Move cursor to the first start tag in the fragment
114 224 if ($tp->next_tag()) {
115 225 $tp->set_attribute('data-extendify-agent-block-id', $value);
@@ -114,8 +224,17 @@
114 224 if ($tp->next_tag()) {
115 225 $tp->set_attribute('data-extendify-agent-block-id', $value);
116 226 $content = $tp->get_updated_html();
117 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 + );
118 237 }
119 238
120 239 // If this block ends an ignored subtree, exit skip mode
121 240 if ($name && in_array($name, self::$ignored, true) && $frame['skip_depth'] > 0) {