GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Issues (423)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

application/helpers/markdown_helper.php (9 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 14 and the first side effect is on line 22.

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

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

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

Loading history...
2
#
3
# 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
14
define('MARKDOWN_VERSION', "1.0.1q"); # 11 Apr 2013
15
define('MARKDOWNEXTRA_VERSION', "1.2.7"); # 11 Apr 2013
16
17
#
18
# Global default settings:
19
#
20
21
# Change to ">" for HTML output
22
@define('MARKDOWN_EMPTY_ELEMENT_SUFFIX', " />");
23
24
# Define the width of a tab for code blocks.
25
@define('MARKDOWN_TAB_WIDTH', 4);
26
27
# Optional title attribute for footnote links and backlinks.
28
@define('MARKDOWN_FN_LINK_TITLE', "");
29
@define('MARKDOWN_FN_BACKLINK_TITLE', "");
30
31
# Optional class attribute for footnote links and backlinks.
32
@define('MARKDOWN_FN_LINK_CLASS', "");
33
@define('MARKDOWN_FN_BACKLINK_CLASS', "");
34
35
# Optional class prefix for fenced code block.
36
@define('MARKDOWN_CODE_CLASS_PREFIX', "");
37
38
# Class attribute for code blocks goes on the `code` tag;
39
# setting this to true will put attributes on the `pre` tag instead.
40
@define('MARKDOWN_CODE_ATTR_ON_PRE', false);
41
42
#
43
# WordPress settings:
44
#
45
46
# Change to false to remove Markdown from posts and/or comments.
47
@define('MARKDOWN_WP_POSTS', true);
48
@define('MARKDOWN_WP_COMMENTS', true);
49
50
### Standard Function Interface ###
51
52
@define('MARKDOWN_PARSER_CLASS', 'MarkdownExtra_Parser');
53
54
function Markdown($text)
55
{
56
#
57
# Initialize the parser and return the result of its transform method.
58
#
59
    # Setup static parser variable.
60
    static $parser;
61
    if (!isset($parser)) {
62
        $parser_class = MARKDOWN_PARSER_CLASS;
63
        $parser       = new $parser_class;
64
    }
65
66
    # Transform text using parser.
67
    return $parser->transform($text);
68
}
69
70
### WordPress Plugin Interface ###
71
72
/*
73
Plugin Name: Markdown Extra
74
Plugin Name: Markdown
75
Plugin URI: http://michelf.ca/projects/php-markdown/
76
Description: <a href="http://daringfireball.net/projects/markdown/syntax">Markdown syntax</a> allows you to write using an easy-to-read, easy-to-write plain text format. Based on the original Perl version by <a href="http://daringfireball.net/">John Gruber</a>. <a href="http://michelf.ca/projects/php-markdown/">More...</a>
77
Version: 1.2.7
78
Author: Michel Fortin
79
Author URI: http://michelf.ca/
80
*/
81
82
if (isset($wp_version)) {
83
    # More details about how it works here:
84
    # <http://michelf.ca/weblog/2005/wordpress-text-flow-vs-markdown/>
85
86
    # Post content and excerpts
87
    # - Remove WordPress paragraph generator.
88
    # - Run Markdown on excerpt, then remove all tags.
89
    # - Add paragraph tag around the excerpt, but remove it for the excerpt rss.
90
    if (MARKDOWN_WP_POSTS) {
91
        remove_filter('the_content', 'wpautop');
92
        remove_filter('the_content_rss', 'wpautop');
93
        remove_filter('the_excerpt', 'wpautop');
94
        add_filter('the_content', 'mdwp_MarkdownPost', 6);
95
        add_filter('the_content_rss', 'mdwp_MarkdownPost', 6);
96
        add_filter('get_the_excerpt', 'mdwp_MarkdownPost', 6);
97
        add_filter('get_the_excerpt', 'trim', 7);
98
        add_filter('the_excerpt', 'mdwp_add_p');
99
        add_filter('the_excerpt_rss', 'mdwp_strip_p');
100
101
        remove_filter('content_save_pre', 'balanceTags', 50);
102
        remove_filter('excerpt_save_pre', 'balanceTags', 50);
103
        add_filter('the_content', 'balanceTags', 50);
104
        add_filter('get_the_excerpt', 'balanceTags', 9);
105
    }
106
107
    # Add a footnote id prefix to posts when inside a loop.
108
    function mdwp_MarkdownPost($text)
109
    {
110
        static $parser;
111
        if (!$parser) {
112
            $parser_class = MARKDOWN_PARSER_CLASS;
113
            $parser       = new $parser_class;
114
        }
115
        if (is_single() || is_page() || is_feed()) {
116
            $parser->fn_id_prefix = "";
117
        } else {
118
            $parser->fn_id_prefix = get_the_ID() . ".";
119
        }
120
        return $parser->transform($text);
121
    }
122
123
    # Comments
124
    # - Remove WordPress paragraph generator.
125
    # - Remove WordPress auto-link generator.
126
    # - Scramble important tags before passing them to the kses filter.
127
    # - Run Markdown on excerpt then remove paragraph tags.
128
    if (MARKDOWN_WP_COMMENTS) {
129
        remove_filter('comment_text', 'wpautop', 30);
130
        remove_filter('comment_text', 'make_clickable');
131
        add_filter('pre_comment_content', 'Markdown', 6);
132
        add_filter('pre_comment_content', 'mdwp_hide_tags', 8);
133
        add_filter('pre_comment_content', 'mdwp_show_tags', 12);
134
        add_filter('get_comment_text', 'Markdown', 6);
135
        add_filter('get_comment_excerpt', 'Markdown', 6);
136
        add_filter('get_comment_excerpt', 'mdwp_strip_p', 7);
137
138
        global $mdwp_hidden_tags, $mdwp_placeholders;
139
        $mdwp_hidden_tags  = explode(
140
            ' ',
141
            '<p> </p> <pre> </pre> <ol> </ol> <ul> </ul> <li> </li>'
142
        );
143
        $mdwp_placeholders = explode(
144
            ' ',
145
            str_rot13(
146
                'pEj07ZbbBZ U1kqgh4w4p pre2zmeN6K QTi31t9pre ol0MP1jzJR ' .
147
                'ML5IjmbRol ulANi1NsGY J7zRLJqPul liA8ctl16T K9nhooUHli'
148
            )
149
        );
150
    }
151
152
    function mdwp_add_p($text)
153
    {
154
        if (!preg_match('{^$|^<(p|ul|ol|dl|pre|blockquote)>}i', $text)) {
155
            $text = '<p>' . $text . '</p>';
156
            $text = preg_replace('{\n{2,}}', "</p>\n\n<p>", $text);
157
        }
158
        return $text;
159
    }
160
161
    function mdwp_strip_p($t) { return preg_replace('{</?p>}i', '', $t); }
162
163
    function mdwp_hide_tags($text)
164
    {
165
        global $mdwp_hidden_tags, $mdwp_placeholders;
166
        return str_replace($mdwp_hidden_tags, $mdwp_placeholders, $text);
167
    }
168
169
    function mdwp_show_tags($text)
170
    {
171
        global $mdwp_hidden_tags, $mdwp_placeholders;
172
        return str_replace($mdwp_placeholders, $mdwp_hidden_tags, $text);
173
    }
174
}
175
176
### bBlog Plugin Info ###
177
178
function identify_modifier_markdown()
179
{
180
    return array(
181
        'name'        => 'markdown',
182
        'type'        => 'modifier',
183
        'nicename'    => 'PHP Markdown Extra',
184
        'description' => 'A text-to-HTML conversion tool for web writers',
185
        'authors'     => 'Michel Fortin and John Gruber',
186
        'licence'     => 'GPL',
187
        'version'     => MARKDOWNEXTRA_VERSION,
188
        'help'        => '<a href="http://daringfireball.net/projects/markdown/syntax">Markdown syntax</a> allows you to write using an easy-to-read, easy-to-write plain text format. Based on the original Perl version by <a href="http://daringfireball.net/">John Gruber</a>. <a href="http://michelf.ca/projects/php-markdown/">More...</a>',
189
    );
190
}
191
192
### Smarty Modifier Interface ###
193
194
function smarty_modifier_markdown($text)
195
{
196
    return Markdown($text);
197
}
198
199
### Textile Compatibility Mode ###
200
201
# Rename this file to "classTextile.php" and it can replace Textile everywhere.
202
203
if (strcasecmp(substr(__FILE__, - 16), "classTextile.php") == 0) {
204
    # Try to include PHP SmartyPants. Should be in the same directory.
205
    @include_once 'smartypants.php';
206
207
    # Fake Textile class. It calls Markdown instead.
208
    class Textile
209
    {
210
        function TextileThis($text, $lite = '', $encode = '')
211
        {
212
            if ($lite == '' && $encode == '') {
213
                $text = Markdown($text);
214
            }
215
            if (function_exists('SmartyPants')) {
216
                $text = SmartyPants($text);
217
            }
218
            return $text;
219
        }
220
221
        # Fake restricted version: restrictions are not supported for now.
222
        function TextileRestricted($text, $lite = '', $noimage = '')
223
        {
224
            return $this->TextileThis($text, $lite);
225
        }
226
227
        # Workaround to ensure compatibility with TextPattern 4.0.3.
228
        function blockLite($text) { return $text; }
229
    }
230
}
231
232
#
233
# Markdown Parser Class
234
#
235
236
class Markdown_Parser
237
{
238
239
    ### Configuration Variables ###
240
241
    # Change to ">" for HTML output.
242
    var $empty_element_suffix = MARKDOWN_EMPTY_ELEMENT_SUFFIX;
243
244
    var $tab_width = MARKDOWN_TAB_WIDTH;
245
246
    # Change to `true` to disallow markup or entities.
247
    var $no_markup = false;
248
249
    var $no_entities = false;
250
251
    # Predefined urls and titles for reference links and images.
252
    var $predef_urls = array();
253
254
    var $predef_titles = array();
255
256
    ### Parser Implementation ###
257
258
    # Regex to match balanced [brackets].
259
    # Needed to insert a maximum bracked depth while converting to PHP.
260
    var $nested_brackets_depth = 6;
261
262
    var $nested_brackets_re;
263
264
    var $nested_url_parenthesis_depth = 4;
265
266
    var $nested_url_parenthesis_re;
267
268
    # Table of hash values for escaped characters:
269
    var $escape_chars = '\`*_{}[]()>#+-.!';
270
271
    var $escape_chars_re;
272
273
    function __construct()
274
    {
275
        #
276
        # Constructor function. Initialize appropriate member variables.
277
        #
278
        $this->_initDetab();
279
        $this->prepareItalicsAndBold();
280
281
        $this->nested_brackets_re =
282
            str_repeat('(?>[^\[\]]+|\[', $this->nested_brackets_depth) .
283
            str_repeat('\])*', $this->nested_brackets_depth);
284
285
        $this->nested_url_parenthesis_re =
286
            str_repeat('(?>[^()\s]+|\(', $this->nested_url_parenthesis_depth) .
287
            str_repeat('(?>\)))*', $this->nested_url_parenthesis_depth);
288
289
        $this->escape_chars_re = '[' . preg_quote($this->escape_chars) . ']';
290
291
        # Sort document, block, and span gamut in ascendent priority order.
292
        asort($this->document_gamut);
293
        asort($this->block_gamut);
294
        asort($this->span_gamut);
295
    }
296
297
    # Internal hashes used during transformation.
298
    var $urls = array();
299
300
    var $titles = array();
301
302
    var $html_hashes = array();
303
304
    # Status flag to avoid invalid nesting.
305
    var $in_anchor = false;
306
307
    function setup()
308
    {
309
        #
310
        # Called before the transformation process starts to setup parser
311
        # states.
312
        #
313
        # Clear global hashes.
314
        $this->urls        = $this->predef_urls;
315
        $this->titles      = $this->predef_titles;
316
        $this->html_hashes = array();
317
318
        $this->in_anchor = false;
319
    }
320
321
    function teardown()
322
    {
323
        #
324
        # Called after the transformation process to clear any variable
325
        # which may be taking up memory unnecessarly.
326
        #
327
        $this->urls        = array();
328
        $this->titles      = array();
329
        $this->html_hashes = array();
330
    }
331
332
    function transform($text)
333
    {
334
        #
335
        # Main function. Performs some preprocessing on the input text
336
        # and pass it through the document gamut.
337
        #
338
        $this->setup();
339
340
        # Remove UTF-8 BOM and marker character in input, if present.
341
        $text = preg_replace('{^\xEF\xBB\xBF|\x1A}', '', $text);
342
343
        # Standardize line endings:
344
        #   DOS to Unix and Mac to Unix
345
        $text = preg_replace('{\r\n?}', "\n", $text);
346
347
        # Make sure $text ends with a couple of newlines:
348
        $text .= "\n\n";
349
350
        # Convert all tabs to spaces.
351
        $text = $this->detab($text);
352
353
        # Turn block-level HTML blocks into hash entries
354
        $text = $this->hashHTMLBlocks($text);
355
356
        # Strip any lines consisting only of spaces and tabs.
357
        # This makes subsequent regexen easier to write, because we can
358
        # match consecutive blank lines with /\n+/ instead of something
359
        # contorted like /[ ]*\n+/ .
360
        $text = preg_replace('/^[ ]+$/m', '', $text);
361
362
        # Run document gamut methods.
363
        foreach ($this->document_gamut as $method => $priority) {
364
            $text = $this->$method($text);
365
        }
366
367
        $this->teardown();
368
369
        return $text . "\n";
370
    }
371
372
    var $document_gamut = array(
373
        # Strip link definitions, store in hashes.
374
        "stripLinkDefinitions" => 20,
375
        "runBasicBlockGamut"   => 30,
376
    );
377
378 View Code Duplication
    function stripLinkDefinitions($text)
379
    {
380
        #
381
        # Strips link definitions from text, stores the URLs and titles in
382
        # hash references.
383
        #
384
        $less_than_tab = $this->tab_width - 1;
385
386
        # Link defs are in the form: ^[id]: url "optional title"
387
        $text = preg_replace_callback(
388
            '{
389
							^[ ]{0,' . $less_than_tab . '}\[(.+)\][ ]?:	# id = $1
390
							  [ ]*
391
							  \n?				# maybe *one* newline
392
							  [ ]*
393
							(?:
394
							  <(.+?)>			# url = $2
395
							|
396
							  (\S+?)			# url = $3
397
							)
398
							  [ ]*
399
							  \n?				# maybe one newline
400
							  [ ]*
401
							(?:
402
								(?<=\s)			# lookbehind for whitespace
403
								["(]
404
								(.*?)			# title = $4
405
								[")]
406
								[ ]*
407
							)?	# title is optional
408
							(?:\n+|\Z)
409
			}xm',
410
            array(&$this, '_stripLinkDefinitions_callback'),
411
            $text
412
        );
413
        return $text;
414
    }
415
416
    function _stripLinkDefinitions_callback($matches)
417
    {
418
        $link_id                = strtolower($matches[1]);
419
        $url                    = $matches[2] == '' ? $matches[3] : $matches[2];
420
        $this->urls[$link_id]   = $url;
421
        $this->titles[$link_id] =& $matches[4];
422
        return ''; # String that will replace the block
423
    }
424
425
    function hashHTMLBlocks($text)
426
    {
427
        if ($this->no_markup) {
428
            return $text;
429
        }
430
431
        $less_than_tab = $this->tab_width - 1;
432
433
        # Hashify HTML blocks:
434
        # We only want to do this for block-level HTML tags, such as headers,
435
        # lists, and tables. That's because we still want to wrap <p>s around
436
        # "paragraphs" that are wrapped in non-block-level tags, such as anchors,
437
        # phrase emphasis, and spans. The list of tags we're looking for is
438
        # hard-coded:
439
        #
440
        # *  List "a" is made of tags which can be both inline or block-level.
441
        #    These will be treated block-level when the start tag is alone on
442
        #    its line, otherwise they're not matched here and will be taken as
443
        #    inline later.
444
        # *  List "b" is made of tags which are always block-level;
445
        #
446
        $block_tags_a_re = 'ins|del';
447
        $block_tags_b_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|' .
448
                           'script|noscript|form|fieldset|iframe|math|svg|' .
449
                           'article|section|nav|aside|hgroup|header|footer|' .
450
                           'figure';
451
452
        # Regular expression for the content of a block tag.
453
        $nested_tags_level = 4;
454
        $attr              = '
455
			(?>				# optional tag attributes
456
			  \s			# starts with whitespace
457
			  (?>
458
				[^>"/]+		# text outside quotes
459
			  |
460
				/+(?!>)		# slash not followed by ">"
461
			  |
462
				"[^"]*"		# text inside double quotes (tolerate ">")
463
			  |
464
				\'[^\']*\'	# text inside single quotes (tolerate ">")
465
			  )*
466
			)?
467
			';
468
        $content           =
469
            str_repeat(
470
                '
471
				(?>
472
				  [^<]+			# content without tag
473
				|
474
				  <\2			# nested opening tag
475
					' . $attr . '	# attributes
476
					(?>
477
					  />
478
					|
479
					  >',
480
                $nested_tags_level
481
            ) . # end of opening tag
482
            '.*?' . # last level nested tag content
483
            str_repeat(
484
                '
485
					  </\2\s*>	# closing nested tag
486
					)
487
				  |
488
					<(?!/\2\s*>	# other tags with a different name
489
				  )
490
				)*',
491
                $nested_tags_level
492
            );
493
        $content2          = str_replace('\2', '\3', $content);
494
495
        # First, look for nested blocks, e.g.:
496
        # 	<div>
497
        # 		<div>
498
        # 		tags for inner block must be indented.
499
        # 		</div>
500
        # 	</div>
501
        #
502
        # The outermost tags must start at the left margin for this to match, and
503
        # the inner nested divs must be indented.
504
        # We need to do this before the next, more liberal match, because the next
505
        # match will start at the first `<div>` and stop at the first `</div>`.
506
        $text = preg_replace_callback(
507
            '{(?>
508
			(?>
509
				(?<=\n\n)		# Starting after a blank line
510
				|				# or
511
				\A\n?			# the beginning of the doc
512
			)
513
			(						# save in $1
514
515
			  # Match from `\n<tag>` to `</tag>\n`, handling nested tags
516
			  # in between.
517
518
						[ ]{0,' . $less_than_tab . '}
519
						<(' . $block_tags_b_re . ')# start tag = $2
520
						' . $attr . '>			# attributes followed by > and \n
521
						' . $content . '		# content, support nesting
522
						</\2>				# the matching end tag
523
						[ ]*				# trailing spaces/tabs
524
						(?=\n+|\Z)	# followed by a newline or end of document
525
526
			| # Special version for tags of group a.
527
528
						[ ]{0,' . $less_than_tab . '}
529
						<(' . $block_tags_a_re . ')# start tag = $3
530
						' . $attr . '>[ ]*\n	# attributes followed by >
531
						' . $content2 . '		# content, support nesting
532
						</\3>				# the matching end tag
533
						[ ]*				# trailing spaces/tabs
534
						(?=\n+|\Z)	# followed by a newline or end of document
535
536
			| # Special case just for <hr />. It was easier to make a special
537
			  # case than to make the other regex more complicated.
538
539
						[ ]{0,' . $less_than_tab . '}
540
						<(hr)				# start tag = $2
541
						' . $attr . '			# attributes
542
						/?>					# the matching end tag
543
						[ ]*
544
						(?=\n{2,}|\Z)		# followed by a blank line or end of document
545
546
			| # Special case for standalone HTML comments:
547
548
					[ ]{0,' . $less_than_tab . '}
549
					(?s:
550
						<!-- .*? -->
551
					)
552
					[ ]*
553
					(?=\n{2,}|\Z)		# followed by a blank line or end of document
554
555
			| # PHP and ASP-style processor instructions (<? and <%)
556
557
					[ ]{0,' . $less_than_tab . '}
558
					(?s:
559
						<([?%])			# $2
560
						.*?
561
						\2>
562
					)
563
					[ ]*
564
					(?=\n{2,}|\Z)		# followed by a blank line or end of document
565
566
			)
567
			)}Sxmi',
568
            array(&$this, '_hashHTMLBlocks_callback'),
569
            $text
570
        );
571
572
        return $text;
573
    }
574
575
    function _hashHTMLBlocks_callback($matches)
576
    {
577
        $text = $matches[1];
578
        $key  = $this->hashBlock($text);
579
        return "\n\n$key\n\n";
580
    }
581
582
    function hashPart($text, $boundary = 'X')
583
    {
584
        #
585
        # Called whenever a tag must be hashed when a function insert an atomic
586
        # element in the text stream. Passing $text to through this function gives
587
        # a unique text-token which will be reverted back when calling unhash.
588
        #
589
        # The $boundary argument specify what character should be used to surround
590
        # the token. By convension, "B" is used for block elements that needs not
591
        # to be wrapped into paragraph tags at the end, ":" is used for elements
592
        # that are word separators and "X" is used in the general case.
593
        #
594
        # Swap back any tag hash found in $text so we do not have to `unhash`
595
        # multiple times at the end.
596
        $text = $this->unhash($text);
597
598
        # Then hash the block.
599
        static $i = 0;
600
        $key                     = "$boundary\x1A" . ++ $i . $boundary;
601
        $this->html_hashes[$key] = $text;
602
        return $key; # String that will replace the tag.
603
    }
604
605
    function hashBlock($text)
606
    {
607
        #
608
        # Shortcut function for hashPart with block-level boundaries.
609
        #
610
        return $this->hashPart($text, 'B');
611
    }
612
613
    var $block_gamut = array(
614
        #
615
        # These are all the transformations that form block-level
616
        # tags like paragraphs, headers, and list items.
617
        #
618
        "doHeaders"         => 10,
619
        "doHorizontalRules" => 20,
620
        "doLists"           => 40,
621
        "doCodeBlocks"      => 50,
622
        "doBlockQuotes"     => 60,
623
    );
624
625
    function runBlockGamut($text)
626
    {
627
        #
628
        # Run block gamut tranformations.
629
        #
630
        # We need to escape raw HTML in Markdown source before doing anything
631
        # else. This need to be done for each block, and not only at the
632
        # begining in the Markdown function since hashed blocks can be part of
633
        # list items and could have been indented. Indented blocks would have
634
        # been seen as a code block in a previous pass of hashHTMLBlocks.
635
        $text = $this->hashHTMLBlocks($text);
636
637
        return $this->runBasicBlockGamut($text);
638
    }
639
640
    function runBasicBlockGamut($text)
641
    {
642
        #
643
        # Run block gamut tranformations, without hashing HTML blocks. This is
644
        # useful when HTML blocks are known to be already hashed, like in the first
645
        # whole-document pass.
646
        #
647
        foreach ($this->block_gamut as $method => $priority) {
648
            $text = $this->$method($text);
649
        }
650
651
        # Finally form paragraph and restore hashed blocks.
652
        $text = $this->formParagraphs($text);
653
654
        return $text;
655
    }
656
657
    function doHorizontalRules($text)
658
    {
659
        # Do Horizontal Rules:
660
        return preg_replace(
661
            '{
662
				^[ ]{0,3}	# Leading space
663
				([-*_])		# $1: First marker
664
				(?>			# Repeated marker group
665
					[ ]{0,2}	# Zero, one, or two spaces.
666
					\1			# Marker character
667
				){2,}		# Group repeated at least twice
668
				[ ]*		# Tailing spaces
669
				$			# End of line.
670
			}mx',
671
            "\n" . $this->hashBlock("<hr$this->empty_element_suffix") . "\n",
672
            $text
673
        );
674
    }
