Completed
Push — branch-4.0 ( fc0d6f...bf547c )
by
unknown
08:22
created

Markdown_Parser::doItalicsAndBold()   D

Complexity

Conditions 16
Paths 3

Size

Total Lines 121
Code Lines 83

Duplication

Lines 13
Ratio 10.74 %

Importance

Changes 0
Metric Value
cc 16
eloc 83
nc 3
nop 1
dl 13
loc 121
rs 4.8736
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
530
		return $this->hashPart("<br$this->empty_element_suffix\n");
531
	}
532
533
534 View Code Duplication
	function doAnchors($text) {
535
	#
536
	# Turn Markdown link shortcuts into XHTML <a> tags.
537
	#
538
		if ($this->in_anchor) return $text;
539
		$this->in_anchor = true;
540
541
		#
542
		# First, handle reference-style links: [link text] [id]
543
		#
544
		$text = preg_replace_callback('{
545
			(					# wrap whole match in $1
546
			  \[
547
				('.$this->nested_brackets_re.')	# link text = $2
548
			  \]
549
550
			  [ ]?				# one optional space
551
			  (?:\n[ ]*)?		# one optional newline followed by spaces
552
553
			  \[
554
				(.*?)		# id = $3
555
			  \]
556
			)
557
			}xs',
558
			array(&$this, '_doAnchors_reference_callback'), $text);
559
560
		#
561
		# Next, inline-style links: [link text](url "optional title")
562
		#
563
		$text = preg_replace_callback('{
564
			(				# wrap whole match in $1
565
			  \[
566
				('.$this->nested_brackets_re.')	# link text = $2
567
			  \]
568
			  \(			# literal paren
569
				[ \n]*
570
				(?:
571
					<(.+?)>	# href = $3
572
				|
573
					('.$this->nested_url_parenthesis_re.')	# href = $4
574
				)
575
				[ \n]*
576
				(			# $5
577
				  ([\'"])	# quote char = $6
578
				  (.*?)		# Title = $7
579
				  \6		# matching quote
580
				  [ \n]*	# ignore any spaces/tabs between closing quote and )
581
				)?			# title is optional
582
			  \)
583
			)
584
			}xs',
585
			array(&$this, '_doAnchors_inline_callback'), $text);
586
587
		#
588
		# Last, handle reference-style shortcuts: [link text]
589
		# These must come last in case you've also got [link text][1]
590
		# or [link text](/foo)
591
		#
592
		$text = preg_replace_callback('{
593
			(					# wrap whole match in $1
594
			  \[
595
				([^\[\]]+)		# link text = $2; can\'t contain [ or ]
596
			  \]
597
			)
598
			}xs',
599
			array(&$this, '_doAnchors_reference_callback'), $text);
600
601
		$this->in_anchor = false;
602
		return $text;
603
	}
604
	function _doAnchors_reference_callback($matches) {
605
		$whole_match =  $matches[1];
606
		$link_text   =  $matches[2];
607
		$link_id     =& $matches[3];
608
609
		if ($link_id == "") {
610
			# for shortcut links like [this][] or [this].
611
			$link_id = $link_text;
612
		}
613
614
		# lower-case and turn embedded newlines into spaces
615
		$link_id = strtolower($link_id);
616
		$link_id = preg_replace('{[ ]?\n}', ' ', $link_id);
617
618
		if (isset($this->urls[$link_id])) {
619
			$url = $this->urls[$link_id];
620
			$url = $this->encodeAttribute($url);
621
622
			$result = "<a href=\"$url\"";
623 View Code Duplication
			if ( isset( $this->titles[$link_id] ) ) {
624
				$title = $this->titles[$link_id];
625
				$title = $this->encodeAttribute($title);
626
				$result .=  " title=\"$title\"";
627
			}
628
629
			$link_text = $this->runSpanGamut($link_text);
630
			$result .= ">$link_text</a>";
631
			$result = $this->hashPart($result);
632
		}
633
		else {
634
			$result = $whole_match;
635
		}
636
		return $result;
637
	}
638 View Code Duplication
	function _doAnchors_inline_callback($matches) {
639
		$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...
640
		$link_text		=  $this->runSpanGamut($matches[2]);
641
		$url			=  $matches[3] == '' ? $matches[4] : $matches[3];
642
		$title			=& $matches[7];
643
644
		$url = $this->encodeAttribute($url);
645
646
		$result = "<a href=\"$url\"";
647
		if (isset($title)) {
648
			$title = $this->encodeAttribute($title);
649
			$result .=  " title=\"$title\"";
650
		}
651
652
		$link_text = $this->runSpanGamut($link_text);
653
		$result .= ">$link_text</a>";
654
655
		return $this->hashPart($result);
656
	}
657
658
659 View Code Duplication
	function doImages($text) {
660
	#
661
	# Turn Markdown image shortcuts into <img> tags.
662
	#
663
		#
664
		# First, handle reference-style labeled images: ![alt text][id]
665
		#
666
		$text = preg_replace_callback('{
667
			(				# wrap whole match in $1
668
			  !\[
669
				('.$this->nested_brackets_re.')		# alt text = $2
670
			  \]
671
672
			  [ ]?				# one optional space
673
			  (?:\n[ ]*)?		# one optional newline followed by spaces
674
675
			  \[
676
				(.*?)		# id = $3
677
			  \]
678
679
			)
680
			}xs',
681
			array(&$this, '_doImages_reference_callback'), $text);
682
683
		#
684
		# Next, handle inline images:  ![alt text](url "optional title")
685
		# Don't forget: encode * and _
686
		#
687
		$text = preg_replace_callback('{
688
			(				# wrap whole match in $1
689
			  !\[
690
				('.$this->nested_brackets_re.')		# alt text = $2
691
			  \]
692
			  \s?			# One optional whitespace character
693
			  \(			# literal paren
694
				[ \n]*
695
				(?:
696
					<(\S*)>	# src url = $3
697
				|
698
					('.$this->nested_url_parenthesis_re.')	# src url = $4
699
				)
700
				[ \n]*
701
				(			# $5
702
				  ([\'"])	# quote char = $6
703
				  (.*?)		# title = $7
704
				  \6		# matching quote
705
				  [ \n]*
706
				)?			# title is optional
707
			  \)
708
			)
709
			}xs',
710
			array(&$this, '_doImages_inline_callback'), $text);
711
712
		return $text;
713
	}
714
	function _doImages_reference_callback($matches) {
715
		$whole_match = $matches[1];
716
		$alt_text    = $matches[2];
717
		$link_id     = strtolower($matches[3]);
718
719
		if ($link_id == "") {
720
			$link_id = strtolower($alt_text); # for shortcut links like ![this][].
721
		}
722
723
		$alt_text = $this->encodeAttribute($alt_text);
724
		if (isset($this->urls[$link_id])) {
725
			$url = $this->encodeAttribute($this->urls[$link_id]);
726
			$result = "<img src=\"$url\" alt=\"$alt_text\"";
727 View Code Duplication
			if (isset($this->titles[$link_id])) {
728
				$title = $this->titles[$link_id];
729
				$title = $this->encodeAttribute($title);
730
				$result .=  " title=\"$title\"";
731
			}
732
			$result .= $this->empty_element_suffix;
733
			$result = $this->hashPart($result);
734
		}
735
		else {
736
			# If there's no such link ID, leave intact:
737
			$result = $whole_match;
738
		}
739
740
		return $result;
741
	}
742 View Code Duplication
	function _doImages_inline_callback($matches) {
743
		$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...
744
		$alt_text		= $matches[2];
745
		$url			= $matches[3] == '' ? $matches[4] : $matches[3];
746
		$title			=& $matches[7];
747
748
		$alt_text = $this->encodeAttribute($alt_text);
749
		$url = $this->encodeAttribute($url);
750
		$result = "<img src=\"$url\" alt=\"$alt_text\"";
751
		if (isset($title)) {
752
			$title = $this->encodeAttribute($title);
753
			$result .=  " title=\"$title\""; # $title already quoted
754
		}
755
		$result .= $this->empty_element_suffix;
756
757
		return $this->hashPart($result);
758
	}
759
760
761
	function doHeaders($text) {
762
		# Setext-style headers:
763
		#	  Header 1
764
		#	  ========
765
		#
766
		#	  Header 2
767
		#	  --------
768
		#
769
		$text = preg_replace_callback('{ ^(.+?)[ ]*\n(=+|-+)[ ]*\n+ }mx',
770
			array(&$this, '_doHeaders_callback_setext'), $text);
771
772
		# atx-style headers:
773
		#	# Header 1
774
		#	## Header 2
775
		#	## Header 2 with closing hashes ##
776
		#	...
777
		#	###### Header 6
778
		#
779
		$text = preg_replace_callback('{
780
				^(\#{1,6})	# $1 = string of #\'s
781
				[ ]*
782
				(.+?)		# $2 = Header text
783
				[ ]*
784
				\#*			# optional closing #\'s (not counted)
785
				\n+
786
			}xm',
787
			array(&$this, '_doHeaders_callback_atx'), $text);
788
789
		return $text;
790
	}
791
	function _doHeaders_callback_setext($matches) {
792
		# Terrible hack to check we haven't found an empty list item.
793 View Code Duplication
		if ($matches[2] == '-' && preg_match('{^-(?: |$)}', $matches[1]))
794
			return $matches[0];
795
796
		$level = $matches[2]{0} == '=' ? 1 : 2;
797
		$block = "<h$level>".$this->runSpanGamut($matches[1])."</h$level>";
798
		return "\n" . $this->hashBlock($block) . "\n\n";
799
	}
800
	function _doHeaders_callback_atx($matches) {
801
		$level = strlen($matches[1]);
802
		$block = "<h$level>".$this->runSpanGamut($matches[2])."</h$level>";
803
		return "\n" . $this->hashBlock($block) . "\n\n";
804
	}
805
806
807
	function doLists($text) {
808
	#
809
	# Form HTML ordered (numbered) and unordered (bulleted) lists.
810
	#
811
		$less_than_tab = $this->tab_width - 1;
812
813
		# Re-usable patterns to match list item bullets and number markers:
814
		$marker_ul_re  = '[*+-]';
815
		$marker_ol_re  = '\d+[\.]';
816
		$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...
817
818
		$markers_relist = array(
819
			$marker_ul_re => $marker_ol_re,
820
			$marker_ol_re => $marker_ul_re,
821
			);
822
823
		foreach ($markers_relist as $marker_re => $other_marker_re) {
824
			# Re-usable pattern to match any entirel ul or ol list:
825
			$whole_list_re = '
826
				(								# $1 = whole list
827
				  (								# $2
828
					([ ]{0,'.$less_than_tab.'})	# $3 = number of spaces
829
					('.$marker_re.')			# $4 = first list item marker
830
					[ ]+
831
				  )
832
				  (?s:.+?)
833
				  (								# $5
834
					  \z
835
					|
836
					  \n{2,}
837
					  (?=\S)
838
					  (?!						# Negative lookahead for another list item marker
839
						[ ]*
840
						'.$marker_re.'[ ]+
841
					  )
842
					|
843
					  (?=						# Lookahead for another kind of list
844
					    \n
845
						\3						# Must have the same indentation
846
						'.$other_marker_re.'[ ]+
847
					  )
848
				  )
849
				)
850
			'; // mx
851
852
			# We use a different prefix before nested lists than top-level lists.
853
			# See extended comment in _ProcessListItems().
854
855
			if ($this->list_level) {
856
				$text = preg_replace_callback('{
857
						^
858
						'.$whole_list_re.'
859
					}mx',
860
					array(&$this, '_doLists_callback'), $text);
861
			}
862
			else {
863
				$text = preg_replace_callback('{
864
						(?:(?<=\n)\n|\A\n?) # Must eat the newline
865
						'.$whole_list_re.'
866
					}mx',
867
					array(&$this, '_doLists_callback'), $text);
868
			}
869
		}
870
871
		return $text;
872
	}
873
	function _doLists_callback($matches) {
874
		# Re-usable patterns to match list item bullets and number markers:
875
		$marker_ul_re  = '[*+-]';
876
		$marker_ol_re  = '\d+[\.]';
877
		$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...
878
879
		$list = $matches[1];
880
		$list_type = preg_match("/$marker_ul_re/", $matches[4]) ? "ul" : "ol";
881
882
		$marker_any_re = ( $list_type == "ul" ? $marker_ul_re : $marker_ol_re );
883
884
		$list .= "\n";
885
		$result = $this->processListItems($list, $marker_any_re);
886
887
		$result = $this->hashBlock("<$list_type>\n" . $result . "</$list_type>");
888
		return "\n". $result ."\n\n";
889
	}
890
891
	public $list_level = 0;
