PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.1-a.3
Jetpack – WP Security, Backup, Speed, & Growth v16.1-a.3
12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 14.4.2 All 500 releases
jetpack / _inc / lib / markdown / gfm.php
gfm.php
450 lines 13.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * GitHub-Flavoured Markdown. Inspired by Evan's plugin, but modified.
4 *
5 * @author Evan Solomon
6 * @author Matt Wiebe <wiebe@automattic.com>
7 * @author Brandon Kraft <kraft@automattic.com> -- Strikedown support converted from GPL code at https://github.com/annaesvensson/yellow-markdown/blob/main/markdown.php
8 * @link https://github.com/evansolomon/wp-github-flavored-markdown-comments
9 *
10 * Add a few extras from GitHub's Markdown implementation. Must be used in a WordPress environment.
11 */
12
13 if ( ! defined( 'ABSPATH' ) ) {
14 exit( 0 );
15 }
16
17 class WPCom_GHF_Markdown_Parser extends MarkdownExtra_Parser {
18
19 /**
20 * Hooray somewhat arbitrary numbers that are fearful of 1.0.x.
21 */
22 const WPCOM_GHF_MARDOWN_VERSION = '0.9.1';
23
24 /**
25 * Use a [code] shortcode when encountering a fenced code block
26 * @var boolean
27 */
28 public $use_code_shortcode = true;
29
30 /**
31 * Preserve shortcodes, untouched by Markdown.
32 * This requires use within a WordPress installation.
33 * @var boolean
34 */
35 public $preserve_shortcodes = true;
36
37 /**
38 * Preserve the legacy $latex your-latex-code-here$ style
39 * LaTeX markup
40 */
41 public $preserve_latex = true;
42
43 /**
44 * Preserve single-line <code> blocks.
45 * @var boolean
46 */
47 public $preserve_inline_code_blocks = true;
48
49 /**
50 * Strip paragraphs from the output. This is the right default for WordPress,
51 * which generally wants to create its own paragraphs with `wpautop`
52 * @var boolean
53 */
54 public $strip_paras = true;
55
56 // Will run through sprintf - you can supply your own syntax if you want
57 public $shortcode_start = '[code lang=%s]';
58 public $shortcode_end = '[/code]';
59
60 // Stores shortcodes we remove and then replace
61 protected $preserve_text_hash = array();
62
63 /**
64 * Set environment defaults based on presence of key functions/classes.
65 */
66 public function __construct() {
67 $this->use_code_shortcode = class_exists( 'SyntaxHighlighter' );
68 /**
69 * Allow processing shortcode contents.
70 *
71 * @module markdown
72 *
73 * @since 4.4.0
74 *
75 * @param boolean $preserve_shortcodes Defaults to $this->preserve_shortcodes.
76 */
77 $this->preserve_shortcodes = apply_filters( 'jetpack_markdown_preserve_shortcodes', $this->preserve_shortcodes ) && function_exists( 'get_shortcode_regex' );
78 $this->preserve_latex = function_exists( 'latex_markup' );
79 $this->strip_paras = function_exists( 'wpautop' );
80
81 $this->span_gamut['do_strikethrough'] = 55;
82
83 parent::__construct();
84 }
85
86 /**
87 * Overload to specify heading styles only if the hash has space(s) after it. This is actually in keeping with
88 * the documentation and eases the semantic overload of the hash character.
89 * #Will Not Produce a Heading 1
90 * # This Will Produce a Heading 1
91 *
92 * @param string $text Markdown text
93 * @return string HTML-transformed text
94 */
95 public function transform( $text ) {
96 // Preserve anything inside a single-line <code> element
97 if ( $this->preserve_inline_code_blocks ) {
98 $text = $this->single_line_code_preserve( $text );
99 }
100 // Remove all shortcodes so their interiors are left intact
101 if ( $this->preserve_shortcodes ) {
102 $text = $this->shortcode_preserve( $text );
103 }
104 // Remove legacy LaTeX so it's left intact
105 if ( $this->preserve_latex ) {
106 $text = $this->latex_preserve( $text );
107 }
108
109 // Do not process characters inside URLs.
110 $text = $this->urls_preserve( $text );
111
112 // escape line-beginning # chars that do not have a space after them.
113 $text = preg_replace_callback( '|^#{1,6}( )?|um', array( $this, '_doEscapeForHashWithoutSpacing' ), $text );
114
115 /**
116 * Allow third-party plugins to define custom patterns that won't be processed by Markdown.
117 *
118 * @module markdown
119 *
120 * @since 3.9.2
121 *
122 * @param array $custom_patterns Array of custom patterns to be ignored by Markdown.
123 */
124 $custom_patterns = apply_filters( 'jetpack_markdown_preserve_pattern', array() );
125 if ( is_array( $custom_patterns ) && ! empty( $custom_patterns ) ) {
126 foreach ( $custom_patterns as $pattern ) {
127 $text = preg_replace_callback( $pattern, array( $this, '_doRemoveText'), $text );
128 }
129 }
130
131 // run through core Markdown
132 $text = parent::transform( $text );
133
134 // Occasionally Markdown Extra chokes on a para structure, producing odd paragraphs.
135 $text = str_replace( "<p>&lt;</p>\n\n<p>p>", '<p>', $text );
136
137 // put start-of-line # chars back in place
138 $text = $this->restore_leading_hash( $text );
139
140 // Strip paras if set
141 if ( $this->strip_paras ) {
142 $text = $this->unp( $text );
143 }
144
145 // Restore preserved things like shortcodes/LaTeX
146 $text = $this->do_restore( $text );
147
148 return $text;
149 }
150
151 /**
152 * Prevents blocks like <code>__this__</code> from turning into <code><strong>this</strong></code>
153 * @param string $text Text that may need preserving
154 * @return string Text that was preserved if needed
155 */
156 public function single_line_code_preserve( $text ) {
157 return preg_replace_callback( '|<code\b[^>]*>(.*?)</code>|', array( $this, 'do_single_line_code_preserve' ), $text );
158 }
159
160 /**
161 * Regex callback for inline code presevation
162 * @param array $matches Regex matches
163 * @return string Hashed content for later restoration
164 */
165 public function do_single_line_code_preserve( $matches ) {
166 return '<code>' . $this->hash_block( $matches[1] ) . '</code>';
167 }
168
169 /**
170 * Preserve code block contents by HTML encoding them. Useful before getting to KSES stripping.
171 * @param string $text Markdown/HTML content
172 * @return string Markdown/HTML content with escaped code blocks
173 */
174 public function codeblock_preserve( $text ) {
175 return preg_replace_callback( "/^([`~]{3})([^`\n]+)?\n([^`~]+)(\\1)/m", array( $this, 'do_codeblock_preserve' ), $text );
176 }
177
178 /**
179 * Regex callback for code block preservation.
180 * @param array $matches Regex matches
181 * @return string Codeblock with escaped interior
182 */
183 public function do_codeblock_preserve( $matches ) {
184 $block = stripslashes( $matches[3] );
185 $block = esc_html( $block );
186 $block = str_replace( '\\', '\\\\', $block );
187 $open = $matches[1] . $matches[2] . "\n";
188 return $open . $block . $matches[4];
189 }
190
191 /**
192 * Restore previously preserved (i.e. escaped) code block contents.
193 * @param string $text Markdown/HTML content with escaped code blocks
194 * @return string Markdown/HTML content
195 */
196 public function codeblock_restore( $text ) {
197 return preg_replace_callback( "/^([`~]{3})([^`\n]+)?\n([^`~]+)(\\1)/m", array( $this, 'do_codeblock_restore' ), $text );
198 }
199
200 /**
201 * Regex callback for code block restoration (unescaping).
202 * @param array $matches Regex matches
203 * @return string Codeblock with unescaped interior
204 */
205 public function do_codeblock_restore( $matches ) {
206 $block = html_entity_decode( $matches[3], ENT_QUOTES );
207 $open = $matches[1] . $matches[2] . "\n";
208 return $open . $block . $matches[4];
209 }
210
211 /**
212 * Called to preserve legacy LaTeX like $latex some-latex-text $
213 * @param string $text Text in which to preserve LaTeX
214 * @return string Text with LaTeX replaced by a hash that will be restored later
215 */
216 protected function latex_preserve( $text ) {
217 // regex from latex_remove()
218 $regex = '%
219 \$latex(?:=\s*|\s+)
220 ((?:
221 [^$]+ # Not a dollar
222 |
223 (?<=(?<!\\\\)\\\\)\$ # Dollar preceded by exactly one slash
224 )+)
225 (?<!\\\\)\$ # Dollar preceded by zero slashes
226 %ix';
227 $text = preg_replace_callback( $regex, array( $this, '_doRemoveText'), $text );
228 return $text;
229 }
230
231 /**
232 * Called to preserve WP shortcodes from being formatted by Markdown in any way.
233 * @param string $text Text in which to preserve shortcodes
234 * @return string Text with shortcodes replaced by a hash that will be restored later
235 */
236 protected function shortcode_preserve( $text ) {
237 $text = preg_replace_callback( $this->get_shortcode_regex(), array( $this, '_doRemoveText' ), $text );
238 return $text;
239 }
240
241 /**
242 * Avoid characters inside URLs from being formatted by Markdown in any way.
243 *
244 * @param string $text Text in which to preserve URLs.
245 *
246 * @return string Text with URLs replaced by a hash that will be restored later.
247 */
248 protected function urls_preserve( $text ) {
249 $text = preg_replace_callback(
250 '#(?<!<)(?:https?|ftp)://([^\s<>"\'\[\]()]+|\[(?1)*+\]|\((?1)*+\))+(?<![_*.?])#i',
251 array( $this, '_doRemoveText' ),
252 $text
253 );
254 return $text;
255 }
256
257 /**
258 * Restores any text preserved by $this->hash_block()
259 * @param string $text Text that may have hashed preservation placeholders
260 * @return string Text with hashed preseravtion placeholders replaced by original text
261 */
262 protected function do_restore( $text ) {
263 // Reverse hashes to ensure nested blocks are restored.
264 $hashes = array_reverse( $this->preserve_text_hash, true );
265 foreach( $hashes as $hash => $value ) {
266 $placeholder = $this->hash_maker( $hash );
267 $text = str_replace( $placeholder, $value, $text );
268 }
269 // reset the hash
270 $this->preserve_text_hash = array();
271 return $text;
272 }
273
274 /**
275 * Regex callback for text preservation
276 * @param array $m Regex $matches array
277 * @return string A placeholder that will later be replaced by the original text
278 */
279 protected function _doRemoveText( $m ) {
280 return $this->hash_block( $m[0] );
281 }
282
283 /**
284 * Call this to store a text block for later restoration.
285 * @param string $text Text to preserve for later
286 * @return string Placeholder that will be swapped out later for the original text
287 */
288 protected function hash_block( $text ) {
289 $hash = md5( $text );
290 $this->preserve_text_hash[ $hash ] = $text;
291 $placeholder = $this->hash_maker( $hash );
292 return $placeholder;
293 }
294
295 /**
296 * Less glamorous than the Keymaker
297 * @param string $hash An md5 hash
298 * @return string A placeholder hash
299 */
300 protected function hash_maker( $hash ) {
301 return 'MARKDOWN_HASH' . $hash . 'MARKDOWN_HASH';
302 }
303
304 /**
305 * Remove bare <p> elements. <p>s with attributes will be preserved.
306 * @param string $text HTML content
307 * @return string <p>-less content
308 */
309 public function unp( $text ) {
310 return preg_replace( "#<p>(.*?)</p>(\n|$)#ums", '$1$2', $text );
311 }
312
313 /**
314 * A regex of all shortcodes currently registered by the current
315 * WordPress installation
316 * @uses get_shortcode_regex()
317 * @return string A regex for grabbing shortcodes.
318 */
319 protected function get_shortcode_regex() {
320 $pattern = get_shortcode_regex();
321
322 // don't match markdown link anchors that could be mistaken for shortcodes.
323 $pattern .= '(?!\()';
324
325 return "/$pattern/s";
326 }
327
328 /**
329 * Since we escape unspaced #Headings, put things back later.
330 * @param string $text text with a leading escaped hash
331 * @return string text with leading hashes unescaped
332 */
333 protected function restore_leading_hash( $text ) {
334 return preg_replace( "/^(<p>)?(&#35;|\\\\#)/um", "$1#", $text );
335 }
336
337 /**
338 * Overload to support ```-fenced code blocks for pre-Markdown Extra 1.2.8
339 * https://help.github.com/articles/github-flavored-markdown#fenced-code-blocks
340 */
341 public function doFencedCodeBlocks( $text ) {
342 // If we're at least at 1.2.8, native fenced code blocks are in.
343 // Below is just copied from it in case we somehow got loaded on
344 // top of someone else's Markdown Extra
345 if ( version_compare( MARKDOWNEXTRA_VERSION, '1.2.8', '>=' ) )
346 return parent::doFencedCodeBlocks( $text );
347
348 #
349 # Adding the fenced code block syntax to regular Markdown:
350 #
351 # ~~~
352 # Code block
353 # ~~~
354 #
355 $less_than_tab = $this->tab_width;
356
357 $text = preg_replace_callback('{
358 (?:\n|\A)
359 # 1: Opening marker
360 (
361 (?:~{3,}|`{3,}) # 3 or more tildes/backticks.
362 )
363 [ ]*
364 (?:
365 \.?([-_:a-zA-Z0-9]+) # 2: standalone class name
366 |
367 '.$this->id_class_attr_catch_re.' # 3: Extra attributes
368 )?
369 [ ]* \n # Whitespace and newline following marker.
370
371 # 4: Content
372 (
373 (?>
374 (?!\1 [ ]* \n) # Not a closing marker.
375 .*\n+
376 )+
377 )
378
379 # Closing marker.
380 \1 [ ]* (?= \n )
381 }xm',
382 array($this, '_doFencedCodeBlocks_callback'), $text);
383
384 return $text;
385 }
386
387 /**
388 * Callback for pre-processing start of line hashes to slyly escape headings that don't
389 * have a leading space
390 * @param array $m preg_match matches
391 * @return string possibly escaped start of line hash
392 */
393 public function _doEscapeForHashWithoutSpacing( $m ) {
394 if ( ! isset( $m[1] ) )
395 $m[0] = '\\' . $m[0];
396 return $m[0];
397 }
398
399 /**
400 * Overload to support Viper's [code] shortcode. Because awesome.
401 */
402 public function _doFencedCodeBlocks_callback( $matches ) {
403 // in case we have some escaped leading hashes right at the start of the block
404 $matches[4] = $this->restore_leading_hash( $matches[4] );
405 // just MarkdownExtra_Parser if we're not going ultra-deluxe
406 if ( ! $this->use_code_shortcode ) {
407 return parent::_doFencedCodeBlocks_callback( $matches );
408 }
409
410 // default to a "text" class if one wasn't passed. Helps with encoding issues later.
411 if ( empty( $matches[2] ) ) {
412 $matches[2] = 'text';
413 }
414
415 $classname =& $matches[2];
416 $codeblock = preg_replace_callback('/^\n+/', array( $this, '_doFencedCodeBlocks_newlines' ), $matches[4] );
417
418 if ( $classname[0] == '.' )
419 $classname = substr( $classname, 1 );
420
421 $codeblock = esc_html( $codeblock );
422 $codeblock = sprintf( $this->shortcode_start, $classname ) . "\n{$codeblock}" . $this->shortcode_end;
423 return "\n\n" . $this->hashBlock( $codeblock ). "\n\n";
424 }
425
426 /**
427 * Add strikethrough support.
428 *
429 * GPL code modified from https://github.com/annaesvensson/yellow-markdown/blob/main/markdown.php
430 */
431 public function do_strikethrough($text) {
432 $parts = preg_split("/(?<![~])(~~)(?![~])/", $text, -1, PREG_SPLIT_DELIM_CAPTURE);
433 if (count($parts)>3) {
434 $text = "";
435 $open = false;
436 foreach ($parts as $part) {
437 if ($part=="~~") {
438 $text .= $open ? "</del>" : "<del>";
439 $open = !$open;
440 } else {
441 $text .= $part;
442 }
443 }
444 if ($open) $text .= "</del>";
445 }
446 return $text;
447 }
448
449 }
450