675
676
    var $span_gamut = array(
677
        #
678
        # These are all the transformations that occur *within* block-level
679
        # tags like paragraphs, headers, and list items.
680
        #
681
        # Process character escapes, code spans, and inline HTML
682
        # in one shot.
683
        "parseSpan"           => - 30,
684
        # Process anchor and image tags. Images must come first,
685
        # because ![foo][f] looks like an anchor.
686
        "doImages"            => 10,
687
        "doAnchors"           => 20,
688
        # Make links out of things like `<http://example.com/>`
689
        # Must come after doAnchors, because you can use < and >
690
        # delimiters in inline links like [this](<url>).
691
        "doAutoLinks"         => 30,
692
        "encodeAmpsAndAngles" => 40,
693
        "doItalicsAndBold"    => 50,
694
        "doHardBreaks"        => 60,
695
    );
696
697
    function runSpanGamut($text)
698
    {
699
        #
700
        # Run span gamut tranformations.
701
        #
702
        foreach ($this->span_gamut as $method => $priority) {
703
            $text = $this->$method($text);
704
        }
705
706
        return $text;
707
    }
708
709
    function doHardBreaks($text)
710
    {
711
        # Do hard breaks:
712
        return preg_replace_callback(
713
            '/ {2,}\n/',
714
            array(&$this, '_doHardBreaks_callback'),
715
            $text
716
        );
717
    }
718
719
    function _doHardBreaks_callback($matches)
720
    {
721
        return $this->hashPart("<br$this->empty_element_suffix\n");
722
    }
723
724 View Code Duplication
    function doAnchors($text)
725
    {
726
        #
727
        # Turn Markdown link shortcuts into XHTML <a> tags.
728
        #
729
        if ($this->in_anchor) {
730
            return $text;
731
        }
732
        $this->in_anchor = true;
733
734
        #
735
        # First, handle reference-style links: [link text] [id]
736
        #
737
        $text = preg_replace_callback(
738
            '{
739
			(					# wrap whole match in $1
740
			  \[
741
				(' . $this->nested_brackets_re . ')	# link text = $2
742
			  \]
743
744
			  [ ]?				# one optional space
745
			  (?:\n[ ]*)?		# one optional newline followed by spaces
746
747
			  \[
748
				(.*?)		# id = $3
749
			  \]
750
			)
751
			}xs',
752
            array(&$this, '_doAnchors_reference_callback'),
753
            $text
754
        );
755
756
        #
757
        # Next, inline-style links: [link text](url "optional title")
758
        #
759
        $text = preg_replace_callback(
760
            '{
761
			(				# wrap whole match in $1
762
			  \[
763
				(' . $this->nested_brackets_re . ')	# link text = $2
764
			  \]
765
			  \(			# literal paren
766
				[ \n]*
767
				(?:
768
					<(.+?)>	# href = $3
769
				|
770
					(' . $this->nested_url_parenthesis_re . ')	# href = $4
771
				)
772
				[ \n]*
773
				(			# $5
774
				  ([\'"])	# quote char = $6
775
				  (.*?)		# Title = $7
776
				  \6		# matching quote
777
				  [ \n]*	# ignore any spaces/tabs between closing quote and )
778
				)?			# title is optional
779
			  \)
780
			)
781
			}xs',
782
            array(&$this, '_doAnchors_inline_callback'),
783
            $text
784
        );
785
786
        #
787
        # Last, handle reference-style shortcuts: [link text]
788
        # These must come last in case you've also got [link text][1]
789
        # or [link text](/foo)
0 ignored issues
show
Unused Code Comprehensibility introduced by
42% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
790
        #
791
        $text = preg_replace_callback(
792
            '{
793
			(					# wrap whole match in $1
794
			  \[
795
				([^\[\]]+)		# link text = $2; can\'t contain [ or ]
796
			  \]
797
			)
798
			}xs',
799
            array(&$this, '_doAnchors_reference_callback'),
800
            $text
801
        );
802
803
        $this->in_anchor = false;
804
        return $text;
805
    }
806
807
    function _doAnchors_reference_callback($matches)
808
    {
809
        $whole_match = $matches[1];
810
        $link_text   = $matches[2];
811
        $link_id     =& $matches[3];
812
813
        if ($link_id == "") {
814
            # for shortcut links like [this][] or [this].
0 ignored issues
show
Unused Code Comprehensibility introduced by
39% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
815
            $link_id = $link_text;
816
        }
817
818
        # lower-case and turn embedded newlines into spaces
819
        $link_id = strtolower($link_id);
820
        $link_id = preg_replace('{[ ]?\n}', ' ', $link_id);
821
822
        if (isset($this->urls[$link_id])) {
823
            $url = $this->urls[$link_id];
824
            $url = $this->encodeAttribute($url);
825
826
            $result = "<a href=\"$url\"";
827 View Code Duplication
            if (isset($this->titles[$link_id])) {
828
                $title = $this->titles[$link_id];
829
                $title = $this->encodeAttribute($title);
830
                $result .= " title=\"$title\"";
831
            }
832
833
            $link_text = $this->runSpanGamut($link_text);
834
            $result .= ">$link_text</a>";
835
            $result = $this->hashPart($result);
836
        } else {
837
            $result = $whole_match;
838
        }
839
        return $result;
840
    }
841
842 View Code Duplication
    function _doAnchors_inline_callback($matches)
843
    {
844
        $whole_match = $matches[1];
845
        $link_text   = $this->runSpanGamut($matches[2]);
846
        $url         = $matches[3] == '' ? $matches[4] : $matches[3];
847
        $title       =& $matches[7];
848
849
        $url = $this->encodeAttribute($url);
850
851
        $result = "<a href=\"$url\"";
852
        if (isset($title)) {
853
            $title = $this->encodeAttribute($title);
854
            $result .= " title=\"$title\"";
855
        }
856
857
        $link_text = $this->runSpanGamut($link_text);
858
        $result .= ">$link_text</a>";
859
860
        return $this->hashPart($result);
861
    }
862
863 View Code Duplication
    function doImages($text)
864
    {
865
        #
866
        # Turn Markdown image shortcuts into <img> tags.
867
        #
868
        #
869
        # First, handle reference-style labeled images: ![alt text][id]
870
        #
871
        $text = preg_replace_callback(
872
            '{
873
			(				# wrap whole match in $1
874
			  !\[
875
				(' . $this->nested_brackets_re . ')		# alt text = $2
876
			  \]
877
878
			  [ ]?				# one optional space
879
			  (?:\n[ ]*)?		# one optional newline followed by spaces
880
881
			  \[
882
				(.*?)		# id = $3
883
			  \]
884
885
			)
886
			}xs',
887
            array(&$this, '_doImages_reference_callback'),
888
            $text
889
        );
890
891
        #
892
        # Next, handle inline images:  ![alt text](url "optional title")
893
        # Don't forget: encode * and _
894
        #
895
        $text = preg_replace_callback(
896
            '{
897
			(				# wrap whole match in $1
898
			  !\[
899
				(' . $this->nested_brackets_re . ')		# alt text = $2
900
			  \]
901
			  \s?			# One optional whitespace character
902
			  \(			# literal paren
903
				[ \n]*
904
				(?:
905
					<(\S*)>	# src url = $3
906
				|
907
					(' . $this->nested_url_parenthesis_re . ')	# src url = $4
908
				)
909
				[ \n]*
910
				(			# $5
911
				  ([\'"])	# quote char = $6
912
				  (.*?)		# title = $7
913
				  \6		# matching quote
914
				  [ \n]*
915
				)?			# title is optional
916
			  \)
917
			)
918
			}xs',
919
            array(&$this, '_doImages_inline_callback'),
920
            $text
921
        );
922
923
        return $text;
924
    }
925
926
    function _doImages_reference_callback($matches)
927
    {
928
        $whole_match = $matches[1];
929
        $alt_text    = $matches[2];
930
        $link_id     = strtolower($matches[3]);
931
932
        if ($link_id == "") {
933
            $link_id = strtolower($alt_text); # for shortcut links like ![this][].
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
934
        }
935
936
        $alt_text = $this->encodeAttribute($alt_text);
937
        if (isset($this->urls[$link_id])) {
938
            $url    = $this->encodeAttribute($this->urls[$link_id]);
939
            $result = "<img src=\"$url\" alt=\"$alt_text\"";
940 View Code Duplication
            if (isset($this->titles[$link_id])) {
941
                $title = $this->titles[$link_id];
942
                $title = $this->encodeAttribute($title);
943
                $result .= " title=\"$title\"";
944
            }
945
            $result .= $this->empty_element_suffix;
946
            $result = $this->hashPart($result);
947
        } else {
948
            # If there's no such link ID, leave intact:
949
            $result = $whole_match;
950
        }
951
952
        return $result;
953
    }
954
955 View Code Duplication
    function _doImages_inline_callback($matches)
956
    {
957
        $whole_match = $matches[1];
958
        $alt_text    = $matches[2];
959
        $url         = $matches[3] == '' ? $matches[4] : $matches[3];
960
        $title       =& $matches[7];
961
962
        $alt_text = $this->encodeAttribute($alt_text);
963
        $url      = $this->encodeAttribute($url);
964
        $result   = "<img src=\"$url\" alt=\"$alt_text\"";
965
        if (isset($title)) {
966
            $title = $this->encodeAttribute($title);
967
            $result .= " title=\"$title\""; # $title already quoted
968
        }
969
        $result .= $this->empty_element_suffix;
970
971
        return $this->hashPart($result);
972
    }
973
974
    function doHeaders($text)