892
893
	function processListItems($list_str, $marker_any_re) {
894
	#
895
	#	Process the contents of a single ordered or unordered list, splitting it
896
	#	into individual list items.
897
	#
898
		# The $this->list_level global keeps track of when we're inside a list.
899
		# Each time we enter a list, we increment it; when we leave a list,
900
		# we decrement. If it's zero, we're not in a list anymore.
901
		#
902
		# We do this because when we're not inside a list, we want to treat
903
		# something like this:
904
		#
905
		#		I recommend upgrading to version
906
		#		8. Oops, now this line is treated
907
		#		as a sub-list.
908
		#
909
		# As a single paragraph, despite the fact that the second line starts
910
		# with a digit-period-space sequence.
911
		#
912
		# Whereas when we're inside a list (or sub-list), that line will be
913
		# treated as the start of a sub-list. What a kludge, huh? This is
914
		# an aspect of Markdown's syntax that's hard to parse perfectly
915
		# without resorting to mind-reading. Perhaps the solution is to
916
		# change the syntax rules such that sub-lists must start with a
917
		# starting cardinal number; e.g. "1." or "a.".
918
919
		$this->list_level++;
920
921
		# trim trailing blank lines:
922
		$list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str);
923
924
		$list_str = preg_replace_callback('{
925
			(\n)?							# leading line = $1
926
			(^[ ]*)							# leading whitespace = $2
927
			('.$marker_any_re.'				# list marker and space = $3
928
				(?:[ ]+|(?=\n))	# space only required if item is not empty
929
			)
930
			((?s:.*?))						# list item text   = $4
931
			(?:(\n+(?=\n))|\n)				# tailing blank line = $5
932
			(?= \n* (\z | \2 ('.$marker_any_re.') (?:[ ]+|(?=\n))))
933
			}xm',
934
			array(&$this, '_processListItems_callback'), $list_str);
935
936
		$this->list_level--;
937
		return $list_str;
938
	}
939
	function _processListItems_callback($matches) {
940
		$item = $matches[4];
941
		$leading_line =& $matches[1];
942
		$leading_space =& $matches[2];
943
		$marker_space = $matches[3];
944
		$tailing_blank_line =& $matches[5];
945
946 View Code Duplication
		if ($leading_line || $tailing_blank_line ||
947
			preg_match('/\n{2,}/', $item))
948
		{
949
			# Replace marker with the appropriate whitespace indentation
950
			$item = $leading_space . str_repeat(' ', strlen($marker_space)) . $item;
951
			$item = $this->runBlockGamut($this->outdent($item)."\n");
952
		}
953
		else {
954
			# Recursion for sub-lists:
955
			$item = $this->doLists($this->outdent($item));
956
			$item = preg_replace('/\n+$/', '', $item);
957
			$item = $this->runSpanGamut($item);
958
		}
959
960
		return "<li>" . $item . "</li>\n";
961
	}
962
963
964
	function doCodeBlocks($text) {
965
	#
966
	#	Process Markdown `<pre><code>` blocks.
967
	#
968
		$text = preg_replace_callback('{
969
				(?:\n\n|\A\n?)
970
				(	            # $1 = the code block -- one or more lines, starting with a space/tab
971
				  (?>
972
					[ ]{'.$this->tab_width.'}  # Lines must start with a tab or a tab-width of spaces
973
					.*\n+
974
				  )+
975
				)
976
				((?=^[ ]{0,'.$this->tab_width.'}\S)|\Z)	# Lookahead for non-space at line-start, or end of doc
977
			}xm',
978
			array(&$this, '_doCodeBlocks_callback'), $text);
979
980
		return $text;
981
	}
982
	function _doCodeBlocks_callback($matches) {
983
		$codeblock = $matches[1];
984
985
		$codeblock = $this->outdent($codeblock);
986
		$codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES);
987
988
		# trim leading newlines and trailing newlines
989
		$codeblock = preg_replace('/\A\n+|\n+\z/', '', $codeblock);
990
991
		$codeblock = "<pre><code>$codeblock\n</code></pre>";
992
		return "\n\n".$this->hashBlock($codeblock)."\n\n";
993
	}
994
995
996
	function makeCodeSpan($code) {
997
	#
998
	# Create a code span markup for $code. Called from handleSpanToken.
999
	#
1000
		$code = htmlspecialchars(trim($code), ENT_NOQUOTES);
1001
		return $this->hashPart("<code>$code</code>");
1002
	}
1003
1004
1005
	public $em_relist = array(
1006
		''  => '(?:(?<!\*)\*(?!\*)|(?<!_)_(?!_))(?=\S|$)(?![\.,:;]\s)',
1007
		'*' => '(?<=\S|^)(?<!\*)\*(?!\*)',
1008
		'_' => '(?<=\S|^)(?<!_)_(?!_)',
1009
		);
1010
	public $strong_relist = array(
1011
		''   => '(?:(?<!\*)\*\*(?!\*)|(?<!_)__(?!_))(?=\S|$)(?![\.,:;]\s)',
1012
		'**' => '(?<=\S|^)(?<!\*)\*\*(?!\*)',
1013
		'__' => '(?<=\S|^)(?<!_)__(?!_)',
1014
		);
1015
	public $em_strong_relist = array(
1016
		''    => '(?:(?<!\*)\*\*\*(?!\*)|(?<!_)___(?!_))(?=\S|$)(?![\.,:;]\s)',
1017
		'***' => '(?<=\S|^)(?<!\*)\*\*\*(?!\*)',
1018
		'___' => '(?<=\S|^)(?<!_)___(?!_)',
1019
		);
1020
	public $em_strong_prepared_relist;
1021
1022
	function prepareItalicsAndBold() {
1023
	#
1024
	# Prepare regular expressions for searching emphasis tokens in any
1025
	# context.
1026
	#
1027
		foreach ($this->em_relist as $em => $em_re) {
1028
			foreach ($this->strong_relist as $strong => $strong_re) {
1029
				# Construct list of allowed token expressions.
1030
				$token_relist = array();
1031
				if (isset($this->em_strong_relist["$em$strong"])) {
1032
					$token_relist[] = $this->em_strong_relist["$em$strong"];
1033
				}
1034
				$token_relist[] = $em_re;
1035
				$token_relist[] = $strong_re;
1036
1037
				# Construct master expression from list.
1038
				$token_re = '{('. implode('|', $token_relist) .')}';
1039
				$this->em_strong_prepared_relist["$em$strong"] = $token_re;
1040
			}
1041
		}
1042
	}
1043
1044
	function doItalicsAndBold($text) {
1045
		$token_stack = array('');
1046
		$text_stack = array('');
1047
		$em = '';
1048
		$strong = '';
1049
		$tree_char_em = false;
1050
1051
		while (1) {
1052
			#
1053
			# Get prepared regular expression for seraching emphasis tokens
1054
			# in current context.
1055
			#
1056
			$token_re = $this->em_strong_prepared_relist["$em$strong"];
1057
1058
			#
1059
			# Each loop iteration search for the next emphasis token.
1060
			# Each token is then passed to handleSpanToken.
1061
			#
1062
			$parts = preg_split($token_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE);
1063
			$text_stack[0] .= $parts[0];
1064
			$token =& $parts[1];
1065
			$text =& $parts[2];
1066
1067 View Code Duplication
			if (empty($token)) {
1068
				# Reached end of text span: empty stack without emitting.
1069
				# any more emphasis.
1070
				while ($token_stack[0]) {
1071
					$text_stack[1] .= array_shift($token_stack);
1072
					$text_stack[0] .= array_shift($text_stack);
1073
				}
1074
				break;
1075
			}
1076
1077
			$token_len = strlen($token);
1078
			if ($tree_char_em) {
1079
				# Reached closing marker while inside a three-char emphasis.
1080
				if ($token_len == 3) {
1081
					# Three-char closing marker, close em and strong.
1082
					array_shift($token_stack);
1083
					$span = array_shift($text_stack);
1084
					$span = $this->runSpanGamut($span);
1085
					$span = "<strong><em>$span</em></strong>";
1086
					$text_stack[0] .= $this->hashPart($span);
1087
					$em = '';
1088
					$strong = '';
1089
				} else {
1090
					# Other closing marker: close one em or strong and
1091
					# change current token state to match the other
1092
					$token_stack[0] = str_repeat($token{0}, 3-$token_len);
1093
					$tag = $token_len == 2 ? "strong" : "em";
1094
					$span = $text_stack[0];
1095
					$span = $this->runSpanGamut($span);
1096
					$span = "<$tag>$span</$tag>";
1097
					$text_stack[0] = $this->hashPart($span);
1098
					$$tag = ''; # $$tag stands for $em or $strong
1099
				}
1100
				$tree_char_em = false;
1101
			} else if ($token_len == 3) {
1102
				if ($em) {
1103
					# Reached closing marker for both em and strong.
1104
					# Closing strong marker:
1105
					for ($i = 0; $i < 2; ++$i) {
1106
						$shifted_token = array_shift($token_stack);
1107
						$tag = strlen($shifted_token) == 2 ? "strong" : "em";
1108
						$span = array_shift($text_stack);
1109
						$span = $this->runSpanGamut($span);
1110
						$span = "<$tag>$span</$tag>";
1111
						$text_stack[0] .= $this->hashPart($span);
1112
						$$tag = ''; # $$tag stands for $em or $strong
1113
					}
1114
				} else {
1115
					# Reached opening three-char emphasis marker. Push on token
1116
					# stack; will be handled by the special condition above.
1117
					$em = $token{0};
1118
					$strong = "$em$em";
1119
					array_unshift($token_stack, $token);
1120
					array_unshift($text_stack, '');
1121
					$tree_char_em = true;
1122
				}
1123
			} else if ($token_len == 2) {
1124
				if ($strong) {
1125
					# Unwind any dangling emphasis marker:
1126 View Code Duplication
					if (strlen($token_stack[0]) == 1) {
1127
						$text_stack[1] .= array_shift($token_stack);
1128
						$text_stack[0] .= array_shift($text_stack);
1129
					}
1130
					# Closing strong marker:
1131
					array_shift($token_stack);
1132
					$span = array_shift($text_stack);
1133
					$span = $this->runSpanGamut($span);
1134
					$span = "<strong>$span</strong>";
1135
					$text_stack[0] .= $this->hashPart($span);
1136
					$strong = '';
1137
				} else {
1138
					array_unshift($token_stack, $token);
1139
					array_unshift($text_stack, '');
1140
					$strong = $token;
1141
				}
1142
			} else {
1143
				# Here $token_len == 1
1144
				if ($em) {
1145
					if (strlen($token_stack[0]) == 1) {
1146
						# Closing emphasis marker:
1147
						array_shift($token_stack);
1148
						$span = array_shift($text_stack);
1149
						$span = $this->runSpanGamut($span);
1150
						$span = "<em>$span</em>";
1151
						$text_stack[0] .= $this->hashPart($span);
1152
						$em = '';
1153
					} else {
1154
						$text_stack[0] .= $token;
1155
					}
1156
				} else {
1157
					array_unshift($token_stack, $token);
1158
					array_unshift($text_stack, '');
1159
					$em = $token;
1160
				}
1161
			}
1162
		}
1163
		return $text_stack[0];
1164
	}
1165
1166
1167
	function doBlockQuotes($text) {
1168
		$text = preg_replace_callback('/
1169
			  (								# Wrap whole match in $1
1170
				(?>
1171
				  ^[ ]*>[ ]?			# ">" at the start of a line
1172
					.+\n					# rest of the first line
1173
				  (.+\n)*					# subsequent consecutive lines
1174
				  \n*						# blanks
1175
				)+
1176
			  )
1177
			/xm',
1178
			array(&$this, '_doBlockQuotes_callback'), $text);
1179
1180
		return $text;
1181
	}
1182
	function _doBlockQuotes_callback($matches) {
1183
		$bq = $matches[1];
1184
		# trim one level of quoting - trim whitespace-only lines
1185
		$bq = preg_replace('/^[ ]*>[ ]?|^[ ]+$/m', '', $bq);
1186
		$bq = $this->runBlockGamut($bq);		# recurse
1187
1188
		$bq = preg_replace('/^/m', "  ", $bq);
1189
		# These leading spaces cause problem with <pre> content,
1190
		# so we need to fix that:
1191
		$bq = preg_replace_callback('{(\s*<pre>.+?</pre>)}sx',
1192
			array(&$this, '_doBlockQuotes_callback2'), $bq);
1193
1194
		return "\n". $this->hashBlock("<blockquote>\n$bq\n</blockquote>")."\n\n";
1195
	}
1196
	function _doBlockQuotes_callback2($matches) {
1197
		$pre = $matches[1];
1198
		$pre = preg_replace('/^  /m', '', $pre);
1199
		return $pre;
1200
	}
1201
1202
1203
	function formParagraphs($text) {
1204
	#
1205
	#	Params:
1206
	#		$text - string to process with html <p> tags
1207
	#
1208
		# Strip leading and trailing lines:
1209
		$text = preg_replace('/\A\n+|\n+\z/', '', $text);
1210
1211
		$grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY);
1212
1213
		#
1214
		# Wrap <p> tags and unhashify HTML blocks
1215
		#
