Completed
Pull Request — 2.x (#4569)
by Scott Kingsley
04:42
created

Markdown_Parser::processListItems()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 48
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
dl 0
loc 48
rs 9.125
c 0
b 0
f 0
eloc 10
nc 1
nop 2
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 30 and the first side effect is on line 36.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
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
8
 * Markdown syntax for Paragraph text fields.
9
 *
10
 * Version: 1.0
11
 *
12
 * Category: Field Types
13
 *
14
 * @package    Pods\Components
15
 * @subpackage Markdown
16
 */
17
18
if ( ! function_exists( 'Markdown' ) ) :
19
	//
20
	// Markdown  -  A text-to-HTML conversion tool for web writers
21
	//
22
	// PHP Markdown
23
	// Copyright (c) 2004-2013 Michel Fortin
24
	// <http://michelf.ca/projects/php-markdown/>
25
	//
26
	// Original Markdown
27
	// Copyright (c) 2004-2006 John Gruber
28
	// <http://daringfireball.net/projects/markdown/>
29
	//
30
	define( 'MARKDOWN_VERSION', '1.0.2' );
31
	// 29 Nov 2013
32
	//
33
	// Global default settings:
34
	//
35
	// Change to ">" for HTML output
36
	@define( 'MARKDOWN_EMPTY_ELEMENT_SUFFIX', ' />' );
37
38
	// Define the width of a tab for code blocks.
39
	@define( 'MARKDOWN_TAB_WIDTH', 4 );
40
41
	//
42
	// WordPress settings:
43
	//
44
	// Change to false to remove Markdown from posts and/or comments.
45
	@define( 'MARKDOWN_WP_POSTS', true );
46
	@define( 'MARKDOWN_WP_COMMENTS', true );
47
48
	// Standard Function Interface ###
49
	@define( 'MARKDOWN_PARSER_CLASS', 'Markdown_Parser' );
50
51
	/**
52
	 * @param $text
53
	 *
54
	 * @return mixed
55
	 */
56
	function Markdown( $text ) {
57
58
		//
59
		// Initialize the parser and return the result of its transform method.
60
		//
61
		// Setup static parser variable.
62
		static $parser;
63
		if ( ! isset( $parser ) ) {
64
			$parser_class = MARKDOWN_PARSER_CLASS;
65
			$parser       = new $parser_class();
66
		}
67
68
		// Transform text using parser.
69
		return $parser->transform( $text );
70
	}
71
72
	// WordPress Plugin Interface ###
73
	if ( isset( $wp_version ) ) {
74
		// More details about how it works here:
75
		// <http://michelf.ca/weblog/2005/wordpress-text-flow-vs-markdown/>
76
		// Post content and excerpts
77
		// - Remove WordPress paragraph generator.
78
		// - Run Markdown on excerpt, then remove all tags.
79
		// - Add paragraph tag around the excerpt, but remove it for the excerpt rss.
80
		if ( MARKDOWN_WP_POSTS ) {
81
			remove_filter( 'the_content', 'wpautop' );
82
			remove_filter( 'the_content_rss', 'wpautop' );
83
			remove_filter( 'the_excerpt', 'wpautop' );
84
			add_filter( 'the_content', 'Markdown', 6 );
85
			add_filter( 'the_content_rss', 'Markdown', 6 );
86
			add_filter( 'get_the_excerpt', 'Markdown', 6 );
87
			add_filter( 'get_the_excerpt', 'trim', 7 );
88
			add_filter( 'the_excerpt', 'mdwp_add_p' );
89
			add_filter( 'the_excerpt_rss', 'mdwp_strip_p' );
90
91
			remove_filter( 'content_save_pre', 'balanceTags', 50 );
92
			remove_filter( 'excerpt_save_pre', 'balanceTags', 50 );
93
			add_filter( 'the_content', 'balanceTags', 50 );
94
			add_filter( 'get_the_excerpt', 'balanceTags', 9 );
95
		}
96
97
		// Comments
98
		// - Remove WordPress paragraph generator.
99
		// - Remove WordPress auto-link generator.
100
		// - Scramble important tags before passing them to the kses filter.
101
		// - Run Markdown on excerpt then remove paragraph tags.
102
		if ( MARKDOWN_WP_COMMENTS ) {
103
			remove_filter( 'comment_text', 'wpautop', 30 );
104
			remove_filter( 'comment_text', 'make_clickable' );
105
			add_filter( 'pre_comment_content', 'Markdown', 6 );
106
			add_filter( 'pre_comment_content', 'mdwp_hide_tags', 8 );
107
			add_filter( 'pre_comment_content', 'mdwp_show_tags', 12 );
108
			add_filter( 'get_comment_text', 'Markdown', 6 );
109
			add_filter( 'get_comment_excerpt', 'Markdown', 6 );
110
			add_filter( 'get_comment_excerpt', 'mdwp_strip_p', 7 );
111
112
			global $mdwp_hidden_tags, $mdwp_placeholders;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
113
			$mdwp_hidden_tags  = explode( ' ', '<p> </p> <pre> </pre> <ol> </ol> <ul> </ul> <li> </li>' );
114
			$mdwp_placeholders = explode( ' ', str_rot13( 'pEj07ZbbBZ U1kqgh4w4p pre2zmeN6K QTi31t9pre ol0MP1jzJR ' . 'ML5IjmbRol ulANi1NsGY J7zRLJqPul liA8ctl16T K9nhooUHli' ) );
115
		}
116
117
		/**
118
		 * @param $text
119
		 *
120
		 * @return mixed|string
121
		 */
122
		function mdwp_add_p( $text ) {
123
124
			if ( ! preg_match( '{^$|^<(p|ul|ol|dl|pre|blockquote)>}i', $text ) ) {
125
				$text = '<p>' . $text . '</p>';
126
				$text = preg_replace( '{\n{2,}}', "</p>\n\n<p>", $text );
127
			}
128
129
			return $text;
130
		}
131
132
		/**
133
		 * @param $t
134
		 *
135
		 * @return mixed
136
		 */
137
		function mdwp_strip_p( $t ) {
138
139
			return preg_replace( '{</?p>}i', '', $t );
140
		}
141
142
		/**
143
		 * @param $text
144
		 *
145
		 * @return mixed
146
		 */
147
		function mdwp_hide_tags( $text ) {
148
149
			global $mdwp_hidden_tags, $mdwp_placeholders;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
150
151
			return str_replace( $mdwp_hidden_tags, $mdwp_placeholders, $text );
152
		}
153
154
		/**
155
		 * @param $text
156
		 *
157
		 * @return mixed
158
		 */
159
		function mdwp_show_tags( $text ) {
160
161
			global $mdwp_hidden_tags, $mdwp_placeholders;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
162
163
			return str_replace( $mdwp_placeholders, $mdwp_hidden_tags, $text );
164
		}
165
	}//end if
166
167
	// bBlog Plugin Info ###
168
	/**
169
	 * @return array
170
	 */
171
	function identify_modifier_markdown() {
172
173
		return array(
174
			'name'        => 'markdown',
175
			'type'        => 'modifier',
176
			'nicename'    => 'Markdown',
177
			'description' => 'A text-to-HTML conversion tool for web writers',
178
			'authors'     => 'Michel Fortin and John Gruber',
179
			'licence'     => 'BSD-like',
180
			'version'     => MARKDOWN_VERSION,
181
			'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>',
182
		);
183
	}
184
185
	// Smarty Modifier Interface ###
186
	/**
187
	 * @param $text
188
	 *
189
	 * @return mixed
190
	 */
191
	function smarty_modifier_markdown( $text ) {
192
193
		return Markdown( $text );
194
	}
195
196
	// Textile Compatibility Mode ###
197
	// Rename this file to "classTextile.php" and it can replace Textile everywhere.
198
	if ( strcasecmp( substr( __FILE__, - 16 ), 'classTextile.php' ) == 0 ) {
199
		// Try to include PHP SmartyPants. Should be in the same directory.
200
		@include_once 'smartypants.php';
201
202
		// Fake Textile class. It calls Markdown instead.
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
			 * @param        $text
230
			 * @param string $lite
231
			 * @param string $noimage
232
			 *
233
			 * @return mixed
234
			 */
235
			public function TextileRestricted( $text, $lite = '', $noimage = '' ) {
236
237
				return $this->TextileThis( $text, $lite );
238
			}
239
240
			// Workaround to ensure compatibility with TextPattern 4.0.3.
241
			/**
242
			 * @param $text
243
			 *
244
			 * @return mixed
245
			 */
246
			public function blockLite( $text ) {
247
248
				return $text;
249
			}
250
		}
251
	}//end if
252
253
	//
254
	// Markdown Parser Class
255
	//
256
	/**
257
	 * Class Markdown_Parser
258
	 */
