Markdown_Parser::_doHeaders_callback_setext()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 9

Duplication

Lines 2
Ratio 22.22 %

Importance

Changes 0
Metric Value
cc 4
nc 3
nop 1
dl 2
loc 9
rs 9.9666
c 0
b 0
f 0
1
<?php
2
#
3
# Markdown Extra  -  A text-to-HTML conversion tool for web writers
4
#
5
# PHP Markdown & Extra
6
# Copyright (c) 2004-2013 Michel Fortin
7
# <http://michelf.ca/projects/php-markdown/>
8
#
9
# Original Markdown
10
# Copyright (c) 2004-2006 John Gruber
11
# <http://daringfireball.net/projects/markdown/>
12
#
13
# Tweaked to remove WordPress interface
14
15
16
define( 'MARKDOWN_VERSION',  "1.0.2" ); # 29 Nov 2013
17
define( 'MARKDOWNEXTRA_VERSION',  "1.2.8" ); # 29 Nov 2013
18
19
20
#
21
# Global default settings:
22
#
23
24
# Change to ">" for HTML output
25
@define( 'MARKDOWN_EMPTY_ELEMENT_SUFFIX',  " />");
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
26
27
# Define the width of a tab for code blocks.
28
@define( 'MARKDOWN_TAB_WIDTH',     4 );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
29
30
# Optional title attribute for footnote links and backlinks.
31
@define( 'MARKDOWN_FN_LINK_TITLE',         "" );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
32
@define( 'MARKDOWN_FN_BACKLINK_TITLE',     "" );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
33
34
# Optional class attribute for footnote links and backlinks.
35
@define( 'MARKDOWN_FN_LINK_CLASS',         "jetpack-footnote" );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
36
@define( 'MARKDOWN_FN_BACKLINK_CLASS',     "" );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
37
38
# Optional class prefix for fenced code block.
39
@define( 'MARKDOWN_CODE_CLASS_PREFIX',     "language-" );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
40
41
# Class attribute for code blocks goes on the `code` tag;
42
# setting this to true will put attributes on the `pre` tag instead.
43
@define( 'MARKDOWN_CODE_ATTR_ON_PRE',   false );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
44
45
46
47
### Standard Function Interface ###
48
49
@define( 'MARKDOWN_PARSER_CLASS',  'MarkdownExtra_Parser' );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
50
51
function Markdown($text) {
52
#
53
# Initialize the parser and return the result of its transform method.
54
#
55
	# Setup static parser variable.
56
	static $parser;
57
	if (!isset($parser)) {
58
		$parser_class = MARKDOWN_PARSER_CLASS;
59
		$parser = new $parser_class;
60
	}
61
62
	# Transform text using parser.
63
	return $parser->transform($text);
64
}
65
66
/**
67
 * Returns the length of $text loosely counting the number of UTF-8 characters with regular expression.
68
 * Used by the Markdown_Parser class when mb_strlen is not available.
69
 *
70
 * @since 5.9
71
 *
72
 * @return string Length of the multibyte string
73
 *
74
 */
75
function jetpack_utf8_strlen( $text ) {
76
	return preg_match_all( "/[\\x00-\\xBF]|[\\xC0-\\xFF][\\x80-\\xBF]*/", $text, $m );
77
}
78
79
#
80
# Markdown Parser Class
81
#
82
83
class Markdown_Parser {
84
85
	### Configuration Variables ###
86
87
	# Change to ">" for HTML output.
88
	public $empty_element_suffix = MARKDOWN_EMPTY_ELEMENT_SUFFIX;
89
	public $tab_width = MARKDOWN_TAB_WIDTH;
90
91
	# Change to `true` to disallow markup or entities.
92
	public $no_markup = false;
93
	public $no_entities = false;
94
95
	# Predefined urls and titles for reference links and images.
96
	public $predef_urls = array();
97
	public $predef_titles = array();
98
99
100
	### Parser Implementation ###
101
102
	# Regex to match balanced [brackets].
103
	# Needed to insert a maximum bracked depth while converting to PHP.
104
	public $nested_brackets_depth = 6;
105
	public $nested_brackets_re;
106
107
	public $nested_url_parenthesis_depth = 4;
108
	public $nested_url_parenthesis_re;
109
110
	# Table of hash values for escaped characters:
111
	public $escape_chars = '\`*_{}[]()>#+-.!';
112
	public $escape_chars_re;
113
114
115
	function __construct() {
116
	#
117
	# Constructor function. Initialize appropriate member variables.
118
	#
119
		$this->_initDetab();
120
		$this->prepareItalicsAndBold();
121
122
		$this->nested_brackets_re =
123
			str_repeat('(?>[^\[\]]+|\[', $this->nested_brackets_depth).
124
			str_repeat('\])*', $this->nested_brackets_depth);
125
126
		$this->nested_url_parenthesis_re =
127
			str_repeat('(?>[^()\s]+|\(', $this->nested_url_parenthesis_depth).
128
			str_repeat('(?>\)))*', $this->nested_url_parenthesis_depth);
129
130
		$this->escape_chars_re = '['.preg_quote($this->escape_chars).']';
131
132
		# Sort document, block, and span gamut in ascendent priority order.
133
		asort($this->document_gamut);
134
		asort($this->block_gamut);
135
		asort($this->span_gamut);
136
	}
137
138
139
	# Internal hashes used during transformation.
140
	public $urls = array();
141
	public $titles = array();
142
	public $html_hashes = array();
143
144
	# Status flag to avoid invalid nesting.
145
	public $in_anchor = false;
146
147
148
	function setup() {
149
	#
150
	# Called before the transformation process starts to setup parser
151
	# states.
152
	#
153
		# Clear global hashes.
154
		$this->urls = $this->predef_urls;
155
		$this->titles = $this->predef_titles;
156
		$this->html_hashes = array();
157
158
		$this->in_anchor = false;
159
	}
160
161
	function teardown() {
162
	#
163
	# Called after the transformation process to clear any variable
164
	# which may be taking up memory unnecessarly.
165
	#
166
		$this->urls = array();
167
		$this->titles = array();
168
		$this->html_hashes = array();
169
	}
170
171
172
	function transform($text) {
173
	#
174
	# Main function. Performs some preprocessing on the input text
175
	# and pass it through the document gamut.
176
	#
177
		$this->setup();
178
179
		# Remove UTF-8 BOM and marker character in input, if present.
180
		$text = preg_replace('{^\xEF\xBB\xBF|\x1A}', '', $text);
181
182
		# Standardize line endings:
183
		#   DOS to Unix and Mac to Unix
184
		$text = preg_replace('{\r\n?}', "\n", $text);
185
186
		# Make sure $text ends with a couple of newlines:
187
		$text .= "\n\n";
188
189
		# Convert all tabs to spaces.
190
		$text = $this->detab($text);
191
192
		# Turn block-level HTML blocks into hash entries
193
		$text = $this->hashHTMLBlocks($text);
194
195
		# Strip any lines consisting only of spaces and tabs.
196
		# This makes subsequent regexen easier to write, because we can
197
		# match consecutive blank lines with /\n+/ instead of something
198
		# contorted like /[ ]*\n+/ .
199
		$text = preg_replace('/^[ ]+$/m', '', $text);
200
201
		# Run document gamut methods.
202
		foreach ($this->document_gamut as $method => $priority) {
203
			$text = $this->$method($text);
204
		}
205
206
		$this->teardown();
207
208
		return $text . "\n";
209
	}
210
211
	public $document_gamut = array(
212
		# Strip link definitions, store in hashes.
213
		"stripLinkDefinitions" => 20,
214
215
		"runBasicBlockGamut"   => 30,
216
		);
217
218
219 View Code Duplication
	function stripLinkDefinitions($text) {
220
	#
221
	# Strips link definitions from text, stores the URLs and titles in
222
	# hash references.
223
	#
224
		$less_than_tab = $this->tab_width - 1;
225
226
		# Link defs are in the form: ^[id]: url "optional title"
227
		$text = preg_replace_callback('{
228
							^[ ]{0,'.$less_than_tab.'}\[(.+)\][ ]?:	# id = $1
229
							  [ ]*
230
							  \n?				# maybe *one* newline
231
							  [ ]*
232
							(?:
233
							  <(.+?)>			# url = $2
234
							|
235
							  (\S+?)			# url = $3
236
							)
237
							  [ ]*
238
							  \n?				# maybe one newline
239
							  [ ]*
240
							(?:
241
								(?<=\s)			# lookbehind for whitespace
242
								["(]
243
								(.*?)			# title = $4
244
								[")]
245
								[ ]*
246
							)?	# title is optional
247
							(?:\n+|\Z)
248
			}xm',
249
			array(&$this, '_stripLinkDefinitions_callback'),
250
			$text);
251
		return $text;
252
	}
253
	function _stripLinkDefinitions_callback($matches) {
254
		$link_id = strtolower($matches[1]);
255
		$url = $matches[2] == '' ? $matches[3] : $matches[2];
256
		$this->urls[$link_id] = $url;
257
		$this->titles[$link_id] =& $matches[4];
258
		return ''; # String that will replace the block
259
	}
260
261
262
	function hashHTMLBlocks($text) {
263
		if ($this->no_markup)  return $text;
264
265
		$less_than_tab = $this->tab_width - 1;
266
267
		# Hashify HTML blocks:
268
		# We only want to do this for block-level HTML tags, such as headers,
269
		# lists, and tables. That's because we still want to wrap <p>s around
270
		# "paragraphs" that are wrapped in non-block-level tags, such as anchors,
271
		# phrase emphasis, and spans. The list of tags we're looking for is
272
		# hard-coded:
273
		#
274
		# *  List "a" is made of tags which can be both inline or block-level.
275
		#    These will be treated block-level when the start tag is alone on
276
		#    its line, otherwise they're not matched here and will be taken as
277
		#    inline later.
278
		# *  List "b" is made of tags which are always block-level;
279
		#
280
		$block_tags_a_re = 'ins|del';
281
		$block_tags_b_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|'.
282
						   'script|noscript|form|fieldset|iframe|math|svg|'.
283
						   'article|section|nav|aside|hgroup|header|footer|'.
284
						   'figure';
285
286
		# Regular expression for the content of a block tag.
287
		$nested_tags_level = 4;
288
		$attr = '
289
			(?>				# optional tag attributes
290
			  \s			# starts with whitespace
291
			  (?>
292
				[^>"/]+		# text outside quotes
293
			  |
294
				/+(?!>)		# slash not followed by ">"
295
			  |
296
				"[^"]*"		# text inside double quotes (tolerate ">")
297
			  |
298
				\'[^\']*\'	# text inside single quotes (tolerate ">")
299
			  )*
300
			)?
301
			';
302
		$content =
303
			str_repeat('
304
				(?>
305
				  [^<]+			# content without tag
306
				|
307
				  <\2			# nested opening tag
308
					'.$attr.'	# attributes
309
					(?>
310
					  />
311
					|
312
					  >', $nested_tags_level).	# end of opening tag
313
					  '.*?'.					# last level nested tag content
314
			str_repeat('
315
					  </\2\s*>	# closing nested tag
316
					)
317
				  |
318
					<(?!/\2\s*>	# other tags with a different name
319
				  )
320
				)*',
321
				$nested_tags_level);
322
		$content2 = str_replace('\2', '\3', $content);
323
324
		# First, look for nested blocks, e.g.:
325
		# 	<div>
326
		# 		<div>
327
		# 		tags for inner block must be indented.
328
		# 		</div>
329
		# 	</div>
330
		#
331
		# The outermost tags must start at the left margin for this to match, and
332
		# the inner nested divs must be indented.
333
		# We need to do this before the next, more liberal match, because the next
334
		# match will start at the first `<div>` and stop at the first `</div>`.
335
		$text = preg_replace_callback('{(?>
336
			(?>
337
				(?<=\n\n)		# Starting after a blank line
338
				|				# or
339
				\A\n?			# the beginning of the doc
340
			)
341
			(						# save in $1
342
343
			  # Match from `\n<tag>` to `</tag>\n`, handling nested tags
344
			  # in between.
345
346
						[ ]{0,'.$less_than_tab.'}
347
						<('.$block_tags_b_re.')# start tag = $2
348
						'.$attr.'>			# attributes followed by > and \n
349
						'.$content.'		# content, support nesting
350
						</\2>				# the matching end tag
351
						[ ]*				# trailing spaces/tabs
352
						(?=\n+|\Z)	# followed by a newline or end of document
353
354
			| # Special version for tags of group a.
355
356
						[ ]{0,'.$less_than_tab.'}
357
						<('.$block_tags_a_re.')# start tag = $3
358
						'.$attr.'>[ ]*\n	# attributes followed by >
359
						'.$content2.'		# content, support nesting
360
						</\3>				# the matching end tag
361
						[ ]*				# trailing spaces/tabs
362
						(?=\n+|\Z)	# followed by a newline or end of document
363
364
			| # Special case just for <hr />. It was easier to make a special
365
			  # case than to make the other regex more complicated.
366
367
						[ ]{0,'.$less_than_tab.'}
368
						<(hr)				# start tag = $2
369
						'.$attr.'			# attributes
370
						/?>					# the matching end tag
371
						[ ]*
372
						(?=\n{2,}|\Z)		# followed by a blank line or end of document
373
374
			| # Special case for standalone HTML comments:
375
376
					[ ]{0,'.$less_than_tab.'}
377
					(?s:
378
						<!-- .*? -->
379
					)
380
					[ ]*
381
					(?=\n{2,}|\Z)		# followed by a blank line or end of document
382
383
			| # PHP and ASP-style processor instructions (<? and <%)
384
385
					[ ]{0,'.$less_than_tab.'}
386
					(?s:
387
						<([?%])			# $2
388
						.*?
389
						\2>
390
					)
391
					[ ]*
392
					(?=\n{2,}|\Z)		# followed by a blank line or end of document
393
394
			)
395
			)}Sxmi',
396
			array(&$this, '_hashHTMLBlocks_callback'),
397
			$text);
398
399
		return $text;
400
	}
401
	function _hashHTMLBlocks_callback($matches) {
402
		$text = $matches[1];
403
		$key  = $this->hashBlock($text);
404
		return "\n\n$key\n\n";
405
	}
406
407
408
	function hashPart($text, $boundary = 'X') {
409
	#
410
	# Called whenever a tag must be hashed when a function insert an atomic
411
	# element in the text stream. Passing $text to through this function gives
412
	# a unique text-token which will be reverted back when calling unhash.
413
	#
414
	# The $boundary argument specify what character should be used to surround
415
	# the token. By convension, "B" is used for block elements that needs not
416
	# to be wrapped into paragraph tags at the end, ":" is used for elements
417
	# that are word separators and "X" is used in the general case.
418
	#
419
		# Swap back any tag hash found in $text so we do not have to `unhash`
420
		# multiple times at the end.
421
		$text = $this->unhash($text);
422
423
		# Then hash the block.
424
		static $i = 0;
425
		$key = "$boundary\x1A" . ++$i . $boundary;
426
		$this->html_hashes[$key] = $text;
427
		return $key; # String that will replace the tag.
428
	}
429
430
431
	function hashBlock($text) {
432
	#
433
	# Shortcut function for hashPart with block-level boundaries.
434
	#
435
		return $this->hashPart($text, 'B');
436
	}
437
438
439
	public $block_gamut = array(
440
	#
441
	# These are all the transformations that form block-level
442
	# tags like paragraphs, headers, and list items.
443
	#
444
		"doHeaders"         => 10,
445
		"doHorizontalRules" => 20,
446
447
		"doLists"           => 40,
448
		"doCodeBlocks"      => 50,
449
		"doBlockQuotes"     => 60,
450
		);
451
452
	function runBlockGamut($text) {
453
	#
454
	# Run block gamut tranformations.
455
	#
456
		# We need to escape raw HTML in Markdown source before doing anything
457
		# else. This need to be done for each block, and not only at the
458
		# beginning in the Markdown function since hashed blocks can be part of
459
		# list items and could have been indented. Indented blocks would have
460
		# been seen as a code block in a previous pass of hashHTMLBlocks.
461
		$text = $this->hashHTMLBlocks($text);
462
463
		return $this->runBasicBlockGamut($text);
464
	}
465
466
	function runBasicBlockGamut($text) {
467
	#
468
	# Run block gamut tranformations, without hashing HTML blocks. This is
469
	# useful when HTML blocks are known to be already hashed, like in the first
470
	# whole-document pass.
471
	#
472
		foreach ($this->block_gamut as $method => $priority) {
473
			$text = $this->$method($text);
474
		}
475
476
		# Finally form paragraph and restore hashed blocks.
477
		$text = $this->formParagraphs($text);
478
479
		return $text;
480
	}
481
482
483
	function doHorizontalRules($text) {
484
		# Do Horizontal Rules:
485
		return preg_replace(
486
			'{
487
				^[ ]{0,3}	# Leading space
488
				([-*_])		# $1: First marker
489
				(?>			# Repeated marker group
490
					[ ]{0,2}	# Zero, one, or two spaces.
491
					\1			# Marker character
492
				){2,}		# Group repeated at least twice
493
				[ ]*		# Tailing spaces
494
				$			# End of line.
495
			}mx',
496
			"\n".$this->hashBlock("<hr$this->empty_element_suffix")."\n",
497
			$text);
498
	}