975
    {
976
        # Setext-style headers:
977
        #	  Header 1
978
        #	  ========
979
        #
980
        #	  Header 2
981
        #	  --------
982
        #
983
        $text = preg_replace_callback(
984
            '{ ^(.+?)[ ]*\n(=+|-+)[ ]*\n+ }mx',
985
            array(&$this, '_doHeaders_callback_setext'),
986
            $text
987
        );
988
989
        # atx-style headers:
990
        #	# Header 1
991
        #	## Header 2
992
        #	## Header 2 with closing hashes ##
993
        #	...
994
        #	###### Header 6
995
        #
996
        $text = preg_replace_callback(
997
            '{
998
				^(\#{1,6})	# $1 = string of #\'s
999
				[ ]*
1000
				(.+?)		# $2 = Header text
1001
				[ ]*
1002
				\#*			# optional closing #\'s (not counted)
1003
				\n+
1004
			}xm',
1005
            array(&$this, '_doHeaders_callback_atx'),
1006
            $text
1007
        );
1008
1009
        return $text;
1010
    }
1011
1012
    function _doHeaders_callback_setext($matches)
1013
    {
1014
        # Terrible hack to check we haven't found an empty list item.
1015 View Code Duplication
        if ($matches[2] == '-' && preg_match('{^-(?: |$)}', $matches[1])) {
1016
            return $matches[0];
1017
        }
1018
1019
        $level = $matches[2]{0} == '=' ? 1 : 2;
1020
        $block = "<h$level>" . $this->runSpanGamut($matches[1]) . "</h$level>";
1021
        return "\n" . $this->hashBlock($block) . "\n\n";
1022
    }
1023
1024
    function _doHeaders_callback_atx($matches)
1025
    {
1026
        $level = strlen($matches[1]);
1027
        $block = "<h$level>" . $this->runSpanGamut($matches[2]) . "</h$level>";
1028
        return "\n" . $this->hashBlock($block) . "\n\n";
1029
    }
1030
1031
    function doLists($text)
1032
    {
1033
        #
1034
        # Form HTML ordered (numbered) and unordered (bulleted) lists.
1035
        #
1036
        $less_than_tab = $this->tab_width - 1;
1037
1038
        # Re-usable patterns to match list item bullets and number markers:
1039
        $marker_ul_re  = '[*+-]';
1040
        $marker_ol_re  = '\d+[\.]';
1041
        $marker_any_re = "(?:$marker_ul_re|$marker_ol_re)";
1042
1043
        $markers_relist = array(
1044
            $marker_ul_re => $marker_ol_re,
1045
            $marker_ol_re => $marker_ul_re,
1046
        );
1047
1048
        foreach ($markers_relist as $marker_re => $other_marker_re) {
1049
            # Re-usable pattern to match any entirel ul or ol list:
1050
            $whole_list_re = '
1051
				(								# $1 = whole list
1052
				  (								# $2
1053
					([ ]{0,' . $less_than_tab . '})	# $3 = number of spaces
1054
					(' . $marker_re . ')			# $4 = first list item marker
1055
					[ ]+
1056
				  )
1057
				  (?s:.+?)
1058
				  (								# $5
1059
					  \z
1060
					|
1061
					  \n{2,}
1062
					  (?=\S)
1063
					  (?!						# Negative lookahead for another list item marker
1064
						[ ]*
1065
						' . $marker_re . '[ ]+
1066
					  )
1067
					|
1068
					  (?=						# Lookahead for another kind of list
1069
					    \n
1070
						\3						# Must have the same indentation
1071
						' . $other_marker_re . '[ ]+
1072
					  )
1073
				  )
1074
				)
1075
			'; // mx
1076
1077
            # We use a different prefix before nested lists than top-level lists.
1078
            # See extended comment in _ProcessListItems().
1079
1080
            if ($this->list_level) {
1081
                $text = preg_replace_callback(
1082
                    '{
1083
						^
1084
						' . $whole_list_re . '
1085
					}mx',
1086
                    array(&$this, '_doLists_callback'),
1087
                    $text
1088
                );
1089
            } else {
1090
                $text = preg_replace_callback(
1091
                    '{
1092
						(?:(?<=\n)\n|\A\n?) # Must eat the newline
1093
						' . $whole_list_re . '
1094
					}mx',
1095
                    array(&$this, '_doLists_callback'),
1096
                    $text
1097
                );
1098
            }
1099
        }
1100
1101
        return $text;
1102
    }
1103
1104
    function _doLists_callback($matches)
1105
    {
1106
        # Re-usable patterns to match list item bullets and number markers:
1107
        $marker_ul_re  = '[*+-]';
1108
        $marker_ol_re  = '\d+[\.]';
1109
        $marker_any_re = "(?:$marker_ul_re|$marker_ol_re)";
1110
1111
        $list      = $matches[1];
1112
        $list_type = preg_match("/$marker_ul_re/", $matches[4]) ? "ul" : "ol";
1113
1114
        $marker_any_re = ($list_type == "ul" ? $marker_ul_re : $marker_ol_re);
1115
1116
        $list .= "\n";
1117
        $result = $this->processListItems($list, $marker_any_re);
1118
1119
        $result = $this->hashBlock("<$list_type>\n" . $result . "</$list_type>");
1120
        return "\n" . $result . "\n\n";
1121
    }
1122
1123
    var $list_level = 0;
1124
1125
    function processListItems($list_str, $marker_any_re)
1126
    {
1127
        #
1128
        #	Process the contents of a single ordered or unordered list, splitting it
1129
        #	into individual list items.
1130
        #
1131
        # The $this->list_level global keeps track of when we're inside a list.
1132
        # Each time we enter a list, we increment it; when we leave a list,
1133
        # we decrement. If it's zero, we're not in a list anymore.
1134
        #
1135
        # We do this because when we're not inside a list, we want to treat
1136
        # something like this:
1137
        #
1138
        #		I recommend upgrading to version
1139
        #		8. Oops, now this line is treated
1140
        #		as a sub-list.
1141
        #
1142
        # As a single paragraph, despite the fact that the second line starts
1143
        # with a digit-period-space sequence.
1144
        #
1145
        # Whereas when we're inside a list (or sub-list), that line will be
1146
        # treated as the start of a sub-list. What a kludge, huh? This is
1147
        # an aspect of Markdown's syntax that's hard to parse perfectly
1148
        # without resorting to mind-reading. Perhaps the solution is to
1149
        # change the syntax rules such that sub-lists must start with a
1150
        # starting cardinal number; e.g. "1." or "a.".
1151
1152
        $this->list_level ++;
1153
1154
        # trim trailing blank lines:
1155
        $list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str);
1156
1157
        $list_str = preg_replace_callback(
1158
            '{
1159
			(\n)?							# leading line = $1
1160
			(^[ ]*)							# leading whitespace = $2
1161
			(' . $marker_any_re . '				# list marker and space = $3
1162
				(?:[ ]+|(?=\n))	# space only required if item is not empty
1163
			)
1164
			((?s:.*?))						# list item text   = $4
1165
			(?:(\n+(?=\n))|\n)				# tailing blank line = $5
1166
			(?= \n* (\z | \2 (' . $marker_any_re . ') (?:[ ]+|(?=\n))))
1167
			}xm',
1168
            array(&$this, '_processListItems_callback'),
1169
            $list_str
1170
        );
1171
1172
        $this->list_level --;
1173
        return $list_str;
1174
    }
1175
1176
    function _processListItems_callback($matches)
1177
    {
1178
        $item               = $matches[4];
1179
        $leading_line       =& $matches[1];
1180
        $leading_space      =& $matches[2];
1181
        $marker_space       = $matches[3];
1182
        $tailing_blank_line =& $matches[5];
1183
1184 View Code Duplication
        if ($leading_line || $tailing_blank_line ||
1185
            preg_match('/\n{2,}/', $item)
1186
        ) {
1187
            # Replace marker with the appropriate whitespace indentation
1188
            $item = $leading_space . str_repeat(' ', strlen($marker_space)) . $item;
1189
            $item = $this->runBlockGamut($this->outdent($item) . "\n");
1190
        } else {
1191
            # Recursion for sub-lists:
1192
            $item = $this->doLists($this->outdent($item));
1193
            $item = preg_replace('/\n+$/', '', $item);
1194
            $item = $this->runSpanGamut($item);
1195
        }
1196
1197
        return "<li>" . $item . "</li>\n";
1198
    }
1199
1200
    function doCodeBlocks($text)
1201
    {
1202
        #
1203
        #	Process Markdown `<pre><code>` blocks.
1204
        #
1205
        $text = preg_replace_callback(
1206
            '{
1207
				(?:\n\n|\A\n?)
1208
				(	            # $1 = the code block -- one or more lines, starting with a space/tab
1209
				  (?>
1210
					[ ]{' . $this->tab_width . '}  # Lines must start with a tab or a tab-width of spaces
1211
					.*\n+
1212
				  )+
1213
				)
1214
				((?=^[ ]{0,' . $this->tab_width . '}\S)|\Z)	# Lookahead for non-space at line-start, or end of doc
1215
			}xm',
1216
            array(&$this, '_doCodeBlocks_callback'),
1217
            $text
1218
        );
1219
1220
        return $text;
1221
    }
1222
1223
    function _doCodeBlocks_callback($matches)
1224
    {
1225
        $codeblock = $matches[1];
1226
1227
        $codeblock = $this->outdent($codeblock);
1228
        $codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES);
1229
1230
        # trim leading newlines and trailing newlines
1231
        $codeblock = preg_replace('/\A\n+|\n+\z/', '', $codeblock);
1232
1233
        $codeblock = "<pre><code>$codeblock\n</code></pre>";
1234
        return "\n\n" . $this->hashBlock($codeblock) . "\n\n";
1235
    }
1236
1237
    function makeCodeSpan($code)
1238
    {
1239
        #
1240
        # Create a code span markup for $code. Called from handleSpanToken.
1241
        #
1242
        $code = htmlspecialchars(trim($code), ENT_NOQUOTES);
1243
        return $this->hashPart("<code>$code</code>");
1244
    }
1245
1246
    var $em_relist = array(
1247
        ''  => '(?:(?<!\*)\*(?!\*)|(?<!_)_(?!_))(?=\S|$)(?![\.,:;]\s)',
1248
        '*' => '(?<=\S|^)(?<!\*)\*(?!\*)',
1249
        '_' => '(?<=\S|^)(?<!_)_(?!_)',
1250
    );
1251
1252
    var $strong_relist = array(
1253
        ''   => '(?:(?<!\*)\*\*(?!\*)|(?<!_)__(?!_))(?=\S|$)(?![\.,:;]\s)',
1254
        '**' => '(?<=\S|^)(?<!\*)\*\*(?!\*)',
1255
        '__' => '(?<=\S|^)(?<!_)__(?!_)',
1256
    );
1257
1258
    var $em_strong_relist = array(
1259
        ''    => '(?:(?<!\*)\*\*\*(?!\*)|(?<!_)___(?!_))(?=\S|$)(?![\.,:;]\s)',
1260
        '***' => '(?<=\S|^)(?<!\*)\*\*\*(?!\*)',
1261
        '___' => '(?<=\S|^)(?<!_)___(?!_)',
1262
    );
1263
1264
    var $em_strong_prepared_relist;
1265
1266
    function prepareItalicsAndBold()
1267
    {
1268
        #
1269
        # Prepare regular expressions for searching emphasis tokens in any
1270
        # context.
1271
        #
1272
        foreach ($this->em_relist as $em => $em_re) {
1273
            foreach ($this->strong_relist as $strong => $strong_re) {
1274
                # Construct list of allowed token expressions.
1275
                $token_relist = array();
1276
                if (isset($this->em_strong_relist["$em$strong"])) {
1277
                    $token_relist[] = $this->em_strong_relist["$em$strong"];
1278
                }
1279
                $token_relist[] = $em_re;
1280
                $token_relist[] = $strong_re;
1281
1282
                # Construct master expression from list.
1283
                $token_re                                      = '{(' . implode('|', $token_relist) . ')}';
1284
                $this->em_strong_prepared_relist["$em$strong"] = $token_re;
1285
            }
1286
        }
1287
    }
1288
1289
    function doItalicsAndBold($text)
1290
    {
1291
        $token_stack  = array('');
1292
        $text_stack   = array('');
1293
        $em           = '';
1294
        $strong       = '';
1295
        $tree_char_em = false;
1296
1297
        while (1) {
1298
            #
1299
            # Get prepared regular expression for seraching emphasis tokens
1300
            # in current context.
1301
            #
1302
            $token_re = $this->em_strong_prepared_relist["$em$strong"];
1303
1304
            #
1305
            # Each loop iteration search for the next emphasis token.
1306
            # Each token is then passed to handleSpanToken.
1307
            #
1308
            $parts = preg_split($token_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE);
1309
            $text_stack[0] .= $parts[0];
1310
            $token =& $parts[1];
1311
            $text  =& $parts[2];
1312
1313 View Code Duplication
            if (empty($token)) {
1314
                # Reached end of text span: empty stack without emitting.
1315
                # any more emphasis.
1316
                while ($token_stack[0]) {
1317
                    $text_stack[1] .= array_shift($token_stack);
1318
                    $text_stack[0] .= array_shift($text_stack);
1319
                }
1320
                break;
1321
            }
1322
1323
            $token_len = strlen($token);
1324
            if ($tree_char_em) {
1325
                # Reached closing marker while inside a three-char emphasis.
1326
                if ($token_len == 3) {
1327
                    # Three-char closing marker, close em and strong.
1328
                    array_shift($token_stack);
1329
                    $span = array_shift($text_stack);
1330
                    $span = $this->runSpanGamut($span);
1331
                    $span = "<strong><em>$span</em></strong>";
1332
                    $text_stack[0] .= $this->hashPart($span);
1333
                    $em     = '';
1334
                    $strong = '';
1335
                } else {
1336
                    # Other closing marker: close one em or strong and
1337
                    # change current token state to match the other
1338
                    $token_stack[0] = str_repeat($token{0}, 3 - $token_len);
1339
                    $tag            = $token_len == 2 ? "strong" : "em";
1340
                    $span           = $text_stack[0];
1341
                    $span           = $this->runSpanGamut($span);
1342
                    $span           = "<$tag>$span</$tag>";
1343
                    $text_stack[0]  = $this->hashPart($span);
1344
                    $$tag           = ''; # $$tag stands for $em or $strong
0 ignored issues
show
Unused Code Comprehensibility introduced by
47% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
1345
                }
1346
                $tree_char_em = false;
1347
            } else if ($token_len == 3) {
1348
                if ($em) {
1349
                    # Reached closing marker for both em and strong.
1350
                    # Closing strong marker:
1351
                    for ($i = 0; $i < 2; ++ $i) {
1352
                        $shifted_token = array_shift($token_stack);
1353
                        $tag           = strlen($shifted_token) == 2 ? "strong" : "em";
1354
                        $span          = array_shift($text_stack);
1355
                        $span          = $this->runSpanGamut($span);
1356
                        $span          = "<$tag>$span</$tag>";
1357
                        $text_stack[0] .= $this->hashPart($span);
1358
                        $$tag = ''; # $$tag stands for $em or $strong
0 ignored issues
show
Unused Code Comprehensibility introduced by
47% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
1359
                    }
1360
                } else {
1361
                    # Reached opening three-char emphasis marker. Push on token
1362
                    # stack; will be handled by the special condition above.
1363
                    $em     = $token{0};
1364
                    $strong = "$em$em";
1365
                    array_unshift($token_stack, $token);
1366
                    array_unshift($text_stack, '');
1367
                    $tree_char_em = true;
1368
                }
1369
            } else if ($token_len == 2) {
1370
                if ($strong) {
1371
                    # Unwind any dangling emphasis marker:
1372 View Code Duplication
                    if (strlen($token_stack[0]) == 1) {
1373
                        $text_stack[1] .= array_shift($token_stack);
1374
                        $text_stack[0] .= array_shift($text_stack);
1375
                    }
1376
                    # Closing strong marker:
1377
                    array_shift($token_stack);
1378
                    $span = array_shift($text_stack);
1379
                    $span = $this->runSpanGamut($span);
1380
                    $span = "<strong>$span</strong>";
1381
                    $text_stack[0] .= $this->hashPart($span);
1382
                    $strong = '';
1383
                } else {
1384
                    array_unshift($token_stack, $token);
1385
                    array_unshift($text_stack, '');
1386
                    $strong = $token;
1387
                }
1388
            } else {
1389
                # Here $token_len == 1
1390
                if ($em) {
1391
                    if (strlen($token_stack[0]) == 1) {
1392
                        # Closing emphasis marker:
1393
                        array_shift($token_stack);
1394
                        $span = array_shift($text_stack);
1395
                        $span = $this->runSpanGamut($span);
1396
                        $span = "<em>$span</em>";
1397
                        $text_stack[0] .= $this->hashPart($span);
1398
                        $em = '';
1399
                    } else {
1400
                        $text_stack[0] .= $token;
1401
                    }
1402
                } else {
1403
                    array_unshift($token_stack, $token);
1404
                    array_unshift($text_stack, '');
1405
                    $em = $token;
1406
                }
1407
            }
1408
        }
1409
        return $text_stack[0];
1410
    }