1216
		foreach ($grafs as $key => $value) {
1217
			if (!preg_match('/^B\x1A[0-9]+B$/', $value)) {
1218
				# Is a paragraph.
1219
				$value = $this->runSpanGamut($value);
1220
				$value = preg_replace('/^([ ]*)/', "<p>", $value);
1221
				$value .= "</p>";
1222
				$grafs[$key] = $this->unhash($value);
1223
			}
1224
			else {
1225
				# Is a block.
1226
				# Modify elements of @grafs in-place...
1227
				$graf = $value;
1228
				$block = $this->html_hashes[$graf];
1229
				$graf = $block;
1230
//				if (preg_match('{
1231
//					\A
1232
//					(							# $1 = <div> tag
1233
//					  <div  \s+
1234
//					  [^>]*
1235
//					  \b
1236
//					  markdown\s*=\s*  ([\'"])	#	$2 = attr quote char
1237
//					  1
1238
//					  \2
1239
//					  [^>]*
1240
//					  >
1241
//					)
1242
//					(							# $3 = contents
1243
//					.*
1244
//					)
1245
//					(</div>)					# $4 = closing tag
1246
//					\z
1247
//					}xs', $block, $matches))
1248
//				{
1249
//					list(, $div_open, , $div_content, $div_close) = $matches;
1250
//
1251
//					# We can't call Markdown(), because that resets the hash;
1252
//					# that initialization code should be pulled into its own sub, though.
1253
//					$div_content = $this->hashHTMLBlocks($div_content);
1254
//
1255
//					# Run document gamut methods on the content.
1256
//					foreach ($this->document_gamut as $method => $priority) {
1257
//						$div_content = $this->$method($div_content);
1258
//					}
1259
//
1260
//					$div_open = preg_replace(
1261
//						'{\smarkdown\s*=\s*([\'"]).+?\1}', '', $div_open);
1262
//
1263
//					$graf = $div_open . "\n" . $div_content . "\n" . $div_close;
1264
//				}
1265
				$grafs[$key] = $graf;
1266
			}
1267
		}
1268
1269
		return implode("\n\n", $grafs);
1270
	}
1271
1272
1273
	function encodeAttribute($text) {
1274
	#
1275
	# Encode text for a double-quoted HTML attribute. This function
1276
	# is *not* suitable for attributes enclosed in single quotes.
1277
	#
1278
		$text = $this->encodeAmpsAndAngles($text);
1279
		$text = str_replace('"', '&quot;', $text);
1280
		return $text;
1281
	}
1282
1283
1284
	function encodeAmpsAndAngles($text) {
1285
	#
1286
	# Smart processing for ampersands and angle brackets that need to
1287
	# be encoded. Valid character entities are left alone unless the
1288
	# no-entities mode is set.
1289
	#
1290
		if ($this->no_entities) {
1291
			$text = str_replace('&', '&amp;', $text);
1292
		} else {
1293
			# Ampersand-encoding based entirely on Nat Irons's Amputator
1294
			# MT plugin: <http://bumppo.net/projects/amputator/>
1295
			$text = preg_replace('/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/',
1296
								'&amp;', $text);;
0 ignored issues
show
Coding Style introduced by
It is generally recommended to place each PHP statement on a line by itself.

Let’s take a look at an example:

// Bad
$a = 5; $b = 6; $c = 7;

// Good
$a = 5;
$b = 6;
$c = 7;
Loading history...
1297
		}
1298
		# Encode remaining <'s
1299
		$text = str_replace('<', '&lt;', $text);
1300
1301
		return $text;
1302
	}
1303
1304
1305
	function doAutoLinks($text) {
1306
		$text = preg_replace_callback('{<((https?|ftp|dict):[^\'">\s]+)>}i',
1307
			array(&$this, '_doAutoLinks_url_callback'), $text);
1308
1309
		# Email addresses: <[email protected]>
1310
		$text = preg_replace_callback('{
1311
			<
1312
			(?:mailto:)?
1313
			(
1314
				(?:
1315
					[-!#$%&\'*+/=?^_`.{|}~\w\x80-\xFF]+
1316
				|
1317
					".*?"
1318
				)
1319
				\@
1320
				(?:
1321
					[-a-z0-9\x80-\xFF]+(\.[-a-z0-9\x80-\xFF]+)*\.[a-z]+
1322
				|
1323
					\[[\d.a-fA-F:]+\]	# IPv4 & IPv6
1324
				)
1325
			)
1326
			>
1327
			}xi',
1328
			array(&$this, '_doAutoLinks_email_callback'), $text);
1329
		$text = preg_replace_callback('{<(tel:([^\'">\s]+))>}i',array(&$this, '_doAutoLinks_tel_callback'), $text);
1330
1331
		return $text;
1332
	}
1333
	function _doAutoLinks_tel_callback($matches) {
1334
		$url = $this->encodeAttribute($matches[1]);
1335
		$tel = $this->encodeAttribute($matches[2]);
1336
		$link = "<a href=\"$url\">$tel</a>";
1337
		return $this->hashPart($link);
1338
	}
1339
	function _doAutoLinks_url_callback($matches) {
1340
		$url = $this->encodeAttribute($matches[1]);
1341
		$link = "<a href=\"$url\">$url</a>";
1342
		return $this->hashPart($link);
1343
	}
1344
	function _doAutoLinks_email_callback($matches) {
1345
		$address = $matches[1];
1346
		$link = $this->encodeEmailAddress($address);
1347
		return $this->hashPart($link);
1348
	}
1349
1350
1351
	function encodeEmailAddress($addr) {
1352
	#
1353
	#	Input: an email address, e.g. "[email protected]"
1354
	#
1355
	#	Output: the email address as a mailto link, with each character
1356
	#		of the address encoded as either a decimal or hex entity, in
1357
	#		the hopes of foiling most address harvesting spam bots. E.g.:
1358
	#
1359
	#	  <p><a href="&#109;&#x61;&#105;&#x6c;&#116;&#x6f;&#58;&#x66;o&#111;
1360
	#        &#x40;&#101;&#x78;&#97;&#x6d;&#112;&#x6c;&#101;&#46;&#x63;&#111;
1361
	#        &#x6d;">&#x66;o&#111;&#x40;&#101;&#x78;&#97;&#x6d;&#112;&#x6c;
1362
	#        &#101;&#46;&#x63;&#111;&#x6d;</a></p>
1363
	#
1364
	#	Based by a filter by Matthew Wickline, posted to BBEdit-Talk.
1365
	#   With some optimizations by Milian Wolff.
1366
	#
1367
		$addr = "mailto:" . $addr;
1368
		$chars = preg_split('/(?<!^)(?!$)/', $addr);
1369
		$seed = (int)abs(crc32($addr) / strlen($addr)); # Deterministic seed.
1370
1371
		foreach ($chars as $key => $char) {
1372
			$ord = ord($char);
1373
			# Ignore non-ascii chars.
1374
			if ($ord < 128) {
1375
				$r = ($seed * (1 + $key)) % 100; # Pseudo-random function.
1376
				# roughly 10% raw, 45% hex, 45% dec
1377
				# '@' *must* be encoded. I insist.
1378
				if ($r > 90 && $char != '@') /* do nothing */;
1379
				else if ($r < 45) $chars[$key] = '&#x'.dechex($ord).';';
1380
				else              $chars[$key] = '&#'.$ord.';';
1381
			}
1382
		}
1383
1384
		$addr = implode('', $chars);
1385
		$text = implode('', array_slice($chars, 7)); # text without `mailto:`
1386
		$addr = "<a href=\"$addr\">$text</a>";
1387
1388
		return $addr;
1389
	}