259
	class Markdown_Parser {
260
261
		// Configuration Variables ###
262
		// Change to ">" for HTML output.
263
		public $empty_element_suffix = MARKDOWN_EMPTY_ELEMENT_SUFFIX;
264
		public $tab_width            = MARKDOWN_TAB_WIDTH;
265
266
		// Change to `true` to disallow markup or entities.
267
		public $no_markup   = false;
268
		public $no_entities = false;
269
270
		// Predefined urls and titles for reference links and images.
271
		public $predef_urls   = array();
272
		public $predef_titles = array();
273
274
		// Parser Implementation ###
275
		// Regex to match balanced [brackets].
276
		// Needed to insert a maximum bracked depth while converting to PHP.
277
		public $nested_brackets_depth = 6;
278
		public $nested_brackets_re;
279
280
		public $nested_url_parenthesis_depth = 4;
281
		public $nested_url_parenthesis_re;
282
283
		// Table of hash values for escaped characters:
284
		public $escape_chars = '\`*_{}[]()>#+-.!';
285
		public $escape_chars_re;
286
287
		/**
288
		 * Markdown_Parser constructor.
289
		 */
290
		public function __construct() {
291
292
			//
293
			// Constructor function. Initialize appropriate member variables.
294
			//
295
			$this->_initDetab();
296
			$this->prepareItalicsAndBold();
297
298
			$this->nested_brackets_re = str_repeat( '(?>[^\[\]]+|\[', $this->nested_brackets_depth ) . str_repeat( '\])*', $this->nested_brackets_depth );
299
300
			$this->nested_url_parenthesis_re = str_repeat( '(?>[^()\s]+|\(', $this->nested_url_parenthesis_depth ) . str_repeat( '(?>\)))*', $this->nested_url_parenthesis_depth );
301
302
			$this->escape_chars_re = '[' . preg_quote( $this->escape_chars ) . ']';
303
304
			// Sort document, block, and span gamut in ascendent priority order.
305
			asort( $this->document_gamut );
306
			asort( $this->block_gamut );
307
			asort( $this->span_gamut );
308
		}
309
310
		// Internal hashes used during transformation.
311
		public $urls        = array();
312
		public $titles      = array();
313
		public $html_hashes = array();
314
315
		// Status flag to avoid invalid nesting.
316
		public $in_anchor = false;
317
318
		public function setup() {
319
320
			//
321
			// Called before the transformation process starts to setup parser
322
			// states.
323
			//
324
			// Clear global hashes.
325
			$this->urls        = $this->predef_urls;
326
			$this->titles      = $this->predef_titles;
327
			$this->html_hashes = array();
328
329
			$this->in_anchor = false;
330
		}
331
332
		public function teardown() {
333
334
			//
335
			// Called after the transformation process to clear any variable
336
			// which may be taking up memory unnecessarly.
337
			//
338
			$this->urls        = array();
339
			$this->titles      = array();
340
			$this->html_hashes = array();
341
		}
342
343
		/**
344
		 * @param $text
345
		 *
346
		 * @return string
347
		 */
348
		public function transform( $text ) {
349
350
			//
351
			// Main function. Performs some preprocessing on the input text
352
			// and pass it through the document gamut.
353
			//
354
			$this->setup();
355
356
			// Remove UTF-8 BOM and marker character in input, if present.
357
			$text = preg_replace( '{^\xEF\xBB\xBF|\x1A}', '', $text );
358
359
			// Standardize line endings:
360
			// DOS to Unix and Mac to Unix
361
			$text = preg_replace( '{\r\n?}', "\n", $text );
362
363
			// Make sure $text ends with a couple of newlines:
364
			$text .= "\n\n";
365
366
			// Convert all tabs to spaces.
367
			$text = $this->detab( $text );
368
369
			// Turn block-level HTML blocks into hash entries
370
			$text = $this->hashHTMLBlocks( $text );
371
372
			// Strip any lines consisting only of spaces and tabs.
373
			// This makes subsequent regexen easier to write, because we can
374
			// match consecutive blank lines with /\n+/ instead of something
375
			// contorted like /[ ]*\n+/ .
376
			$text = preg_replace( '/^[ ]+$/m', '', $text );
377
378
			// Run document gamut methods.
379
			foreach ( $this->document_gamut as $method => $priority ) {
380
				$text = $this->$method( $text );
381
			}
382
383
			$this->teardown();
384
385
			return $text . "\n";
386
		}
387
388
		public $document_gamut = array(
389
			// Strip link definitions, store in hashes.
390
			'stripLinkDefinitions' => 20,
391
392
			'runBasicBlockGamut'   => 30,
393
		);
394
395
		/**
396
		 * @param $text
397
		 *
398
		 * @return mixed
399
		 */
400
		public function stripLinkDefinitions( $text ) {
401
402
			//
403
			// Strips link definitions from text, stores the URLs and titles in
404
			// hash references.
405
			//
406
			$less_than_tab = $this->tab_width - 1;
407
408
			// Link defs are in the form: ^[id]: url "optional title"
409
			$text = preg_replace_callback(
410
				'{
411
							^[ ]{0,' . $less_than_tab . '}\[(.+)\][ ]?:	# id = $1
412
							  [ ]*
413
							  \n?				# maybe *one* newline
414
							  [ ]*
415
							(?:
416
							  <(.+?)>			# url = $2
417
							|
418
							  (\S+?)			# url = $3
419
							)
420
							  [ ]*
421
							  \n?				# maybe one newline
422
							  [ ]*
423
							(?:
424
								(?<=\s)			# lookbehind for whitespace
425
								["(]
426
								(.*?)			# title = $4
427
								[")]
428
								[ ]*
429
							)?	# title is optional
430
							(?:\n+|\Z)
431
			}xm', array( &$this, '_stripLinkDefinitions_callback' ), $text
432
			);
433
434
			return $text;
435
		}
436
437
		/**
438
		 * @param $matches
439
		 *
440
		 * @return string
441
		 */
442
		public function _stripLinkDefinitions_callback( $matches ) {
443
444
			$link_id                  = strtolower( $matches[1] );
445
			$url                      = $matches[2] == '' ? $matches[3] : $matches[2];
446
			$this->urls[ $link_id ]   = $url;
447
			$this->titles[ $link_id ] =& $matches[4];
448
449
			return '';
450
			// String that will replace the block
451
		}
452
453
		/**
454
		 * @param $text
455
		 *
456
		 * @return mixed
457
		 */
458
		public function hashHTMLBlocks( $text ) {
459
460
			if ( $this->no_markup ) {
461
				return $text;
462
			}
463
464
			$less_than_tab = $this->tab_width - 1;
465
466
			// Hashify HTML blocks:
467
			// We only want to do this for block-level HTML tags, such as headers,
468
			// lists, and tables. That's because we still want to wrap <p>s around
469
			// "paragraphs" that are wrapped in non-block-level tags, such as anchors,
470
			// phrase emphasis, and spans. The list of tags we're looking for is
471
			// hard-coded:
472
			//
473
			// *  List "a" is made of tags which can be both inline or block-level.
474
			// These will be treated block-level when the start tag is alone on
475
			// its line, otherwise they're not matched here and will be taken as
476
			// inline later.
477
			// *  List "b" is made of tags which are always block-level;
478
			//
479
			$block_tags_a_re = 'ins|del';
480
			$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';
481
482
			// Regular expression for the content of a block tag.
483
			$nested_tags_level = 4;
484
			$attr              = '
485
			(?>				# optional tag attributes
486
			  \s			# starts with whitespace
487
			  (?>
488
				[^>"/]+		# text outside quotes
489
			  |
490
				/+(?!>)		# slash not followed by ">"
491
			  |
492
				"[^"]*"		# text inside double quotes (tolerate ">")
493
			  |
494
				\'[^\']*\'	# text inside single quotes (tolerate ">")
495
			  )*
496
			)?
497
			';
498
			$content           = str_repeat(
499
				'
500
				(?>
501
				  [^<]+			# content without tag
502
				|
503
				  <\2			# nested opening tag
504
					' . $attr . '	# attributes
505
					(?>
506
					  />
507
					|
508
					  >', $nested_tags_level
509
			) .
510
			// end of opening tag
511
								 '.*?' .
512
			// last level nested tag content
513
								 str_repeat(
514
									 '
515
					  </\2\s*>	# closing nested tag
516
					)
517
				  |
518
					<(?!/\2\s*>	# other tags with a different name
519
				  )
520
				)*', $nested_tags_level
521
								 );
522
			$content2 = str_replace( '\2', '\3', $content );
523
524
			// First, look for nested blocks, e.g.:
525
			// <div>
526
			// <div>
527
			// tags for inner block must be indented.
528
			// </div>
529
			// </div>
530
			//
531
			// The outermost tags must start at the left margin for this to match, and
532
			// the inner nested divs must be indented.
533
			// We need to do this before the next, more liberal match, because the next
534
			// match will start at the first `<div>` and stop at the first `</div>`.
535
			$text = preg_replace_callback(
536
				'{(?>
537
			(?>
538
				(?<=\n\n)		# Starting after a blank line
539
				|				# or
540
				\A\n?			# the beginning of the doc
541
			)
542
			(						# save in $1
543
544
			  # Match from `\n<tag>` to `</tag>\n`, handling nested tags
545
			  # in between.
546
547
						[ ]{0,' . $less_than_tab . '}
548
						<(' . $block_tags_b_re . ')# start tag = $2
549
						' . $attr . '>			# attributes followed by > and \n
550
						' . $content . '		# content, support nesting
551
						</\2>				# the matching end tag
552
						[ ]*				# trailing spaces/tabs
553
						(?=\n+|\Z)	# followed by a newline or end of document
554
555
			| # Special version for tags of group a.
556
557
						[ ]{0,' . $less_than_tab . '}
558
						<(' . $block_tags_a_re . ')# start tag = $3
559
						' . $attr . '>[ ]*\n	# attributes followed by >
560
						' . $content2 . '		# content, support nesting
561
						</\3>				# the matching end tag
562
						[ ]*				# trailing spaces/tabs
563
						(?=\n+|\Z)	# followed by a newline or end of document
564
565
			| # Special case just for <hr />. It was easier to make a special
566
			  # case than to make the other regex more complicated.
567
568
						[ ]{0,' . $less_than_tab . '}
569
						<(hr)				# start tag = $2
570
						' . $attr . '			# attributes
571
						/?>					# the matching end tag
572
						[ ]*
573
						(?=\n{2,}|\Z)		# followed by a blank line or end of document
574
575
			| # Special case for standalone HTML comments:
576
577
					[ ]{0,' . $less_than_tab . '}
578
					(?s:
579
						<!-- .*? -->
580
					)
581
					[ ]*
582
					(?=\n{2,}|\Z)		# followed by a blank line or end of document
583
584
			| # PHP and ASP-style processor instructions (<? and <%)
585
586
					[ ]{0,' . $less_than_tab . '}
587
					(?s:
588
						<([?%])			# $2
589
						.*?
590
						\2>
591
					)
592
					[ ]*
593
					(?=\n{2,}|\Z)		# followed by a blank line or end of document
594
595
			)
596
			)}Sxmi', array( &$this, '_hashHTMLBlocks_callback' ), $text
597
			);
598
599
			return $text;
600
		}