499
500
501
	public $span_gamut = array(
502
	#
503
	# These are all the transformations that occur *within* block-level
504
	# tags like paragraphs, headers, and list items.
505
	#
506
		# Process character escapes, code spans, and inline HTML
507
		# in one shot.
508
		"parseSpan"           => -30,
509
510
		# Process anchor and image tags. Images must come first,
511
		# because ![foo][f] looks like an anchor.
512
		"doImages"            =>  10,
513
		"doAnchors"           =>  20,
514
515
		# Make links out of things like `<http://example.com/>`
516
		# Must come after doAnchors, because you can use < and >
517
		# delimiters in inline links like [this](<url>).
518
		"doAutoLinks"         =>  30,
519
		"encodeAmpsAndAngles" =>  40,
520
521
		"doItalicsAndBold"    =>  50,
522
		"doHardBreaks"        =>  60,
523
		);
524
525
	function runSpanGamut($text) {
526
	#
527
	# Run span gamut tranformations.
528
	#
529
		foreach ($this->span_gamut as $method => $priority) {
530
			$text = $this->$method($text);
531
		}
532
533
		return $text;
534
	}
535
536
537
	function doHardBreaks($text) {
538
		# Do hard breaks:
539
		return preg_replace_callback('/ {2,}\n/',
540
			array(&$this, '_doHardBreaks_callback'), $text);
541
	}
542
	function _doHardBreaks_callback($matches) {
543
		return $this->hashPart("<br$this->empty_element_suffix\n");
544
	}
545
546
547 View Code Duplication
	function doAnchors($text) {
548
	#
549
	# Turn Markdown link shortcuts into XHTML <a> tags.
550
	#
551
		if ($this->in_anchor) return $text;
552
		$this->in_anchor = true;
553
554
		#
555
		# First, handle reference-style links: [link text] [id]
556
		#
557
		$text = preg_replace_callback('{
558
			(					# wrap whole match in $1
559
			  \[
560
				('.$this->nested_brackets_re.')	# link text = $2
561
			  \]
562
563
			  [ ]?				# one optional space
564
			  (?:\n[ ]*)?		# one optional newline followed by spaces
565
566
			  \[
567
				(.*?)		# id = $3
568
			  \]
569
			)
570
			}xs',
571
			array(&$this, '_doAnchors_reference_callback'), $text);
572
573
		#
574
		# Next, inline-style links: [link text](url "optional title")
575
		#
576
		$text = preg_replace_callback('{
577
			(				# wrap whole match in $1
578
			  \[
579
				('.$this->nested_brackets_re.')	# link text = $2
580
			  \]
581
			  \(			# literal paren
582
				[ \n]*
583
				(?:
584
					<(.+?)>	# href = $3
585
				|
586
					('.$this->nested_url_parenthesis_re.')	# href = $4
587
				)
588
				[ \n]*
589
				(			# $5
590
				  ([\'"])	# quote char = $6
591
				  (.*?)		# Title = $7
592
				  \6		# matching quote
593
				  [ \n]*	# ignore any spaces/tabs between closing quote and )
594
				)?			# title is optional
595
			  \)
596
			)
597
			}xs',
598
			array(&$this, '_doAnchors_inline_callback'), $text);
599
600
		#
601
		# Last, handle reference-style shortcuts: [link text]
602
		# These must come last in case you've also got [link text][1]
603
		# or [link text](/foo)
604
		#
605
		$text = preg_replace_callback('{
606
			(					# wrap whole match in $1
607
			  \[
608
				([^\[\]]+)		# link text = $2; can\'t contain [ or ]
609
			  \]
610
			)
611
			}xs',
612
			array(&$this, '_doAnchors_reference_callback'), $text);
613
614
		$this->in_anchor = false;
615
		return $text;
616
	}
617
	function _doAnchors_reference_callback($matches) {
618
		$whole_match =  $matches[1];
619
		$link_text   =  $matches[2];
620
		$link_id     =& $matches[3];
621
622
		if ($link_id == "") {
623
			# for shortcut links like [this][] or [this].
624
			$link_id = $link_text;
625
		}
626
627
		# lower-case and turn embedded newlines into spaces
628
		$link_id = strtolower($link_id);
629
		$link_id = preg_replace('{[ ]?\n}', ' ', $link_id);
630
631
		if (isset($this->urls[$link_id])) {
632
			$url = $this->urls[$link_id];
633
			$url = $this->encodeAttribute($url);
634
635
			$result = "<a href=\"$url\"";
636 View Code Duplication
			if ( isset( $this->titles[$link_id] ) ) {
637
				$title = $this->titles[$link_id];
638
				$title = $this->encodeAttribute($title);
639
				$result .=  " title=\"$title\"";
640
			}
641
642
			$link_text = $this->runSpanGamut($link_text);
643
			$result .= ">$link_text</a>";
644
			$result = $this->hashPart($result);
645
		}
646
		else {
647
			$result = $whole_match;
648
		}
649
		return $result;
650
	}
651 View Code Duplication
	function _doAnchors_inline_callback($matches) {
652
		$whole_match	=  $matches[1];
0 ignored issues
show
Unused Code introduced by
$whole_match is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
653
		$link_text		=  $this->runSpanGamut($matches[2]);
654
		$url			=  $matches[3] == '' ? $matches[4] : $matches[3];
655
		$title			=& $matches[7];
656
657
		$url = $this->encodeAttribute($url);
658
659
		$result = "<a href=\"$url\"";
660
		if (isset($title)) {
661
			$title = $this->encodeAttribute($title);
662
			$result .=  " title=\"$title\"";
663
		}
664
665
		$link_text = $this->runSpanGamut($link_text);
666
		$result .= ">$link_text</a>";
667
668
		return $this->hashPart($result);
669
	}
670
671
672 View Code Duplication
	function doImages($text) {
673
	#
674
	# Turn Markdown image shortcuts into <img> tags.
675
	#
676
		#
677
		# First, handle reference-style labeled images: ![alt text][id]
678
		#
679
		$text = preg_replace_callback('{
680
			(				# wrap whole match in $1
681
			  !\[
682
				('.$this->nested_brackets_re.')		# alt text = $2
683
			  \]
684
685
			  [ ]?				# one optional space
686
			  (?:\n[ ]*)?		# one optional newline followed by spaces
687
688
			  \[
689
				(.*?)		# id = $3
690
			  \]
691
692
			)
693
			}xs',
694
			array(&$this, '_doImages_reference_callback'), $text);
695
696
		#
697
		# Next, handle inline images:  ![alt text](url "optional title")
698
		# Don't forget: encode * and _
699
		#
700
		$text = preg_replace_callback('{
701
			(				# wrap whole match in $1
702
			  !\[
703
				('.$this->nested_brackets_re.')		# alt text = $2
704
			  \]
705
			  \s?			# One optional whitespace character
706
			  \(			# literal paren
707
				[ \n]*
708
				(?:
709
					<(\S*)>	# src url = $3
710
				|
711
					('.$this->nested_url_parenthesis_re.')	# src url = $4
712
				)
713
				[ \n]*
714
				(			# $5
715
				  ([\'"])	# quote char = $6
716
				  (.*?)		# title = $7
717
				  \6		# matching quote
718
				  [ \n]*
719
				)?			# title is optional
720
			  \)
721
			)
722
			}xs',
723
			array(&$this, '_doImages_inline_callback'), $text);
724
725
		return $text;
726
	}
727
	function _doImages_reference_callback($matches) {
728
		$whole_match = $matches[1];
729
		$alt_text    = $matches[2];
730
		$link_id     = strtolower($matches[3]);
731
732
		if ($link_id == "") {
733
			$link_id = strtolower($alt_text); # for shortcut links like ![this][].
734
		}
735
736
		$alt_text = $this->encodeAttribute($alt_text);
737
		if (isset($this->urls[$link_id])) {
738
			$url = $this->encodeAttribute($this->urls[$link_id]);
739
			$result = "<img src=\"$url\" alt=\"$alt_text\"";
740 View Code Duplication
			if (isset($this->titles[$link_id])) {
741
				$title = $this->titles[$link_id];
742
				$title = $this->encodeAttribute($title);
743
				$result .=  " title=\"$title\"";
744
			}
745
			$result .= $this->empty_element_suffix;
746
			$result = $this->hashPart($result);
747
		}
748
		else {
749
			# If there's no such link ID, leave intact:
750
			$result = $whole_match;
751
		}
752
753
		return $result;
754
	}
755 View Code Duplication
	function _doImages_inline_callback($matches) {
756
		$whole_match	= $matches[1];
0 ignored issues
show
Unused Code introduced by
$whole_match is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
757
		$alt_text		= $matches[2];
758
		$url			= $matches[3] == '' ? $matches[4] : $matches[3];
759
		$title			=& $matches[7];
760
761
		$alt_text = $this->encodeAttribute($alt_text);
762
		$url = $this->encodeAttribute($url);
763
		$result = "<img src=\"$url\" alt=\"$alt_text\"";
764
		if (isset($title)) {
765
			$title = $this->encodeAttribute($title);
766
			$result .=  " title=\"$title\""; # $title already quoted
767
		}
768
		$result .= $this->empty_element_suffix;
769
770
		return $this->hashPart($result);
771
	}
772
773
774
	function doHeaders($text) {
775
		# Setext-style headers:
776
		#	  Header 1
777
		#	  ========
778
		#
779
		#	  Header 2
780
		#	  --------
781
		#
782
		$text = preg_replace_callback('{ ^(.+?)[ ]*\n(=+|-+)[ ]*\n+ }mx',
783
			array(&$this, '_doHeaders_callback_setext'), $text);
784
785
		# atx-style headers:
786
		#	# Header 1
787
		#	## Header 2
788
		#	## Header 2 with closing hashes ##
789
		#	...
790
		#	###### Header 6
791
		#
792
		$text = preg_replace_callback('{
793
				^(\#{1,6})	# $1 = string of #\'s
794
				[ ]*
795
				(.+?)		# $2 = Header text
796
				[ ]*
797
				\#*			# optional closing #\'s (not counted)
798
				\n+
799
			}xm',
800
			array(&$this, '_doHeaders_callback_atx'), $text);
801
802
		return $text;
803
	}
804
	function _doHeaders_callback_setext($matches) {
805
		# Terrible hack to check we haven't found an empty list item.
806 View Code Duplication
		if ($matches[2] == '-' && preg_match('{^-(?: |$)}', $matches[1]))
807
			return $matches[0];
808
809
		$level = $matches[2][0] == '=' ? 1 : 2;
810
		$block = "<h$level>".$this->runSpanGamut($matches[1])."</h$level>";
811
		return "\n" . $this->hashBlock($block) . "\n\n";
812
	}
813
	function _doHeaders_callback_atx($matches) {
814
		$level = strlen($matches[1]);
815
		$block = "<h$level>".$this->runSpanGamut($matches[2])."</h$level>";
816
		return "\n" . $this->hashBlock($block) . "\n\n";
817
	}
818
819
820
	function doLists($text) {
821
	#
822
	# Form HTML ordered (numbered) and unordered (bulleted) lists.
823
	#
824
		$less_than_tab = $this->tab_width - 1;
825
826
		# Re-usable patterns to match list item bullets and number markers:
827
		$marker_ul_re  = '[*+-]';
828
		$marker_ol_re  = '\d+[\.]';
829
		$marker_any_re = "(?:$marker_ul_re|$marker_ol_re)";
0 ignored issues
show
Unused Code introduced by
$marker_any_re is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
830
831
		$markers_relist = array(
832
			$marker_ul_re => $marker_ol_re,
833
			$marker_ol_re => $marker_ul_re,
834
			);
835
836
		foreach ($markers_relist as $marker_re => $other_marker_re) {
837
			# Re-usable pattern to match any entirel ul or ol list:
838
			$whole_list_re = '
839
				(								# $1 = whole list
840
				  (								# $2
841
					([ ]{0,'.$less_than_tab.'})	# $3 = number of spaces
842
					('.$marker_re.')			# $4 = first list item marker
843
					[ ]+
844
				  )
845
				  (?s:.+?)
846
				  (								# $5
847
					  \z
848
					|
849
					  \n{2,}
850
					  (?=\S)
851
					  (?!						# Negative lookahead for another list item marker
852
						[ ]*
853
						'.$marker_re.'[ ]+
854
					  )
855
					|
856
					  (?=						# Lookahead for another kind of list
857
					    \n
858
						\3						# Must have the same indentation
859
						'.$other_marker_re.'[ ]+
860
					  )
861
				  )
862
				)
863
			'; // mx
864
865
			# We use a different prefix before nested lists than top-level lists.
866
			# See extended comment in _ProcessListItems().
867
868
			if ($this->list_level) {
869
				$text = preg_replace_callback('{
870
						^
871
						'.$whole_list_re.'
872
					}mx',
873
					array(&$this, '_doLists_callback'), $text);
874
			}
875
			else {
876
				$text = preg_replace_callback('{
877
						(?:(?<=\n)\n|\A\n?) # Must eat the newline
878
						'.$whole_list_re.'
879
					}mx',
880
					array(&$this, '_doLists_callback'), $text);
881
			}
882
		}
883
884
		return $text;
885
	}
886
	function _doLists_callback($matches) {
887
		# Re-usable patterns to match list item bullets and number markers:
888
		$marker_ul_re  = '[*+-]';
889
		$marker_ol_re  = '\d+[\.]';
890
		$marker_any_re = "(?:$marker_ul_re|$marker_ol_re)";
0 ignored issues
show
Unused Code introduced by
$marker_any_re is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
891
892
		$list = $matches[1];
893
		$list_type = preg_match("/$marker_ul_re/", $matches[4]) ? "ul" : "ol";
894
895
		$marker_any_re = ( $list_type == "ul" ? $marker_ul_re : $marker_ol_re );
896
897
		$list .= "\n";
898
		$result = $this->processListItems($list, $marker_any_re);
899
900
		$result = $this->hashBlock("<$list_type>\n" . $result . "</$list_type>");
901
		return "\n". $result ."\n\n";
902
	}
