PluginProbe
Radio Station by netmix® – Manage and play your Show Schedule in WordPress! / trunk
Radio Station by netmix® – Manage and play your Show Schedule in WordPress! vtrunk
2.7.1 trunk 2.2.6 2.2.7 2.2.8 2.3.0 2.3.1 2.3.2 2.3.3 2.3.3.1 2.3.3.2 2.3.3.3 2.3.3.4 2.3.3.5 2.3.3.6 2.3.3.7 2.3.3.8 2.3.3.9 2.4.0 2.4.0.3 2.4.0.4 2.4.0.5 2.4.0.6 2.4.0.7 2.4.0.8 All 44 releases
radio-station / reader.php

reader.php in Radio Station by netmix® – Manage and play your Show Schedule in WordPress! trunk, at reader.php

4,019 lines 119.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 // ========================
4 // Prefixed Markdown Reader
5 // ========================
6 // for Plugin Panel Loader 1.3.1+
7 // ------------------------------
8
9 // === Markdown ===
10 // - Markdown Function
11 // - Markdown Parser Class
12 // - Markdown Extra Parser Class
13 // - WordPress Readme Parser
14 // - Github Flavoured Markdown
15
16 // === Usage ===
17 // Simply replace all occurrences of radio_station_ in this file with the plugin namespace prefix eg. my_plugin_
18 // that matches your usage in loader.php - and remember to repeat this process if/when updating!
19
20
21 #
22 # Markdown Extra - A text-to-HTML conversion tool for web writers
23 #
24 # PHP Markdown & Extra
25 # Copyright (c) 2004-2013 Michel Fortin
26 # <http://michelf.ca/projects/php-markdown/>
27 #
28 # Original Markdown
29 # Copyright (c) 2004-2006 John Gruber
30 # <http://daringfireball.net/projects/markdown/>
31 #
32 # Tweaked to remove WordPress interface
33
34 if ( !defined( 'ABSPATH' ) ) exit;
35
36 // if (!defined('MARKDOWN_VERSION')) {
37 // define( 'MARKDOWN_VERSION', "1.0.2" ); # 29 Nov 2013
38 // }
39 // if (!defined('MARKDOWNEXTRA_VERSION')) {
40 // define( 'MARKDOWNEXTRA_VERSION', "1.2.8" ); # 29 Nov 2013
41 // }
42
43
44 #
45 # Global default settings:
46 #
47
48 # Change to ">" for HTML output
49 // if (!defined('MARKDOWN_EMPTY_ELEMENT_SUFFIX')) {
50 // @define( 'MARKDOWN_EMPTY_ELEMENT_SUFFIX', " />");
51 // }
52
53 # Define the width of a tab for code blocks.
54 // if (!defined('MARKDOWN_TAB_WIDTH')) {
55 // @define( 'MARKDOWN_TAB_WIDTH', 4 );
56 // }
57
58 # Optional title attribute for footnote links and backlinks.
59 // if (!defined('MARKDOWN_FN_LINK_TITLE')) {
60 // @define( 'MARKDOWN_FN_LINK_TITLE', "" );
61 // }
62 // if (!defined('MARKDOWN_FN_BACKLINK_TITLE')) {
63 // @define( 'MARKDOWN_FN_BACKLINK_TITLE', "" );
64 // }
65
66 # Optional class attribute for footnote links and backlinks.
67 // if (!defined('MARKDOWN_FN_LINK_CLASS')) {
68 // @define( 'MARKDOWN_FN_LINK_CLASS', "" );
69 // }
70 // if (!defined('MARKDOWN_FN_BACKLINK_CLASS')) {
71 // @define( 'MARKDOWN_FN_BACKLINK_CLASS', "" );
72 // }
73
74 # Optional class prefix for fenced code block.
75 // if (!defined('MARKDOWN_CODE_CLASS_PREFIX')) {
76 // @define( 'MARKDOWN_CODE_CLASS_PREFIX', "" );
77 // }
78
79 # Class attribute for code blocks goes on the `code` tag;
80 # setting this to true will put attributes on the `pre` tag instead.
81 // if (!defined('MARKDOWN_CODE_ATTR_ON_PRE')) {
82 // @define( 'MARKDOWN_CODE_ATTR_ON_PRE', false );
83 // }
84
85
86 #
87 # WordPress settings:
88 #
89
90 # Change to false to remove Markdown from posts and/or comments.
91 // if (!defined('MARKDOWN_WP_POSTS')) {
92 // @define( 'MARKDOWN_WP_POSTS', false );
93 // }
94 // if (!defined('MARKDOWN_WP_COMMENTS')) {
95 // @define( 'MARKDOWN_WP_COMMENTS', false );
96 // }
97
98
99 ### Standard Function Interface ###
100 // if (!defined('MARKDOWN_PARSER_CLASS')) {
101 // @define( 'MARKDOWN_PARSER_CLASS', 'MarkdownExtra_Parser' );
102 // }
103
104 if ( !function_exists( 'radio_station_markdown' ) ) {
105
106 function radio_station_markdown( $text ) {
107 #
108 # Initialize the parser and return the result of its transform method.
109 #
110 # Setup static parser variable.
111 static $parser;
112 if ( !isset( $parser ) ) {
113 $parser_class = 'radio_station_markdown_extra_parser'; // MARKDOWN_PARSER_CLASS
114 $parser = new $parser_class;
115 }
116
117 # Transform text using parser.
118 return $parser->transform( $text );
119 }
120 }
121
122 // moved to internal function method utf8_strlen
123 /**
124 * Returns the length of $text loosely counting the number of UTF-8 characters with regular expression.
125 * Used by the Markdown_Parser class when mb_strlen is not available.
126 *
127 * @since 5.9
128 *
129 * @return string Length of the multibyte string
130 *
131 */
132 // if (!function_exists('markdown_extra_utf8_strlen')) {
133 // function markdown_extra_utf8_strlen( $text ) {
134 // return preg_match_all( "/[\\x00-\\xBF]|[\\xC0-\\xFF][\\x80-\\xBF]*/", $text, $m );
135 // }
136 // }
137
138 #
139 # Markdown Parser Class
140 #
141
142 if ( !class_exists( 'radio_station_markdown_parser' ) ) {
143 class radio_station_markdown_parser {
144
145 ### Configuration Variables ###
146
147 # Change to ">" for HTML output.
148 public $empty_element_suffix = " />"; // MARKDOWN_EMPTY_ELEMENT_SUFFIX
149 public $tab_width = 4; // MARKDOWN_TAB_WIDTH;
150
151 # Change to `true` to disallow markup or entities.
152 public $no_markup = false;
153 public $no_entities = false;
154
155 # Predefined urls and titles for reference links and images.
156 public $predef_urls = array();
157 public $predef_titles = array();
158
159
160 ### Parser Implementation ###
161
162 # Regex to match balanced [brackets].
163 # Needed to insert a maximum bracked depth while converting to PHP.
164 public $nested_brackets_depth = 6;
165 public $nested_brackets_re;
166
167 public $nested_url_parenthesis_depth = 4;
168 public $nested_url_parenthesis_re;
169
170 # Table of hash values for escaped characters:
171 public $escape_chars = '\`*_{}[]()>#+-.!';
172 public $escape_chars_re;
173
174 # Constructor function. Initialize appropriate member variables.
175 function __construct() {
176
177 // _initDetab removed due to internal utf8_strlen function
178 // $this->_initDetab();
179 $this->prepareItalicsAndBold();
180
181 $this->nested_brackets_re =
182 str_repeat( '(?>[^\[\]]+|\[', $this->nested_brackets_depth ) .
183 str_repeat( '\])*', $this->nested_brackets_depth );
184
185 $this->nested_url_parenthesis_re =
186 str_repeat( '(?>[^()\s]+|\(', $this->nested_url_parenthesis_depth ) .
187 str_repeat( '(?>\)))*', $this->nested_url_parenthesis_depth );
188
189 $this->escape_chars_re = '[' . preg_quote( $this->escape_chars ) . ']';
190
191 # Sort document, block, and span gamut in ascendent priority order.
192 asort( $this->document_gamut );
193 asort( $this->block_gamut );
194 asort( $this->span_gamut );
195 }
196
197
198 # Internal hashes used during transformation.
199 public $urls = array();
200 public $titles = array();
201 public $html_hashes = array();
202
203 # Status flag to avoid invalid nesting.
204 public $in_anchor = false;
205
206 # Called before the transformation process starts to setup parser
207 # states.
208 function setup() {
209 # Clear global hashes.
210 $this->urls = $this->predef_urls;
211 $this->titles = $this->predef_titles;
212 $this->html_hashes = array();
213 $this->in_anchor = false;
214 }
215
216 # Called after the transformation process to clear any variable
217 # which may be taking up memory unnecessarly.
218 function teardown() {
219 $this->urls = array();
220 $this->titles = array();
221 $this->html_hashes = array();
222 }
223
224 # Main function. Performs some preprocessing on the input text
225 # and pass it through the document gamut.
226 function transform($text) {
227
228 $this->setup();
229
230 # Remove UTF-8 BOM and marker character in input, if present.
231 $text = preg_replace( '{^\xEF\xBB\xBF|\x1A}', '', $text );
232
233 # Standardize line endings:
234 # DOS to Unix and Mac to Unix
235 $text = preg_replace( '{\r\n?}', "\n", $text );
236
237 # Make sure $text ends with a couple of newlines:
238 $text .= "\n\n";
239
240 # Convert all tabs to spaces.
241 $text = $this->detab( $text );
242
243 # Turn block-level HTML blocks into hash entries
244 $text = $this->hashHTMLBlocks( $text );
245
246 # Strip any lines consisting only of spaces and tabs.
247 # This makes subsequent regexen easier to write, because we can
248 # match consecutive blank lines with /\n+/ instead of something
249 # contorted like /[ ]*\n+/ .
250 $text = preg_replace( '/^[ ]+$/m', '', $text );
251
252 # Run document gamut methods.
253 foreach ( $this->document_gamut as $method => $priority ) {
254 $text = $this->$method($text);
255 }
256
257 $this->teardown();
258
259 return $text . "\n";
260 }
261
262 public $document_gamut = array(
263 # Strip link definitions, store in hashes.
264 "stripLinkDefinitions" => 20,
265 "runBasicBlockGamut" => 30,
266 );
267
268 # Strips link definitions from text, stores the URLs and titles in
269 # hash references.
270 function stripLinkDefinitions( $text ) {
271
272 $less_than_tab = $this->tab_width - 1;
273
274 # Link defs are in the form: ^[id]: url "optional title"
275 $text = preg_replace_callback( '{
276 ^[ ]{0,' . $less_than_tab . '}\[(.+)\][ ]?: # id = $1
277 [ ]*
278 \n? # maybe *one* newline
279 [ ]*
280 (?:
281 <(.+?)> # url = $2
282 |
283 (\S+?) # url = $3
284 )
285 [ ]*
286 \n? # maybe one newline
287 [ ]*
288 (?:
289 (?<=\s) # lookbehind for whitespace
290 ["(]
291 (.*?) # title = $4
292 [")]
293 [ ]*
294 )? # title is optional
295 (?:\n+|\Z)
296 }xm',
297 array( &$this, '_stripLinkDefinitions_callback' ),
298 $text
299 );
300 return $text;
301 }
302 function _stripLinkDefinitions_callback( $matches ) {
303 $link_id = strtolower( $matches[1] );
304 $url = $matches[2] == '' ? $matches[3] : $matches[2];
305 $this->urls[$link_id] = $url;
306 $this->titles[$link_id] =& $matches[4];
307 return ''; # String that will replace the block
308 }
309
310
311 function hashHTMLBlocks( $text ) {
312 if ( $this->no_markup ) {
313 return $text;
314 }
315
316 $less_than_tab = $this->tab_width - 1;
317
318 # Hashify HTML blocks:
319 # We only want to do this for block-level HTML tags, such as headers,
320 # lists, and tables. That's because we still want to wrap <p>s around
321 # "paragraphs" that are wrapped in non-block-level tags, such as anchors,
322 # phrase emphasis, and spans. The list of tags we're looking for is
323 # hard-coded:
324 #
325 # * List "a" is made of tags which can be both inline or block-level.
326 # These will be treated block-level when the start tag is alone on
327 # its line, otherwise they're not matched here and will be taken as
328 # inline later.
329 # * List "b" is made of tags which are always block-level;
330 #
331 $block_tags_a_re = 'ins|del';
332 $block_tags_b_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|'.
333 'script|noscript|form|fieldset|iframe|math|svg|'.
334 'article|section|nav|aside|hgroup|header|footer|'.
335 'figure';
336
337 # Regular expression for the content of a block tag.
338 $nested_tags_level = 4;
339 $attr = '
340 (?> # optional tag attributes
341 \s # starts with whitespace
342 (?>
343 [^>"/]+ # text outside quotes
344 |
345 /+(?!>) # slash not followed by ">"
346 |
347 "[^"]*" # text inside double quotes (tolerate ">")
348 |
349 \'[^\']*\' # text inside single quotes (tolerate ">")
350 )*
351 )?
352 ';
353 $content =
354 str_repeat( '
355 (?>
356 [^<]+ # content without tag
357 |
358 <\2 # nested opening tag
359 ' . $attr . ' # attributes
360 (?>
361 />
362 |
363 >', $nested_tags_level ) . # end of opening tag
364 '.*?'. # last level nested tag content
365 str_repeat( '
366 </\2\s*> # closing nested tag
367 )
368 |
369 <(?!/\2\s*> # other tags with a different name
370 )
371 )*',
372 $nested_tags_level );
373 $content2 = str_replace( '\2', '\3', $content );
374
375 # First, look for nested blocks, e.g.:
376 # <div>
377 # <div>
378 # tags for inner block must be indented.
379 # </div>
380 # </div>
381 #
382 # The outermost tags must start at the left margin for this to match, and
383 # the inner nested divs must be indented.
384 # We need to do this before the next, more liberal match, because the next
385 # match will start at the first `<div>` and stop at the first `</div>`.
386 $text = preg_replace_callback( '{(?>
387 (?>
388 (?<=\n\n) # Starting after a blank line
389 | # or
390 \A\n? # the beginning of the doc
391 )
392 ( # save in $1
393
394 # Match from `\n<tag>` to `</tag>\n`, handling nested tags
395 # in between.
396
397 [ ]{0,' . $less_than_tab . '}
398 <(' . $block_tags_b_re . ')# start tag = $2
399 ' . $attr . '> # attributes followed by > and \n
400 ' . $content . ' # content, support nesting
401 </\2> # the matching end tag
402 [ ]* # trailing spaces/tabs
403 (?=\n+|\Z) # followed by a newline or end of document
404
405 | # Special version for tags of group a.
406
407 [ ]{0,' . $less_than_tab . '}
408 <(' . $block_tags_a_re . ')# start tag = $3
409 ' . $attr . '>[ ]*\n # attributes followed by >
410 ' . $content2 . ' # content, support nesting
411 </\3> # the matching end tag
412 [ ]* # trailing spaces/tabs
413 (?=\n+|\Z) # followed by a newline or end of document
414
415 | # Special case just for <hr />. It was easier to make a special
416 # case than to make the other regex more complicated.
417
418 [ ]{0,' . $less_than_tab . '}
419 <(hr) # start tag = $2
420 ' . $attr . ' # attributes
421 /?> # the matching end tag
422 [ ]*
423 (?=\n{2,}|\Z) # followed by a blank line or end of document
424
425 | # Special case for standalone HTML comments:
426
427 [ ]{0,' . $less_than_tab . '}
428 (?s:
429 <!-- .*? -->
430 )
431 [ ]*
432 (?=\n{2,}|\Z) # followed by a blank line or end of document
433
434 | # PHP and ASP-style processor instructions
435
436 [ ]{0,' . $less_than_tab . '}
437 (?s:
438 <([?%]) # $2
439 .*?
440 \2>
441 )
442 [ ]*
443 (?=\n{2,}|\Z) # followed by a blank line or end of document
444
445 )
446 )}Sxmi',
447 array( &$this, '_hashHTMLBlocks_callback' ),
448 $text
449 );
450
451 return $text;
452 }
453
454 function _hashHTMLBlocks_callback( $matches ) {
455 $text = $matches[1];
456 $key = $this->hashBlock( $text );
457 return "\n\n" . $key . "\n\n";
458 }
459
460 # Called whenever a tag must be hashed when a function insert an atomic
461 # element in the text stream. Passing $text to through this function gives
462 # a unique text-token which will be reverted back when calling unhash.
463 #
464 # The $boundary argument specify what character should be used to surround
465 # the token. By convension, "B" is used for block elements that needs not
466 # to be wrapped into paragraph tags at the end, ":" is used for elements
467 # that are word separators and "X" is used in the general case.
468 function hashPart( $text, $boundary = 'X' ) {
469
470 # Swap back any tag hash found in $text so we do not have to `unhash`
471 # multiple times at the end.
472 $text = $this->unhash( $text );
473
474 # Then hash the block.
475 static $i = 0;
476 $key = $boundary . "\x1A" . ++$i . $boundary;
477 $this->html_hashes[$key] = $text;
478 return $key; # String that will replace the tag.
479 }
480
481 # Shortcut function for hashPart with block-level boundaries.
482 function hashBlock( $text ) {
483 return $this->hashPart( $text, 'B' );
484 }
485
486 # These are all the transformations that form block-level
487 # tags like paragraphs, headers, and list items.
488 public $block_gamut = array(
489 "doHeaders" => 10,
490 "doHorizontalRules" => 20,
491
492 "doLists" => 40,
493 "doCodeBlocks" => 50,
494 "doBlockQuotes" => 60,
495 );
496
497 # Run block gamut tranformations.
498 function runBlockGamut( $text ) {
499 # We need to escape raw HTML in Markdown source before doing anything
500 # else. This need to be done for each block, and not only at the
501 # beginning in the Markdown function since hashed blocks can be part of
502 # list items and could have been indented. Indented blocks would have
503 # been seen as a code block in a previous pass of hashHTMLBlocks.
504 $text = $this->hashHTMLBlocks( $text );
505
506 return $this->runBasicBlockGamut( $text );
507 }
508
509 # Run block gamut tranformations, without hashing HTML blocks. This is
510 # useful when HTML blocks are known to be already hashed, like in the first
511 # whole-document pass.
512 function runBasicBlockGamut( $text ) {
513 foreach ( $this->block_gamut as $method => $priority ) {
514 $text = $this->$method($text);
515 }
516
517 # Finally form paragraph and restore hashed blocks.
518 $text = $this->formParagraphs( $text );
519
520 return $text;
521 }
522
523 function doHorizontalRules( $text ) {
524 # Do Horizontal Rules:
525 return preg_replace(
526 '{
527 ^[ ]{0,3} # Leading space
528 ([-*_]) # $1: First marker
529 (?> # Repeated marker group
530 [ ]{0,2} # Zero, one, or two spaces.
531 \1 # Marker character
532 ){2,} # Group repeated at least twice
533 [ ]* # Tailing spaces
534 $ # End of line.
535 }mx',
536 "\n" . $this->hashBlock( "<hr$this->empty_element_suffix" ) . "\n",
537 $text
538 );
539 }
540
541 # These are all the transformations that occur *within* block-level
542 # tags like paragraphs, headers, and list items.
543 public $span_gamut = array(
544 # Process character escapes, code spans, and inline HTML
545 # in one shot.
546 "parseSpan" => -30,
547
548 # Process anchor and image tags. Images must come first,
549 # because ![foo][f] looks like an anchor.
550 "doImages" => 10,
551 "doAnchors" => 20,
552
553 # Make links out of things like `<http://example.com/>`
554 # Must come after doAnchors, because you can use < and >
555 # delimiters in inline links like [this](<url>).
556 "doAutoLinks" => 30,
557 "encodeAmpsAndAngles" => 40,
558
559 "doItalicsAndBold" => 50,
560 "doHardBreaks" => 60,
561 );
562
563 # Run span gamut tranformations.
564 function runSpanGamut( $text ) {
565
566 foreach ( $this->span_gamut as $method => $priority ) {
567 $text = $this->$method($text);
568 }
569
570 return $text;
571 }
572
573 # Do hard breaks:
574 function doHardBreaks( $text ) {
575 return preg_replace_callback(
576 '/ {2,}\n/',
577 array( &$this, '_doHardBreaks_callback' ),
578 $text
579 );
580 }
581
582 function _doHardBreaks_callback( $matches ) {
583 return $this->hashPart( "<br$this->empty_element_suffix\n" );
584 }
585
586 # Turn Markdown link shortcuts into XHTML <a> tags.
587 function doAnchors( $text ) {
588
589 if ( $this->in_anchor ) {
590 return $text;
591 }
592 $this->in_anchor = true;
593
594 # First, handle reference-style links: [link text] [id]
595 $text = preg_replace_callback( '{
596 ( # wrap whole match in $1
597 \[
598 ('.$this->nested_brackets_re.') # link text = $2
599 \]
600
601 [ ]? # one optional space
602 (?:\n[ ]*)? # one optional newline followed by spaces
603
604 \[
605 (.*?) # id = $3
606 \]
607 )
608 }xs',
609 array( &$this, '_doAnchors_reference_callback' ),
610 $text
611 );
612
613 # Next, inline-style links: [link text](url "optional title")
614 $text = preg_replace_callback( '{
615 ( # wrap whole match in $1
616 \[
617 (' . $this->nested_brackets_re . ') # link text = $2
618 \]
619 \( # literal paren
620 [ \n]*
621 (?:
622 <(.+?)> # href = $3
623 |
624 (' . $this->nested_url_parenthesis_re . ') # href = $4
625 )
626 [ \n]*
627 ( # $5
628 ([\'"]) # quote char = $6
629 (.*?) # Title = $7
630 \6 # matching quote
631 [ \n]* # ignore any spaces/tabs between closing quote and )
632 )? # title is optional
633 \)
634 )
635 }xs',
636 array( &$this, '_doAnchors_inline_callback' ),
637 $text
638 );
639
640 # Last, handle reference-style shortcuts: [link text]
641 # These must come last in case you've also got [link text][1]
642 # or [link text](/foo)
643 $text = preg_replace_callback( '{
644 ( # wrap whole match in $1
645 \[
646 ([^\[\]]+) # link text = $2; can\'t contain [ or ]
647 \]
648 )
649 }xs',
650 array( &$this, '_doAnchors_reference_callback' ),
651 $text
652 );
653
654 $this->in_anchor = false;
655 return $text;
656 }
657
658 function _doAnchors_reference_callback( $matches ) {
659
660 $whole_match = $matches[1];
661 $link_text = $matches[2];
662 $link_id =& $matches[3];
663
664 if ( "" == $link_id ) {
665 # for shortcut links like [this][] or [this].
666 $link_id = $link_text;
667 }
668
669 # lower-case and turn embedded newlines into spaces
670 $link_id = strtolower( $link_id );
671 $link_id = preg_replace( '{[ ]?\n}', ' ', $link_id );
672
673 if ( isset( $this->urls[$link_id] ) ) {
674 $url = $this->urls[$link_id];
675 $url = $this->encodeAttribute( $url );
676
677 $result = "<a href=\"" . $url . "\"";
678 if ( isset( $this->titles[$link_id] ) ) {
679 $title = $this->titles[$link_id];
680 $title = $this->encodeAttribute( $title );
681 $result .= " title=\"" . $title . "\"";
682 }
683
684 $link_text = $this->runSpanGamut( $link_text );
685 $result .= ">" . $link_text . "</a>";
686 $result = $this->hashPart( $result );
687 }
688 else {
689 $result = $whole_match;
690 }
691 return $result;
692 }
693
694 function _doAnchors_inline_callback( $matches ) {
695 $whole_match = $matches[1];
696 $link_text = $this->runSpanGamut( $matches[2] );
697 $url = $matches[3] == '' ? $matches[4] : $matches[3];
698 $title =& $matches[7];
699
700 $url = $this->encodeAttribute( $url );
701
702 $result = "<a href=\"" . $url . "\"";
703 if ( isset( $title ) ) {
704 $title = $this->encodeAttribute( $title );
705 $result .= " title=\"$title\"";
706 }
707
708 $link_text = $this->runSpanGamut( $link_text );
709 $result .= ">" . $link_text . "</a>";
710
711 return $this->hashPart( $result );
712 }
713
714 # Turn Markdown image shortcuts into <img> tags.
715 function doImages( $text ) {
716
717 # First, handle reference-style labeled images: ![alt text][id]
718 $text = preg_replace_callback( '{
719 ( # wrap whole match in $1
720 !\[
721 (' . $this->nested_brackets_re . ') # alt text = $2
722 \]
723
724 [ ]? # one optional space
725 (?:\n[ ]*)? # one optional newline followed by spaces
726
727 \[
728 (.*?) # id = $3
729 \]
730
731 )
732 }xs',
733 array( &$this, '_doImages_reference_callback' ),
734 $text
735 );
736
737 # Next, handle inline images: ![alt text](url "optional title")
738 # Don't forget: encode * and _
739 $text = preg_replace_callback( '{
740 ( # wrap whole match in $1
741 !\[
742 (' . $this->nested_brackets_re . ') # alt text = $2
743 \]
744 \s? # One optional whitespace character
745 \( # literal paren
746 [ \n]*
747 (?:
748 <(\S*)> # src url = $3
749 |
750 (' . $this->nested_url_parenthesis_re . ') # src url = $4
751 )
752 [ \n]*
753 ( # $5
754 ([\'"]) # quote char = $6
755 (.*?) # title = $7
756 \6 # matching quote
757 [ \n]*
758 )? # title is optional
759 \)
760 )
761 }xs',
762 array( &$this, '_doImages_inline_callback' ),
763 $text
764 );
765
766 return $text;
767 }
768
769 function _doImages_reference_callback( $matches ) {
770
771 $whole_match = $matches[1];
772 $alt_text = $matches[2];
773 $link_id = strtolower($matches[3]);
774
775 if ( "" == $link_id ) {
776 $link_id = strtolower( $alt_text ); # for shortcut links like ![this][].
777 }
778
779 $alt_text = $this->encodeAttribute( $alt_text );
780 if ( isset( $this->urls[$link_id] ) ) {
781 $url = $this->encodeAttribute( $this->urls[$link_id] );
782 $result = "<img src=\"" . $url . "\" alt=\"" . $alt_text . "\"";
783 if ( isset( $this->titles[$link_id] ) ) {
784 $title = $this->titles[$link_id];
785 $title = $this->encodeAttribute( $title );
786 $result .= " title=\"" . $title . "\"";
787 }
788 $result .= $this->empty_element_suffix;
789 $result = $this->hashPart( $result );
790 } else {
791 # If there's no such link ID, leave intact:
792 $result = $whole_match;
793 }
794
795 return $result;
796 }
797
798 function _doImages_inline_callback( $matches ) {
799 $whole_match = $matches[1];
800 $alt_text = $matches[2];
801 $url = $matches[3] == '' ? $matches[4] : $matches[3];
802 $title =& $matches[7];
803
804 $alt_text = $this->encodeAttribute( $alt_text );
805 $url = $this->encodeAttribute( $url );
806 $result = "<img src=\"" . $url . "\" alt=\"" . $alt_text . "\"";
807 if ( isset( $title ) ) {
808 $title = $this->encodeAttribute( $title );
809 $result .= " title=\"" . $title . "\""; # $title already quoted
810 }
811 $result .= $this->empty_element_suffix;
812
813 return $this->hashPart( $result );
814 }
815
816 function doHeaders( $text ) {
817
818 # Setext-style headers:
819 # Header 1
820 # ========
821 #
822 # Header 2
823 # --------
824 $text = preg_replace_callback(
825 '{ ^(.+?)[ ]*\n(=+|-+)[ ]*\n+ }mx',
826 array( &$this, '_doHeaders_callback_setext' ),
827 $text
828 );
829
830 # atx-style headers:
831 # # Header 1
832 # ## Header 2
833 # ## Header 2 with closing hashes ##
834 # ...
835 # ###### Header 6
836 #
837 $text = preg_replace_callback( '{
838 ^(\#{1,6}) # $1 = string of #\'s
839 [ ]*
840 (.+?) # $2 = Header text
841 [ ]*
842 \#* # optional closing #\'s (not counted)
843 \n+
844 }xm',
845 array( &$this, '_doHeaders_callback_atx' ),
846 $text
847 );
848
849 return $text;
850 }
851
852 function _doHeaders_callback_setext( $matches ) {
853 # Terrible hack to check we haven't found an empty list item.
854 if ( $matches[2] == '-' && preg_match( '{^-(?: |$)}', $matches[1] ) )
855 return $matches[0];
856
857 $level = $matches[2][0] == '=' ? 1 : 2;
858 $block = "<h" . $level . ">" . $this->runSpanGamut( $matches[1] ) . "</h" . $level . ">";
859 return "\n" . $this->hashBlock( $block ) . "\n\n";
860 }
861
862 function _doHeaders_callback_atx( $matches ) {
863 $level = strlen( $matches[1] );
864 $block = "<h" . $level . ">" . $this->runSpanGamut( $matches[2] ) . "</h" . $level . ">";
865 return "\n" . $this->hashBlock( $block ) . "\n\n";
866 }
867
868 # Form HTML ordered (numbered) and unordered (bulleted) lists.
869 function doLists( $text ) {
870
871 $less_than_tab = $this->tab_width - 1;
872
873 # Re-usable patterns to match list item bullets and number markers:
874 $marker_ul_re = '[*+-]';
875 $marker_ol_re = '\d+[\.]';
876 $marker_any_re = "(?:$marker_ul_re|$marker_ol_re)";
877
878 $markers_relist = array(
879 $marker_ul_re => $marker_ol_re,
880 $marker_ol_re => $marker_ul_re,
881 );
882
883 foreach ( $markers_relist as $marker_re => $other_marker_re ) {
884 # Re-usable pattern to match any entirel ul or ol list:
885 $whole_list_re = '
886 ( # $1 = whole list
887 ( # $2
888 ([ ]{0,' . $less_than_tab . '}) # $3 = number of spaces
889 (' . $marker_re . ') # $4 = first list item marker
890 [ ]+
891 )
892 (?s:.+?)
893 ( # $5
894 \z
895 |
896 \n{2,}
897 (?=\S)
898 (?! # Negative lookahead for another list item marker
899 [ ]*
900 ' . $marker_re . '[ ]+
901 )
902 |
903 (?= # Lookahead for another kind of list
904 \n
905 \3 # Must have the same indentation
906 ' . $other_marker_re . '[ ]+
907 )
908 )
909 )
910 '; // mx
911
912 # We use a different prefix before nested lists than top-level lists.
913 # See extended comment in _ProcessListItems().
914 if ( $this->list_level ) {
915 $text = preg_replace_callback( '{
916 ^
917 ' . $whole_list_re . '
918 }mx',
919 array( &$this, '_doLists_callback' ),
920 $text
921 );
922 } else {
923 $text = preg_replace_callback( '{
924 (?:(?<=\n)\n|\A\n?) # Must eat the newline
925 '.$whole_list_re.'
926 }mx',
927 array( &$this, '_doLists_callback' ),
928 $text
929 );
930 }
931 }
932
933 return $text;
934 }
935
936 function _doLists_callback( $matches ) {
937 # Re-usable patterns to match list item bullets and number markers:
938 $marker_ul_re = '[*+-]';
939 $marker_ol_re = '\d+[\.]';
940 $marker_any_re = "(?:$marker_ul_re|$marker_ol_re)";
941
942 $list = $matches[1];
943 $list_type = preg_match( "/$marker_ul_re/", $matches[4]) ? "ul" : "ol";
944
945 $marker_any_re = ( $list_type == "ul" ? $marker_ul_re : $marker_ol_re );
946
947 $list .= "\n";
948 $result = $this->processListItems( $list, $marker_any_re );
949
950 $result = $this->hashBlock("<" . $list_type . ">\n" . $result . "</" . $list_type . ">");
951 return "\n" . $result . "\n\n";
952 }
953
954 public $list_level = 0;
955
956 # Process the contents of a single ordered or unordered list, splitting it
957 # into individual list items.
958 function processListItems( $list_str, $marker_any_re ) {
959
960 # The $this->list_level global keeps track of when we're inside a list.
961 # Each time we enter a list, we increment it; when we leave a list,
962 # we decrement. If it's zero, we're not in a list anymore.
963 #
964 # We do this because when we're not inside a list, we want to treat
965 # something like this:
966 #
967 # I recommend upgrading to version
968 # 8. Oops, now this line is treated
969 # as a sub-list.
970 #
971 # As a single paragraph, despite the fact that the second line starts
972 # with a digit-period-space sequence.
973 #
974 # Whereas when we're inside a list (or sub-list), that line will be
975 # treated as the start of a sub-list. What a kludge, huh? This is
976 # an aspect of Markdown's syntax that's hard to parse perfectly
977 # without resorting to mind-reading. Perhaps the solution is to
978 # change the syntax rules such that sub-lists must start with a
979 # starting cardinal number; e.g. "1." or "a.".
980
981 $this->list_level++;
982
983 # trim trailing blank lines:
984 $list_str = preg_replace( "/\n{2,}\\z/", "\n", $list_str );
985
986 $list_str = preg_replace_callback('{
987 (\n)? # leading line = $1
988 (^[ ]*) # leading whitespace = $2
989 (' . $marker_any_re . ' # list marker and space = $3
990 (?:[ ]+|(?=\n)) # space only required if item is not empty
991 )
992 ((?s:.*?)) # list item text = $4
993 (?:(\n+(?=\n))|\n) # tailing blank line = $5
994 (?= \n* (\z | \2 (' . $marker_any_re . ') (?:[ ]+|(?=\n))))
995 }xm',
996 array( &$this, '_processListItems_callback'), $list_str );
997
998 $this->list_level--;
999 return $list_str;
1000 }
1001
1002 function _processListItems_callback( $matches ) {
1003 $item = $matches[4];
1004 $leading_line =& $matches[1];
1005 $leading_space =& $matches[2];
1006 $marker_space = $matches[3];
1007 $tailing_blank_line =& $matches[5];
1008
1009 if ( $leading_line || $tailing_blank_line || preg_match( '/\n{2,}/', $item ) ) {
1010 # Replace marker with the appropriate whitespace indentation
1011 $item = $leading_space . str_repeat( ' ', strlen( $marker_space ) ) . $item;
1012 $item = $this->runBlockGamut( $this->outdent( $item ) . "\n");
1013 } else {
1014 # Recursion for sub-lists:
1015 $item = $this->doLists( $this->outdent( $item ) );
1016 $item = preg_replace( '/\n+$/', '', $item );
1017 $item = $this->runSpanGamut( $item );
1018 }
1019
1020 return "<li>" . $item . "</li>\n";
1021 }
1022
1023 # Process Markdown `<pre><code>` blocks.
1024 function doCodeBlocks( $text ) {
1025
1026 $text = preg_replace_callback( '{
1027 (?:\n\n|\A\n?)
1028 ( # $1 = the code block -- one or more lines, starting with a space/tab
1029 (?>
1030 [ ]{'.$this->tab_width.'} # Lines must start with a tab or a tab-width of spaces
1031 .*\n+
1032 )+
1033 )
1034 ((?=^[ ]{0,'.$this->tab_width.'}\S)|\Z) # Lookahead for non-space at line-start, or end of doc
1035 }xm',
1036 array( &$this, '_doCodeBlocks_callback'),
1037 $text
1038 );
1039
1040 return $text;
1041 }
1042
1043 function _doCodeBlocks_callback( $matches ) {
1044 $codeblock = $matches[1];
1045
1046 $codeblock = $this->outdent( $codeblock );
1047 $codeblock = htmlspecialchars( $codeblock, ENT_NOQUOTES );
1048
1049 # trim leading newlines and trailing newlines
1050 $codeblock = preg_replace( '/\A\n+|\n+\z/', '', $codeblock );
1051
1052 $codeblock = "<pre><code>" . $codeblock . "\n</code></pre>";
1053 return "\n\n" . $this->hashBlock( $codeblock ) . "\n\n";
1054 }
1055
1056 function makeCodeSpan( $code ) {
1057 #
1058 # Create a code span markup for $code. Called from handleSpanToken.
1059 #
1060 $code = htmlspecialchars( trim( $code ), ENT_NOQUOTES );
1061 return $this->hashPart( "<code>" . $code . "</code>" );
1062 }
1063
1064 public $em_relist = array(
1065 '' => '(?:(?<!\*)\*(?!\*)|(?<!_)_(?!_))(?=\S|$)(?![\.,:;]\s)',
1066 '*' => '(?<=\S|^)(?<!\*)\*(?!\*)',
1067 '_' => '(?<=\S|^)(?<!_)_(?!_)',
1068 );
1069 public $strong_relist = array(
1070 '' => '(?:(?<!\*)\*\*(?!\*)|(?<!_)__(?!_))(?=\S|$)(?![\.,:;]\s)',
1071 '**' => '(?<=\S|^)(?<!\*)\*\*(?!\*)',
1072 '__' => '(?<=\S|^)(?<!_)__(?!_)',
1073 );
1074 public $em_strong_relist = array(
1075 '' => '(?:(?<!\*)\*\*\*(?!\*)|(?<!_)___(?!_))(?=\S|$)(?![\.,:;]\s)',
1076 '***' => '(?<=\S|^)(?<!\*)\*\*\*(?!\*)',
1077 '___' => '(?<=\S|^)(?<!_)___(?!_)',
1078 );
1079 public $em_strong_prepared_relist;
1080
1081 # Prepare regular expressions for searching emphasis tokens in any
1082 # context.
1083 function prepareItalicsAndBold() {
1084
1085 foreach ( $this->em_relist as $em => $em_re ) {
1086 foreach ( $this->strong_relist as $strong => $strong_re ) {
1087 # Construct list of allowed token expressions.
1088 $token_relist = array();
1089 if ( isset( $this->em_strong_relist["$em$strong"] ) ) {
1090 $token_relist[] = $this->em_strong_relist["$em$strong"];
1091 }
1092 $token_relist[] = $em_re;
1093 $token_relist[] = $strong_re;
1094
1095 # Construct master expression from list.
1096 $token_re = '{(' . implode( '|', $token_relist ) . ')}';
1097 $this->em_strong_prepared_relist["$em$strong"] = $token_re;
1098 }
1099 }
1100 }
1101
1102 function doItalicsAndBold( $text ) {
1103 $token_stack = array( '' );
1104 $text_stack = array( '' );
1105 $em = '';
1106 $strong = '';
1107 $tree_char_em = false;
1108
1109 while ( 1 ) {
1110
1111 # Get prepared regular expression for seraching emphasis tokens
1112 # in current context.
1113 $token_re = $this->em_strong_prepared_relist["$em$strong"];
1114
1115 # Each loop iteration search for the next emphasis token.
1116 # Each token is then passed to handleSpanToken.
1117 $parts = preg_split( $token_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE );
1118 $text_stack[0] .= $parts[0];
1119 $token =& $parts[1];
1120 $text =& $parts[2];
1121
1122 if ( empty( $token ) ) {
1123 # Reached end of text span: empty stack without emitting.
1124 # any more emphasis.
1125 while ( $token_stack[0] ) {
1126 $text_stack[1] .= array_shift( $token_stack );
1127 $text_stack[0] .= array_shift( $text_stack );
1128 }
1129 break;
1130 }
1131
1132 $token_len = strlen( $token );
1133 if ( $tree_char_em ) {
1134 # Reached closing marker while inside a three-char emphasis.
1135 if ( $token_len == 3 ) {
1136 # Three-char closing marker, close em and strong.
1137 array_shift( $token_stack );
1138 $span = array_shift( $text_stack );
1139 $span = $this->runSpanGamut( $span );
1140 $span = "<strong><em>" . $span . "</em></strong>";
1141 $text_stack[0] .= $this->hashPart( $span );
1142 $em = '';
1143 $strong = '';
1144 } else {
1145 # Other closing marker: close one em or strong and
1146 # change current token state to match the other
1147 $token_stack[0] = str_repeat( $token[0], 3-$token_len );
1148 $tag = $token_len == 2 ? "strong" : "em";
1149 $span = $text_stack[0];
1150 $span = $this->runSpanGamut( $span );
1151 $span = "<" . $tag . ">" . $span . "</" . $tag . ">";
1152 $text_stack[0] = $this->hashPart( $span );
1153 $$tag = ''; # $$tag stands for $em or $strong
1154 }
1155 $tree_char_em = false;
1156 } else if ( $token_len == 3 ) {
1157 if ( $em ) {
1158 # Reached closing marker for both em and strong.
1159 # Closing strong marker:
1160 for ( $i = 0; $i < 2; ++$i ) {
1161 $shifted_token = array_shift( $token_stack );
1162 $tag = strlen( $shifted_token ) == 2 ? "strong" : "em";
1163 $span = array_shift( $text_stack );
1164 $span = $this->runSpanGamut( $span );
1165 $span = "<" . $tag . ">" . $span . "</" . $tag . ">";
1166 $text_stack[0] .= $this->hashPart( $span );
1167 $$tag = ''; # $$tag stands for $em or $strong
1168 }
1169 } else {
1170 # Reached opening three-char emphasis marker. Push on token
1171 # stack; will be handled by the special condition above.
1172 $em = $token[0];
1173 $strong = "$em$em";
1174 array_unshift( $token_stack, $token );
1175 array_unshift( $text_stack, '' );
1176 $tree_char_em = true;
1177 }
1178 } else if ( $token_len == 2 ) {
1179 if ( $strong ) {
1180 # Unwind any dangling emphasis marker:
1181 if ( strlen( $token_stack[0] ) == 1 ) {
1182 $text_stack[1] .= array_shift( $token_stack );
1183 $text_stack[0] .= array_shift( $text_stack );
1184 }
1185 # Closing strong marker:
1186 array_shift( $token_stack );
1187 $span = array_shift( $text_stack );
1188 $span = $this->runSpanGamut( $span );
1189 $span = "<strong>" . $span . "</strong>";
1190 $text_stack[0] .= $this->hashPart( $span );
1191 $strong = '';
1192 } else {
1193 array_unshift( $token_stack, $token );
1194 array_unshift( $text_stack, '' );
1195 $strong = $token;
1196 }
1197 } else {
1198 # Here $token_len == 1
1199 if ( $em ) {
1200 if ( strlen( $token_stack[0] ) == 1 ) {
1201 # Closing emphasis marker:
1202 array_shift( $token_stack );
1203 $span = array_shift( $text_stack );
1204 $span = $this->runSpanGamut( $span );
1205 $span = "<em>" . $span . "</em>";
1206 $text_stack[0] .= $this->hashPart( $span );
1207 $em = '';
1208 } else {
1209 $text_stack[0] .= $token;
1210 }
1211 } else {
1212 array_unshift( $token_stack, $token );
1213 array_unshift( $text_stack, '' );
1214 $em = $token;
1215 }
1216 }
1217 }
1218 return $text_stack[0];
1219 }
1220
1221 function doBlockQuotes( $text ) {
1222 $text = preg_replace_callback( '/
1223 ( # Wrap whole match in $1
1224 (?>
1225 ^[ ]*>[ ]? # ">" at the start of a line
1226 .+\n # rest of the first line
1227 (.+\n)* # subsequent consecutive lines
1228 \n* # blanks
1229 )+
1230 )
1231 /xm',
1232 array( &$this, '_doBlockQuotes_callback'),
1233 $text
1234 );
1235
1236 return $text;
1237 }
1238
1239 function _doBlockQuotes_callback( $matches ) {
1240 $bq = $matches[1];
1241 # trim one level of quoting - trim whitespace-only lines
1242 $bq = preg_replace( '/^[ ]*>[ ]?|^[ ]+$/m', '', $bq );
1243 $bq = $this->runBlockGamut($bq ); # recurse
1244
1245 $bq = preg_replace( '/^/m', " ", $bq );
1246 # These leading spaces cause problem with <pre> content,
1247 # so we need to fix that:
1248 $bq = preg_replace_callback( '{(\s*<pre>.+?</pre>)}sx',
1249 array( &$this, '_doBlockQuotes_callback2' ),
1250 $bq
1251 );
1252
1253 return "\n" . $this->hashBlock( "<blockquote>\n" . $bq . "\n</blockquote>" ) . "\n\n";
1254 }
1255
1256 function _doBlockQuotes_callback2( $matches ) {
1257 $pre = $matches[1];
1258 $pre = preg_replace( '/^ /m', '', $pre );
1259 return $pre;
1260 }
1261
1262 function formParagraphs( $text ) {
1263 #
1264 # Params:
1265 # $text - string to process with html <p> tags
1266 #
1267 # Strip leading and trailing lines:
1268 $text = preg_replace( '/\A\n+|\n+\z/', '', $text );
1269
1270 $grafs = preg_split( '/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY );
1271
1272 #
1273 # Wrap <p> tags and unhashify HTML blocks
1274 #
1275 foreach ( $grafs as $key => $value ) {
1276 if ( !preg_match( '/^B\x1A[0-9]+B$/', $value ) ) {
1277 # Is a paragraph.
1278 $value = $this->runSpanGamut( $value );
1279 $value = preg_replace( '/^([ ]*)/', "<p>", $value );
1280 $value .= "</p>";
1281 $grafs[$key] = $this->unhash( $value );
1282 } else {
1283 # Is a block.
1284 # Modify elements of @grafs in-place...
1285 $graf = $value;
1286 $block = $this->html_hashes[$graf];
1287 $graf = $block;
1288 // if (preg_match('{
1289 // \A
1290 // ( # $1 = <div> tag
1291 // <div \s+
1292 // [^>]*
1293 // \b
1294 // markdown\s*=\s* ([\'"]) # $2 = attr quote char
1295 // 1
1296 // \2
1297 // [^>]*
1298 // >
1299 // )
1300 // ( # $3 = contents
1301 // .*
1302 // )
1303 // (</div>) # $4 = closing tag
1304 // \z
1305 // }xs', $block, $matches))
1306 // {
1307 // list(, $div_open, , $div_content, $div_close) = $matches;
1308 //
1309 // # We can't call Markdown(), because that resets the hash;
1310 // # that initialization code should be pulled into its own sub, though.
1311 // $div_content = $this->hashHTMLBlocks($div_content);
1312 //
1313 // # Run document gamut methods on the content.
1314 // foreach ($this->document_gamut as $method => $priority) {
1315 // $div_content = $this->$method($div_content);
1316 // }
1317 //
1318 // $div_open = preg_replace(
1319 // '{\smarkdown\s*=\s*([\'"]).+?\1}', '', $div_open);
1320 //
1321 // $graf = $div_open . "\n" . $div_content . "\n" . $div_close;
1322 // }
1323 $grafs[$key] = $graf;
1324 }
1325 }
1326
1327 return implode( "\n\n", $grafs );
1328 }
1329
1330 # Encode text for a double-quoted HTML attribute. This function
1331 # is *not* suitable for attributes enclosed in single quotes.
1332 function encodeAttribute( $text ) {
1333
1334 $text = $this->encodeAmpsAndAngles( $text );
1335 $text = str_replace( '"', '&quot;', $text );
1336 return $text;
1337 }
1338
1339 # Smart processing for ampersands and angle brackets that need to
1340 # be encoded. Valid character entities are left alone unless the
1341 # no-entities mode is set.
1342 function encodeAmpsAndAngles( $text ) {
1343
1344 if ( $this->no_entities ) {
1345 $text = str_replace( '&', '&amp;', $text );
1346 } else {
1347 # Ampersand-encoding based entirely on Nat Irons's Amputator
1348 # MT plugin: <http://bumppo.net/projects/amputator/>
1349 $text = preg_replace(
1350 '/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/',
1351 '&amp;',
1352 $text
1353 );
1354 }
1355 # Encode remaining <'s
1356 $text = str_replace( '<', '&lt;', $text );
1357
1358 return $text;
1359 }
1360
1361 function doAutoLinks( $text ) {
1362
1363 $text = preg_replace_callback(
1364 '{<((https?|ftp|dict):[^\'">\s]+)>}i',
1365 array( &$this, '_doAutoLinks_url_callback' ),
1366 $text
1367 );
1368
1369 # Email addresses: <address@domain.foo>
1370 $text = preg_replace_callback('{
1371 <
1372 (?:mailto:)?
1373 (
1374 (?:
1375 [-!#$%&\'*+/=?^_`.{|}~\w\x80-\xFF]+
1376 |
1377 ".*?"
1378 )
1379 \@
1380 (?:
1381 [-a-z0-9\x80-\xFF]+(\.[-a-z0-9\x80-\xFF]+)*\.[a-z]+
1382 |
1383 \[[\d.a-fA-F:]+\] # IPv4 & IPv6
1384 )
1385 )
1386 >
1387 }xi',
1388 array( &$this, '_doAutoLinks_email_callback' ),
1389 $text
1390 );
1391 $text = preg_replace_callback(
1392 '{<(tel:([^\'">\s]+))>}i',
1393 array( &$this, '_doAutoLinks_tel_callback' ),
1394 $text
1395 );
1396
1397 return $text;
1398 }
1399
1400 function _doAutoLinks_tel_callback( $matches ) {
1401 $url = $this->encodeAttribute( $matches[1] );
1402 $tel = $this->encodeAttribute( $matches[2] );
1403 $link = "<a href=\"" . $url . "\">" . $tel . "</a>";
1404 return $this->hashPart( $link );
1405 }
1406
1407 function _doAutoLinks_url_callback( $matches ) {
1408 $url = $this->encodeAttribute( $matches[1] );
1409 $link = "<a href=\"" . $url . "\">" . $url . "</a>";
1410 return $this->hashPart( $link );
1411 }
1412
1413 function _doAutoLinks_email_callback( $matches ) {
1414 $address = $matches[1];
1415 $link = $this->encodeEmailAddress( $address );
1416 return $this->hashPart( $link );
1417 }
1418
1419 function encodeEmailAddress( $addr ) {
1420 #
1421 # Input: an email address, e.g. "foo@example.com"
1422 #
1423 # Output: the email address as a mailto link, with each character
1424 # of the address encoded as either a decimal or hex entity, in
1425 # the hopes of foiling most address harvesting spam bots. E.g.:
1426 #
1427 # <p><a href="&#109;&#x61;&#105;&#x6c;&#116;&#x6f;&#58;&#x66;o&#111;
1428 # &#x40;&#101;&#x78;&#97;&#x6d;&#112;&#x6c;&#101;&#46;&#x63;&#111;
1429 # &#x6d;">&#x66;o&#111;&#x40;&#101;&#x78;&#97;&#x6d;&#112;&#x6c;
1430 # &#101;&#46;&#x63;&#111;&#x6d;</a></p>
1431 #
1432 # Based by a filter by Matthew Wickline, posted to BBEdit-Talk.
1433 # With some optimizations by Milian Wolff.
1434 #
1435 $addr = "mailto:" . $addr;
1436 $chars = preg_split( '/(?<!^)(?!$)/', $addr );
1437 $seed = (int) abs( crc32( $addr ) / strlen( $addr ) ); # Deterministic seed.
1438
1439 foreach ( $chars as $key => $char ) {
1440 $ord = ord( $char );
1441 # Ignore non-ascii chars.
1442 if ( $ord < 128 ) {
1443 $r = ( $seed * ( 1 + $key ) ) % 100; # Pseudo-random function.
1444 # roughly 10% raw, 45% hex, 45% dec
1445 # '@' *must* be encoded. I insist.
1446 if ( $r > 90 && $char != '@' ) /* do nothing */;
1447 else if ( $r < 45 ) $chars[$key] = '&#x' . dechex( $ord ) . ';';
1448 else $chars[$key] = '&#' . $ord . ';';
1449 }
1450 }
1451
1452 $addr = implode( '', $chars );
1453 $text = implode( '', array_slice( $chars, 7 ) ); # text without `mailto:`
1454 $addr = "<a href=\"" . $addr . "\">" . $text . "</a>";
1455
1456 return $addr;
1457 }
1458
1459 function parseSpan( $str ) {
1460 #
1461 # Take the string $str and parse it into tokens, hashing embedded HTML,
1462 # escaped characters and handling code spans.
1463 #
1464 $output = '';
1465
1466 $span_re = '{
1467 (
1468 \\\\' . $this->escape_chars_re . '
1469 |
1470 (?<![`\\\\])
1471 `+ # code span marker
1472 ' . ( $this->no_markup ? '' : '
1473 |
1474 <!-- .*? --> # comment
1475 |
1476 <\?.*?\?> | <%.*?%> # processing instruction
1477 |
1478 <[!$]?[-a-zA-Z0-9:_]+ # regular tags
1479 (?>
1480 \s
1481 (?>[^"\'>]+|"[^"]*"|\'[^\']*\')*
1482 )?
1483 >
1484 |
1485 <[-a-zA-Z0-9:_]+\s*/> # xml-style empty tag
1486 |
1487 </[-a-zA-Z0-9:_]+\s*> # closing tag
1488 ' ) . '
1489 )
1490 }xs';
1491
1492 # Each loop iteration search for either the next tag, the next
1493 # openning code span marker, or the next escaped character.
1494 # Each token is then passed to handleSpanToken.
1495 while ( 1 ) {
1496
1497 $parts = preg_split( $span_re, $str, 2, PREG_SPLIT_DELIM_CAPTURE );
1498
1499 # Create token from text preceding tag.
1500 if ( $parts[0] != "" ) {
1501 $output .= $parts[0];
1502 }
1503
1504 # Check if we reach the end.
1505 if ( isset( $parts[1] ) ) {
1506 $output .= $this->handleSpanToken( $parts[1], $parts[2] );
1507 $str = $parts[2];
1508 } else {
1509 break;
1510 }
1511 }
1512
1513 return $output;
1514 }
1515
1516 # Handle $token provided by parseSpan by determining its nature and
1517 # returning the corresponding value that should replace it.
1518 function handleSpanToken( $token, &$str ) {
1519
1520 switch ( $token[0] ) {
1521 case "\\":
1522 return $this->hashPart( "&#" . ord($token[1]) . ";" );
1523 case "`":
1524 # Search for end marker in remaining text.
1525 if ( preg_match( '/^(.*?[^`])' . preg_quote( $token ) . '(?!`)(.*)$/sm', $str, $matches ) ) {
1526 $str = $matches[2];
1527 $codespan = $this->makeCodeSpan( $matches[1] );
1528 return $this->hashPart( $codespan );
1529 }
1530 return $token; // return as text since no ending marker found.
1531 default:
1532 return $this->hashPart( $token );
1533 }
1534 }
1535
1536 # Remove one level of line-leading tabs or spaces
1537 function outdent( $text ) {
1538 return preg_replace( '/^(\t|[ ]{1,' . $this->tab_width . '})/m', '', $text );
1539 }
1540
1541 # String length function for detab. ``` will create a function to
1542 # hanlde UTF-8 if the default function does not exist.
1543 // note: removed in favour of utf8_strlen method
1544 // public $utf8_strlen = 'mb_strlen';
1545
1546 # Replace tabs with the appropriate amount of space.
1547 function detab( $text ) {
1548
1549 # For each line we separate the line in blocks delemited by
1550 # tab characters. Then we reconstruct every line by adding the
1551 # appropriate number of space between each blocks.
1552
1553 $text = preg_replace_callback(
1554 '/^.*\t.*$/m',
1555 array( &$this, '_detab_callback' ),
1556 $text
1557 );
1558
1559 return $text;
1560 }
1561
1562 function _detab_callback( $matches ) {
1563 $line = $matches[0];
1564 // $strlen = $this->utf8_strlen; # strlen function for UTF-8.
1565
1566 # Split in blocks.
1567 $blocks = explode( "\t", $line );
1568 # Add each blocks to the line.
1569 $line = $blocks[0];
1570 unset( $blocks[0] ); # Do not add first block twice.
1571 foreach ( $blocks as $block ) {
1572 # Calculate amount of space, insert spaces, insert block.
1573 $amount = $this->tab_width - $this->utf8_strlen( $line, 'UTF-8' ) % $this->tab_width;
1574 $line .= str_repeat( " ", $amount) . $block;
1575 }
1576 return $line;
1577 }
1578
1579 # Check for the availability of the function in the `utf8_strlen` property
1580 # (initially `mb_strlen`). If the function is not available, use markdown_extra_utf8_strlen
1581 # that will loosely count the number of UTF-8 characters with a
1582 # regular expression.
1583 // note: removed in favour of utf8_strlen method
1584 // function _initDetab() {
1585 // if ( function_exists( $this->utf8_strlen ) ) {
1586 // return;
1587 // }
1588 // $this->utf8_strlen = 'markdown_extra_utf8_strlen';
1589 // }
1590
1591 // note: replacement for _initDetab and markdown_extra_utf8_strlen function
1592 function utf8_strlen( $text ) {
1593 if ( function_exists( 'mb_strlen' ) ) {
1594 return mb_strlen( $text );
1595 }
1596 return preg_match_all( "/[\\x00-\\xBF]|[\\xC0-\\xFF][\\x80-\\xBF]*/", $text, $m );
1597 }
1598
1599 # Swap back in all the tags hashed by _HashHTMLBlocks.
1600 function unhash( $text ) {
1601 return preg_replace_callback(
1602 '/(.)\x1A[0-9]+\1/',
1603 array( &$this, '_unhash_callback' ),
1604 $text
1605 );
1606 }
1607
1608 function _unhash_callback( $matches ) {
1609 return $this->html_hashes[$matches[0]];
1610 }
1611
1612 }
1613 }
1614
1615
1616 #
1617 # Markdown Extra Parser Class
1618 #
1619 if ( !class_exists( 'radio_station_markdown_extra_parser' ) ) {
1620 class radio_station_markdown_extra_parser extends radio_station_markdown_parser {
1621
1622 ### Configuration Variables ###
1623
1624 # Prefix for footnote ids.
1625 public $fn_id_prefix = "";
1626
1627 # Optional title attribute for footnote links and backlinks.
1628 public $fn_link_title = ""; // MARKDOWN_FN_LINK_TITLE
1629 public $fn_backlink_title = ""; // MARKDOWN_FN_BACKLINK_TITLE
1630
1631 # Optional class attribute for footnote links and backlinks.
1632 public $fn_link_class = ""; // MARKDOWN_FN_LINK_CLASS
1633 public $fn_backlink_class = ""; // MARKDOWN_FN_BACKLINK_CLASS
1634
1635 # Optional class prefix for fenced code block.
1636 public $code_class_prefix = ""; // MARKDOWN_CODE_CLASS_PREFIX
1637
1638 # Class attribute for code blocks goes on the `code` tag;
1639 # setting this to true will put attributes on the `pre` tag instead.
1640 public $code_attr_on_pre = ""; // MARKDOWN_CODE_ATTR_ON_PRE
1641
1642 # Predefined abbreviations.
1643 public $predef_abbr = array();
1644
1645
1646 ### Parser Implementation ###
1647
1648 # Constructor function. Initialize the parser object.
1649 function __construct() {
1650
1651 # Add extra escapable characters before parent constructor
1652 # initialize the table.
1653 $this->escape_chars .= ':|';
1654
1655 # Insert extra document, block, and span transformations.
1656 # Parent constructor will do the sorting.
1657 $this->document_gamut += array(
1658 "doFencedCodeBlocks" => 5,
1659 "stripFootnotes" => 15,
1660 "stripAbbreviations" => 25,
1661 "appendFootnotes" => 50,
1662 );
1663 $this->block_gamut += array(
1664 "doFencedCodeBlocks" => 5,
1665 "doTables" => 15,
1666 "doDefLists" => 45,
1667 );
1668 $this->span_gamut += array(
1669 "doFootnotes" => 5,
1670 "doAbbreviations" => 70,
1671 );
1672
1673 parent::__construct();
1674 }
1675
1676 # Extra variables used during extra transformations.
1677 public $footnotes = array();
1678 public $footnotes_ordered = array();
1679 public $footnotes_ref_count = array();
1680 public $footnotes_numbers = array();
1681 public $abbr_desciptions = array();
1682 public $abbr_word_re = '';
1683
1684 # Give the current footnote number.
1685 public $footnote_counter = 1;
1686
1687 # Setting up Extra-specific variables.
1688 function setup() {
1689
1690 parent::setup();
1691
1692 $this->footnotes = array();
1693 $this->footnotes_ordered = array();
1694 $this->footnotes_ref_count = array();
1695 $this->footnotes_numbers = array();
1696 $this->abbr_desciptions = array();
1697 $this->abbr_word_re = '';
1698 $this->footnote_counter = 1;
1699
1700 foreach ( $this->predef_abbr as $abbr_word => $abbr_desc ) {
1701 if ( $this->abbr_word_re )
1702 $this->abbr_word_re .= '|';
1703 $this->abbr_word_re .= preg_quote( $abbr_word );
1704 $this->abbr_desciptions[$abbr_word] = trim( $abbr_desc );
1705 }
1706 }
1707
1708 # Clearing Extra-specific variables.
1709 function teardown() {
1710
1711 $this->footnotes = array();
1712 $this->footnotes_ordered = array();
1713 $this->footnotes_ref_count = array();
1714 $this->footnotes_numbers = array();
1715 $this->abbr_desciptions = array();
1716 $this->abbr_word_re = '';
1717
1718 parent::teardown();
1719 }
1720
1721
1722 ### Extra Attribute Parser ###
1723
1724 # Expression to use to catch attributes (includes the braces)
1725 public $id_class_attr_catch_re = '\{((?:[ ]*[#.][-_:a-zA-Z0-9]+){1,})[ ]*\}';
1726 # Expression to use when parsing in a context when no capture is desired
1727 public $id_class_attr_nocatch_re = '\{(?:[ ]*[#.][-_:a-zA-Z0-9]+){1,}[ ]*\}';
1728
1729 # Parse attributes caught by the $this->id_class_attr_catch_re expression
1730 # and return the HTML-formatted list of attributes.
1731 # Currently supported attributes are .class and #id.
1732 function doExtraAttributes( $tag_name, $attr ) {
1733
1734 if ( empty( $attr ) ) {
1735 return "";
1736 }
1737
1738 # Split on components
1739 preg_match_all( '/[#.][-_:a-zA-Z0-9]+/', $attr, $matches );
1740 $elements = $matches[0];
1741
1742 # handle classes and ids (only first id taken into account)
1743 $classes = array();
1744 $id = false;
1745 foreach ( $elements as $element ) {
1746 if ( $element[0] == '.' ) {
1747 $classes[] = substr( $element, 1 );
1748 } elseif ( $element[0] == '#' ) {
1749 if ( $id === false ) {
1750 $id = substr( $element, 1 );
1751 }
1752 }
1753 }
1754
1755 # compose attributes as string
1756 $attr_str = "";
1757 if ( !empty( $id ) ) {
1758 $attr_str .= ' id="' . $id . '"';
1759 }
1760 if ( !empty( $classes ) ) {
1761 $attr_str .= ' class="' . implode( " ", $classes ) . '"';
1762 }
1763 return $attr_str;
1764 }
1765
1766 # Strips link definitions from text, stores the URLs and titles in
1767 # hash references.
1768 function stripLinkDefinitions( $text ) {
1769
1770 $less_than_tab = $this->tab_width - 1;
1771
1772 # Link defs are in the form: ^[id]: url "optional title"
1773 $text = preg_replace_callback( '{
1774 ^[ ]{0,' . $less_than_tab . '}\[(.+)\][ ]?: # id = $1
1775 [ ]*
1776 \n? # maybe *one* newline
1777 [ ]*
1778 (?:
1779 <(.+?)> # url = $2
1780 |
1781 (\S+?) # url = $3
1782 )
1783 [ ]*
1784 \n? # maybe one newline
1785 [ ]*
1786 (?:
1787 (?<=\s) # lookbehind for whitespace
1788 ["(]
1789 (.*?) # title = $4
1790 [")]
1791 [ ]*
1792 )? # title is optional
1793 (?:[ ]* ' . $this->id_class_attr_catch_re . ' )? # $5 = extra id & class attr
1794 (?:\n+|\Z)
1795 }xm',
1796 array( &$this, '_stripLinkDefinitions_callback' ),
1797 $text
1798 );
1799 return $text;
1800 }
1801
1802 function _stripLinkDefinitions_callback( $matches ) {
1803 $link_id = strtolower( $matches[1] );
1804 $url = $matches[2] == '' ? $matches[3] : $matches[2];
1805 $this->urls[$link_id] = $url;
1806 $this->titles[$link_id] =& $matches[4];
1807 $this->ref_attr[$link_id] = $this->doExtraAttributes( "", $dummy =& $matches[5] );
1808 return ''; # String that will replace the block
1809 }
1810
1811 ### HTML Block Parser ###
1812
1813 # Tags that are always treated as block tags:
1814 public $block_tags_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|form|fieldset|iframe|hr|legend|article|section|nav|aside|hgroup|header|footer|figcaption';
1815
1816 # Tags treated as block tags only if the opening tag is alone on its line:
1817 public $context_block_tags_re = 'script|noscript|ins|del|iframe|object|source|track|param|math|svg|canvas|audio|video';
1818
1819 # Tags where markdown="1" default to span mode:
1820 public $contain_span_tags_re = 'p|h[1-6]|li|dd|dt|td|th|legend|address';
1821
1822 # Tags which must not have their contents modified, no matter where
1823 # they appear:
1824 public $clean_tags_re = 'script|math|svg';
1825
1826 # Tags that do not need to be closed.
1827 public $auto_close_tags_re = 'hr|img|param|source|track';
1828
1829
1830 # Hashify HTML Blocks and "clean tags".
1831 #
1832 # We only want to do this for block-level HTML tags, such as headers,
1833 # lists, and tables. That's because we still want to wrap <p>s around
1834 # "paragraphs" that are wrapped in non-block-level tags, such as anchors,
1835 # phrase emphasis, and spans. The list of tags we're looking for is
1836 # hard-coded.
1837 #
1838 # This works by calling _HashHTMLBlocks_InMarkdown, which then calls
1839 # _HashHTMLBlocks_InHTML when it encounter block tags. When the markdown="1"
1840 # attribute is found within a tag, _HashHTMLBlocks_InHTML calls back
1841 # _HashHTMLBlocks_InMarkdown to handle the Markdown syntax within the tag.
1842 # These two functions are calling each other. It's recursive!
1843 function hashHTMLBlocks( $text ) {
1844
1845 if ( $this->no_markup ) {
1846 return $text;
1847 }
1848
1849 # Call the HTML-in-Markdown hasher.
1850 list( $text, ) = $this->_hashHTMLBlocks_inMarkdown( $text );
1851
1852 return $text;
1853 }
1854
1855 # Parse markdown text, calling _HashHTMLBlocks_InHTML for block tags.
1856 #
1857 # * $indent is the number of space to be ignored when checking for code
1858 # blocks. This is important because if we don't take the indent into
1859 # account, something like this (which looks right) won't work as expected:
1860 #
1861 # <div>
1862 # <div markdown="1">
1863 # Hello World. <-- Is this a Markdown code block or text?
1864 # </div> <-- Is this a Markdown code block or a real tag?
1865 # <div>
1866 #
1867 # If you don't like this, just don't indent the tag on which
1868 # you apply the markdown="1" attribute.
1869 #
1870 # * If $enclosing_tag_re is not empty, stops at the first unmatched closing
1871 # tag with that name. Nested tags supported.
1872 #
1873 # * If $span is true, text inside must treated as span. So any double
1874 # newline will be replaced by a single newline so that it does not create
1875 # paragraphs.
1876 #
1877 # Returns an array of that form: ( processed text , remaining text )
1878 function _hashHTMLBlocks_inMarkdown( $text, $indent = 0, $enclosing_tag_re = '', $span = false ) {
1879
1880 if ( $text === '' ) {
1881 return array( '', '' );
1882 }
1883
1884 # Regex to check for the presence of newlines around a block tag.
1885 $newline_before_re = '/(?:^\n?|\n\n)*$/';
1886 $newline_after_re =
1887 '{
1888 ^ # Start of text following the tag.
1889 (?>[ ]*<!--.*?-->)? # Optional comment.
1890 [ ]*\n # Must be followed by newline.
1891 }xs';
1892
1893 # Regex to match any tag.
1894 $block_tag_re =
1895 '{
1896 ( # $2: Capture whole tag.
1897 </? # Any opening or closing tag.
1898 (?> # Tag name.
1899 ' . $this->block_tags_re . ' |
1900 ' . $this->context_block_tags_re . ' |
1901 ' . $this->clean_tags_re . ' |
1902 (?!\s)' . $enclosing_tag_re . '
1903 )
1904 (?:
1905 (?=[\s"\'/a-zA-Z0-9]) # Allowed characters after tag name.
1906 (?>
1907 ".*?" | # Double quotes (can contain `>`)
1908 \'.*?\' | # Single quotes (can contain `>`)
1909 .+? # Anything but quotes and `>`.
1910 )*?
1911 )?
1912 > # End of tag.
1913 |
1914 <!-- .*? --> # HTML Comment
1915 |
1916 <\?.*?\?> | <%.*?%> # Processing instruction
1917 |
1918 <!\[CDATA\[.*?\]\]> # CData Block
1919 '. ( !$span ? ' # If not in span.
1920 |
1921 # Indented code block
1922 (?: ^[ ]*\n | ^ | \n[ ]*\n )
1923 [ ]{' . ( $indent + 4 ) .'}[^\n]* \n
1924 (?>
1925 (?: [ ]{' . ( $indent + 4 ) . '}[^\n]* | [ ]* ) \n
1926 )*
1927 |
1928 # Fenced code block marker
1929 (?<= ^ | \n )
1930 [ ]{0,' . ( $indent + 3 ) .'}(?:~{3,}|`{3,})
1931 [ ]*
1932 (?:
1933 \.?[-_:a-zA-Z0-9]+ # standalone class name
1934 |
1935 ' . $this->id_class_attr_nocatch_re . ' # extra attributes
1936 )?
1937 [ ]*
1938 (?= \n )
1939 ' : '' ) . ' # End (if not is span).
1940 |
1941 # Code span marker
1942 # Note, this regex needs to go after backtick fenced
1943 # code blocks but it should also be kept outside of the
1944 # "if not in span" condition adding backticks to the parser
1945 `+
1946 )
1947 }xs';
1948
1949
1950 $depth = 0; # Current depth inside the tag tree.
1951 $parsed = ""; # Parsed text that will be returned.
1952
1953 # Loop through every tag until we find the closing tag of the parent
1954 # or loop until reaching the end of text if no parent tag specified.
1955 do {
1956
1957 # Split the text using the first $tag_match pattern found.
1958 # Text before pattern will be first in the array, text after
1959 # pattern will be at the end, and between will be any catches made
1960 # by the pattern.
1961 $parts = preg_split( $block_tag_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE );
1962
1963 # If in Markdown span mode, add a empty-string span-level hash
1964 # after each newline to prevent triggering any block element.
1965 if ( $span ) {
1966 $void = $this->hashPart( "", ':' );
1967 $newline = "$void\n";
1968 $parts[0] = $void . str_replace( "\n", $newline, $parts[0] ) . $void;
1969 }
1970
1971 $parsed .= $parts[0]; # Text before current tag.
1972
1973 # If end of $text has been reached. Stop loop.
1974 if ( count( $parts ) < 3 ) {
1975 $text = "";
1976 break;
1977 }
1978
1979 $tag = $parts[1]; # Tag to handle.
1980 $text = $parts[2]; # Remaining text after current tag.
1981 $tag_re = preg_quote( $tag ); # For use in a regular expression.
1982
1983 # Check for: Fenced code block marker.
1984 # Note: need to recheck the whole tag to disambiguate backtick
1985 # fences from code spans
1986 if ( preg_match( '{^\n?([ ]{0,'.($indent+3).'})(~{3,}|`{3,})[ ]*(?:\.?[-_:a-zA-Z0-9]+|' . $this->id_class_attr_nocatch_re . ')?[ ]*\n?$}', $tag, $capture ) ) {
1987 # Fenced code block marker: find matching end marker.
1988 $fence_indent = strlen( $capture[1] ); # use captured indent in re
1989 $fence_re = $capture[2]; # use captured fence in re
1990 if ( preg_match( '{^(?>.*\n)*?[ ]{' . ( $fence_indent ) . '}' . $fence_re . '[ ]*(?:\n|$)}', $text, $matches ) ) {
1991 # End marker found: pass text unchanged until marker.
1992 $parsed .= $tag . $matches[0];
1993 $text = substr( $text, strlen( $matches[0] ) );
1994 } else {
1995 # No end marker: just skip it.
1996 $parsed .= $tag;
1997 }
1998 }
1999
2000 # Check for: Indented code block.
2001 elseif ( $tag[0] == "\n" || $tag[0] == " " ) {
2002 # Indented code block: pass it unchanged, will be handled
2003 # later.
2004 $parsed .= $tag;
2005 }
2006
2007 # Check for: Code span marker
2008 # Note: need to check this after backtick fenced code blocks
2009 elseif ( $tag[0] == "`" ) {
2010 # Find corresponding end marker.
2011 $tag_re = preg_quote( $tag );
2012 if ( preg_match('{^(?>.+?|\n(?!\n))*?(?<!`)' . $tag_re . '(?!`)}', $text, $matches ) ) {
2013 # End marker found: pass text unchanged until marker.
2014 $parsed .= $tag . $matches[0];
2015 $text = substr( $text, strlen( $matches[0] ) );
2016 } else {
2017 # Unmatched marker: just skip it.
2018 $parsed .= $tag;
2019 }
2020 }
2021
2022 # Check for: Opening Block level tag or
2023 # Opening Context Block tag (like ins and del)
2024 # used as a block tag (tag is alone on it's line).
2025 elseif ( preg_match( '{^<(?:' . $this->block_tags_re . ')\b}', $tag ) ||
2026 ( preg_match('{^<(?:' . $this->context_block_tags_re.')\b}', $tag ) &&
2027 preg_match( $newline_before_re, $parsed ) &&
2028 preg_match( $newline_after_re, $text ) )
2029 ) {
2030 # Need to parse tag and following text using the HTML parser.
2031 list( $block_text, $text ) = $this->_hashHTMLBlocks_inHTML( $tag . $text, "hashBlock", true );
2032
2033 # Make sure it stays outside of any paragraph by adding newlines.
2034 $parsed .= "\n\n" . $block_text . "\n\n";
2035 }
2036
2037 # Check for: Clean tag (like script, math)
2038 # HTML Comments, processing instructions.
2039 elseif ( preg_match( '{^<(?:'.$this->clean_tags_re.')\b}', $tag ) || $tag[1] == '!' || $tag[1] == '?' ) {
2040 # Need to parse tag and following text using the HTML parser.
2041 # (don't check for markdown attribute)
2042 list( $block_text, $text ) = $this->_hashHTMLBlocks_inHTML( $tag . $text, "hashClean", false );
2043
2044 $parsed .= $block_text;
2045 }
2046
2047 # Check for: Tag with same name as enclosing tag.
2048 elseif ( $enclosing_tag_re !== '' &&
2049 # Same name as enclosing tag.
2050 preg_match( '{^</?(?:' . $enclosing_tag_re . ')\b}', $tag ) ) {
2051
2052 #
2053 # Increase/decrease nested tag count.
2054 #
2055 if ( $tag[1] == '/' ) $depth--;
2056 else if ( $tag[strlen($tag)-2] != '/' ) $depth++;
2057
2058 if ( $depth < 0 ) {
2059 #
2060 # Going out of parent element. Clean up and break so we
2061 # return to the calling function.
2062 #
2063 $text = $tag . $text;
2064 break;
2065 }
2066
2067 $parsed .= $tag;
2068 } else {
2069 $parsed .= $tag;
2070 }
2071 } while ( $depth >= 0 );
2072
2073 return array( $parsed, $text );
2074 }
2075
2076 # Parse HTML, calling _HashHTMLBlocks_InMarkdown for block tags.
2077 #
2078 # * Calls $hash_method to convert any blocks.
2079 # * Stops when the first opening tag closes.
2080 # * $md_attr indicate if the use of the `markdown="1"` attribute is allowed.
2081 # (it is not inside clean tags)
2082 #
2083 # Returns an array of that form: ( processed text , remaining text )
2084 function _hashHTMLBlocks_inHTML( $text, $hash_method, $md_attr ) {
2085
2086 if ( $text === '' ) {
2087 return array( '', '' );
2088 }
2089
2090 # Regex to match `markdown` attribute inside of a tag.
2091 $markdown_attr_re = '
2092 {
2093 \s* # Eat whitespace before the `markdown` attribute
2094 markdown
2095 \s*=\s*
2096 (?>
2097 (["\']) # $1: quote delimiter
2098 (.*?) # $2: attribute value
2099 \1 # matching delimiter
2100 |
2101 ([^\s>]*) # $3: unquoted attribute value
2102 )
2103 () # $4: make $3 always defined (avoid warnings)
2104 }xs';
2105
2106 # Regex to match any tag.
2107 $tag_re = '{
2108 ( # $2: Capture whole tag.
2109 </? # Any opening or closing tag.
2110 [\w:$]+ # Tag name.
2111 (?:
2112 (?=[\s"\'/a-zA-Z0-9]) # Allowed characters after tag name.
2113 (?>
2114 ".*?" | # Double quotes (can contain `>`)
2115 \'.*?\' | # Single quotes (can contain `>`)
2116 .+? # Anything but quotes and `>`.
2117 )*?
2118 )?
2119 > # End of tag.
2120 |
2121 <!-- .*? --> # HTML Comment
2122 |
2123 <\?.*?\?> | <%.*?%> # Processing instruction
2124 |
2125 <!\[CDATA\[.*?\]\]> # CData Block
2126 )
2127 }xs';
2128
2129 $original_text = $text; # Save original text in case of faliure.
2130
2131 $depth = 0; # Current depth inside the tag tree.
2132 $block_text = ""; # Temporary text holder for current text.
2133 $parsed = ""; # Parsed text that will be returned.
2134
2135 # Get the name of the starting tag.
2136 # (This pattern makes $base_tag_name_re safe without quoting.)
2137 if ( preg_match( '/^<([\w:$]*)\b/', $text, $matches ) ) {
2138 $base_tag_name_re = $matches[1];
2139 }
2140
2141 # Loop through every tag until we find the corresponding closing tag.
2142 do {
2143
2144 # Split the text using the first $tag_match pattern found.
2145 # Text before pattern will be first in the array, text after
2146 # pattern will be at the end, and between will be any catches made
2147 # by the pattern.
2148 $parts = preg_split( $tag_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE );
2149
2150 if ( count( $parts ) < 3 ) {
2151
2152 # End of $text reached with unbalenced tag(s).
2153 # In that case, we return original text unchanged and pass the
2154 # first character as filtered to prevent an infinite loop in the
2155 # parent function.
2156 return array( $original_text[0], substr( $original_text, 1 ) );
2157 }
2158
2159 $block_text .= $parts[0]; # Text before current tag.
2160 $tag = $parts[1]; # Tag to handle.
2161 $text = $parts[2]; # Remaining text after current tag.
2162
2163 # Check for: Auto-close tag (like <hr/>)
2164 # Comments and Processing Instructions.
2165 if ( preg_match('{^</?(?:' . $this->auto_close_tags_re.')\b}', $tag ) || $tag[1] == '!' || $tag[1] == '?' ) {
2166 # Just add the tag to the block as if it was text.
2167 $block_text .= $tag;
2168 } else {
2169 # Increase/decrease nested tag count. Only do so if
2170 # the tag's name match base tag's.
2171 if ( preg_match( '{^</?' . $base_tag_name_re . '\b}', $tag ) ) {
2172 if ( $tag[1] == '/' ) {
2173 $depth--;
2174 } elseif ( $tag[strlen($tag)-2] != '/' ) {
2175 $depth++;
2176 }
2177 }
2178
2179 # Check for `markdown="1"` attribute and handle it.
2180 if ( $md_attr &&
2181 preg_match( $markdown_attr_re, $tag, $attr_m ) &&
2182 preg_match( '/^1|block|span$/', $attr_m[2] . $attr_m[3] ) ) {
2183
2184 # Remove `markdown` attribute from opening tag.
2185 $tag = preg_replace( $markdown_attr_re, '', $tag );
2186
2187 # Check if text inside this tag must be parsed in span mode.
2188 $this->mode = $attr_m[2] . $attr_m[3];
2189 $span_mode = $this->mode == 'span' || $this->mode != 'block' &&
2190 preg_match( '{^<(?:'.$this->contain_span_tags_re.')\b}', $tag );
2191
2192 # Calculate indent before tag.
2193 if ( preg_match( '/(?:^|\n)( *?)(?! ).*?$/', $block_text, $matches ) ) {
2194 // $strlen = $this->utf8_strlen;
2195 $indent = $this->utf8_strlen( $matches[1], 'UTF-8' );
2196 } else {
2197 $indent = 0;
2198 }
2199
2200 # End preceding block with this tag.
2201 $block_text .= $tag;
2202 $parsed .= $this->$hash_method( $block_text );
2203
2204 # Get enclosing tag name for the ParseMarkdown function.
2205 # (This pattern makes $tag_name_re safe without quoting.)
2206 preg_match( '/^<([\w:$]*)\b/', $tag, $matches );
2207 $tag_name_re = $matches[1];
2208
2209 # Parse the content using the HTML-in-Markdown parser.
2210 list( $block_text, $text ) = $this->_hashHTMLBlocks_inMarkdown($text, $indent, $tag_name_re, $span_mode);
2211
2212 # Outdent markdown text.
2213 if ( $indent > 0 ) {
2214 $block_text = preg_replace( "/^[ ]{1,$indent}/m", "", $block_text );
2215 }
2216
2217 # Append tag content to parsed text.
2218 if ( !$span_mode ) {
2219 $parsed .= "\n\n" . $block_text . "\n\n";
2220 } else {
2221 $parsed .= $block_text;
2222 }
2223
2224 # Start over with a new block.
2225 $block_text = "";
2226 } else {
2227 $block_text .= $tag;
2228 }
2229 }
2230
2231 } while ( $depth > 0 );
2232
2233 # Hash last block text that wasn't processed inside the loop.
2234 $parsed .= $this->$hash_method( $block_text );
2235
2236 return array( $parsed, $text );
2237 }
2238
2239 # Called whenever a tag must be hashed when a function inserts a "clean" tag
2240 # in $text, it passes through this function and is automaticaly escaped,
2241 # blocking invalid nested overlap.
2242 function hashClean( $text ) {
2243 return $this->hashPart( $text, 'C' );
2244 }
2245
2246 # Turn Markdown link shortcuts into XHTML <a> tags.
2247 function doAnchors( $text ) {
2248
2249 if ( $this->in_anchor ) {
2250 return $text;
2251 }
2252 $this->in_anchor = true;
2253
2254 # First, handle reference-style links: [link text] [id]
2255 $text = preg_replace_callback( '{
2256 ( # wrap whole match in $1
2257 \[
2258 (' . $this->nested_brackets_re . ') # link text = $2
2259 \]
2260
2261 [ ]? # one optional space
2262 (?:\n[ ]*)? # one optional newline followed by spaces
2263
2264 \[
2265 (.*?) # id = $3
2266 \]
2267 )
2268 }xs',
2269 array( &$this, '_doAnchors_reference_callback' ), $text );
2270
2271 #
2272 # Next, inline-style links: [link text](url "optional title")
2273 #
2274 $text = preg_replace_callback( '{
2275 ( # wrap whole match in $1
2276 \[
2277 (' . $this->nested_brackets_re . ') # link text = $2
2278 \]
2279 \( # literal paren
2280 [ \n]*
2281 (?:
2282 <(.+?)> # href = $3
2283 |
2284 (' . $this->nested_url_parenthesis_re . ') # href = $4
2285 )
2286 [ \n]*
2287 ( # $5
2288 ([\'"]) # quote char = $6
2289 (.*?) # Title = $7
2290 \6 # matching quote
2291 [ \n]* # ignore any spaces/tabs between closing quote and )
2292 )? # title is optional
2293 \)
2294 (?:[ ]? ' . $this->id_class_attr_catch_re . ' )? # $8 = id/class attributes
2295 )
2296 }xs',
2297 array( &$this, '_doAnchors_inline_callback' ),
2298 $text
2299 );
2300
2301 # Last, handle reference-style shortcuts: [link text]
2302 # These must come last in case you've also got [link text][1]
2303 # or [link text](/foo)
2304 $text = preg_replace_callback( '{
2305 ( # wrap whole match in $1
2306 \[
2307 ([^\[\]]+) # link text = $2; can\'t contain [ or ]
2308 \]
2309 )
2310 }xs',
2311 array( &$this, '_doAnchors_reference_callback'),
2312 $text
2313 );
2314
2315 $this->in_anchor = false;
2316 return $text;
2317 }
2318
2319 function _doAnchors_reference_callback( $matches ) {
2320 $whole_match = $matches[1];
2321 $link_text = $matches[2];
2322 $link_id =& $matches[3];
2323
2324 if ( $link_id == "" ) {
2325 # for shortcut links like [this][] or [this].
2326 $link_id = $link_text;
2327 }
2328
2329 # lower-case and turn embedded newlines into spaces
2330 $link_id = strtolower( $link_id );
2331 $link_id = preg_replace( '{[ ]?\n}', ' ', $link_id );
2332
2333 if ( isset( $this->urls[$link_id] ) ) {
2334 $url = $this->urls[$link_id];
2335 $url = $this->encodeAttribute( $url );
2336
2337 $result = "<a href=\"" . $url . "\"";
2338 if ( isset( $this->titles[$link_id] ) ) {
2339 $title = $this->titles[$link_id];
2340 $title = $this->encodeAttribute( $title );
2341 $result .= " title=\"" . $title . "\"";
2342 }
2343 if ( isset( $this->ref_attr[$link_id] ) )
2344 $result .= $this->ref_attr[$link_id];
2345
2346 $link_text = $this->runSpanGamut( $link_text );
2347 $result .= ">" . $link_text . "</a>";
2348 $result = $this->hashPart( $result );
2349 } else {
2350 $result = $whole_match;
2351 }
2352 return $result;
2353 }
2354
2355 function _doAnchors_inline_callback( $matches ) {
2356 $whole_match = $matches[1];
2357 $link_text = $this->runSpanGamut( $matches[2] );
2358 $url = $matches[3] == '' ? $matches[4] : $matches[3];
2359 $title =& $matches[7];
2360 $attr = $this->doExtraAttributes( "a", $dummy =& $matches[8] );
2361
2362 $url = $this->encodeAttribute( $url );
2363
2364 $result = "<a href=\"" . $url . "\"";
2365 if ( isset( $title ) ) {
2366 $title = $this->encodeAttribute( $title );
2367 $result .= " title=\"" . $title . "\"";
2368 }
2369 $result .= $attr;
2370
2371 $link_text = $this->runSpanGamut( $link_text );
2372 $result .= ">" . $link_text . "</a>";
2373
2374 return $this->hashPart( $result );
2375 }
2376
2377 # Turn Markdown image shortcuts into <img> tags.
2378 function doImages( $text ) {
2379
2380 # First, handle reference-style labeled images: ![alt text][id]
2381 $text = preg_replace_callback( '{
2382 ( # wrap whole match in $1
2383 !\[
2384 (' . $this->nested_brackets_re . ') # alt text = $2
2385 \]
2386
2387 [ ]? # one optional space
2388 (?:\n[ ]*)? # one optional newline followed by spaces
2389
2390 \[
2391 (.*?) # id = $3
2392 \]
2393
2394 )
2395 }xs',
2396 array( &$this, '_doImages_reference_callback' ),
2397 $text
2398 );
2399
2400 # Next, handle inline images: ![alt text](url "optional title")
2401 # Don't forget: encode * and _
2402 $text = preg_replace_callback( '{
2403 ( # wrap whole match in $1
2404 !\[
2405 (' . $this->nested_brackets_re . ') # alt text = $2
2406 \]
2407 \s? # One optional whitespace character
2408 \( # literal paren
2409 [ \n]*
2410 (?:
2411 <(\S*)> # src url = $3
2412 |
2413 (' . $this->nested_url_parenthesis_re . ') # src url = $4
2414 )
2415 [ \n]*
2416 ( # $5
2417 ([\'"]) # quote char = $6
2418 (.*?) # title = $7
2419 \6 # matching quote
2420 [ \n]*
2421 )? # title is optional
2422 \)
2423 (?:[ ]? ' . $this->id_class_attr_catch_re . ' )? # $8 = id/class attributes
2424 )
2425 }xs',
2426 array( &$this, '_doImages_inline_callback' ),
2427 $text
2428 );
2429
2430 return $text;
2431 }
2432
2433 function _doImages_reference_callback( $matches ) {
2434 $whole_match = $matches[1];
2435 $alt_text = $matches[2];
2436 $link_id = strtolower( $matches[3] );
2437
2438 if ( $link_id == "" ) {
2439 $link_id = strtolower( $alt_text ); # for shortcut links like ![this][].
2440 }
2441
2442 $alt_text = $this->encodeAttribute( $alt_text );
2443 if ( isset($this->urls[$link_id] ) ) {
2444 $url = $this->encodeAttribute( $this->urls[$link_id] );
2445 $result = "<img src=\"" . $url . "\" alt=\"" . $alt_text . "\"";
2446 if ( isset( $this->titles[$link_id] ) ) {
2447 $title = $this->titles[$link_id];
2448 $title = $this->encodeAttribute( $title );
2449 $result .= " title=\"" . $title . "\"";
2450 }
2451 if ( isset( $this->ref_attr[$link_id] ) )
2452 $result .= $this->ref_attr[$link_id];
2453 $result .= $this->empty_element_suffix;
2454 $result = $this->hashPart( $result );
2455 } else {
2456 # If there's no such link ID, leave intact:
2457 $result = $whole_match;
2458 }
2459
2460 return $result;
2461 }
2462
2463 function _doImages_inline_callback( $matches ) {
2464 $whole_match = $matches[1];
2465 $alt_text = $matches[2];
2466 $url = $matches[3] == '' ? $matches[4] : $matches[3];
2467 $title =& $matches[7];
2468 $attr = $this->doExtraAttributes( "img", $dummy =& $matches[8] );
2469
2470 $alt_text = $this->encodeAttribute( $alt_text );
2471 $url = $this->encodeAttribute( $url );
2472 $result = "<img src=\"" . $url . "\" alt=\"" . $alt_text . "\"";
2473 if ( isset( $title ) ) {
2474 $title = $this->encodeAttribute( $title );
2475 $result .= " title=\"" . $title . "\""; # $title already quoted
2476 }
2477 $result .= $attr;
2478 $result .= $this->empty_element_suffix;
2479
2480 return $this->hashPart( $result );
2481 }
2482
2483 # Redefined to add id and class attribute support.
2484 function doHeaders( $text ) {
2485
2486 # Setext-style headers:
2487 # Header 1 {#header1}
2488 # ========
2489 #
2490 # Header 2 {#header2 .class1 .class2}
2491 # --------
2492 #
2493 $text = preg_replace_callback(
2494 '{
2495 (^.+?) # $1: Header text
2496 (?:[ ]+ ' . $this->id_class_attr_catch_re . ' )? # $3 = id/class attributes
2497 [ ]*\n(=+|-+)[ ]*\n+ # $3: Header footer
2498 }mx',
2499 array( &$this, '_doHeaders_callback_setext'),
2500 $text
2501 );
2502
2503 # atx-style headers:
2504 # # Header 1 {#header1}
2505 # ## Header 2 {#header2}
2506 # ## Header 2 with closing hashes ## {#header3.class1.class2}
2507 # ...
2508 # ###### Header 6 {.class2}
2509 #
2510 $text = preg_replace_callback( '{
2511 ^(\#{1,6}) # $1 = string of #\'s
2512 [ ]*
2513 (.+?) # $2 = Header text
2514 [ ]*
2515 \#* # optional closing #\'s (not counted)
2516 (?:[ ]+ ' . $this->id_class_attr_catch_re . ' )? # $3 = id/class attributes
2517 [ ]*
2518 \n+
2519 }xm',
2520 array( &$this, '_doHeaders_callback_atx'),
2521 $text
2522 );
2523
2524 return $text;
2525 }
2526
2527 function _doHeaders_callback_setext( $matches ) {
2528 if ( $matches[3] == '-' && preg_match( '{^- }', $matches[1] ) )
2529 return $matches[0];
2530 $level = $matches[3][0] == '=' ? 1 : 2;
2531 $attr = $this->doExtraAttributes( "h" . $level, $dummy =& $matches[2] );
2532 $block = "<h" . $level . $attr . ">" . $this->runSpanGamut( $matches[1] ) . "</h" . $level . ">";
2533 return "\n" . $this->hashBlock( $block ) . "\n\n";
2534 }
2535
2536 function _doHeaders_callback_atx( $matches ) {
2537 $level = strlen( $matches[1] );
2538 $attr = $this->doExtraAttributes("h" . $level, $dummy =& $matches[3] );
2539 $block = "<h" . $level . $attr . ">" . $this->runSpanGamut( $matches[2] ) . "</h" . $level . ">";
2540 return "\n" . $this->hashBlock( $block ) . "\n\n";
2541 }
2542
2543 # Form HTML tables.
2544 function doTables( $text ) {
2545
2546 $less_than_tab = $this->tab_width - 1;
2547
2548 # Find tables with leading pipe.
2549 #
2550 # | Header 1 | Header 2
2551 # | -------- | --------
2552 # | Cell 1 | Cell 2
2553 # | Cell 3 | Cell 4
2554 $text = preg_replace_callback('
2555 {
2556 ^ # Start of a line
2557 [ ]{0,' . $less_than_tab . '} # Allowed whitespace.
2558 [|] # Optional leading pipe (present)
2559 (.+) \n # $1: Header row (at least one pipe)
2560
2561 [ ]{0,' . $less_than_tab . '} # Allowed whitespace.
2562 [|] ([ ]*[-:]+[-| :]*) \n # $2: Header underline
2563
2564 ( # $3: Cells
2565 (?>
2566 [ ]* # Allowed whitespace.
2567 [|] .* \n # Row content.
2568 )*
2569 )
2570 (?=\n|\Z) # Stop at final double newline.
2571 }xm',
2572 array( &$this, '_doTable_leadingPipe_callback'),
2573 $text
2574 );
2575
2576 # Find tables without leading pipe.
2577 #
2578 # Header 1 | Header 2
2579 # -------- | --------
2580 # Cell 1 | Cell 2
2581 # Cell 3 | Cell 4
2582 $text = preg_replace_callback( '
2583 {
2584 ^ # Start of a line
2585 [ ]{0,' . $less_than_tab . '} # Allowed whitespace.
2586 (\S.*[|].*) \n # $1: Header row (at least one pipe)
2587
2588 [ ]{0,' . $less_than_tab . '} # Allowed whitespace.
2589 ([-:]+[ ]*[|][-| :]*) \n # $2: Header underline
2590
2591 ( # $3: Cells
2592 (?>
2593 .* [|] .* \n # Row content
2594 )*
2595 )
2596 (?=\n|\Z) # Stop at final double newline.
2597 }xm',
2598 array( &$this, '_DoTable_callback' ),
2599 $text
2600 );
2601
2602 return $text;
2603 }
2604
2605 function _doTable_leadingPipe_callback( $matches ) {
2606 $head = $matches[1];
2607 $underline = $matches[2];
2608 $content = $matches[3];
2609
2610 # Remove leading pipe for each row.
2611 $content = preg_replace( '/^ *[|]/m', '', $content );
2612
2613 return $this->_doTable_callback( array( $matches[0], $head, $underline, $content ) );
2614 }
2615
2616 function _doTable_callback( $matches ) {
2617 $head = $matches[1];
2618 $underline = $matches[2];
2619 $content = $matches[3];
2620
2621 # Remove any tailing pipes for each line.
2622 $head = preg_replace( '/[|] *$/m', '', $head );
2623 $underline = preg_replace( '/[|] *$/m', '', $underline );
2624 $content = preg_replace( '/[|] *$/m', '', $content );
2625
2626 # Reading alignement from header underline.
2627 $separators = preg_split( '/ *[|] */', $underline );
2628 foreach ( $separators as $n => $s ) {
2629 if ( preg_match('/^ *-+: *$/', $s) ) {
2630 $attr[$n] = ' align="right"';
2631 } else if ( preg_match('/^ *:-+: *$/', $s ) ) {
2632 $attr[$n] = ' align="center"';
2633 } else if ( preg_match('/^ *:-+ *$/', $s ) ) {
2634 $attr[$n] = ' align="left"';
2635 } else {
2636 $attr[$n] = '';
2637 }
2638 }
2639
2640 # Parsing span elements, including code spans, character escapes,
2641 # and inline HTML tags, so that pipes inside those gets ignored.
2642 $head = $this->parseSpan( $head );
2643 $headers = preg_split( '/ *[|] */', $head );
2644 $col_count = count( $headers );
2645 $attr = array_pad( $attr, $col_count, '' );
2646
2647 # Write column headers.
2648 $text = "<table>\n";
2649 $text .= "<thead>\n";
2650 $text .= "<tr>\n";
2651 foreach ( $headers as $n => $header ) {
2652 $text .= " <th" . $attr[$n] . ">" . $this->runSpanGamut( trim( $header ) ) . "</th>\n";
2653 }
2654 $text .= "</tr>\n";
2655 $text .= "</thead>\n";
2656
2657 # Split content by row.
2658 $rows = explode( "\n", trim( $content, "\n" ) );
2659
2660 $text .= "<tbody>\n";
2661 foreach ( $rows as $row ) {
2662 # Parsing span elements, including code spans, character escapes,
2663 # and inline HTML tags, so that pipes inside those gets ignored.
2664 $row = $this->parseSpan( $row );
2665
2666 # Split row by cell.
2667 $row_cells = preg_split( '/ *[|] */', $row, $col_count );
2668 $row_cells = array_pad( $row_cells, $col_count, '' );
2669
2670 $text .= "<tr>\n";
2671 foreach ( $row_cells as $n => $cell ) {
2672 $text .= " <td" . $attr[$n] . ">" . $this->runSpanGamut( trim( $cell ) ) . "</td>\n";
2673 }
2674 $text .= "</tr>\n";
2675 }
2676 $text .= "</tbody>\n";
2677 $text .= "</table>";
2678
2679 return $this->hashBlock( $text ) . "\n";
2680 }
2681
2682 # Form HTML definition lists.
2683 function doDefLists( $text ) {
2684
2685 $less_than_tab = $this->tab_width - 1;
2686
2687 # Re-usable pattern to match any entire dl list:
2688 $whole_list_re = '(?>
2689 ( # $1 = whole list
2690 ( # $2
2691 [ ]{0,' . $less_than_tab . '}
2692 ((?>.*\S.*\n)+) # $3 = defined term
2693 \n?
2694 [ ]{0,' . $less_than_tab . '}:[ ]+ # colon starting definition
2695 )
2696 (?s:.+?)
2697 ( # $4
2698 \z
2699 |
2700 \n{2,}
2701 (?=\S)
2702 (?! # Negative lookahead for another term
2703 [ ]{0,' . $less_than_tab . '}
2704 (?: \S.*\n )+? # defined term
2705 \n?
2706 [ ]{0,' . $less_than_tab . '}:[ ]+ # colon starting definition
2707 )
2708 (?! # Negative lookahead for another definition
2709 [ ]{0,' . $less_than_tab . '}:[ ]+ # colon starting definition
2710 )
2711 )
2712 )
2713 )'; // mx
2714
2715 $text = preg_replace_callback( '{
2716 (?>\A\n?|(?<=\n\n))
2717 ' . $whole_list_re . '
2718 }mx',
2719 array( &$this, '_doDefLists_callback' ),
2720 $text
2721 );
2722
2723 return $text;
2724 }
2725
2726 function _doDefLists_callback( $matches ) {
2727 # Re-usable patterns to match list item bullets and number markers:
2728 $list = $matches[1];
2729
2730 # Turn double returns into triple returns, so that we can make a
2731 # paragraph for the last item in a list, if necessary:
2732 $result = trim( $this->processDefListItems( $list ) );
2733 $result = "<dl>\n" . $result . "\n</dl>";
2734 return $this->hashBlock( $result ) . "\n\n";
2735 }
2736
2737 # Process the contents of a single definition list, splitting it
2738 # into individual term and definition list items.
2739 function processDefListItems( $list_str ) {
2740
2741 $less_than_tab = $this->tab_width - 1;
2742
2743 # trim trailing blank lines:
2744 $list_str = preg_replace( "/\n{2,}\\z/", "\n", $list_str );
2745
2746 # Process definition terms.
2747 $list_str = preg_replace_callback( '{
2748 (?>\A\n?|\n\n+) # leading line
2749 ( # definition terms = $1
2750 [ ]{0,' . $less_than_tab . '} # leading whitespace
2751 (?!\:[ ]|[ ]) # negative lookahead for a definition
2752 # mark (colon) or more whitespace.
2753 (?> \S.* \n)+? # actual term (not whitespace).
2754 )
2755 (?=\n?[ ]{0,3}:[ ]) # lookahead for following line feed
2756 # with a definition mark.
2757 }xm',
2758 array( &$this, '_processDefListItems_callback_dt' ),
2759 $list_str
2760 );
2761
2762 # Process actual definitions.
2763 $list_str = preg_replace_callback( '{
2764 \n(\n+)? # leading line = $1
2765 ( # marker space = $2
2766 [ ]{0,' . $less_than_tab . '} # whitespace before colon
2767 \:[ ]+ # definition mark (colon)
2768 )
2769 ((?s:.+?)) # definition text = $3
2770 (?= \n+ # stop at next definition mark,
2771 (?: # next term or end of text
2772 [ ]{0,' . $less_than_tab . '} \:[ ] |
2773 <dt> | \z
2774 )
2775 )
2776 }xm',
2777 array( &$this, '_processDefListItems_callback_dd' ),
2778 $list_str
2779 );
2780
2781 return $list_str;
2782 }
2783
2784 function _processDefListItems_callback_dt( $matches ) {
2785 $terms = explode( "\n", trim( $matches[1] ) );
2786 $text = '';
2787 foreach ( $terms as $term ) {
2788 $term = $this->runSpanGamut( trim( $term ) );
2789 $text .= "\n<dt>" . $term . "</dt>";
2790 }
2791 return $text . "\n";
2792 }
2793
2794 function _processDefListItems_callback_dd( $matches ) {
2795 $leading_line = $matches[1];
2796 $marker_space = $matches[2];
2797 $def = $matches[3];
2798
2799 if ( $leading_line || preg_match( '/\n{2,}/', $def ) ) {
2800 # Replace marker with the appropriate whitespace indentation
2801 $def = str_repeat( ' ', strlen( $marker_space)) . $def;
2802 $def = $this->runBlockGamut( $this->outdent( $def . "\n\n" ) );
2803 $def = "\n" . $def ."\n";
2804 } else {
2805 $def = rtrim( $def );
2806 $def = $this->runSpanGamut( $this->outdent( $def ) );
2807 }
2808
2809 return "\n<dd>" . $def . "</dd>\n";
2810 }
2811
2812 # Adding the fenced code block syntax to regular Markdown:
2813 function doFencedCodeBlocks( $text ) {
2814
2815 # ~~~
2816 # Code block
2817 # ~~~
2818 $less_than_tab = $this->tab_width;
2819
2820 $text = preg_replace_callback( '{
2821 (?:\n|\A)
2822 # 1: Opening marker
2823 (
2824 (?:~{3,}|`{3,}) # 3 or more tildes/backticks.
2825 )
2826 [ ]*
2827 (?:
2828 \.?([-_:a-zA-Z0-9]+) # 2: standalone class name
2829 |
2830 ' . $this->id_class_attr_catch_re . ' # 3: Extra attributes
2831 )?
2832 [ ]* \n # Whitespace and newline following marker.
2833
2834 # 4: Content
2835 (
2836 (?>
2837 (?!\1 [ ]* \n) # Not a closing marker.
2838 .*\n+
2839 )+
2840 )
2841
2842 # Closing marker.
2843 \1 [ ]* (?= \n )
2844 }xm',
2845 array( &$this, '_doFencedCodeBlocks_callback' ),
2846 $text
2847 );
2848
2849 return $text;
2850 }
2851
2852 function _doFencedCodeBlocks_callback( $matches ) {
2853 $classname =& $matches[2];
2854 $attrs =& $matches[3];
2855 $codeblock = $matches[4];
2856 $codeblock = htmlspecialchars( $codeblock, ENT_NOQUOTES );
2857 $codeblock = preg_replace_callback( '/^\n+/', array( &$this, '_doFencedCodeBlocks_newlines' ), $codeblock );
2858
2859 if ( $classname != "" ) {
2860 if ( $classname[0] == '.' ) {
2861 $classname = substr($classname, 1 );
2862 }
2863 $attr_str = ' class="' . $this->code_class_prefix.$classname . '"';
2864 } else {
2865 $attr_str = $this->doExtraAttributes( $this->code_attr_on_pre ? "pre" : "code", $attrs );
2866 }
2867 $pre_attr_str = $this->code_attr_on_pre ? $attr_str : '';
2868 $code_attr_str = $this->code_attr_on_pre ? '' : $attr_str;
2869 $codeblock = "<pre" . $pre_attr_str . "><code" . $code_attr_str . ">" . $codeblock . "</code></pre>";
2870
2871 return "\n\n".$this->hashBlock( $codeblock ) . "\n\n";
2872 }
2873
2874 function _doFencedCodeBlocks_newlines( $matches ) {
2875 return str_repeat( "<br" . $this->empty_element_suffix, strlen( $matches[0] ) );
2876 }
2877
2878 # Redefining emphasis markers so that emphasis by underscore does not
2879 # work in the middle of a word.
2880 public $em_relist = array(
2881 '' => '(?:(?<!\*)\*(?!\*)|(?<![a-zA-Z0-9_])_(?!_))(?=\S|$)(?![\.,:;]\s)',
2882 '*' => '(?<=\S|^)(?<!\*)\*(?!\*)',
2883 '_' => '(?<=\S|^)(?<!_)_(?![a-zA-Z0-9_])',
2884 );
2885 public $strong_relist = array(
2886 '' => '(?:(?<!\*)\*\*(?!\*)|(?<![a-zA-Z0-9_])__(?!_))(?=\S|$)(?![\.,:;]\s)',
2887 '**' => '(?<=\S|^)(?<!\*)\*\*(?!\*)',
2888 '__' => '(?<=\S|^)(?<!_)__(?![a-zA-Z0-9_])',
2889 );
2890 public $em_strong_relist = array(
2891 '' => '(?:(?<!\*)\*\*\*(?!\*)|(?<![a-zA-Z0-9_])___(?!_))(?=\S|$)(?![\.,:;]\s)',
2892 '***' => '(?<=\S|^)(?<!\*)\*\*\*(?!\*)',
2893 '___' => '(?<=\S|^)(?<!_)___(?![a-zA-Z0-9_])',
2894 );
2895
2896 # Params: $text - string to process with html <p> tags
2897 function formParagraphs( $text ) {
2898
2899 # Strip leading and trailing lines:
2900 $text = preg_replace( '/\A\n+|\n+\z/', '', $text );
2901
2902 $grafs = preg_split( '/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY );
2903
2904 # Wrap <p> tags and unhashify HTML blocks
2905 foreach ( $grafs as $key => $value ) {
2906 $value = trim( $this->runSpanGamut( $value ) );
2907
2908 # Check if this should be enclosed in a paragraph.
2909 # Clean tag hashes & block tag hashes are left alone.
2910 $is_p = !preg_match( '/^B\x1A[0-9]+B|^C\x1A[0-9]+C$/', $value );
2911
2912 if ( $is_p ) {
2913 $value = "<p>$value</p>";
2914 }
2915 $grafs[$key] = $value;
2916 }
2917
2918 # Join grafs in one text, then unhash HTML tags.
2919 $text = implode( "\n\n", $grafs );
2920
2921 # Finish by removing any tag hashes still present in $text.
2922 $text = $this->unhash( $text );
2923
2924 return $text;
2925 }
2926
2927 ### Footnotes
2928
2929 # Strips link definitions from text, stores the URLs and titles in
2930 # hash references.
2931 function stripFootnotes( $text ) {
2932
2933 $less_than_tab = $this->tab_width - 1;
2934
2935 # Link defs are in the form: [^id]: url "optional title"
2936 $text = preg_replace_callback( '{
2937 ^[ ]{0,' . $less_than_tab . '}\[\^(.+?)\][ ]?: # note_id = $1
2938 [ ]*
2939 \n? # maybe *one* newline
2940 ( # text = $2 (no blank lines allowed)
2941 (?:
2942 .+ # actual text
2943 |
2944 \n # newlines but
2945 (?!\[\^.+?\]:\s)# negative lookahead for footnote marker.
2946 (?!\n+[ ]{0,3}\S)# ensure line is not blank and followed
2947 # by non-indented content
2948 )*
2949 )
2950 }xm',
2951 array( &$this, '_stripFootnotes_callback' ),
2952 $text
2953 );
2954 return $text;
2955 }
2956
2957 function _stripFootnotes_callback( $matches ) {
2958 $note_id = $this->fn_id_prefix . $matches[1];
2959 $this->footnotes[$note_id] = $this->outdent( $matches[2] );
2960 return ''; # String that will replace the block
2961 }
2962
2963 # Replace footnote references in $text [^id] with a special text-token
2964 # which will be replaced by the actual footnote marker in appendFootnotes.
2965 function doFootnotes( $text ) {
2966
2967 if ( !$this->in_anchor ) {
2968 $text = preg_replace( '{\[\^(.+?)\]}', "F\x1Afn:\\1\x1A:", $text );
2969 }
2970 return $text;
2971 }
2972
2973
2974 # Append footnote list to text.
2975 function appendFootnotes( $text ) {
2976
2977 $text = preg_replace_callback( '{F\x1Afn:(.*?)\x1A:}', array( &$this, '_appendFootnotes_callback' ), $text );
2978
2979 if ( !empty( $this->footnotes_ordered ) ) {
2980 $text .= "\n\n";
2981 $text .= "<div class=\"footnotes\">\n";
2982 $text .= "<hr" . $this->empty_element_suffix ."\n";
2983 $text .= "<ol>\n\n";
2984
2985 $attr = "";
2986 if ( $this->fn_backlink_class != "" ) {
2987 $class = $this->fn_backlink_class;
2988 $class = $this->encodeAttribute( $class );
2989 $attr .= " class=\"" . $class . "\"";
2990 }
2991 if ( $this->fn_backlink_title != "" ) {
2992 $title = $this->fn_backlink_title;
2993 $title = $this->encodeAttribute( $title );
2994 $attr .= " title=\"" . $title . "\"";
2995 }
2996 $num = 0;
2997
2998 while ( !empty( $this->footnotes_ordered ) ) {
2999 $footnote = reset( $this->footnotes_ordered );
3000 $note_id = key( $this->footnotes_ordered );
3001 unset( $this->footnotes_ordered[$note_id] );
3002 $ref_count = $this->footnotes_ref_count[$note_id];
3003 unset( $this->footnotes_ref_count[$note_id] );
3004 unset( $this->footnotes[$note_id] );
3005
3006 $footnote .= "\n"; # Need to append newline before parsing.
3007 $footnote = $this->runBlockGamut( $footnote . "\n");
3008 $footnote = preg_replace_callback( '{F\x1Afn:(.*?)\x1A:}', array( &$this, '_appendFootnotes_callback' ), $footnote );
3009
3010 $attr = str_replace( "%%", ++$num, $attr );
3011 $note_id = $this->encodeAttribute( $note_id );
3012
3013 # Prepare backlink, multiple backlinks if multiple references
3014 $backlink = "<a href=\"#fnref:" . $note_id . "\"" . $attr . ">&#8617;</a>";
3015 for ( $ref_num = 2; $ref_num <= $ref_count; ++$ref_num ) {
3016 $backlink .= " <a href=\"#fnref" . $ref_num . ":" . $note_id . "\"" . $attr .">&#8617;</a>";
3017 }
3018 # Add backlink to last paragraph; create new paragraph if needed.
3019 if ( preg_match( '{</p>$}', $footnote ) ) {
3020 $footnote = substr( $footnote, 0, -4 ) . "&#160;" . $backlink . "</p>";
3021 } else {
3022 $footnote .= "\n\n<p>" . $backlink . "</p>";
3023 }
3024
3025 $text .= "<li id=\"fn:" . $note_id . "\">\n";
3026 $text .= $footnote . "\n";
3027 $text .= "</li>\n\n";
3028 }
3029
3030 $text .= "</ol>\n";
3031 $text .= "</div>";
3032 }
3033 return $text;
3034 }
3035
3036 function _appendFootnotes_callback( $matches ) {
3037 $node_id = $this->fn_id_prefix . $matches[1];
3038
3039 # Create footnote marker only if it has a corresponding footnote *and*
3040 # the footnote hasn't been used by another marker.
3041 if ( isset( $this->footnotes[$node_id] ) ) {
3042 $num =& $this->footnotes_numbers[$node_id];
3043 if ( !isset( $num ) ) {
3044 # Transfer footnote content to the ordered list and give it its
3045 # number
3046 $this->footnotes_ordered[$node_id] = $this->footnotes[$node_id];
3047 $this->footnotes_ref_count[$node_id] = 1;
3048 $num = $this->footnote_counter++;
3049 $ref_count_mark = '';
3050 } else {
3051 $ref_count_mark = $this->footnotes_ref_count[$node_id] += 1;
3052 }
3053
3054 $attr = "";
3055 if ( $this->fn_link_class != "" ) {
3056 $class = $this->fn_link_class;
3057 $class = $this->encodeAttribute( $class );
3058 $attr .= " class=\"" . $class . "\"";
3059 }
3060 if ( $this->fn_link_title != "" ) {
3061 $title = $this->fn_link_title;
3062 $title = $this->encodeAttribute( $title );
3063 $attr .= " title=\"" . $title . "\"";
3064 }
3065
3066 $attr = str_replace( "%%", $num, $attr );
3067 $node_id = $this->encodeAttribute( $node_id );
3068
3069 return
3070 "<sup id=\"fnref" . $ref_count_mark . ":" . $node_id . "\">" .
3071 "<a href=\"#fn:" . $node_id . "\"" . $attr . ">" . $num . "</a>" .
3072 "</sup>";
3073 }
3074
3075 return "[^" . $matches[1] . "]";
3076 }
3077
3078 ### Abbreviations ###
3079
3080 # Strips abbreviations from text, stores titles in hash references.
3081 function stripAbbreviations( $text ) {
3082
3083 $less_than_tab = $this->tab_width - 1;
3084
3085 # Link defs are in the form: [id]*: url "optional title"
3086 $text = preg_replace_callback( '{
3087 ^[ ]{0,' . $less_than_tab . '}\*\[(.+?)\][ ]?: # abbr_id = $1
3088 (.*) # text = $2 (no blank lines allowed)
3089 }xm',
3090 array( &$this, '_stripAbbreviations_callback' ),
3091 $text
3092 );
3093 return $text;
3094 }
3095
3096 function _stripAbbreviations_callback( $matches ) {
3097 $abbr_word = $matches[1];
3098 $abbr_desc = $matches[2];
3099 if ( $this->abbr_word_re ) {
3100 $this->abbr_word_re .= '|';
3101 }
3102 $this->abbr_word_re .= preg_quote( $abbr_word );
3103 $this->abbr_desciptions[$abbr_word] = trim( $abbr_desc );
3104 return ''; # String that will replace the block
3105 }
3106
3107 # Find defined abbreviations in text and wrap them in <abbr> elements.
3108 function doAbbreviations( $text ) {
3109
3110 if ( $this->abbr_word_re ) {
3111 // cannot use the /x modifier because abbr_word_re may
3112 // contain significant spaces:
3113 $text = preg_replace_callback( '{'.
3114 '(?<![\w\x1A])'.
3115 '(?:' . $this->abbr_word_re . ')'.
3116 '(?![\w\x1A])'.
3117 '}',
3118 array( &$this, '_doAbbreviations_callback' ),
3119 $text
3120 );
3121 }
3122 return $text;
3123 }
3124
3125 function _doAbbreviations_callback( $matches ) {
3126 $abbr = $matches[0];
3127 if ( isset( $this->abbr_desciptions[$abbr] ) ) {
3128 $desc = $this->abbr_desciptions[$abbr];
3129 if ( empty( $desc ) ) {
3130 return $this->hashPart( "<abbr>" . $abbr . "</abbr>");
3131 } else {
3132 $desc = $this->encodeAttribute( $desc );
3133 return $this->hashPart( "<abbr title=\"" . $desc . "\">" . $abbr . "</abbr>" );
3134 }
3135 } else {
3136 return $matches[0];
3137 }
3138 }
3139
3140 }
3141 }
3142
3143
3144 /*
3145
3146 PHP Markdown Extra
3147 ==================
3148
3149 Description
3150 -----------
3151
3152 This is a PHP port of the original Markdown formatter written in Perl
3153 by John Gruber. This special "Extra" version of PHP Markdown features
3154 further enhancements to the syntax for making additional constructs
3155 such as tables and definition list.
3156
3157 Markdown is a text-to-HTML filter; it translates an easy-to-read /
3158 easy-to-write structured text format into HTML. Markdown's text format
3159 is mostly similar to that of plain text email, and supports features such
3160 as headers, *emphasis*, code blocks, blockquotes, and links.
3161
3162 Markdown's syntax is designed not as a generic markup language, but
3163 specifically to serve as a front-end to (X)HTML. You can use span-level
3164 HTML tags anywhere in a Markdown document, and you can use block level
3165 HTML tags (like <div> and <table> as well).
3166
3167 For more information about Markdown's syntax, see:
3168
3169 <http://daringfireball.net/projects/markdown/>
3170
3171
3172 Bugs
3173 ----
3174
3175 To file bug reports please send email to:
3176
3177 <michel.fortin@michelf.ca>
3178
3179 Please include with your report: (1) the example input; (2) the output you
3180 expected; (3) the output Markdown actually produced.
3181
3182
3183 Version History
3184 ---------------
3185
3186 See the readme file for detailed release notes for this version.
3187
3188
3189 Copyright and License
3190 ---------------------
3191
3192 PHP Markdown & Extra
3193 Copyright (c) 2004-2013 Michel Fortin
3194 <http://michelf.ca/>
3195 All rights reserved.
3196
3197 Based on Markdown
3198 Copyright (c) 2003-2006 John Gruber
3199 <http://daringfireball.net/>
3200 All rights reserved.
3201
3202 Redistribution and use in source and binary forms, with or without
3203 modification, are permitted provided that the following conditions are
3204 met:
3205
3206 * Redistributions of source code must retain the above copyright notice,
3207 this list of conditions and the following disclaimer.
3208
3209 * Redistributions in binary form must reproduce the above copyright
3210 notice, this list of conditions and the following disclaimer in the
3211 documentation and/or other materials provided with the distribution.
3212
3213 * Neither the name "Markdown" nor the names of its contributors may
3214 be used to endorse or promote products derived from this software
3215 without specific prior written permission.
3216
3217 This software is provided by the copyright holders and contributors "as
3218 is" and any express or implied warranties, including, but not limited
3219 to, the implied warranties of merchantability and fitness for a
3220 particular purpose are disclaimed. In no event shall the copyright owner
3221 or contributors be liable for any direct, indirect, incidental, special,
3222 exemplary, or consequential damages (including, but not limited to,
3223 procurement of substitute goods or services; loss of use, data, or
3224 profits; or business interruption) however caused and on any theory of
3225 liability, whether in contract, strict liability, or tort (including
3226 negligence or otherwise) arising in any way out of the use of this
3227 software, even if advised of the possibility of such damage.
3228
3229 */
3230
3231
3232 // -----------------------
3233 // WordPress Readme Parser
3234 // -----------------------
3235
3236 // if ( !class_exists( 'WordPress_Readme_Parser' ) ) {
3237 if ( !class_exists( 'radio_station_readme_parser' ) ) {
3238 class radio_station_readme_parser {
3239
3240 function __construct() {
3241 // This space intentially blank
3242 }
3243
3244 function parse_readme( $file ) {
3245 $file_contents = @implode( '', @file( $file ) );
3246 return $this->parse_readme_contents( $file_contents );
3247 }
3248
3249 function parse_readme_contents( $file_contents ) {
3250 $file_contents = str_replace( array( "\r\n", "\r" ), "\n", $file_contents );
3251 $file_contents = trim( $file_contents );
3252 if ( 0 === strpos( $file_contents, "\xEF\xBB\xBF" ) ) {
3253 $file_contents = substr( $file_contents, 3 );
3254 }
3255
3256 // Markdown transformations
3257 $file_contents = preg_replace( "|^###([^#]+)#*?\s*?\n|im", '=$1=' . "\n", $file_contents );
3258 $file_contents = preg_replace( "|^##([^#]+)#*?\s*?\n|im", '==$1==' . "\n", $file_contents );
3259 $file_contents = preg_replace( "|^#([^#]+)#*?\s*?\n|im", '===$1===' . "\n", $file_contents );
3260
3261 // === Plugin Name ===
3262 // Must be the very first thing.
3263 if ( !preg_match('|^===(.*)===|', $file_contents, $_name ) ) {
3264 return array(); // require a name
3265 }
3266 $name = trim( $_name[1], '=' );
3267 $name = $this->sanitize_text( $name );
3268
3269 $file_contents = $this->chop_string( $file_contents, $_name[0] );
3270
3271 // Requires at least: 1.5
3272 if ( preg_match( '|Requires at least:(.*)|i', $file_contents, $_requires_at_least ) ) {
3273 $requires_at_least = $this->sanitize_text($_requires_at_least[1]);
3274 } else {
3275 $requires_at_least = NULL;
3276 }
3277
3278 // Tested up to: 2.1
3279 if ( preg_match( '|Tested up to:(.*)|i', $file_contents, $_tested_up_to ) ) {
3280 $tested_up_to = $this->sanitize_text( $_tested_up_to[1] );
3281 } else {
3282 $tested_up_to = NULL;
3283 }
3284
3285 // Stable tag: 10.4-ride-the-fire-eagle-danger-day
3286 if ( preg_match( '|Stable tag:(.*)|i', $file_contents, $_stable_tag ) ) {
3287 $stable_tag = $this->sanitize_text( $_stable_tag[1] );
3288 } else {
3289 $stable_tag = NULL; // we assume trunk, but don't set it here to tell the difference between specified trunk and default trunk
3290 }
3291
3292 // Tags: some tag, another tag, we like tags
3293 if ( preg_match( '|Tags:(.*)|i', $file_contents, $_tags ) ) {
3294 $tags = preg_split('|,[\s]*?|', trim( $_tags[1] ) );
3295 foreach ( array_keys( $tags ) as $t ) {
3296 $tags[$t] = $this->sanitize_text( $tags[$t] );
3297 }
3298 } else {
3299 $tags = array();
3300 }
3301
3302 // Contributors: markjaquith, mdawaffe, zefrank
3303 $contributors = array();
3304 if ( preg_match( '|Contributors:(.*)|i', $file_contents, $_contributors ) ) {
3305 $temp_contributors = preg_split( '|,[\s]*|', trim( $_contributors[1] ) );
3306 foreach ( array_keys( $temp_contributors ) as $c ) {
3307 $tmp_sanitized = $this->user_sanitize( $temp_contributors[$c] );
3308 if ( strlen( trim( $tmp_sanitized ) ) > 0 ) {
3309 $contributors[$c] = $tmp_sanitized;
3310 }
3311 unset( $tmp_sanitized );
3312 }
3313 }
3314
3315 // Donate Link: URL
3316 if ( preg_match( '|Donate link:(.*)|i', $file_contents, $_donate_link ) ) {
3317 $donate_link = esc_url( $_donate_link[1] );
3318 } else {
3319 $donate_link = NULL;
3320 }
3321
3322 // togs, conts, etc are optional and order shouldn't matter. So we chop them only after we've grabbed their values.
3323 foreach ( array( 'tags', 'contributors', 'requires_at_least', 'tested_up_to', 'stable_tag', 'donate_link') as $chop ) {
3324 if ( $$chop ) {
3325 $_chop = '_' . $chop;
3326 $file_contents = $this->chop_string( $file_contents, ${$_chop}[0] );
3327 }
3328 }
3329
3330 $file_contents = trim( $file_contents );
3331
3332 // short-description fu
3333 if ( !preg_match( '/(^(.*?))^[\s]*=+?[\s]*.+?[\s]*=+?/ms', $file_contents, $_short_description ) ) {
3334 $_short_description = array( 1 => &$file_contents, 2 => &$file_contents );
3335 }
3336 $short_desc_filtered = $this->sanitize_text( $_short_description[2] );
3337 $short_desc_length = strlen( $short_desc_filtered );
3338 $short_description = substr( $short_desc_filtered, 0, 150 );
3339 if ( $short_desc_length > strlen( $short_description ) ) {
3340 $truncated = true;
3341 } else {
3342 $truncated = false;
3343 }
3344 if ( $_short_description[1] ) {
3345 $file_contents = $this->chop_string( $file_contents, $_short_description[1] ); // yes, the [1] is intentional
3346 }
3347
3348 // == Section ==
3349 // Break into sections
3350 // $_sections[0] will be the title of the first section, $_sections[1] will be the content of the first section
3351 // the array alternates from there: title2, content2, title3, content3... and so forth
3352 $_sections = preg_split( '/^[\s]*==[\s]*(.+?)[\s]*==/m', $file_contents, -1, PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY );
3353
3354 $sections = array();
3355 for ( $i = 1; $i <= count($_sections); $i +=2 ) {
3356 $_sections[$i] = preg_replace( '/^[\s]*=[\s]+(.+?)[\s]+=/m', '<h4>$1</h4>', $_sections[$i] );
3357 $_sections[$i] = $this->filter_text( $_sections[$i], true );
3358 $title = $this->sanitize_text( $_sections[$i-1] );
3359 $sections[str_replace( ' ', '_', strtolower( $title ) )] = array( 'title' => $title, 'content' => $_sections[$i] );
3360 }
3361
3362
3363 // Special sections
3364 // This is where we nab our special sections, so we can enforce their order and treat them differently, if needed
3365 // upgrade_notice is not a section, but parse it like it is for now
3366 $final_sections = array();
3367 foreach ( array( 'description', 'installation', 'frequently_asked_questions', 'screenshots', 'changelog', 'change_log', 'upgrade_notice', 'extra_notes') as $special_section ) {
3368 if ( isset( $sections[$special_section] ) ) {
3369 $final_sections[$special_section] = $sections[$special_section]['content'];
3370 unset( $sections[$special_section] );
3371 }
3372 }
3373 if ( isset( $final_sections['change_log'] ) && empty( $final_sections['changelog'] ) ) {
3374 $final_sections['changelog'] = $final_sections['change_log'];
3375 }
3376
3377 $final_screenshots = array();
3378 if ( isset( $final_sections['screenshots'] ) ) {
3379 preg_match_all( '|<li>(.*?)</li>|s', $final_sections['screenshots'], $screenshots, PREG_SET_ORDER );
3380 if ( $screenshots ) {
3381 foreach ( (array) $screenshots as $ss ) {
3382 $final_screenshots[] = $ss[1];
3383 }
3384 }
3385 }
3386
3387 // Parse the upgrade_notice section specially:
3388 // 1.0 => blah, 1.1 => fnord
3389 if ( isset( $final_sections['upgrade_notice'] ) ) {
3390 $upgrade_notice = array();
3391 $split = preg_split( '#<h4>(.*?)</h4>#', $final_sections['upgrade_notice'], -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY );
3392 for ( $i = 0; $i < count( $split ); $i += 2 ) {
3393 // modified: use filter_text instead of sanitize text to maintain markup
3394 $upgrade_notice[$this->sanitize_text( $split[$i] )] = substr( $this->filter_text( $split[$i + 1] ), 0, 1000 );
3395 }
3396 unset( $final_sections['upgrade_notice'] );
3397 }
3398
3399 // No description?
3400 // No problem... we'll just fall back to the old style of description
3401 // We'll even let you use markup this time!
3402 $excerpt = false;
3403 if ( !isset( $final_sections['description'] ) ) {
3404 $final_sections = array_merge( array( 'description' => $this->filter_text( $_short_description[2], true ) ), $final_sections );
3405 $excerpt = true;
3406 }
3407
3408 // dump the non-special sections into $remaining_content
3409 // their order will be determined by their original order in the readme.txt
3410 $remaining_content = '';
3411 foreach ( $sections as $s_name => $s_data ) {
3412 $remaining_content .= "\n<h3>" . $s_data['title'] . "</h3>\n" . $s_data['content'];
3413 }
3414 $remaining_content = trim( $remaining_content );
3415
3416 // All done!
3417 // $r['tags'] and $r['contributors'] are simple arrays
3418 // $r['sections'] is an array with named elements
3419 $r = array(
3420 'name' => $name,
3421 'tags' => $tags,
3422 'requires_at_least' => $requires_at_least,
3423 'tested_up_to' => $tested_up_to,
3424 'stable_tag' => $stable_tag,
3425 'contributors' => $contributors,
3426 'donate_link' => $donate_link,
3427 'short_description' => $short_description,
3428 'screenshots' => $final_screenshots,
3429 'is_excerpt' => $excerpt,
3430 'is_truncated' => $truncated,
3431 'sections' => $final_sections,
3432 'remaining_content' => $remaining_content,
3433 'upgrade_notice' => $upgrade_notice
3434 );
3435
3436 return $r;
3437 }
3438
3439 function chop_string( $string, $chop ) { // chop a "prefix" from a string: Agressive! uses strstr not 0 === strpos
3440 if ( $_string = strstr( $string, $chop ) ) {
3441 $_string = substr( $_string, strlen( $chop ) );
3442 return trim( $_string );
3443 } else {
3444 return trim( $string );
3445 }
3446 }
3447
3448 function user_sanitize( $text, $strict = false ) { // whitelisted chars
3449 // if ( function_exists( 'user_sanitize' ) ) { // bbPress native
3450 // return user_sanitize( $text, $strict );
3451 // }
3452
3453 if ( $strict ) {
3454 $text = preg_replace('/[^a-z0-9-]/i', '', $text );
3455 $text = preg_replace('|-+|', '-', $text );
3456 } else {
3457 $text = preg_replace('/[^a-z0-9_-]/i', '', $text );
3458 }
3459 return $text;
3460 }
3461
3462 function sanitize_text( $text ) { // not fancy
3463 // $text = strip_tags( $text );
3464 $text = wp_strip_all_tags( $text );
3465 $text = esc_html( $text );
3466 $text = trim( $text );
3467 return $text;
3468 }
3469
3470 function filter_text( $text, $markdown = false ) { // fancy, Markdown
3471 $text = trim( $text );
3472
3473 $text = call_user_func( array( __CLASS__, 'code_trick' ), $text, $markdown ); // A better parser than Markdown's for: backticks -> CODE
3474
3475 if ( $markdown ) { // Parse markdown.
3476 // if ( !function_exists( 'Markdown' ) )
3477 // require WORDPRESS_README_MARKDOWN;
3478 // $text = Markdown( $text );
3479 $text = radio_station_markdown( $text );
3480 }
3481
3482 $allowed = array(
3483 'a' => array(
3484 'href' => array(),
3485 'title' => array(),
3486 'rel' => array()),
3487 'blockquote' => array('cite' => array()),
3488 'br' => array(),
3489 'p' => array(),
3490 'code' => array(),
3491 'pre' => array(),
3492 'em' => array(),
3493 'strong' => array(),
3494 'ul' => array(),
3495 'ol' => array(),
3496 'li' => array(),
3497 'h3' => array(),
3498 'h4' => array()
3499 );
3500
3501 $text = balanceTags( $text );
3502
3503 $text = wp_kses( $text, $allowed );
3504 $text = trim( $text );
3505 return $text;
3506 }
3507
3508 function code_trick( $text, $markdown ) {
3509 // Don't use bbPress native function - it's incompatible with Markdown
3510 // If doing markdown, first take any user formatted code blocks and turn them into backticks so that
3511 // markdown will preserve things like underscores in code blocks
3512 if ( $markdown ) {
3513 $text = preg_replace_callback( "!(<pre><code>|<code>)(.*?)(</code></pre>|</code>)!s", array( __CLASS__,'decodeit' ), $text );
3514 }
3515
3516 $text = str_replace(array("\r\n", "\r"), "\n", $text);
3517 if ( !$markdown ) {
3518 // This gets the "inline" code blocks, but can't be used with Markdown.
3519 $text = preg_replace_callback("|(`)(.*?)`|", array( __CLASS__, 'encodeit'), $text);
3520 // This gets the "block level" code blocks and converts them to PRE CODE
3521 $text = preg_replace_callback("!(^|\n)`(.*?)`!s", array( __CLASS__, 'encodeit'), $text);
3522 } else {
3523 // Markdown can do inline code, we convert bbPress style block level code to Markdown style
3524 $text = preg_replace_callback("!(^|\n)([ \t]*?)`(.*?)`!s", array( __CLASS__, 'indent'), $text);
3525 }
3526 return $text;
3527 }
3528
3529 function indent( $matches ) {
3530 $text = $matches[3];
3531 $text = preg_replace('|^|m', $matches[2] . ' ', $text);
3532 return $matches[1] . $text;
3533 }
3534
3535 function encodeit( $matches ) {
3536 // if ( function_exists('encodeit') ) { // bbPress native
3537 // return encodeit( $matches );
3538 // }
3539
3540 $text = trim( $matches[2] );
3541 $text = htmlspecialchars( $text, ENT_QUOTES );
3542 $text = str_replace(array( "\r\n", "\r"), "\n", $text );
3543 $text = preg_replace("|\n\n\n+|", "\n\n", $text );
3544 $text = str_replace( '&amp;lt;', '&lt;', $text );
3545 $text = str_replace( '&amp;gt;', '&gt;', $text );
3546 $text = "<code>" . $text . "</code>";
3547 if ( "`" != $matches[1] ) {
3548 $text = "<pre>" . $text . "</pre>";
3549 }
3550 return $text;
3551 }
3552
3553 function decodeit( $matches ) {
3554 // if ( function_exists( 'decodeit' ) ) { // bbPress native
3555 // return decodeit( $matches );
3556 // }
3557
3558 $text = $matches[2];
3559 $trans_table = array_flip( get_html_translation_table( HTML_ENTITIES ) );
3560 $text = strtr( $text, $trans_table );
3561 $text = str_replace( '<br />', '', $text );
3562 $text = str_replace( '&#38;', '&', $text );
3563 $text = str_replace( '&#39;', "'", $text );
3564 if ( '<pre><code>' == $matches[1] ) {
3565 $text = "\n" . $text . "\n";
3566 }
3567 return "`" . $text . "`";
3568 }
3569
3570 } // end class
3571 }
3572
3573 /**
3574 * GitHub-Flavoured Markdown. Inspired by Evan's plugin, but modified.
3575 *
3576 * @author Evan Solomon
3577 * @author Matt Wiebe <wiebe@automattic.com>
3578 * @link https://github.com/evansolomon/wp-github-flavored-markdown-comments
3579 *
3580 * Add a few extras from GitHub's Markdown implementation. Must be used in a WordPress environment.
3581 */
3582
3583 // if ( !class_exists( 'GHF_Markdown_Parser' ) ) {
3584 if ( !class_exists( 'radio_station_github_markdown_parser' ) ) {
3585 class radio_station_github_markdown_parser extends radio_station_markdown_extra_parser {
3586
3587 /**
3588 * Hooray somewhat arbitrary numbers that are fearful of 1.0.x.
3589 */
3590 // const GHF_MARDOWN_VERSION = '0.9.0';
3591
3592 /**
3593 * Use a [code] shortcode when encountering a fenced code block
3594 * @var boolean
3595 */
3596 public $use_code_shortcode = true;
3597
3598 /**
3599 * Preserve shortcodes, untouched by Markdown.
3600 * This requires use within a WordPress installation.
3601 * @var boolean
3602 */
3603 public $preserve_shortcodes = true;
3604
3605 /**
3606 * Preserve the legacy $latex your-latex-code-here$ style
3607 * LaTeX markup
3608 */
3609 public $preserve_latex = true;
3610
3611 /**
3612 * Preserve single-line <code> blocks.
3613 * @var boolean
3614 */
3615 public $preserve_inline_code_blocks = true;
3616
3617 /**
3618 * Strip paragraphs from the output. This is the right default for WordPress,
3619 * which generally wants to create its own paragraphs with `wpautop`
3620 * @var boolean
3621 */
3622 public $strip_paras = true;
3623
3624 // Will run through sprintf - you can supply your own syntax if you want
3625 public $shortcode_start = '[code lang=%s]';
3626 public $shortcode_end = '[/code]';
3627
3628 // Stores shortcodes we remove and then replace
3629 protected $preserve_text_hash = array();
3630
3631 /**
3632 * Set environment defaults based on presence of key functions/classes.
3633 */
3634 public function __construct() {
3635 $this->use_code_shortcode = class_exists( 'SyntaxHighlighter' );
3636 /**
3637 * Allow processing shortcode contents.
3638 *
3639 * @module markdown
3640 *
3641 * @since 4.4.0
3642 *
3643 * @param boolean $preserve_shortcodes Defaults to $this->preserve_shortcodes.
3644 */
3645 $this->preserve_shortcodes = apply_filters( 'jetpack_markdown_preserve_shortcodes', $this->preserve_shortcodes ) && function_exists( 'get_shortcode_regex' );
3646 $this->preserve_latex = function_exists( 'latex_markup' );
3647 $this->strip_paras = function_exists( 'wpautop' );
3648
3649 parent::__construct();
3650 }
3651
3652 /**
3653 * Overload to specify heading styles only if the hash has space(s) after it. This is actually in keeping with
3654 * the documentation and eases the semantic overload of the hash character.
3655 * #Will Not Produce a Heading 1
3656 * # This Will Produce a Heading 1
3657 *
3658 * @param string $text Markdown text
3659 * @return string HTML-transformed text
3660 */
3661 public function transform( $text ) {
3662 // Preserve anything inside a single-line <code> element
3663 if ( $this->preserve_inline_code_blocks ) {
3664 $text = $this->single_line_code_preserve( $text );
3665 }
3666 // Remove all shortcodes so their interiors are left intact
3667 if ( $this->preserve_shortcodes ) {
3668 $text = $this->shortcode_preserve( $text );
3669 }
3670 // Remove legacy LaTeX so it's left intact
3671 if ( $this->preserve_latex ) {
3672 $text = $this->latex_preserve( $text );
3673 }
3674
3675 // Do not process characters inside URLs.
3676 $text = $this->urls_preserve( $text );
3677
3678 // escape line-beginning # chars that do not have a space after them.
3679 $text = preg_replace_callback( '|^#{1,6}( )?|um', array( $this, '_doEscapeForHashWithoutSpacing' ), $text );
3680
3681 /**
3682 * Allow third-party plugins to define custom patterns that won't be processed by Markdown.
3683 *
3684 * @module markdown
3685 *
3686 * @since 3.9.2
3687 *
3688 * @param array $custom_patterns Array of custom patterns to be ignored by Markdown.
3689 */
3690 $custom_patterns = apply_filters( 'jetpack_markdown_preserve_pattern', array() );
3691 if ( is_array( $custom_patterns ) && ! empty( $custom_patterns ) ) {
3692 foreach ( $custom_patterns as $pattern ) {
3693 $text = preg_replace_callback( $pattern, array( $this, '_doRemoveText'), $text );
3694 }
3695 }
3696
3697 // run through core Markdown
3698 $text = parent::transform( $text );
3699
3700 // Occasionally Markdown Extra chokes on a para structure, producing odd paragraphs.
3701 $text = str_replace( "<p>&lt;</p>\n\n<p>p>", '<p>', $text );
3702
3703 // put start-of-line # chars back in place
3704 $text = $this->restore_leading_hash( $text );
3705
3706 // Strip paras if set
3707 if ( $this->strip_paras ) {
3708 $text = $this->unp( $text );
3709 }
3710
3711 // Restore preserved things like shortcodes/LaTeX
3712 $text = $this->do_restore( $text );
3713
3714 return $text;
3715 }
3716
3717 /**
3718 * Prevents blocks like <code>__this__</code> from turning into <code><strong>this</strong></code>
3719 * @param string $text Text that may need preserving
3720 * @return string Text that was preserved if needed
3721 */
3722 public function single_line_code_preserve( $text ) {
3723 return preg_replace_callback( '|<code\b[^>]*>(.*?)</code>|', array( $this, 'do_single_line_code_preserve' ), $text );
3724 }
3725
3726 /**
3727 * Regex callback for inline code presevation
3728 * @param array $matches Regex matches
3729 * @return string Hashed content for later restoration
3730 */
3731 public function do_single_line_code_preserve( $matches ) {
3732 return '<code>' . $this->hash_block( $matches[1] ) . '</code>';
3733 }
3734
3735 /**
3736 * Preserve code block contents by HTML encoding them. Useful before getting to KSES stripping.
3737 * @param string $text Markdown/HTML content
3738 * @return string Markdown/HTML content with escaped code blocks
3739 */
3740 public function codeblock_preserve( $text ) {
3741 return preg_replace_callback( "/^([`~]{3})([^`\n]+)?\n([^`~]+)(\\1)/m", array( $this, 'do_codeblock_preserve' ), $text );
3742 }
3743
3744 /**
3745 * Regex callback for code block preservation.
3746 * @param array $matches Regex matches
3747 * @return string Codeblock with escaped interior
3748 */
3749 public function do_codeblock_preserve( $matches ) {
3750 $block = stripslashes( $matches[3] );
3751 $block = esc_html( $block );
3752 $block = str_replace( '\\', '\\\\', $block );
3753 $open = $matches[1] . $matches[2] . "\n";
3754 return $open . $block . $matches[4];
3755 }
3756
3757 /**
3758 * Restore previously preserved (i.e. escaped) code block contents.
3759 * @param string $text Markdown/HTML content with escaped code blocks
3760 * @return string Markdown/HTML content
3761 */
3762 public function codeblock_restore( $text ) {
3763 return preg_replace_callback( "/^([`~]{3})([^`\n]+)?\n([^`~]+)(\\1)/m", array( $this, 'do_codeblock_restore' ), $text );
3764 }
3765
3766 /**
3767 * Regex callback for code block restoration (unescaping).
3768 * @param array $matches Regex matches
3769 * @return string Codeblock with unescaped interior
3770 */
3771 public function do_codeblock_restore( $matches ) {
3772 $block = html_entity_decode( $matches[3], ENT_QUOTES );
3773 $open = $matches[1] . $matches[2] . "\n";
3774 return $open . $block . $matches[4];
3775 }
3776
3777 /**
3778 * Called to preserve legacy LaTeX like $latex some-latex-text $
3779 * @param string $text Text in which to preserve LaTeX
3780 * @return string Text with LaTeX replaced by a hash that will be restored later
3781 */
3782 protected function latex_preserve( $text ) {
3783 // regex from latex_remove()
3784 $regex = '%
3785 \$latex(?:=\s*|\s+)
3786 ((?:
3787 [^$]+ # Not a dollar
3788 |
3789 (?<=(?<!\\\\)\\\\)\$ # Dollar preceded by exactly one slash
3790 )+)
3791 (?<!\\\\)\$ # Dollar preceded by zero slashes
3792 %ix';
3793 $text = preg_replace_callback( $regex, array( $this, '_doRemoveText'), $text );
3794 return $text;
3795 }
3796
3797 /**
3798 * Called to preserve WP shortcodes from being formatted by Markdown in any way.
3799 * @param string $text Text in which to preserve shortcodes
3800 * @return string Text with shortcodes replaced by a hash that will be restored later
3801 */
3802 protected function shortcode_preserve( $text ) {
3803 $text = preg_replace_callback( $this->get_shortcode_regex(), array( $this, '_doRemoveText' ), $text );
3804 return $text;
3805 }
3806
3807 /**
3808 * Avoid characters inside URLs from being formatted by Markdown in any way.
3809 *
3810 * @param string $text Text in which to preserve URLs.
3811 *
3812 * @return string Text with URLs replaced by a hash that will be restored later.
3813 */
3814 protected function urls_preserve( $text ) {
3815 $text = preg_replace_callback(
3816 '#(?<!<)(?:https?|ftp)://([^\s<>"\'\[\]()]+|\[(?1)*+\]|\((?1)*+\))+(?<![_*.?])#i',
3817 array( $this, '_doRemoveText' ),
3818 $text
3819 );
3820 return $text;
3821 }
3822
3823 /**
3824 * Restores any text preserved by $this->hash_block()
3825 * @param string $text Text that may have hashed preservation placeholders
3826 * @return string Text with hashed preseravtion placeholders replaced by original text
3827 */
3828 protected function do_restore( $text ) {
3829 // Reverse hashes to ensure nested blocks are restored.
3830 $hashes = array_reverse( $this->preserve_text_hash, true );
3831 foreach( $hashes as $hash => $value ) {
3832 $placeholder = $this->hash_maker( $hash );
3833 $text = str_replace( $placeholder, $value, $text );
3834 }
3835 // reset the hash
3836 $this->preserve_text_hash = array();
3837 return $text;
3838 }
3839
3840 /**
3841 * Regex callback for text preservation
3842 * @param array $m Regex $matches array
3843 * @return string A placeholder that will later be replaced by the original text
3844 */
3845 protected function _doRemoveText( $m ) {
3846 return $this->hash_block( $m[0] );
3847 }
3848
3849 /**
3850 * Call this to store a text block for later restoration.
3851 * @param string $text Text to preserve for later
3852 * @return string Placeholder that will be swapped out later for the original text
3853 */
3854 protected function hash_block( $text ) {
3855 $hash = md5( $text );
3856 $this->preserve_text_hash[ $hash ] = $text;
3857 $placeholder = $this->hash_maker( $hash );
3858 return $placeholder;
3859 }
3860
3861 /**
3862 * Less glamorous than the Keymaker
3863 * @param string $hash An md5 hash
3864 * @return string A placeholder hash
3865 */
3866 protected function hash_maker( $hash ) {
3867 return 'MARKDOWN_HASH' . $hash . 'MARKDOWN_HASH';
3868 }
3869
3870 /**
3871 * Remove bare <p> elements. <p>s with attributes will be preserved.
3872 * @param string $text HTML content
3873 * @return string <p>-less content
3874 */
3875 public function unp( $text ) {
3876 return preg_replace( "#<p>(.*?)</p>(\n|$)#ums", '$1$2', $text );
3877 }
3878
3879 /**
3880 * A regex of all shortcodes currently registered by the current
3881 * WordPress installation
3882 * @uses get_shortcode_regex()
3883 * @return string A regex for grabbing shortcodes.
3884 */
3885 protected function get_shortcode_regex() {
3886 $pattern = get_shortcode_regex();
3887
3888 // don't match markdown link anchors that could be mistaken for shortcodes.
3889 $pattern .= '(?!\()';
3890
3891 return "/" . $pattern . "/s";
3892 }
3893
3894 /**
3895 * Since we escape unspaced #Headings, put things back later.
3896 * @param string $text text with a leading escaped hash
3897 * @return string text with leading hashes unescaped
3898 */
3899 protected function restore_leading_hash( $text ) {
3900 return preg_replace( "/^(<p>)?(&#35;|\\\\#)/um", "$1#", $text );
3901 }
3902
3903 /**
3904 * Overload to support ```-fenced code blocks for pre-Markdown Extra 1.2.8
3905 * https://help.github.com/articles/github-flavored-markdown#fenced-code-blocks
3906 */
3907 public function doFencedCodeBlocks( $text ) {
3908 // If we're at least at 1.2.8, native fenced code blocks are in.
3909 // Below is just copied from it in case we somehow got loaded on
3910 // top of someone else's Markdown Extra
3911
3912 // if ( version_compare( MARKDOWNEXTRA_VERSION, '1.2.8', '>=' ) )
3913 return parent::doFencedCodeBlocks( $text );
3914
3915 #
3916 # Adding the fenced code block syntax to regular Markdown:
3917 #
3918 # ~~~
3919 # Code block
3920 # ~~~
3921 #
3922 $less_than_tab = $this->tab_width;
3923
3924 $text = preg_replace_callback( '{
3925 (?:\n|\A)
3926 # 1: Opening marker
3927 (
3928 (?:~{3,}|`{3,}) # 3 or more tildes/backticks.
3929 )
3930 [ ]*
3931 (?:
3932 \.?([-_:a-zA-Z0-9]+) # 2: standalone class name
3933 |
3934 ' . $this->id_class_attr_catch_re . ' # 3: Extra attributes
3935 )?
3936 [ ]* \n # Whitespace and newline following marker.
3937
3938 # 4: Content
3939 (
3940 (?>
3941 (?!\1 [ ]* \n) # Not a closing marker.
3942 .*\n+
3943 )+
3944 )
3945
3946 # Closing marker.
3947 \1 [ ]* (?= \n )
3948 }xm',
3949 array( $this, '_doFencedCodeBlocks_callback' ),
3950 $text
3951 );
3952
3953 return $text;
3954 }
3955
3956 /**
3957 * Callback for pre-processing start of line hashes to slyly escape headings that don't
3958 * have a leading space
3959 * @param array $m preg_match matches
3960 * @return string possibly escaped start of line hash
3961 */
3962 public function _doEscapeForHashWithoutSpacing( $m ) {
3963 if ( !isset( $m[1] ) ) {
3964 $m[0] = '\\' . $m[0];
3965 }
3966 return $m[0];
3967 }
3968
3969 /**
3970 * Overload to support Viper's [code] shortcode. Because awesome.
3971 */
3972 public function _doFencedCodeBlocks_callback( $matches ) {
3973 // in case we have some escaped leading hashes right at the start of the block
3974 $matches[4] = $this->restore_leading_hash( $matches[4] );
3975 // just MarkdownExtra_Parser if we're not going ultra-deluxe
3976 if ( !$this->use_code_shortcode ) {
3977 return parent::_doFencedCodeBlocks_callback( $matches );
3978 }
3979
3980 // default to a "text" class if one wasn't passed. Helps with encoding issues later.
3981 if ( empty( $matches[2] ) ) {
3982 $matches[2] = 'text';
3983 }
3984
3985 $classname =& $matches[2];
3986 $codeblock = preg_replace_callback('/^\n+/', array( $this, '_doFencedCodeBlocks_newlines' ), $matches[4] );
3987
3988 if ( $classname[0] == '.' ) {
3989 $classname = substr( $classname, 1 );
3990 }
3991
3992 $codeblock = esc_html( $codeblock );
3993 $codeblock = sprintf( $this->shortcode_start, $classname ) . "\n{$codeblock}" . $this->shortcode_end;
3994 return "\n\n" . $this->hashBlock( $codeblock ). "\n\n";
3995 }
3996
3997 }
3998 }
3999
4000
4001 // =========
4002 // CHANGELOG
4003 // =========
4004
4005 // == 1.3.1 ==
4006 // - Updated: Prefixed all classes and functions
4007 // - Cleaned: Coding (WPCS) and comment formatting
4008
4009 // == 1.1.8 ==
4010 // - Added: Github Flavoured Reademe Parser
4011 // - Added: class_exists wrapper checks
4012
4013 // == 1.0,7 ==
4014 // - Added: function_exists and already defined checks
4015 // - Changed: filename from readme.php to reader.PHP
4016
4017
4018
4019