601
602
		/**
603
		 * @param $matches
604
		 *
605
		 * @return string
606
		 */
607
		public function _hashHTMLBlocks_callback( $matches ) {
608
609
			$text = $matches[1];
610
			$key  = $this->hashBlock( $text );
611
612
			return "\n\n$key\n\n";
613
		}
614
615
		/**
616
		 * @param        $text
617
		 * @param string $boundary
618
		 *
619
		 * @return string
620
		 */
621
		public function hashPart( $text, $boundary = 'X' ) {
622
623
			//
624
			// Called whenever a tag must be hashed when a function insert an atomic
625
			// element in the text stream. Passing $text to through this function gives
626
			// a unique text-token which will be reverted back when calling unhash.
627
			//
628
			// The $boundary argument specify what character should be used to surround
629
			// the token. By convension, "B" is used for block elements that needs not
630
			// to be wrapped into paragraph tags at the end, ":" is used for elements
631
			// that are word separators and "X" is used in the general case.
632
			//
633
			// Swap back any tag hash found in $text so we do not have to `unhash`
634
			// multiple times at the end.
635
			$text = $this->unhash( $text );
636
637
			// Then hash the block.
638
			static $i                  = 0;
639
			$key                       = "$boundary\x1A" . ++ $i . $boundary;
640
			$this->html_hashes[ $key ] = $text;
641
642
			return $key;
643
			// String that will replace the tag.
644
		}
645
646
		/**
647
		 * @param $text
648
		 *
649
		 * @return string
650
		 */
651
		public function hashBlock( $text ) {
652
653
			//
654
			// Shortcut function for hashPart with block-level boundaries.
655
			//
656
			return $this->hashPart( $text, 'B' );
657
		}
658
659
		public $block_gamut = array(
660
			//
661
			// These are all the transformations that form block-level
662
			// tags like paragraphs, headers, and list items.
663
			//
664
			'doHeaders'         => 10,
665
			'doHorizontalRules' => 20,
666
667
			'doLists'           => 40,
668
			'doCodeBlocks'      => 50,
669
			'doBlockQuotes'     => 60,
670
		);
671
672
		/**
673
		 * @param $text
674
		 *
675
		 * @return string
676
		 */
677
		public function runBlockGamut( $text ) {
678
679
			//
680
			// Run block gamut tranformations.
681
			//
682
			// We need to escape raw HTML in Markdown source before doing anything
683
			// else. This need to be done for each block, and not only at the
684
			// begining in the Markdown function since hashed blocks can be part of
685
			// list items and could have been indented. Indented blocks would have
686
			// been seen as a code block in a previous pass of hashHTMLBlocks.
687
			$text = $this->hashHTMLBlocks( $text );
688
689
			return $this->runBasicBlockGamut( $text );
690
		}
691
692
		/**
693
		 * @param $text
694
		 *
695
		 * @return string
696
		 */
697
		public function runBasicBlockGamut( $text ) {
698
699
			//
700
			// Run block gamut tranformations, without hashing HTML blocks. This is
701
			// useful when HTML blocks are known to be already hashed, like in the first
702
			// whole-document pass.
703
			//
704
			foreach ( $this->block_gamut as $method => $priority ) {
705
				$text = $this->$method( $text );
706
			}
707
708
			// Finally form paragraph and restore hashed blocks.
709
			$text = $this->formParagraphs( $text );
710
711
			return $text;
712
		}
713
714
		/**
715
		 * @param $text
716
		 *
717
		 * @return mixed
718
		 */
719
		public function doHorizontalRules( $text ) {
720
721
			// Do Horizontal Rules:
722
			return preg_replace(
723
				'{
724
				^[ ]{0,3}	# Leading space
725
				([-*_])		# $1: First marker
726
				(?>			# Repeated marker group
727
					[ ]{0,2}	# Zero, one, or two spaces.
728
					\1			# Marker character
729
				){2,}		# Group repeated at least twice
730
				[ ]*		# Tailing spaces
731
				$			# End of line.
732
			}mx', "\n" . $this->hashBlock( "<hr$this->empty_element_suffix" ) . "\n", $text
733
			);
734
		}
735
736
		public $span_gamut = array(
737
			//
738
			// These are all the transformations that occur *within* block-level
739
			// tags like paragraphs, headers, and list items.
740
			//
741
			// Process character escapes, code spans, and inline HTML
742
			// in one shot.
743
			'parseSpan'           => - 30,
744
745
			// Process anchor and image tags. Images must come first,
746
			// because ![foo][f] looks like an anchor.
747
			'doImages'            => 10,
748
			'doAnchors'           => 20,
749
750
			// Make links out of things like `<http://example.com/>`
751
			// Must come after doAnchors, because you can use < and >
752
			// delimiters in inline links like [this](<url>).
753
			'doAutoLinks'         => 30,
754
			'encodeAmpsAndAngles' => 40,
755
756
			'doItalicsAndBold'    => 50,
757
			'doHardBreaks'        => 60,
758
		);
759
760
		/**
761
		 * @param $text
762
		 *
763
		 * @return mixed
764
		 */
765
		public function runSpanGamut( $text ) {
766
767
			//
768
			// Run span gamut tranformations.
769
			//
770
			foreach ( $this->span_gamut as $method => $priority ) {
771
				$text = $this->$method( $text );
772
			}
773
774
			return $text;
775
		}
776
777
		/**
778
		 * @param $text
779
		 *
780
		 * @return mixed
781
		 */
782
		public function doHardBreaks( $text ) {
783
784
			// Do hard breaks:
785
			return preg_replace_callback( '/ {2,}\n/', array( &$this, '_doHardBreaks_callback' ), $text );
786
		}
787
788
		/**
789
		 * @param $matches
790
		 *
791
		 * @return string
792
		 */
793
		public function _doHardBreaks_callback( $matches ) {
794
795
			return $this->hashPart( "<br$this->empty_element_suffix\n" );
796
		}
797
798
		/**
799
		 * @param $text
800
		 *
801
		 * @return mixed
802
		 */
803
		public function doAnchors( $text ) {
804
805
			//
806
			// Turn Markdown link shortcuts into XHTML <a> tags.
807
			//
808
			if ( $this->in_anchor ) {
809
				return $text;
810
			}
811
			$this->in_anchor = true;
812
813
			//
814
			// First, handle reference-style links: [link text] [id]
815
			//
816
			$text = preg_replace_callback(
817
				'{
818
			(					# wrap whole match in $1
819
			  \[
820
				(' . $this->nested_brackets_re . ')	# link text = $2
821
			  \]
822
823
			  [ ]?				# one optional space
824
			  (?:\n[ ]*)?		# one optional newline followed by spaces
825
826
			  \[
827
				(.*?)		# id = $3
828
			  \]
829
			)
830
			}xs', array( &$this, '_doAnchors_reference_callback' ), $text
831
			);
832
833
			//
834
			// Next, inline-style links: [link text](url "optional title")
835
			//
836
			$text = preg_replace_callback(
837
				'{
838
			(				# wrap whole match in $1
839
			  \[
840
				(' . $this->nested_brackets_re . ')	# link text = $2
841
			  \]
842
			  \(			# literal paren
843
				[ \n]*
844
				(?:
845
					<(.+?)>	# href = $3
846
				|
847
					(' . $this->nested_url_parenthesis_re . ')	# href = $4
848
				)
849
				[ \n]*
850
				(			# $5
851
				  ([\'"])	# quote char = $6
852
				  (.*?)		# Title = $7
853
				  \6		# matching quote
854
				  [ \n]*	# ignore any spaces/tabs between closing quote and )
855
				)?			# title is optional
856
			  \)
857
			)
858
			}xs', array( &$this, '_doAnchors_inline_callback' ), $text
859
			);
860
861
			//
862
			// Last, handle reference-style shortcuts: [link text]
863
			// These must come last in case you've also got [link text][1]
864
			// or [link text](/foo)
0 ignored issues
show
Unused Code Comprehensibility introduced by
42% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
865
			//
866
			$text = preg_replace_callback(
867
				'{
868
			(					# wrap whole match in $1
869
			  \[
870
				([^\[\]]+)		# link text = $2; can\'t contain [ or ]
871
			  \]
872
			)
873
			}xs', array( &$this, '_doAnchors_reference_callback' ), $text
874
			);