1411
1412
    function doBlockQuotes($text)
1413
    {
1414
        $text = preg_replace_callback(
1415
            '/
1416
			  (								# Wrap whole match in $1
1417
				(?>
1418
				  ^[ ]*>[ ]?			# ">" at the start of a line
1419
					.+\n					# rest of the first line
1420
				  (.+\n)*					# subsequent consecutive lines
1421
				  \n*						# blanks
1422
				)+
1423
			  )
1424
			/xm',
1425
            array(&$this, '_doBlockQuotes_callback'),
1426
            $text
1427
        );
1428
1429
        return $text;
1430
    }
1431
1432
    function _doBlockQuotes_callback($matches)
1433
    {
1434
        $bq = $matches[1];
1435
        # trim one level of quoting - trim whitespace-only lines
1436
        $bq = preg_replace('/^[ ]*>[ ]?|^[ ]+$/m', '', $bq);
1437
        $bq = $this->runBlockGamut($bq); # recurse
1438
1439
        $bq = preg_replace('/^/m', "  ", $bq);
1440
        # These leading spaces cause problem with <pre> content,
1441
        # so we need to fix that:
1442
        $bq = preg_replace_callback(
1443
            '{(\s*<pre>.+?</pre>)}sx',
1444
            array(&$this, '_doBlockQuotes_callback2'),
1445
            $bq
1446
        );
1447
1448
        return "\n" . $this->hashBlock("<blockquote>\n$bq\n</blockquote>") . "\n\n";
1449
    }
1450
1451
    function _doBlockQuotes_callback2($matches)
1452
    {
1453
        $pre = $matches[1];
1454
        $pre = preg_replace('/^  /m', '', $pre);
1455
        return $pre;
1456
    }
1457
1458
    function formParagraphs($text)
1459
    {
1460
        #
1461
        #	Params:
1462
        #		$text - string to process with html <p> tags
1463
        #
1464
        # Strip leading and trailing lines:
1465
        $text = preg_replace('/\A\n+|\n+\z/', '', $text);
1466
1467
        $grafs = preg_split('/\n{2,}/', $text, - 1, PREG_SPLIT_NO_EMPTY);
1468
1469
        #
1470
        # Wrap <p> tags and unhashify HTML blocks
1471
        #
1472
        foreach ($grafs as $key => $value) {
1473
            if (!preg_match('/^B\x1A[0-9]+B$/', $value)) {
1474
                # Is a paragraph.
1475
                $value = $this->runSpanGamut($value);
1476
                $value = preg_replace('/^([ ]*)/', "<p>", $value);
1477
                $value .= "</p>";
1478
                $grafs[$key] = $this->unhash($value);
1479
            } else {
1480
                # Is a block.
1481
                # Modify elements of @grafs in-place...
1482
                $graf  = $value;
1483
                $block = $this->html_hashes[$graf];
1484
                $graf  = $block;
1485
//				if (preg_match('{
1486
//					\A
1487
//					(							# $1 = <div> tag
1488
//					  <div  \s+
1489
//					  [^>]*
1490
//					  \b
1491
//					  markdown\s*=\s*  ([\'"])	#	$2 = attr quote char
1492
//					  1
1493
//					  \2
1494
//					  [^>]*
1495
//					  >
1496
//					)
1497
//					(							# $3 = contents
1498
//					.*
1499
//					)
1500
//					(</div>)					# $4 = closing tag
1501
//					\z
1502
//					}xs', $block, $matches))
1503
//				{
1504
//					list(, $div_open, , $div_content, $div_close) = $matches;
1505
//
1506
//					# We can't call Markdown(), because that resets the hash;
1507
//					# that initialization code should be pulled into its own sub, though.
1508
//					$div_content = $this->hashHTMLBlocks($div_content);
1509
//
1510
//					# Run document gamut methods on the content.
1511
//					foreach ($this->document_gamut as $method => $priority) {
1512
//						$div_content = $this->$method($div_content);
1513
//					}
1514
//
1515
//					$div_open = preg_replace(
1516
//						'{\smarkdown\s*=\s*([\'"]).+?\1}', '', $div_open);
1517
//
1518
//					$graf = $div_open . "\n" . $div_content . "\n" . $div_close;
1519
//				}
1520
                $grafs[$key] = $graf;
1521
            }
1522
        }
1523
1524
        return implode("\n\n", $grafs);
1525
    }
1526
1527
    function encodeAttribute($text)
1528
    {
1529
        #
1530
        # Encode text for a double-quoted HTML attribute. This function
1531
        # is *not* suitable for attributes enclosed in single quotes.
1532
        #
1533
        $text = $this->encodeAmpsAndAngles($text);
1534
        $text = str_replace('"', '&quot;', $text);
1535
        return $text;
1536
    }
1537
1538
    function encodeAmpsAndAngles($text)
1539
    {
1540
        #
1541
        # Smart processing for ampersands and angle brackets that need to
1542
        # be encoded. Valid character entities are left alone unless the
1543
        # no-entities mode is set.
1544
        #
1545
        if ($this->no_entities) {
1546
            $text = str_replace('&', '&amp;', $text);
1547
        } else {
1548
            # Ampersand-encoding based entirely on Nat Irons's Amputator
1549
            # MT plugin: <http://bumppo.net/projects/amputator/>
1550
            $text = preg_replace(
1551
                '/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/',
1552
                '&amp;',
1553
                $text
1554
            );;
1555
        }
1556
        # Encode remaining <'s
1557
        $text = str_replace('<', '&lt;', $text);
1558
1559
        return $text;
1560
    }
1561
1562
    function doAutoLinks($text)
1563
    {
1564
        $text = preg_replace_callback(
1565
            '{<((https?|ftp|dict):[^\'">\s]+)>}i',
1566
            array(&$this, '_doAutoLinks_url_callback'),
1567
            $text
1568
        );
1569
1570
        # Email addresses: <[email protected]>
1571
        $text = preg_replace_callback(
1572
            '{
1573
			<
1574
			(?:mailto:)?
1575
			(
1576
				(?:
1577
					[-!#$%&\'*+/=?^_`.{|}~\w\x80-\xFF]+
1578
				|
1579
					".*?"
1580
				)
1581
				\@
1582
				(?:
1583
					[-a-z0-9\x80-\xFF]+(\.[-a-z0-9\x80-\xFF]+)*\.[a-z]+
1584
				|
1585
					\[[\d.a-fA-F:]+\]	# IPv4 & IPv6
1586
				)
1587
			)
1588
			>
1589
			}xi',
1590
            array(&$this, '_doAutoLinks_email_callback'),
1591
            $text
1592
        );
1593
1594
        return $text;
1595
    }
1596
1597
    function _doAutoLinks_url_callback($matches)
1598
    {
1599
        $url  = $this->encodeAttribute($matches[1]);
1600
        $link = "<a href=\"$url\">$url</a>";
1601
        return $this->hashPart($link);
1602
    }
1603
1604
    function _doAutoLinks_email_callback($matches)
1605
    {
1606
        $address = $matches[1];
1607
        $link    = $this->encodeEmailAddress($address);
1608
        return $this->hashPart($link);
1609
    }
1610
1611
    function encodeEmailAddress($addr)
1612
    {
1613
        #
1614
        #	Input: an email address, e.g. "[email protected]"
1615
        #
1616
        #	Output: the email address as a mailto link, with each character
1617
        #		of the address encoded as either a decimal or hex entity, in
1618
        #		the hopes of foiling most address harvesting spam bots. E.g.:
1619
        #
1620
        #	  <p><a href="&#109;&#x61;&#105;&#x6c;&#116;&#x6f;&#58;&#x66;o&#111;
1621
        #        &#x40;&#101;&#x78;&#97;&#x6d;&#112;&#x6c;&#101;&#46;&#x63;&#111;
1622
        #        &#x6d;">&#x66;o&#111;&#x40;&#101;&#x78;&#97;&#x6d;&#112;&#x6c;
1623
        #        &#101;&#46;&#x63;&#111;&#x6d;</a></p>
1624
        #
1625
        #	Based by a filter by Matthew Wickline, posted to BBEdit-Talk.
1626
        #   With some optimizations by Milian Wolff.
1627
        #
1628
        $addr  = "mailto:" . $addr;
1629
        $chars = preg_split('/(?<!^)(?!$)/', $addr);
1630
        $seed  = (int)abs(crc32($addr) / strlen($addr)); # Deterministic seed.
1631
1632
        foreach ($chars as $key => $char) {
1633
            $ord = ord($char);
1634
            # Ignore non-ascii chars.
1635
            if ($ord < 128) {
1636
                $r = ($seed * (1 + $key)) % 100; # Pseudo-random function.
1637
                # roughly 10% raw, 45% hex, 45% dec
1638
                # '@' *must* be encoded. I insist.
1639
                if ($r > 90 && $char != '@') /* do nothing */ {
1640
                    ;
1641
                } else if ($r < 45) {
1642
                    $chars[$key] = '&#x' . dechex($ord) . ';';
1643
                } else {
1644
                    $chars[$key] = '&#' . $ord . ';';
1645
                }
1646
            }
1647
        }
1648
1649
        $addr = implode('', $chars);
1650
        $text = implode('', array_slice($chars, 7)); # text without `mailto:`
1651
        $addr = "<a href=\"$addr\">$text</a>";
1652
1653
        return $addr;
1654
    }
1655
1656
    function parseSpan($str)