1390
1391
1392
	function parseSpan($str) {
1393
	#
1394
	# Take the string $str and parse it into tokens, hashing embedded HTML,
1395
	# escaped characters and handling code spans.
1396
	#
1397
		$output = '';
1398
1399
		$span_re = '{
1400
				(
1401
					\\\\'.$this->escape_chars_re.'
1402
				|
1403
					(?<![`\\\\])
1404
					`+						# code span marker
1405
			'.( $this->no_markup ? '' : '
1406
				|
1407
					<!--    .*?     -->		# comment
1408
				|
1409
					<\?.*?\?> | <%.*?%>		# processing instruction
1410
				|
1411
					<[!$]?[-a-zA-Z0-9:_]+	# regular tags
1412
					(?>
1413
						\s
1414
						(?>[^"\'>]+|"[^"]*"|\'[^\']*\')*
1415
					)?
1416
					>
1417
				|
1418
					<[-a-zA-Z0-9:_]+\s*/> # xml-style empty tag
1419
				|
1420
					</[-a-zA-Z0-9:_]+\s*> # closing tag
1421
			').'
1422
				)
1423
				}xs';
1424
1425
		while (1) {
1426
			#
1427
			# Each loop iteration search for either the next tag, the next
1428
			# openning code span marker, or the next escaped character.
1429
			# Each token is then passed to handleSpanToken.
1430
			#
1431
			$parts = preg_split($span_re, $str, 2, PREG_SPLIT_DELIM_CAPTURE);
1432
1433
			# Create token from text preceding tag.
1434
			if ($parts[0] != "") {
1435
				$output .= $parts[0];
1436
			}
1437
1438
			# Check if we reach the end.
1439
			if (isset($parts[1])) {
1440
				$output .= $this->handleSpanToken($parts[1], $parts[2]);
1441
				$str = $parts[2];
1442
			}
1443
			else {
1444
				break;
1445
			}
1446
		}
1447
1448
		return $output;
1449
	}
1450
1451
1452
	function handleSpanToken($token, &$str) {
1453
	#
1454
	# Handle $token provided by parseSpan by determining its nature and
1455
	# returning the corresponding value that should replace it.
1456
	#
1457
		switch ($token{0}) {
1458
			case "\\":
1459
				return $this->hashPart("&#". ord($token{1}). ";");
1460
			case "`":
1461
				# Search for end marker in remaining text.
1462
				if (preg_match('/^(.*?[^`])'.preg_quote($token).'(?!`)(.*)$/sm',
1463
					$str, $matches))
1464
				{
1465
					$str = $matches[2];
1466
					$codespan = $this->makeCodeSpan($matches[1]);
1467
					return $this->hashPart($codespan);
1468
				}
1469
				return $token; // return as text since no ending marker found.
1470
			default:
1471
				return $this->hashPart($token);
1472
		}
1473
	}
1474
1475
1476
	function outdent($text) {
1477
	#
1478
	# Remove one level of line-leading tabs or spaces
1479
	#
1480
		return preg_replace('/^(\t|[ ]{1,'.$this->tab_width.'})/m', '', $text);
1481
	}
1482
1483
1484
	# String length function for detab. `_initDetab` will create a function to
1485
	# hanlde UTF-8 if the default function does not exist.
1486
	public $utf8_strlen = 'mb_strlen';
1487
1488
	function detab($text) {
1489
	#
1490
	# Replace tabs with the appropriate amount of space.
1491
	#
1492
		# For each line we separate the line in blocks delemited by
1493
		# tab characters. Then we reconstruct every line by adding the
1494
		# appropriate number of space between each blocks.
1495
1496
		$text = preg_replace_callback('/^.*\t.*$/m',
1497
			array(&$this, '_detab_callback'), $text);
1498
1499
		return $text;
1500
	}
1501
	function _detab_callback($matches) {
1502
		$line = $matches[0];
1503
		$strlen = $this->utf8_strlen; # strlen function for UTF-8.
1504
1505
		# Split in blocks.
1506
		$blocks = explode("\t", $line);
1507
		# Add each blocks to the line.
1508
		$line = $blocks[0];
1509
		unset($blocks[0]); # Do not add first block twice.
1510
		foreach ($blocks as $block) {
1511
			# Calculate amount of space, insert spaces, insert block.
1512
			$amount = $this->tab_width -
1513
				$strlen($line, 'UTF-8') % $this->tab_width;
1514
			$line .= str_repeat(" ", $amount) . $block;
1515
		}
1516
		return $line;
1517
	}
1518
	function _initDetab() {
1519
	#
1520
	# Check for the availability of the function in the `utf8_strlen` property
1521
	# (initially `mb_strlen`). If the function is not available, create a
1522
	# function that will loosely count the number of UTF-8 characters with a
1523
	# regular expression.
1524
	#
1525
		if (function_exists($this->utf8_strlen)) return;
1526
		$this->utf8_strlen = create_function('$text', 'return preg_match_all(
0 ignored issues
show
Security Best Practice introduced by
The use of create_function is highly discouraged, better use a closure.

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

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

// Better use
$function = function($a, $b) { return $a + $b; }
Loading history...
1527
			"/[\\\\x00-\\\\xBF]|[\\\\xC0-\\\\xFF][\\\\x80-\\\\xBF]*/",
1528
			$text, $m);');
1529
	}
1530
1531
1532
	function unhash($text) {
1533
	#
1534
	# Swap back in all the tags hashed by _HashHTMLBlocks.
1535
	#
1536
		return preg_replace_callback('/(.)\x1A[0-9]+\1/',
1537
			array(&$this, '_unhash_callback'), $text);
1538
	}
1539
	function _unhash_callback($matches) {
1540
		return $this->html_hashes[$matches[0]];
1541
	}
1542
1543
}
1544
1545
1546
#
1547
# Markdown Extra Parser Class
1548
#
1549
1550
class MarkdownExtra_Parser extends Markdown_Parser {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
1551
1552
	### Configuration Variables ###
1553
1554
	# Prefix for footnote ids.
1555
	public $fn_id_prefix = "";
1556
1557
	# Optional title attribute for footnote links and backlinks.
1558
	public $fn_link_title = MARKDOWN_FN_LINK_TITLE;
1559
	public $fn_backlink_title = MARKDOWN_FN_BACKLINK_TITLE;
1560
1561
	# Optional class attribute for footnote links and backlinks.
1562
	public $fn_link_class = MARKDOWN_FN_LINK_CLASS;
1563
	public $fn_backlink_class = MARKDOWN_FN_BACKLINK_CLASS;
1564
1565
	# Optional class prefix for fenced code block.
1566
	public $code_class_prefix = MARKDOWN_CODE_CLASS_PREFIX;
1567
	# Class attribute for code blocks goes on the `code` tag;
1568
	# setting this to true will put attributes on the `pre` tag instead.
1569
	public $code_attr_on_pre = MARKDOWN_CODE_ATTR_ON_PRE;
1570
1571
	# Predefined abbreviations.
1572
	public $predef_abbr = array();
1573
1574
1575
	### Parser Implementation ###
1576
1577
	function __construct() {
1578
	#
1579
	# Constructor function. Initialize the parser object.
1580
	#
1581
		# Add extra escapable characters before parent constructor
1582
		# initialize the table.
1583
		$this->escape_chars .= ':|';
1584
1585
		# Insert extra document, block, and span transformations.
1586
		# Parent constructor will do the sorting.
1587
		$this->document_gamut += array(
1588
			"doFencedCodeBlocks" => 5,
1589
			"stripFootnotes"     => 15,
1590
			"stripAbbreviations" => 25,
1591
			"appendFootnotes"    => 50,
1592
			);
1593
		$this->block_gamut += array(
1594
			"doFencedCodeBlocks" => 5,
1595
			"doTables"           => 15,
1596
			"doDefLists"         => 45,
1597
			);
1598
		$this->span_gamut += array(
1599
			"doFootnotes"        => 5,
1600
			"doAbbreviations"    => 70,
1601
			);
1602
1603
		parent::__construct();
1604
	}
1605
1606
1607
	# Extra variables used during extra transformations.
1608
	public $footnotes = array();
1609
	public $footnotes_ordered = array();
1610
	public $footnotes_ref_count = array();
1611
	public $footnotes_numbers = array();
1612
	public $abbr_desciptions = array();
1613
	public $abbr_word_re = '';
1614
1615
	# Give the current footnote number.
1616
	public $footnote_counter = 1;
1617
1618
1619
	function setup() {
1620
	#
1621
	# Setting up Extra-specific variables.
1622
	#
1623
		parent::setup();
1624
1625
		$this->footnotes = array();
1626
		$this->footnotes_ordered = array();
1627
		$this->footnotes_ref_count = array();
1628
		$this->footnotes_numbers = array();
1629
		$this->abbr_desciptions = array();
1630
		$this->abbr_word_re = '';
1631
		$this->footnote_counter = 1;
1632
1633
		foreach ($this->predef_abbr as $abbr_word => $abbr_desc) {
1634
			if ($this->abbr_word_re)
1635
				$this->abbr_word_re .= '|';
1636
			$this->abbr_word_re .= preg_quote($abbr_word);
1637
			$this->abbr_desciptions[$abbr_word] = trim($abbr_desc);
1638
		}
1639
	}
1640
1641
	function teardown() {
1642
	#
1643
	# Clearing Extra-specific variables.
1644
	#
1645
		$this->footnotes = array();
1646
		$this->footnotes_ordered = array();
1647
		$this->footnotes_ref_count = array();
1648
		$this->footnotes_numbers = array();
1649
		$this->abbr_desciptions = array();
1650
		$this->abbr_word_re = '';
1651
1652
		parent::teardown();
1653
	}
1654
1655
1656
	### Extra Attribute Parser ###
1657
1658
	# Expression to use to catch attributes (includes the braces)
1659
	public $id_class_attr_catch_re = '\{((?:[ ]*[#.][-_:a-zA-Z0-9]+){1,})[ ]*\}';
1660
	# Expression to use when parsing in a context when no capture is desired
1661
	public $id_class_attr_nocatch_re = '\{(?:[ ]*[#.][-_:a-zA-Z0-9]+){1,}[ ]*\}';
1662
1663
	function doExtraAttributes($tag_name, $attr) {
0 ignored issues
show
Unused Code introduced by
The parameter $tag_name is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1664
	#
1665
	# Parse attributes caught by the $this->id_class_attr_catch_re expression
1666
	# and return the HTML-formatted list of attributes.
1667
	#
1668
	# Currently supported attributes are .class and #id.
1669
	#
1670
		if (empty($attr)) return "";
1671
1672
		# Split on components
1673
		preg_match_all('/[#.][-_:a-zA-Z0-9]+/', $attr, $matches);
1674
		$elements = $matches[0];
1675
1676
		# handle classes and ids (only first id taken into account)
1677
		$classes = array();
1678
		$id = false;
1679
		foreach ($elements as $element) {
1680
			if ($element{0} == '.') {
1681
				$classes[] = substr($element, 1);
1682
			} else if ($element{0} == '#') {
1683
				if ($id === false) $id = substr($element, 1);
1684
			}
1685
		}
1686
1687
		# compose attributes as string
1688
		$attr_str = "";
1689
		if (!empty($id)) {
1690
			$attr_str .= ' id="'.$id.'"';
1691
		}
1692
		if (!empty($classes)) {
1693
			$attr_str .= ' class="'.implode(" ", $classes).'"';
1694
		}
1695
		return $attr_str;
1696
	}
1697
1698
1699 View Code Duplication
	function stripLinkDefinitions($text) {
1700
	#
1701
	# Strips link definitions from text, stores the URLs and titles in
1702
	# hash references.
1703
	#
1704
		$less_than_tab = $this->tab_width - 1;
1705
1706
		# Link defs are in the form: ^[id]: url "optional title"
1707
		$text = preg_replace_callback('{
1708
							^[ ]{0,'.$less_than_tab.'}\[(.+)\][ ]?:	# id = $1
1709
							  [ ]*
1710
							  \n?				# maybe *one* newline
1711
							  [ ]*
1712
							(?:
1713
							  <(.+?)>			# url = $2
1714
							|
1715
							  (\S+?)			# url = $3
1716
							)
1717
							  [ ]*
1718
							  \n?				# maybe one newline
1719
							  [ ]*
1720
							(?:
1721
								(?<=\s)			# lookbehind for whitespace
1722
								["(]
1723
								(.*?)			# title = $4
1724
								[")]
1725
								[ ]*
1726
							)?	# title is optional
1727
					(?:[ ]* '.$this->id_class_attr_catch_re.' )?  # $5 = extra id & class attr
1728
							(?:\n+|\Z)
1729
			}xm',
1730
			array(&$this, '_stripLinkDefinitions_callback'),
1731
			$text);
1732
		return $text;
1733
	}
1734
	function _stripLinkDefinitions_callback($matches) {
1735
		$link_id = strtolower($matches[1]);
1736
		$url = $matches[2] == '' ? $matches[3] : $matches[2];
1737
		$this->urls[$link_id] = $url;
1738
		$this->titles[$link_id] =& $matches[4];
1739
		$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...
1740
		return ''; # String that will replace the block
1741
	}
1742
1743
1744
	### HTML Block Parser ###
1745
1746
	# Tags that are always treated as block tags:
1747
	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';
1748
1749
	# Tags treated as block tags only if the opening tag is alone on its line:
1750
	public $context_block_tags_re = 'script|noscript|ins|del|iframe|object|source|track|param|math|svg|canvas|audio|video';
1751
1752
	# Tags where markdown="1" default to span mode:
1753
	public $contain_span_tags_re = 'p|h[1-6]|li|dd|dt|td|th|legend|address';
1754
1755
	# Tags which must not have their contents modified, no matter where
1756
	# they appear:
1757
	public $clean_tags_re = 'script|math|svg';
1758
1759
	# Tags that do not need to be closed.
1760
	public $auto_close_tags_re = 'hr|img|param|source|track';
1761
1762
1763
	function hashHTMLBlocks($text) {
1764
	#
1765
	# Hashify HTML Blocks and "clean tags".
1766
	#
1767
	# We only want to do this for block-level HTML tags, such as headers,
1768
	# lists, and tables. That's because we still want to wrap <p>s around
1769
	# "paragraphs" that are wrapped in non-block-level tags, such as anchors,
1770
	# phrase emphasis, and spans. The list of tags we're looking for is
1771
	# hard-coded.
1772
	#
1773
	# This works by calling _HashHTMLBlocks_InMarkdown, which then calls
1774
	# _HashHTMLBlocks_InHTML when it encounter block tags. When the markdown="1"
1775
	# attribute is found within a tag, _HashHTMLBlocks_InHTML calls back
1776
	#  _HashHTMLBlocks_InMarkdown to handle the Markdown syntax within the tag.
1777
	# These two functions are calling each other. It's recursive!
1778
	#
1779
		if ($this->no_markup)  return $text;
1780
1781
		#
1782
		# Call the HTML-in-Markdown hasher.
1783
		#
1784
		list($text, ) = $this->_hashHTMLBlocks_inMarkdown($text);
1785
1786
		return $text;
1787
	}
1788
	function _hashHTMLBlocks_inMarkdown($text, $indent = 0,
1789
										$enclosing_tag_re = '', $span = false)
1790
	{
1791
	#
1792
	# Parse markdown text, calling _HashHTMLBlocks_InHTML for block tags.
1793
	#
1794
	# *   $indent is the number of space to be ignored when checking for code
1795
	#     blocks. This is important because if we don't take the indent into
1796
	#     account, something like this (which looks right) won't work as expected:
1797
	#
1798
	#     <div>
1799
	#         <div markdown="1">
1800
	#         Hello World.  <-- Is this a Markdown code block or text?
1801
	#         </div>  <-- Is this a Markdown code block or a real tag?
1802
	#     <div>
1803
	#
1804
	#     If you don't like this, just don't indent the tag on which
1805
	#     you apply the markdown="1" attribute.
1806
	#
1807
	# *   If $enclosing_tag_re is not empty, stops at the first unmatched closing
1808
	#     tag with that name. Nested tags supported.
1809
	#
1810
	# *   If $span is true, text inside must treated as span. So any double
1811
	#     newline will be replaced by a single newline so that it does not create
1812
	#     paragraphs.
1813
	#
1814
	# Returns an array of that form: ( processed text , remaining text )
1815
	#
1816
		if ($text === '') return array('', '');
1817
1818
		# Regex to check for the presence of newlines around a block tag.
1819
		$newline_before_re = '/(?:^\n?|\n\n)*$/';
1820
		$newline_after_re =
1821
			'{
1822
				^						# Start of text following the tag.
1823
				(?>[ ]*<!--.*?-->)?		# Optional comment.
1824
				[ ]*\n					# Must be followed by newline.
1825
			}xs';
1826
1827
		# Regex to match any tag.
1828
		$block_tag_re =
1829
			'{
1830
				(					# $2: Capture whole tag.
1831
					</?					# Any opening or closing tag.
1832
						(?>				# Tag name.
1833
							'.$this->block_tags_re.'			|
1834
							'.$this->context_block_tags_re.'	|
1835
							'.$this->clean_tags_re.'        	|
1836
							(?!\s)'.$enclosing_tag_re.'
1837
						)
1838
						(?:
1839
							(?=[\s"\'/a-zA-Z0-9])	# Allowed characters after tag name.
1840
							(?>
1841
								".*?"		|	# Double quotes (can contain `>`)
1842
								\'.*?\'   	|	# Single quotes (can contain `>`)
1843
								.+?				# Anything but quotes and `>`.
1844
							)*?
1845
						)?
1846
					>					# End of tag.
1847
				|
1848
					<!--    .*?     -->	# HTML Comment
1849
				|
1850
					<\?.*?\?> | <%.*?%>	# Processing instruction
1851
				|
1852
					<!\[CDATA\[.*?\]\]>	# CData Block
1853
				'. ( !$span ? ' # If not in span.
1854
				|
1855
					# Indented code block
1856
					(?: ^[ ]*\n | ^ | \n[ ]*\n )
1857
					[ ]{'.($indent+4).'}[^\n]* \n
1858
					(?>
1859
						(?: [ ]{'.($indent+4).'}[^\n]* | [ ]* ) \n
1860
					)*
1861
				|
1862
					# Fenced code block marker
1863
					(?<= ^ | \n )
1864
					[ ]{0,'.($indent+3).'}(?:~{3,}|`{3,})
1865
									[ ]*
1866
					(?:
1867
					\.?[-_:a-zA-Z0-9]+ # standalone class name
1868
					|
1869
						'.$this->id_class_attr_nocatch_re.' # extra attributes
1870
					)?
1871
					[ ]*
1872
					(?= \n )
1873
				' : '' ). ' # End (if not is span).
1874
				|
1875
					# Code span marker
1876
					# Note, this regex needs to go after backtick fenced
1877
					# code blocks but it should also be kept outside of the
1878
					# "if not in span" condition adding backticks to the parser
1879
					`+
1880
				)
1881
			}xs';
1882
1883
1884
		$depth = 0;		# Current depth inside the tag tree.
1885
		$parsed = "";	# Parsed text that will be returned.
1886
1887
		#
1888
		# Loop through every tag until we find the closing tag of the parent
1889
		# or loop until reaching the end of text if no parent tag specified.
1890
		#
1891
		do {
1892
			#
1893
			# Split the text using the first $tag_match pattern found.
1894
			# Text before  pattern will be first in the array, text after
1895
			# pattern will be at the end, and between will be any catches made
1896
			# by the pattern.
1897
			#
1898
			$parts = preg_split($block_tag_re, $text, 2,
1899
								PREG_SPLIT_DELIM_CAPTURE);
1900
1901
			# If in Markdown span mode, add a empty-string span-level hash
1902
			# after each newline to prevent triggering any block element.
1903
			if ($span) {
1904
				$void = $this->hashPart("", ':');
1905
				$newline = "$void\n";
1906
				$parts[0] = $void . str_replace("\n", $newline, $parts[0]) . $void;
1907
			}
1908
1909
			$parsed .= $parts[0]; # Text before current tag.
1910
1911
			# If end of $text has been reached. Stop loop.
1912
			if (count($parts) < 3) {
1913
				$text = "";
1914
				break;
1915
			}
1916
1917
			$tag  = $parts[1]; # Tag to handle.
1918
			$text = $parts[2]; # Remaining text after current tag.
1919
			$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...
1920
1921
			#
1922
			# Check for: Fenced code block marker.
1923
			# Note: need to recheck the whole tag to disambiguate backtick
1924
			# fences from code spans
1925
			#
1926
			if (preg_match('{^\n?([ ]{0,'.($indent+3).'})(~{3,}|`{3,})[ ]*(?:\.?[-_:a-zA-Z0-9]+|'.$this->id_class_attr_nocatch_re.')?[ ]*\n?$}', $tag, $capture)) {
1927
				# Fenced code block marker: find matching end marker.
1928
				$fence_indent = strlen($capture[1]); # use captured indent in re
1929
				$fence_re = $capture[2]; # use captured fence in re
1930 View Code Duplication
				if (preg_match('{^(?>.*\n)*?[ ]{'.($fence_indent).'}'.$fence_re.'[ ]*(?:\n|$)}', $text,
1931
					$matches))
1932
				{
1933
					# End marker found: pass text unchanged until marker.
1934
					$parsed .= $tag . $matches[0];
1935
					$text = substr($text, strlen($matches[0]));
1936
				}
1937
				else {
1938
					# No end marker: just skip it.
1939
					$parsed .= $tag;
1940
				}
1941
			}
1942
			#
1943
			# Check for: Indented code block.
1944
			#
1945
			else if ($tag{0} == "\n" || $tag{0} == " ") {
1946
				# Indented code block: pass it unchanged, will be handled
1947
				# later.
1948
				$parsed .= $tag;
1949
			}
1950
			#
1951
			# Check for: Code span marker
1952
			# Note: need to check this after backtick fenced code blocks
1953
			#
1954
			else if ($tag{0} == "`") {
1955
				# Find corresponding end marker.
1956
				$tag_re = preg_quote($tag);
1957 View Code Duplication
				if (preg_match('{^(?>.+?|\n(?!\n))*?(?<!`)'.$tag_re.'(?!`)}',
1958
					$text, $matches))
1959
				{
1960
					# End marker found: pass text unchanged until marker.
1961
					$parsed .= $tag . $matches[0];
1962
					$text = substr($text, strlen($matches[0]));
1963
				}
1964
				else {
1965
					# Unmatched marker: just skip it.
1966
					$parsed .= $tag;
1967
				}
1968
			}
1969
			#
1970
			# Check for: Opening Block level tag or
1971
			#            Opening Context Block tag (like ins and del)
1972
			#               used as a block tag (tag is alone on it's line).
1973
			#
1974
			else if (preg_match('{^<(?:'.$this->block_tags_re.')\b}', $tag) ||
1975
				(	preg_match('{^<(?:'.$this->context_block_tags_re.')\b}', $tag) &&
1976
					preg_match($newline_before_re, $parsed) &&
1977
					preg_match($newline_after_re, $text)	)
1978
				)
1979
			{
1980
				# Need to parse tag and following text using the HTML parser.
1981
				list($block_text, $text) =
1982
					$this->_hashHTMLBlocks_inHTML($tag . $text, "hashBlock", true);
1983
1984
				# Make sure it stays outside of any paragraph by adding newlines.
1985
				$parsed .= "\n\n$block_text\n\n";
1986
			}
1987
			#
1988
			# Check for: Clean tag (like script, math)
1989
			#            HTML Comments, processing instructions.
1990
			#
1991
			else if (preg_match('{^<(?:'.$this->clean_tags_re.')\b}', $tag) ||
1992
				$tag{1} == '!' || $tag{1} == '?')
1993
			{
1994
				# Need to parse tag and following text using the HTML parser.
1995
				# (don't check for markdown attribute)
1996
				list($block_text, $text) =
1997
					$this->_hashHTMLBlocks_inHTML($tag . $text, "hashClean", false);
1998
1999
				$parsed .= $block_text;
2000
			}
2001
			#
2002
			# Check for: Tag with same name as enclosing tag.
2003
			#
2004
			else if ($enclosing_tag_re !== '' &&
2005
				# Same name as enclosing tag.
2006
				preg_match('{^</?(?:'.$enclosing_tag_re.')\b}', $tag))
2007
			{
2008
				#
2009
				# Increase/decrease nested tag count.
2010
				#
2011 View Code Duplication
				if ($tag{1} == '/')						$depth--;
2012
				else if ($tag{strlen($tag)-2} != '/')	$depth++;
2013
2014
				if ($depth < 0) {
2015
					#
2016
					# Going out of parent element. Clean up and break so we
2017
					# return to the calling function.
2018
					#
2019
					$text = $tag . $text;
2020
					break;
2021
				}
2022
2023
				$parsed .= $tag;
2024
			}
2025
			else {
2026
				$parsed .= $tag;
2027
			}
2028
		} while ($depth >= 0);
2029
2030
		return array($parsed, $text);
2031
	}
2032
	function _hashHTMLBlocks_inHTML($text, $hash_method, $md_attr) {
2033
	#
2034
	# Parse HTML, calling _HashHTMLBlocks_InMarkdown for block tags.
2035
	#
2036
	# *   Calls $hash_method to convert any blocks.
2037
	# *   Stops when the first opening tag closes.
2038
	# *   $md_attr indicate if the use of the `markdown="1"` attribute is allowed.
2039
	#     (it is not inside clean tags)
2040
	#
2041
	# Returns an array of that form: ( processed text , remaining text )
2042
	#
2043
		if ($text === '') return array('', '');
2044
2045
		# Regex to match `markdown` attribute inside of a tag.
2046
		$markdown_attr_re = '
2047
			{
2048
				\s*			# Eat whitespace before the `markdown` attribute
2049
				markdown
2050
				\s*=\s*
2051
				(?>
2052
					(["\'])		# $1: quote delimiter
2053
					(.*?)		# $2: attribute value
2054
					\1			# matching delimiter
2055
				|
2056
					([^\s>]*)	# $3: unquoted attribute value
2057
				)
2058
				()				# $4: make $3 always defined (avoid warnings)
2059
			}xs';
2060
2061
		# Regex to match any tag.
2062
		$tag_re = '{
2063
				(					# $2: Capture whole tag.
2064
					</?					# Any opening or closing tag.
2065
						[\w:$]+			# Tag name.
2066
						(?:
2067
							(?=[\s"\'/a-zA-Z0-9])	# Allowed characters after tag name.
2068
							(?>
2069
								".*?"		|	# Double quotes (can contain `>`)
2070
								\'.*?\'   	|	# Single quotes (can contain `>`)
2071
								.+?				# Anything but quotes and `>`.
2072
							)*?
2073
						)?
2074
					>					# End of tag.
2075
				|
2076
					<!--    .*?     -->	# HTML Comment
2077
				|
2078
					<\?.*?\?> | <%.*?%>	# Processing instruction
2079
				|
2080
					<!\[CDATA\[.*?\]\]>	# CData Block
2081
				)
2082
			}xs';
2083
2084
		$original_text = $text;		# Save original text in case of faliure.
2085
2086
		$depth		= 0;	# Current depth inside the tag tree.
2087
		$block_text	= "";	# Temporary text holder for current text.
2088
		$parsed		= "";	# Parsed text that will be returned.
2089
2090
		#
2091
		# Get the name of the starting tag.
2092
		# (This pattern makes $base_tag_name_re safe without quoting.)
2093
		#
2094
		if (preg_match('/^<([\w:$]*)\b/', $text, $matches))
2095
			$base_tag_name_re = $matches[1];
2096
2097
		#
2098
		# Loop through every tag until we find the corresponding closing tag.
2099
		#
2100
		do {
2101
			#
2102
			# Split the text using the first $tag_match pattern found.
2103
			# Text before  pattern will be first in the array, text after
2104
			# pattern will be at the end, and between will be any catches made
2105
			# by the pattern.
2106
			#
2107
			$parts = preg_split($tag_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE);
2108
2109
			if (count($parts) < 3) {
2110
				#
2111
				# End of $text reached with unbalenced tag(s).
2112
				# In that case, we return original text unchanged and pass the
2113
				# first character as filtered to prevent an infinite loop in the
2114
				# parent function.
2115
				#
2116
				return array($original_text{0}, substr($original_text, 1));
2117
			}
2118
2119
			$block_text .= $parts[0]; # Text before current tag.
2120
			$tag         = $parts[1]; # Tag to handle.
2121
			$text        = $parts[2]; # Remaining text after current tag.
2122
2123
			#
2124
			# Check for: Auto-close tag (like <hr/>)
2125
			#			 Comments and Processing Instructions.
2126
			#
2127
			if (preg_match('{^</?(?:'.$this->auto_close_tags_re.')\b}', $tag) ||
2128
				$tag{1} == '!' || $tag{1} == '?')
2129
			{
2130
				# Just add the tag to the block as if it was text.
2131
				$block_text .= $tag;
2132
			}
2133
			else {
2134
				#
2135
				# Increase/decrease nested tag count. Only do so if
2136
				# the tag's name match base tag's.
2137
				#
2138
				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...
2139 View Code Duplication
					if ($tag{1} == '/')						$depth--;
2140
					else if ($tag{strlen($tag)-2} != '/')	$depth++;
2141
				}
2142
2143
				#
2144
				# Check for `markdown="1"` attribute and handle it.
2145
				#
2146
				if ($md_attr &&
2147
					preg_match($markdown_attr_re, $tag, $attr_m) &&
2148
					preg_match('/^1|block|span$/', $attr_m[2] . $attr_m[3]))
2149
				{
2150
					# Remove `markdown` attribute from opening tag.
2151
					$tag = preg_replace($markdown_attr_re, '', $tag);
2152
2153
					# Check if text inside this tag must be parsed in span mode.
2154
					$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...
2155
					$span_mode = $this->mode == 'span' || $this->mode != 'block' &&
2156
						preg_match('{^<(?:'.$this->contain_span_tags_re.')\b}', $tag);
2157
2158
					# Calculate indent before tag.
2159
					if (preg_match('/(?:^|\n)( *?)(?! ).*?$/', $block_text, $matches)) {
2160
						$strlen = $this->utf8_strlen;
2161
						$indent = $strlen($matches[1], 'UTF-8');
2162
					} else {
2163
						$indent = 0;
2164
					}
2165
2166
					# End preceding block with this tag.
2167
					$block_text .= $tag;
2168
					$parsed .= $this->$hash_method($block_text);
2169
2170
					# Get enclosing tag name for the ParseMarkdown function.
2171
					# (This pattern makes $tag_name_re safe without quoting.)
2172
					preg_match('/^<([\w:$]*)\b/', $tag, $matches);
2173
					$tag_name_re = $matches[1];
2174
2175
					# Parse the content using the HTML-in-Markdown parser.
2176
					list ($block_text, $text)
2177
						= $this->_hashHTMLBlocks_inMarkdown($text, $indent,
2178
							$tag_name_re, $span_mode);
2179
2180
					# Outdent markdown text.
2181
					if ($indent > 0) {
2182
						$block_text = preg_replace("/^[ ]{1,$indent}/m", "",
2183
													$block_text);
2184
					}
2185
2186
					# Append tag content to parsed text.
2187
					if (!$span_mode)	$parsed .= "\n\n$block_text\n\n";
2188
					else				$parsed .= "$block_text";
2189
2190
					# Start over with a new block.
2191
					$block_text = "";
2192
				}
2193
				else $block_text .= $tag;
2194
			}
2195
2196
		} while ($depth > 0);
2197
2198
		#
2199
		# Hash last block text that wasn't processed inside the loop.
2200
		#
2201
		$parsed .= $this->$hash_method($block_text);
2202
2203
		return array($parsed, $text);
2204
	}
2205
2206
2207
	function hashClean($text) {
2208
	#
2209
	# Called whenever a tag must be hashed when a function inserts a "clean" tag
2210
	# in $text, it passes through this function and is automaticaly escaped,
2211
	# blocking invalid nested overlap.
2212
	#
2213
		return $this->hashPart($text, 'C');
2214
	}
2215
2216
2217 View Code Duplication
	function doAnchors($text) {
2218
	#
2219
	# Turn Markdown link shortcuts into XHTML <a> tags.
2220
	#
2221
		if ($this->in_anchor) return $text;
2222
		$this->in_anchor = true;
2223
2224
		#
2225
		# First, handle reference-style links: [link text] [id]
2226
		#
2227
		$text = preg_replace_callback('{
2228
			(					# wrap whole match in $1
2229
			  \[
2230
				('.$this->nested_brackets_re.')	# link text = $2
2231
			  \]
2232
2233
			  [ ]?				# one optional space
2234
			  (?:\n[ ]*)?		# one optional newline followed by spaces
2235
2236
			  \[
2237
				(.*?)		# id = $3
2238
			  \]
2239
			)
2240
			}xs',
2241
			array(&$this, '_doAnchors_reference_callback'), $text);
2242
2243
		#
2244
		# Next, inline-style links: [link text](url "optional title")
2245
		#
2246
		$text = preg_replace_callback('{
2247
			(				# wrap whole match in $1
2248
			  \[
2249
				('.$this->nested_brackets_re.')	# link text = $2
2250
			  \]
2251
			  \(			# literal paren
2252
				[ \n]*
2253
				(?:
2254
					<(.+?)>	# href = $3
2255
				|
2256
					('.$this->nested_url_parenthesis_re.')	# href = $4
2257
				)
2258
				[ \n]*
2259
				(			# $5
2260
				  ([\'"])	# quote char = $6
2261
				  (.*?)		# Title = $7
2262
				  \6		# matching quote
2263
				  [ \n]*	# ignore any spaces/tabs between closing quote and )
2264
				)?			# title is optional
2265
			  \)
2266
			  (?:[ ]? '.$this->id_class_attr_catch_re.' )?	 # $8 = id/class attributes
2267
			)
2268
			}xs',
2269
			array(&$this, '_doAnchors_inline_callback'), $text);
2270
2271
		#
2272
		# Last, handle reference-style shortcuts: [link text]
2273
		# These must come last in case you've also got [link text][1]
2274
		# or [link text](/foo)
2275
		#
2276
		$text = preg_replace_callback('{
2277
			(					# wrap whole match in $1
2278
			  \[
2279
				([^\[\]]+)		# link text = $2; can\'t contain [ or ]
2280
			  \]
2281
			)
2282
			}xs',
2283
			array(&$this, '_doAnchors_reference_callback'), $text);
2284
2285
		$this->in_anchor = false;
2286
		return $text;
2287
	}
2288
	function _doAnchors_reference_callback($matches) {
2289
		$whole_match =  $matches[1];
2290
		$link_text   =  $matches[2];
2291
		$link_id     =& $matches[3];
2292
2293
		if ($link_id == "") {
2294
			# for shortcut links like [this][] or [this].
2295
			$link_id = $link_text;
2296
		}
2297
2298
		# lower-case and turn embedded newlines into spaces
2299
		$link_id = strtolower($link_id);
2300
		$link_id = preg_replace('{[ ]?\n}', ' ', $link_id);
2301
2302
		if (isset($this->urls[$link_id])) {
2303
			$url = $this->urls[$link_id];
2304
			$url = $this->encodeAttribute($url);
2305
2306
			$result = "<a href=\"$url\"";
2307 View Code Duplication
			if ( isset( $this->titles[$link_id] ) ) {
2308
				$title = $this->titles[$link_id];
2309
				$title = $this->encodeAttribute($title);
2310
				$result .=  " title=\"$title\"";
2311
			}
2312
			if (isset($this->ref_attr[$link_id]))
2313
				$result .= $this->ref_attr[$link_id];
2314
2315
			$link_text = $this->runSpanGamut($link_text);
2316
			$result .= ">$link_text</a>";
2317
			$result = $this->hashPart($result);
2318
		}
2319
		else {
2320
			$result = $whole_match;
2321
		}
2322
		return $result;
2323
	}
2324 View Code Duplication
	function _doAnchors_inline_callback($matches) {
2325
		$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...
2326
		$link_text		=  $this->runSpanGamut($matches[2]);
2327
		$url			=  $matches[3] == '' ? $matches[4] : $matches[3];
2328
		$title			=& $matches[7];
2329
		$attr  = $this->doExtraAttributes("a", $dummy =& $matches[8]);
2330
2331
2332
		$url = $this->encodeAttribute($url);
2333
2334
		$result = "<a href=\"$url\"";
2335
		if (isset($title)) {
2336
			$title = $this->encodeAttribute($title);
2337
			$result .=  " title=\"$title\"";
2338
		}
2339
		$result .= $attr;
2340
2341
		$link_text = $this->runSpanGamut($link_text);
2342
		$result .= ">$link_text</a>";
2343
2344
		return $this->hashPart($result);
2345
	}
2346
2347
2348 View Code Duplication
	function doImages($text) {
2349
	#
2350
	# Turn Markdown image shortcuts into <img> tags.
2351
	#
2352
		#
2353
		# First, handle reference-style labeled images: ![alt text][id]
2354
		#
2355
		$text = preg_replace_callback('{
2356
			(				# wrap whole match in $1
2357
			  !\[
2358
				('.$this->nested_brackets_re.')		# alt text = $2
2359
			  \]
2360
2361
			  [ ]?				# one optional space
2362
			  (?:\n[ ]*)?		# one optional newline followed by spaces
2363
2364
			  \[
2365
				(.*?)		# id = $3
2366
			  \]
2367
2368
			)
2369
			}xs',
2370
			array(&$this, '_doImages_reference_callback'), $text);
2371
2372
		#
2373
		# Next, handle inline images:  ![alt text](url "optional title")
2374
		# Don't forget: encode * and _
2375
		#
2376
		$text = preg_replace_callback('{
2377
			(				# wrap whole match in $1
2378
			  !\[
2379
				('.$this->nested_brackets_re.')		# alt text = $2
2380
			  \]
2381
			  \s?			# One optional whitespace character
2382
			  \(			# literal paren
2383
				[ \n]*
2384
				(?:
2385
					<(\S*)>	# src url = $3
2386
				|
2387
					('.$this->nested_url_parenthesis_re.')	# src url = $4
2388
				)
2389
				[ \n]*
2390
				(			# $5
2391
				  ([\'"])	# quote char = $6
2392
				  (.*?)		# title = $7
2393
				  \6		# matching quote
2394
				  [ \n]*
2395
				)?			# title is optional
2396
			  \)
2397
			  (?:[ ]? '.$this->id_class_attr_catch_re.' )?	 # $8 = id/class attributes
2398
			)
2399
			}xs',
2400
			array(&$this, '_doImages_inline_callback'), $text);
2401
2402
		return $text;
2403
	}
2404
	function _doImages_reference_callback($matches) {
2405
		$whole_match = $matches[1];
2406
		$alt_text    = $matches[2];
2407
		$link_id     = strtolower($matches[3]);
2408
2409
		if ($link_id == "") {
2410
			$link_id = strtolower($alt_text); # for shortcut links like ![this][].
2411
		}
2412
2413
		$alt_text = $this->encodeAttribute($alt_text);
2414
		if (isset($this->urls[$link_id])) {
2415
			$url = $this->encodeAttribute($this->urls[$link_id]);
2416
			$result = "<img src=\"$url\" alt=\"$alt_text\"";
2417 View Code Duplication
			if (isset($this->titles[$link_id])) {
2418
				$title = $this->titles[$link_id];
2419
				$title = $this->encodeAttribute($title);
2420
				$result .=  " title=\"$title\"";
2421
			}
2422
			if (isset($this->ref_attr[$link_id]))
2423
				$result .= $this->ref_attr[$link_id];
2424
			$result .= $this->empty_element_suffix;
2425
			$result = $this->hashPart($result);
2426
		}
2427
		else {
2428
			# If there's no such link ID, leave intact:
2429
			$result = $whole_match;
2430
		}
2431
2432
		return $result;
2433
	}
2434 View Code Duplication
	function _doImages_inline_callback($matches) {
2435
		$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...
2436
		$alt_text		= $matches[2];
2437
		$url			= $matches[3] == '' ? $matches[4] : $matches[3];
2438
		$title			=& $matches[7];
2439
		$attr  = $this->doExtraAttributes("img", $dummy =& $matches[8]);
2440
2441
		$alt_text = $this->encodeAttribute($alt_text);
2442
		$url = $this->encodeAttribute($url);
2443
		$result = "<img src=\"$url\" alt=\"$alt_text\"";
2444
		if (isset($title)) {
2445
			$title = $this->encodeAttribute($title);
2446
			$result .=  " title=\"$title\""; # $title already quoted
2447
		}
2448
		$result .= $attr;
2449
		$result .= $this->empty_element_suffix;
2450
2451
		return $this->hashPart($result);
2452
	}
2453
2454
2455
	function doHeaders($text) {
2456
	#
2457
	# Redefined to add id and class attribute support.
2458
	#
2459
		# Setext-style headers:
2460
		#	  Header 1  {#header1}
2461
		#	  ========
2462
		#
2463
		#	  Header 2  {#header2 .class1 .class2}
2464
		#	  --------
2465
		#
2466
		$text = preg_replace_callback(
2467
			'{
2468
				(^.+?)								# $1: Header text
2469
				(?:[ ]+ '.$this->id_class_attr_catch_re.' )?	 # $3 = id/class attributes
2470
				[ ]*\n(=+|-+)[ ]*\n+				# $3: Header footer
2471
			}mx',
2472
			array(&$this, '_doHeaders_callback_setext'), $text);
2473
2474
		# atx-style headers:
2475
		#	# Header 1        {#header1}
2476
		#	## Header 2       {#header2}
2477
		#	## Header 2 with closing hashes ##  {#header3.class1.class2}
2478
		#	...
2479
		#	###### Header 6   {.class2}
2480
		#
2481
		$text = preg_replace_callback('{
2482
				^(\#{1,6})	# $1 = string of #\'s
2483
				[ ]*
2484
				(.+?)		# $2 = Header text
2485
				[ ]*
2486
				\#*			# optional closing #\'s (not counted)
2487
				(?:[ ]+ '.$this->id_class_attr_catch_re.' )?	 # $3 = id/class attributes
2488
				[ ]*
2489
				\n+
2490
			}xm',
2491
			array(&$this, '_doHeaders_callback_atx'), $text);
2492
2493
		return $text;
2494
	}
2495
	function _doHeaders_callback_setext($matches) {
2496 View Code Duplication
		if ($matches[3] == '-' && preg_match('{^- }', $matches[1]))
2497
			return $matches[0];
2498
		$level = $matches[3]{0} == '=' ? 1 : 2;
2499
		$attr  = $this->doExtraAttributes("h$level", $dummy =& $matches[2]);
2500
		$block = "<h$level$attr>".$this->runSpanGamut($matches[1])."</h$level>";
2501
		return "\n" . $this->hashBlock($block) . "\n\n";
2502
	}
2503
	function _doHeaders_callback_atx($matches) {
2504
		$level = strlen($matches[1]);
2505
		$attr  = $this->doExtraAttributes("h$level", $dummy =& $matches[3]);
2506
		$block = "<h$level$attr>".$this->runSpanGamut($matches[2])."</h$level>";
2507
		return "\n" . $this->hashBlock($block) . "\n\n";
2508
	}
2509
2510
2511
	function doTables($text) {
2512
	#
2513
	# Form HTML tables.
2514
	#
2515
		$less_than_tab = $this->tab_width - 1;
2516
		#
2517
		# Find tables with leading pipe.
2518
		#
2519
		#	| Header 1 | Header 2
2520
		#	| -------- | --------
2521
		#	| Cell 1   | Cell 2
2522
		#	| Cell 3   | Cell 4
2523
		#
2524
		$text = preg_replace_callback('
2525
			{
2526
				^							# Start of a line
2527
				[ ]{0,'.$less_than_tab.'}	# Allowed whitespace.
2528
				[|]							# Optional leading pipe (present)
2529
				(.+) \n						# $1: Header row (at least one pipe)
2530
2531
				[ ]{0,'.$less_than_tab.'}	# Allowed whitespace.
2532
				[|] ([ ]*[-:]+[-| :]*) \n	# $2: Header underline
2533
2534
				(							# $3: Cells
2535
					(?>
2536
						[ ]*				# Allowed whitespace.
2537
						[|] .* \n			# Row content.
2538
					)*
2539
				)
2540
				(?=\n|\Z)					# Stop at final double newline.
2541
			}xm',
2542
			array(&$this, '_doTable_leadingPipe_callback'), $text);
2543
2544
		#
2545
		# Find tables without leading pipe.
2546
		#
2547
		#	Header 1 | Header 2
2548
		#	-------- | --------
2549
		#	Cell 1   | Cell 2
2550
		#	Cell 3   | Cell 4
2551
		#
2552
		$text = preg_replace_callback('
2553
			{
2554
				^							# Start of a line
2555
				[ ]{0,'.$less_than_tab.'}	# Allowed whitespace.
2556
				(\S.*[|].*) \n				# $1: Header row (at least one pipe)
2557
2558
				[ ]{0,'.$less_than_tab.'}	# Allowed whitespace.
2559
				([-:]+[ ]*[|][-| :]*) \n	# $2: Header underline
2560
2561
				(							# $3: Cells
2562
					(?>
2563
						.* [|] .* \n		# Row content
2564
					)*
2565
				)
2566
				(?=\n|\Z)					# Stop at final double newline.
2567
			}xm',
2568
			array(&$this, '_DoTable_callback'), $text);
2569
2570
		return $text;
2571
	}
2572
	function _doTable_leadingPipe_callback($matches) {
2573
		$head		= $matches[1];
2574
		$underline	= $matches[2];
2575
		$content	= $matches[3];
2576
2577
		# Remove leading pipe for each row.
2578
		$content	= preg_replace('/^ *[|]/m', '', $content);
2579
2580
		return $this->_doTable_callback(array($matches[0], $head, $underline, $content));
2581
	}
2582
	function _doTable_callback($matches) {
2583
		$head		= $matches[1];
2584
		$underline	= $matches[2];
2585
		$content	= $matches[3];
2586
2587
		# Remove any tailing pipes for each line.
2588
		$head		= preg_replace('/[|] *$/m', '', $head);
2589
		$underline	= preg_replace('/[|] *$/m', '', $underline);
2590
		$content	= preg_replace('/[|] *$/m', '', $content);
2591
2592
		# Reading alignement from header underline.
2593
		$separators	= preg_split('/ *[|] */', $underline);
2594
		foreach ($separators as $n => $s) {
2595
			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...
2596
			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...
2597
			else if (preg_match('/^ *:-+ *$/', $s))	$attr[$n] = ' align="left"';
2598
			else									$attr[$n] = '';
2599
		}
2600
2601
		# Parsing span elements, including code spans, character escapes,
2602
		# and inline HTML tags, so that pipes inside those gets ignored.
2603
		$head		= $this->parseSpan($head);
2604
		$headers	= preg_split('/ *[|] */', $head);
2605
		$col_count	= count($headers);
2606
		$attr       = array_pad($attr, $col_count, '');
2607
2608
		# Write column headers.
2609
		$text = "<table>\n";
2610
		$text .= "<thead>\n";
2611
		$text .= "<tr>\n";
2612
		foreach ($headers as $n => $header)
2613
			$text .= "  <th$attr[$n]>".$this->runSpanGamut(trim($header))."</th>\n";
2614
		$text .= "</tr>\n";
2615
		$text .= "</thead>\n";
2616
2617
		# Split content by row.
2618
		$rows = explode("\n", trim($content, "\n"));
2619
2620
		$text .= "<tbody>\n";
2621
		foreach ($rows as $row) {
2622
			# Parsing span elements, including code spans, character escapes,
2623
			# and inline HTML tags, so that pipes inside those gets ignored.
2624
			$row = $this->parseSpan($row);
2625
2626
			# Split row by cell.
2627
			$row_cells = preg_split('/ *[|] */', $row, $col_count);
2628
			$row_cells = array_pad($row_cells, $col_count, '');
2629
2630
			$text .= "<tr>\n";
2631
			foreach ($row_cells as $n => $cell)
2632
				$text .= "  <td$attr[$n]>".$this->runSpanGamut(trim($cell))."</td>\n";
2633
			$text .= "</tr>\n";
2634
		}
2635
		$text .= "</tbody>\n";
2636
		$text .= "</table>";
2637
2638
		return $this->hashBlock($text) . "\n";
2639
	}
2640
2641
2642
	function doDefLists($text) {
2643
	#
2644
	# Form HTML definition lists.
2645
	#
2646
		$less_than_tab = $this->tab_width - 1;
2647
2648
		# Re-usable pattern to match any entire dl list:
2649
		$whole_list_re = '(?>
2650
			(								# $1 = whole list
2651
			  (								# $2
2652
				[ ]{0,'.$less_than_tab.'}
2653
				((?>.*\S.*\n)+)				# $3 = defined term
2654
				\n?
2655
				[ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition
2656
			  )
2657
			  (?s:.+?)
2658
			  (								# $4
2659
				  \z
2660
				|
2661
				  \n{2,}
2662
				  (?=\S)
2663
				  (?!						# Negative lookahead for another term
2664
					[ ]{0,'.$less_than_tab.'}
2665
					(?: \S.*\n )+?			# defined term
2666
					\n?
2667
					[ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition
2668
				  )
2669
				  (?!						# Negative lookahead for another definition
2670
					[ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition
2671
				  )
2672
			  )
2673
			)
2674
		)'; // mx
2675
2676
		$text = preg_replace_callback('{
2677
				(?>\A\n?|(?<=\n\n))
2678
				'.$whole_list_re.'
2679
			}mx',
2680
			array(&$this, '_doDefLists_callback'), $text);
2681
2682
		return $text;
2683
	}
2684
	function _doDefLists_callback($matches) {
2685
		# Re-usable patterns to match list item bullets and number markers:
2686
		$list = $matches[1];
2687
2688
		# Turn double returns into triple returns, so that we can make a
2689
		# paragraph for the last item in a list, if necessary:
2690
		$result = trim($this->processDefListItems($list));
2691
		$result = "<dl>\n" . $result . "\n</dl>";
2692
		return $this->hashBlock($result) . "\n\n";
2693
	}
2694
2695
2696
	function processDefListItems($list_str) {
2697
	#
2698
	#	Process the contents of a single definition list, splitting it
2699
	#	into individual term and definition list items.
2700
	#
2701
		$less_than_tab = $this->tab_width - 1;
2702
2703
		# trim trailing blank lines:
2704
		$list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str);
2705
2706
		# Process definition terms.
2707
		$list_str = preg_replace_callback('{
2708
			(?>\A\n?|\n\n+)					# leading line
2709
			(								# definition terms = $1
2710
				[ ]{0,'.$less_than_tab.'}	# leading whitespace
2711
				(?!\:[ ]|[ ])				# negative lookahead for a definition
2712
											#   mark (colon) or more whitespace.
2713
				(?> \S.* \n)+?				# actual term (not whitespace).
2714
			)
2715
			(?=\n?[ ]{0,3}:[ ])				# lookahead for following line feed
2716
											#   with a definition mark.
2717
			}xm',
2718
			array(&$this, '_processDefListItems_callback_dt'), $list_str);
2719
2720
		# Process actual definitions.
2721
		$list_str = preg_replace_callback('{
2722
			\n(\n+)?						# leading line = $1
2723
			(								# marker space = $2
2724
				[ ]{0,'.$less_than_tab.'}	# whitespace before colon
2725
				\:[ ]+						# definition mark (colon)
2726
			)
2727
			((?s:.+?))						# definition text = $3
2728
			(?= \n+ 						# stop at next definition mark,
2729
				(?:							# next term or end of text
2730
					[ ]{0,'.$less_than_tab.'} \:[ ]	|
2731
					<dt> | \z
2732
				)
2733
			)
2734
			}xm',
2735
			array(&$this, '_processDefListItems_callback_dd'), $list_str);
2736
2737
		return $list_str;
2738
	}
2739
	function _processDefListItems_callback_dt($matches) {
2740
		$terms = explode("\n", trim($matches[1]));
2741
		$text = '';
2742
		foreach ($terms as $term) {
2743
			$term = $this->runSpanGamut(trim($term));
2744
			$text .= "\n<dt>" . $term . "</dt>";
2745
		}
2746
		return $text . "\n";
2747
	}
2748
	function _processDefListItems_callback_dd($matches) {
2749
		$leading_line	= $matches[1];
2750
		$marker_space	= $matches[2];
2751
		$def			= $matches[3];
2752
2753 View Code Duplication
		if ($leading_line || preg_match('/\n{2,}/', $def)) {
2754
			# Replace marker with the appropriate whitespace indentation
2755
			$def = str_repeat(' ', strlen($marker_space)) . $def;
2756
			$def = $this->runBlockGamut($this->outdent($def . "\n\n"));
2757
			$def = "\n". $def ."\n";
2758
		}
2759
		else {
2760
			$def = rtrim($def);
2761
			$def = $this->runSpanGamut($this->outdent($def));
2762
		}
2763
2764
		return "\n<dd>" . $def . "</dd>\n";
2765
	}
2766
2767
2768 View Code Duplication
	function doFencedCodeBlocks($text) {
2769
	#
2770
	# Adding the fenced code block syntax to regular Markdown:
2771
	#
2772
	# ~~~
2773
	# Code block
2774
	# ~~~
2775
	#
2776
		$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...
2777
2778
		$text = preg_replace_callback('{
2779
				(?:\n|\A)
2780
				# 1: Opening marker
2781
				(
2782
					(?:~{3,}|`{3,}) # 3 or more tildes/backticks.
2783
				)
2784
				[ ]*
2785
				(?:
2786
					\.?([-_:a-zA-Z0-9]+) # 2: standalone class name
2787
				|
2788
					'.$this->id_class_attr_catch_re.' # 3: Extra attributes
2789
				)?
2790
				[ ]* \n # Whitespace and newline following marker.
2791
2792
				# 4: Content
2793
				(
2794
					(?>
2795
						(?!\1 [ ]* \n)	# Not a closing marker.
2796
						.*\n+
2797
					)+
2798
				)
2799
2800
				# Closing marker.
2801
				\1 [ ]* (?= \n )
2802
			}xm',
2803
			array(&$this, '_doFencedCodeBlocks_callback'), $text);
2804
2805
		return $text;
2806
	}
2807
	function _doFencedCodeBlocks_callback($matches) {
2808
		$classname =& $matches[2];
2809
		$attrs     =& $matches[3];
2810
		$codeblock = $matches[4];
2811
		$codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES);
2812
		$codeblock = preg_replace_callback('/^\n+/',
2813
			array(&$this, '_doFencedCodeBlocks_newlines'), $codeblock);
2814
2815
		if ($classname != "") {
2816
			if ($classname{0} == '.')
2817
				$classname = substr($classname, 1);
2818
			$attr_str = ' class="'.$this->code_class_prefix.$classname.'"';
2819
		} else {
2820
			$attr_str = $this->doExtraAttributes($this->code_attr_on_pre ? "pre" : "code", $attrs);
2821
		}
2822
		$pre_attr_str  = $this->code_attr_on_pre ? $attr_str : '';
2823
		$code_attr_str = $this->code_attr_on_pre ? '' : $attr_str;
2824
		$codeblock  = "<pre$pre_attr_str><code$code_attr_str>$codeblock</code></pre>";
2825
2826
		return "\n\n".$this->hashBlock($codeblock)."\n\n";
2827
	}
2828
	function _doFencedCodeBlocks_newlines($matches) {
2829
		return str_repeat("<br$this->empty_element_suffix",
2830
			strlen($matches[0]));
2831
	}
2832
2833
2834
	#
2835
	# Redefining emphasis markers so that emphasis by underscore does not
2836
	# work in the middle of a word.
2837
	#
2838
	public $em_relist = array(
2839
		''  => '(?:(?<!\*)\*(?!\*)|(?<![a-zA-Z0-9_])_(?!_))(?=\S|$)(?![\.,:;]\s)',
2840
		'*' => '(?<=\S|^)(?<!\*)\*(?!\*)',
2841
		'_' => '(?<=\S|^)(?<!_)_(?![a-zA-Z0-9_])',
2842
		);
2843
	public $strong_relist = array(
2844
		''   => '(?:(?<!\*)\*\*(?!\*)|(?<![a-zA-Z0-9_])__(?!_))(?=\S|$)(?![\.,:;]\s)',
2845
		'**' => '(?<=\S|^)(?<!\*)\*\*(?!\*)',
2846
		'__' => '(?<=\S|^)(?<!_)__(?![a-zA-Z0-9_])',
2847
		);
2848
	public $em_strong_relist = array(
2849
		''    => '(?:(?<!\*)\*\*\*(?!\*)|(?<![a-zA-Z0-9_])___(?!_))(?=\S|$)(?![\.,:;]\s)',
2850
		'***' => '(?<=\S|^)(?<!\*)\*\*\*(?!\*)',
2851
		'___' => '(?<=\S|^)(?<!_)___(?![a-zA-Z0-9_])',
2852
		);
2853
2854
2855
	function formParagraphs($text) {
2856
	#
2857
	#	Params:
2858
	#		$text - string to process with html <p> tags
2859
	#
2860
		# Strip leading and trailing lines:
2861
		$text = preg_replace('/\A\n+|\n+\z/', '', $text);
2862
2863
		$grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY);
2864
2865
		#
2866
		# Wrap <p> tags and unhashify HTML blocks
2867
		#
2868
		foreach ($grafs as $key => $value) {
2869
			$value = trim($this->runSpanGamut($value));
2870
2871
			# Check if this should be enclosed in a paragraph.
2872
			# Clean tag hashes & block tag hashes are left alone.
2873
			$is_p = !preg_match('/^B\x1A[0-9]+B|^C\x1A[0-9]+C$/', $value);
2874
2875
			if ($is_p) {
2876
				$value = "<p>$value</p>";
2877
			}
2878
			$grafs[$key] = $value;
2879
		}
2880
2881
		# Join grafs in one text, then unhash HTML tags.
2882
		$text = implode("\n\n", $grafs);
2883
2884
		# Finish by removing any tag hashes still present in $text.
2885
		$text = $this->unhash($text);
2886
2887
		return $text;
2888
	}
2889
2890
2891
	### Footnotes
2892
2893 View Code Duplication
	function stripFootnotes($text) {
2894
	#
2895
	# Strips link definitions from text, stores the URLs and titles in
2896
	# hash references.
2897
	#
2898
		$less_than_tab = $this->tab_width - 1;
2899
2900
		# Link defs are in the form: [^id]: url "optional title"
2901
		$text = preg_replace_callback('{
2902
			^[ ]{0,'.$less_than_tab.'}\[\^(.+?)\][ ]?:	# note_id = $1
2903
			  [ ]*
2904
			  \n?					# maybe *one* newline
2905
			(						# text = $2 (no blank lines allowed)
2906
				(?:
2907
					.+				# actual text
2908
				|
2909
					\n				# newlines but
2910
					(?!\[\^.+?\]:\s)# negative lookahead for footnote marker.
2911
					(?!\n+[ ]{0,3}\S)# ensure line is not blank and followed
2912
									# by non-indented content
2913
				)*
2914
			)
2915
			}xm',
2916
			array(&$this, '_stripFootnotes_callback'),
2917
			$text);
2918
		return $text;
2919
	}
2920
	function _stripFootnotes_callback($matches) {
2921
		$note_id = $this->fn_id_prefix . $matches[1];
2922
		$this->footnotes[$note_id] = $this->outdent($matches[2]);
2923
		return ''; # String that will replace the block
2924
	}
2925
2926
2927
	function doFootnotes($text) {
2928
	#
2929
	# Replace footnote references in $text [^id] with a special text-token
2930
	# which will be replaced by the actual footnote marker in appendFootnotes.
2931
	#
2932
		if (!$this->in_anchor) {
2933
			$text = preg_replace('{\[\^(.+?)\]}', "F\x1Afn:\\1\x1A:", $text);
2934
		}
2935
		return $text;
2936
	}
2937
2938
2939
	function appendFootnotes($text) {
2940
	#
2941
	# Append footnote list to text.
2942
	#
2943
		$text = preg_replace_callback('{F\x1Afn:(.*?)\x1A:}',
2944
			array(&$this, '_appendFootnotes_callback'), $text);
2945
2946
		if (!empty($this->footnotes_ordered)) {
2947
			$text .= "\n\n";
2948
			$text .= "<div class=\"footnotes\">\n";
2949
			$text .= "<hr". $this->empty_element_suffix ."\n";
2950
			$text .= "<ol>\n\n";
2951
2952
			$attr = "";
2953
			if ($this->fn_backlink_class != "") {
2954
				$class = $this->fn_backlink_class;
2955
				$class = $this->encodeAttribute($class);
2956
				$attr .= " class=\"$class\"";
2957
			}
2958
			if ($this->fn_backlink_title != "") {
2959
				$title = $this->fn_backlink_title;
2960
				$title = $this->encodeAttribute($title);
2961
				$attr .= " title=\"$title\"";
2962
			}
2963
			$num = 0;
2964
2965
			while (!empty($this->footnotes_ordered)) {
2966
				$footnote = reset($this->footnotes_ordered);
2967
				$note_id = key($this->footnotes_ordered);
2968
				unset($this->footnotes_ordered[$note_id]);
2969
				$ref_count = $this->footnotes_ref_count[$note_id];
2970
				unset($this->footnotes_ref_count[$note_id]);
2971
				unset($this->footnotes[$note_id]);
2972
2973
				$footnote .= "\n"; # Need to append newline before parsing.
2974
				$footnote = $this->runBlockGamut("$footnote\n");
2975
				$footnote = preg_replace_callback('{F\x1Afn:(.*?)\x1A:}',
2976
					array(&$this, '_appendFootnotes_callback'), $footnote);
2977
2978
				$attr = str_replace("%%", ++$num, $attr);
2979
				$note_id = $this->encodeAttribute($note_id);
2980
2981
				# Prepare backlink, multiple backlinks if multiple references
2982
				$backlink = "<a href=\"#fnref:$note_id\"$attr>&#8617;</a>";
2983
				for ($ref_num = 2; $ref_num <= $ref_count; ++$ref_num) {
2984
					$backlink .= " <a href=\"#fnref$ref_num:$note_id\"$attr>&#8617;</a>";
2985
				}
2986
				# Add backlink to last paragraph; create new paragraph if needed.
2987
				if (preg_match('{</p>$}', $footnote)) {
2988
					$footnote = substr($footnote, 0, -4) . "&#160;$backlink</p>";
2989
				} else {
2990
					$footnote .= "\n\n<p>$backlink</p>";
2991
				}
2992
2993
				$text .= "<li id=\"fn:$note_id\">\n";
2994
				$text .= $footnote . "\n";
2995
				$text .= "</li>\n\n";
2996
			}
2997
2998
			$text .= "</ol>\n";
2999
			$text .= "</div>";
3000
		}
3001
		return $text;
3002
	}
3003
	function _appendFootnotes_callback($matches) {
3004
		$node_id = $this->fn_id_prefix . $matches[1];
3005
3006
		# Create footnote marker only if it has a corresponding footnote *and*
3007
		# the footnote hasn't been used by another marker.
3008
		if (isset($this->footnotes[$node_id])) {
3009
			$num =& $this->footnotes_numbers[$node_id];
3010
			if (!isset($num)) {
3011
				# Transfer footnote content to the ordered list and give it its
3012
				# number
3013
				$this->footnotes_ordered[$node_id] = $this->footnotes[$node_id];
3014
				$this->footnotes_ref_count[$node_id] = 1;
3015
				$num = $this->footnote_counter++;
3016
				$ref_count_mark = '';
3017
			} else {
3018
				$ref_count_mark = $this->footnotes_ref_count[$node_id] += 1;
3019
			}
3020
3021
			$attr = "";
3022
			if ($this->fn_link_class != "") {
3023
				$class = $this->fn_link_class;
3024
				$class = $this->encodeAttribute($class);
3025
				$attr .= " class=\"$class\"";
3026
			}
3027
			if ($this->fn_link_title != "") {
3028
				$title = $this->fn_link_title;
3029
				$title = $this->encodeAttribute($title);
3030
				$attr .= " title=\"$title\"";
3031
			}
3032
3033
			$attr = str_replace("%%", $num, $attr);
3034
			$node_id = $this->encodeAttribute($node_id);
3035
3036
			return
3037
				"<sup id=\"fnref$ref_count_mark:$node_id\">".
3038
				"<a href=\"#fn:$node_id\"$attr>$num</a>".
3039
				"</sup>";
3040
		}
3041
3042
		return "[^".$matches[1]."]";
3043
	}
3044
3045
3046
	### Abbreviations ###
3047
3048 View Code Duplication
	function stripAbbreviations($text) {
3049
	#
3050
	# Strips abbreviations from text, stores titles in hash references.
3051
	#
3052
		$less_than_tab = $this->tab_width - 1;
3053
3054
		# Link defs are in the form: [id]*: url "optional title"
3055
		$text = preg_replace_callback('{
3056
			^[ ]{0,'.$less_than_tab.'}\*\[(.+?)\][ ]?:	# abbr_id = $1
3057
			(.*)					# text = $2 (no blank lines allowed)
3058
			}xm',
3059
			array(&$this, '_stripAbbreviations_callback'),
3060
			$text);
3061
		return $text;
3062
	}
3063
	function _stripAbbreviations_callback($matches) {
3064
		$abbr_word = $matches[1];
3065
		$abbr_desc = $matches[2];
3066
		if ($this->abbr_word_re)
3067
			$this->abbr_word_re .= '|';
3068
		$this->abbr_word_re .= preg_quote($abbr_word);
3069
		$this->abbr_desciptions[$abbr_word] = trim($abbr_desc);
3070
		return ''; # String that will replace the block
3071
	}
3072
3073
3074
	function doAbbreviations($text) {
3075
	#
3076
	# Find defined abbreviations in text and wrap them in <abbr> elements.
3077
	#
3078
		if ($this->abbr_word_re) {
3079
			// cannot use the /x modifier because abbr_word_re may
3080
			// contain significant spaces:
3081
			$text = preg_replace_callback('{'.
3082
				'(?<![\w\x1A])'.
3083
				'(?:'.$this->abbr_word_re.')'.
3084
				'(?![\w\x1A])'.
3085
				'}',
3086
				array(&$this, '_doAbbreviations_callback'), $text);
3087
		}
3088
		return $text;
3089
	}
3090
	function _doAbbreviations_callback($matches) {
3091
		$abbr = $matches[0];
3092
		if (isset($this->abbr_desciptions[$abbr])) {
3093
			$desc = $this->abbr_desciptions[$abbr];
3094
			if (empty($desc)) {
3095
				return $this->hashPart("<abbr>$abbr</abbr>");
3096
			} else {
3097
				$desc = $this->encodeAttribute($desc);
3098
				return $this->hashPart("<abbr title=\"$desc\">$abbr</abbr>");
3099
			}
3100
		} else {
3101
			return $matches[0];
3102
		}
3103
	}
3104
3105
}
3106
3107
3108
/*
3109
3110
PHP Markdown Extra
3111
==================
3112
3113
Description
3114
-----------
3115
3116
This is a PHP port of the original Markdown formatter written in Perl
3117
by John Gruber. This special "Extra" version of PHP Markdown features
3118
further enhancements to the syntax for making additional constructs
3119
such as tables and definition list.
3120
3121
Markdown is a text-to-HTML filter; it translates an easy-to-read /
3122
easy-to-write structured text format into HTML. Markdown's text format
3123
is mostly similar to that of plain text email, and supports features such
3124
as headers, *emphasis*, code blocks, blockquotes, and links.
3125
3126
Markdown's syntax is designed not as a generic markup language, but
3127
specifically to serve as a front-end to (X)HTML. You can use span-level
3128
HTML tags anywhere in a Markdown document, and you can use block level
3129
HTML tags (like <div> and <table> as well).
3130
3131
For more information about Markdown's syntax, see:
3132
3133
<http://daringfireball.net/projects/markdown/>
3134
3135
3136
Bugs
3137
----
3138
3139
To file bug reports please send email to:
3140
3141
<[email protected]>
3142
3143
Please include with your report: (1) the example input; (2) the output you
3144
expected; (3) the output Markdown actually produced.
3145
3146
3147
Version History
3148
---------------
3149
3150
See the readme file for detailed release notes for this version.
3151
3152
3153
Copyright and License
3154
---------------------
3155
3156
PHP Markdown & Extra
3157
Copyright (c) 2004-2013 Michel Fortin
3158
<http://michelf.ca/>
3159
All rights reserved.
3160
3161
Based on Markdown
3162
Copyright (c) 2003-2006 John Gruber
3163
<http://daringfireball.net/>
3164
All rights reserved.
3165
3166
Redistribution and use in source and binary forms, with or without
3167
modification, are permitted provided that the following conditions are
3168
met:
3169
3170
*	Redistributions of source code must retain the above copyright notice,
3171
	this list of conditions and the following disclaimer.
3172
3173
*	Redistributions in binary form must reproduce the above copyright
3174
	notice, this list of conditions and the following disclaimer in the
3175
	documentation and/or other materials provided with the distribution.
3176
3177
*	Neither the name "Markdown" nor the names of its contributors may
3178
	be used to endorse or promote products derived from this software
3179
	without specific prior written permission.
3180
3181
This software is provided by the copyright holders and contributors "as
3182
is" and any express or implied warranties, including, but not limited
3183
to, the implied warranties of merchantability and fitness for a
3184
particular purpose are disclaimed. In no event shall the copyright owner
3185
or contributors be liable for any direct, indirect, incidental, special,
3186
exemplary, or consequential damages (including, but not limited to,
3187
procurement of substitute goods or services; loss of use, data, or
3188
profits; or business interruption) however caused and on any theory of
3189
liability, whether in contract, strict liability, or tort (including
3190
negligence or otherwise) arising in any way out of the use of this
3191
software, even if advised of the possibility of such damage.
3192
3193
*/
3194
?>
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...