875
876
			$this->in_anchor = false;
877
878
			return $text;
879
		}
880
881
		/**
882
		 * @param $matches
883
		 *
884
		 * @return string
885
		 */
886
		public function _doAnchors_reference_callback( $matches ) {
887
888
			$whole_match = $matches[1];
889
			$link_text   = $matches[2];
890
			$link_id     =& $matches[3];
891
892
			if ( $link_id == '' ) {
893
				// for shortcut links like [this][] or [this].
0 ignored issues
show
Unused Code Comprehensibility introduced by
39% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
894
				$link_id = $link_text;
895
			}
896
897
			// lower-case and turn embedded newlines into spaces
898
			$link_id = strtolower( $link_id );
899
			$link_id = preg_replace( '{[ ]?\n}', ' ', $link_id );
900
901
			if ( isset( $this->urls[ $link_id ] ) ) {
902
				$url = $this->urls[ $link_id ];
903
				$url = $this->encodeAttribute( $url );
904
905
				$result = "<a href=\"$url\"";
906 View Code Duplication
				if ( isset( $this->titles[ $link_id ] ) ) {
907
					$title   = $this->titles[ $link_id ];
908
					$title   = $this->encodeAttribute( $title );
909
					$result .= " title=\"$title\"";
910
				}
911
912
				$link_text = $this->runSpanGamut( $link_text );
913
				$result   .= ">$link_text</a>";
914
				$result    = $this->hashPart( $result );
915
			} else {
916
				$result = $whole_match;
917
			}
918
919
			return $result;
920
		}
921
922
		/**
923
		 * @param $matches
924
		 *
925
		 * @return string
926
		 */
927 View Code Duplication
		public function _doAnchors_inline_callback( $matches ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
928
929
			$whole_match = $matches[1];
930
			$link_text   = $this->runSpanGamut( $matches[2] );
931
			$url         = $matches[3] == '' ? $matches[4] : $matches[3];
932
			$title       =& $matches[7];
933
934
			$url = $this->encodeAttribute( $url );
935
936
			$result = "<a href=\"$url\"";
937
			if ( isset( $title ) ) {
938
				$title   = $this->encodeAttribute( $title );
939
				$result .= " title=\"$title\"";
940
			}
941
942
			$link_text = $this->runSpanGamut( $link_text );
943
			$result   .= ">$link_text</a>";
944
945
			return $this->hashPart( $result );
946
		}
947
948
		/**
949
		 * @param $text
950
		 *
951
		 * @return mixed
952
		 */
953
		public function doImages( $text ) {
954
955
			//
956
			// Turn Markdown image shortcuts into <img> tags.
957
			//
958
			//
959
			// First, handle reference-style labeled images: ![alt text][id]
960
			//
961
			$text = preg_replace_callback(
962
				'{
963
			(				# wrap whole match in $1
964
			  !\[
965
				(' . $this->nested_brackets_re . ')		# alt text = $2
966
			  \]
967
968
			  [ ]?				# one optional space
969
			  (?:\n[ ]*)?		# one optional newline followed by spaces
970
971
			  \[
972
				(.*?)		# id = $3
973
			  \]
974
975
			)
976
			}xs', array( &$this, '_doImages_reference_callback' ), $text
977
			);
978
979
			//
980
			// Next, handle inline images:  ![alt text](url "optional title")
981
			// Don't forget: encode * and _
982
			//
983
			$text = preg_replace_callback(
984
				'{
985
			(				# wrap whole match in $1
986
			  !\[
987
				(' . $this->nested_brackets_re . ')		# alt text = $2
988
			  \]
989
			  \s?			# One optional whitespace character
990
			  \(			# literal paren
991
				[ \n]*
992
				(?:
993
					<(\S*)>	# src url = $3
994
				|
995
					(' . $this->nested_url_parenthesis_re . ')	# src url = $4
996
				)
997
				[ \n]*
998
				(			# $5
999
				  ([\'"])	# quote char = $6
1000
				  (.*?)		# title = $7
1001
				  \6		# matching quote
1002
				  [ \n]*
1003
				)?			# title is optional
1004
			  \)
1005
			)
1006
			}xs', array( &$this, '_doImages_inline_callback' ), $text
1007
			);
1008
1009
			return $text;
1010
		}
1011
1012
		/**
1013
		 * @param $matches
1014
		 *
1015
		 * @return string
1016
		 */
1017
		public function _doImages_reference_callback( $matches ) {
1018
1019
			$whole_match = $matches[1];
1020
			$alt_text    = $matches[2];
1021
			$link_id     = strtolower( $matches[3] );
1022
1023
			if ( $link_id == '' ) {
1024
				$link_id = strtolower( $alt_text );
1025
				// for shortcut links like ![this][].
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1026
			}
1027
1028
			$alt_text = $this->encodeAttribute( $alt_text );
1029
			if ( isset( $this->urls[ $link_id ] ) ) {
1030
				$url    = $this->encodeAttribute( $this->urls[ $link_id ] );
1031
				$result = "<img src=\"$url\" alt=\"$alt_text\"";
1032 View Code Duplication
				if ( isset( $this->titles[ $link_id ] ) ) {
1033
					$title   = $this->titles[ $link_id ];
1034
					$title   = $this->encodeAttribute( $title );
1035
					$result .= " title=\"$title\"";
1036
				}
1037
				$result .= $this->empty_element_suffix;
1038
				$result  = $this->hashPart( $result );
1039
			} else {
1040
				// If there's no such link ID, leave intact:
1041
				$result = $whole_match;
1042
			}
1043
1044
			return $result;
1045
		}
1046
1047
		/**
1048
		 * @param $matches
1049
		 *
1050
		 * @return string
1051
		 */
1052 View Code Duplication
		public function _doImages_inline_callback( $matches ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1053
1054
			$whole_match = $matches[1];
1055
			$alt_text    = $matches[2];
1056
			$url         = $matches[3] == '' ? $matches[4] : $matches[3];
1057
			$title       =& $matches[7];
1058
1059
			$alt_text = $this->encodeAttribute( $alt_text );
1060
			$url      = $this->encodeAttribute( $url );
1061
			$result   = "<img src=\"$url\" alt=\"$alt_text\"";
1062
			if ( isset( $title ) ) {
1063
				$title   = $this->encodeAttribute( $title );
1064
				$result .= " title=\"$title\"";
1065
				// $title already quoted
1066
			}
1067
			$result .= $this->empty_element_suffix;
1068
1069
			return $this->hashPart( $result );
1070
		}
1071
1072
		/**
1073
		 * @param $text
1074
		 *
1075
		 * @return mixed
1076
		 */
1077
		public function doHeaders( $text ) {
1078
1079
			// Setext-style headers:
1080
			// Header 1
1081
			// ========
1082
			//
1083
			// Header 2
1084
			// --------
1085
			//
1086
			$text = preg_replace_callback(
1087
				'{ ^(.+?)[ ]*\n(=+|-+)[ ]*\n+ }mx', array(
1088
					&$this,
1089
					'_doHeaders_callback_setext',
1090
				), $text
1091
			);
1092
1093
			// atx-style headers:
1094
			// Header 1
1095
			// Header 2
1096
			// Header 2 with closing hashes ##
1097
			// ...
1098
			// Header 6
1099
			//
1100
			$text = preg_replace_callback(
1101
				'{
1102
				^(\#{1,6})	# $1 = string of #\'s
1103
				[ ]*
1104
				(.+?)		# $2 = Header text
1105
				[ ]*
1106
				\#*			# optional closing #\'s (not counted)
1107
				\n+
1108
			}xm', array( &$this, '_doHeaders_callback_atx' ), $text
1109
			);
1110
1111
			return $text;
1112
		}
1113
1114
		/**
1115
		 * @param $matches
1116
		 *
1117
		 * @return string
1118
		 */
1119
		public function _doHeaders_callback_setext( $matches ) {
1120
1121
			// Terrible hack to check we haven't found an empty list item.
1122
			if ( $matches[2] == '-' && preg_match( '{^-(?: |$)}', $matches[1] ) ) {
1123
				return $matches[0];
1124
			}
1125
1126
			$level = $matches[2]{0} == '=' ? 1 : 2;
1127
			$block = "<h$level>" . $this->runSpanGamut( $matches[1] ) . "</h$level>";
1128
1129
			return "\n" . $this->hashBlock( $block ) . "\n\n";
1130
		}
1131
1132
		/**
1133
		 * @param $matches
1134
		 *
1135
		 * @return string
1136
		 */
1137
		public function _doHeaders_callback_atx( $matches ) {
1138
1139
			$level = strlen( $matches[1] );
1140
			$block = "<h$level>" . $this->runSpanGamut( $matches[2] ) . "</h$level>";
1141
1142
			return "\n" . $this->hashBlock( $block ) . "\n\n";
1143
		}
1144
1145
		/**
1146
		 * @param $text
1147
		 *
1148
		 * @return mixed
1149
		 */