1657
    {
1658
        #
1659
        # Take the string $str and parse it into tokens, hashing embeded HTML,
1660
        # escaped characters and handling code spans.
1661
        #
1662
        $output = '';
1663
1664
        $span_re = '{
1665
				(
1666
					\\\\' . $this->escape_chars_re . '
1667
				|
1668
					(?<![`\\\\])
1669
					`+						# code span marker
1670
			' . ($this->no_markup ? '' : '
1671
				|
1672
					<!--    .*?     -->		# comment
1673
				|
1674
					<\?.*?\?> | <%.*?%>		# processing instruction
1675
				|
1676
					<[!$]?[-a-zA-Z0-9:_]+	# regular tags
1677
					(?>
1678
						\s
1679
						(?>[^"\'>]+|"[^"]*"|\'[^\']*\')*
1680
					)?
1681
					>
1682
				|
1683
					<[-a-zA-Z0-9:_]+\s*/> # xml-style empty tag
1684
				|
1685
					</[-a-zA-Z0-9:_]+\s*> # closing tag
1686
			') . '
1687
				)
1688
				}xs';
1689
1690
        while (1) {
1691
            #
1692
            # Each loop iteration seach for either the next tag, the next
1693
            # openning code span marker, or the next escaped character.
1694
            # Each token is then passed to handleSpanToken.
1695
            #
1696
            $parts = preg_split($span_re, $str, 2, PREG_SPLIT_DELIM_CAPTURE);
1697
1698
            # Create token from text preceding tag.
1699
            if ($parts[0] != "") {
1700
                $output .= $parts[0];
1701
            }
1702
1703
            # Check if we reach the end.
1704
            if (isset($parts[1])) {
1705
                $output .= $this->handleSpanToken($parts[1], $parts[2]);
1706
                $str = $parts[2];
1707
            } else {
1708
                break;
1709
            }
1710
        }
1711
1712
        return $output;
1713
    }
1714
1715
    function handleSpanToken($token, &$str)
1716
    {
1717
        #
1718
        # Handle $token provided by parseSpan by determining its nature and
1719
        # returning the corresponding value that should replace it.
1720
        #
1721
        switch ($token{0}) {
1722
            case "\\":
1723
                return $this->hashPart("&#" . ord($token{1}) . ";");
1724
            case "`":
1725
                # Search for end marker in remaining text.
1726
                if (preg_match(
1727
                    '/^(.*?[^`])' . preg_quote($token) . '(?!`)(.*)$/sm',
1728
                    $str,
1729
                    $matches
1730
                )) {
1731
                    $str      = $matches[2];
1732
                    $codespan = $this->makeCodeSpan($matches[1]);
1733
                    return $this->hashPart($codespan);
1734
                }
1735
                return $token; // return as text since no ending marker found.
1736
            default:
1737
                return $this->hashPart($token);
1738
        }
1739
    }
1740
1741
    function outdent($text)
1742
    {
1743
        #
1744
        # Remove one level of line-leading tabs or spaces
1745
        #
1746
        return preg_replace('/^(\t|[ ]{1,' . $this->tab_width . '})/m', '', $text);
1747
    }
1748
1749
1750
    # String length function for detab. `_initDetab` will create a function to
1751
    # hanlde UTF-8 if the default function does not exist.
1752
    var $utf8_strlen = 'mb_strlen';
1753
1754
    function detab($text)
1755
    {
1756
        #
1757
        # Replace tabs with the appropriate amount of space.
1758
        #
1759
        # For each line we separate the line in blocks delemited by
1760
        # tab characters. Then we reconstruct every line by adding the
1761
        # appropriate number of space between each blocks.
1762
1763
        $text = preg_replace_callback(
1764
            '/^.*\t.*$/m',
1765
            array(&$this, '_detab_callback'),
1766
            $text
1767
        );
1768
1769
        return $text;
1770
    }
1771
1772
    function _detab_callback($matches)
1773
    {
1774
        $line   = $matches[0];
1775
        $strlen = $this->utf8_strlen; # strlen function for UTF-8.
1776
1777
        # Split in blocks.
1778
        $blocks = explode("\t", $line);
1779
        # Add each blocks to the line.
1780
        $line = $blocks[0];
1781
        unset($blocks[0]); # Do not add first block twice.
1782
        foreach ($blocks as $block) {
1783
            # Calculate amount of space, insert spaces, insert block.
1784
            $amount = $this->tab_width -
1785
                      $strlen($line, 'UTF-8') % $this->tab_width;
1786
            $line .= str_repeat(" ", $amount) . $block;
1787
        }
1788
        return $line;
1789
    }
1790
1791
    function _initDetab()
1792
    {
1793
        #
1794
        # Check for the availability of the function in the `utf8_strlen` property
1795
        # (initially `mb_strlen`). If the function is not available, create a
1796
        # function that will loosely count the number of UTF-8 characters with a
1797
        # regular expression.
1798
        #
1799
        if (function_exists($this->utf8_strlen)) {
1800
            return;
1801
        }
1802
        $this->utf8_strlen = create_function(
1803
            '$text',
1804
            'return preg_match_all(
1805
			"/[\\\\x00-\\\\xBF]|[\\\\xC0-\\\\xFF][\\\\x80-\\\\xBF]*/",
1806
			$text, $m);'
1807
        );
1808
    }
1809
1810
    function unhash($text)
1811
    {
1812
        #
1813
        # Swap back in all the tags hashed by _HashHTMLBlocks.
1814
        #
1815
        return preg_replace_callback(
1816
            '/(.)\x1A[0-9]+\1/',
1817
            array(&$this, '_unhash_callback'),
1818
            $text
1819
        );
1820
    }
1821
1822
    function _unhash_callback($matches)
1823
    {
1824
        return $this->html_hashes[$matches[0]];
1825
    }
1826
}
1827
1828
#
1829
# Markdown Extra Parser Class
1830
#
1831
1832
class MarkdownExtra_Parser extends Markdown_Parser
1833
{
1834
1835
    ### Configuration Variables ###
1836
1837
    # Prefix for footnote ids.
1838
    var $fn_id_prefix = "";
1839
1840
    # Optional title attribute for footnote links and backlinks.
1841
    var $fn_link_title = MARKDOWN_FN_LINK_TITLE;
1842
1843
    var $fn_backlink_title = MARKDOWN_FN_BACKLINK_TITLE;
1844
1845
    # Optional class attribute for footnote links and backlinks.
1846
    var $fn_link_class = MARKDOWN_FN_LINK_CLASS;
1847
1848
    var $fn_backlink_class = MARKDOWN_FN_BACKLINK_CLASS;
1849
1850
    # Optional class prefix for fenced code block.
1851
    var $code_class_prefix = MARKDOWN_CODE_CLASS_PREFIX;
1852
1853
    # Class attribute for code blocks goes on the `code` tag;
1854
    # setting this to true will put attributes on the `pre` tag instead.
1855
    var $code_attr_on_pre = MARKDOWN_CODE_ATTR_ON_PRE;
1856
1857
    # Predefined abbreviations.
1858
    var $predef_abbr = array();
1859
1860
    ### Parser Implementation ###
1861
1862
    function __construct()
1863
    {
1864
        #
1865
        # Constructor function. Initialize the parser object.
1866
        #
1867
        # Add extra escapable characters before parent constructor
1868
        # initialize the table.
1869
        $this->escape_chars .= ':|';
1870
1871
        # Insert extra document, block, and span transformations.
1872
        # Parent constructor will do the sorting.
1873
        $this->document_gamut += array(
1874
            "doFencedCodeBlocks" => 5,
1875
            "stripFootnotes"     => 15,
1876
            "stripAbbreviations" => 25,
1877
            "appendFootnotes"    => 50,
1878
        );
1879
        $this->block_gamut += array(
1880
            "doFencedCodeBlocks" => 5,
1881
            "doTables"           => 15,
1882
            "doDefLists"         => 45,
1883
        );
1884
        $this->span_gamut += array(
1885
            "doFootnotes"     => 5,
1886
            "doAbbreviations" => 70,
1887
        );
1888
1889
        parent::__construct();
1890
    }
1891
1892
    # Extra variables used during extra transformations.
1893
    var $footnotes = array();
1894
1895
    var $footnotes_ordered = array();
1896
1897
    var $footnotes_ref_count = array();
1898
1899
    var $footnotes_numbers = array();
1900
1901
    var $abbr_desciptions = array();
1902
1903
    var $abbr_word_re = '';
1904
1905
    # Give the current footnote number.
1906
    var $footnote_counter = 1;
1907
1908
    function setup()
1909
    {
1910
        #
1911
        # Setting up Extra-specific variables.
1912
        #
1913
        parent::setup();
1914
1915
        $this->footnotes           = array();
1916
        $this->footnotes_ordered   = array();
1917
        $this->footnotes_ref_count = array();
1918
        $this->footnotes_numbers   = array();
1919
        $this->abbr_desciptions    = array();
1920
        $this->abbr_word_re        = '';
1921
        $this->footnote_counter    = 1;
1922
1923
        foreach ($this->predef_abbr as $abbr_word => $abbr_desc) {
1924
            if ($this->abbr_word_re) {
1925
                $this->abbr_word_re .= '|';
1926
            }
1927
            $this->abbr_word_re .= preg_quote($abbr_word);
1928
            $this->abbr_desciptions[$abbr_word] = trim($abbr_desc);
1929
        }
1930
    }
1931
1932
    function teardown()
1933
    {
1934
        #
1935
        # Clearing Extra-specific variables.
1936
        #
1937
        $this->footnotes           = array();
1938
        $this->footnotes_ordered   = array();
1939
        $this->footnotes_ref_count = array();
1940
        $this->footnotes_numbers   = array();
1941
        $this->abbr_desciptions    = array();
1942
        $this->abbr_word_re        = '';
1943
1944
        parent::teardown();
1945
    }
1946
1947
1948
    ### Extra Attribute Parser ###
1949
1950
    # Expression to use to catch attributes (includes the braces)
1951
    var $id_class_attr_catch_re = '\{((?:[ ]*[#.][-_:a-zA-Z0-9]+){1,})[ ]*\}';
1952
1953
    # Expression to use when parsing in a context when no capture is desired
1954
    var $id_class_attr_nocatch_re = '\{(?:[ ]*[#.][-_:a-zA-Z0-9]+){1,}[ ]*\}';
1955
1956
    function doExtraAttributes($tag_name, $attr)
1957
    {
1958
        #
1959
        # Parse attributes caught by the $this->id_class_attr_catch_re expression
1960
        # and return the HTML-formatted list of attributes.
1961
        #
1962
        # Currently supported attributes are .class and #id.
1963
        #
1964
        if (empty($attr)) {
1965
            return "";
1966
        }
1967
1968
        # Split on components
1969
        preg_match_all('/[#.][-_:a-zA-Z0-9]+/', $attr, $matches);
1970
        $elements = $matches[0];
1971
1972
        # handle classes and ids (only first id taken into account)
1973
        $classes = array();
1974
        $id      = false;
1975
        foreach ($elements as $element) {
1976
            if ($element{0} == '.') {
1977
                $classes[] = substr($element, 1);
1978
            } else if ($element{0} == '#') {
1979
                if ($id === false) {
1980
                    $id = substr($element, 1);
1981
                }
1982
            }
1983
        }
1984
1985
        # compose attributes as string
1986
        $attr_str = "";
1987
        if (!empty($id)) {
1988
            $attr_str .= ' id="' . $id . '"';
1989
        }
1990
        if (!empty($classes)) {
1991
            $attr_str .= ' class="' . implode(" ", $classes) . '"';
1992
        }
1993
        return $attr_str;
1994
    }
1995
1996 View Code Duplication
    function stripLinkDefinitions($text)
1997
    {
1998
        #
1999
        # Strips link definitions from text, stores the URLs and titles in
2000
        # hash references.
2001
        #
2002
        $less_than_tab = $this->tab_width - 1;
2003
2004
        # Link defs are in the form: ^[id]: url "optional title"
2005
        $text = preg_replace_callback(
2006
            '{
2007
							^[ ]{0,' . $less_than_tab . '}\[(.+)\][ ]?:	# id = $1
2008
							  [ ]*
2009
							  \n?				# maybe *one* newline
2010
							  [ ]*
2011
							(?:
2012
							  <(.+?)>			# url = $2
2013
							|
2014
							  (\S+?)			# url = $3
2015
							)
2016
							  [ ]*
2017
							  \n?				# maybe one newline
2018
							  [ ]*
2019
							(?:
2020
								(?<=\s)			# lookbehind for whitespace
2021
								["(]
2022
								(.*?)			# title = $4
2023
								[")]
2024
								[ ]*
2025
							)?	# title is optional
2026
					(?:[ ]* ' . $this->id_class_attr_catch_re . ' )?  # $5 = extra id & class attr
2027
							(?:\n+|\Z)
2028
			}xm',
2029
            array(&$this, '_stripLinkDefinitions_callback'),
2030
            $text
2031
        );
2032
        return $text;
2033
    }
2034
2035
    function _stripLinkDefinitions_callback($matches)
2036
    {
2037
        $link_id                  = strtolower($matches[1]);
2038
        $url                      = $matches[2] == '' ? $matches[3] : $matches[2];
2039
        $this->urls[$link_id]     = $url;
2040
        $this->titles[$link_id]   =& $matches[4];
2041
        $this->ref_attr[$link_id] = $this->doExtraAttributes("", $dummy =& $matches[5]);
2042
        return ''; # String that will replace the block
2043
    }
2044
2045
2046
    ### HTML Block Parser ###
2047
2048
    # Tags that are always treated as block tags:
2049
    var $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';
2050
2051
    # Tags treated as block tags only if the opening tag is alone on its line:
2052
    var $context_block_tags_re = 'script|noscript|ins|del|iframe|object|source|track|param|math|svg|canvas|audio|video';
2053
2054
    # Tags where markdown="1" default to span mode:
2055
    var $contain_span_tags_re = 'p|h[1-6]|li|dd|dt|td|th|legend|address';
2056
2057
    # Tags which must not have their contents modified, no matter where
2058
    # they appear:
2059
    var $clean_tags_re = 'script|math|svg';
2060
2061
    # Tags that do not need to be closed.
2062
    var $auto_close_tags_re = 'hr|img|param|source|track';
2063
2064
    function hashHTMLBlocks($text)
2065
    {
2066
        #
2067
        # Hashify HTML Blocks and "clean tags".
2068
        #
2069
        # We only want to do this for block-level HTML tags, such as headers,
2070
        # lists, and tables. That's because we still want to wrap <p>s around
2071
        # "paragraphs" that are wrapped in non-block-level tags, such as anchors,
2072
        # phrase emphasis, and spans. The list of tags we're looking for is
2073
        # hard-coded.
2074
        #
2075
        # This works by calling _HashHTMLBlocks_InMarkdown, which then calls
2076
        # _HashHTMLBlocks_InHTML when it encounter block tags. When the markdown="1"
2077
        # attribute is found within a tag, _HashHTMLBlocks_InHTML calls back
2078
        #  _HashHTMLBlocks_InMarkdown to handle the Markdown syntax within the tag.
2079
        # These two functions are calling each other. It's recursive!
2080
        #
2081
        if ($this->no_markup) {
2082
            return $text;
2083
        }
2084
2085
        #
2086
        # Call the HTML-in-Markdown hasher.
2087
        #
2088
        list($text,) = $this->_hashHTMLBlocks_inMarkdown($text);
2089
2090
        return $text;
2091
    }
2092
2093
    function _hashHTMLBlocks_inMarkdown(
2094
        $text,
2095
        $indent = 0,
2096
        $enclosing_tag_re = '',
2097
        $span = false
2098
    ) {
2099
        #
2100
        # Parse markdown text, calling _HashHTMLBlocks_InHTML for block tags.
2101
        #
2102
        # *   $indent is the number of space to be ignored when checking for code
2103
        #     blocks. This is important because if we don't take the indent into
2104
        #     account, something like this (which looks right) won't work as expected:
2105
        #
2106
        #     <div>
2107
        #         <div markdown="1">
2108
        #         Hello World.  <-- Is this a Markdown code block or text?
2109
        #         </div>  <-- Is this a Markdown code block or a real tag?
2110
        #     <div>
2111
        #
2112
        #     If you don't like this, just don't indent the tag on which
2113
        #     you apply the markdown="1" attribute.
2114
        #
2115
        # *   If $enclosing_tag_re is not empty, stops at the first unmatched closing
2116
        #     tag with that name. Nested tags supported.
2117
        #
2118
        # *   If $span is true, text inside must treated as span. So any double
2119
        #     newline will be replaced by a single newline so that it does not create
2120
        #     paragraphs.
2121
        #
2122
        # Returns an array of that form: ( processed text , remaining text )
2123
        #
2124
        if ($text === '') {
2125
            return array('', '');
2126
        }
2127
2128
        # Regex to check for the presense of newlines around a block tag.
2129
        $newline_before_re = '/(?:^\n?|\n\n)*$/';
2130
        $newline_after_re  =
2131
            '{
2132
				^						# Start of text following the tag.
2133
				(?>[ ]*<!--.*?-->)?		# Optional comment.
2134
				[ ]*\n					# Must be followed by newline.
2135
			}xs';
2136
2137
        # Regex to match any tag.
2138
        $block_tag_re =
2139
            '{
2140
				(					# $2: Capture whole tag.
2141
					</?					# Any opening or closing tag.
2142
						(?>				# Tag name.
2143
							' . $this->block_tags_re . '			|
2144
							' . $this->context_block_tags_re . '	|
2145
							' . $this->clean_tags_re . '        	|
2146
							(?!\s)' . $enclosing_tag_re . '
2147
						)
2148
						(?:
2149
							(?=[\s"\'/a-zA-Z0-9])	# Allowed characters after tag name.
2150
							(?>
2151
								".*?"		|	# Double quotes (can contain `>`)
2152
								\'.*?\'   	|	# Single quotes (can contain `>`)
2153
								.+?				# Anything but quotes and `>`.
2154
							)*?
2155
						)?
2156
					>					# End of tag.
2157
				|
2158
					<!--    .*?     -->	# HTML Comment
2159
				|
2160
					<\?.*?\?> | <%.*?%>	# Processing instruction
2161
				|
2162
					<!\[CDATA\[.*?\]\]>	# CData Block
2163
				|
2164
					# Code span marker
2165
					`+
2166
				' . (!$span ? ' # If not in span.
2167
				|
2168
					# Indented code block
2169
					(?: ^[ ]*\n | ^ | \n[ ]*\n )
2170
					[ ]{' . ($indent + 4) . '}[^\n]* \n
2171
					(?>
2172
						(?: [ ]{' . ($indent + 4) . '}[^\n]* | [ ]* ) \n
2173
					)*
2174
				|
2175
					# Fenced code block marker
2176
					(?<= ^ | \n )
2177
					[ ]{0,' . ($indent + 3) . '}~{3,}
2178
									[ ]*
2179
					(?:
2180
					\.?[-_:a-zA-Z0-9]+ # standalone class name
2181
					|
2182
						' . $this->id_class_attr_nocatch_re . ' # extra attributes
2183
					)?
2184
					[ ]*
2185
					\n
2186
				' : '') . ' # End (if not is span).
2187
				)
2188
			}xs';
2189
2190
        $depth  = 0; # Current depth inside the tag tree.
2191
        $parsed = ""; # Parsed text that will be returned.
2192
2193
        #
2194
        # Loop through every tag until we find the closing tag of the parent
2195
        # or loop until reaching the end of text if no parent tag specified.
2196
        #
2197
        do {
2198
            #
2199
            # Split the text using the first $tag_match pattern found.
2200
            # Text before  pattern will be first in the array, text after
2201
            # pattern will be at the end, and between will be any catches made
2202
            # by the pattern.
2203
            #
2204
            $parts = preg_split(
2205
                $block_tag_re,
2206
                $text,
2207
                2,
2208
                PREG_SPLIT_DELIM_CAPTURE
2209
            );
2210
2211
            # If in Markdown span mode, add a empty-string span-level hash
2212
            # after each newline to prevent triggering any block element.
2213
            if ($span) {
2214
                $void     = $this->hashPart("", ':');
2215
                $newline  = "$void\n";
2216
                $parts[0] = $void . str_replace("\n", $newline, $parts[0]) . $void;
2217
            }
2218
2219
            $parsed .= $parts[0]; # Text before current tag.
2220
2221
            # If end of $text has been reached. Stop loop.
2222
            if (count($parts) < 3) {
2223
                $text = "";
2224
                break;
2225
            }
2226
2227
            $tag    = $parts[1]; # Tag to handle.
2228
            $text   = $parts[2]; # Remaining text after current tag.
2229
            $tag_re = preg_quote($tag); # For use in a regular expression.
2230
2231
            #
2232
            # Check for: Code span marker
2233
            #
2234
            if ($tag{0} == "`") {
2235
                # Find corresponding end marker.
2236
                $tag_re = preg_quote($tag);
2237 View Code Duplication
                if (preg_match(
2238
                    '{^(?>.+?|\n(?!\n))*?(?<!`)' . $tag_re . '(?!`)}',
2239
                    $text,
2240
                    $matches
2241
                )) {
2242
                    # End marker found: pass text unchanged until marker.
2243
                    $parsed .= $tag . $matches[0];
2244
                    $text = substr($text, strlen($matches[0]));
2245
                } else {
2246
                    # Unmatched marker: just skip it.
2247
                    $parsed .= $tag;
2248
                }
2249
            }
2250
            #
2251
            # Check for: Fenced code block marker.
2252
            #
2253
            else if (preg_match('{^\n?([ ]{0,' . ($indent + 3) . '})(~+)}', $tag, $capture)) {
2254
                # Fenced code block marker: find matching end marker.
2255
                $fence_indent = strlen($capture[1]); # use captured indent in re
2256
                $fence_re     = $capture[2]; # use captured fence in re
2257 View Code Duplication
                if (preg_match(
2258
                    '{^(?>.*\n)*?[ ]{' . ($fence_indent) . '}' . $fence_re . '[ ]*(?:\n|$)}',
2259
                    $text,
2260
                    $matches
2261
                )) {
2262
                    # End marker found: pass text unchanged until marker.
2263
                    $parsed .= $tag . $matches[0];
2264
                    $text = substr($text, strlen($matches[0]));
2265
                } else {
2266
                    # No end marker: just skip it.
2267
                    $parsed .= $tag;
2268
                }
2269
            }
2270
            #
2271
            # Check for: Indented code block.
2272
            #
2273
            else if ($tag{0} == "\n" || $tag{0} == " ") {
2274
                # Indented code block: pass it unchanged, will be handled
2275
                # later.
2276
                $parsed .= $tag;
2277
            }
2278
            #
2279
            # Check for: Opening Block level tag or
2280
            #            Opening Context Block tag (like ins and del)
2281
            #               used as a block tag (tag is alone on it's line).
2282
            #
2283
            else if (preg_match('{^<(?:' . $this->block_tags_re . ')\b}', $tag) ||
2284
                     (preg_match('{^<(?:' . $this->context_block_tags_re . ')\b}', $tag) &&
2285
                      preg_match($newline_before_re, $parsed) &&
2286
                      preg_match($newline_after_re, $text))
2287
            ) {
2288
                # Need to parse tag and following text using the HTML parser.
2289
                list($block_text, $text) =
2290
                    $this->_hashHTMLBlocks_inHTML($tag . $text, "hashBlock", true);
2291
2292
                # Make sure it stays outside of any paragraph by adding newlines.
2293
                $parsed .= "\n\n$block_text\n\n";
2294
            }
2295
            #
2296
            # Check for: Clean tag (like script, math)
2297
            #            HTML Comments, processing instructions.
2298
            #
2299
            else if (preg_match('{^<(?:' . $this->clean_tags_re . ')\b}', $tag) ||
2300
                     $tag{1} == '!' || $tag{1} == '?'
2301
            ) {
2302
                # Need to parse tag and following text using the HTML parser.
2303
                # (don't check for markdown attribute)
2304
                list($block_text, $text) =
2305
                    $this->_hashHTMLBlocks_inHTML($tag . $text, "hashClean", false);
2306
2307
                $parsed .= $block_text;
2308
            }
2309
            #
2310
            # Check for: Tag with same name as enclosing tag.
2311
            #
2312
            else if ($enclosing_tag_re !== '' &&
2313
                     # Same name as enclosing tag.
2314
                     preg_match('{^</?(?:' . $enclosing_tag_re . ')\b}', $tag)
2315
            ) {
2316
                #
2317
                # Increase/decrease nested tag count.
2318
                #
2319 View Code Duplication
                if ($tag{1} == '/') {
2320
                    $depth --;
2321
                } else if ($tag{strlen($tag) - 2} != '/') {
2322
                    $depth ++;
2323
                }
2324
2325
                if ($depth < 0) {
2326
                    #
2327
                    # Going out of parent element. Clean up and break so we
2328
                    # return to the calling function.
2329
                    #
2330
                    $text = $tag . $text;
2331
                    break;
2332
                }
2333
2334
                $parsed .= $tag;
2335
            } else {
2336
                $parsed .= $tag;
2337
            }
2338
        } while ($depth >= 0);
2339
2340
        return array($parsed, $text);
2341
    }
2342
2343
    function _hashHTMLBlocks_inHTML($text, $hash_method, $md_attr)
2344
    {
2345
        #
2346
        # Parse HTML, calling _HashHTMLBlocks_InMarkdown for block tags.
2347
        #
2348
        # *   Calls $hash_method to convert any blocks.
2349
        # *   Stops when the first opening tag closes.
2350
        # *   $md_attr indicate if the use of the `markdown="1"` attribute is allowed.
2351
        #     (it is not inside clean tags)
2352
        #
2353
        # Returns an array of that form: ( processed text , remaining text )
2354
        #
2355
        if ($text === '') {
2356
            return array('', '');
2357
        }
2358
2359
        # Regex to match `markdown` attribute inside of a tag.
2360
        $markdown_attr_re = '
2361
			{
2362
				\s*			# Eat whitespace before the `markdown` attribute
2363
				markdown
2364
				\s*=\s*
2365
				(?>
2366
					(["\'])		# $1: quote delimiter
2367
					(.*?)		# $2: attribute value
2368
					\1			# matching delimiter
2369
				|
2370
					([^\s>]*)	# $3: unquoted attribute value
2371
				)
2372
				()				# $4: make $3 always defined (avoid warnings)
2373
			}xs';
2374
2375
        # Regex to match any tag.
2376
        $tag_re = '{
2377
				(					# $2: Capture whole tag.
2378
					</?					# Any opening or closing tag.
2379
						[\w:$]+			# Tag name.
2380
						(?:
2381
							(?=[\s"\'/a-zA-Z0-9])	# Allowed characters after tag name.
2382
							(?>
2383
								".*?"		|	# Double quotes (can contain `>`)
2384
								\'.*?\'   	|	# Single quotes (can contain `>`)
2385
								.+?				# Anything but quotes and `>`.
2386
							)*?
2387
						)?
2388
					>					# End of tag.
2389
				|
2390
					<!--    .*?     -->	# HTML Comment
2391
				|
2392
					<\?.*?\?> | <%.*?%>	# Processing instruction
2393
				|
2394
					<!\[CDATA\[.*?\]\]>	# CData Block
2395
				)
2396
			}xs';
2397
2398
        $original_text = $text; # Save original text in case of faliure.
2399
2400
        $depth      = 0; # Current depth inside the tag tree.
2401
        $block_text = ""; # Temporary text holder for current text.
2402
        $parsed     = ""; # Parsed text that will be returned.
2403
2404
        #
2405
        # Get the name of the starting tag.
2406
        # (This pattern makes $base_tag_name_re safe without quoting.)
2407
        #
2408
        if (preg_match('/^<([\w:$]*)\b/', $text, $matches)) {
2409
            $base_tag_name_re = $matches[1];
2410
        }
2411
2412
        #
2413
        # Loop through every tag until we find the corresponding closing tag.
2414
        #
2415
        do {
2416
            #
2417
            # Split the text using the first $tag_match pattern found.
2418
            # Text before  pattern will be first in the array, text after
2419
            # pattern will be at the end, and between will be any catches made
2420
            # by the pattern.
2421
            #
2422
            $parts = preg_split($tag_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE);
2423
2424
            if (count($parts) < 3) {
2425
                #
2426
                # End of $text reached with unbalenced tag(s).
2427
                # In that case, we return original text unchanged and pass the
2428
                # first character as filtered to prevent an infinite loop in the
2429
                # parent function.
2430
                #
2431
                return array($original_text{0}, substr($original_text, 1));
2432
            }
2433
2434
            $block_text .= $parts[0]; # Text before current tag.
2435
            $tag  = $parts[1]; # Tag to handle.
2436
            $text = $parts[2]; # Remaining text after current tag.
2437
2438
            #
2439
            # Check for: Auto-close tag (like <hr/>)
2440
            #			 Comments and Processing Instructions.
2441
            #
2442
            if (preg_match('{^</?(?:' . $this->auto_close_tags_re . ')\b}', $tag) ||
2443
                $tag{1} == '!' || $tag{1} == '?'
2444
            ) {
2445
                # Just add the tag to the block as if it was text.
2446
                $block_text .= $tag;
2447
            } else {
2448
                #
2449
                # Increase/decrease nested tag count. Only do so if
2450
                # the tag's name match base tag's.
2451
                #
2452
                if (preg_match('{^</?' . $base_tag_name_re . '\b}', $tag)) {
2453 View Code Duplication
                    if ($tag{1} == '/') {
2454
                        $depth --;
2455
                    } else if ($tag{strlen($tag) - 2} != '/') {
2456
                        $depth ++;
2457
                    }
2458
                }
2459
2460
                #
2461
                # Check for `markdown="1"` attribute and handle it.
2462
                #
2463
                if ($md_attr &&
2464
                    preg_match($markdown_attr_re, $tag, $attr_m) &&
2465
                    preg_match('/^1|block|span$/', $attr_m[2] . $attr_m[3])
2466
                ) {
2467
                    # Remove `markdown` attribute from opening tag.
2468
                    $tag = preg_replace($markdown_attr_re, '', $tag);
2469
2470
                    # Check if text inside this tag must be parsed in span mode.
2471
                    $this->mode = $attr_m[2] . $attr_m[3];
2472
                    $span_mode  = $this->mode == 'span' || $this->mode != 'block' &&
2473
                                                           preg_match(
2474
                                                               '{^<(?:' . $this->contain_span_tags_re . ')\b}',
2475
                                                               $tag
2476
                                                           );
2477
2478
                    # Calculate indent before tag.
2479
                    if (preg_match('/(?:^|\n)( *?)(?! ).*?$/', $block_text, $matches)) {
2480
                        $strlen = $this->utf8_strlen;
2481
                        $indent = $strlen($matches[1], 'UTF-8');
2482
                    } else {
2483
                        $indent = 0;
2484
                    }
2485
2486
                    # End preceding block with this tag.
2487
                    $block_text .= $tag;
2488
                    $parsed .= $this->$hash_method($block_text);
2489
2490
                    # Get enclosing tag name for the ParseMarkdown function.
2491
                    # (This pattern makes $tag_name_re safe without quoting.)
2492
                    preg_match('/^<([\w:$]*)\b/', $tag, $matches);
2493
                    $tag_name_re = $matches[1];
2494
2495
                    # Parse the content using the HTML-in-Markdown parser.
2496
                    list ($block_text, $text)
2497
                        = $this->_hashHTMLBlocks_inMarkdown(
2498
                        $text,
2499
                        $indent,
2500
                        $tag_name_re,
2501
                        $span_mode
2502
                    );
2503
2504
                    # Outdent markdown text.
2505
                    if ($indent > 0) {
2506
                        $block_text = preg_replace(
2507
                            "/^[ ]{1,$indent}/m",
2508
                            "",
2509
                            $block_text
2510
                        );
2511
                    }
2512
2513
                    # Append tag content to parsed text.
2514
                    if (!$span_mode) {
2515
                        $parsed .= "\n\n$block_text\n\n";
2516
                    } else {
2517
                        $parsed .= "$block_text";
2518
                    }
2519
2520
                    # Start over with a new block.
2521
                    $block_text = "";
2522
                } else {
2523
                    $block_text .= $tag;
2524
                }
2525
            }
2526
        } while ($depth > 0);
2527
2528
        #
2529
        # Hash last block text that wasn't processed inside the loop.
2530
        #
2531
        $parsed .= $this->$hash_method($block_text);
2532
2533
        return array($parsed, $text);
2534
    }
2535
2536
    function hashClean($text)
2537
    {
2538
        #
2539
        # Called whenever a tag must be hashed when a function inserts a "clean" tag
2540
        # in $text, it passes through this function and is automaticaly escaped,
2541
        # blocking invalid nested overlap.
2542
        #
2543
        return $this->hashPart($text, 'C');
2544
    }
2545
2546 View Code Duplication
    function doAnchors($text)
2547
    {
2548
        #
2549
        # Turn Markdown link shortcuts into XHTML <a> tags.
2550
        #
2551
        if ($this->in_anchor) {
2552
            return $text;
2553
        }
2554
        $this->in_anchor = true;
2555
2556
        #
2557
        # First, handle reference-style links: [link text] [id]
2558
        #
2559
        $text = preg_replace_callback(
2560
            '{
2561
			(					# wrap whole match in $1
2562
			  \[
2563
				(' . $this->nested_brackets_re . ')	# link text = $2
2564
			  \]
2565
2566
			  [ ]?				# one optional space
2567
			  (?:\n[ ]*)?		# one optional newline followed by spaces
2568
2569
			  \[
2570
				(.*?)		# id = $3
2571
			  \]
2572
			)
2573
			}xs',
2574
            array(&$this, '_doAnchors_reference_callback'),
2575
            $text
2576
        );
2577
2578
        #
2579
        # Next, inline-style links: [link text](url "optional title")
2580
        #
2581
        $text = preg_replace_callback(
2582
            '{
2583
			(				# wrap whole match in $1
2584
			  \[
2585
				(' . $this->nested_brackets_re . ')	# link text = $2
2586
			  \]
2587
			  \(			# literal paren
2588
				[ \n]*
2589
				(?:
2590
					<(.+?)>	# href = $3
2591
				|
2592
					(' . $this->nested_url_parenthesis_re . ')	# href = $4
2593
				)
2594
				[ \n]*
2595
				(			# $5
2596
				  ([\'"])	# quote char = $6
2597
				  (.*?)		# Title = $7
2598
				  \6		# matching quote
2599
				  [ \n]*	# ignore any spaces/tabs between closing quote and )
2600
				)?			# title is optional
2601
			  \)
2602
			  (?:[ ]? ' . $this->id_class_attr_catch_re . ' )?	 # $8 = id/class attributes
2603
			)
2604
			}xs',
2605
            array(&$this, '_doAnchors_inline_callback'),
2606
            $text
2607
        );
2608
2609
        #
2610
        # Last, handle reference-style shortcuts: [link text]
2611
        # These must come last in case you've also got [link text][1]
2612
        # or [link text](/foo)
0 ignored issues
show
Unused Code Comprehensibility introduced by
42% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
2613
        #
2614
        $text = preg_replace_callback(
2615
            '{
2616
			(					# wrap whole match in $1
2617
			  \[
2618
				([^\[\]]+)		# link text = $2; can\'t contain [ or ]
2619
			  \]
2620
			)
2621
			}xs',
2622
            array(&$this, '_doAnchors_reference_callback'),
2623
            $text
2624
        );
2625
2626
        $this->in_anchor = false;
2627
        return $text;
2628
    }
2629
2630
    function _doAnchors_reference_callback($matches)
2631
    {
2632
        $whole_match = $matches[1];
2633
        $link_text   = $matches[2];
2634
        $link_id     =& $matches[3];
2635
2636
        if ($link_id == "") {
2637
            # for shortcut links like [this][] or [this].
0 ignored issues
show
Unused Code Comprehensibility introduced by
39% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
2638
            $link_id = $link_text;
2639
        }
2640
2641
        # lower-case and turn embedded newlines into spaces
2642
        $link_id = strtolower($link_id);
2643
        $link_id = preg_replace('{[ ]?\n}', ' ', $link_id);
2644
2645
        if (isset($this->urls[$link_id])) {
2646
            $url = $this->urls[$link_id];
2647
            $url = $this->encodeAttribute($url);
2648
2649
            $result = "<a href=\"$url\"";
2650 View Code Duplication
            if (isset($this->titles[$link_id])) {
2651
                $title = $this->titles[$link_id];
2652
                $title = $this->encodeAttribute($title);
2653
                $result .= " title=\"$title\"";
2654
            }
2655
            if (isset($this->ref_attr[$link_id])) {
2656
                $result .= $this->ref_attr[$link_id];
2657
            }
2658
2659
            $link_text = $this->runSpanGamut($link_text);
2660
            $result .= ">$link_text</a>";
2661
            $result = $this->hashPart($result);
2662
        } else {
2663
            $result = $whole_match;
2664
        }
2665
        return $result;
2666
    }
2667
2668 View Code Duplication
    function _doAnchors_inline_callback($matches)
2669
    {
2670
        $whole_match = $matches[1];
2671
        $link_text   = $this->runSpanGamut($matches[2]);
2672
        $url         = $matches[3] == '' ? $matches[4] : $matches[3];
2673
        $title       =& $matches[7];
2674
        $attr        = $this->doExtraAttributes("a", $dummy =& $matches[8]);
2675
2676
        $url = $this->encodeAttribute($url);
2677
2678
        $result = "<a href=\"$url\"";
2679
        if (isset($title)) {
2680
            $title = $this->encodeAttribute($title);
2681
            $result .= " title=\"$title\"";
2682
        }
2683
        $result .= $attr;
2684
2685
        $link_text = $this->runSpanGamut($link_text);
2686
        $result .= ">$link_text</a>";
2687
2688
        return $this->hashPart($result);
2689
    }
2690
2691 View Code Duplication
    function doImages($text)
2692
    {
2693
        #
2694
        # Turn Markdown image shortcuts into <img> tags.
2695
        #
2696
        #
2697
        # First, handle reference-style labeled images: ![alt text][id]
2698
        #
2699
        $text = preg_replace_callback(
2700
            '{
2701
			(				# wrap whole match in $1
2702
			  !\[
2703
				(' . $this->nested_brackets_re . ')		# alt text = $2
2704
			  \]
2705
2706
			  [ ]?				# one optional space
2707
			  (?:\n[ ]*)?		# one optional newline followed by spaces
2708
2709
			  \[
2710
				(.*?)		# id = $3
2711
			  \]
2712
2713
			)
2714
			}xs',
2715
            array(&$this, '_doImages_reference_callback'),
2716
            $text
2717
        );
2718
2719
        #
2720
        # Next, handle inline images:  ![alt text](url "optional title")
2721
        # Don't forget: encode * and _
2722
        #
2723
        $text = preg_replace_callback(
2724
            '{
2725
			(				# wrap whole match in $1
2726
			  !\[
2727
				(' . $this->nested_brackets_re . ')		# alt text = $2
2728
			  \]
2729
			  \s?			# One optional whitespace character
2730
			  \(			# literal paren
2731
				[ \n]*
2732
				(?:
2733
					<(\S*)>	# src url = $3
2734
				|
2735
					(' . $this->nested_url_parenthesis_re . ')	# src url = $4
2736
				)
2737
				[ \n]*
2738
				(			# $5
2739
				  ([\'"])	# quote char = $6
2740
				  (.*?)		# title = $7
2741
				  \6		# matching quote
2742
				  [ \n]*
2743
				)?			# title is optional
2744
			  \)
2745
			  (?:[ ]? ' . $this->id_class_attr_catch_re . ' )?	 # $8 = id/class attributes
2746
			)
2747
			}xs',
2748
            array(&$this, '_doImages_inline_callback'),
2749
            $text
2750
        );
2751
2752
        return $text;
2753
    }
2754
2755
    function _doImages_reference_callback($matches)
2756
    {
2757
        $whole_match = $matches[1];
2758
        $alt_text    = $matches[2];
2759
        $link_id     = strtolower($matches[3]);
2760
2761
        if ($link_id == "") {
2762
            $link_id = strtolower($alt_text); # for shortcut links like ![this][].
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
2763
        }
2764
2765
        $alt_text = $this->encodeAttribute($alt_text);
2766
        if (isset($this->urls[$link_id])) {
2767
            $url    = $this->encodeAttribute($this->urls[$link_id]);
2768
            $result = "<img src=\"$url\" alt=\"$alt_text\"";
2769 View Code Duplication
            if (isset($this->titles[$link_id])) {
2770
                $title = $this->titles[$link_id];
2771
                $title = $this->encodeAttribute($title);
2772
                $result .= " title=\"$title\"";
2773
            }
2774
            if (isset($this->ref_attr[$link_id])) {
2775
                $result .= $this->ref_attr[$link_id];
2776
            }
2777
            $result .= $this->empty_element_suffix;
2778
            $result = $this->hashPart($result);
2779
        } else {
2780
            # If there's no such link ID, leave intact:
2781
            $result = $whole_match;
2782
        }
2783
2784
        return $result;
2785
    }
2786
2787 View Code Duplication
    function _doImages_inline_callback($matches)
2788
    {
2789
        $whole_match = $matches[1];
2790
        $alt_text    = $matches[2];
2791
        $url         = $matches[3] == '' ? $matches[4] : $matches[3];
2792
        $title       =& $matches[7];
2793
        $attr        = $this->doExtraAttributes("img", $dummy =& $matches[8]);
2794
2795
        $alt_text = $this->encodeAttribute($alt_text);
2796
        $url      = $this->encodeAttribute($url);
2797
        $result   = "<img src=\"$url\" alt=\"$alt_text\"";
2798
        if (isset($title)) {
2799
            $title = $this->encodeAttribute($title);
2800
            $result .= " title=\"$title\""; # $title already quoted
2801
        }
2802
        $result .= $attr;
2803
        $result .= $this->empty_element_suffix;
2804
2805
        return $this->hashPart($result);
2806
    }
2807
2808
    function doHeaders($text)
2809
    {
2810
        #
2811
        # Redefined to add id and class attribute support.
2812
        #
2813
        # Setext-style headers:
2814
        #	  Header 1  {#header1}
2815
        #	  ========
2816
        #
2817
        #	  Header 2  {#header2 .class1 .class2}
2818
        #	  --------
2819
        #
2820
        $text = preg_replace_callback(
2821
            '{
2822
				(^.+?)								# $1: Header text
2823
				(?:[ ]+ ' . $this->id_class_attr_catch_re . ' )?	 # $3 = id/class attributes
2824
				[ ]*\n(=+|-+)[ ]*\n+				# $3: Header footer
2825
			}mx',
2826
            array(&$this, '_doHeaders_callback_setext'),
2827
            $text
2828
        );
2829
2830
        # atx-style headers:
2831
        #	# Header 1        {#header1}
2832
        #	## Header 2       {#header2}
2833
        #	## Header 2 with closing hashes ##  {#header3.class1.class2}
2834
        #	...
2835
        #	###### Header 6   {.class2}
2836
        #
2837
        $text = preg_replace_callback(
2838
            '{
2839
				^(\#{1,6})	# $1 = string of #\'s
2840
				[ ]*
2841
				(.+?)		# $2 = Header text
2842
				[ ]*
2843
				\#*			# optional closing #\'s (not counted)
2844
				(?:[ ]+ ' . $this->id_class_attr_catch_re . ' )?	 # $3 = id/class attributes
2845
				[ ]*
2846
				\n+
2847
			}xm',
2848
            array(&$this, '_doHeaders_callback_atx'),
2849
            $text
2850
        );
2851
2852
        return $text;
2853
    }
2854
2855
    function _doHeaders_callback_setext($matches)
2856
    {
2857 View Code Duplication
        if ($matches[3] == '-' && preg_match('{^- }', $matches[1])) {
2858
            return $matches[0];
2859
        }
2860
        $level = $matches[3]{0} == '=' ? 1 : 2;
2861
        $attr  = $this->doExtraAttributes("h$level", $dummy =& $matches[2]);
2862
        $block = "<h$level$attr>" . $this->runSpanGamut($matches[1]) . "</h$level>";
2863
        return "\n" . $this->hashBlock($block) . "\n\n";
2864
    }
2865
2866
    function _doHeaders_callback_atx($matches)
2867
    {
2868
        $level = strlen($matches[1]);
2869
        $attr  = $this->doExtraAttributes("h$level", $dummy =& $matches[3]);
2870
        $block = "<h$level$attr>" . $this->runSpanGamut($matches[2]) . "</h$level>";
2871
        return "\n" . $this->hashBlock($block) . "\n\n";
2872
    }
2873
2874
    function doTables($text)
2875
    {
2876
        #
2877
        # Form HTML tables.
2878
        #
2879
        $less_than_tab = $this->tab_width - 1;
2880
        #
2881
        # Find tables with leading pipe.
2882
        #
2883
        #	| Header 1 | Header 2
2884
        #	| -------- | --------
2885
        #	| Cell 1   | Cell 2
2886
        #	| Cell 3   | Cell 4
2887
        #
2888
        $text = preg_replace_callback(
2889
            '
2890
			{
2891
				^							# Start of a line
2892
				[ ]{0,' . $less_than_tab . '}	# Allowed whitespace.
2893
				[|]							# Optional leading pipe (present)
2894
				(.+) \n						# $1: Header row (at least one pipe)
2895
2896
				[ ]{0,' . $less_than_tab . '}	# Allowed whitespace.
2897
				[|] ([ ]*[-:]+[-| :]*) \n	# $2: Header underline
2898
2899
				(							# $3: Cells
2900
					(?>
2901
						[ ]*				# Allowed whitespace.
2902
						[|] .* \n			# Row content.
2903
					)*
2904
				)
2905
				(?=\n|\Z)					# Stop at final double newline.
2906
			}xm',
2907
            array(&$this, '_doTable_leadingPipe_callback'),
2908
            $text
2909
        );
2910
2911
        #
2912
        # Find tables without leading pipe.
2913
        #
2914
        #	Header 1 | Header 2
2915
        #	-------- | --------
2916
        #	Cell 1   | Cell 2
2917
        #	Cell 3   | Cell 4
2918
        #
2919
        $text = preg_replace_callback(
2920
            '
2921
			{
2922
				^							# Start of a line
2923
				[ ]{0,' . $less_than_tab . '}	# Allowed whitespace.
2924
				(\S.*[|].*) \n				# $1: Header row (at least one pipe)
2925
2926
				[ ]{0,' . $less_than_tab . '}	# Allowed whitespace.
2927
				([-:]+[ ]*[|][-| :]*) \n	# $2: Header underline
2928
2929
				(							# $3: Cells
2930
					(?>
2931
						.* [|] .* \n		# Row content
2932
					)*
2933
				)
2934
				(?=\n|\Z)					# Stop at final double newline.
2935
			}xm',
2936
            array(&$this, '_DoTable_callback'),
2937
            $text
2938
        );
2939
2940
        return $text;
2941
    }
2942
2943
    function _doTable_leadingPipe_callback($matches)
2944
    {
2945
        $head      = $matches[1];
2946
        $underline = $matches[2];
2947
        $content   = $matches[3];
2948
2949
        # Remove leading pipe for each row.
2950
        $content = preg_replace('/^ *[|]/m', '', $content);
2951
2952
        return $this->_doTable_callback(array($matches[0], $head, $underline, $content));
2953
    }
2954
2955
    function _doTable_callback($matches)
2956
    {
2957
        $head      = $matches[1];
2958
        $underline = $matches[2];
2959
        $content   = $matches[3];
2960
2961
        # Remove any tailing pipes for each line.
2962
        $head      = preg_replace('/[|] *$/m', '', $head);
2963
        $underline = preg_replace('/[|] *$/m', '', $underline);
2964
        $content   = preg_replace('/[|] *$/m', '', $content);
2965
2966
        # Reading alignement from header underline.
2967
        $separators = preg_split('/ *[|] */', $underline);
2968
        foreach ($separators as $n => $s) {
2969
            if (preg_match('/^ *-+: *$/', $s)) {
2970
                $attr[$n] = ' align="right"';
2971
            } else if (preg_match('/^ *:-+: *$/', $s)) {
2972
                $attr[$n] = ' align="center"';
2973
            } else if (preg_match('/^ *:-+ *$/', $s)) {
2974
                $attr[$n] = ' align="left"';
2975
            } else {
2976
                $attr[$n] = '';
2977
            }
2978
        }
2979
2980
        # Parsing span elements, including code spans, character escapes,
2981
        # and inline HTML tags, so that pipes inside those gets ignored.
2982
        $head      = $this->parseSpan($head);
2983
        $headers   = preg_split('/ *[|] */', $head);
2984
        $col_count = count($headers);
2985
        $attr      = array_pad($attr, $col_count, '');
2986
2987
        # Write column headers.
2988
        $text = "<table>\n";
2989
        $text .= "<thead>\n";
2990
        $text .= "<tr>\n";
2991
        foreach ($headers as $n => $header) {
2992
            $text .= "  <th$attr[$n]>" . $this->runSpanGamut(trim($header)) . "</th>\n";
2993
        }
2994
        $text .= "</tr>\n";
2995
        $text .= "</thead>\n";
2996
2997
        # Split content by row.
2998
        $rows = explode("\n", trim($content, "\n"));
2999
3000
        $text .= "<tbody>\n";
3001
        foreach ($rows as $row) {
3002
            # Parsing span elements, including code spans, character escapes,
3003
            # and inline HTML tags, so that pipes inside those gets ignored.
3004
            $row = $this->parseSpan($row);
3005
3006
            # Split row by cell.
3007
            $row_cells = preg_split('/ *[|] */', $row, $col_count);
3008
            $row_cells = array_pad($row_cells, $col_count, '');
3009
3010
            $text .= "<tr>\n";
3011
            foreach ($row_cells as $n => $cell) {
3012
                $text .= "  <td$attr[$n]>" . $this->runSpanGamut(trim($cell)) . "</td>\n";
3013
            }
3014
            $text .= "</tr>\n";
3015
        }
3016
        $text .= "</tbody>\n";
3017
        $text .= "</table>";
3018
3019
        return $this->hashBlock($text) . "\n";
3020
    }
3021
3022
    function doDefLists($text)
3023
    {
3024
        #
3025
        # Form HTML definition lists.
3026
        #
3027
        $less_than_tab = $this->tab_width - 1;
3028
3029
        # Re-usable pattern to match any entire dl list:
3030
        $whole_list_re = '(?>
3031
			(								# $1 = whole list
3032
			  (								# $2
3033
				[ ]{0,' . $less_than_tab . '}
3034
				((?>.*\S.*\n)+)				# $3 = defined term
3035
				\n?
3036
				[ ]{0,' . $less_than_tab . '}:[ ]+ # colon starting definition
3037
			  )
3038
			  (?s:.+?)
3039
			  (								# $4
3040
				  \z
3041
				|
3042
				  \n{2,}
3043
				  (?=\S)
3044
				  (?!						# Negative lookahead for another term
3045
					[ ]{0,' . $less_than_tab . '}
3046
					(?: \S.*\n )+?			# defined term
3047
					\n?
3048
					[ ]{0,' . $less_than_tab . '}:[ ]+ # colon starting definition
3049
				  )
3050
				  (?!						# Negative lookahead for another definition
3051
					[ ]{0,' . $less_than_tab . '}:[ ]+ # colon starting definition
3052
				  )
3053
			  )
3054
			)
3055
		)'; // mx
3056
3057
        $text = preg_replace_callback(
3058
            '{
3059
				(?>\A\n?|(?<=\n\n))
3060
				' . $whole_list_re . '
3061
			}mx',
3062
            array(&$this, '_doDefLists_callback'),
3063
            $text
3064
        );
3065
3066
        return $text;
3067
    }
3068
3069
    function _doDefLists_callback($matches)
3070
    {
3071
        # Re-usable patterns to match list item bullets and number markers:
3072
        $list = $matches[1];
3073
3074
        # Turn double returns into triple returns, so that we can make a
3075
        # paragraph for the last item in a list, if necessary:
3076
        $result = trim($this->processDefListItems($list));
3077
        $result = "<dl>\n" . $result . "\n</dl>";
3078
        return $this->hashBlock($result) . "\n\n";
3079
    }
3080
3081
    function processDefListItems($list_str)
3082
    {
3083
        #
3084
        #	Process the contents of a single definition list, splitting it
3085
        #	into individual term and definition list items.
3086
        #
3087
        $less_than_tab = $this->tab_width - 1;
3088
3089
        # trim trailing blank lines:
3090
        $list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str);
3091
3092
        # Process definition terms.
3093
        $list_str = preg_replace_callback(
3094
            '{
3095
			(?>\A\n?|\n\n+)					# leading line
3096
			(								# definition terms = $1
3097
				[ ]{0,' . $less_than_tab . '}	# leading whitespace
3098
				(?!\:[ ]|[ ])				# negative lookahead for a definition
3099
											#   mark (colon) or more whitespace.
3100
				(?> \S.* \n)+?				# actual term (not whitespace).
3101
			)
3102
			(?=\n?[ ]{0,3}:[ ])				# lookahead for following line feed
3103
											#   with a definition mark.
3104
			}xm',
3105
            array(&$this, '_processDefListItems_callback_dt'),
3106
            $list_str
3107
        );
3108
3109
        # Process actual definitions.
3110
        $list_str = preg_replace_callback(
3111
            '{
3112
			\n(\n+)?						# leading line = $1
3113
			(								# marker space = $2
3114
				[ ]{0,' . $less_than_tab . '}	# whitespace before colon
3115
				\:[ ]+						# definition mark (colon)
3116
			)
3117
			((?s:.+?))						# definition text = $3
3118
			(?= \n+ 						# stop at next definition mark,
3119
				(?:							# next term or end of text
3120
					[ ]{0,' . $less_than_tab . '} \:[ ]	|
3121
					<dt> | \z
3122
				)
3123
			)
3124
			}xm',
3125
            array(&$this, '_processDefListItems_callback_dd'),
3126
            $list_str
3127
        );
3128
3129
        return $list_str;
3130
    }