903
904
	public $list_level = 0;
905
906
	function processListItems($list_str, $marker_any_re) {
907
	#
908
	#	Process the contents of a single ordered or unordered list, splitting it
909
	#	into individual list items.
910
	#
911
		# The $this->list_level global keeps track of when we're inside a list.
912
		# Each time we enter a list, we increment it; when we leave a list,
913
		# we decrement. If it's zero, we're not in a list anymore.
914
		#
915
		# We do this because when we're not inside a list, we want to treat
916
		# something like this:
917
		#
918
		#		I recommend upgrading to version
919
		#		8. Oops, now this line is treated
920
		#		as a sub-list.
921
		#
922
		# As a single paragraph, despite the fact that the second line starts
923
		# with a digit-period-space sequence.
924
		#
925
		# Whereas when we're inside a list (or sub-list), that line will be
926
		# treated as the start of a sub-list. What a kludge, huh? This is
927
		# an aspect of Markdown's syntax that's hard to parse perfectly
928
		# without resorting to mind-reading. Perhaps the solution is to
929
		# change the syntax rules such that sub-lists must start with a
930
		# starting cardinal number; e.g. "1." or "a.".
931
932
		$this->list_level++;
933
934
		# trim trailing blank lines:
935
		$list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str);
936
937
		$list_str = preg_replace_callback('{
938
			(\n)?							# leading line = $1
939
			(^[ ]*)							# leading whitespace = $2
940
			('.$marker_any_re.'				# list marker and space = $3
941
				(?:[ ]+|(?=\n))	# space only required if item is not empty
942
			)
943
			((?s:.*?))						# list item text   = $4
944
			(?:(\n+(?=\n))|\n)				# tailing blank line = $5
945
			(?= \n* (\z | \2 ('.$marker_any_re.') (?:[ ]+|(?=\n))))
946
			}xm',
947
			array(&$this, '_processListItems_callback'), $list_str);
948
949
		$this->list_level--;
950
		return $list_str;
951
	}
952
	function _processListItems_callback($matches) {
953
		$item = $matches[4];
954
		$leading_line =& $matches[1];
955
		$leading_space =& $matches[2];
956
		$marker_space = $matches[3];
957
		$tailing_blank_line =& $matches[5];
958
959 View Code Duplication
		if ($leading_line || $tailing_blank_line ||
960
			preg_match('/\n{2,}/', $item))
961
		{
962
			# Replace marker with the appropriate whitespace indentation
963
			$item = $leading_space . str_repeat(' ', strlen($marker_space)) . $item;
964
			$item = $this->runBlockGamut($this->outdent($item)."\n");
965
		}
966
		else {
967
			# Recursion for sub-lists:
968
			$item = $this->doLists($this->outdent($item));
969
			$item = preg_replace('/\n+$/', '', $item);
970
			$item = $this->runSpanGamut($item);
971
		}
972
973
		return "<li>" . $item . "</li>\n";
974
	}
975
976
977
	function doCodeBlocks($text) {
978
	#
979
	#	Process Markdown `<pre><code>` blocks.
980
	#
981
		$text = preg_replace_callback('{
982
				(?:\n\n|\A\n?)
983
				(	            # $1 = the code block -- one or more lines, starting with a space/tab
984
				  (?>
985
					[ ]{'.$this->tab_width.'}  # Lines must start with a tab or a tab-width of spaces
986
					.*\n+
987
				  )+
988
				)
989
				((?=^[ ]{0,'.$this->tab_width.'}\S)|\Z)	# Lookahead for non-space at line-start, or end of doc
990
			}xm',
991
			array(&$this, '_doCodeBlocks_callback'), $text);
992
993
		return $text;
994
	}
995
	function _doCodeBlocks_callback($matches) {
996
		$codeblock = $matches[1];
997
998
		$codeblock = $this->outdent($codeblock);
999
		$codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES);
1000
1001
		# trim leading newlines and trailing newlines
1002
		$codeblock = preg_replace('/\A\n+|\n+\z/', '', $codeblock);
1003
1004
		$codeblock = "<pre><code>$codeblock\n</code></pre>";
1005
		return "\n\n".$this->hashBlock($codeblock)."\n\n";
1006
	}
1007
1008
1009
	function makeCodeSpan($code) {
1010
	#
1011
	# Create a code span markup for $code. Called from handleSpanToken.
1012
	#
1013
		$code = htmlspecialchars(trim($code), ENT_NOQUOTES);
1014
		return $this->hashPart("<code>$code</code>");
1015
	}
1016
1017
1018
	public $em_relist = array(
1019
		''  => '(?:(?<!\*)\*(?!\*)|(?<!_)_(?!_))(?=\S|$)(?![\.,:;]\s)',
1020
		'*' => '(?<=\S|^)(?<!\*)\*(?!\*)',
1021
		'_' => '(?<=\S|^)(?<!_)_(?!_)',
1022
		);
1023
	public $strong_relist = array(
1024
		''   => '(?:(?<!\*)\*\*(?!\*)|(?<!_)__(?!_))(?=\S|$)(?![\.,:;]\s)',
1025
		'**' => '(?<=\S|^)(?<!\*)\*\*(?!\*)',
1026
		'__' => '(?<=\S|^)(?<!_)__(?!_)',
1027
		);
1028
	public $em_strong_relist = array(
1029
		''    => '(?:(?<!\*)\*\*\*(?!\*)|(?<!_)___(?!_))(?=\S|$)(?![\.,:;]\s)',
1030
		'***' => '(?<=\S|^)(?<!\*)\*\*\*(?!\*)',
1031
		'___' => '(?<=\S|^)(?<!_)___(?!_)',
1032
		);
1033
	public $em_strong_prepared_relist;
1034
1035
	function prepareItalicsAndBold() {
1036
	#
1037
	# Prepare regular expressions for searching emphasis tokens in any
1038
	# context.
1039
	#
1040
		foreach ($this->em_relist as $em => $em_re) {
1041
			foreach ($this->strong_relist as $strong => $strong_re) {
1042
				# Construct list of allowed token expressions.
1043
				$token_relist = array();
1044
				if (isset($this->em_strong_relist["$em$strong"])) {
1045
					$token_relist[] = $this->em_strong_relist["$em$strong"];
1046
				}
1047
				$token_relist[] = $em_re;
1048
				$token_relist[] = $strong_re;
1049
1050
				# Construct master expression from list.
1051
				$token_re = '{('. implode('|', $token_relist) .')}';
1052
				$this->em_strong_prepared_relist["$em$strong"] = $token_re;
1053
			}
1054
		}
1055
	}
1056
1057
	function doItalicsAndBold($text) {
1058
		$token_stack = array('');
1059
		$text_stack = array('');
1060
		$em = '';
1061
		$strong = '';
1062
		$tree_char_em = false;
1063
1064
		while (1) {
1065
			#
1066
			# Get prepared regular expression for seraching emphasis tokens
1067
			# in current context.
1068
			#
1069
			$token_re = $this->em_strong_prepared_relist["$em$strong"];
1070
1071
			#
1072
			# Each loop iteration search for the next emphasis token.
1073
			# Each token is then passed to handleSpanToken.
1074
			#
1075
			$parts = preg_split($token_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE);
1076
			$text_stack[0] .= $parts[0];
1077
			$token =& $parts[1];
1078
			$text =& $parts[2];
1079
1080 View Code Duplication
			if (empty($token)) {
1081
				# Reached end of text span: empty stack without emitting.
1082
				# any more emphasis.
1083
				while ($token_stack[0]) {
1084
					$text_stack[1] .= array_shift($token_stack);
1085
					$text_stack[0] .= array_shift($text_stack);
1086
				}
1087
				break;
1088
			}
1089
1090
			$token_len = strlen($token);
1091
			if ($tree_char_em) {
1092
				# Reached closing marker while inside a three-char emphasis.
1093
				if ($token_len == 3) {
1094
					# Three-char closing marker, close em and strong.
1095
					array_shift($token_stack);
1096
					$span = array_shift($text_stack);
1097
					$span = $this->runSpanGamut($span);
1098
					$span = "<strong><em>$span</em></strong>";
1099
					$text_stack[0] .= $this->hashPart($span);
1100
					$em = '';
1101
					$strong = '';
1102
				} else {
1103
					# Other closing marker: close one em or strong and
1104
					# change current token state to match the other
1105
					$token_stack[0] = str_repeat($token[0], 3-$token_len);
1106
					$tag = $token_len == 2 ? "strong" : "em";
1107
					$span = $text_stack[0];
1108
					$span = $this->runSpanGamut($span);
1109
					$span = "<$tag>$span</$tag>";
1110
					$text_stack[0] = $this->hashPart($span);
1111
					$$tag = ''; # $$tag stands for $em or $strong
1112
				}
1113
				$tree_char_em = false;
1114
			} else if ($token_len == 3) {
1115
				if ($em) {
1116
					# Reached closing marker for both em and strong.
1117
					# Closing strong marker:
1118
					for ($i = 0; $i < 2; ++$i) {
1119
						$shifted_token = array_shift($token_stack);
1120
						$tag = strlen($shifted_token) == 2 ? "strong" : "em";
1121
						$span = array_shift($text_stack);
1122
						$span = $this->runSpanGamut($span);
1123
						$span = "<$tag>$span</$tag>";
1124
						$text_stack[0] .= $this->hashPart($span);
1125
						$$tag = ''; # $$tag stands for $em or $strong
1126
					}
1127
				} else {
1128
					# Reached opening three-char emphasis marker. Push on token
1129
					# stack; will be handled by the special condition above.
1130
					$em = $token[0];
1131
					$strong = "$em$em";
1132
					array_unshift($token_stack, $token);
1133
					array_unshift($text_stack, '');
1134
					$tree_char_em = true;
1135
				}
1136
			} else if ($token_len == 2) {
1137
				if ($strong) {
1138
					# Unwind any dangling emphasis marker:
1139 View Code Duplication
					if (strlen($token_stack[0]) == 1) {
1140
						$text_stack[1] .= array_shift($token_stack);
1141
						$text_stack[0] .= array_shift($text_stack);
1142
					}
1143
					# Closing strong marker:
1144
					array_shift($token_stack);
1145
					$span = array_shift($text_stack);
1146
					$span = $this->runSpanGamut($span);
1147
					$span = "<strong>$span</strong>";
1148
					$text_stack[0] .= $this->hashPart($span);
1149
					$strong = '';
1150
				} else {
1151
					array_unshift($token_stack, $token);
1152
					array_unshift($text_stack, '');
1153
					$strong = $token;
1154
				}
1155
			} else {
1156
				# Here $token_len == 1
1157
				if ($em) {
1158
					if (strlen($token_stack[0]) == 1) {
1159
						# Closing emphasis marker:
1160
						array_shift($token_stack);
1161
						$span = array_shift($text_stack);
1162
						$span = $this->runSpanGamut($span);
1163
						$span = "<em>$span</em>";
1164
						$text_stack[0] .= $this->hashPart($span);
1165
						$em = '';
1166
					} else {
1167
						$text_stack[0] .= $token;
1168
					}
1169
				} else {
1170
					array_unshift($token_stack, $token);
1171
					array_unshift($text_stack, '');
1172
					$em = $token;
1173
				}
1174
			}
1175
		}
1176
		return $text_stack[0];
1177
	}
1178
1179
1180
	function doBlockQuotes($text) {
1181
		$text = preg_replace_callback('/
1182
			  (								# Wrap whole match in $1
1183
				(?>
1184
				  ^[ ]*>[ ]?			# ">" at the start of a line
1185
					.+\n					# rest of the first line
1186
				  (.+\n)*					# subsequent consecutive lines
1187
				  \n*						# blanks
1188
				)+
1189
			  )
1190
			/xm',
1191
			array(&$this, '_doBlockQuotes_callback'), $text);
1192
1193
		return $text;
1194
	}
1195
	function _doBlockQuotes_callback($matches) {
1196
		$bq = $matches[1];
1197
		# trim one level of quoting - trim whitespace-only lines
1198
		$bq = preg_replace('/^[ ]*>[ ]?|^[ ]+$/m', '', $bq);
1199
		$bq = $this->runBlockGamut($bq);		# recurse
1200
1201
		$bq = preg_replace('/^/m', "  ", $bq);
1202
		# These leading spaces cause problem with <pre> content,
1203
		# so we need to fix that:
1204
		$bq = preg_replace_callback('{(\s*<pre>.+?</pre>)}sx',
1205
			array(&$this, '_doBlockQuotes_callback2'), $bq);
1206
1207
		return "\n". $this->hashBlock("<blockquote>\n$bq\n</blockquote>")."\n\n";
1208
	}
1209
	function _doBlockQuotes_callback2($matches) {
1210
		$pre = $matches[1];
1211
		$pre = preg_replace('/^  /m', '', $pre);
1212
		return $pre;
1213
	}
1214
1215
1216
	function formParagraphs($text) {
1217
	#
1218
	#	Params:
1219
	#		$text - string to process with html <p> tags
1220
	#
1221
		# Strip leading and trailing lines:
1222
		$text = preg_replace('/\A\n+|\n+\z/', '', $text);
1223
1224
		$grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY);
1225
1226
		#
1227
		# Wrap <p> tags and unhashify HTML blocks
1228
		#
1229
		foreach ($grafs as $key => $value) {
1230
			if (!preg_match('/^B\x1A[0-9]+B$/', $value)) {
1231
				# Is a paragraph.
1232
				$value = $this->runSpanGamut($value);
1233
				$value = preg_replace('/^([ ]*)/', "<p>", $value);
1234
				$value .= "</p>";
1235
				$grafs[$key] = $this->unhash($value);
1236
			}
1237
			else {
1238
				# Is a block.
1239
				# Modify elements of @grafs in-place...
1240
				$graf = $value;
1241
				$block = $this->html_hashes[$graf];
1242
				$graf = $block;
1243
//				if (preg_match('{
1244
//					\A
1245
//					(							# $1 = <div> tag
1246
//					  <div  \s+
1247
//					  [^>]*
1248
//					  \b
1249
//					  markdown\s*=\s*  ([\'"])	#	$2 = attr quote char
1250
//					  1
1251
//					  \2
1252
//					  [^>]*
1253
//					  >
1254
//					)
1255
//					(							# $3 = contents
1256
//					.*
1257
//					)
1258
//					(</div>)					# $4 = closing tag
1259
//					\z
1260
//					}xs', $block, $matches))
1261
//				{
1262
//					list(, $div_open, , $div_content, $div_close) = $matches;
1263
//
1264
//					# We can't call Markdown(), because that resets the hash;
1265
//					# that initialization code should be pulled into its own sub, though.
1266
//					$div_content = $this->hashHTMLBlocks($div_content);
1267
//
1268
//					# Run document gamut methods on the content.
1269
//					foreach ($this->document_gamut as $method => $priority) {
1270
//						$div_content = $this->$method($div_content);
1271
//					}
1272
//
1273
//					$div_open = preg_replace(
1274
//						'{\smarkdown\s*=\s*([\'"]).+?\1}', '', $div_open);
1275
//
1276
//					$graf = $div_open . "\n" . $div_content . "\n" . $div_close;
1277
//				}
1278
				$grafs[$key] = $graf;
1279
			}
1280
		}