1150
		public function doLists( $text ) {
1151
1152
			//
1153
			// Form HTML ordered (numbered) and unordered (bulleted) lists.
1154
			//
1155
			$less_than_tab = $this->tab_width - 1;
1156
1157
			// Re-usable patterns to match list item bullets and number markers:
1158
			$marker_ul_re  = '[*+-]';
1159
			$marker_ol_re  = '\d+[\.]';
1160
			$marker_any_re = "(?:$marker_ul_re|$marker_ol_re)";
1161
1162
			$markers_relist = array(
1163
				$marker_ul_re => $marker_ol_re,
1164
				$marker_ol_re => $marker_ul_re,
1165
			);
1166
1167
			foreach ( $markers_relist as $marker_re => $other_marker_re ) {
1168
				// Re-usable pattern to match any entirel ul or ol list:
1169
				$whole_list_re = '
1170
				(								# $1 = whole list
1171
				  (								# $2
1172
					([ ]{0,' . $less_than_tab . '})	# $3 = number of spaces
1173
					(' . $marker_re . ')			# $4 = first list item marker
1174
					[ ]+
1175
				  )
1176
				  (?s:.+?)
1177
				  (								# $5
1178
					  \z
1179
					|
1180
					  \n{2,}
1181
					  (?=\S)
1182
					  (?!						# Negative lookahead for another list item marker
1183
						[ ]*
1184
						' . $marker_re . '[ ]+
1185
					  )
1186
					|
1187
					  (?=						# Lookahead for another kind of list
1188
					    \n
1189
						\3						# Must have the same indentation
1190
						' . $other_marker_re . '[ ]+
1191
					  )
1192
				  )
1193
				)
1194
			';
1195
				// mx
1196
				// We use a different prefix before nested lists than top-level lists.
1197
				// See extended comment in _ProcessListItems().
1198
				if ( $this->list_level ) {
1199
					$text = preg_replace_callback(
1200
						'{
1201
						^
1202
						' . $whole_list_re . '
1203
					}mx', array( &$this, '_doLists_callback' ), $text
1204
					);
1205
				} else {
1206
					$text = preg_replace_callback(
1207
						'{
1208
						(?:(?<=\n)\n|\A\n?) # Must eat the newline
1209
						' . $whole_list_re . '
1210
					}mx', array( &$this, '_doLists_callback' ), $text
1211
					);
1212
				}
1213
			}//end foreach
1214
1215
			return $text;
1216
		}
1217
1218
		/**
1219
		 * @param $matches
1220
		 *
1221
		 * @return string
1222
		 */
1223
		public function _doLists_callback( $matches ) {
1224
1225
			// Re-usable patterns to match list item bullets and number markers:
1226
			$marker_ul_re  = '[*+-]';
1227
			$marker_ol_re  = '\d+[\.]';
1228
			$marker_any_re = "(?:$marker_ul_re|$marker_ol_re)";
1229
1230
			$list      = $matches[1];
1231
			$list_type = preg_match( "/$marker_ul_re/", $matches[4] ) ? 'ul' : 'ol';
1232
1233
			$marker_any_re = ( $list_type == 'ul' ? $marker_ul_re : $marker_ol_re );
1234
1235
			$list  .= "\n";
1236
			$result = $this->processListItems( $list, $marker_any_re );
1237
1238
			$result = $this->hashBlock( "<$list_type>\n" . $result . "</$list_type>" );
1239
1240
			return "\n" . $result . "\n\n";
1241
		}
1242
1243
		public $list_level = 0;
1244
1245
		/**
1246
		 * @param $list_str
1247
		 * @param $marker_any_re
1248
		 *
1249
		 * @return mixed
1250
		 */
1251
		public function processListItems( $list_str, $marker_any_re ) {
1252
1253
			//
1254
			// Process the contents of a single ordered or unordered list, splitting it
1255
			// into individual list items.
1256
			//
1257
			// The $this->list_level global keeps track of when we're inside a list.
1258
			// Each time we enter a list, we increment it; when we leave a list,
1259
			// we decrement. If it's zero, we're not in a list anymore.
1260
			//
1261
			// We do this because when we're not inside a list, we want to treat
1262
			// something like this:
1263
			//
1264
			// I recommend upgrading to version
1265
			// 8. Oops, now this line is treated
1266
			// as a sub-list.
1267
			//
1268
			// As a single paragraph, despite the fact that the second line starts
1269
			// with a digit-period-space sequence.
1270
			//
1271
			// Whereas when we're inside a list (or sub-list), that line will be
1272
			// treated as the start of a sub-list. What a kludge, huh? This is
1273
			// an aspect of Markdown's syntax that's hard to parse perfectly
1274
			// without resorting to mind-reading. Perhaps the solution is to
1275
			// change the syntax rules such that sub-lists must start with a
1276
			// starting cardinal number; e.g. "1." or "a.".
1277
			$this->list_level ++;
1278
1279
			// trim trailing blank lines:
1280
			$list_str = preg_replace( "/\n{2,}\\z/", "\n", $list_str );
1281
1282
			$list_str = preg_replace_callback(
1283
				'{
1284
			(\n)?							# leading line = $1
1285
			(^[ ]*)							# leading whitespace = $2
1286
			(' . $marker_any_re . '				# list marker and space = $3
1287
				(?:[ ]+|(?=\n))	# space only required if item is not empty
1288
			)
1289
			((?s:.*?))						# list item text   = $4
1290
			(?:(\n+(?=\n))|\n)				# tailing blank line = $5
1291
			(?= \n* (\z | \2 (' . $marker_any_re . ') (?:[ ]+|(?=\n))))
1292
			}xm', array( &$this, '_processListItems_callback' ), $list_str
1293
			);
1294
1295
			$this->list_level --;
1296
1297
			return $list_str;
1298
		}
1299
1300
		/**
1301
		 * @param $matches
1302
		 *
1303
		 * @return string
1304
		 */
1305
		public function _processListItems_callback( $matches ) {
1306
1307
			$item               = $matches[4];
1308
			$leading_line       =& $matches[1];
1309
			$leading_space      =& $matches[2];
1310
			$marker_space       = $matches[3];
1311
			$tailing_blank_line =& $matches[5];
1312
1313
			if ( $leading_line || $tailing_blank_line || preg_match( '/\n{2,}/', $item ) ) {
1314
				// Replace marker with the appropriate whitespace indentation
1315
				$item = $leading_space . str_repeat( ' ', strlen( $marker_space ) ) . $item;
1316
				$item = $this->runBlockGamut( $this->outdent( $item ) . "\n" );
1317
			} else {
1318
				// Recursion for sub-lists:
1319
				$item = $this->doLists( $this->outdent( $item ) );
1320
				$item = preg_replace( '/\n+$/', '', $item );
1321
				$item = $this->runSpanGamut( $item );
1322
			}
1323
1324
			return '<li>' . $item . "</li>\n";
1325
		}
1326
1327
		/**
1328
		 * @param $text
1329
		 *
1330
		 * @return mixed
1331
		 */
1332
		public function doCodeBlocks( $text ) {
1333
1334
			//
1335
			// Process Markdown `<pre><code>` blocks.
1336
			//
1337
			$text = preg_replace_callback(
1338
				'{
1339
				(?:\n\n|\A\n?)
1340
				(	            # $1 = the code block -- one or more lines, starting with a space/tab
1341
				  (?>
1342
					[ ]{' . $this->tab_width . '}  # Lines must start with a tab or a tab-width of spaces
1343
					.*\n+
1344
				  )+
1345
				)
1346
				((?=^[ ]{0,' . $this->tab_width . '}\S)|\Z)	# Lookahead for non-space at line-start, or end of doc
1347
			}xm', array( &$this, '_doCodeBlocks_callback' ), $text
1348
			);
1349
1350
			return $text;
1351
		}
1352
1353
		/**
1354
		 * @param $matches
1355
		 *
1356
		 * @return string
1357
		 */
1358
		public function _doCodeBlocks_callback( $matches ) {
1359
1360
			$codeblock = $matches[1];
1361
1362
			$codeblock = $this->outdent( $codeblock );
1363
			$codeblock = htmlspecialchars( $codeblock, ENT_NOQUOTES );
1364
1365
			// trim leading newlines and trailing newlines
1366
			$codeblock = preg_replace( '/\A\n+|\n+\z/', '', $codeblock );
1367
1368
			$codeblock = "<pre><code>$codeblock\n</code></pre>";
1369
1370
			return "\n\n" . $this->hashBlock( $codeblock ) . "\n\n";
1371
		}
1372
1373
		/**
1374
		 * @param $code
1375
		 *
1376
		 * @return string
1377
		 */
1378
		public function makeCodeSpan( $code ) {
1379
1380
			//
1381
			// Create a code span markup for $code. Called from handleSpanToken.
1382
			//
1383
			$code = htmlspecialchars( trim( $code ), ENT_NOQUOTES );
1384
1385
			return $this->hashPart( "<code>$code</code>" );
1386
		}
1387
1388
		public $em_relist        = array(
1389
			''  => '(?:(?<!\*)\*(?!\*)|(?<!_)_(?!_))(?=\S|$)(?![\.,:;]\s)',
1390
			'*' => '(?<=\S|^)(?<!\*)\*(?!\*)',
1391
			'_' => '(?<=\S|^)(?<!_)_(?!_)',
1392
		);
1393
		public $strong_relist    = array(
1394
			''   => '(?:(?<!\*)\*\*(?!\*)|(?<!_)__(?!_))(?=\S|$)(?![\.,:;]\s)',
1395
			'**' => '(?<=\S|^)(?<!\*)\*\*(?!\*)',
1396
			'__' => '(?<=\S|^)(?<!_)__(?!_)',
1397
		);
