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/TagTemplateParts.php +137 -17 3.1.3 → trunk View file →
@@ -48,8 +48,36 @@
48 48 {
49 49 return (($b['blockName'] ?? '') === 'core/template-part');
50 50 }
51 51
52 + private static function isSyncedPattern(array $b): bool
53 + {
54 + return (($b['blockName'] ?? '') === 'core/block');
55 + }
56 +
57 + private static function inFrame(string $prefix): bool
58 + {
59 + return (self::currentFrame()['prefix'] ?? '') === $prefix;
60 + }
61 +
62 + // A prefix opens an id space that belongs to another post; `nav` marks the
63 + // one whose items are stamped from the finished html instead of counted.
64 + private static function newFrame(
65 + string $label,
66 + string $slug,
67 + string $prefix = '',
68 + bool $nav = false
69 + ): array {
70 + return [
71 + 'label' => $label,
72 + 'slug' => $slug,
73 + 'prefix' => $prefix,
74 + 'nav' => $nav,
75 + 'seq' => 0,
76 + 'skip_depth' => 0,
77 + ];
78 + }
79 +
52 80 private static function currentFrame()
53 81 {
54 82 return self::$frames ? self::$frames[count(self::$frames) - 1] : null;
55 83 }
@@ -77,14 +105,12 @@
77 105 return $block;
78 106 }
79 107
80 108 if (self::isTemplatePart($block)) {
81 - self::$frames[] = [
82 - 'label' => self::labelForPart($block),
83 - 'slug' => $block['attrs']['slug'] ?? '',
84 - 'seq' => 0,
85 - 'skip_depth' => 0,
86 - ];
109 + self::$frames[] = self::newFrame(
110 + self::labelForPart($block),
111 + $block['attrs']['slug'] ?? ''
112 + );
87 113 $block['attrs']['__extendify_scope_open'] = 1;
88 114 return $block;
89 115 }
90 116
@@ -96,8 +122,25 @@
96 122 if (!$frame) {
97 123 return $block;
98 124 }
99 125
126 + // The pattern's blocks render inline; counting them here would inflate
127 + // every later id in the part.
128 + if (self::isSyncedPattern($block) && ($frame['skip_depth'] ?? 0) === 0) {
129 + self::$frames[] = self::newFrame(
130 + $frame['label'],
131 + $frame['slug'] ?? '',
132 + TemplatePartBlockFinder::refPrefix('block', $block)
133 + );
134 + return $block;
135 + }
136 +
137 + // A ref nav's items are numbered off its post once it has rendered, so
138 + // nothing inside its frame is counted here.
139 + if (($frame['nav'] ?? false)) {
140 + return $block;
141 + }
142 +
100 143 // render_block_data runs top-down (before a block's own render), so an
101 144 // ignored block is counted here as a leaf and the skip is raised *after*
102 145 // — its descendants then render with skip_depth > 0 and are not counted.
103 146 if (($frame['skip_depth'] ?? 0) === 0) {
@@ -103,9 +146,9 @@
103 146 if (($frame['skip_depth'] ?? 0) === 0) {
104 147 $frame['seq']++;
105 148 self::$blockStack[] = [
106 149 'name' => $name,
107 - 'id' => $frame['seq'],
150 + 'id' => ($frame['prefix'] ?? '') . $frame['seq'],
108 151 'label' => $frame['label'],
109 152 'slug' => $frame['slug'] ?? '',
110 153 ];
111 154 }
@@ -113,11 +156,61 @@
113 156 $frame['skip_depth'] = ($frame['skip_depth'] ?? 0) + 1;
114 157 }
115 158 self::setCurrentFrame($frame);
116 159
160 + if (TemplatePartBlockFinder::isRefNav($block)) {
161 + self::$frames[] = self::newFrame(
162 + $frame['label'],
163 + $frame['slug'] ?? '',
164 + TemplatePartBlockFinder::refPrefix('navigation', $block),
165 + true
166 + );
167 + }
168 +
117 169 return $block;
118 170 }
119 171
172 + // A page-list renders pages that aren't blocks, so a menu holding one is
173 + // left unstamped rather than wrongly numbered.
174 + // phpcs:ignore PSR12.Properties.ConstantVisibility.NotFound
175 + const STAMPABLE_NAV_ITEMS = ['core/navigation-link', 'core/navigation-submenu'];
176 +
177 + public static function stampNavItems(
178 + string $html,
179 + string $prefix,
180 + int $ref,
181 + string $idAttr,
182 + array $extra = []
183 + ): string {
184 + $navPost = \get_post($ref);
185 + if (!$navPost || $navPost->post_type !== 'wp_navigation') {
186 + return $html;
187 + }
188 +
189 + $outline = TemplatePartBlockFinder::outline(parse_blocks($navPost->post_content));
190 + foreach ($outline as $entry) {
191 + if (!in_array($entry['n'], self::STAMPABLE_NAV_ITEMS, true)) {
192 + return $html;
193 + }
194 + }
195 +
196 + $tp = new \WP_HTML_Tag_Processor($html);
197 + $index = 0;
198 + while ($tp->next_tag('LI') && isset($outline[$index])) {
199 + $class = (string) $tp->get_attribute('class');
200 + if (strpos($class, 'wp-block-navigation-item') === false) {
201 + continue;
202 + }
203 + $tp->set_attribute($idAttr, $prefix . $outline[$index]['c']);
204 + foreach ($extra as $attr => $value) {
205 + $tp->set_attribute($attr, $value);
206 + }
207 + $index++;
208 + }
209 +
210 + return $tp->get_updated_html();
211 + }
212 +
120 213 public static function onRenderBlock(string $html, array $block): string
121 214 {
122 215 $name = $block['blockName'] ?? '';
123 216 if ($name === '') {
@@ -132,15 +225,31 @@
132 225 if (empty(self::$frames)) {
133 226 return $html;
134 227 }
135 228
229 + // Popping on the second pass would take the part's own frame.
230 + if (self::isSyncedPattern($block)) {
231 + if (self::inFrame(TemplatePartBlockFinder::refPrefix('block', $block))) {
232 + array_pop(self::$frames);
233 + }
234 + return $html;
235 + }
236 +
237 + // A ref nav's items get stamped from the finished html, not counted here.
238 + $navPrefix = TemplatePartBlockFinder::isRefNav($block)
239 + ? TemplatePartBlockFinder::refPrefix('navigation', $block)
240 + : '';
241 + if ($navPrefix && self::inFrame($navPrefix)) {
242 + array_pop(self::$frames);
243 + } elseif (self::currentFrame()['nav'] ?? false) {
244 + return $html;
245 + }
246 +
136 247 $frame = self::currentFrame();
137 248 $skip = $frame['skip_depth'] ?? 0;
138 249
139 - // render_block runs bottom-up, so the ignored block's own filter fires
140 - // after its descendants — drop one skip level here. Only the outermost
141 - // ignored block (skip === 1) was counted in render_block_data, so only it
142 - // falls through to be stamped; nested ones and descendants bail.
250 + // render_block runs bottom-up, so only the outermost ignored block was
251 + // counted and only it may be stamped.
143 252 if (in_array($name, self::$ignored, true)) {
144 253 $frame['skip_depth'] = max(0, $skip - 1);
145 254 self::setCurrentFrame($frame);
146 255 if ($skip > 1) {
@@ -149,12 +258,10 @@
149 258 } elseif ($skip > 0) {
150 259 return $html;
151 260 }
152 261
153 - // Name-keyed lookup, not array_pop: core/navigation fires render_block
154 - // for inner items without firing render_block_data first, so a
155 - // straight pop would consume entries belonging to unrelated outer
156 - // blocks. If nothing matches, mint a fresh id (nav-link/-submenu path).
262 + // core/navigation fires render_block with no render_block_data, so a
263 + // straight pop eats an outer block's entry.
157 264 $info = null;
158 265 $infoIndex = -1;
159 266 for ($i = count(self::$blockStack) - 1; $i >= 0; $i--) {
160 267 if (self::$blockStack[$i]['name'] === $name) {
@@ -171,9 +278,9 @@
171 278 $frame['seq']++;
172 279 self::setCurrentFrame($frame);
173 280 $info = [
174 281 'name' => $name,
175 - 'id' => $frame['seq'],
282 + 'id' => ($frame['prefix'] ?? '') . $frame['seq'],
176 283 'label' => $frame['label'],
177 284 'slug' => $frame['slug'] ?? '',
178 285 ];
179 286 }
@@ -181,9 +288,9 @@
181 288
182 289 if ($info && $html) {
183 290 $tp = new \WP_HTML_Tag_Processor($html);
184 291 if ($tp->next_tag()) {
185 - $tp->set_attribute('data-extendify-part-block-id', (string) (int) $info['id']);
292 + $tp->set_attribute('data-extendify-part-block-id', (string) $info['id']);
186 293 $tp->set_attribute('data-extendify-part', $info['label']);
187 294 if (!empty($info['slug'])) {
188 295 $tp->set_attribute('data-extendify-part-slug', $info['slug']);
189 296 }
@@ -188,8 +295,21 @@
188 295 $tp->set_attribute('data-extendify-part-slug', $info['slug']);
189 296 }
190 297 $html = $tp->get_updated_html();
191 298 }
299 + }
300 + if ($navPrefix && $info) {
301 + $extra = ['data-extendify-part' => $info['label']];
302 + if (!empty($info['slug'])) {
303 + $extra['data-extendify-part-slug'] = $info['slug'];
304 + }
305 + $html = self::stampNavItems(
306 + $html,
307 + $navPrefix,
308 + (int) $block['attrs']['ref'],
309 + 'data-extendify-part-block-id',
310 + $extra
311 + );
192 312 }
193 313 return $html;
194 314 }
195 315 }