1281
1282
		return implode("\n\n", $grafs);
1283
	}
1284
1285
1286
	function encodeAttribute($text) {
1287
	#
1288
	# Encode text for a double-quoted HTML attribute. This function
1289
	# is *not* suitable for attributes enclosed in single quotes.
1290
	#
1291
		$text = $this->encodeAmpsAndAngles($text);
1292
		$text = str_replace('"', '&quot;', $text);
1293
		return $text;
1294
	}
1295
1296
1297
	function encodeAmpsAndAngles($text) {
1298
	#
1299
	# Smart processing for ampersands and angle brackets that need to
1300
	# be encoded. Valid character entities are left alone unless the
1301
	# no-entities mode is set.
1302
	#
1303
		if ($this->no_entities) {
1304
			$text = str_replace('&', '&amp;', $text);
1305
		} else {
1306
			# Ampersand-encoding based entirely on Nat Irons's Amputator
1307
			# MT plugin: <http://bumppo.net/projects/amputator/>
1308
			$text = preg_replace('/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/',
1309
								'&amp;', $text);;
1310
		}
1311
		# Encode remaining <'s
1312
		$text = str_replace('<', '&lt;', $text);
1313
1314
		return $text;
1315
	}
1316
1317
1318
	function doAutoLinks($text) {
1319
		$text = preg_replace_callback('{<((https?|ftp|dict):[^\'">\s]+)>}i',
1320
			array(&$this, '_doAutoLinks_url_callback'), $text);
1321
1322
		# Email addresses: <[email protected]>
1323
		$text = preg_replace_callback('{
1324
			<
1325
			(?:mailto:)?
1326
			(
1327
				(?:
1328
					[-!#$%&\'*+/=?^_`.{|}~\w\x80-\xFF]+
1329
				|
1330
					".*?"
1331
				)
1332
				\@
1333
				(?:
1334
					[-a-z0-9\x80-\xFF]+(\.[-a-z0-9\x80-\xFF]+)*\.[a-z]+
1335
				|
1336
					\[[\d.a-fA-F:]+\]	# IPv4 & IPv6
1337
				)
1338
			)
1339
			>
1340
			}xi',
1341
			array(&$this, '_doAutoLinks_email_callback'), $text);
1342
		$text = preg_replace_callback('{<(tel:([^\'">\s]+))>}i',array(&$this, '_doAutoLinks_tel_callback'), $text);
1343
1344
		return $text;
1345
	}
1346
	function _doAutoLinks_tel_callback($matches) {
1347
		$url = $this->encodeAttribute($matches[1]);
1348
		$tel = $this->encodeAttribute($matches[2]);
1349
		$link = "<a href=\"$url\">$tel</a>";
1350
		return $this->hashPart($link);
1351
	}
1352
	function _doAutoLinks_url_callback($matches) {
1353
		$url = $this->encodeAttribute($matches[1]);
1354
		$link = "<a href=\"$url\">$url</a>";
1355
		return $this->hashPart($link);
1356
	}
1357
	function _doAutoLinks_email_callback($matches) {
1358
		$address = $matches[1];
1359
		$link = $this->encodeEmailAddress($address);
1360
		return $this->hashPart($link);
1361
	}
1362
1363
1364
	function encodeEmailAddress($addr) {
1365
	#
1366
	#	Input: an email address, e.g. "[email protected]"
1367
	#
1368
	#	Output: the email address as a mailto link, with each character
1369
	#		of the address encoded as either a decimal or hex entity, in
1370
	#		the hopes of foiling most address harvesting spam bots. E.g.:
1371
	#
1372
	#	  <p><a href="&#109;&#x61;&#105;&#x6c;&#116;&#x6f;&#58;&#x66;o&#111;
1373
	#        &#x40;&#101;&#x78;&#97;&#x6d;&#112;&#x6c;&#101;&#46;&#x63;&#111;
1374
	#        &#x6d;">&#x66;o&#111;&#x40;&#101;&#x78;&#97;&#x6d;&#112;&#x6c;
1375
	#        &#101;&#46;&#x63;&#111;&#x6d;</a></p>
1376
	#
1377
	#	Based by a filter by Matthew Wickline, posted to BBEdit-Talk.
1378
	#   With some optimizations by Milian Wolff.
1379
	#
1380
		$addr = "mailto:" . $addr;
1381
		$chars = preg_split('/(?<!^)(?!$)/', $addr);
1382
		$seed = (int)abs(crc32($addr) / strlen($addr)); # Deterministic seed.
1383
1384
		foreach ($chars as $key => $char) {
1385
			$ord = ord($char);
1386
			# Ignore non-ascii chars.
1387
			if ($ord < 128) {
1388
				$r = ($seed * (1 + $key)) % 100; # Pseudo-random function.
1389
				# roughly 10% raw, 45% hex, 45% dec
1390
				# '@' *must* be encoded. I insist.
1391
				if ($r > 90 && $char != '@') /* do nothing */;
1392
				else if ($r < 45) $chars[$key] = '&#x'.dechex($ord).';';
1393
				else              $chars[$key] = '&#'.$ord.';';
1394
			}
1395
		}
1396
1397
		$addr = implode('', $chars);
1398
		$text = implode('', array_slice($chars, 7)); # text without `mailto:`
1399
		$addr = "<a href=\"$addr\">$text</a>";
1400
1401
		return $addr;
1402
	}