3131
3132
    function _processDefListItems_callback_dt($matches)
3133
    {
3134
        $terms = explode("\n", trim($matches[1]));
3135
        $text  = '';
3136
        foreach ($terms as $term) {
3137
            $term = $this->runSpanGamut(trim($term));
3138
            $text .= "\n<dt>" . $term . "</dt>";
3139
        }
3140
        return $text . "\n";
3141
    }
3142
3143
    function _processDefListItems_callback_dd($matches)
3144
    {
3145
        $leading_line = $matches[1];
3146
        $marker_space = $matches[2];
3147
        $def          = $matches[3];
3148
3149 View Code Duplication
        if ($leading_line || preg_match('/\n{2,}/', $def)) {
3150
            # Replace marker with the appropriate whitespace indentation
3151
            $def = str_repeat(' ', strlen($marker_space)) . $def;
3152
            $def = $this->runBlockGamut($this->outdent($def . "\n\n"));
3153
            $def = "\n" . $def . "\n";
3154
        } else {
3155
            $def = rtrim($def);
3156
            $def = $this->runSpanGamut($this->outdent($def));
3157
        }
3158
3159
        return "\n<dd>" . $def . "</dd>\n";
3160
    }
3161
3162 View Code Duplication
    function doFencedCodeBlocks($text)
