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/TagTemplateParts.php +137 -17 3.1.33.2.1 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 }