1398
		public $em_strong_relist = array(
1399
			''    => '(?:(?<!\*)\*\*\*(?!\*)|(?<!_)___(?!_))(?=\S|$)(?![\.,:;]\s)',
1400
			'***' => '(?<=\S|^)(?<!\*)\*\*\*(?!\*)',
1401
			'___' => '(?<=\S|^)(?<!_)___(?!_)',
1402
		);
1403
		public $em_strong_prepared_relist;
1404
1405
		public function prepareItalicsAndBold() {
1406
1407
			//
1408
			// Prepare regular expressions for searching emphasis tokens in any
1409
			// context.
1410
			//
1411
			foreach ( $this->em_relist as $em => $em_re ) {
1412
				foreach ( $this->strong_relist as $strong => $strong_re ) {
1413
					// Construct list of allowed token expressions.
1414
					$token_relist = array();
1415
					if ( isset( $this->em_strong_relist[ "$em$strong" ] ) ) {
1416
						$token_relist[] = $this->em_strong_relist[ "$em$strong" ];
1417
					}
1418
					$token_relist[] = $em_re;
1419
					$token_relist[] = $strong_re;
1420
1421
					// Construct master expression from list.
1422
					$token_re                                        = '{(' . implode( '|', $token_relist ) . ')}';
1423
					$this->em_strong_prepared_relist[ "$em$strong" ] = $token_re;
1424
				}
1425
			}
1426
		}
1427
1428
		/**
1429
		 * @param $text
1430
		 *
1431
		 * @return string
1432
		 */
1433
		public function doItalicsAndBold( $text ) {
1434
1435
			$token_stack  = array( '' );
1436
			$text_stack   = array( '' );
1437
			$em           = '';
1438
			$strong       = '';
1439
			$tree_char_em = false;
1440
1441
			while ( 1 ) {
1442
				//
1443
				// Get prepared regular expression for seraching emphasis tokens
1444
				// in current context.
1445
				//
1446
				$token_re = $this->em_strong_prepared_relist[ "$em$strong" ];
1447
1448
				//
1449
				// Each loop iteration search for the next emphasis token.
1450
				// Each token is then passed to handleSpanToken.
1451
				//
1452
				$parts          = preg_split( $token_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE );
1453
				$text_stack[0] .= $parts[0];
1454
				$token          =& $parts[1];
1455
				$text           =& $parts[2];
1456
1457 View Code Duplication
				if ( empty( $token ) ) {
1458
					// Reached end of text span: empty stack without emitting.
1459
					// any more emphasis.
1460
					while ( $token_stack[0] ) {
1461
						$text_stack[1] .= array_shift( $token_stack );
1462
						$text_stack[0] .= array_shift( $text_stack );
1463
					}
1464
					break;
1465
				}
1466
1467
				$token_len = strlen( $token );
1468
				if ( $tree_char_em ) {
1469
					// Reached closing marker while inside a three-char emphasis.
1470
					if ( $token_len == 3 ) {
1471
						// Three-char closing marker, close em and strong.
1472
						array_shift( $token_stack );
1473
						$span           = array_shift( $text_stack );
1474
						$span           = $this->runSpanGamut( $span );
1475
						$span           = "<strong><em>$span</em></strong>";
1476
						$text_stack[0] .= $this->hashPart( $span );
1477
						$em             = '';
1478
						$strong         = '';
1479
					} else {
1480
						// Other closing marker: close one em or strong and
1481
						// change current token state to match the other
1482
						$token_stack[0] = str_repeat( $token{0}, 3 - $token_len );
1483
						$tag            = $token_len == 2 ? 'strong' : 'em';
1484
						$span           = $text_stack[0];
1485
						$span           = $this->runSpanGamut( $span );
1486
						$span           = "<$tag>$span</$tag>";
1487
						$text_stack[0]  = $this->hashPart( $span );
1488
						$$tag           = '';
1489
						// $$tag stands for $em or $strong
0 ignored issues
show
Unused Code Comprehensibility introduced by
47% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1490
					}//end if
1491
					$tree_char_em = false;
1492
				} elseif ( $token_len == 3 ) {
1493
					if ( $em ) {
1494
						// Reached closing marker for both em and strong.
1495
						// Closing strong marker:
1496
						for ( $i = 0; $i < 2; ++ $i ) {
1497
							$shifted_token  = array_shift( $token_stack );
1498
							$tag            = strlen( $shifted_token ) == 2 ? 'strong' : 'em';
1499
							$span           = array_shift( $text_stack );
1500
							$span           = $this->runSpanGamut( $span );
1501
							$span           = "<$tag>$span</$tag>";
1502
							$text_stack[0] .= $this->hashPart( $span );
1503
							$$tag           = '';
1504
							// $$tag stands for $em or $strong
0 ignored issues
show
Unused Code Comprehensibility introduced by
47% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1505
						}
1506
					} else {
1507
						// Reached opening three-char emphasis marker. Push on token
1508
						// stack; will be handled by the special condition above.
1509
						$em     = $token{0};
1510
						$strong = "$em$em";
1511
						array_unshift( $token_stack, $token );
1512
						array_unshift( $text_stack, '' );
1513
						$tree_char_em = true;
1514
					}//end if
1515
				} elseif ( $token_len == 2 ) {
1516
					if ( $strong ) {
1517
						// Unwind any dangling emphasis marker:
1518 View Code Duplication
						if ( strlen( $token_stack[0] ) == 1 ) {
1519
							$text_stack[1] .= array_shift( $token_stack );
1520
							$text_stack[0] .= array_shift( $text_stack );
1521
						}
1522
						// Closing strong marker:
1523
						array_shift( $token_stack );
1524
						$span           = array_shift( $text_stack );
1525
						$span           = $this->runSpanGamut( $span );
1526
						$span           = "<strong>$span</strong>";
1527
						$text_stack[0] .= $this->hashPart( $span );
1528
						$strong         = '';
1529
					} else {
1530
						array_unshift( $token_stack, $token );
1531
						array_unshift( $text_stack, '' );
1532
						$strong = $token;
1533
					}
1534
				} else {
1535
					// Here $token_len == 1
1536
					if ( $em ) {
1537
						if ( strlen( $token_stack[0] ) == 1 ) {
1538
							// Closing emphasis marker:
1539
							array_shift( $token_stack );
1540
							$span           = array_shift( $text_stack );
1541
							$span           = $this->runSpanGamut( $span );
1542
							$span           = "<em>$span</em>";
1543
							$text_stack[0] .= $this->hashPart( $span );
1544
							$em             = '';
1545
						} else {
1546
							$text_stack[0] .= $token;
1547
						}
1548
					} else {
1549
						array_unshift( $token_stack, $token );
1550
						array_unshift( $text_stack, '' );
1551
						$em = $token;
1552
					}
1553
				}//end if
1554
			}//end while
1555
1556
			return $text_stack[0];
1557
		}
1558
1559
		/**
1560
		 * @param $text
1561
		 *
1562
		 * @return mixed
1563
		 */
1564
		public function doBlockQuotes( $text ) {
1565
1566
			$text = preg_replace_callback(
1567
				'/
1568
			  (								# Wrap whole match in $1
1569
				(?>
1570
				  ^[ ]*>[ ]?			# ">" at the start of a line
1571
					.+\n					# rest of the first line
1572
				  (.+\n)*					# subsequent consecutive lines
1573
				  \n*						# blanks
1574
				)+
1575
			  )
1576
			/xm', array( &$this, '_doBlockQuotes_callback' ), $text
1577
			);
1578
1579
			return $text;
1580
		}
1581
1582
		/**
1583
		 * @param $matches
1584
		 *
1585
		 * @return string
1586
		 */
1587
		public function _doBlockQuotes_callback( $matches ) {
1588
1589
			$bq = $matches[1];
1590
			// trim one level of quoting - trim whitespace-only lines
1591
			$bq = preg_replace( '/^[ ]*>[ ]?|^[ ]+$/m', '', $bq );
1592
			$bq = $this->runBlockGamut( $bq );
1593
			// recurse
1594
			$bq = preg_replace( '/^/m', '  ', $bq );
1595
			// These leading spaces cause problem with <pre> content,
1596
			// so we need to fix that:
1597
			$bq = preg_replace_callback( '{(\s*<pre>.+?</pre>)}sx', array( &$this, '_doBlockQuotes_callback2' ), $bq );
1598
1599
			return "\n" . $this->hashBlock( "<blockquote>\n$bq\n</blockquote>" ) . "\n\n";
1600
		}
1601
1602
		/**
1603
		 * @param $matches
1604
		 *
1605
		 * @return mixed
1606
		 */
1607
		public function _doBlockQuotes_callback2( $matches ) {
1608
1609
			$pre = $matches[1];
1610
			$pre = preg_replace( '/^  /m', '', $pre );
1611
1612
			return $pre;
1613
		}
1614
1615
		/**
1616
		 * @param $text
1617
		 *
1618
		 * @return string
1619
		 */