3163
    {
3164
        #
3165
        # Adding the fenced code block syntax to regular Markdown:
3166
        #
3167
        # ~~~
3168
        # Code block
3169
        # ~~~
3170
        #
3171
        $less_than_tab = $this->tab_width;
3172
3173
        $text = preg_replace_callback(
3174
            '{
3175
				(?:\n|\A)
3176
				# 1: Opening marker
3177
				(
3178
					~{3,} # Marker: three tilde or more.
3179
				)
3180
				[ ]*
3181
				(?:
3182
					\.?([-_:a-zA-Z0-9]+) # 2: standalone class name
3183
				|
3184
					' . $this->id_class_attr_catch_re . ' # 3: Extra attributes
3185
				)?
3186
				[ ]* \n # Whitespace and newline following marker.
3187
3188
				# 4: Content
3189
				(
3190
					(?>
3191
						(?!\1 [ ]* \n)	# Not a closing marker.
3192
						.*\n+
3193
					)+
3194
				)
3195
3196
				# Closing marker.
3197
				\1 [ ]* \n
3198
			}xm',
3199
            array(&$this, '_doFencedCodeBlocks_callback'),
3200
            $text
3201
        );
3202
3203
        return $text;
3204
    }
3205
3206
    function _doFencedCodeBlocks_callback($matches)