1403
1404
1405
	function parseSpan($str) {
1406
	#
1407
	# Take the string $str and parse it into tokens, hashing embedded HTML,
1408
	# escaped characters and handling code spans.
1409
	#
1410
		$output = '';
1411
1412
		$span_re = '{
1413
				(
1414
					\\\\'.$this->escape_chars_re.'
1415
				|
1416
					(?<![`\\\\])
1417
					`+						# code span marker
1418
			'.( $this->no_markup ? '' : '
1419
				|
1420
					<!--    .*?     -->		# comment
1421
				|
1422
					<\?.*?\?> | <%.*?%>		# processing instruction
1423
				|
1424
					<[!$]?[-a-zA-Z0-9:_]+	# regular tags
1425
					(?>
1426
						\s
1427
						(?>[^"\'>]+|"[^"]*"|\'[^\']*\')*
1428
					)?
1429
					>
1430
				|
1431
					<[-a-zA-Z0-9:_]+\s*/> # xml-style empty tag
1432
				|
1433
					</[-a-zA-Z0-9:_]+\s*> # closing tag
1434
			').'
1435
				)
1436
				}xs';
1437
1438
		while (1) {
1439
			#
1440
			# Each loop iteration search for either the next tag, the next
1441
			# openning code span marker, or the next escaped character.
1442
			# Each token is then passed to handleSpanToken.
1443
			#
1444
			$parts = preg_split($span_re, $str, 2, PREG_SPLIT_DELIM_CAPTURE);
1445
1446
			# Create token from text preceding tag.
1447
			if ($parts[0] != "") {
1448
				$output .= $parts[0];
1449
			}
1450
1451
			# Check if we reach the end.
1452
			if (isset($parts[1])) {
1453
				$output .= $this->handleSpanToken($parts[1], $parts[2]);
1454
				$str = $parts[2];
1455
			}
1456
			else {
1457
				break;
1458
			}
1459
		}
1460
1461
		return $output;
1462
	}
1463
1464
1465
	function handleSpanToken($token, &$str) {
1466
	#
1467
	# Handle $token provided by parseSpan by determining its nature and
1468
	# returning the corresponding value that should replace it.
1469
	#
1470
		switch ($token[0]) {
1471
			case "\\":
1472
				return $this->hashPart("&#". ord($token[1]). ";");
1473
			case "`":
1474
				# Search for end marker in remaining text.
1475
				if (preg_match('/^(.*?[^`])'.preg_quote($token).'(?!`)(.*)$/sm',
1476
					$str, $matches))
1477
				{
1478
					$str = $matches[2];
1479
					$codespan = $this->makeCodeSpan($matches[1]);
1480
					return $this->hashPart($codespan);
1481
				}
1482
				return $token; // return as text since no ending marker found.
1483
			default:
1484
				return $this->hashPart($token);
1485
		}
1486
	}
1487
1488
1489
	function outdent($text) {
1490
	#
1491
	# Remove one level of line-leading tabs or spaces
1492
	#
1493
		return preg_replace('/^(\t|[ ]{1,'.$this->tab_width.'})/m', '', $text);
1494
	}
1495
1496
1497
	# String length function for detab. `_initDetab` will create a function to
1498
	# hanlde UTF-8 if the default function does not exist.
1499
	public $utf8_strlen = 'mb_strlen';
1500
1501
	function detab($text) {
1502
	#
1503
	# Replace tabs with the appropriate amount of space.
1504
	#
1505
		# For each line we separate the line in blocks delemited by
1506
		# tab characters. Then we reconstruct every line by adding the
1507
		# appropriate number of space between each blocks.
1508
1509
		$text = preg_replace_callback('/^.*\t.*$/m',
1510
			array(&$this, '_detab_callback'), $text);
1511
1512
		return $text;
1513
	}
1514
	function _detab_callback($matches) {
1515
		$line = $matches[0];
1516
		$strlen = $this->utf8_strlen; # strlen function for UTF-8.
1517
1518
		# Split in blocks.
1519
		$blocks = explode("\t", $line);
1520
		# Add each blocks to the line.
1521
		$line = $blocks[0];
1522
		unset($blocks[0]); # Do not add first block twice.
1523
		foreach ($blocks as $block) {
1524
			# Calculate amount of space, insert spaces, insert block.
1525
			$amount = $this->tab_width -
1526
				$strlen($line, 'UTF-8') % $this->tab_width;
1527
			$line .= str_repeat(" ", $amount) . $block;
1528
		}
1529
		return $line;
1530
	}
1531
	function _initDetab() {
1532
	#
1533
	# Check for the availability of the function in the `utf8_strlen` property
1534
	# (initially `mb_strlen`). If the function is not available, use jetpack_utf8_strlen 
1535
	# that will loosely count the number of UTF-8 characters with a
1536
	# regular expression.
1537
	#
1538
		if ( function_exists( $this->utf8_strlen ) )  {
1539
			return;
1540
		}
1541
		$this->utf8_strlen = 'jetpack_utf8_strlen';
1542
	}
1543
1544
1545
	function unhash($text) {
1546
	#
1547
	# Swap back in all the tags hashed by _HashHTMLBlocks.
1548
	#
1549
		return preg_replace_callback('/(.)\x1A[0-9]+\1/',
1550
			array(&$this, '_unhash_callback'), $text);
1551
	}
1552
	function _unhash_callback($matches) {
1553
		return $this->html_hashes[$matches[0]];
1554
	}
1555
1556
}
1557
1558
1559
#
1560
# Markdown Extra Parser Class
1561
#
1562
1563
class MarkdownExtra_Parser extends Markdown_Parser {
1564
1565
	### Configuration Variables ###
1566
1567
	# Prefix for footnote ids.
1568
	public $fn_id_prefix = "";
1569
1570
	# Optional title attribute for footnote links and backlinks.
1571
	public $fn_link_title = MARKDOWN_FN_LINK_TITLE;
1572
	public $fn_backlink_title = MARKDOWN_FN_BACKLINK_TITLE;
1573
1574
	# Optional class attribute for footnote links and backlinks.
1575
	public $fn_link_class = MARKDOWN_FN_LINK_CLASS;
1576
	public $fn_backlink_class = MARKDOWN_FN_BACKLINK_CLASS;
1577
1578
	# Optional class prefix for fenced code block.
1579
	public $code_class_prefix = MARKDOWN_CODE_CLASS_PREFIX;
1580
	# Class attribute for code blocks goes on the `code` tag;
1581
	# setting this to true will put attributes on the `pre` tag instead.
1582
	public $code_attr_on_pre = MARKDOWN_CODE_ATTR_ON_PRE;
1583
1584
	# Predefined abbreviations.
1585
	public $predef_abbr = array();
1586
1587
1588
	### Parser Implementation ###
1589
1590
	function __construct() {
1591
	#
1592
	# Constructor function. Initialize the parser object.
1593
	#
1594
		# Add extra escapable characters before parent constructor
1595
		# initialize the table.
1596
		$this->escape_chars .= ':|';
1597
1598
		# Insert extra document, block, and span transformations.
1599
		# Parent constructor will do the sorting.
1600
		$this->document_gamut += array(
1601
			"doFencedCodeBlocks" => 5,
1602
			"stripFootnotes"     => 15,
1603
			"stripAbbreviations" => 25,
1604
			"appendFootnotes"    => 50,
1605
			);
1606
		$this->block_gamut += array(
1607
			"doFencedCodeBlocks" => 5,
1608
			"doTables"           => 15,
1609
			"doDefLists"         => 45,
1610
			);
1611
		$this->span_gamut += array(
1612
			"doFootnotes"        => 5,
1613
			"doAbbreviations"    => 70,
1614
			);
1615
1616
		parent::__construct();
1617
	}
1618
1619
1620
	# Extra variables used during extra transformations.
1621
	public $footnotes = array();
1622
	public $footnotes_ordered = array();
1623
	public $footnotes_ref_count = array();
1624
	public $footnotes_numbers = array();
1625
	public $abbr_desciptions = array();
1626
	public $abbr_word_re = '';
1627
1628
	# Give the current footnote number.
1629
	public $footnote_counter = 1;
1630
1631
1632
	function setup() {
1633
	#
1634
	# Setting up Extra-specific variables.
1635
	#
1636
		parent::setup();
1637
1638
		$this->footnotes = array();
1639
		$this->footnotes_ordered = array();
1640
		$this->footnotes_ref_count = array();
1641
		$this->footnotes_numbers = array();
1642
		$this->abbr_desciptions = array();
1643
		$this->abbr_word_re = '';
1644
		$this->footnote_counter = 1;
1645
1646
		foreach ($this->predef_abbr as $abbr_word => $abbr_desc) {
1647
			if ($this->abbr_word_re)
1648
				$this->abbr_word_re .= '|';
1649
			$this->abbr_word_re .= preg_quote($abbr_word);
1650
			$this->abbr_desciptions[$abbr_word] = trim($abbr_desc);
1651
		}
1652
	}
1653
1654
	function teardown() {
1655
	#
1656
	# Clearing Extra-specific variables.
1657
	#
1658
		$this->footnotes = array();
1659
		$this->footnotes_ordered = array();
1660
		$this->footnotes_ref_count = array();
1661
		$this->footnotes_numbers = array();
1662
		$this->abbr_desciptions = array();
1663
		$this->abbr_word_re = '';
1664
1665
		parent::teardown();
1666
	}
1667
1668
1669
	### Extra Attribute Parser ###
1670
1671
	# Expression to use to catch attributes (includes the braces)
1672
	public $id_class_attr_catch_re = '\{((?:[ ]*[#.][-_:a-zA-Z0-9]+){1,})[ ]*\}';
1673
	# Expression to use when parsing in a context when no capture is desired
1674
	public $id_class_attr_nocatch_re = '\{(?:[ ]*[#.][-_:a-zA-Z0-9]+){1,}[ ]*\}';
1675
1676
	function doExtraAttributes($tag_name, $attr) {
1677
	#
1678
	# Parse attributes caught by the $this->id_class_attr_catch_re expression
1679
	# and return the HTML-formatted list of attributes.
1680
	#
1681
	# Currently supported attributes are .class and #id.
1682
	#
1683
		if (empty($attr)) return "";
1684
1685
		# Split on components
1686
		preg_match_all('/[#.][-_:a-zA-Z0-9]+/', $attr, $matches);
1687
		$elements = $matches[0];
1688
1689
		# handle classes and ids (only first id taken into account)
1690
		$classes = array();
1691
		$id = false;
1692
		foreach ($elements as $element) {
1693
			if ($element[0] == '.') {
1694
				$classes[] = substr($element, 1);
1695
			} else if ($element[0] == '#') {
1696
				if ($id === false) $id = substr($element, 1);
1697
			}
1698
		}
1699
1700
		# compose attributes as string
1701
		$attr_str = "";
1702
		if (!empty($id)) {
1703
			$attr_str .= ' id="'.$id.'"';
1704
		}
1705
		if (!empty($classes)) {
1706
			$attr_str .= ' class="'.implode(" ", $classes).'"';
1707
		}
1708
		return $attr_str;
1709
	}
1710
1711
1712 View Code Duplication
	function stripLinkDefinitions($text) {
1713
	#
1714
	# Strips link definitions from text, stores the URLs and titles in
1715
	# hash references.
1716
	#
1717
		$less_than_tab = $this->tab_width - 1;
1718
1719
		# Link defs are in the form: ^[id]: url "optional title"
1720
		$text = preg_replace_callback('{
1721
							^[ ]{0,'.$less_than_tab.'}\[(.+)\][ ]?:	# id = $1
1722
							  [ ]*
1723
							  \n?				# maybe *one* newline
1724
							  [ ]*
1725
							(?:
1726
							  <(.+?)>			# url = $2
1727
							|
1728
							  (\S+?)			# url = $3
1729
							)
1730
							  [ ]*
1731
							  \n?				# maybe one newline
1732
							  [ ]*
1733
							(?:
1734
								(?<=\s)			# lookbehind for whitespace
1735
								["(]
1736
								(.*?)			# title = $4
1737
								[")]
1738
								[ ]*
1739
							)?	# title is optional
1740
					(?:[ ]* '.$this->id_class_attr_catch_re.' )?  # $5 = extra id & class attr
1741
							(?:\n+|\Z)
1742
			}xm',
1743
			array(&$this, '_stripLinkDefinitions_callback'),
1744
			$text);
1745
		return $text;
1746
	}
1747
	function _stripLinkDefinitions_callback($matches) {
1748
		$link_id = strtolower($matches[1]);
1749
		$url = $matches[2] == '' ? $matches[3] : $matches[2];
1750
		$this->urls[$link_id] = $url;
1751
		$this->titles[$link_id] =& $matches[4];
1752
		$this->ref_attr[$link_id] = $this->doExtraAttributes("", $dummy =& $matches[5]);
0 ignored issues
show
Bug introduced by
The property ref_attr does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
1753
		return ''; # String that will replace the block
1754
	}
1755
1756
1757
	### HTML Block Parser ###
1758
1759
	# Tags that are always treated as block tags:
1760
	public $block_tags_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|form|fieldset|iframe|hr|legend|article|section|nav|aside|hgroup|header|footer|figcaption';
1761
1762
	# Tags treated as block tags only if the opening tag is alone on its line:
1763
	public $context_block_tags_re = 'script|noscript|ins|del|iframe|object|source|track|param|math|svg|canvas|audio|video';
1764
1765
	# Tags where markdown="1" default to span mode:
1766
	public $contain_span_tags_re = 'p|h[1-6]|li|dd|dt|td|th|legend|address';
1767
1768
	# Tags which must not have their contents modified, no matter where
1769
	# they appear:
1770
	public $clean_tags_re = 'script|math|svg';
1771
1772
	# Tags that do not need to be closed.
1773
	public $auto_close_tags_re = 'hr|img|param|source|track';
1774
1775
1776
	function hashHTMLBlocks($text) {
1777
	#
1778
	# Hashify HTML Blocks and "clean tags".
1779
	#
1780
	# We only want to do this for block-level HTML tags, such as headers,
1781
	# lists, and tables. That's because we still want to wrap <p>s around
1782
	# "paragraphs" that are wrapped in non-block-level tags, such as anchors,
1783
	# phrase emphasis, and spans. The list of tags we're looking for is
1784
	# hard-coded.
1785
	#
1786
	# This works by calling _HashHTMLBlocks_InMarkdown, which then calls
1787
	# _HashHTMLBlocks_InHTML when it encounter block tags. When the markdown="1"
1788
	# attribute is found within a tag, _HashHTMLBlocks_InHTML calls back
1789
	#  _HashHTMLBlocks_InMarkdown to handle the Markdown syntax within the tag.
1790
	# These two functions are calling each other. It's recursive!
1791
	#
1792
		if ($this->no_markup)  return $text;
1793
1794
		#
1795
		# Call the HTML-in-Markdown hasher.
1796
		#
1797
		list($text, ) = $this->_hashHTMLBlocks_inMarkdown($text);
1798
1799
		return $text;
1800
	}
1801
	function _hashHTMLBlocks_inMarkdown($text, $indent = 0,
1802
										$enclosing_tag_re = '', $span = false)
1803
	{
1804
	#
1805
	# Parse markdown text, calling _HashHTMLBlocks_InHTML for block tags.
1806
	#
1807
	# *   $indent is the number of space to be ignored when checking for code
1808
	#     blocks. This is important because if we don't take the indent into
1809
	#     account, something like this (which looks right) won't work as expected:
1810
	#
1811
	#     <div>
1812
	#         <div markdown="1">
1813
	#         Hello World.  <-- Is this a Markdown code block or text?
1814
	#         </div>  <-- Is this a Markdown code block or a real tag?
1815
	#     <div>
1816
	#
1817
	#     If you don't like this, just don't indent the tag on which
1818
	#     you apply the markdown="1" attribute.
1819
	#
1820
	# *   If $enclosing_tag_re is not empty, stops at the first unmatched closing
1821
	#     tag with that name. Nested tags supported.
1822
	#
1823
	# *   If $span is true, text inside must treated as span. So any double
1824
	#     newline will be replaced by a single newline so that it does not create
1825
	#     paragraphs.
1826
	#
1827
	# Returns an array of that form: ( processed text , remaining text )
1828
	#
1829
		if ($text === '') return array('', '');
1830
1831
		# Regex to check for the presence of newlines around a block tag.
1832
		$newline_before_re = '/(?:^\n?|\n\n)*$/';
1833
		$newline_after_re =
1834
			'{
1835
				^						# Start of text following the tag.
1836
				(?>[ ]*<!--.*?-->)?		# Optional comment.
1837
				[ ]*\n					# Must be followed by newline.
1838
			}xs';
1839
1840
		# Regex to match any tag.
1841
		$block_tag_re =
1842
			'{
1843
				(					# $2: Capture whole tag.
1844
					</?					# Any opening or closing tag.
1845
						(?>				# Tag name.
1846
							'.$this->block_tags_re.'			|
1847
							'.$this->context_block_tags_re.'	|
1848
							'.$this->clean_tags_re.'        	|
1849
							(?!\s)'.$enclosing_tag_re.'
1850
						)
1851
						(?:
1852
							(?=[\s"\'/a-zA-Z0-9])	# Allowed characters after tag name.
1853
							(?>
1854
								".*?"		|	# Double quotes (can contain `>`)
1855
								\'.*?\'   	|	# Single quotes (can contain `>`)
1856
								.+?				# Anything but quotes and `>`.
1857
							)*?
1858
						)?
1859
					>					# End of tag.
1860
				|
1861
					<!--    .*?     -->	# HTML Comment
1862
				|
1863
					<\?.*?\?> | <%.*?%>	# Processing instruction
1864
				|
1865
					<!\[CDATA\[.*?\]\]>	# CData Block
1866
				'. ( !$span ? ' # If not in span.
1867
				|
1868
					# Indented code block
1869
					(?: ^[ ]*\n | ^ | \n[ ]*\n )
1870
					[ ]{'.($indent+4).'}[^\n]* \n
1871
					(?>
1872
						(?: [ ]{'.($indent+4).'}[^\n]* | [ ]* ) \n
1873
					)*
1874
				|
1875
					# Fenced code block marker
1876
					(?<= ^ | \n )
1877
					[ ]{0,'.($indent+3).'}(?:~{3,}|`{3,})
1878
									[ ]*
1879
					(?:
1880
					\.?[-_:a-zA-Z0-9]+ # standalone class name
1881
					|
1882
						'.$this->id_class_attr_nocatch_re.' # extra attributes
1883
					)?
1884
					[ ]*
1885
					(?= \n )
1886
				' : '' ). ' # End (if not is span).
1887
				|
1888
					# Code span marker
1889
					# Note, this regex needs to go after backtick fenced
1890
					# code blocks but it should also be kept outside of the
1891
					# "if not in span" condition adding backticks to the parser
1892
					`+
1893
				)
1894
			}xs';
1895
1896
1897
		$depth = 0;		# Current depth inside the tag tree.
1898
		$parsed = "";	# Parsed text that will be returned.
1899
1900
		#
1901
		# Loop through every tag until we find the closing tag of the parent
1902
		# or loop until reaching the end of text if no parent tag specified.
1903
		#
1904
		do {
1905
			#
1906
			# Split the text using the first $tag_match pattern found.
1907
			# Text before  pattern will be first in the array, text after
1908
			# pattern will be at the end, and between will be any catches made
1909
			# by the pattern.
1910
			#
1911
			$parts = preg_split($block_tag_re, $text, 2,
1912
								PREG_SPLIT_DELIM_CAPTURE);
1913
1914
			# If in Markdown span mode, add a empty-string span-level hash
1915
			# after each newline to prevent triggering any block element.
1916
			if ($span) {
1917
				$void = $this->hashPart("", ':');
1918
				$newline = "$void\n";
1919
				$parts[0] = $void . str_replace("\n", $newline, $parts[0]) . $void;
1920
			}
1921
1922
			$parsed .= $parts[0]; # Text before current tag.
1923
1924
			# If end of $text has been reached. Stop loop.
1925
			if (count($parts) < 3) {
1926
				$text = "";
1927
				break;
1928
			}
1929
1930
			$tag  = $parts[1]; # Tag to handle.
1931
			$text = $parts[2]; # Remaining text after current tag.
1932
			$tag_re = preg_quote($tag); # For use in a regular expression.
0 ignored issues
show
Unused Code introduced by
$tag_re is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1933
1934
			#
1935
			# Check for: Fenced code block marker.
1936
			# Note: need to recheck the whole tag to disambiguate backtick
1937
			# fences from code spans
1938
			#
1939
			if (preg_match('{^\n?([ ]{0,'.($indent+3).'})(~{3,}|`{3,})[ ]*(?:\.?[-_:a-zA-Z0-9]+|'.$this->id_class_attr_nocatch_re.')?[ ]*\n?$}', $tag, $capture)) {
1940
				# Fenced code block marker: find matching end marker.
1941
				$fence_indent = strlen($capture[1]); # use captured indent in re
1942
				$fence_re = $capture[2]; # use captured fence in re
1943 View Code Duplication
				if (preg_match('{^(?>.*\n)*?[ ]{'.($fence_indent).'}'.$fence_re.'[ ]*(?:\n|$)}', $text,
1944
					$matches))
1945
				{
1946
					# End marker found: pass text unchanged until marker.
1947
					$parsed .= $tag . $matches[0];
1948
					$text = substr($text, strlen($matches[0]));
1949
				}
1950
				else {
1951
					# No end marker: just skip it.
1952
					$parsed .= $tag;
1953
				}
1954
			}
1955
			#
1956
			# Check for: Indented code block.
1957
			#
1958
			else if ($tag[0] == "\n" || $tag[0] == " ") {
1959
				# Indented code block: pass it unchanged, will be handled
1960
				# later.
1961
				$parsed .= $tag;
1962
			}
1963
			#
1964
			# Check for: Code span marker
1965
			# Note: need to check this after backtick fenced code blocks
1966
			#
1967
			else if ($tag[0] == "`") {
1968
				# Find corresponding end marker.
1969
				$tag_re = preg_quote($tag);
1970 View Code Duplication
				if (preg_match('{^(?>.+?|\n(?!\n))*?(?<!`)'.$tag_re.'(?!`)}',
1971
					$text, $matches))
1972
				{
1973
					# End marker found: pass text unchanged until marker.
1974
					$parsed .= $tag . $matches[0];
1975
					$text = substr($text, strlen($matches[0]));
1976
				}
1977
				else {
1978
					# Unmatched marker: just skip it.
1979
					$parsed .= $tag;
1980
				}
1981
			}
1982
			#
1983
			# Check for: Opening Block level tag or
1984
			#            Opening Context Block tag (like ins and del)
1985
			#               used as a block tag (tag is alone on it's line).
1986
			#
1987
			else if (preg_match('{^<(?:'.$this->block_tags_re.')\b}', $tag) ||
1988
				(	preg_match('{^<(?:'.$this->context_block_tags_re.')\b}', $tag) &&
1989
					preg_match($newline_before_re, $parsed) &&
1990
					preg_match($newline_after_re, $text)	)
1991
				)
1992
			{
1993
				# Need to parse tag and following text using the HTML parser.
1994
				list($block_text, $text) =
1995
					$this->_hashHTMLBlocks_inHTML($tag . $text, "hashBlock", true);
1996
1997
				# Make sure it stays outside of any paragraph by adding newlines.
1998
				$parsed .= "\n\n$block_text\n\n";
1999
			}
2000
			#
2001
			# Check for: Clean tag (like script, math)
2002
			#            HTML Comments, processing instructions.
2003
			#
2004
			else if (preg_match('{^<(?:'.$this->clean_tags_re.')\b}', $tag) ||
2005
				$tag[1] == '!' || $tag[1] == '?')
2006
			{
2007
				# Need to parse tag and following text using the HTML parser.
2008
				# (don't check for markdown attribute)
2009
				list($block_text, $text) =
2010
					$this->_hashHTMLBlocks_inHTML($tag . $text, "hashClean", false);
2011
2012
				$parsed .= $block_text;
2013
			}
2014
			#
2015
			# Check for: Tag with same name as enclosing tag.
2016
			#
2017
			else if ($enclosing_tag_re !== '' &&
2018
				# Same name as enclosing tag.
2019
				preg_match('{^</?(?:'.$enclosing_tag_re.')\b}', $tag))
2020
			{
2021
				#
2022
				# Increase/decrease nested tag count.
2023
				#
2024 View Code Duplication
				if ($tag[1] == '/')						$depth--;
2025
				else if ($tag[strlen($tag)-2] != '/')	$depth++;
2026
2027
				if ($depth < 0) {
2028
					#
2029
					# Going out of parent element. Clean up and break so we
2030
					# return to the calling function.
2031
					#
2032
					$text = $tag . $text;
2033
					break;
2034
				}
2035
2036
				$parsed .= $tag;
2037
			}
2038
			else {
2039
				$parsed .= $tag;
2040
			}
2041
		} while ($depth >= 0);
2042
2043
		return array($parsed, $text);
2044
	}
2045
	function _hashHTMLBlocks_inHTML($text, $hash_method, $md_attr) {
2046
	#
2047
	# Parse HTML, calling _HashHTMLBlocks_InMarkdown for block tags.
2048
	#
2049
	# *   Calls $hash_method to convert any blocks.
2050
	# *   Stops when the first opening tag closes.
2051
	# *   $md_attr indicate if the use of the `markdown="1"` attribute is allowed.
2052
	#     (it is not inside clean tags)
2053
	#
2054
	# Returns an array of that form: ( processed text , remaining text )
2055
	#
2056
		if ($text === '') return array('', '');
2057
2058
		# Regex to match `markdown` attribute inside of a tag.
2059
		$markdown_attr_re = '
2060
			{
2061
				\s*			# Eat whitespace before the `markdown` attribute
2062
				markdown
2063
				\s*=\s*
2064
				(?>
2065
					(["\'])		# $1: quote delimiter
2066
					(.*?)		# $2: attribute value
2067
					\1			# matching delimiter
2068
				|
2069
					([^\s>]*)	# $3: unquoted attribute value
2070
				)
2071
				()				# $4: make $3 always defined (avoid warnings)
2072
			}xs';
2073
2074
		# Regex to match any tag.
2075
		$tag_re = '{
2076
				(					# $2: Capture whole tag.
2077
					</?					# Any opening or closing tag.
2078
						[\w:$]+			# Tag name.
2079
						(?:
2080
							(?=[\s"\'/a-zA-Z0-9])	# Allowed characters after tag name.
2081
							(?>
2082
								".*?"		|	# Double quotes (can contain `>`)
2083
								\'.*?\'   	|	# Single quotes (can contain `>`)
2084
								.+?				# Anything but quotes and `>`.
2085
							)*?
2086
						)?
2087
					>					# End of tag.
2088
				|
2089
					<!--    .*?     -->	# HTML Comment
2090
				|
2091
					<\?.*?\?> | <%.*?%>	# Processing instruction
2092
				|
2093
					<!\[CDATA\[.*?\]\]>	# CData Block
2094
				)
2095
			}xs';
2096
2097
		$original_text = $text;		# Save original text in case of faliure.
2098
2099
		$depth		= 0;	# Current depth inside the tag tree.
2100
		$block_text	= "";	# Temporary text holder for current text.
2101
		$parsed		= "";	# Parsed text that will be returned.
2102
2103
		#
2104
		# Get the name of the starting tag.
2105
		# (This pattern makes $base_tag_name_re safe without quoting.)
2106
		#
2107
		if (preg_match('/^<([\w:$]*)\b/', $text, $matches))
2108
			$base_tag_name_re = $matches[1];
2109
2110
		#
2111
		# Loop through every tag until we find the corresponding closing tag.
2112
		#
2113
		do {
2114
			#
2115
			# Split the text using the first $tag_match pattern found.
2116
			# Text before  pattern will be first in the array, text after
2117
			# pattern will be at the end, and between will be any catches made
2118
			# by the pattern.
2119
			#
2120
			$parts = preg_split($tag_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE);
2121
2122
			if (count($parts) < 3) {
2123
				#
2124
				# End of $text reached with unbalenced tag(s).
2125
				# In that case, we return original text unchanged and pass the
2126
				# first character as filtered to prevent an infinite loop in the
2127
				# parent function.
2128
				#
2129
				return array($original_text[0], substr($original_text, 1));
2130
			}
2131
2132
			$block_text .= $parts[0]; # Text before current tag.
2133
			$tag         = $parts[1]; # Tag to handle.
2134
			$text        = $parts[2]; # Remaining text after current tag.
2135
2136
			#
2137
			# Check for: Auto-close tag (like <hr/>)
2138
			#			 Comments and Processing Instructions.
2139
			#
2140
			if (preg_match('{^</?(?:'.$this->auto_close_tags_re.')\b}', $tag) ||
2141
				$tag[1] == '!' || $tag[1] == '?')
2142
			{
2143
				# Just add the tag to the block as if it was text.
2144
				$block_text .= $tag;
2145
			}
2146
			else {
2147
				#
2148
				# Increase/decrease nested tag count. Only do so if
2149
				# the tag's name match base tag's.
2150
				#
2151
				if (preg_match('{^</?'.$base_tag_name_re.'\b}', $tag)) {
0 ignored issues
show
Bug introduced by
The variable $base_tag_name_re does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
2152 View Code Duplication
					if ($tag[1] == '/')						$depth--;
2153
					else if ($tag[strlen($tag)-2] != '/')	$depth++;
2154
				}
2155
2156
				#
2157
				# Check for `markdown="1"` attribute and handle it.
2158
				#
2159
				if ($md_attr &&
2160
					preg_match($markdown_attr_re, $tag, $attr_m) &&
2161
					preg_match('/^1|block|span$/', $attr_m[2] . $attr_m[3]))
2162
				{
2163
					# Remove `markdown` attribute from opening tag.
2164
					$tag = preg_replace($markdown_attr_re, '', $tag);
2165
2166
					# Check if text inside this tag must be parsed in span mode.
2167
					$this->mode = $attr_m[2] . $attr_m[3];
0 ignored issues
show
Bug introduced by
The property mode does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
2168
					$span_mode = $this->mode == 'span' || $this->mode != 'block' &&
2169
						preg_match('{^<(?:'.$this->contain_span_tags_re.')\b}', $tag);
2170
2171
					# Calculate indent before tag.
2172
					if (preg_match('/(?:^|\n)( *?)(?! ).*?$/', $block_text, $matches)) {
2173
						$strlen = $this->utf8_strlen;
2174
						$indent = $strlen($matches[1], 'UTF-8');
2175
					} else {
2176
						$indent = 0;
2177
					}
2178
2179
					# End preceding block with this tag.
2180
					$block_text .= $tag;
2181
					$parsed .= $this->$hash_method($block_text);
2182
2183
					# Get enclosing tag name for the ParseMarkdown function.
2184
					# (This pattern makes $tag_name_re safe without quoting.)
2185
					preg_match('/^<([\w:$]*)\b/', $tag, $matches);
2186
					$tag_name_re = $matches[1];
2187
2188
					# Parse the content using the HTML-in-Markdown parser.
2189
					list ($block_text, $text)
2190
						= $this->_hashHTMLBlocks_inMarkdown($text, $indent,
2191
							$tag_name_re, $span_mode);
2192
2193
					# Outdent markdown text.
2194
					if ($indent > 0) {
2195
						$block_text = preg_replace("/^[ ]{1,$indent}/m", "",
2196
													$block_text);
2197
					}
2198
2199
					# Append tag content to parsed text.
2200
					if (!$span_mode)	$parsed .= "\n\n$block_text\n\n";
2201
					else				$parsed .= "$block_text";
2202
2203
					# Start over with a new block.
2204
					$block_text = "";
2205
				}
2206
				else $block_text .= $tag;
2207
			}
2208
2209
		} while ($depth > 0);
2210
2211
		#
2212
		# Hash last block text that wasn't processed inside the loop.
2213
		#
2214
		$parsed .= $this->$hash_method($block_text);
2215
2216
		return array($parsed, $text);
2217
	}
2218
2219
2220
	function hashClean($text) {
2221
	#
2222
	# Called whenever a tag must be hashed when a function inserts a "clean" tag
2223
	# in $text, it passes through this function and is automaticaly escaped,
2224
	# blocking invalid nested overlap.
2225
	#
2226
		return $this->hashPart($text, 'C');
2227
	}
2228
2229
2230 View Code Duplication
	function doAnchors($text) {
2231
	#
2232
	# Turn Markdown link shortcuts into XHTML <a> tags.
2233
	#
2234
		if ($this->in_anchor) return $text;
2235
		$this->in_anchor = true;
2236
2237
		#
2238
		# First, handle reference-style links: [link text] [id]
2239
		#
2240
		$text = preg_replace_callback('{
2241
			(					# wrap whole match in $1
2242
			  \[
2243
				('.$this->nested_brackets_re.')	# link text = $2
2244
			  \]
2245
2246
			  [ ]?				# one optional space
2247
			  (?:\n[ ]*)?		# one optional newline followed by spaces
2248
2249
			  \[
2250
				(.*?)		# id = $3
2251
			  \]
2252
			)
2253
			}xs',
2254
			array(&$this, '_doAnchors_reference_callback'), $text);
2255
2256
		#
2257
		# Next, inline-style links: [link text](url "optional title")
2258
		#
2259
		$text = preg_replace_callback('{
2260
			(				# wrap whole match in $1
2261
			  \[
2262
				('.$this->nested_brackets_re.')	# link text = $2
2263
			  \]
2264
			  \(			# literal paren
2265
				[ \n]*
2266
				(?:
2267
					<(.+?)>	# href = $3
2268
				|
2269
					('.$this->nested_url_parenthesis_re.')	# href = $4
2270
				)
2271
				[ \n]*
2272
				(			# $5
2273
				  ([\'"])	# quote char = $6
2274
				  (.*?)		# Title = $7
2275
				  \6		# matching quote
2276
				  [ \n]*	# ignore any spaces/tabs between closing quote and )
2277
				)?			# title is optional
2278
			  \)
2279
			  (?:[ ]? '.$this->id_class_attr_catch_re.' )?	 # $8 = id/class attributes
2280
			)
2281
			}xs',
2282
			array(&$this, '_doAnchors_inline_callback'), $text);
2283
2284
		#
2285
		# Last, handle reference-style shortcuts: [link text]
2286
		# These must come last in case you've also got [link text][1]
2287
		# or [link text](/foo)
2288
		#
2289
		$text = preg_replace_callback('{
2290
			(					# wrap whole match in $1
2291
			  \[
2292
				([^\[\]]+)		# link text = $2; can\'t contain [ or ]
2293
			  \]
2294
			)
2295
			}xs',
2296
			array(&$this, '_doAnchors_reference_callback'), $text);
2297
2298
		$this->in_anchor = false;
2299
		return $text;
2300
	}
2301
	function _doAnchors_reference_callback($matches) {
2302
		$whole_match =  $matches[1];
2303
		$link_text   =  $matches[2];
2304
		$link_id     =& $matches[3];
2305
2306
		if ($link_id == "") {
2307
			# for shortcut links like [this][] or [this].
2308
			$link_id = $link_text;
2309
		}
2310
2311
		# lower-case and turn embedded newlines into spaces
2312
		$link_id = strtolower($link_id);
2313
		$link_id = preg_replace('{[ ]?\n}', ' ', $link_id);
2314
2315
		if (isset($this->urls[$link_id])) {
2316
			$url = $this->urls[$link_id];
2317
			$url = $this->encodeAttribute($url);
2318
2319
			$result = "<a href=\"$url\"";
2320 View Code Duplication
			if ( isset( $this->titles[$link_id] ) ) {
2321
				$title = $this->titles[$link_id];
2322
				$title = $this->encodeAttribute($title);
2323
				$result .=  " title=\"$title\"";
2324
			}
2325
			if (isset($this->ref_attr[$link_id]))
2326
				$result .= $this->ref_attr[$link_id];
2327
2328
			$link_text = $this->runSpanGamut($link_text);
2329
			$result .= ">$link_text</a>";
2330
			$result = $this->hashPart($result);
2331
		}
2332
		else {
2333
			$result = $whole_match;
2334
		}
2335
		return $result;
2336
	}
2337 View Code Duplication
	function _doAnchors_inline_callback($matches) {
2338
		$whole_match	=  $matches[1];
0 ignored issues
show
Unused Code introduced by
$whole_match is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
2339
		$link_text		=  $this->runSpanGamut($matches[2]);
2340
		$url			=  $matches[3] == '' ? $matches[4] : $matches[3];
2341
		$title			=& $matches[7];
2342
		$attr  = $this->doExtraAttributes("a", $dummy =& $matches[8]);
2343
2344
2345
		$url = $this->encodeAttribute($url);
2346
2347
		$result = "<a href=\"$url\"";
2348
		if (isset($title)) {
2349
			$title = $this->encodeAttribute($title);
2350
			$result .=  " title=\"$title\"";
2351
		}
2352
		$result .= $attr;
2353
2354
		$link_text = $this->runSpanGamut($link_text);
2355
		$result .= ">$link_text</a>";
2356
2357
		return $this->hashPart($result);
2358
	}
2359
2360
2361 View Code Duplication
	function doImages($text) {
2362
	#
2363
	# Turn Markdown image shortcuts into <img> tags.
2364
	#
2365
		#
2366
		# First, handle reference-style labeled images: ![alt text][id]
2367
		#
2368
		$text = preg_replace_callback('{
2369
			(				# wrap whole match in $1
2370
			  !\[
2371
				('.$this->nested_brackets_re.')		# alt text = $2
2372
			  \]
2373
2374
			  [ ]?				# one optional space
2375
			  (?:\n[ ]*)?		# one optional newline followed by spaces
2376
2377
			  \[
2378
				(.*?)		# id = $3
2379
			  \]
2380
2381
			)
2382
			}xs',
2383
			array(&$this, '_doImages_reference_callback'), $text);
2384
2385
		#
2386
		# Next, handle inline images:  ![alt text](url "optional title")
2387
		# Don't forget: encode * and _
2388
		#
2389
		$text = preg_replace_callback('{
2390
			(				# wrap whole match in $1
2391
			  !\[
2392
				('.$this->nested_brackets_re.')		# alt text = $2
2393
			  \]
2394
			  \s?			# One optional whitespace character
2395
			  \(			# literal paren
2396
				[ \n]*
2397
				(?:
2398
					<(\S*)>	# src url = $3
2399
				|
2400
					('.$this->nested_url_parenthesis_re.')	# src url = $4
2401
				)
2402
				[ \n]*
2403
				(			# $5
2404
				  ([\'"])	# quote char = $6
2405
				  (.*?)		# title = $7
2406
				  \6		# matching quote
2407
				  [ \n]*
2408
				)?			# title is optional
2409
			  \)
2410
			  (?:[ ]? '.$this->id_class_attr_catch_re.' )?	 # $8 = id/class attributes
2411
			)
2412
			}xs',
2413
			array(&$this, '_doImages_inline_callback'), $text);
2414
2415
		return $text;
2416
	}
2417
	function _doImages_reference_callback($matches) {
2418
		$whole_match = $matches[1];
2419
		$alt_text    = $matches[2];
2420
		$link_id     = strtolower($matches[3]);
2421
2422
		if ($link_id == "") {
2423
			$link_id = strtolower($alt_text); # for shortcut links like ![this][].
2424
		}
2425
2426
		$alt_text = $this->encodeAttribute($alt_text);
2427
		if (isset($this->urls[$link_id])) {
2428
			$url = $this->encodeAttribute($this->urls[$link_id]);
2429
			$result = "<img src=\"$url\" alt=\"$alt_text\"";
2430 View Code Duplication
			if (isset($this->titles[$link_id])) {
2431
				$title = $this->titles[$link_id];
2432
				$title = $this->encodeAttribute($title);
2433
				$result .=  " title=\"$title\"";
2434
			}
2435
			if (isset($this->ref_attr[$link_id]))
2436
				$result .= $this->ref_attr[$link_id];
2437
			$result .= $this->empty_element_suffix;
2438
			$result = $this->hashPart($result);
2439
		}
2440
		else {
2441
			# If there's no such link ID, leave intact:
2442
			$result = $whole_match;
2443
		}
2444
2445
		return $result;
2446
	}
2447 View Code Duplication
	function _doImages_inline_callback($matches) {
2448
		$whole_match	= $matches[1];
0 ignored issues
show
Unused Code introduced by
$whole_match is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
2449
		$alt_text		= $matches[2];
2450
		$url			= $matches[3] == '' ? $matches[4] : $matches[3];
2451
		$title			=& $matches[7];
2452
		$attr  = $this->doExtraAttributes("img", $dummy =& $matches[8]);
2453
2454
		$alt_text = $this->encodeAttribute($alt_text);
2455
		$url = $this->encodeAttribute($url);
2456
		$result = "<img src=\"$url\" alt=\"$alt_text\"";
2457
		if (isset($title)) {
2458
			$title = $this->encodeAttribute($title);
2459
			$result .=  " title=\"$title\""; # $title already quoted
2460
		}
2461
		$result .= $attr;
2462
		$result .= $this->empty_element_suffix;
2463
2464
		return $this->hashPart($result);
2465
	}
2466
2467
2468
	function doHeaders($text) {
2469
	#
2470
	# Redefined to add id and class attribute support.
2471
	#
2472
		# Setext-style headers:
2473
		#	  Header 1  {#header1}
2474
		#	  ========
2475
		#
2476
		#	  Header 2  {#header2 .class1 .class2}
2477
		#	  --------
2478
		#
2479
		$text = preg_replace_callback(
2480
			'{
2481
				(^.+?)								# $1: Header text
2482
				(?:[ ]+ '.$this->id_class_attr_catch_re.' )?	 # $3 = id/class attributes
2483
				[ ]*\n(=+|-+)[ ]*\n+				# $3: Header footer
2484
			}mx',
2485
			array(&$this, '_doHeaders_callback_setext'), $text);
2486
2487
		# atx-style headers:
2488
		#	# Header 1        {#header1}
2489
		#	## Header 2       {#header2}
2490
		#	## Header 2 with closing hashes ##  {#header3.class1.class2}
2491
		#	...
2492
		#	###### Header 6   {.class2}
2493
		#
2494
		$text = preg_replace_callback('{
2495
				^(\#{1,6})	# $1 = string of #\'s
2496
				[ ]*
2497
				(.+?)		# $2 = Header text
2498
				[ ]*
2499
				\#*			# optional closing #\'s (not counted)
2500
				(?:[ ]+ '.$this->id_class_attr_catch_re.' )?	 # $3 = id/class attributes
2501
				[ ]*
2502
				\n+
2503
			}xm',
2504
			array(&$this, '_doHeaders_callback_atx'), $text);
2505
2506
		return $text;
2507
	}
2508
	function _doHeaders_callback_setext($matches) {
2509 View Code Duplication
		if ($matches[3] == '-' && preg_match('{^- }', $matches[1]))
2510
			return $matches[0];
2511
		$level = $matches[3][0] == '=' ? 1 : 2;
2512
		$attr  = $this->doExtraAttributes("h$level", $dummy =& $matches[2]);
2513
		$block = "<h$level$attr>".$this->runSpanGamut($matches[1])."</h$level>";
2514
		return "\n" . $this->hashBlock($block) . "\n\n";
2515
	}
2516
	function _doHeaders_callback_atx($matches) {
2517
		$level = strlen($matches[1]);
2518
		$attr  = $this->doExtraAttributes("h$level", $dummy =& $matches[3]);
2519
		$block = "<h$level$attr>".$this->runSpanGamut($matches[2])."</h$level>";
2520
		return "\n" . $this->hashBlock($block) . "\n\n";
2521
	}
2522
2523
2524
	function doTables($text) {
2525
	#
2526
	# Form HTML tables.
2527
	#
2528
		$less_than_tab = $this->tab_width - 1;
2529
		#
2530
		# Find tables with leading pipe.
2531
		#
2532
		#	| Header 1 | Header 2
2533
		#	| -------- | --------
2534
		#	| Cell 1   | Cell 2
2535
		#	| Cell 3   | Cell 4
2536
		#
2537
		$text = preg_replace_callback('
2538
			{
2539
				^							# Start of a line
2540
				[ ]{0,'.$less_than_tab.'}	# Allowed whitespace.
2541
				[|]							# Optional leading pipe (present)
2542
				(.+) \n						# $1: Header row (at least one pipe)
2543
2544
				[ ]{0,'.$less_than_tab.'}	# Allowed whitespace.
2545
				[|] ([ ]*[-:]+[-| :]*) \n	# $2: Header underline
2546
2547
				(							# $3: Cells
2548
					(?>
2549
						[ ]*				# Allowed whitespace.
2550
						[|] .* \n			# Row content.
2551
					)*
2552
				)
2553
				(?=\n|\Z)					# Stop at final double newline.
2554
			}xm',
2555
			array(&$this, '_doTable_leadingPipe_callback'), $text);
2556
2557
		#
2558
		# Find tables without leading pipe.
2559
		#
2560
		#	Header 1 | Header 2
2561
		#	-------- | --------
2562
		#	Cell 1   | Cell 2
2563
		#	Cell 3   | Cell 4
2564
		#
2565
		$text = preg_replace_callback('
2566
			{
2567
				^							# Start of a line
2568
				[ ]{0,'.$less_than_tab.'}	# Allowed whitespace.
2569
				(\S.*[|].*) \n				# $1: Header row (at least one pipe)
2570
2571
				[ ]{0,'.$less_than_tab.'}	# Allowed whitespace.
2572
				([-:]+[ ]*[|][-| :]*) \n	# $2: Header underline
2573
2574
				(							# $3: Cells
2575
					(?>
2576
						.* [|] .* \n		# Row content
2577
					)*
2578
				)
2579
				(?=\n|\Z)					# Stop at final double newline.
2580
			}xm',
2581
			array(&$this, '_DoTable_callback'), $text);
2582
2583
		return $text;
2584
	}
2585
	function _doTable_leadingPipe_callback($matches) {
2586
		$head		= $matches[1];
2587
		$underline	= $matches[2];
2588
		$content	= $matches[3];
2589
2590
		# Remove leading pipe for each row.
2591
		$content	= preg_replace('/^ *[|]/m', '', $content);
2592
2593
		return $this->_doTable_callback(array($matches[0], $head, $underline, $content));
2594
	}
2595
	function _doTable_callback($matches) {
2596
		$head		= $matches[1];
2597
		$underline	= $matches[2];
2598
		$content	= $matches[3];
2599
2600
		# Remove any tailing pipes for each line.
2601
		$head		= preg_replace('/[|] *$/m', '', $head);
2602
		$underline	= preg_replace('/[|] *$/m', '', $underline);
2603
		$content	= preg_replace('/[|] *$/m', '', $content);
2604
2605
		# Reading alignement from header underline.
2606
		$separators	= preg_split('/ *[|] */', $underline);
2607
		foreach ($separators as $n => $s) {
2608
			if (preg_match('/^ *-+: *$/', $s))		$attr[$n] = ' align="right"';
0 ignored issues
show
Coding Style Comprehensibility introduced by
$attr was never initialized. Although not strictly required by PHP, it is generally a good practice to add $attr = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
2609
			else if (preg_match('/^ *:-+: *$/', $s))$attr[$n] = ' align="center"';
0 ignored issues
show
Bug introduced by
The variable $attr does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
2610
			else if (preg_match('/^ *:-+ *$/', $s))	$attr[$n] = ' align="left"';
2611
			else									$attr[$n] = '';
2612
		}
2613
2614
		# Parsing span elements, including code spans, character escapes,
2615
		# and inline HTML tags, so that pipes inside those gets ignored.
2616
		$head		= $this->parseSpan($head);
2617
		$headers	= preg_split('/ *[|] */', $head);
2618
		$col_count	= count($headers);
2619
		$attr       = array_pad($attr, $col_count, '');
2620
2621
		# Write column headers.
2622
		$text = "<table>\n";
2623
		$text .= "<thead>\n";
2624
		$text .= "<tr>\n";
2625
		foreach ($headers as $n => $header)
2626
			$text .= "  <th$attr[$n]>".$this->runSpanGamut(trim($header))."</th>\n";
2627
		$text .= "</tr>\n";
2628
		$text .= "</thead>\n";
2629
2630
		# Split content by row.
2631
		$rows = explode("\n", trim($content, "\n"));
2632
2633
		$text .= "<tbody>\n";
2634
		foreach ($rows as $row) {
2635
			# Parsing span elements, including code spans, character escapes,
2636
			# and inline HTML tags, so that pipes inside those gets ignored.
2637
			$row = $this->parseSpan($row);
2638
2639
			# Split row by cell.
2640
			$row_cells = preg_split('/ *[|] */', $row, $col_count);
2641
			$row_cells = array_pad($row_cells, $col_count, '');
2642
2643
			$text .= "<tr>\n";
2644
			foreach ($row_cells as $n => $cell)
2645
				$text .= "  <td$attr[$n]>".$this->runSpanGamut(trim($cell))."</td>\n";
2646
			$text .= "</tr>\n";
2647
		}
2648
		$text .= "</tbody>\n";
2649
		$text .= "</table>";
2650
2651
		return $this->hashBlock($text) . "\n";
2652
	}
2653
2654
2655
	function doDefLists($text) {
2656
	#
2657
	# Form HTML definition lists.
2658
	#
2659
		$less_than_tab = $this->tab_width - 1;
2660
2661
		# Re-usable pattern to match any entire dl list:
2662
		$whole_list_re = '(?>
2663
			(								# $1 = whole list
2664
			  (								# $2
2665
				[ ]{0,'.$less_than_tab.'}
2666
				((?>.*\S.*\n)+)				# $3 = defined term
2667
				\n?
2668
				[ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition
2669
			  )
2670
			  (?s:.+?)
2671
			  (								# $4
2672
				  \z
2673
				|
2674
				  \n{2,}
2675
				  (?=\S)
2676
				  (?!						# Negative lookahead for another term
2677
					[ ]{0,'.$less_than_tab.'}
2678
					(?: \S.*\n )+?			# defined term
2679
					\n?
2680
					[ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition
2681
				  )
2682
				  (?!						# Negative lookahead for another definition
2683
					[ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition
2684
				  )
2685
			  )
2686
			)
2687
		)'; // mx
2688
2689
		$text = preg_replace_callback('{
2690
				(?>\A\n?|(?<=\n\n))
2691
				'.$whole_list_re.'
2692
			}mx',
2693
			array(&$this, '_doDefLists_callback'), $text);
2694
2695
		return $text;
2696
	}
2697
	function _doDefLists_callback($matches) {
2698
		# Re-usable patterns to match list item bullets and number markers:
2699
		$list = $matches[1];
2700
2701
		# Turn double returns into triple returns, so that we can make a
2702
		# paragraph for the last item in a list, if necessary:
2703
		$result = trim($this->processDefListItems($list));
2704
		$result = "<dl>\n" . $result . "\n</dl>";
2705
		return $this->hashBlock($result) . "\n\n";
2706
	}
2707
2708
2709
	function processDefListItems($list_str) {
2710
	#
2711
	#	Process the contents of a single definition list, splitting it
2712
	#	into individual term and definition list items.
2713
	#
2714
		$less_than_tab = $this->tab_width - 1;
2715
2716
		# trim trailing blank lines:
2717
		$list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str);
2718
2719
		# Process definition terms.
2720
		$list_str = preg_replace_callback('{
2721
			(?>\A\n?|\n\n+)					# leading line
2722
			(								# definition terms = $1
2723
				[ ]{0,'.$less_than_tab.'}	# leading whitespace
2724
				(?!\:[ ]|[ ])				# negative lookahead for a definition
2725
											#   mark (colon) or more whitespace.
2726
				(?> \S.* \n)+?				# actual term (not whitespace).
2727
			)
2728
			(?=\n?[ ]{0,3}:[ ])				# lookahead for following line feed
2729
											#   with a definition mark.
2730
			}xm',
2731
			array(&$this, '_processDefListItems_callback_dt'), $list_str);
2732
2733
		# Process actual definitions.
2734
		$list_str = preg_replace_callback('{
2735
			\n(\n+)?						# leading line = $1
2736
			(								# marker space = $2
2737
				[ ]{0,'.$less_than_tab.'}	# whitespace before colon
2738
				\:[ ]+						# definition mark (colon)
2739
			)
2740
			((?s:.+?))						# definition text = $3
2741
			(?= \n+ 						# stop at next definition mark,
2742
				(?:							# next term or end of text
2743
					[ ]{0,'.$less_than_tab.'} \:[ ]	|
2744
					<dt> | \z
2745
				)
2746
			)
2747
			}xm',
2748
			array(&$this, '_processDefListItems_callback_dd'), $list_str);
2749
2750
		return $list_str;
2751
	}
2752
	function _processDefListItems_callback_dt($matches) {
2753
		$terms = explode("\n", trim($matches[1]));
2754
		$text = '';
2755
		foreach ($terms as $term) {
2756
			$term = $this->runSpanGamut(trim($term));
2757
			$text .= "\n<dt>" . $term . "</dt>";
2758
		}
2759
		return $text . "\n";
2760
	}
2761
	function _processDefListItems_callback_dd($matches) {
2762
		$leading_line	= $matches[1];
2763
		$marker_space	= $matches[2];
2764
		$def			= $matches[3];
2765
2766 View Code Duplication
		if ($leading_line || preg_match('/\n{2,}/', $def)) {
2767
			# Replace marker with the appropriate whitespace indentation
2768
			$def = str_repeat(' ', strlen($marker_space)) . $def;
2769
			$def = $this->runBlockGamut($this->outdent($def . "\n\n"));
2770
			$def = "\n". $def ."\n";
2771
		}
2772
		else {
2773
			$def = rtrim($def);
2774
			$def = $this->runSpanGamut($this->outdent($def));
2775
		}
2776
2777
		return "\n<dd>" . $def . "</dd>\n";
2778
	}
2779
2780
2781 View Code Duplication
	function doFencedCodeBlocks($text) {
2782
	#
2783
	# Adding the fenced code block syntax to regular Markdown:
2784
	#
2785
	# ~~~
2786
	# Code block
2787
	# ~~~
2788
	#
2789
		$less_than_tab = $this->tab_width;
0 ignored issues
show
Unused Code introduced by
$less_than_tab is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
2790
2791
		$text = preg_replace_callback('{
2792
				(?:\n|\A)
2793
				# 1: Opening marker
2794
				(
2795
					(?:~{3,}|`{3,}) # 3 or more tildes/backticks.
2796
				)
2797
				[ ]*
2798
				(?:
2799
					\.?([-_:a-zA-Z0-9]+) # 2: standalone class name
2800
				|
2801
					'.$this->id_class_attr_catch_re.' # 3: Extra attributes
2802
				)?
2803
				[ ]* \n # Whitespace and newline following marker.
2804
2805
				# 4: Content
2806
				(
2807
					(?>
2808
						(?!\1 [ ]* \n)	# Not a closing marker.
2809
						.*\n+
2810
					)+
2811
				)
2812
2813
				# Closing marker.
2814
				\1 [ ]* (?= \n )
2815
			}xm',
2816
			array(&$this, '_doFencedCodeBlocks_callback'), $text);
2817
2818
		return $text;
2819
	}
2820
	function _doFencedCodeBlocks_callback($matches) {
2821
		$classname =& $matches[2];
2822
		$attrs     =& $matches[3];
2823
		$codeblock = $matches[4];
2824
		$codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES);
2825
		$codeblock = preg_replace_callback('/^\n+/',
2826
			array(&$this, '_doFencedCodeBlocks_newlines'), $codeblock);
2827
2828
		if ($classname != "") {
2829
			if ($classname[0] == '.')
2830
				$classname = substr($classname, 1);
2831
			$attr_str = ' class="'.$this->code_class_prefix.$classname.'"';
2832
		} else {
2833
			$attr_str = $this->doExtraAttributes($this->code_attr_on_pre ? "pre" : "code", $attrs);
2834
		}
2835
		$pre_attr_str  = $this->code_attr_on_pre ? $attr_str : '';
2836
		$code_attr_str = $this->code_attr_on_pre ? '' : $attr_str;
2837
		$codeblock  = "<pre$pre_attr_str><code$code_attr_str>$codeblock</code></pre>";
2838
2839
		return "\n\n".$this->hashBlock($codeblock)."\n\n";
2840
	}
2841
	function _doFencedCodeBlocks_newlines($matches) {
2842
		return str_repeat("<br$this->empty_element_suffix",
2843
			strlen($matches[0]));
2844
	}
2845
2846
2847
	#
2848
	# Redefining emphasis markers so that emphasis by underscore does not
2849
	# work in the middle of a word.
2850
	#
2851
	public $em_relist = array(
2852
		''  => '(?:(?<!\*)\*(?!\*)|(?<![a-zA-Z0-9_])_(?!_))(?=\S|$)(?![\.,:;]\s)',
2853
		'*' => '(?<=\S|^)(?<!\*)\*(?!\*)',
2854
		'_' => '(?<=\S|^)(?<!_)_(?![a-zA-Z0-9_])',
2855
		);
2856
	public $strong_relist = array(
2857
		''   => '(?:(?<!\*)\*\*(?!\*)|(?<![a-zA-Z0-9_])__(?!_))(?=\S|$)(?![\.,:;]\s)',
2858
		'**' => '(?<=\S|^)(?<!\*)\*\*(?!\*)',
2859
		'__' => '(?<=\S|^)(?<!_)__(?![a-zA-Z0-9_])',
2860
		);
2861
	public $em_strong_relist = array(
2862
		''    => '(?:(?<!\*)\*\*\*(?!\*)|(?<![a-zA-Z0-9_])___(?!_))(?=\S|$)(?![\.,:;]\s)',
2863
		'***' => '(?<=\S|^)(?<!\*)\*\*\*(?!\*)',
2864
		'___' => '(?<=\S|^)(?<!_)___(?![a-zA-Z0-9_])',
2865
		);
2866
2867
2868
	function formParagraphs($text) {
2869
	#
2870
	#	Params:
2871
	#		$text - string to process with html <p> tags
2872
	#
2873
		# Strip leading and trailing lines:
2874
		$text = preg_replace('/\A\n+|\n+\z/', '', $text);
2875
2876
		$grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY);
2877
2878
		#
2879
		# Wrap <p> tags and unhashify HTML blocks
2880
		#
2881
		foreach ($grafs as $key => $value) {
2882
			$value = trim($this->runSpanGamut($value));
2883
2884
			# Check if this should be enclosed in a paragraph.
2885
			# Clean tag hashes & block tag hashes are left alone.
2886
			$is_p = !preg_match('/^B\x1A[0-9]+B|^C\x1A[0-9]+C$/', $value);
2887
2888
			if ($is_p) {
2889
				$value = "<p>$value</p>";
2890
			}
2891
			$grafs[$key] = $value;
2892
		}
2893
2894
		# Join grafs in one text, then unhash HTML tags.
2895
		$text = implode("\n\n", $grafs);
2896
2897
		# Finish by removing any tag hashes still present in $text.
2898
		$text = $this->unhash($text);
2899
2900
		return $text;
2901
	}
2902
2903
2904
	### Footnotes
2905
2906 View Code Duplication
	function stripFootnotes($text) {
2907
	#
2908
	# Strips link definitions from text, stores the URLs and titles in
2909
	# hash references.
2910
	#
2911
		$less_than_tab = $this->tab_width - 1;
2912
2913
		# Link defs are in the form: [^id]: url "optional title"
2914
		$text = preg_replace_callback('{
2915
			^[ ]{0,'.$less_than_tab.'}\[\^(.+?)\][ ]?:	# note_id = $1
2916
			  [ ]*
2917
			  \n?					# maybe *one* newline
2918
			(						# text = $2 (no blank lines allowed)
2919
				(?:
2920
					.+				# actual text
2921
				|
2922
					\n				# newlines but
2923
					(?!\[\^.+?\]:\s)# negative lookahead for footnote marker.
2924
					(?!\n+[ ]{0,3}\S)# ensure line is not blank and followed
2925
									# by non-indented content
2926
				)*
2927
			)
2928
			}xm',
2929
			array(&$this, '_stripFootnotes_callback'),
2930
			$text);
2931
		return $text;
2932
	}
2933
	function _stripFootnotes_callback($matches) {
2934
		$note_id = $this->fn_id_prefix . $matches[1];
2935
		$this->footnotes[$note_id] = $this->outdent($matches[2]);
2936
		return ''; # String that will replace the block
2937
	}
2938
2939
2940
	function doFootnotes($text) {
2941
	#
2942
	# Replace footnote references in $text [^id] with a special text-token
2943
	# which will be replaced by the actual footnote marker in appendFootnotes.
2944
	#
2945
		if (!$this->in_anchor) {
2946
			$text = preg_replace('{\[\^(.+?)\]}', "F\x1Afn:\\1\x1A:", $text);
2947
		}
2948
		return $text;
2949
	}
2950
2951
2952
	function appendFootnotes($text) {
2953
	#
2954
	# Append footnote list to text.
2955
	#
2956
		$text = preg_replace_callback('{F\x1Afn:(.*?)\x1A:}',
2957
			array(&$this, '_appendFootnotes_callback'), $text);
2958
2959
		if (!empty($this->footnotes_ordered)) {
2960
			$text .= "\n\n";
2961
			$text .= "<div class=\"footnotes\">\n";
2962
			$text .= "<hr". $this->empty_element_suffix ."\n";
2963
			$text .= "<ol>\n\n";
2964
2965
			$attr = "";
2966
			if ($this->fn_backlink_class != "") {
2967
				$class = $this->fn_backlink_class;
2968
				$class = $this->encodeAttribute($class);
2969
				$attr .= " class=\"$class\"";
2970
			}
2971
			if ($this->fn_backlink_title != "") {
2972
				$title = $this->fn_backlink_title;
2973
				$title = $this->encodeAttribute($title);
2974
				$attr .= " title=\"$title\"";
2975
			}
2976
			$num = 0;
2977
2978
			while (!empty($this->footnotes_ordered)) {
2979
				$footnote = reset($this->footnotes_ordered);
2980
				$note_id = key($this->footnotes_ordered);
2981
				unset($this->footnotes_ordered[$note_id]);
2982
				$ref_count = $this->footnotes_ref_count[$note_id];
2983
				unset($this->footnotes_ref_count[$note_id]);
2984
				unset($this->footnotes[$note_id]);
2985
2986
				$footnote .= "\n"; # Need to append newline before parsing.
2987
				$footnote = $this->runBlockGamut("$footnote\n");
2988
				$footnote = preg_replace_callback('{F\x1Afn:(.*?)\x1A:}',
2989
					array(&$this, '_appendFootnotes_callback'), $footnote);
2990
2991
				$attr = str_replace("%%", ++$num, $attr);
2992
				$note_id = $this->encodeAttribute($note_id);
2993
2994
				# Prepare backlink, multiple backlinks if multiple references
2995
				$backlink = "<a href=\"#fnref:$note_id\"$attr>&#8617;</a>";
2996
				for ($ref_num = 2; $ref_num <= $ref_count; ++$ref_num) {
2997
					$backlink .= " <a href=\"#fnref$ref_num:$note_id\"$attr>&#8617;</a>";
2998
				}
2999
				# Add backlink to last paragraph; create new paragraph if needed.
3000
				if (preg_match('{</p>$}', $footnote)) {
3001
					$footnote = substr($footnote, 0, -4) . "&#160;$backlink</p>";
3002
				} else {
3003
					$footnote .= "\n\n<p>$backlink</p>";
3004
				}
3005
3006
				$text .= "<li id=\"fn:$note_id\">\n";
3007
				$text .= $footnote . "\n";
3008
				$text .= "</li>\n\n";
3009
			}
3010
3011
			$text .= "</ol>\n";
3012
			$text .= "</div>";
3013
		}
3014
		return $text;
3015
	}
3016
	function _appendFootnotes_callback($matches) {
3017
		$node_id = $this->fn_id_prefix . $matches[1];
3018
3019
		# Create footnote marker only if it has a corresponding footnote *and*
3020
		# the footnote hasn't been used by another marker.
3021
		if (isset($this->footnotes[$node_id])) {
3022
			$num =& $this->footnotes_numbers[$node_id];
3023
			if (!isset($num)) {
3024
				# Transfer footnote content to the ordered list and give it its
3025
				# number
3026
				$this->footnotes_ordered[$node_id] = $this->footnotes[$node_id];
3027
				$this->footnotes_ref_count[$node_id] = 1;
3028
				$num = $this->footnote_counter++;
3029
				$ref_count_mark = '';
3030
			} else {
3031
				$ref_count_mark = $this->footnotes_ref_count[$node_id] += 1;
3032
			}
3033
3034
			$attr = "";
3035
			if ($this->fn_link_class != "") {
3036
				$class = $this->fn_link_class;
3037
				$class = $this->encodeAttribute($class);
3038
				$attr .= " class=\"$class\"";
3039
			}
3040
			if ($this->fn_link_title != "") {
3041
				$title = $this->fn_link_title;
3042
				$title = $this->encodeAttribute($title);
3043
				$attr .= " title=\"$title\"";
3044
			}
3045
3046
			$attr = str_replace("%%", $num, $attr);
3047
			$node_id = $this->encodeAttribute($node_id);
3048
3049
			return
3050
				"<sup id=\"fnref$ref_count_mark:$node_id\">".
3051
				"<a href=\"#fn:$node_id\"$attr>$num</a>".
3052
				"</sup>";
3053
		}
3054
3055
		return "[^".$matches[1]."]";
3056
	}
3057
3058
3059
	### Abbreviations ###
3060
3061 View Code Duplication
	function stripAbbreviations($text) {
3062
	#
3063
	# Strips abbreviations from text, stores titles in hash references.
3064
	#
3065
		$less_than_tab = $this->tab_width - 1;
3066
3067
		# Link defs are in the form: [id]*: url "optional title"
3068
		$text = preg_replace_callback('{
3069
			^[ ]{0,'.$less_than_tab.'}\*\[(.+?)\][ ]?:	# abbr_id = $1
3070
			(.*)					# text = $2 (no blank lines allowed)
3071
			}xm',
3072
			array(&$this, '_stripAbbreviations_callback'),
3073
			$text);
3074
		return $text;
3075
	}
3076
	function _stripAbbreviations_callback($matches) {
3077
		$abbr_word = $matches[1];
3078
		$abbr_desc = $matches[2];
3079
		if ($this->abbr_word_re)
3080
			$this->abbr_word_re .= '|';
3081
		$this->abbr_word_re .= preg_quote($abbr_word);
3082
		$this->abbr_desciptions[$abbr_word] = trim($abbr_desc);
3083
		return ''; # String that will replace the block
3084
	}
3085
3086
3087
	function doAbbreviations($text) {
3088
	#
3089
	# Find defined abbreviations in text and wrap them in <abbr> elements.
3090
	#
3091
		if ($this->abbr_word_re) {
3092
			// cannot use the /x modifier because abbr_word_re may
3093
			// contain significant spaces:
3094
			$text = preg_replace_callback('{'.
3095
				'(?<![\w\x1A])'.
3096
				'(?:'.$this->abbr_word_re.')'.
3097
				'(?![\w\x1A])'.
3098
				'}',
3099
				array(&$this, '_doAbbreviations_callback'), $text);
3100
		}
3101
		return $text;
3102
	}
3103
	function _doAbbreviations_callback($matches) {
3104
		$abbr = $matches[0];
3105
		if (isset($this->abbr_desciptions[$abbr])) {
3106
			$desc = $this->abbr_desciptions[$abbr];
3107
			if (empty($desc)) {
3108
				return $this->hashPart("<abbr>$abbr</abbr>");
3109
			} else {
3110
				$desc = $this->encodeAttribute($desc);
3111
				return $this->hashPart("<abbr title=\"$desc\">$abbr</abbr>");
3112
			}
3113
		} else {
3114
			return $matches[0];
3115
		}
3116
	}
3117
3118
}
3119
3120
3121
/*
3122
3123
PHP Markdown Extra
3124
==================
3125
3126
Description
3127
-----------
3128
3129
This is a PHP port of the original Markdown formatter written in Perl
3130
by John Gruber. This special "Extra" version of PHP Markdown features
3131
further enhancements to the syntax for making additional constructs
3132
such as tables and definition list.
3133
3134
Markdown is a text-to-HTML filter; it translates an easy-to-read /
3135
easy-to-write structured text format into HTML. Markdown's text format
3136
is mostly similar to that of plain text email, and supports features such
3137
as headers, *emphasis*, code blocks, blockquotes, and links.
3138
3139
Markdown's syntax is designed not as a generic markup language, but
3140
specifically to serve as a front-end to (X)HTML. You can use span-level
3141
HTML tags anywhere in a Markdown document, and you can use block level
3142
HTML tags (like <div> and <table> as well).
3143
3144
For more information about Markdown's syntax, see:
3145
3146
<http://daringfireball.net/projects/markdown/>
3147
3148
3149
Bugs
3150
----
3151
3152
To file bug reports please send email to:
3153
3154
<[email protected]>
3155
3156
Please include with your report: (1) the example input; (2) the output you
3157
expected; (3) the output Markdown actually produced.
3158
3159
3160
Version History
3161
---------------
3162
3163
See the readme file for detailed release notes for this version.
3164
3165
3166
Copyright and License
3167
---------------------
3168
3169
PHP Markdown & Extra
3170
Copyright (c) 2004-2013 Michel Fortin
3171
<http://michelf.ca/>
3172
All rights reserved.
3173
3174
Based on Markdown
3175
Copyright (c) 2003-2006 John Gruber
3176
<http://daringfireball.net/>
3177
All rights reserved.
3178
3179
Redistribution and use in source and binary forms, with or without
3180
modification, are permitted provided that the following conditions are
3181
met:
3182
3183
*	Redistributions of source code must retain the above copyright notice,
3184
	this list of conditions and the following disclaimer.
3185
3186
*	Redistributions in binary form must reproduce the above copyright
3187
	notice, this list of conditions and the following disclaimer in the
3188
	documentation and/or other materials provided with the distribution.
3189
3190
*	Neither the name "Markdown" nor the names of its contributors may
3191
	be used to endorse or promote products derived from this software
3192
	without specific prior written permission.
3193
3194
This software is provided by the copyright holders and contributors "as
3195
is" and any express or implied warranties, including, but not limited
3196
to, the implied warranties of merchantability and fitness for a
3197
particular purpose are disclaimed. In no event shall the copyright owner
3198
or contributors be liable for any direct, indirect, incidental, special,
3199
exemplary, or consequential damages (including, but not limited to,
3200
procurement of substitute goods or services; loss of use, data, or
3201
profits; or business interruption) however caused and on any theory of
3202
liability, whether in contract, strict liability, or tort (including
3203
negligence or otherwise) arising in any way out of the use of this
3204
software, even if advised of the possibility of such damage.
3205
3206
*/
3207
?>
0 ignored issues
show
Best Practice introduced by
It is not recommended to use PHP's closing tag ?> in files other than templates.

Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore.

A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever.

Loading history...
3208