1620
		public function formParagraphs( $text ) {
1621
1622
			//
1623
			// Params:
1624
			// $text - string to process with html <p> tags
1625
			//
1626
			// Strip leading and trailing lines:
1627
			$text = preg_replace( '/\A\n+|\n+\z/', '', $text );
1628
1629
			$grafs = preg_split( '/\n{2,}/', $text, - 1, PREG_SPLIT_NO_EMPTY );
1630
1631
			//
1632
			// Wrap <p> tags and unhashify HTML blocks
1633
			//
1634
			foreach ( $grafs as $key => $value ) {
1635
				if ( ! preg_match( '/^B\x1A[0-9]+B$/', $value ) ) {
1636
					// Is a paragraph.
1637
					$value         = $this->runSpanGamut( $value );
1638
					$value         = preg_replace( '/^([ ]*)/', '<p>', $value );
1639
					$value        .= '</p>';
1640
					$grafs[ $key ] = $this->unhash( $value );
1641
				} else {
1642
					// Is a block.
1643
					// Modify elements of @grafs in-place...
1644
					$graf  = $value;
1645
					$block = $this->html_hashes[ $graf ];
1646
					$graf  = $block;
1647
					// if (preg_match('{
1648
					// \A
1649
					// (                           # $1 = <div> tag
1650
					// <div  \s+
1651
					// [^>]*
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1652
					// \b
1653
					// markdown\s*=\s*  ([\'"])  #   $2 = attr quote char
1654
					// 1
1655
					// \2
1656
					// [^>]*
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1657
					// >
1658
					// )
1659
					// (                           # $3 = contents
1660
					// .*
1661
					// )
1662
					// (</div>)                    # $4 = closing tag
1663
					// \z
1664
					// }xs', $block, $matches))
1665
					// {
1666
					// list(, $div_open, , $div_content, $div_close) = $matches;
0 ignored issues
show
Unused Code Comprehensibility introduced by
60% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1667
					//
1668
					// # We can't call Markdown(), because that resets the hash;
1669
					// # that initialization code should be pulled into its own sub, though.
1670
					// $div_content = $this->hashHTMLBlocks($div_content);
0 ignored issues
show
Unused Code Comprehensibility introduced by
59% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1671
					//
1672
					// # Run document gamut methods on the content.
1673
					// foreach ($this->document_gamut as $method => $priority) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
56% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1674
					// $div_content = $this->$method($div_content);
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1675
					// }
1676
					//
1677
					// $div_open = preg_replace(
1678
					// '{\smarkdown\s*=\s*([\'"]).+?\1}', '', $div_open);
1679
					//
1680
					// $graf = $div_open . "\n" . $div_content . "\n" . $div_close;
1681
					// }
1682
					$grafs[ $key ] = $graf;
1683
				}//end if
1684
			}//end foreach
1685
1686
			return implode( "\n\n", $grafs );
1687
		}
1688
1689
		/**
1690
		 * @param $text
1691
		 *
1692
		 * @return mixed
1693
		 */
1694
		public function encodeAttribute( $text ) {
1695
1696
			//
1697
			// Encode text for a double-quoted HTML attribute. This function
1698
			// is *not* suitable for attributes enclosed in single quotes.
1699
			//
1700
			$text = $this->encodeAmpsAndAngles( $text );
1701
			$text = str_replace( '"', '&quot;', $text );
1702
1703
			return $text;
1704
		}
1705
1706
		/**
1707
		 * @param $text
1708
		 *
1709
		 * @return mixed
1710
		 */
1711
		public function encodeAmpsAndAngles( $text ) {
1712
1713
			//
1714
			// Smart processing for ampersands and angle brackets that need to
1715
			// be encoded. Valid character entities are left alone unless the
1716
			// no-entities mode is set.
1717
			//
1718
			if ( $this->no_entities ) {
1719
				$text = str_replace( '&', '&amp;', $text );
1720
			} else {
1721
				// Ampersand-encoding based entirely on Nat Irons's Amputator
1722
				// MT plugin: <http://bumppo.net/projects/amputator/>
1723
				$text = preg_replace( '/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/', '&amp;', $text );
1724
			}
1725
			// Encode remaining <'s
1726
			$text = str_replace( '<', '&lt;', $text );
1727
1728
			return $text;
1729
		}
1730
1731
		/**
1732
		 * @param $text
1733
		 *
1734
		 * @return mixed
1735
		 */
1736
		public function doAutoLinks( $text ) {
1737
1738
			$text = preg_replace_callback(
1739
				'{<((https?|ftp|dict):[^\'">\s]+)>}i', array(
1740
					&$this,
1741
					'_doAutoLinks_url_callback',
1742
				), $text
1743
			);
1744
1745
			// Email addresses: <[email protected]>
1746
			$text = preg_replace_callback(
1747
				'{
1748
			<
1749
			(?:mailto:)?
1750
			(
1751
				(?:
1752
					[-!#$%&\'*+/=?^_`.{|}~\w\x80-\xFF]+
1753
				|
1754
					".*?"
1755
				)
1756
				\@
1757
				(?:
1758
					[-a-z0-9\x80-\xFF]+(\.[-a-z0-9\x80-\xFF]+)*\.[a-z]+
1759
				|
1760
					\[[\d.a-fA-F:]+\]	# IPv4 & IPv6
1761
				)
1762
			)
1763
			>
1764
			}xi', array( &$this, '_doAutoLinks_email_callback' ), $text
1765
			);
1766
			$text = preg_replace_callback(
1767
				'{<(tel:([^\'">\s]+))>}i', array(
1768
					&$this,
1769
					'_doAutoLinks_tel_callback',
1770
				), $text
1771
			);
1772
1773
			return $text;
1774
		}
1775
1776
		/**
1777
		 * @param $matches
1778
		 *
1779
		 * @return string
1780
		 */
1781 View Code Duplication
		public function _doAutoLinks_tel_callback( $matches ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1782
1783
			$url  = $this->encodeAttribute( $matches[1] );
1784
			$tel  = $this->encodeAttribute( $matches[2] );
1785
			$link = "<a href=\"$url\">$tel</a>";
1786
1787
			return $this->hashPart( $link );
1788
		}
1789
1790
		/**
1791
		 * @param $matches
1792
		 *
1793
		 * @return string
1794
		 */
1795 View Code Duplication
		public function _doAutoLinks_url_callback( $matches ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1796
1797
			$url  = $this->encodeAttribute( $matches[1] );
1798
			$link = "<a href=\"$url\">$url</a>";
1799
1800
			return $this->hashPart( $link );
1801
		}
1802
1803
		/**
1804
		 * @param $matches
1805
		 *
1806
		 * @return string
1807
		 */
1808
		public function _doAutoLinks_email_callback( $matches ) {
1809
1810
			$address = $matches[1];
1811
			$link    = $this->encodeEmailAddress( $address );
1812
1813
			return $this->hashPart( $link );
1814
		}
1815
1816
		/**
1817
		 * @param $addr
1818
		 *
1819
		 * @return string
1820
		 */
1821
		public function encodeEmailAddress( $addr ) {
1822
1823
			//
1824
			// Input: an email address, e.g. "[email protected]"
1825
			//
1826
			// Output: the email address as a mailto link, with each character
1827
			// of the address encoded as either a decimal or hex entity, in
1828
			// the hopes of foiling most address harvesting spam bots. E.g.:
1829
			//
1830
			// <p><a href="&#109;&#x61;&#105;&#x6c;&#116;&#x6f;&#58;&#x66;o&#111;
1831
			// &#x40;&#101;&#x78;&#97;&#x6d;&#112;&#x6c;&#101;&#46;&#x63;&#111;
1832
			// &#x6d;">&#x66;o&#111;&#x40;&#101;&#x78;&#97;&#x6d;&#112;&#x6c;
1833
			// &#101;&#46;&#x63;&#111;&#x6d;</a></p>
1834
			//
1835
			// Based by a filter by Matthew Wickline, posted to BBEdit-Talk.
1836
			// With some optimizations by Milian Wolff.
1837
			//
1838
			$addr  = 'mailto:' . $addr;
1839
			$chars = preg_split( '/(?<!^)(?!$)/', $addr );
1840
			$seed  = (int) abs( crc32( $addr ) / strlen( $addr ) );
1841
			// Deterministic seed.
1842
			foreach ( $chars as $key => $char ) {
1843
				$ord = ord( $char );
1844
				// Ignore non-ascii chars.
1845
				if ( $ord < 128 ) {
1846
					$r = ( $seed * ( 1 + $key ) ) % 100;
1847
					// Pseudo-random function.
1848
					// roughly 10% raw, 45% hex, 45% dec
1849
					// '@' *must* be encoded. I insist.
1850
					if ( $r > 90 && $char != '@' ) { /* do nothing */
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
1851
					} elseif ( $r < 45 ) {
1852
						$chars[ $key ] = '&#x' . dechex( $ord ) . ';';
1853
					} else {
1854
						$chars[ $key ] = '&#' . $ord . ';';
1855
					}
1856
				}
1857
			}
1858
1859
			$addr = implode( '', $chars );
1860
			$text = implode( '', array_slice( $chars, 7 ) );
1861
			// text without `mailto:`
1862
			$addr = "<a href=\"$addr\">$text</a>";
1863
1864
			return $addr;
1865
		}
1866
1867
		/**
1868
		 * @param $str
1869
		 *
1870
		 * @return string
1871
		 */
