PluginProbe ʕ •ᴥ•ʔ
Pods – Custom Content Types and Fields / 2.9.19.4
Pods – Custom Content Types and Fields v2.9.19.4
2.8.23.4 2.9.19.4 3.0.10.4 3.1.4.2 3.2.8.3 3.3.9.1 trunk 1.14.8 2.7.31.3 2.8.23.3 2.9.19.3 3.0.10.3 3.1.4.1 3.2.0 3.2.1 3.2.1.1 3.2.2 3.2.4 3.2.5 3.2.6 3.2.7 3.2.7.1 3.2.8 3.2.8.1 3.2.8.2 3.3.0 3.3.1 3.3.2 3.3.3 3.3.4 3.3.5 3.3.6 3.3.7 3.3.8 3.3.9
pods / components / Markdown.php
pods / components Last commit date
Builder 2 weeks ago I18n 2 weeks ago Migrate-ACF 2 weeks ago Migrate-CPTUI 2 weeks ago Migrate-Packages 2 weeks ago Roles 2 weeks ago Templates 2 weeks ago Advanced-Content-Types.php 2 weeks ago Advanced-Relationships.php 2 weeks ago Markdown.php 2 weeks ago Pages.php 2 weeks ago Table-Storage.php 2 weeks ago
Markdown.php
2109 lines
1 <?php
2 /**
3 * ID: markdown-syntax
4 *
5 * Name: Markdown Syntax
6 *
7 * Description: Integration with Markdown (http://michelf.com/projects/php-markdown/); Adds an option to enable Markdown syntax for Paragraph text fields.
8 *
9 * Version: 1.0
10 *
11 * Category: Field Types
12 *
13 * @package Pods\Components
14 * @subpackage Markdown
15 */
16
17 if ( ! function_exists( 'Markdown' ) ) :
18 //
19 // Markdown - A text-to-HTML conversion tool for web writers
20 //
21 // PHP Markdown
22 // Copyright (c) 2004-2013 Michel Fortin
23 // <http://michelf.ca/projects/php-markdown/>
24 //
25 // Original Markdown
26 // Copyright (c) 2004-2006 John Gruber
27 // <http://daringfireball.net/projects/markdown/>
28 //
29 define( 'MARKDOWN_VERSION', '1.0.2' );
30 // 29 Nov 2013
31 //
32 // Global default settings:
33 //
34 // Change to ">" for HTML output
35 @define( 'MARKDOWN_EMPTY_ELEMENT_SUFFIX', ' />' );
36
37 // Define the width of a tab for code blocks.
38 @define( 'MARKDOWN_TAB_WIDTH', 4 );
39
40 //
41 // WordPress settings:
42 //
43 // Change to false to remove Markdown from posts and/or comments.
44 @define( 'MARKDOWN_WP_POSTS', true );
45 @define( 'MARKDOWN_WP_COMMENTS', true );
46
47 // Standard Function Interface ###
48 @define( 'MARKDOWN_PARSER_CLASS', 'Markdown_Parser' );
49
50 /**
51 * @param $text
52 *
53 * @return mixed
54 */
55 function Markdown( $text ) {
56
57 //
58 // Initialize the parser and return the result of its transform method.
59 //
60 // Setup static parser variable.
61 static $parser;
62 if ( ! isset( $parser ) ) {
63 $parser_class = MARKDOWN_PARSER_CLASS;
64 $parser = new $parser_class();
65 }
66
67 // Transform text using parser.
68 return $parser->transform( $text );
69 }
70
71 // WordPress Plugin Interface ###
72 if ( isset( $wp_version ) ) {
73 // More details about how it works here:
74 // <http://michelf.ca/weblog/2005/wordpress-text-flow-vs-markdown/>
75 // Post content and excerpts
76 // - Remove WordPress paragraph generator.
77 // - Run Markdown on excerpt, then remove all tags.
78 // - Add paragraph tag around the excerpt, but remove it for the excerpt rss.
79 if ( MARKDOWN_WP_POSTS ) {
80 remove_filter( 'the_content', 'wpautop' );
81 remove_filter( 'the_content_rss', 'wpautop' );
82 remove_filter( 'the_excerpt', 'wpautop' );
83 add_filter( 'the_content', 'Markdown', 6 );
84 add_filter( 'the_content_rss', 'Markdown', 6 );
85 add_filter( 'get_the_excerpt', 'Markdown', 6 );
86 add_filter( 'get_the_excerpt', 'trim', 7 );
87 add_filter( 'the_excerpt', 'mdwp_add_p' );
88 add_filter( 'the_excerpt_rss', 'mdwp_strip_p' );
89
90 remove_filter( 'content_save_pre', 'balanceTags', 50 );
91 remove_filter( 'excerpt_save_pre', 'balanceTags', 50 );
92 add_filter( 'the_content', 'balanceTags', 50 );
93 add_filter( 'get_the_excerpt', 'balanceTags', 9 );
94 }
95
96 // Comments
97 // - Remove WordPress paragraph generator.
98 // - Remove WordPress auto-link generator.
99 // - Scramble important tags before passing them to the kses filter.
100 // - Run Markdown on excerpt then remove paragraph tags.
101 if ( MARKDOWN_WP_COMMENTS ) {
102 remove_filter( 'comment_text', 'wpautop', 30 );
103 remove_filter( 'comment_text', 'make_clickable' );
104 add_filter( 'pre_comment_content', 'Markdown', 6 );
105 add_filter( 'pre_comment_content', 'mdwp_hide_tags', 8 );
106 add_filter( 'pre_comment_content', 'mdwp_show_tags', 12 );
107 add_filter( 'get_comment_text', 'Markdown', 6 );
108 add_filter( 'get_comment_excerpt', 'Markdown', 6 );
109 add_filter( 'get_comment_excerpt', 'mdwp_strip_p', 7 );
110
111 global $mdwp_hidden_tags, $mdwp_placeholders;
112 $mdwp_hidden_tags = explode( ' ', '<p> </p> <pre> </pre> <ol> </ol> <ul> </ul> <li> </li>' );
113 $mdwp_placeholders = explode( ' ', str_rot13( 'pEj07ZbbBZ U1kqgh4w4p pre2zmeN6K QTi31t9pre ol0MP1jzJR ' . 'ML5IjmbRol ulANi1NsGY J7zRLJqPul liA8ctl16T K9nhooUHli' ) );
114 }
115
116 /**
117 * @param $text
118 *
119 * @return mixed|string
120 */
121 function mdwp_add_p( $text ) {
122
123 if ( ! preg_match( '{^$|^<(p|ul|ol|dl|pre|blockquote)>}i', $text ) ) {
124 $text = '<p>' . $text . '</p>';
125 $text = preg_replace( '{\n{2,}}', "</p>\n\n<p>", $text );
126 }
127
128 return $text;
129 }
130
131 /**
132 * @param $t
133 *
134 * @return mixed
135 */
136 function mdwp_strip_p( $t ) {
137
138 return preg_replace( '{</?p>}i', '', $t );
139 }
140
141 /**
142 * @param $text
143 *
144 * @return mixed
145 */
146 function mdwp_hide_tags( $text ) {
147
148 global $mdwp_hidden_tags, $mdwp_placeholders;
149
150 return str_replace( $mdwp_hidden_tags, $mdwp_placeholders, $text );
151 }
152
153 /**
154 * @param $text
155 *
156 * @return mixed
157 */
158 function mdwp_show_tags( $text ) {
159
160 global $mdwp_hidden_tags, $mdwp_placeholders;
161
162 return str_replace( $mdwp_placeholders, $mdwp_hidden_tags, $text );
163 }
164 }//end if
165
166 // bBlog Plugin Info ###
167 /**
168 * @return array
169 */
170 function identify_modifier_markdown() {
171
172 return array(
173 'name' => 'markdown',
174 'type' => 'modifier',
175 'nicename' => 'Markdown',
176 'description' => 'A text-to-HTML conversion tool for web writers',
177 'authors' => 'Michel Fortin and John Gruber',
178 'licence' => 'BSD-like',
179 'version' => MARKDOWN_VERSION,
180 'help' => '<a href="http://daringfireball.net/projects/markdown/syntax">Markdown syntax</a> allows you to write using an easy-to-read, easy-to-write plain text format. Based on the original Perl version by <a href="http://daringfireball.net/">John Gruber</a>. <a href="http://michelf.ca/projects/php-markdown/">More...</a>',
181 );
182 }
183
184 // Smarty Modifier Interface ###
185 /**
186 * @param $text
187 *
188 * @return mixed
189 */
190 function smarty_modifier_markdown( $text ) {
191
192 return Markdown( $text );
193 }
194
195 // Textile Compatibility Mode ###
196 // Rename this file to "classTextile.php" and it can replace Textile everywhere.
197 if ( strcasecmp( substr( __FILE__, - 16 ), 'classTextile.php' ) == 0 ) {
198 // Try to include PHP SmartyPants. Should be in the same directory.
199 @include_once 'smartypants.php';
200
201 // Fake Textile class. It calls Markdown instead.
202
203 /**
204 * Class Textile
205 */
206 class Textile {
207
208 /**
209 * @param $text
210 * @param string $lite
211 * @param string $encode
212 *
213 * @return mixed
214 */
215 public function TextileThis( $text, $lite = '', $encode = '' ) {
216
217 if ( $lite == '' && $encode == '' ) {
218 $text = Markdown( $text );
219 }
220 if ( function_exists( 'SmartyPants' ) ) {
221 $text = SmartyPants( $text );
222 }
223
224 return $text;
225 }
226
227 // Fake restricted version: restrictions are not supported for now.
228
229 /**
230 * @param $text
231 * @param string $lite
232 * @param string $noimage
233 *
234 * @return mixed
235 */
236 public function TextileRestricted( $text, $lite = '', $noimage = '' ) {
237
238 return $this->TextileThis( $text, $lite );
239 }
240
241 // Workaround to ensure compatibility with TextPattern 4.0.3.
242
243 /**
244 * @param $text
245 *
246 * @return mixed
247 */
248 public function blockLite( $text ) {
249
250 return $text;
251 }
252 }
253 }//end if
254
255 //
256 // Markdown Parser Class
257 //
258
259 /**
260 * Class Markdown_Parser
261 */
262 class Markdown_Parser {
263
264 // Configuration Variables ###
265 // Change to ">" for HTML output.
266 public $empty_element_suffix = MARKDOWN_EMPTY_ELEMENT_SUFFIX;
267 public $tab_width = MARKDOWN_TAB_WIDTH;
268
269 // Change to `true` to disallow markup or entities.
270 public $no_markup = false;
271 public $no_entities = false;
272
273 // Predefined urls and titles for reference links and images.
274 public $predef_urls = array();
275 public $predef_titles = array();
276
277 // Parser Implementation ###
278 // Regex to match balanced [brackets].
279 // Needed to insert a maximum bracked depth while converting to PHP.
280 public $nested_brackets_depth = 6;
281 public $nested_brackets_re;
282
283 public $nested_url_parenthesis_depth = 4;
284 public $nested_url_parenthesis_re;
285
286 // Table of hash values for escaped characters:
287 public $escape_chars = '\`*_{}[]()>#+-.!';
288 public $escape_chars_re;
289
290 /**
291 * Markdown_Parser constructor.
292 */
293 public function __construct() {
294
295 //
296 // Constructor function. Initialize appropriate member variables.
297 //
298 $this->_initDetab();
299 $this->prepareItalicsAndBold();
300
301 $this->nested_brackets_re = str_repeat( '(?>[^\[\]]+|\[', $this->nested_brackets_depth ) . str_repeat( '\])*', $this->nested_brackets_depth );
302
303 $this->nested_url_parenthesis_re = str_repeat( '(?>[^()\s]+|\(', $this->nested_url_parenthesis_depth ) . str_repeat( '(?>\)))*', $this->nested_url_parenthesis_depth );
304
305 $this->escape_chars_re = '[' . preg_quote( $this->escape_chars ) . ']';
306
307 // Sort document, block, and span gamut in ascendent priority order.
308 asort( $this->document_gamut );
309 asort( $this->block_gamut );
310 asort( $this->span_gamut );
311 }
312
313 // Internal hashes used during transformation.
314 public $urls = array();
315 public $titles = array();
316 public $html_hashes = array();
317
318 // Status flag to avoid invalid nesting.
319 public $in_anchor = false;
320
321 public function setup() {
322
323 //
324 // Called before the transformation process starts to setup parser
325 // states.
326 //
327 // Clear global hashes.
328 $this->urls = $this->predef_urls;
329 $this->titles = $this->predef_titles;
330 $this->html_hashes = array();
331
332 $this->in_anchor = false;
333 }
334
335 public function teardown() {
336
337 //
338 // Called after the transformation process to clear any variable
339 // which may be taking up memory unnecessarly.
340 //
341 $this->urls = array();
342 $this->titles = array();
343 $this->html_hashes = array();
344 }
345
346 /**
347 * @param $text
348 *
349 * @return string
350 */
351 public function transform( $text ) {
352
353 //
354 // Main function. Performs some preprocessing on the input text
355 // and pass it through the document gamut.
356 //
357 $this->setup();
358
359 // Remove UTF-8 BOM and marker character in input, if present.
360 $text = preg_replace( '{^\xEF\xBB\xBF|\x1A}', '', $text );
361
362 // Standardize line endings:
363 // DOS to Unix and Mac to Unix
364 $text = preg_replace( '{\r\n?}', "\n", $text );
365
366 // Make sure $text ends with a couple of newlines:
367 $text .= "\n\n";
368
369 // Convert all tabs to spaces.
370 $text = $this->detab( $text );
371
372 // Turn block-level HTML blocks into hash entries
373 $text = $this->hashHTMLBlocks( $text );
374
375 // Strip any lines consisting only of spaces and tabs.
376 // This makes subsequent regexen easier to write, because we can
377 // match consecutive blank lines with /\n+/ instead of something
378 // contorted like /[ ]*\n+/ .
379 $text = preg_replace( '/^[ ]+$/m', '', $text );
380
381 // Run document gamut methods.
382 foreach ( $this->document_gamut as $method => $priority ) {
383 $text = $this->$method( $text );
384 }
385
386 $this->teardown();
387
388 return $text . "\n";
389 }
390
391 public $document_gamut = array(
392 // Strip link definitions, store in hashes.
393 'stripLinkDefinitions' => 20,
394
395 'runBasicBlockGamut' => 30,
396 );
397
398 /**
399 * @param $text
400 *
401 * @return mixed
402 */
403 public function stripLinkDefinitions( $text ) {
404
405 //
406 // Strips link definitions from text, stores the URLs and titles in
407 // hash references.
408 //
409 $less_than_tab = $this->tab_width - 1;
410
411 // Link defs are in the form: ^[id]: url "optional title"
412 $text = preg_replace_callback( '{
413 ^[ ]{0,' . $less_than_tab . '}\[(.+)\][ ]?: # id = $1
414 [ ]*
415 \n? # maybe *one* newline
416 [ ]*
417 (?:
418 <(.+?)> # url = $2
419 |
420 (\S+?) # url = $3
421 )
422 [ ]*
423 \n? # maybe one newline
424 [ ]*
425 (?:
426 (?<=\s) # lookbehind for whitespace
427 ["(]
428 (.*?) # title = $4
429 [")]
430 [ ]*
431 )? # title is optional
432 (?:\n+|\Z)
433 }xm', array( &$this, '_stripLinkDefinitions_callback' ), $text );
434
435 return $text;
436 }
437
438 /**
439 * @param $matches
440 *
441 * @return string
442 */
443 public function _stripLinkDefinitions_callback( $matches ) {
444
445 $link_id = strtolower( $matches[1] );
446 $url = $matches[2] == '' ? $matches[3] : $matches[2];
447 $this->urls[ $link_id ] = $url;
448 $this->titles[ $link_id ] =& $matches[4];
449
450 return '';
451 // String that will replace the block
452 }
453
454 /**
455 * @param $text
456 *
457 * @return mixed
458 */
459 public function hashHTMLBlocks( $text ) {
460
461 if ( $this->no_markup ) {
462 return $text;
463 }
464
465 $less_than_tab = $this->tab_width - 1;
466
467 // Hashify HTML blocks:
468 // We only want to do this for block-level HTML tags, such as headers,
469 // lists, and tables. That's because we still want to wrap <p>s around
470 // "paragraphs" that are wrapped in non-block-level tags, such as anchors,
471 // phrase emphasis, and spans. The list of tags we're looking for is
472 // hard-coded:
473 //
474 // * List "a" is made of tags which can be both inline or block-level.
475 // These will be treated block-level when the start tag is alone on
476 // its line, otherwise they're not matched here and will be taken as
477 // inline later.
478 // * List "b" is made of tags which are always block-level;
479 //
480 $block_tags_a_re = 'ins|del';
481 $block_tags_b_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|' . 'script|noscript|form|fieldset|iframe|math|svg|' . 'article|section|nav|aside|hgroup|header|footer|' . 'figure';
482
483 // Regular expression for the content of a block tag.
484 $nested_tags_level = 4;
485 $attr = '
486 (?> # optional tag attributes
487 \s # starts with whitespace
488 (?>
489 [^>"/]+ # text outside quotes
490 |
491 /+(?!>) # slash not followed by ">"
492 |
493 "[^"]*" # text inside double quotes (tolerate ">")
494 |
495 \'[^\']*\' # text inside single quotes (tolerate ">")
496 )*
497 )?
498 ';
499 $content = str_repeat( '
500 (?>
501 [^<]+ # content without tag
502 |
503 <\2 # nested opening tag
504 ' . $attr . ' # attributes
505 (?>
506 />
507 |
508 >', $nested_tags_level ) . // end of opening tag
509 '.*?' . // last level nested tag content
510 str_repeat( '
511 </\2\s*> # closing nested tag
512 )
513 |
514 <(?!/\2\s*> # other tags with a different name
515 )
516 )*', $nested_tags_level );
517 $content2 = str_replace( '\2', '\3', $content );
518
519 // First, look for nested blocks, e.g.:
520 // <div>
521 // <div>
522 // tags for inner block must be indented.
523 // </div>
524 // </div>
525 //
526 // The outermost tags must start at the left margin for this to match, and
527 // the inner nested divs must be indented.
528 // We need to do this before the next, more liberal match, because the next
529 // match will start at the first `<div>` and stop at the first `</div>`.
530 $text = preg_replace_callback( '{(?>
531 (?>
532 (?<=\n\n) # Starting after a blank line
533 | # or
534 \A\n? # the beginning of the doc
535 )
536 ( # save in $1
537
538 # Match from `\n<tag>` to `</tag>\n`, handling nested tags
539 # in between.
540
541 [ ]{0,' . $less_than_tab . '}
542 <(' . $block_tags_b_re . ')# start tag = $2
543 ' . $attr . '> # attributes followed by > and \n
544 ' . $content . ' # content, support nesting
545 </\2> # the matching end tag
546 [ ]* # trailing spaces/tabs
547 (?=\n+|\Z) # followed by a newline or end of document
548
549 | # Special version for tags of group a.
550
551 [ ]{0,' . $less_than_tab . '}
552 <(' . $block_tags_a_re . ')# start tag = $3
553 ' . $attr . '>[ ]*\n # attributes followed by >
554 ' . $content2 . ' # content, support nesting
555 </\3> # the matching end tag
556 [ ]* # trailing spaces/tabs
557 (?=\n+|\Z) # followed by a newline or end of document
558
559 | # Special case just for <hr />. It was easier to make a special
560 # case than to make the other regex more complicated.
561
562 [ ]{0,' . $less_than_tab . '}
563 <(hr) # start tag = $2
564 ' . $attr . ' # attributes
565 /?> # the matching end tag
566 [ ]*
567 (?=\n{2,}|\Z) # followed by a blank line or end of document
568
569 | # Special case for standalone HTML comments:
570
571 [ ]{0,' . $less_than_tab . '}
572 (?s:
573 <!-- .*? -->
574 )
575 [ ]*
576 (?=\n{2,}|\Z) # followed by a blank line or end of document
577
578 | # PHP and ASP-style processor instructions (<? and <%)
579
580 [ ]{0,' . $less_than_tab . '}
581 (?s:
582 <([?%]) # $2
583 .*?
584 \2>
585 )
586 [ ]*
587 (?=\n{2,}|\Z) # followed by a blank line or end of document
588
589 )
590 )}Sxmi', array( &$this, '_hashHTMLBlocks_callback' ), $text );
591
592 return $text;
593 }
594
595 /**
596 * @param $matches
597 *
598 * @return string
599 */
600 public function _hashHTMLBlocks_callback( $matches ) {
601
602 $text = $matches[1];
603 $key = $this->hashBlock( $text );
604
605 return "\n\n$key\n\n";
606 }
607
608 /**
609 * @param $text
610 * @param string $boundary
611 *
612 * @return string
613 */
614 public function hashPart( $text, $boundary = 'X' ) {
615
616 //
617 // Called whenever a tag must be hashed when a function insert an atomic
618 // element in the text stream. Passing $text to through this function gives
619 // a unique text-token which will be reverted back when calling unhash.
620 //
621 // The $boundary argument specify what character should be used to surround
622 // the token. By convension, "B" is used for block elements that needs not
623 // to be wrapped into paragraph tags at the end, ":" is used for elements
624 // that are word separators and "X" is used in the general case.
625 //
626 // Swap back any tag hash found in $text so we do not have to `unhash`
627 // multiple times at the end.
628 $text = $this->unhash( $text );
629
630 // Then hash the block.
631 static $i = 0;
632 $key = "$boundary\x1A" . ++ $i . $boundary;
633 $this->html_hashes[ $key ] = $text;
634
635 return $key;
636 // String that will replace the tag.
637 }
638
639 /**
640 * @param $text
641 *
642 * @return string
643 */
644 public function hashBlock( $text ) {
645
646 //
647 // Shortcut function for hashPart with block-level boundaries.
648 //
649 return $this->hashPart( $text, 'B' );
650 }
651
652 public $block_gamut = array(
653 //
654 // These are all the transformations that form block-level
655 // tags like paragraphs, headers, and list items.
656 //
657 'doHeaders' => 10,
658 'doHorizontalRules' => 20,
659
660 'doLists' => 40,
661 'doCodeBlocks' => 50,
662 'doBlockQuotes' => 60,
663 );
664
665 /**
666 * @param $text
667 *
668 * @return string
669 */
670 public function runBlockGamut( $text ) {
671
672 //
673 // Run block gamut tranformations.
674 //
675 // We need to escape raw HTML in Markdown source before doing anything
676 // else. This need to be done for each block, and not only at the
677 // begining in the Markdown function since hashed blocks can be part of
678 // list items and could have been indented. Indented blocks would have
679 // been seen as a code block in a previous pass of hashHTMLBlocks.
680 $text = $this->hashHTMLBlocks( $text );
681
682 return $this->runBasicBlockGamut( $text );
683 }
684
685 /**
686 * @param $text
687 *
688 * @return string
689 */
690 public function runBasicBlockGamut( $text ) {
691
692 //
693 // Run block gamut tranformations, without hashing HTML blocks. This is
694 // useful when HTML blocks are known to be already hashed, like in the first
695 // whole-document pass.
696 //
697 foreach ( $this->block_gamut as $method => $priority ) {
698 $text = $this->$method( $text );
699 }
700
701 // Finally form paragraph and restore hashed blocks.
702 $text = $this->formParagraphs( $text );
703
704 return $text;
705 }
706
707 /**
708 * @param $text
709 *
710 * @return mixed
711 */
712 public function doHorizontalRules( $text ) {
713
714 // Do Horizontal Rules:
715 return preg_replace( '{
716 ^[ ]{0,3} # Leading space
717 ([-*_]) # $1: First marker
718 (?> # Repeated marker group
719 [ ]{0,2} # Zero, one, or two spaces.
720 \1 # Marker character
721 ){2,} # Group repeated at least twice
722 [ ]* # Tailing spaces
723 $ # End of line.
724 }mx', "\n" . $this->hashBlock( "<hr$this->empty_element_suffix" ) . "\n", $text );
725 }
726
727 public $span_gamut = array(
728 //
729 // These are all the transformations that occur *within* block-level
730 // tags like paragraphs, headers, and list items.
731 //
732 // Process character escapes, code spans, and inline HTML
733 // in one shot.
734 'parseSpan' => - 30,
735
736 // Process anchor and image tags. Images must come first,
737 // because ![foo][f] looks like an anchor.
738 'doImages' => 10,
739 'doAnchors' => 20,
740
741 // Make links out of things like `<http://example.com/>`
742 // Must come after doAnchors, because you can use < and >
743 // delimiters in inline links like [this](<url>).
744 'doAutoLinks' => 30,
745 'encodeAmpsAndAngles' => 40,
746
747 'doItalicsAndBold' => 50,
748 'doHardBreaks' => 60,
749 );
750
751 /**
752 * @param $text
753 *
754 * @return mixed
755 */
756 public function runSpanGamut( $text ) {
757
758 //
759 // Run span gamut tranformations.
760 //
761 foreach ( $this->span_gamut as $method => $priority ) {
762 $text = $this->$method( $text );
763 }
764
765 return $text;
766 }
767
768 /**
769 * @param $text
770 *
771 * @return mixed
772 */
773 public function doHardBreaks( $text ) {
774
775 // Do hard breaks:
776 return preg_replace_callback( '/ {2,}\n/', array( &$this, '_doHardBreaks_callback' ), $text );
777 }
778
779 /**
780 * @param $matches
781 *
782 * @return string
783 */
784 public function _doHardBreaks_callback( $matches ) {
785
786 return $this->hashPart( "<br$this->empty_element_suffix\n" );
787 }
788
789 /**
790 * @param $text
791 *
792 * @return mixed
793 */
794 public function doAnchors( $text ) {
795
796 //
797 // Turn Markdown link shortcuts into XHTML <a> tags.
798 //
799 if ( $this->in_anchor ) {
800 return $text;
801 }
802 $this->in_anchor = true;
803
804 //
805 // First, handle reference-style links: [link text] [id]
806 //
807 $text = preg_replace_callback( '{
808 ( # wrap whole match in $1
809 \[
810 (' . $this->nested_brackets_re . ') # link text = $2
811 \]
812
813 [ ]? # one optional space
814 (?:\n[ ]*)? # one optional newline followed by spaces
815
816 \[
817 (.*?) # id = $3
818 \]
819 )
820 }xs', array( &$this, '_doAnchors_reference_callback' ), $text );
821
822 //
823 // Next, inline-style links: [link text](url "optional title")
824 //
825 $text = preg_replace_callback( '{
826 ( # wrap whole match in $1
827 \[
828 (' . $this->nested_brackets_re . ') # link text = $2
829 \]
830 \( # literal paren
831 [ \n]*
832 (?:
833 <(.+?)> # href = $3
834 |
835 (' . $this->nested_url_parenthesis_re . ') # href = $4
836 )
837 [ \n]*
838 ( # $5
839 ([\'"]) # quote char = $6
840 (.*?) # Title = $7
841 \6 # matching quote
842 [ \n]* # ignore any spaces/tabs between closing quote and )
843 )? # title is optional
844 \)
845 )
846 }xs', array( &$this, '_doAnchors_inline_callback' ), $text );
847
848 //
849 // Last, handle reference-style shortcuts: [link text]
850 // These must come last in case you've also got [link text][1]
851 // or [link text](/foo)
852 //
853 $text = preg_replace_callback( '{
854 ( # wrap whole match in $1
855 \[
856 ([^\[\]]+) # link text = $2; can\'t contain [ or ]
857 \]
858 )
859 }xs', array( &$this, '_doAnchors_reference_callback' ), $text );
860
861 $this->in_anchor = false;
862
863 return $text;
864 }
865
866 /**
867 * @param $matches
868 *
869 * @return string
870 */
871 public function _doAnchors_reference_callback( $matches ) {
872
873 $whole_match = $matches[1];
874 $link_text = $matches[2];
875 $link_id =& $matches[3];
876
877 if ( $link_id == '' ) {
878 // for shortcut links like [this][] or [this].
879 $link_id = $link_text;
880 }
881
882 // lower-case and turn embedded newlines into spaces
883 $link_id = strtolower( $link_id );
884 $link_id = preg_replace( '{[ ]?\n}', ' ', $link_id );
885
886 if ( isset( $this->urls[ $link_id ] ) ) {
887 $url = $this->urls[ $link_id ];
888 $url = $this->encodeAttribute( $url );
889
890 $result = "<a href=\"$url\"";
891 if ( isset( $this->titles[ $link_id ] ) ) {
892 $title = $this->titles[ $link_id ];
893 $title = $this->encodeAttribute( $title );
894 $result .= " title=\"$title\"";
895 }
896
897 $link_text = $this->runSpanGamut( $link_text );
898 $result .= ">$link_text</a>";
899 $result = $this->hashPart( $result );
900 } else {
901 $result = $whole_match;
902 }
903
904 return $result;
905 }
906
907 /**
908 * @param $matches
909 *
910 * @return string
911 */
912 public function _doAnchors_inline_callback( $matches ) {
913
914 $whole_match = $matches[1];
915 $link_text = $this->runSpanGamut( $matches[2] );
916 $url = $matches[3] == '' ? $matches[4] : $matches[3];
917 $title =& $matches[7];
918
919 $url = $this->encodeAttribute( $url );
920
921 $result = "<a href=\"$url\"";
922 if ( isset( $title ) ) {
923 $title = $this->encodeAttribute( $title );
924 $result .= " title=\"$title\"";
925 }
926
927 $link_text = $this->runSpanGamut( $link_text );
928 $result .= ">$link_text</a>";
929
930 return $this->hashPart( $result );
931 }
932
933 /**
934 * @param $text
935 *
936 * @return mixed
937 */
938 public function doImages( $text ) {
939
940 //
941 // Turn Markdown image shortcuts into <img> tags.
942 //
943 //
944 // First, handle reference-style labeled images: ![alt text][id]
945 //
946 $text = preg_replace_callback( '{
947 ( # wrap whole match in $1
948 !\[
949 (' . $this->nested_brackets_re . ') # alt text = $2
950 \]
951
952 [ ]? # one optional space
953 (?:\n[ ]*)? # one optional newline followed by spaces
954
955 \[
956 (.*?) # id = $3
957 \]
958
959 )
960 }xs', array( &$this, '_doImages_reference_callback' ), $text );
961
962 //
963 // Next, handle inline images: ![alt text](url "optional title")
964 // Don't forget: encode * and _
965 //
966 $text = preg_replace_callback( '{
967 ( # wrap whole match in $1
968 !\[
969 (' . $this->nested_brackets_re . ') # alt text = $2
970 \]
971 \s? # One optional whitespace character
972 \( # literal paren
973 [ \n]*
974 (?:
975 <(\S*)> # src url = $3
976 |
977 (' . $this->nested_url_parenthesis_re . ') # src url = $4
978 )
979 [ \n]*
980 ( # $5
981 ([\'"]) # quote char = $6
982 (.*?) # title = $7
983 \6 # matching quote
984 [ \n]*
985 )? # title is optional
986 \)
987 )
988 }xs', array( &$this, '_doImages_inline_callback' ), $text );
989
990 return $text;
991 }
992
993 /**
994 * @param $matches
995 *
996 * @return string
997 */
998 public function _doImages_reference_callback( $matches ) {
999
1000 $whole_match = $matches[1];
1001 $alt_text = $matches[2];
1002 $link_id = strtolower( $matches[3] );
1003
1004 if ( $link_id == '' ) {
1005 $link_id = strtolower( $alt_text );
1006 // for shortcut links like ![this][].
1007 }
1008
1009 $alt_text = $this->encodeAttribute( $alt_text );
1010 if ( isset( $this->urls[ $link_id ] ) ) {
1011 $url = $this->encodeAttribute( $this->urls[ $link_id ] );
1012 $result = "<img src=\"$url\" alt=\"$alt_text\"";
1013 if ( isset( $this->titles[ $link_id ] ) ) {
1014 $title = $this->titles[ $link_id ];
1015 $title = $this->encodeAttribute( $title );
1016 $result .= " title=\"$title\"";
1017 }
1018 $result .= $this->empty_element_suffix;
1019 $result = $this->hashPart( $result );
1020 } else {
1021 // If there's no such link ID, leave intact:
1022 $result = $whole_match;
1023 }
1024
1025 return $result;
1026 }
1027
1028 /**
1029 * @param $matches
1030 *
1031 * @return string
1032 */
1033 public function _doImages_inline_callback( $matches ) {
1034
1035 $whole_match = $matches[1];
1036 $alt_text = $matches[2];
1037 $url = $matches[3] == '' ? $matches[4] : $matches[3];
1038 $title =& $matches[7];
1039
1040 $alt_text = $this->encodeAttribute( $alt_text );
1041 $url = $this->encodeAttribute( $url );
1042 $result = "<img src=\"$url\" alt=\"$alt_text\"";
1043 if ( isset( $title ) ) {
1044 $title = $this->encodeAttribute( $title );
1045 $result .= " title=\"$title\"";
1046 // $title already quoted
1047 }
1048 $result .= $this->empty_element_suffix;
1049
1050 return $this->hashPart( $result );
1051 }
1052
1053 /**
1054 * @param $text
1055 *
1056 * @return mixed
1057 */
1058 public function doHeaders( $text ) {
1059
1060 // Setext-style headers:
1061 // Header 1
1062 // ========
1063 //
1064 // Header 2
1065 // --------
1066 //
1067 $text = preg_replace_callback( '{ ^(.+?)[ ]*\n(=+|-+)[ ]*\n+ }mx', array(
1068 &$this,
1069 '_doHeaders_callback_setext',
1070 ), $text );
1071
1072 // atx-style headers:
1073 // Header 1
1074 // Header 2
1075 // Header 2 with closing hashes ##
1076 // ...
1077 // Header 6
1078 //
1079 $text = preg_replace_callback( '{
1080 ^(\#{1,6}) # $1 = string of #\'s
1081 [ ]*
1082 (.+?) # $2 = Header text
1083 [ ]*
1084 \#* # optional closing #\'s (not counted)
1085 \n+
1086 }xm', array( &$this, '_doHeaders_callback_atx' ), $text );
1087
1088 return $text;
1089 }
1090
1091 /**
1092 * @param $matches
1093 *
1094 * @return string
1095 */
1096 public function _doHeaders_callback_setext( $matches ) {
1097
1098 // Terrible hack to check we haven't found an empty list item.
1099 if ( $matches[2] == '-' && preg_match( '{^-(?: |$)}', $matches[1] ) ) {
1100 return $matches[0];
1101 }
1102
1103 $level = $matches[2][0] == '=' ? 1 : 2;
1104 $block = "<h$level>" . $this->runSpanGamut( $matches[1] ) . "</h$level>";
1105
1106 return "\n" . $this->hashBlock( $block ) . "\n\n";
1107 }
1108
1109 /**
1110 * @param $matches
1111 *
1112 * @return string
1113 */
1114 public function _doHeaders_callback_atx( $matches ) {
1115
1116 $level = strlen( $matches[1] );
1117 $block = "<h$level>" . $this->runSpanGamut( $matches[2] ) . "</h$level>";
1118
1119 return "\n" . $this->hashBlock( $block ) . "\n\n";
1120 }
1121
1122 /**
1123 * @param $text
1124 *
1125 * @return mixed
1126 */
1127 public function doLists( $text ) {
1128
1129 //
1130 // Form HTML ordered (numbered) and unordered (bulleted) lists.
1131 //
1132 $less_than_tab = $this->tab_width - 1;
1133
1134 // Re-usable patterns to match list item bullets and number markers:
1135 $marker_ul_re = '[*+-]';
1136 $marker_ol_re = '\d+[\.]';
1137 $marker_any_re = "(?:$marker_ul_re|$marker_ol_re)";
1138
1139 $markers_relist = array(
1140 $marker_ul_re => $marker_ol_re,
1141 $marker_ol_re => $marker_ul_re,
1142 );
1143
1144 foreach ( $markers_relist as $marker_re => $other_marker_re ) {
1145 // Re-usable pattern to match any entirel ul or ol list:
1146 $whole_list_re = '
1147 ( # $1 = whole list
1148 ( # $2
1149 ([ ]{0,' . $less_than_tab . '}) # $3 = number of spaces
1150 (' . $marker_re . ') # $4 = first list item marker
1151 [ ]+
1152 )
1153 (?s:.+?)
1154 ( # $5
1155 \z
1156 |
1157 \n{2,}
1158 (?=\S)
1159 (?! # Negative lookahead for another list item marker
1160 [ ]*
1161 ' . $marker_re . '[ ]+
1162 )
1163 |
1164 (?= # Lookahead for another kind of list
1165 \n
1166 \3 # Must have the same indentation
1167 ' . $other_marker_re . '[ ]+
1168 )
1169 )
1170 )
1171 ';
1172 // mx
1173 // We use a different prefix before nested lists than top-level lists.
1174 // See extended comment in _ProcessListItems().
1175 if ( $this->list_level ) {
1176 $text = preg_replace_callback( '{
1177 ^
1178 ' . $whole_list_re . '
1179 }mx', array( &$this, '_doLists_callback' ), $text );
1180 } else {
1181 $text = preg_replace_callback( '{
1182 (?:(?<=\n)\n|\A\n?) # Must eat the newline
1183 ' . $whole_list_re . '
1184 }mx', array( &$this, '_doLists_callback' ), $text );
1185 }
1186 }//end foreach
1187
1188 return $text;
1189 }
1190
1191 /**
1192 * @param $matches
1193 *
1194 * @return string
1195 */
1196 public function _doLists_callback( $matches ) {
1197
1198 // Re-usable patterns to match list item bullets and number markers:
1199 $marker_ul_re = '[*+-]';
1200 $marker_ol_re = '\d+[\.]';
1201 $marker_any_re = "(?:$marker_ul_re|$marker_ol_re)";
1202
1203 $list = $matches[1];
1204 $list_type = preg_match( "/$marker_ul_re/", $matches[4] ) ? 'ul' : 'ol';
1205
1206 $marker_any_re = ( $list_type == 'ul' ? $marker_ul_re : $marker_ol_re );
1207
1208 $list .= "\n";
1209 $result = $this->processListItems( $list, $marker_any_re );
1210
1211 $result = $this->hashBlock( "<$list_type>\n" . $result . "</$list_type>" );
1212
1213 return "\n" . $result . "\n\n";
1214 }
1215
1216 public $list_level = 0;
1217
1218 /**
1219 * @param $list_str
1220 * @param $marker_any_re
1221 *
1222 * @return mixed
1223 */
1224 public function processListItems( $list_str, $marker_any_re ) {
1225
1226 //
1227 // Process the contents of a single ordered or unordered list, splitting it
1228 // into individual list items.
1229 //
1230 // The $this->list_level global keeps track of when we're inside a list.
1231 // Each time we enter a list, we increment it; when we leave a list,
1232 // we decrement. If it's zero, we're not in a list anymore.
1233 //
1234 // We do this because when we're not inside a list, we want to treat
1235 // something like this:
1236 //
1237 // I recommend upgrading to version
1238 // 8. Oops, now this line is treated
1239 // as a sub-list.
1240 //
1241 // As a single paragraph, despite the fact that the second line starts
1242 // with a digit-period-space sequence.
1243 //
1244 // Whereas when we're inside a list (or sub-list), that line will be
1245 // treated as the start of a sub-list. What a kludge, huh? This is
1246 // an aspect of Markdown's syntax that's hard to parse perfectly
1247 // without resorting to mind-reading. Perhaps the solution is to
1248 // change the syntax rules such that sub-lists must start with a
1249 // starting cardinal number; e.g. "1." or "a.".
1250 $this->list_level ++;
1251
1252 // trim trailing blank lines:
1253 $list_str = preg_replace( "/\n{2,}\\z/", "\n", $list_str );
1254
1255 $list_str = preg_replace_callback( '{
1256 (\n)? # leading line = $1
1257 (^[ ]*) # leading whitespace = $2
1258 (' . $marker_any_re . ' # list marker and space = $3
1259 (?:[ ]+|(?=\n)) # space only required if item is not empty
1260 )
1261 ((?s:.*?)) # list item text = $4
1262 (?:(\n+(?=\n))|\n) # tailing blank line = $5
1263 (?= \n* (\z | \2 (' . $marker_any_re . ') (?:[ ]+|(?=\n))))
1264 }xm', array( &$this, '_processListItems_callback' ), $list_str );
1265
1266 $this->list_level --;
1267
1268 return $list_str;
1269 }
1270
1271 /**
1272 * @param $matches
1273 *
1274 * @return string
1275 */
1276 public function _processListItems_callback( $matches ) {
1277
1278 $item = $matches[4];
1279 $leading_line =& $matches[1];
1280 $leading_space =& $matches[2];
1281 $marker_space = $matches[3];
1282 $tailing_blank_line =& $matches[5];
1283
1284 if ( $leading_line || $tailing_blank_line || preg_match( '/\n{2,}/', $item ) ) {
1285 // Replace marker with the appropriate whitespace indentation
1286 $item = $leading_space . str_repeat( ' ', strlen( $marker_space ) ) . $item;
1287 $item = $this->runBlockGamut( $this->outdent( $item ) . "\n" );
1288 } else {
1289 // Recursion for sub-lists:
1290 $item = $this->doLists( $this->outdent( $item ) );
1291 $item = preg_replace( '/\n+$/', '', $item );
1292 $item = $this->runSpanGamut( $item );
1293 }
1294
1295 return '<li>' . $item . "</li>\n";
1296 }
1297
1298 /**
1299 * @param $text
1300 *
1301 * @return mixed
1302 */
1303 public function doCodeBlocks( $text ) {
1304
1305 //
1306 // Process Markdown `<pre><code>` blocks.
1307 //
1308 $text = preg_replace_callback( '{
1309 (?:\n\n|\A\n?)
1310 ( # $1 = the code block -- one or more lines, starting with a space/tab
1311 (?>
1312 [ ]{' . $this->tab_width . '} # Lines must start with a tab or a tab-width of spaces
1313 .*\n+
1314 )+
1315 )
1316 ((?=^[ ]{0,' . $this->tab_width . '}\S)|\Z) # Lookahead for non-space at line-start, or end of doc
1317 }xm', array( &$this, '_doCodeBlocks_callback' ), $text );
1318
1319 return $text;
1320 }
1321
1322 /**
1323 * @param $matches
1324 *
1325 * @return string
1326 */
1327 public function _doCodeBlocks_callback( $matches ) {
1328
1329 $codeblock = $matches[1];
1330
1331 $codeblock = $this->outdent( $codeblock );
1332 $codeblock = htmlspecialchars( $codeblock, ENT_NOQUOTES );
1333
1334 // trim leading newlines and trailing newlines
1335 $codeblock = preg_replace( '/\A\n+|\n+\z/', '', $codeblock );
1336
1337 $codeblock = "<pre><code>$codeblock\n</code></pre>";
1338
1339 return "\n\n" . $this->hashBlock( $codeblock ) . "\n\n";
1340 }
1341
1342 /**
1343 * @param $code
1344 *
1345 * @return string
1346 */
1347 public function makeCodeSpan( $code ) {
1348
1349 //
1350 // Create a code span markup for $code. Called from handleSpanToken.
1351 //
1352 $code = htmlspecialchars( trim( $code ), ENT_NOQUOTES );
1353
1354 return $this->hashPart( "<code>$code</code>" );
1355 }
1356
1357 public $em_relist = array(
1358 '' => '(?:(?<!\*)\*(?!\*)|(?<!_)_(?!_))(?=\S|$)(?![\.,:;]\s)',
1359 '*' => '(?<=\S|^)(?<!\*)\*(?!\*)',
1360 '_' => '(?<=\S|^)(?<!_)_(?!_)',
1361 );
1362 public $strong_relist = array(
1363 '' => '(?:(?<!\*)\*\*(?!\*)|(?<!_)__(?!_))(?=\S|$)(?![\.,:;]\s)',
1364 '**' => '(?<=\S|^)(?<!\*)\*\*(?!\*)',
1365 '__' => '(?<=\S|^)(?<!_)__(?!_)',
1366 );
1367 public $em_strong_relist = array(
1368 '' => '(?:(?<!\*)\*\*\*(?!\*)|(?<!_)___(?!_))(?=\S|$)(?![\.,:;]\s)',
1369 '***' => '(?<=\S|^)(?<!\*)\*\*\*(?!\*)',
1370 '___' => '(?<=\S|^)(?<!_)___(?!_)',
1371 );
1372 public $em_strong_prepared_relist;
1373
1374 public function prepareItalicsAndBold() {
1375
1376 //
1377 // Prepare regular expressions for searching emphasis tokens in any
1378 // context.
1379 //
1380 foreach ( $this->em_relist as $em => $em_re ) {
1381 foreach ( $this->strong_relist as $strong => $strong_re ) {
1382 // Construct list of allowed token expressions.
1383 $token_relist = array();
1384 if ( isset( $this->em_strong_relist["$em$strong"] ) ) {
1385 $token_relist[] = $this->em_strong_relist["$em$strong"];
1386 }
1387 $token_relist[] = $em_re;
1388 $token_relist[] = $strong_re;
1389
1390 // Construct master expression from list.
1391 $token_re = '{(' . implode( '|', $token_relist ) . ')}';
1392 $this->em_strong_prepared_relist["$em$strong"] = $token_re;
1393 }
1394 }
1395 }
1396
1397 /**
1398 * @param $text
1399 *
1400 * @return string
1401 */
1402 public function doItalicsAndBold( $text ) {
1403
1404 $token_stack = array( '' );
1405 $text_stack = array( '' );
1406 $em = '';
1407 $strong = '';
1408 $tree_char_em = false;
1409
1410 while ( 1 ) {
1411 //
1412 // Get prepared regular expression for seraching emphasis tokens
1413 // in current context.
1414 //
1415 $token_re = $this->em_strong_prepared_relist["$em$strong"];
1416
1417 //
1418 // Each loop iteration search for the next emphasis token.
1419 // Each token is then passed to handleSpanToken.
1420 //
1421 $parts = preg_split( $token_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE );
1422 $text_stack[0] .= $parts[0];
1423 $token =& $parts[1];
1424 $text =& $parts[2];
1425
1426 if ( empty( $token ) ) {
1427 // Reached end of text span: empty stack without emitting.
1428 // any more emphasis.
1429 while ( $token_stack[0] ) {
1430 $text_stack[1] .= array_shift( $token_stack );
1431 $text_stack[0] .= array_shift( $text_stack );
1432 }
1433 break;
1434 }
1435
1436 $token_len = strlen( $token );
1437 if ( $tree_char_em ) {
1438 // Reached closing marker while inside a three-char emphasis.
1439 if ( $token_len == 3 ) {
1440 // Three-char closing marker, close em and strong.
1441 array_shift( $token_stack );
1442 $span = array_shift( $text_stack );
1443 $span = $this->runSpanGamut( $span );
1444 $span = "<strong><em>$span</em></strong>";
1445 $text_stack[0] .= $this->hashPart( $span );
1446 $em = '';
1447 $strong = '';
1448 } else {
1449 // Other closing marker: close one em or strong and
1450 // change current token state to match the other
1451 $token_stack[0] = str_repeat( $token[0], 3 - $token_len );
1452 $tag = $token_len == 2 ? 'strong' : 'em';
1453 $span = $text_stack[0];
1454 $span = $this->runSpanGamut( $span );
1455 $span = "<$tag>$span</$tag>";
1456 $text_stack[0] = $this->hashPart( $span );
1457 $$tag = '';
1458 // $$tag stands for $em or $strong
1459 }//end if
1460 $tree_char_em = false;
1461 } elseif ( $token_len == 3 ) {
1462 if ( $em ) {
1463 // Reached closing marker for both em and strong.
1464 // Closing strong marker:
1465 for ( $i = 0; $i < 2; ++ $i ) {
1466 $shifted_token = array_shift( $token_stack );
1467 $tag = strlen( $shifted_token ) == 2 ? 'strong' : 'em';
1468 $span = array_shift( $text_stack );
1469 $span = $this->runSpanGamut( $span );
1470 $span = "<$tag>$span</$tag>";
1471 $text_stack[0] .= $this->hashPart( $span );
1472 $$tag = '';
1473 // $$tag stands for $em or $strong
1474 }
1475 } else {
1476 // Reached opening three-char emphasis marker. Push on token
1477 // stack; will be handled by the special condition above.
1478 $em = $token[0];
1479 $strong = "$em$em";
1480 array_unshift( $token_stack, $token );
1481 array_unshift( $text_stack, '' );
1482 $tree_char_em = true;
1483 }//end if
1484 } elseif ( $token_len == 2 ) {
1485 if ( $strong ) {
1486 // Unwind any dangling emphasis marker:
1487 if ( strlen( $token_stack[0] ) == 1 ) {
1488 $text_stack[1] .= array_shift( $token_stack );
1489 $text_stack[0] .= array_shift( $text_stack );
1490 }
1491 // Closing strong marker:
1492 array_shift( $token_stack );
1493 $span = array_shift( $text_stack );
1494 $span = $this->runSpanGamut( $span );
1495 $span = "<strong>$span</strong>";
1496 $text_stack[0] .= $this->hashPart( $span );
1497 $strong = '';
1498 } else {
1499 array_unshift( $token_stack, $token );
1500 array_unshift( $text_stack, '' );
1501 $strong = $token;
1502 }
1503 } else {
1504 // Here $token_len == 1
1505 if ( $em ) {
1506 if ( strlen( $token_stack[0] ) == 1 ) {
1507 // Closing emphasis marker:
1508 array_shift( $token_stack );
1509 $span = array_shift( $text_stack );
1510 $span = $this->runSpanGamut( $span );
1511 $span = "<em>$span</em>";
1512 $text_stack[0] .= $this->hashPart( $span );
1513 $em = '';
1514 } else {
1515 $text_stack[0] .= $token;
1516 }
1517 } else {
1518 array_unshift( $token_stack, $token );
1519 array_unshift( $text_stack, '' );
1520 $em = $token;
1521 }
1522 }//end if
1523 }//end while
1524
1525 return $text_stack[0];
1526 }
1527
1528 /**
1529 * @param $text
1530 *
1531 * @return mixed
1532 */
1533 public function doBlockQuotes( $text ) {
1534
1535 $text = preg_replace_callback( '/
1536 ( # Wrap whole match in $1
1537 (?>
1538 ^[ ]*>[ ]? # ">" at the start of a line
1539 .+\n # rest of the first line
1540 (.+\n)* # subsequent consecutive lines
1541 \n* # blanks
1542 )+
1543 )
1544 /xm', array( &$this, '_doBlockQuotes_callback' ), $text );
1545
1546 return $text;
1547 }
1548
1549 /**
1550 * @param $matches
1551 *
1552 * @return string
1553 */
1554 public function _doBlockQuotes_callback( $matches ) {
1555
1556 $bq = $matches[1];
1557 // trim one level of quoting - trim whitespace-only lines
1558 $bq = preg_replace( '/^[ ]*>[ ]?|^[ ]+$/m', '', $bq );
1559 $bq = $this->runBlockGamut( $bq );
1560 // recurse
1561 $bq = preg_replace( '/^/m', ' ', $bq );
1562 // These leading spaces cause problem with <pre> content,
1563 // so we need to fix that:
1564 $bq = preg_replace_callback( '{(\s*<pre>.+?</pre>)}sx', array( &$this, '_doBlockQuotes_callback2' ), $bq );
1565
1566 return "\n" . $this->hashBlock( "<blockquote>\n$bq\n</blockquote>" ) . "\n\n";
1567 }
1568
1569 /**
1570 * @param $matches
1571 *
1572 * @return mixed
1573 */
1574 public function _doBlockQuotes_callback2( $matches ) {
1575
1576 $pre = $matches[1];
1577 $pre = preg_replace( '/^ /m', '', $pre );
1578
1579 return $pre;
1580 }
1581
1582 /**
1583 * @param $text
1584 *
1585 * @return string
1586 */
1587 public function formParagraphs( $text ) {
1588
1589 //
1590 // Params:
1591 // $text - string to process with html <p> tags
1592 //
1593 // Strip leading and trailing lines:
1594 $text = preg_replace( '/\A\n+|\n+\z/', '', $text );
1595
1596 $grafs = preg_split( '/\n{2,}/', $text, - 1, PREG_SPLIT_NO_EMPTY );
1597
1598 //
1599 // Wrap <p> tags and unhashify HTML blocks
1600 //
1601 foreach ( $grafs as $key => $value ) {
1602 if ( ! preg_match( '/^B\x1A[0-9]+B$/', $value ) ) {
1603 // Is a paragraph.
1604 $value = $this->runSpanGamut( $value );
1605 $value = preg_replace( '/^([ ]*)/', '<p>', $value );
1606 $value .= '</p>';
1607 $grafs[ $key ] = $this->unhash( $value );
1608 } else {
1609 // Is a block.
1610 // Modify elements of @grafs in-place...
1611 $graf = $value;
1612 $block = $this->html_hashes[ $graf ];
1613 $graf = $block;
1614 // if (preg_match('{
1615 // \A
1616 // ( # $1 = <div> tag
1617 // <div \s+
1618 // [^>]*
1619 // \b
1620 // markdown\s*=\s* ([\'"]) # $2 = attr quote char
1621 // 1
1622 // \2
1623 // [^>]*
1624 // >
1625 // )
1626 // ( # $3 = contents
1627 // .*
1628 // )
1629 // (</div>) # $4 = closing tag
1630 // \z
1631 // }xs', $block, $matches))
1632 // {
1633 // list(, $div_open, , $div_content, $div_close) = $matches;
1634 //
1635 // # We can't call Markdown(), because that resets the hash;
1636 // # that initialization code should be pulled into its own sub, though.
1637 // $div_content = $this->hashHTMLBlocks($div_content);
1638 //
1639 // # Run document gamut methods on the content.
1640 // foreach ($this->document_gamut as $method => $priority) {
1641 // $div_content = $this->$method($div_content);
1642 // }
1643 //
1644 // $div_open = preg_replace(
1645 // '{\smarkdown\s*=\s*([\'"]).+?\1}', '', $div_open);
1646 //
1647 // $graf = $div_open . "\n" . $div_content . "\n" . $div_close;
1648 // }
1649 $grafs[ $key ] = $graf;
1650 }//end if
1651 }//end foreach
1652
1653 return implode( "\n\n", $grafs );
1654 }
1655
1656 /**
1657 * @param $text
1658 *
1659 * @return mixed
1660 */
1661 public function encodeAttribute( $text ) {
1662
1663 //
1664 // Encode text for a double-quoted HTML attribute. This function
1665 // is *not* suitable for attributes enclosed in single quotes.
1666 //
1667 $text = $this->encodeAmpsAndAngles( $text );
1668 $text = str_replace( '"', '&quot;', $text );
1669
1670 return $text;
1671 }
1672
1673 /**
1674 * @param $text
1675 *
1676 * @return mixed
1677 */
1678 public function encodeAmpsAndAngles( $text ) {
1679
1680 //
1681 // Smart processing for ampersands and angle brackets that need to
1682 // be encoded. Valid character entities are left alone unless the
1683 // no-entities mode is set.
1684 //
1685 if ( $this->no_entities ) {
1686 $text = str_replace( '&', '&amp;', $text );
1687 } else {
1688 // Ampersand-encoding based entirely on Nat Irons's Amputator
1689 // MT plugin: <http://bumppo.net/projects/amputator/>
1690 $text = preg_replace( '/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/', '&amp;', $text );
1691 }
1692 // Encode remaining <'s
1693 $text = str_replace( '<', '&lt;', $text );
1694
1695 return $text;
1696 }
1697
1698 /**
1699 * @param $text
1700 *
1701 * @return mixed
1702 */
1703 public function doAutoLinks( $text ) {
1704
1705 $text = preg_replace_callback( '{<((https?|ftp|dict):[^\'">\s]+)>}i', array(
1706 &$this,
1707 '_doAutoLinks_url_callback',
1708 ), $text );
1709
1710 // Email addresses: <address@domain.foo>
1711 $text = preg_replace_callback( '{
1712 <
1713 (?:mailto:)?
1714 (
1715 (?:
1716 [-!#$%&\'*+/=?^_`.{|}~\w\x80-\xFF]+
1717 |
1718 ".*?"
1719 )
1720 \@
1721 (?:
1722 [-a-z0-9\x80-\xFF]+(\.[-a-z0-9\x80-\xFF]+)*\.[a-z]+
1723 |
1724 \[[\d.a-fA-F:]+\] # IPv4 & IPv6
1725 )
1726 )
1727 >
1728 }xi', array( &$this, '_doAutoLinks_email_callback' ), $text );
1729 $text = preg_replace_callback( '{<(tel:([^\'">\s]+))>}i', array(
1730 &$this,
1731 '_doAutoLinks_tel_callback',
1732 ), $text );
1733
1734 return $text;
1735 }
1736
1737 /**
1738 * @param $matches
1739 *
1740 * @return string
1741 */
1742 public function _doAutoLinks_tel_callback( $matches ) {
1743
1744 $url = $this->encodeAttribute( $matches[1] );
1745 $tel = $this->encodeAttribute( $matches[2] );
1746 $link = "<a href=\"$url\">$tel</a>";
1747
1748 return $this->hashPart( $link );
1749 }
1750
1751 /**
1752 * @param $matches
1753 *
1754 * @return string
1755 */
1756 public function _doAutoLinks_url_callback( $matches ) {
1757
1758 $url = $this->encodeAttribute( $matches[1] );
1759 $link = "<a href=\"$url\">$url</a>";
1760
1761 return $this->hashPart( $link );
1762 }
1763
1764 /**
1765 * @param $matches
1766 *
1767 * @return string
1768 */
1769 public function _doAutoLinks_email_callback( $matches ) {
1770
1771 $address = $matches[1];
1772 $link = $this->encodeEmailAddress( $address );
1773
1774 return $this->hashPart( $link );
1775 }
1776
1777 /**
1778 * @param $addr
1779 *
1780 * @return string
1781 */
1782 public function encodeEmailAddress( $addr ) {
1783
1784 //
1785 // Input: an email address, e.g. "foo@example.com"
1786 //
1787 // Output: the email address as a mailto link, with each character
1788 // of the address encoded as either a decimal or hex entity, in
1789 // the hopes of foiling most address harvesting spam bots. E.g.:
1790 //
1791 // <p><a href="&#109;&#x61;&#105;&#x6c;&#116;&#x6f;&#58;&#x66;o&#111;
1792 // &#x40;&#101;&#x78;&#97;&#x6d;&#112;&#x6c;&#101;&#46;&#x63;&#111;
1793 // &#x6d;">&#x66;o&#111;&#x40;&#101;&#x78;&#97;&#x6d;&#112;&#x6c;
1794 // &#101;&#46;&#x63;&#111;&#x6d;</a></p>
1795 //
1796 // Based by a filter by Matthew Wickline, posted to BBEdit-Talk.
1797 // With some optimizations by Milian Wolff.
1798 //
1799 $addr = 'mailto:' . $addr;
1800 $chars = preg_split( '/(?<!^)(?!$)/', $addr );
1801 $seed = (int) abs( crc32( $addr ) / strlen( $addr ) );
1802 // Deterministic seed.
1803 foreach ( $chars as $key => $char ) {
1804 $ord = ord( $char );
1805 // Ignore non-ascii chars.
1806 if ( $ord < 128 ) {
1807 $r = ( $seed * ( 1 + $key ) ) % 100;
1808 // Pseudo-random function.
1809 // roughly 10% raw, 45% hex, 45% dec
1810 // '@' *must* be encoded. I insist.
1811 if ( $r > 90 && $char != '@' ) { /* do nothing */
1812 } elseif ( $r < 45 ) {
1813 $chars[ $key ] = '&#x' . dechex( $ord ) . ';';
1814 } else {
1815 $chars[ $key ] = '&#' . $ord . ';';
1816 }
1817 }
1818 }
1819
1820 $addr = implode( '', $chars );
1821 $text = implode( '', array_slice( $chars, 7 ) );
1822 // text without `mailto:`
1823 $addr = "<a href=\"$addr\">$text</a>";
1824
1825 return $addr;
1826 }
1827
1828 /**
1829 * @param $str
1830 *
1831 * @return string
1832 */
1833 public function parseSpan( $str ) {
1834
1835 //
1836 // Take the string $str and parse it into tokens, hashing embeded HTML,
1837 // escaped characters and handling code spans.
1838 //
1839 $output = '';
1840
1841 $span_re = '{
1842 (
1843 \\\\' . $this->escape_chars_re . '
1844 |
1845 (?<![`\\\\])
1846 `+ # code span marker
1847 ' . ( $this->no_markup ? '' : '
1848 |
1849 <!-- .*? --> # comment
1850 |
1851 <\?.*?\?> | <%.*?%> # processing instruction
1852 |
1853 <[!$]?[-a-zA-Z0-9:_]+ # regular tags
1854 (?>
1855 \s
1856 (?>[^"\'>]+|"[^"]*"|\'[^\']*\')*
1857 )?
1858 >
1859 |
1860 <[-a-zA-Z0-9:_]+\s*/> # xml-style empty tag
1861 |
1862 </[-a-zA-Z0-9:_]+\s*> # closing tag
1863 ' ) . '
1864 )
1865 }xs';
1866
1867 while ( 1 ) {
1868 //
1869 // Each loop iteration seach for either the next tag, the next
1870 // openning code span marker, or the next escaped character.
1871 // Each token is then passed to handleSpanToken.
1872 //
1873 $parts = preg_split( $span_re, $str, 2, PREG_SPLIT_DELIM_CAPTURE );
1874
1875 // Create token from text preceding tag.
1876 if ( $parts[0] != '' ) {
1877 $output .= $parts[0];
1878 }
1879
1880 // Check if we reach the end.
1881 if ( isset( $parts[1] ) ) {
1882 $output .= $this->handleSpanToken( $parts[1], $parts[2] );
1883 $str = $parts[2];
1884 } else {
1885 break;
1886 }
1887 }//end while
1888
1889 return $output;
1890 }
1891
1892 /**
1893 * @param $token
1894 * @param $str
1895 *
1896 * @return string
1897 */
1898 public function handleSpanToken( $token, &$str ) {
1899
1900 //
1901 // Handle $token provided by parseSpan by determining its nature and
1902 // returning the corresponding value that should replace it.
1903 //
1904 switch ( $token[0] ) {
1905 case '\\':
1906 return $this->hashPart( '&#' . ord( $token[1] ) . ';' );
1907 case '`':
1908 // Search for end marker in remaining text.
1909 if ( preg_match( '/^(.*?[^`])' . preg_quote( $token ) . '(?!`)(.*)$/sm', $str, $matches ) ) {
1910 $str = $matches[2];
1911 $codespan = $this->makeCodeSpan( $matches[1] );
1912
1913 return $this->hashPart( $codespan );
1914 }
1915
1916 return $token;
1917 // return as text since no ending marker found.
1918 default:
1919 return $this->hashPart( $token );
1920 }
1921 }
1922
1923 /**
1924 * @param $text
1925 *
1926 * @return mixed
1927 */
1928 public function outdent( $text ) {
1929
1930 //
1931 // Remove one level of line-leading tabs or spaces
1932 //
1933 return preg_replace( '/^(\t|[ ]{1,' . $this->tab_width . '})/m', '', $text );
1934 }
1935
1936
1937 // String length function for detab. `_initDetab` will create a function to
1938 // hanlde UTF-8 if the default function does not exist.
1939 public $utf8_strlen = 'mb_strlen';
1940
1941 /**
1942 * @param $text
1943 *
1944 * @return mixed
1945 */
1946 public function detab( $text ) {
1947
1948 //
1949 // Replace tabs with the appropriate amount of space.
1950 //
1951 // For each line we separate the line in blocks delemited by
1952 // tab characters. Then we reconstruct every line by adding the
1953 // appropriate number of space between each blocks.
1954 $text = preg_replace_callback( '/^.*\t.*$/m', array( &$this, '_detab_callback' ), $text );
1955
1956 return $text;
1957 }
1958
1959 /**
1960 * @param $matches
1961 *
1962 * @return string
1963 */
1964 public function _detab_callback( $matches ) {
1965
1966 $line = $matches[0];
1967 $strlen = $this->utf8_strlen;
1968 // strlen function for UTF-8.
1969 // Split in blocks.
1970 $blocks = explode( "\t", $line );
1971 // Add each blocks to the line.
1972 $line = $blocks[0];
1973 unset( $blocks[0] );
1974 // Do not add first block twice.
1975 foreach ( $blocks as $block ) {
1976 // Calculate amount of space, insert spaces, insert block.
1977 $amount = $this->tab_width - $strlen( $line, 'UTF-8' ) % $this->tab_width;
1978 $line .= str_repeat( ' ', $amount ) . $block;
1979 }
1980
1981 return $line;
1982 }
1983
1984 public function _initDetab() {
1985
1986 //
1987 // Check for the availability of the function in the `utf8_strlen` property
1988 // (initially `mb_strlen`). If the function is not available, create a
1989 // function that will loosely count the number of UTF-8 characters with a
1990 // regular expression.
1991 //
1992 if ( function_exists( $this->utf8_strlen ) ) {
1993 return;
1994 }
1995 $this->utf8_strlen = create_function( '$text', 'return preg_match_all(
1996 "/[\\\\x00-\\\\xBF]|[\\\\xC0-\\\\xFF][\\\\x80-\\\\xBF]*/",
1997 $text, $m);' );
1998 }
1999
2000 /**
2001 * @param $text
2002 *
2003 * @return mixed
2004 */
2005 public function unhash( $text ) {
2006
2007 //
2008 // Swap back in all the tags hashed by _HashHTMLBlocks.
2009 //
2010 return preg_replace_callback( '/(.)\x1A[0-9]+\1/', array( &$this, '_unhash_callback' ), $text );
2011 }
2012
2013 /**
2014 * @param $matches
2015 *
2016 * @return mixed
2017 */
2018 public function _unhash_callback( $matches ) {
2019
2020 return $this->html_hashes[ $matches[0] ];
2021 }
2022
2023 }
2024
2025 /*
2026 PHP Markdown
2027 ============
2028
2029 Description
2030 -----------
2031
2032 This is a PHP translation of the original Markdown formatter written in
2033 Perl by John Gruber.
2034
2035 Markdown is a text-to-HTML filter; it translates an easy-to-read /
2036 easy-to-write structured text format into HTML. Markdown's text format
2037 is mostly similar to that of plain text email, and supports features such
2038 as headers, *emphasis*, code blocks, blockquotes, and links.
2039
2040 Markdown's syntax is designed not as a generic markup language, but
2041 specifically to serve as a front-end to (X)HTML. You can use span-level
2042 HTML tags anywhere in a Markdown document, and you can use block level
2043 HTML tags (like <div> and <table> as well).
2044
2045 For more information about Markdown's syntax, see:
2046
2047 <http://daringfireball.net/projects/markdown/>
2048
2049
2050 Bugs
2051 ----
2052
2053 To file bug reports please send email to:
2054
2055 <michel.fortin@michelf.ca>
2056
2057 Please include with your report: (1) the example input; (2) the output you
2058 expected; (3) the output Markdown actually produced.
2059
2060
2061 Version History
2062 ---------------
2063
2064 See the readme file for detailed release notes for this version.
2065
2066
2067 Copyright and License
2068 ---------------------
2069
2070 PHP Markdown
2071 Copyright (c) 2004-2013 Michel Fortin
2072 <http://michelf.ca/>
2073 All rights reserved.
2074
2075 Based on Markdown
2076 Copyright (c) 2003-2006 John Gruber
2077 <http://daringfireball.net/>
2078 All rights reserved.
2079
2080 Redistribution and use in source and binary forms, with or without
2081 modification, are permitted provided that the following conditions are
2082 met:
2083
2084 * Redistributions of source code must retain the above copyright notice,
2085 this list of conditions and the following disclaimer.
2086
2087 * Redistributions in binary form must reproduce the above copyright
2088 notice, this list of conditions and the following disclaimer in the
2089 documentation and/or other materials provided with the distribution.
2090
2091 * Neither the name "Markdown" nor the names of its contributors may
2092 be used to endorse or promote products derived from this software
2093 without specific prior written permission.
2094
2095 This software is provided by the copyright holders and contributors "as
2096 is" and any express or implied warranties, including, but not limited
2097 to, the implied warranties of merchantability and fitness for a
2098 particular purpose are disclaimed. In no event shall the copyright owner
2099 or contributors be liable for any direct, indirect, incidental, special,
2100 exemplary, or consequential damages (including, but not limited to,
2101 procurement of substitute goods or services; loss of use, data, or
2102 profits; or business interruption) however caused and on any theory of
2103 liability, whether in contract, strict liability, or tort (including
2104 negligence or otherwise) arising in any way out of the use of this
2105 software, even if advised of the possibility of such damage.
2106
2107 */
2108 endif;
2109