3207
    {
3208
        $classname =& $matches[2];
3209
        $attrs     =& $matches[3];
3210
        $codeblock = $matches[4];
3211
        $codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES);
3212
        $codeblock = preg_replace_callback(
3213
            '/^\n+/',
3214
            array(&$this, '_doFencedCodeBlocks_newlines'),
3215
            $codeblock
3216
        );
3217
3218
        if ($classname != "") {
3219
            if ($classname{0} == '.') {
3220
                $classname = substr($classname, 1);
3221
            }
3222
            $attr_str = ' class="' . $this->code_class_prefix . $classname . '"';
3223
        } else {
3224
            $attr_str = $this->doExtraAttributes($this->code_attr_on_pre ? "pre" : "code", $attrs);
3225
        }
3226
        $pre_attr_str  = $this->code_attr_on_pre ? $attr_str : '';
3227
        $code_attr_str = $this->code_attr_on_pre ? '' : $attr_str;
3228
        $codeblock     = "<pre$pre_attr_str><code$code_attr_str>$codeblock</code></pre>";
3229
3230
        return "\n\n" . $this->hashBlock($codeblock) . "\n\n";
3231
    }
3232
3233
    function _doFencedCodeBlocks_newlines($matches)
3234
    {
3235
        return str_repeat(
3236
            "<br$this->empty_element_suffix",
3237
            strlen($matches[0])
3238
        );
3239
    }
3240
3241
3242
    #
3243
    # Redefining emphasis markers so that emphasis by underscore does not
3244
    # work in the middle of a word.
3245
    #
3246
    var $em_relist = array(
3247
        ''  => '(?:(?<!\*)\*(?!\*)|(?<![a-zA-Z0-9_])_(?!_))(?=\S|$)(?![\.,:;]\s)',
3248
        '*' => '(?<=\S|^)(?<!\*)\*(?!\*)',
3249
        '_' => '(?<=\S|^)(?<!_)_(?![a-zA-Z0-9_])',
3250
    );
3251
3252
    var $strong_relist = array(
3253
        ''   => '(?:(?<!\*)\*\*(?!\*)|(?<![a-zA-Z0-9_])__(?!_))(?=\S|$)(?![\.,:;]\s)',
3254
        '**' => '(?<=\S|^)(?<!\*)\*\*(?!\*)',
3255
        '__' => '(?<=\S|^)(?<!_)__(?![a-zA-Z0-9_])',
3256
    );
3257
3258
    var $em_strong_relist = array(
3259
        ''    => '(?:(?<!\*)\*\*\*(?!\*)|(?<![a-zA-Z0-9_])___(?!_))(?=\S|$)(?![\.,:;]\s)',
3260
        '***' => '(?<=\S|^)(?<!\*)\*\*\*(?!\*)',
3261
        '___' => '(?<=\S|^)(?<!_)___(?![a-zA-Z0-9_])',
3262
    );
3263
3264
    function formParagraphs($text)
3265
    {
3266
        #
3267
        #	Params:
3268
        #		$text - string to process with html <p> tags
3269
        #
3270
        # Strip leading and trailing lines:
3271
        $text = preg_replace('/\A\n+|\n+\z/', '', $text);
3272
3273
        $grafs = preg_split('/\n{2,}/', $text, - 1, PREG_SPLIT_NO_EMPTY);
3274
3275
        #
3276
        # Wrap <p> tags and unhashify HTML blocks
3277
        #
3278
        foreach ($grafs as $key => $value) {
3279
            $value = trim($this->runSpanGamut($value));
3280
3281
            # Check if this should be enclosed in a paragraph.
3282
            # Clean tag hashes & block tag hashes are left alone.
3283
            $is_p = !preg_match('/^B\x1A[0-9]+B|^C\x1A[0-9]+C$/', $value);
3284
3285
            if ($is_p) {
3286
                $value = "<p>$value</p>";
3287
            }
3288
            $grafs[$key] = $value;
3289
        }
3290
3291
        # Join grafs in one text, then unhash HTML tags.
3292
        $text = implode("\n\n", $grafs);
3293
3294
        # Finish by removing any tag hashes still present in $text.
3295
        $text = $this->unhash($text);
3296
3297
        return $text;
3298
    }
3299
3300
    ### Footnotes
3301
3302 View Code Duplication
    function stripFootnotes($text)
3303
    {
3304
        #
3305
        # Strips link definitions from text, stores the URLs and titles in
3306
        # hash references.
3307
        #
3308
        $less_than_tab = $this->tab_width - 1;
3309
3310
        # Link defs are in the form: [^id]: url "optional title"
3311
        $text = preg_replace_callback(
3312
            '{
3313
			^[ ]{0,' . $less_than_tab . '}\[\^(.+?)\][ ]?:	# note_id = $1
3314
			  [ ]*
3315
			  \n?					# maybe *one* newline
3316
			(						# text = $2 (no blank lines allowed)
3317
				(?:
3318
					.+				# actual text
3319
				|
3320
					\n				# newlines but
3321
					(?!\[\^.+?\]:\s)# negative lookahead for footnote marker.
3322
					(?!\n+[ ]{0,3}\S)# ensure line is not blank and followed
3323
									# by non-indented content
3324
				)*
3325
			)
3326
			}xm',
3327
            array(&$this, '_stripFootnotes_callback'),
3328
            $text
3329
        );
3330
        return $text;
3331
    }
3332
3333
    function _stripFootnotes_callback($matches)
3334
    {
3335
        $note_id                   = $this->fn_id_prefix . $matches[1];
3336
        $this->footnotes[$note_id] = $this->outdent($matches[2]);
3337
        return ''; # String that will replace the block
3338
    }
3339
3340
    function doFootnotes($text)
3341
    {
3342
        #
3343
        # Replace footnote references in $text [^id] with a special text-token
3344
        # which will be replaced by the actual footnote marker in appendFootnotes.
3345
        #
3346
        if (!$this->in_anchor) {
3347
            $text = preg_replace('{\[\^(.+?)\]}', "F\x1Afn:\\1\x1A:", $text);
3348
        }
3349
        return $text;
3350
    }
3351
3352
    function appendFootnotes($text)
3353
    {
3354
        #
3355
        # Append footnote list to text.
3356
        #
3357
        $text = preg_replace_callback(
3358
            '{F\x1Afn:(.*?)\x1A:}',
3359
            array(&$this, '_appendFootnotes_callback'),
3360
            $text
3361
        );
3362
3363
        if (!empty($this->footnotes_ordered)) {
3364
            $text .= "\n\n";
3365
            $text .= "<div class=\"footnotes\">\n";
3366
            $text .= "<hr" . $this->empty_element_suffix . "\n";
3367
            $text .= "<ol>\n\n";
3368
3369
            $attr = " rev=\"footnote\"";
3370
            if ($this->fn_backlink_class != "") {
3371
                $class = $this->fn_backlink_class;
3372
                $class = $this->encodeAttribute($class);
3373
                $attr .= " class=\"$class\"";
3374
            }
3375
            if ($this->fn_backlink_title != "") {
3376
                $title = $this->fn_backlink_title;
3377
                $title = $this->encodeAttribute($title);
3378
                $attr .= " title=\"$title\"";
3379
            }
3380
            $num = 0;
3381
3382
            while (!empty($this->footnotes_ordered)) {
3383
                $footnote = reset($this->footnotes_ordered);
3384
                $note_id  = key($this->footnotes_ordered);
3385
                unset($this->footnotes_ordered[$note_id]);
3386
                $ref_count = $this->footnotes_ref_count[$note_id];
3387
                unset($this->footnotes_ref_count[$note_id]);
3388
                unset($this->footnotes[$note_id]);
3389
3390
                $footnote .= "\n"; # Need to append newline before parsing.
3391
                $footnote = $this->runBlockGamut("$footnote\n");
3392
                $footnote = preg_replace_callback(
3393
                    '{F\x1Afn:(.*?)\x1A:}',
3394
                    array(&$this, '_appendFootnotes_callback'),
3395
                    $footnote
3396
                );
3397
3398
                $attr    = str_replace("%%", ++ $num, $attr);
3399
                $note_id = $this->encodeAttribute($note_id);
3400
3401
                # Prepare backlink, multiple backlinks if multiple references
3402
                $backlink = "<a href=\"#fnref:$note_id\"$attr>&#8617;</a>";
3403
                for ($ref_num = 2; $ref_num <= $ref_count; ++ $ref_num) {
3404
                    $backlink .= " <a href=\"#fnref$ref_num:$note_id\"$attr>&#8617;</a>";
3405
                }
3406
                # Add backlink to last paragraph; create new paragraph if needed.
3407
                if (preg_match('{</p>$}', $footnote)) {
3408
                    $footnote = substr($footnote, 0, - 4) . "&#160;$backlink</p>";
3409
                } else {
3410
                    $footnote .= "\n\n<p>$backlink</p>";
3411
                }
3412
3413
                $text .= "<li id=\"fn:$note_id\">\n";
3414
                $text .= $footnote . "\n";
3415
                $text .= "</li>\n\n";
3416
            }
3417
3418
            $text .= "</ol>\n";
3419
            $text .= "</div>";
3420
        }
3421
        return $text;
3422
    }
3423
3424
    function _appendFootnotes_callback($matches)
3425
    {
3426
        $node_id = $this->fn_id_prefix . $matches[1];
3427
3428
        # Create footnote marker only if it has a corresponding footnote *and*
3429
        # the footnote hasn't been used by another marker.
3430
        if (isset($this->footnotes[$node_id])) {
3431
            $num =& $this->footnotes_numbers[$node_id];
3432
            if (!isset($num)) {
3433
                # Transfer footnote content to the ordered list and give it its
3434
                # number
3435
                $this->footnotes_ordered[$node_id]   = $this->footnotes[$node_id];
3436
                $this->footnotes_ref_count[$node_id] = 1;
3437
                $num                                 = $this->footnote_counter ++;
3438
                $ref_count_mark                      = '';
3439
            } else {
3440
                $ref_count_mark = $this->footnotes_ref_count[$node_id] += 1;
3441
            }
3442
3443
            $attr = " rel=\"footnote\"";
3444
            if ($this->fn_link_class != "") {
3445
                $class = $this->fn_link_class;
3446
                $class = $this->encodeAttribute($class);
3447
                $attr .= " class=\"$class\"";
3448
            }
3449
            if ($this->fn_link_title != "") {
3450
                $title = $this->fn_link_title;
3451
                $title = $this->encodeAttribute($title);
3452
                $attr .= " title=\"$title\"";
3453
            }
3454
3455
            $attr    = str_replace("%%", $num, $attr);
3456
            $node_id = $this->encodeAttribute($node_id);
3457
3458
            return
3459
                "<sup id=\"fnref$ref_count_mark:$node_id\">" .
3460
                "<a href=\"#fn:$node_id\"$attr>$num</a>" .
3461
                "</sup>";
3462
        }
3463
3464
        return "[^" . $matches[1] . "]";
3465
    }
3466
3467
    ### Abbreviations ###
3468
3469 View Code Duplication
    function stripAbbreviations($text)
3470
    {
3471
        #
3472
        # Strips abbreviations from text, stores titles in hash references.
3473
        #
3474
        $less_than_tab = $this->tab_width - 1;
3475
3476
        # Link defs are in the form: [id]*: url "optional title"
3477
        $text = preg_replace_callback(
3478
            '{
3479
			^[ ]{0,' . $less_than_tab . '}\*\[(.+?)\][ ]?:	# abbr_id = $1
3480
			(.*)					# text = $2 (no blank lines allowed)
3481
			}xm',
3482
            array(&$this, '_stripAbbreviations_callback'),
3483
            $text
3484
        );
3485
        return $text;
3486
    }
3487
3488
    function _stripAbbreviations_callback($matches)
3489
    {
3490
        $abbr_word = $matches[1];
3491
        $abbr_desc = $matches[2];
3492
        if ($this->abbr_word_re) {
3493
            $this->abbr_word_re .= '|';
3494
        }
3495
        $this->abbr_word_re .= preg_quote($abbr_word);
3496
        $this->abbr_desciptions[$abbr_word] = trim($abbr_desc);
3497
        return ''; # String that will replace the block
3498
    }
3499
3500
    function doAbbreviations($text)
3501
    {
3502
        #
3503
        # Find defined abbreviations in text and wrap them in <abbr> elements.
3504
        #
3505
        if ($this->abbr_word_re) {
3506
            // cannot use the /x modifier because abbr_word_re may
3507
            // contain significant spaces:
3508
            $text = preg_replace_callback(
3509
                '{' .
3510
                '(?<![\w\x1A])' .
3511
                '(?:' . $this->abbr_word_re . ')' .
3512
                '(?![\w\x1A])' .
3513
                '}',
3514
                array(&$this, '_doAbbreviations_callback'),
3515
                $text
3516
            );
3517
        }
3518
        return $text;
3519
    }
3520
3521
    function _doAbbreviations_callback($matches)
3522
    {
3523
        $abbr = $matches[0];
3524
        if (isset($this->abbr_desciptions[$abbr])) {
3525
            $desc = $this->abbr_desciptions[$abbr];
3526
            if (empty($desc)) {
3527
                return $this->hashPart("<abbr>$abbr</abbr>");
3528
            } else {
3529
                $desc = $this->encodeAttribute($desc);
3530
                return $this->hashPart("<abbr title=\"$desc\">$abbr</abbr>");
3531
            }
3532
        } else {
3533
            return $matches[0];
3534
        }
3535
    }
3536
}
3537
3538
/*
3539
3540
PHP Markdown Extra
3541
==================
3542
3543
Description
3544
-----------
3545
3546
This is a PHP port of the original Markdown formatter written in Perl
3547
by John Gruber. This special "Extra" version of PHP Markdown features
3548
further enhancements to the syntax for making additional constructs
3549
such as tables and definition list.
3550
3551
Markdown is a text-to-HTML filter; it translates an easy-to-read /
3552
easy-to-write structured text format into HTML. Markdown's text format
3553
is mostly similar to that of plain text email, and supports features such
3554
as headers, *emphasis*, code blocks, blockquotes, and links.
3555
3556
Markdown's syntax is designed not as a generic markup language, but
3557
specifically to serve as a front-end to (X)HTML. You can use span-level
3558
HTML tags anywhere in a Markdown document, and you can use block level
3559
HTML tags (like <div> and <table> as well).
3560
3561
For more information about Markdown's syntax, see:
3562
3563
<http://daringfireball.net/projects/markdown/>
3564
3565
3566
Bugs
3567
----
3568
3569
To file bug reports please send email to:
3570
3571
<[email protected]>
3572
3573
Please include with your report: (1) the example input; (2) the output you
3574
expected; (3) the output Markdown actually produced.
3575
3576
3577
Version History
3578
---------------
3579
3580
See the readme file for detailed release notes for this version.
3581
3582
3583
Copyright and License
3584
---------------------
3585
3586
PHP Markdown & Extra
3587
Copyright (c) 2004-2013 Michel Fortin
3588
<http://michelf.ca/>
3589
All rights reserved.
3590
3591
Based on Markdown
3592
Copyright (c) 2003-2006 John Gruber
3593
<http://daringfireball.net/>
3594
All rights reserved.
3595
3596
Redistribution and use in source and binary forms, with or without
3597
modification, are permitted provided that the following conditions are
3598
met:
3599
3600
*	Redistributions of source code must retain the above copyright notice,
3601
	this list of conditions and the following disclaimer.
3602
3603
*	Redistributions in binary form must reproduce the above copyright
3604
	notice, this list of conditions and the following disclaimer in the
3605
	documentation and/or other materials provided with the distribution.
3606
3607
*	Neither the name "Markdown" nor the names of its contributors may
3608
	be used to endorse or promote products derived from this software
3609
	without specific prior written permission.
3610
3611
This software is provided by the copyright holders and contributors "as
3612
is" and any express or implied warranties, including, but not limited
3613
to, the implied warranties of merchantability and fitness for a
3614
particular purpose are disclaimed. In no event shall the copyright owner
3615
or contributors be liable for any direct, indirect, incidental, special,
3616
exemplary, or consequential damages (including, but not limited to,
3617
procurement of substitute goods or services; loss of use, data, or
3618
profits; or business interruption) however caused and on any theory of
3619
liability, whether in contract, strict liability, or tort (including
3620
negligence or otherwise) arising in any way out of the use of this
3621
software, even if advised of the possibility of such damage.
3622
3623
*/
3624
?>