1872
		public function parseSpan( $str ) {
1873
1874
			//
1875
			// Take the string $str and parse it into tokens, hashing embeded HTML,
1876
			// escaped characters and handling code spans.
1877
			//
1878
			$output = '';
1879
1880
			$span_re = '{
1881
				(
1882
					\\\\' . $this->escape_chars_re . '
1883
				|
1884
					(?<![`\\\\])
1885
					`+						# code span marker
1886
			' . ( $this->no_markup ? '' : '
1887
				|
1888
					<!--    .*?     -->		# comment
1889
				|
1890
					<\?.*?\?> | <%.*?%>		# processing instruction
1891
				|
1892
					<[!$]?[-a-zA-Z0-9:_]+	# regular tags
1893
					(?>
1894
						\s
1895
						(?>[^"\'>]+|"[^"]*"|\'[^\']*\')*
1896
					)?
1897
					>
1898
				|
1899
					<[-a-zA-Z0-9:_]+\s*/> # xml-style empty tag
1900
				|
1901
					</[-a-zA-Z0-9:_]+\s*> # closing tag
1902
			' ) . '
1903
				)
1904
				}xs';
1905
1906
			while ( 1 ) {
1907
				//
1908
				// Each loop iteration seach for either the next tag, the next
1909
				// openning code span marker, or the next escaped character.
1910
				// Each token is then passed to handleSpanToken.
1911
				//
1912
				$parts = preg_split( $span_re, $str, 2, PREG_SPLIT_DELIM_CAPTURE );
1913
1914
				// Create token from text preceding tag.
1915
				if ( $parts[0] != '' ) {
1916
					$output .= $parts[0];
1917
				}
1918
1919
				// Check if we reach the end.
1920
				if ( isset( $parts[1] ) ) {
1921
					$output .= $this->handleSpanToken( $parts[1], $parts[2] );
1922
					$str     = $parts[2];
1923
				} else {
1924
					break;
1925
				}
1926
			}//end while
1927
1928
			return $output;
1929
		}
1930
1931
		/**
1932
		 * @param $token
1933
		 * @param $str
1934
		 *
1935
		 * @return string
1936
		 */
1937
		public function handleSpanToken( $token, &$str ) {
1938
1939
			//
1940
			// Handle $token provided by parseSpan by determining its nature and
1941
			// returning the corresponding value that should replace it.
1942
			//
1943
			switch ( $token{0} ) {
1944
				case '\\':
1945
					return $this->hashPart( '&#' . ord( $token{1} ) . ';' );
1946
				case '`':
1947
					// Search for end marker in remaining text.
1948
					if ( preg_match( '/^(.*?[^`])' . preg_quote( $token ) . '(?!`)(.*)$/sm', $str, $matches ) ) {
1949
						$str      = $matches[2];
1950
						$codespan = $this->makeCodeSpan( $matches[1] );
1951
1952
						return $this->hashPart( $codespan );
1953
					}
1954
1955
					return $token;
1956
				// return as text since no ending marker found.
1957
				default:
1958
					return $this->hashPart( $token );
1959
			}
1960
		}
1961
1962
		/**
1963
		 * @param $text
1964
		 *
1965
		 * @return mixed
1966
		 */
1967
		public function outdent( $text ) {
1968
1969
			//
1970
			// Remove one level of line-leading tabs or spaces
1971
			//
1972
			return preg_replace( '/^(\t|[ ]{1,' . $this->tab_width . '})/m', '', $text );
1973
		}
1974
1975
1976
		// String length function for detab. `_initDetab` will create a function to
1977
		// hanlde UTF-8 if the default function does not exist.
1978
		public $utf8_strlen = 'mb_strlen';
1979
1980
		/**
1981
		 * @param $text
1982
		 *
1983
		 * @return mixed
1984
		 */
1985
		public function detab( $text ) {
1986
1987
			//
1988
			// Replace tabs with the appropriate amount of space.
1989
			//
1990
			// For each line we separate the line in blocks delemited by
1991
			// tab characters. Then we reconstruct every line by adding the
1992
			// appropriate number of space between each blocks.
1993
			$text = preg_replace_callback( '/^.*\t.*$/m', array( &$this, '_detab_callback' ), $text );
1994
1995
			return $text;
1996
		}
1997
1998
		/**
1999
		 * @param $matches
2000
		 *
2001
		 * @return string
2002
		 */
2003
		public function _detab_callback( $matches ) {
2004
2005
			$line   = $matches[0];
2006
			$strlen = $this->utf8_strlen;
2007
			// strlen function for UTF-8.
2008
			// Split in blocks.
2009
			$blocks = explode( "\t", $line );
2010
			// Add each blocks to the line.
2011
			$line = $blocks[0];
2012
			unset( $blocks[0] );
2013
			// Do not add first block twice.
2014
			foreach ( $blocks as $block ) {
2015
				// Calculate amount of space, insert spaces, insert block.
2016
				$amount = $this->tab_width - $strlen( $line, 'UTF-8' ) % $this->tab_width;
2017
				$line  .= str_repeat( ' ', $amount ) . $block;
2018
			}
2019
2020
			return $line;
2021
		}
2022
2023
		public function _initDetab() {
2024
2025
			//
2026
			// Check for the availability of the function in the `utf8_strlen` property
2027
			// (initially `mb_strlen`). If the function is not available, create a
2028
			// function that will loosely count the number of UTF-8 characters with a
2029
			// regular expression.
2030
			//
2031
			if ( function_exists( $this->utf8_strlen ) ) {
2032
				return;
2033
			}
2034
			$this->utf8_strlen = create_function(
0 ignored issues
show
Security Best Practice introduced by
The use of create_function is highly discouraged, better use a closure.

create_function can pose a great security vulnerability as it is similar to eval, and could be used for arbitrary code execution. We highly recommend to use a closure instead.

// Instead of
$function = create_function('$a, $b', 'return $a + $b');

// Better use
$function = function($a, $b) { return $a + $b; }
Loading history...
2035
				'$text', 'return preg_match_all(
2036
			"/[\\\\x00-\\\\xBF]|[\\\\xC0-\\\\xFF][\\\\x80-\\\\xBF]*/",
2037
			$text, $m);'
2038
			);
2039
		}
2040
2041
		/**
2042
		 * @param $text
2043
		 *
2044
		 * @return mixed
2045
		 */
2046
		public function unhash( $text ) {
2047
2048
			//
2049
			// Swap back in all the tags hashed by _HashHTMLBlocks.
2050
			//
2051
			return preg_replace_callback( '/(.)\x1A[0-9]+\1/', array( &$this, '_unhash_callback' ), $text );
2052
		}
2053
2054
		/**
2055
		 * @param $matches
2056
		 *
2057
		 * @return mixed
2058
		 */
2059
		public function _unhash_callback( $matches ) {
2060
2061
			return $this->html_hashes[ $matches[0] ];
2062
		}
2063
2064
	}
2065
2066
	/*
2067
	PHP Markdown
2068
	============
2069
2070
	Description
2071
	-----------
2072
2073
	This is a PHP translation of the original Markdown formatter written in
2074
	Perl by John Gruber.
2075
2076
	Markdown is a text-to-HTML filter; it translates an easy-to-read /
2077
	easy-to-write structured text format into HTML. Markdown's text format
2078
	is mostly similar to that of plain text email, and supports features such
2079
	as headers, *emphasis*, code blocks, blockquotes, and links.
2080
2081
	Markdown's syntax is designed not as a generic markup language, but
2082
	specifically to serve as a front-end to (X)HTML. You can use span-level
2083
	HTML tags anywhere in a Markdown document, and you can use block level
2084
	HTML tags (like <div> and <table> as well).
2085
2086
	For more information about Markdown's syntax, see:
2087
2088
	<http://daringfireball.net/projects/markdown/>
2089
2090
2091
	Bugs
2092
	----
2093
2094
	To file bug reports please send email to:
2095
2096
	<[email protected]>
2097
2098
	Please include with your report: (1) the example input; (2) the output you
2099
	expected; (3) the output Markdown actually produced.
2100
2101
2102
	Version History
2103
	---------------
2104
2105
	See the readme file for detailed release notes for this version.
2106
2107
2108
	Copyright and License
2109
	---------------------
2110
2111
	PHP Markdown
2112
	Copyright (c) 2004-2013 Michel Fortin
2113
	<http://michelf.ca/>
2114
	All rights reserved.
2115
2116
	Based on Markdown
2117
	Copyright (c) 2003-2006 John Gruber
2118
	<http://daringfireball.net/>
2119
	All rights reserved.
2120
2121
	Redistribution and use in source and binary forms, with or without
2122
	modification, are permitted provided that the following conditions are
2123
	met:
2124
2125
	*	Redistributions of source code must retain the above copyright notice,
2126
		this list of conditions and the following disclaimer.
2127
2128
	*	Redistributions in binary form must reproduce the above copyright
2129
		notice, this list of conditions and the following disclaimer in the
2130
		documentation and/or other materials provided with the distribution.
2131
2132
	*	Neither the name "Markdown" nor the names of its contributors may
2133
		be used to endorse or promote products derived from this software
2134
		without specific prior written permission.
2135
2136
	This software is provided by the copyright holders and contributors "as
2137
	is" and any express or implied warranties, including, but not limited
2138
	to, the implied warranties of merchantability and fitness for a
2139
	particular purpose are disclaimed. In no event shall the copyright owner
2140
	or contributors be liable for any direct, indirect, incidental, special,
2141
	exemplary, or consequential damages (including, but not limited to,
2142
	procurement of substitute goods or services; loss of use, data, or
2143
	profits; or business interruption) however caused and on any theory of
2144
	liability, whether in contract, strict liability, or tort (including
2145
	negligence or otherwise) arising in any way out of the use of this
2146
	software, even if advised of the possibility of such damage.
2147
2148
	*/
2149
endif;
2150