PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.1-beta.2
Jetpack – WP Security, Backup, Speed, & Growth v16.1-beta.2
16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / vendor / wp-php-toolkit / data-liberation / DataFormatConsumer / class-markupprocessorconsumer.php
jetpack / vendor / wp-php-toolkit / data-liberation / DataFormatConsumer Last commit date
class-annotatedblockmarkupconsumer.php 6 days ago class-blockswithmetadata.php 6 days ago class-markupprocessorconsumer.php 6 days ago interface-data-format-consumer.php 6 days ago
class-markupprocessorconsumer.php
460 lines
1 <?php
2
3 namespace WordPress\DataLiberation\DataFormatConsumer;
4
5 use WordPress\DataLiberation\BlockMarkup\BlockObject;
6 use WordPress\DataLiberation\DataLiberationException;
7 use WordPress\DataLiberation\Importer\ImportUtils;
8 use WordPress\XML\XMLProcessor;
9 use WP_HTML_Processor;
10 use WP_HTML_Tag_Processor;
11
12 /**
13 * Creates block markup from a WP_HTML_Processor or WP_XML_Processor instance.
14 *
15 * It only considers the markup and won't consider any visual
16 * changes introduced via CSS or JavaScript.
17 *
18 * Example:
19 *
20 * <meta name="post_title" content="My first post">
21 * <p>Hello <b>world</b>!</p>
22 *
23 * Becomes:
24 *
25 * <!-- wp:paragraph -->
26 * <p>Hello <b>world</b>!</p>
27 * <!-- /wp:paragraph -->
28 *
29 * With the following metadata:
30 *
31 * array(
32 * 'post_title' => array( 'My first post' ),
33 * )
34 *
35 * @TODO: Satisfy the same test suite as the Gutenberg paste/raw handler.
36 * Look for the test examples (input/output) in the Gutenberg repo.
37 * @TODO: Consider option for "presentation" vs "semantics" mode. We can preserve
38 * anything in the HTML block if needed.
39 */
40 class MarkupProcessorConsumer implements DataFormatConsumer {
41 private $markup_processor;
42 private $ignore_text = false;
43 private $in_ephemeral_paragraph = false;
44 private $block_stack = array();
45
46 private $parsed;
47 private $block_markup = '';
48 private $metadata = array();
49
50 public function __construct( $markup_processor ) {
51 $this->markup_processor = $markup_processor;
52 }
53
54 public function consume() {
55 if ( ! $this->parsed ) {
56 while ( $this->markup_processor->next_token() ) {
57 switch ( $this->markup_processor->get_token_type() ) {
58 case '#text':
59 if ( $this->ignore_text ) {
60 break;
61 }
62 $this->append_rich_text( htmlspecialchars( $this->markup_processor->get_modifiable_text() ) );
63 if ( in_array( $this->get_tag_name(), array( 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ) ) ) {
64 $this->on_title_candidate( $this->markup_processor->get_modifiable_text() );
65 }
66 break;
67 case '#tag':
68 $this->handle_tag();
69 break;
70 }
71 }
72
73 if ( $this->markup_processor->get_last_error() ) {
74 if ( $this->markup_processor instanceof WP_HTML_Processor ) {
75 $exception = $this->markup_processor->get_unsupported_exception();
76 } elseif ( $this->markup_processor instanceof XMLProcessor ) {
77 $exception = $this->markup_processor->get_exception();
78 } else {
79 $exception = null;
80 }
81 throw new DataLiberationException( esc_html( $this->markup_processor->get_last_error() ), 0, $exception );
82 }
83
84 $this->close_ephemeral_paragraph();
85 $this->parsed = new BlocksWithMetadata( $this->block_markup, $this->metadata );
86 }
87
88 return $this->parsed;
89 }
90
91 private function handle_tag() {
92 $html = $this->markup_processor;
93 if ( $html instanceof WP_HTML_Processor ) {
94 $tag = strtoupper( $html->get_tag() );
95 } else {
96 $tag = strtoupper( $html->get_tag_local_name() );
97 }
98 $tag_lowercase = strtolower( $tag );
99
100 $is_void_tag = ! $html->expects_closer() && ! $html->is_tag_closer();
101 if ( $is_void_tag ) {
102 switch ( $tag ) {
103 case 'TITLE':
104 $this->on_title_candidate( $html->get_modifiable_text() );
105 break;
106 case 'META':
107 $key = $this->get_attribute( 'name' );
108 $value = $this->get_attribute( 'content' );
109 if ( ! array_key_exists( $key, $this->metadata ) ) {
110 if ( $key ) {
111 $this->metadata[ $key ] = array();
112 }
113 }
114 switch ( $this->get_attribute( 'type' ) ) {
115 case 'integer':
116 $value = (int) $value;
117 break;
118 case 'boolean':
119 $value = (bool) $value;
120 break;
121 // @TODO: Discuss what would support for other types look like.
122 }
123 if ( $key ) {
124 $this->metadata[ $key ][] = $value;
125 }
126 break;
127 case 'IMG':
128 $template = new WP_HTML_Tag_Processor( '<img>' );
129 $template->next_tag();
130 foreach ( array( 'alt', 'title', 'src' ) as $attr ) {
131 if ( $this->get_attribute( $attr ) ) {
132 $template->set_attribute( $attr, $this->get_attribute( $attr ) );
133 }
134 }
135 /**
136 * Set the image template with updated HTML.
137 */
138 $this->append_rich_text( $template->get_updated_html() );
139 break;
140 default:
141 // @TODO: What to do with other void tags, e.g. <input>?
142 // Just insert an HTML block or what?
143 break;
144 }
145 } elseif ( ! $html->is_tag_closer() ) {
146 switch ( $tag ) {
147 // Block elements.
148 case 'SCRIPT':
149 $this->ignore_text = true;
150 break;
151 case 'UL':
152 case 'OL':
153 $this->push_block( 'list', array( 'ordered' => 'ol' === $tag ) );
154 $this->block_markup .= '<ul class="wp-block-list">';
155 break;
156 case 'LI':
157 $this->push_block( 'list-item' );
158 $this->block_markup .= '<' . $tag_lowercase . '>';
159 break;
160 case 'TABLE':
161 $this->push_block( 'table' );
162 $this->block_markup .= '<figure class="wp-block-table">';
163 $this->block_markup .= '<table class="has-fixed-layout">';
164 break;
165 case 'THEAD':
166 case 'TBODY':
167 case 'TFOOT':
168 case 'TR':
169 case 'TD':
170 case 'TH':
171 $this->block_markup .= '<' . $tag_lowercase . '>';
172 break;
173 case 'BLOCKQUOTE':
174 $this->push_block( 'quote' );
175 $this->block_markup .= '<' . $tag_lowercase . '>';
176 break;
177 case 'PRE':
178 $this->push_block( 'code' );
179 $this->block_markup .= '<' . $tag_lowercase . ' class="wp-block-code">';
180 break;
181 case 'CODE':
182 // Guess whether this is:
183 // * An inline <code> element? Let's convert it into a formatting element.
184 // * A block <code> element? Let's convert it into a block.
185 if ( $this->is_at_inline_code_element() ) {
186 $this->append_rich_text( '<' . $tag_lowercase . '>' );
187 } else {
188 $this->push_block( 'code' );
189 $this->block_markup .= '<' . $tag_lowercase . ' class="wp-block-code">';
190 }
191 break;
192 case 'HR':
193 $this->push_block( 'separator' );
194 break;
195 case 'P':
196 $this->push_block( 'paragraph' );
197 $this->block_markup .= '<p>';
198 break;
199 case 'H1':
200 case 'H2':
201 case 'H3':
202 case 'H4':
203 case 'H5':
204 case 'H6':
205 $this->push_block(
206 'heading',
207 array(
208 'level' => (int) $tag[1] ? (int) $tag[1] : 1,
209 )
210 );
211 $this->block_markup .= '<h' . $tag[1] . '>';
212 break;
213
214 // Inline elements.
215 case 'A':
216 $template = new WP_HTML_Tag_Processor( '<a>' );
217 $template->next_tag();
218 if ( $this->get_attribute( 'href' ) ) {
219 $template->set_attribute( 'href', $this->get_attribute( 'href' ) );
220 }
221 /**
222 * Set the link template with updated HTML.
223 */
224 $this->append_rich_text( $template->get_updated_html() );
225 break;
226
227 // Formats – just pass through (minus the HTML attributes).
228 default:
229 if ( $this->should_preserve_tag_in_rich_text( $tag ) ) {
230 $this->append_rich_text( '<' . $tag_lowercase . '>' );
231 }
232 // @TODO: What to do with other tags? Just insert an HTML block or what?
233 break;
234 }
235 } elseif ( $html->is_tag_closer() ) {
236 switch ( $tag ) {
237 case 'SCRIPT':
238 $this->ignore_text = false;
239 break;
240 // Maintain the same lists as in the tag opener code branch above,
241 // otherwise we won't pop the correct block.
242
243 // Block elements.
244 // Post-process the collected table data.
245 case 'TABLE':
246 $this->block_markup .= '</table>';
247 $this->block_markup .= '</figure>';
248 $this->pop_block();
249 break;
250
251 case 'THEAD':
252 case 'TBODY':
253 case 'TFOOT':
254 case 'TR':
255 case 'TD':
256 case 'TH':
257 $this->block_markup .= '</' . $tag_lowercase . '>';
258 break;
259
260 case 'CODE':
261 $this->block_markup .= '</' . $tag_lowercase . '>';
262 if ( ! $this->is_at_inline_code_element() ) {
263 $this->pop_block();
264 }
265 break;
266
267 // Block elements.
268 case 'UL':
269 case 'OL':
270 $this->block_markup .= '</ul>';
271 $this->pop_block();
272 break;
273
274 case 'LI':
275 case 'BLOCKQUOTE':
276 case 'PRE':
277 case 'HR':
278 case 'P':
279 case 'H1':
280 case 'H2':
281 case 'H3':
282 case 'H4':
283 case 'H5':
284 case 'H6':
285 $this->block_markup .= '</' . $tag_lowercase . '>';
286 $this->pop_block();
287 break;
288
289 case 'A':
290 $this->block_markup .= '</a>';
291 break;
292
293 // Formats.
294 default:
295 if ( $this->should_preserve_tag_in_rich_text( $tag ) ) {
296 $this->block_markup .= '</' . $tag_lowercase . '>';
297 }
298 break;
299 }
300 }
301 }
302
303 private function get_tag_name() {
304 if ( $this->markup_processor instanceof WP_HTML_Processor ) {
305 return $this->markup_processor->get_tag();
306 } else {
307 return $this->markup_processor->get_tag_local_name();
308 }
309 }
310
311 private function get_attribute( $key ) {
312 if ( $this->markup_processor instanceof WP_HTML_Processor ) {
313 return $this->markup_processor->get_attribute( $key );
314 } else {
315 return $this->markup_processor->get_attribute( '', $key );
316 }
317 }
318
319 private function on_title_candidate( $text ) {
320 if ( ! array_key_exists( 'post_title', $this->metadata ) ) {
321 $this->metadata['post_title'] = array(
322 $text,
323 );
324 }
325 if ( ! array_key_exists( 'post_name', $this->metadata ) ) {
326 $this->metadata['post_name'] = array(
327 // @TODO: Slugify.
328 $text,
329 );
330 }
331 }
332
333 /**
334 * Checks whether the given tag is an inline formatting element
335 * that we want to preserve when parsing rich text. For example,
336 * <b> tags are meaningful from the rich text perspective, but
337 * <div> tags are not.
338 *
339 * @param string $tag The tag to check.
340 *
341 * @return bool Whether the tag should be preserved in rich text.
342 */
343 private function should_preserve_tag_in_rich_text( $tag ) {
344 return in_array(
345 $tag,
346 array(
347 'B',
348 'STRONG',
349 'I',
350 'U',
351 'S',
352 'SMALL',
353 'SUP',
354 'SUB',
355 'MARK',
356 'EM',
357 'CITE',
358 'DFN',
359 'CODE',
360 'KBD',
361 'SAMP',
362 'VAR',
363 ),
364 true
365 );
366 }
367
368 private function is_at_inline_code_element() {
369 $breadcrumbs = $this->markup_processor->get_breadcrumbs();
370 foreach ( $breadcrumbs as $tag ) {
371 switch ( $tag ) {
372 case 'A':
373 case 'P':
374 case 'LI':
375 case 'TABLE':
376 case 'H1':
377 case 'H2':
378 case 'H3':
379 case 'H4':
380 case 'H5':
381 case 'H6':
382 return true;
383 }
384 }
385
386 return false;
387 }
388
389 /**
390 * Appends a snippet of HTML to the block markup.
391 * Ensures given $html is a part of a block. If no block is
392 * currently open, it appends a new paragraph block.
393 *
394 * @param string $html The HTML snippet to append.
395 */
396 private function append_rich_text( $html ) {
397 $html = trim( $html );
398 if ( empty( $html ) ) {
399 return;
400 }
401 // Make sure two subsequent append_text() calls don't merge the text.
402 $html .= ' ';
403 $this->ensure_open_block();
404 $this->block_markup .= $html;
405 }
406
407 /**
408 * Pushes a new block onto the stack of open blocks and appends the block
409 * opener to the block markup.
410 *
411 * @param string $name The name of the block to push.
412 * @param array $attributes The attributes of the block to push.
413 */
414 private function push_block( $name, $attributes = array() ) {
415 $this->close_ephemeral_paragraph();
416 $block = new BlockObject( $name, $attributes );
417 array_push( $this->block_stack, $block );
418 $this->block_markup .= ImportUtils::block_opener( $block->block_name, $block->attrs ) . "\n";
419 }
420
421 /**
422 * Pops the last block from the stack of open blocks and appends the block
423 * closer to the block markup.
424 *
425 * @return BlockObject The last block that was popped.
426 */
427 private function pop_block() {
428 if ( ! empty( $this->block_stack ) ) {
429 $popped = array_pop( $this->block_stack );
430 $this->block_markup .= ImportUtils::block_closer( $popped->block_name ) . "\n";
431
432 return $popped;
433 }
434 }
435
436 /**
437 * Ensures that a block is open. If no block is currently open, it appends
438 * a new, ephemeral paragraph block that will be automatically closed
439 * when the next block opens OR when the HTML ends.
440 */
441 private function ensure_open_block() {
442 if ( empty( $this->block_stack ) && ! $this->in_ephemeral_paragraph ) {
443 $this->block_markup .= ImportUtils::block_opener( 'paragraph' ) . "\n";
444 $this->block_markup .= '<p>';
445 $this->in_ephemeral_paragraph = true;
446 }
447 }
448
449 /**
450 * Closes the ephemeral paragraph if it is currently open.
451 */
452 private function close_ephemeral_paragraph() {
453 if ( $this->in_ephemeral_paragraph ) {
454 $this->block_markup .= '</p>';
455 $this->block_markup .= ImportUtils::block_closer( 'paragraph' );
456 $this->in_ephemeral_paragraph = false;
457 }
458 }
459 }
460