Upgrade serendipity_event_markdown for PHP 8.0/8.1 compatibility (#141)

Upgrade bundled markdown lib `michelf/php-markdown` to version 1.9.1 that enables PHP 8 compatibility. Also dump support for EOL PHP versions in serendipity_event_markdown, because the new version of the bundled markdown lib raises the required PHP version to PHP >=7.4.
This commit is contained in:
Gregor Nathanael Meyer 2022-08-21 17:40:27 +02:00 committed by GitHub
parent 9dc7a400d2
commit 76f75e5351
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 345 additions and 236 deletions

View file

@ -1,3 +1,10 @@
Version 1.31:
=============
Changes by spackmat
- Upgrade PHP Markdown Lib to 1.9.1 (was 1.8.0) to enable compatibility with PHP 8.0 and 8.1.
- Raise required PHP version to >=7.4 to reflect the changes in PHP Markdown Lib to 1.9
Version 1.30.1: Version 1.30.1:
============= =============
Changes by surrim Changes by surrim

View file

@ -1,5 +1,5 @@
PHP Markdown Lib PHP Markdown Lib
Copyright (c) 2004-2018 Michel Fortin Copyright (c) 2004-2021 Michel Fortin
<https://michelf.ca/> <https://michelf.ca/>
All rights reserved. All rights reserved.

View file

@ -4,7 +4,7 @@
* *
* @package php-markdown * @package php-markdown
* @author Michel Fortin <michel.fortin@michelf.com> * @author Michel Fortin <michel.fortin@michelf.com>
* @copyright 2004-2018 Michel Fortin <https://michelf.com/projects/php-markdown/> * @copyright 2004-2019 Michel Fortin <https://michelf.com/projects/php-markdown/>
* @copyright (Original Markdown) 2004-2006 John Gruber <https://daringfireball.net/projects/markdown/> * @copyright (Original Markdown) 2004-2006 John Gruber <https://daringfireball.net/projects/markdown/>
*/ */
@ -18,7 +18,7 @@ class Markdown implements MarkdownInterface {
* Define the package version * Define the package version
* @var string * @var string
*/ */
const MARKDOWNLIB_VERSION = "1.8.0"; const MARKDOWNLIB_VERSION = "2.0";
/** /**
* Simple function interface - Initialize the parser and return the result * Simple function interface - Initialize the parser and return the result
@ -31,7 +31,7 @@ class Markdown implements MarkdownInterface {
*/ */
public static function defaultTransform($text) { public static function defaultTransform($text) {
// Take parser class on which this function was called. // Take parser class on which this function was called.
$parser_class = \get_called_class(); $parser_class = static::class;
// Try to take parser from the static parser list // Try to take parser from the static parser list
static $parser_list; static $parser_list;
@ -49,61 +49,56 @@ class Markdown implements MarkdownInterface {
/** /**
* Configuration variables * Configuration variables
*/ */
/** /**
* Change to ">" for HTML output. * Change to ">" for HTML output.
* @var string
*/ */
public $empty_element_suffix = " />"; public string $empty_element_suffix = " />";
/** /**
* The width of indentation of the output markup * The width of indentation of the output markup
* @var int
*/ */
public $tab_width = 4; public int $tab_width = 4;
/** /**
* Change to `true` to disallow markup or entities. * Change to `true` to disallow markup or entities.
* @var boolean
*/ */
public $no_markup = false; public bool $no_markup = false;
public $no_entities = false; public bool $no_entities = false;
/** /**
* Change to `true` to enable line breaks on \n without two trailling spaces * Change to `true` to enable line breaks on \n without two trailling spaces
* @var boolean * @var boolean
*/ */
public $hard_wrap = false; public bool $hard_wrap = false;
/** /**
* Predefined URLs and titles for reference links and images. * Predefined URLs and titles for reference links and images.
* @var array
*/ */
public $predef_urls = array(); public array $predef_urls = array();
public $predef_titles = array(); public array $predef_titles = array();
/** /**
* Optional filter function for URLs * Optional filter function for URLs
* @var callable * @var callable|null
*/ */
public $url_filter_func = null; public $url_filter_func = null;
/** /**
* Optional header id="" generation callback function. * Optional header id="" generation callback function.
* @var callable * @var callable|null
*/ */
public $header_id_func = null; public $header_id_func = null;
/** /**
* Optional function for converting code block content to HTML * Optional function for converting code block content to HTML
* @var callable * @var callable|null
*/ */
public $code_block_content_func = null; public $code_block_content_func = null;
/** /**
* Optional function for converting code span content to HTML. * Optional function for converting code span content to HTML.
* @var callable * @var callable|null
*/ */
public $code_span_content_func = null; public $code_span_content_func = null;
@ -121,32 +116,27 @@ class Markdown implements MarkdownInterface {
* <li>List item two</li> * <li>List item two</li>
* <li>List item three</li> * <li>List item three</li>
* </ol> * </ol>
*
* @var bool
*/ */
public $enhanced_ordered_list = false; public bool $enhanced_ordered_list = false;
/** /**
* Parser implementation * Parser implementation
*/ */
/** /**
* Regex to match balanced [brackets]. * Regex to match balanced [brackets].
* Needed to insert a maximum bracked depth while converting to PHP. * Needed to insert a maximum bracked depth while converting to PHP.
* @var int
*/ */
protected $nested_brackets_depth = 6; protected int $nested_brackets_depth = 6;
protected $nested_brackets_re; protected $nested_brackets_re;
protected $nested_url_parenthesis_depth = 4; protected int $nested_url_parenthesis_depth = 4;
protected $nested_url_parenthesis_re; protected $nested_url_parenthesis_re;
/** /**
* Table of hash values for escaped characters: * Table of hash values for escaped characters:
* @var string
*/ */
protected $escape_chars = '\`*_{}[]()>#+-.!'; protected string $escape_chars = '\`*_{}[]()>#+-.!';
protected $escape_chars_re; protected string $escape_chars_re;
/** /**
* Constructor function. Initialize appropriate member variables. * Constructor function. Initialize appropriate member variables.
@ -175,23 +165,20 @@ class Markdown implements MarkdownInterface {
/** /**
* Internal hashes used during transformation. * Internal hashes used during transformation.
* @var array
*/ */
protected $urls = array(); protected array $urls = array();
protected $titles = array(); protected $titles = array();
protected $html_hashes = array(); protected array $html_hashes = array();
/** /**
* Status flag to avoid invalid nesting. * Status flag to avoid invalid nesting.
* @var boolean
*/ */
protected $in_anchor = false; protected bool $in_anchor = false;
/** /**
* Status flag to avoid invalid nesting. * Status flag to avoid invalid nesting.
* @var boolean
*/ */
protected $in_emphasis_processing = false; protected bool $in_emphasis_processing = false;
/** /**
* Called before the transformation process starts to setup parser states. * Called before the transformation process starts to setup parser states.
@ -263,9 +250,8 @@ class Markdown implements MarkdownInterface {
/** /**
* Define the document gamut * Define the document gamut
* @var array
*/ */
protected $document_gamut = array( protected array $document_gamut = array(
// Strip link definitions, store in hashes. // Strip link definitions, store in hashes.
"stripLinkDefinitions" => 20, "stripLinkDefinitions" => 20,
"runBasicBlockGamut" => 30, "runBasicBlockGamut" => 30,
@ -525,9 +511,8 @@ class Markdown implements MarkdownInterface {
/** /**
* Define the block gamut - these are all the transformations that form * Define the block gamut - these are all the transformations that form
* block-level tags like paragraphs, headers, and list items. * block-level tags like paragraphs, headers, and list items.
* @var array
*/ */
protected $block_gamut = array( protected array $block_gamut = array(
"doHeaders" => 10, "doHeaders" => 10,
"doHorizontalRules" => 20, "doHorizontalRules" => 20,
"doLists" => 40, "doLists" => 40,
@ -597,9 +582,8 @@ class Markdown implements MarkdownInterface {
/** /**
* These are all the transformations that occur *within* block-level * These are all the transformations that occur *within* block-level
* tags like paragraphs, headers, and list items. * tags like paragraphs, headers, and list items.
* @var array
*/ */
protected $span_gamut = array( protected array $span_gamut = array(
// Process character escapes, code spans, and inline HTML // Process character escapes, code spans, and inline HTML
// in one shot. // in one shot.
"parseSpan" => -30, "parseSpan" => -30,
@ -724,7 +708,7 @@ class Markdown implements MarkdownInterface {
/** /**
* Callback method to parse referenced anchors * Callback method to parse referenced anchors
* @param string $matches * @param array $matches
* @return string * @return string
*/ */
protected function _doAnchors_reference_callback($matches) { protected function _doAnchors_reference_callback($matches) {
@ -763,26 +747,25 @@ class Markdown implements MarkdownInterface {
/** /**
* Callback method to parse inline anchors * Callback method to parse inline anchors
* @param string $matches * @param array $matches
* @return string * @return string
*/ */
protected function _doAnchors_inline_callback($matches) { protected function _doAnchors_inline_callback($matches) {
$whole_match = $matches[1];
$link_text = $this->runSpanGamut($matches[2]); $link_text = $this->runSpanGamut($matches[2]);
$url = $matches[3] == '' ? $matches[4] : $matches[3]; $url = $matches[3] === '' ? $matches[4] : $matches[3];
$title =& $matches[7]; $title =& $matches[7];
// If the URL was of the form <s p a c e s> it got caught by the HTML // If the URL was of the form <s p a c e s> it got caught by the HTML
// tag parser and hashed. Need to reverse the process before using // tag parser and hashed. Need to reverse the process before using
// the URL. // the URL.
$unhashed = $this->unhash($url); $unhashed = $this->unhash($url);
if ($unhashed != $url) if ($unhashed !== $url)
$url = preg_replace('/^<(.*)>$/', '\1', $unhashed); $url = preg_replace('/^<(.*)>$/', '\1', $unhashed);
$url = $this->encodeURLAttribute($url); $url = $this->encodeURLAttribute($url);
$result = "<a href=\"$url\""; $result = "<a href=\"$url\"";
if (isset($title)) { if ($title) {
$title = $this->encodeAttribute($title); $title = $this->encodeAttribute($title);
$result .= " title=\"$title\""; $result .= " title=\"$title\"";
} }
@ -952,7 +935,7 @@ class Markdown implements MarkdownInterface {
return $matches[0]; return $matches[0];
} }
$level = $matches[2]{0} == '=' ? 1 : 2; $level = $matches[2][0] == '=' ? 1 : 2;
// ID attribute generation // ID attribute generation
$idAtt = $this->_generateIdFromHeaderValue($matches[1]); $idAtt = $this->_generateIdFromHeaderValue($matches[1]);
@ -1106,9 +1089,8 @@ class Markdown implements MarkdownInterface {
/** /**
* Nesting tracker for list levels * Nesting tracker for list levels
* @var integer
*/ */
protected $list_level = 0; protected int $list_level = 0;
/** /**
* Process the contents of a single ordered or unordered list, splitting it * Process the contents of a single ordered or unordered list, splitting it
@ -1218,7 +1200,7 @@ class Markdown implements MarkdownInterface {
$codeblock = $matches[1]; $codeblock = $matches[1];
$codeblock = $this->outdent($codeblock); $codeblock = $this->outdent($codeblock);
if ($this->code_block_content_func) { if (is_callable($this->code_block_content_func)) {
$codeblock = call_user_func($this->code_block_content_func, $codeblock, ""); $codeblock = call_user_func($this->code_block_content_func, $codeblock, "");
} else { } else {
$codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES); $codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES);
@ -1237,7 +1219,7 @@ class Markdown implements MarkdownInterface {
* @return string * @return string
*/ */
protected function makeCodeSpan($code) { protected function makeCodeSpan($code) {
if ($this->code_span_content_func) { if (is_callable($this->code_span_content_func)) {
$code = call_user_func($this->code_span_content_func, $code); $code = call_user_func($this->code_span_content_func, $code);
} else { } else {
$code = htmlspecialchars(trim($code), ENT_NOQUOTES); $code = htmlspecialchars(trim($code), ENT_NOQUOTES);
@ -1249,7 +1231,7 @@ class Markdown implements MarkdownInterface {
* Define the emphasis operators with their regex matches * Define the emphasis operators with their regex matches
* @var array * @var array
*/ */
protected $em_relist = array( protected array $em_relist = array(
'' => '(?:(?<!\*)\*(?!\*)|(?<!_)_(?!_))(?![\.,:;]?\s)', '' => '(?:(?<!\*)\*(?!\*)|(?<!_)_(?!_))(?![\.,:;]?\s)',
'*' => '(?<![\s*])\*(?!\*)', '*' => '(?<![\s*])\*(?!\*)',
'_' => '(?<![\s_])_(?!_)', '_' => '(?<![\s_])_(?!_)',
@ -1259,7 +1241,7 @@ class Markdown implements MarkdownInterface {
* Define the strong operators with their regex matches * Define the strong operators with their regex matches
* @var array * @var array
*/ */
protected $strong_relist = array( protected array $strong_relist = array(
'' => '(?:(?<!\*)\*\*(?!\*)|(?<!_)__(?!_))(?![\.,:;]?\s)', '' => '(?:(?<!\*)\*\*(?!\*)|(?<!_)__(?!_))(?![\.,:;]?\s)',
'**' => '(?<![\s*])\*\*(?!\*)', '**' => '(?<![\s*])\*\*(?!\*)',
'__' => '(?<![\s_])__(?!_)', '__' => '(?<![\s_])__(?!_)',
@ -1269,7 +1251,7 @@ class Markdown implements MarkdownInterface {
* Define the emphasis + strong operators with their regex matches * Define the emphasis + strong operators with their regex matches
* @var array * @var array
*/ */
protected $em_strong_relist = array( protected array $em_strong_relist = array(
'' => '(?:(?<!\*)\*\*\*(?!\*)|(?<!_)___(?!_))(?![\.,:;]?\s)', '' => '(?:(?<!\*)\*\*\*(?!\*)|(?<!_)___(?!_))(?![\.,:;]?\s)',
'***' => '(?<![\s*])\*\*\*(?!\*)', '***' => '(?<![\s*])\*\*\*(?!\*)',
'___' => '(?<![\s_])___(?!_)', '___' => '(?<![\s_])___(?!_)',
@ -1277,9 +1259,8 @@ class Markdown implements MarkdownInterface {
/** /**
* Container for prepared regular expressions * Container for prepared regular expressions
* @var array
*/ */
protected $em_strong_prepared_relist; protected ?array $em_strong_prepared_relist = null;
/** /**
* Prepare regular expressions for searching emphasis tokens in any * Prepare regular expressions for searching emphasis tokens in any
@ -1358,7 +1339,7 @@ class Markdown implements MarkdownInterface {
} else { } else {
// Other closing marker: close one em or strong and // Other closing marker: close one em or strong and
// change current token state to match the other // change current token state to match the other
$token_stack[0] = str_repeat($token{0}, 3-$token_len); $token_stack[0] = str_repeat($token[0], 3-$token_len);
$tag = $token_len == 2 ? "strong" : "em"; $tag = $token_len == 2 ? "strong" : "em";
$span = $text_stack[0]; $span = $text_stack[0];
$span = $this->runSpanGamut($span); $span = $this->runSpanGamut($span);
@ -1383,7 +1364,7 @@ class Markdown implements MarkdownInterface {
} else { } else {
// Reached opening three-char emphasis marker. Push on token // Reached opening three-char emphasis marker. Push on token
// stack; will be handled by the special condition above. // stack; will be handled by the special condition above.
$em = $token{0}; $em = $token[0];
$strong = "$em$em"; $strong = "$em$em";
array_unshift($token_stack, $token); array_unshift($token_stack, $token);
array_unshift($text_stack, ''); array_unshift($text_stack, '');
@ -1576,11 +1557,11 @@ class Markdown implements MarkdownInterface {
* This function is *not* suitable for attributes enclosed in single quotes. * This function is *not* suitable for attributes enclosed in single quotes.
* *
* @param string $url * @param string $url
* @param string &$text Passed by reference * @param string $text Passed by reference
* @return string URL * @return string URL
*/ */
protected function encodeURLAttribute($url, &$text = null) { protected function encodeURLAttribute($url, &$text = null) {
if ($this->url_filter_func) { if (is_callable($this->url_filter_func)) {
$url = call_user_func($this->url_filter_func, $url); $url = call_user_func($this->url_filter_func, $url);
} }
@ -1694,7 +1675,7 @@ class Markdown implements MarkdownInterface {
* attribute special characters by Allan Odgaard. * attribute special characters by Allan Odgaard.
* *
* @param string $text * @param string $text
* @param string &$tail * @param string $tail Passed by reference
* @param integer $head_length * @param integer $head_length
* @return string * @return string
*/ */
@ -1792,13 +1773,13 @@ class Markdown implements MarkdownInterface {
* Handle $token provided by parseSpan by determining its nature and * Handle $token provided by parseSpan by determining its nature and
* returning the corresponding value that should replace it. * returning the corresponding value that should replace it.
* @param string $token * @param string $token
* @param string &$str * @param string $str Passed by reference
* @return string * @return string
*/ */
protected function handleSpanToken($token, &$str) { protected function handleSpanToken($token, &$str) {
switch ($token{0}) { switch ($token[0]) {
case "\\": case "\\":
return $this->hashPart("&#". ord($token{1}). ";"); return $this->hashPart("&#". ord($token[1]). ";");
case "`": case "`":
// Search for end marker in remaining text. // Search for end marker in remaining text.
if (preg_match('/^(.*?[^`])'.preg_quote($token).'(?!`)(.*)$/sm', if (preg_match('/^(.*?[^`])'.preg_quote($token).'(?!`)(.*)$/sm',
@ -1827,7 +1808,7 @@ class Markdown implements MarkdownInterface {
/** /**
* String length function for detab. `_initDetab` will create a function to * String length function for detab. `_initDetab` will create a function to
* handle UTF-8 if the default function does not exist. * handle UTF-8 if the default function does not exist.
* @var string * can be a string or function
*/ */
protected $utf8_strlen = 'mb_strlen'; protected $utf8_strlen = 'mb_strlen';
@ -1884,9 +1865,7 @@ class Markdown implements MarkdownInterface {
return; return;
} }
$this->utf8_strlen = function($text) { $this->utf8_strlen = fn($text) => preg_match_all('/[\x00-\xBF]|[\xC0-\xFF][\x80-\xBF]*/', $text, $m);
return preg_match_all('/[\x00-\xBF]|[\xC0-\xFF][\x80-\xBF]*/', $text, $m);
};
} }
/** /**

View file

@ -4,7 +4,7 @@
* *
* @package php-markdown * @package php-markdown
* @author Michel Fortin <michel.fortin@michelf.com> * @author Michel Fortin <michel.fortin@michelf.com>
* @copyright 2004-2018 Michel Fortin <https://michelf.com/projects/php-markdown/> * @copyright 2004-2019 Michel Fortin <https://michelf.com/projects/php-markdown/>
* @copyright (Original Markdown) 2004-2006 John Gruber <https://daringfireball.net/projects/markdown/> * @copyright (Original Markdown) 2004-2006 John Gruber <https://daringfireball.net/projects/markdown/>
*/ */
@ -17,67 +17,86 @@ class MarkdownExtra extends \Michelf\Markdown {
/** /**
* Configuration variables * Configuration variables
*/ */
/** /**
* Prefix for footnote ids. * Prefix for footnote ids.
* @var string
*/ */
public $fn_id_prefix = ""; public string $fn_id_prefix = "";
/** /**
* Optional title attribute for footnote links and backlinks. * Optional title attribute for footnote links.
* @var string
*/ */
public $fn_link_title = ""; public string $fn_link_title = "";
public $fn_backlink_title = "";
/** /**
* Optional class attribute for footnote links and backlinks. * Optional class attribute for footnote links and backlinks.
* @var string
*/ */
public $fn_link_class = "footnote-ref"; public string $fn_link_class = "footnote-ref";
public $fn_backlink_class = "footnote-backref"; public string $fn_backlink_class = "footnote-backref";
/** /**
* Content to be displayed within footnote backlinks. The default is '↩'; * Content to be displayed within footnote backlinks. The default is '↩';
* the U+FE0E on the end is a Unicode variant selector used to prevent iOS * the U+FE0E on the end is a Unicode variant selector used to prevent iOS
* from displaying the arrow character as an emoji. * from displaying the arrow character as an emoji.
* @var string * Optionally use '^^' and '%%' to refer to the footnote number and
* reference number respectively. {@see parseFootnotePlaceholders()}
*/ */
public $fn_backlink_html = '&#8617;&#xFE0E;'; public string $fn_backlink_html = '&#8617;&#xFE0E;';
/**
* Optional title and aria-label attributes for footnote backlinks for
* added accessibility (to ensure backlink uniqueness).
* Use '^^' and '%%' to refer to the footnote number and reference number
* respectively. {@see parseFootnotePlaceholders()}
*/
public string $fn_backlink_title = "";
public string $fn_backlink_label = "";
/** /**
* Class name for table cell alignment (%% replaced left/center/right) * Class name for table cell alignment (%% replaced left/center/right)
* For instance: 'go-%%' becomes 'go-left' or 'go-right' or 'go-center' * For instance: 'go-%%' becomes 'go-left' or 'go-right' or 'go-center'
* If empty, the align attribute is used instead of a class name. * If empty, the align attribute is used instead of a class name.
* @var string
*/ */
public $table_align_class_tmpl = ''; public string $table_align_class_tmpl = '';
/** /**
* Optional class prefix for fenced code block. * Optional class prefix for fenced code block.
* @var string
*/ */
public $code_class_prefix = ""; public string $code_class_prefix = "";
/** /**
* Class attribute for code blocks goes on the `code` tag; * Class attribute for code blocks goes on the `code` tag;
* setting this to true will put attributes on the `pre` tag instead. * setting this to true will put attributes on the `pre` tag instead.
* @var boolean
*/ */
public $code_attr_on_pre = false; public bool $code_attr_on_pre = false;
/** /**
* Predefined abbreviations. * Predefined abbreviations.
* @var array
*/ */
public $predef_abbr = array(); public array $predef_abbr = array();
/** /**
* Only convert atx-style headers if there's a space between the header and # * Only convert atx-style headers if there's a space between the header and #
* @var boolean
*/ */
public $hashtag_protection = false; public bool $hashtag_protection = false;
/**
* Determines whether footnotes should be appended to the end of the document.
* If true, footnote html can be retrieved from $this->footnotes_assembled.
*/
public bool $omit_footnotes = false;
/**
* After parsing, the HTML for the list of footnotes appears here.
* This is available only if $omit_footnotes == true.
*
* Note: when placing the content of `footnotes_assembled` on the page,
* consider adding the attribute `role="doc-endnotes"` to the `div` or
* `section` that will enclose the list of footnotes so they are
* reachable to accessibility tools the same way they would be with the
* default HTML output.
*/
public ?string $footnotes_assembled = null;
/** /**
* Parser implementation * Parser implementation
@ -117,21 +136,23 @@ class MarkdownExtra extends \Michelf\Markdown {
/** /**
* Extra variables used during extra transformations. * Extra variables used during extra transformations.
* @var array
*/ */
protected $footnotes = array(); protected array $footnotes = array();
protected $footnotes_ordered = array(); protected array $footnotes_ordered = array();
protected $footnotes_ref_count = array(); protected array $footnotes_ref_count = array();
protected $footnotes_numbers = array(); protected array $footnotes_numbers = array();
protected $abbr_desciptions = array(); protected array $abbr_desciptions = array();
/** @var string */ protected string $abbr_word_re = '';
protected $abbr_word_re = '';
/** /**
* Give the current footnote number. * Give the current footnote number.
* @var integer
*/ */
protected $footnote_counter = 1; protected int $footnote_counter = 1;
/**
* Ref attribute for links
*/
protected array $ref_attr = array();
/** /**
* Setting up Extra-specific variables. * Setting up Extra-specific variables.
@ -146,6 +167,7 @@ class MarkdownExtra extends \Michelf\Markdown {
$this->abbr_desciptions = array(); $this->abbr_desciptions = array();
$this->abbr_word_re = ''; $this->abbr_word_re = '';
$this->footnote_counter = 1; $this->footnote_counter = 1;
$this->footnotes_assembled = null;
foreach ($this->predef_abbr as $abbr_word => $abbr_desc) { foreach ($this->predef_abbr as $abbr_word => $abbr_desc) {
if ($this->abbr_word_re) if ($this->abbr_word_re)
@ -166,6 +188,9 @@ class MarkdownExtra extends \Michelf\Markdown {
$this->abbr_desciptions = array(); $this->abbr_desciptions = array();
$this->abbr_word_re = ''; $this->abbr_word_re = '';
if ( ! $this->omit_footnotes )
$this->footnotes_assembled = null;
parent::teardown(); parent::teardown();
} }
@ -173,18 +198,15 @@ class MarkdownExtra extends \Michelf\Markdown {
/** /**
* Extra attribute parser * Extra attribute parser
*/ */
/** /**
* Expression to use to catch attributes (includes the braces) * Expression to use to catch attributes (includes the braces)
* @var string
*/ */
protected $id_class_attr_catch_re = '\{((?>[ ]*[#.a-z][-_:a-zA-Z0-9=]+){1,})[ ]*\}'; protected string $id_class_attr_catch_re = '\{((?>[ ]*[#.a-z][-_:a-zA-Z0-9=]+){1,})[ ]*\}';
/** /**
* Expression to use when parsing in a context when no capture is desired * Expression to use when parsing in a context when no capture is desired
* @var string
*/ */
protected $id_class_attr_nocatch_re = '\{(?>[ ]*[#.a-z][-_:a-zA-Z0-9=]+){1,}[ ]*\}'; protected string $id_class_attr_nocatch_re = '\{(?>[ ]*[#.a-z][-_:a-zA-Z0-9=]+){1,}[ ]*\}';
/** /**
* Parse attributes caught by the $this->id_class_attr_catch_re expression * Parse attributes caught by the $this->id_class_attr_catch_re expression
@ -202,7 +224,9 @@ class MarkdownExtra extends \Michelf\Markdown {
* @return string * @return string
*/ */
protected function doExtraAttributes($tag_name, $attr, $defaultIdValue = null, $classes = array()) { protected function doExtraAttributes($tag_name, $attr, $defaultIdValue = null, $classes = array()) {
if (empty($attr) && !$defaultIdValue && empty($classes)) return ""; if (empty($attr) && !$defaultIdValue && empty($classes)) {
return "";
}
// Split on components // Split on components
preg_match_all('/[#.a-z][-_:a-zA-Z0-9=]+/', $attr, $matches); preg_match_all('/[#.a-z][-_:a-zA-Z0-9=]+/', $attr, $matches);
@ -212,9 +236,9 @@ class MarkdownExtra extends \Michelf\Markdown {
$attributes = array(); $attributes = array();
$id = false; $id = false;
foreach ($elements as $element) { foreach ($elements as $element) {
if ($element{0} == '.') { if ($element[0] === '.') {
$classes[] = substr($element, 1); $classes[] = substr($element, 1);
} else if ($element{0} == '#') { } else if ($element[0] === '#') {
if ($id === false) $id = substr($element, 1); if ($id === false) $id = substr($element, 1);
} else if (strpos($element, '=') > 0) { } else if (strpos($element, '=') > 0) {
$parts = explode('=', $element, 2); $parts = explode('=', $element, 2);
@ -222,7 +246,9 @@ class MarkdownExtra extends \Michelf\Markdown {
} }
} }
if (!$id) $id = $defaultIdValue; if ($id === false || $id === '') {
$id = $defaultIdValue;
}
// Compose attributes as string // Compose attributes as string
$attr_str = ""; $attr_str = "";
@ -294,37 +320,31 @@ class MarkdownExtra extends \Michelf\Markdown {
/** /**
* HTML block parser * HTML block parser
*/ */
/** /**
* Tags that are always treated as block tags * Tags that are always treated as block tags
* @var string
*/ */
protected $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|figure|details|summary'; protected string $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|figure|details|summary';
/** /**
* Tags treated as block tags only if the opening tag is alone on its line * Tags treated as block tags only if the opening tag is alone on its line
* @var string
*/ */
protected $context_block_tags_re = 'script|noscript|style|ins|del|iframe|object|source|track|param|math|svg|canvas|audio|video'; protected string $context_block_tags_re = 'script|noscript|style|ins|del|iframe|object|source|track|param|math|svg|canvas|audio|video';
/** /**
* Tags where markdown="1" default to span mode: * Tags where markdown="1" default to span mode:
* @var string
*/ */
protected $contain_span_tags_re = 'p|h[1-6]|li|dd|dt|td|th|legend|address'; protected string $contain_span_tags_re = 'p|h[1-6]|li|dd|dt|td|th|legend|address';
/** /**
* Tags which must not have their contents modified, no matter where * Tags which must not have their contents modified, no matter where
* they appear * they appear
* @var string
*/ */
protected $clean_tags_re = 'script|style|math|svg'; protected string $clean_tags_re = 'script|style|math|svg';
/** /**
* Tags that do not need to be closed. * Tags that do not need to be closed.
* @var string
*/ */
protected $auto_close_tags_re = 'hr|img|param|source|track'; protected string $auto_close_tags_re = 'hr|img|param|source|track';
/** /**
* Hashify HTML Blocks and "clean tags". * Hashify HTML Blocks and "clean tags".
@ -486,7 +506,6 @@ class MarkdownExtra extends \Michelf\Markdown {
$tag = $parts[1]; // Tag to handle. $tag = $parts[1]; // Tag to handle.
$text = $parts[2]; // Remaining text after current tag. $text = $parts[2]; // Remaining text after current tag.
$tag_re = preg_quote($tag); // For use in a regular expression.
// Check for: Fenced code block marker. // Check for: Fenced code block marker.
// Note: need to recheck the whole tag to disambiguate backtick // Note: need to recheck the whole tag to disambiguate backtick
@ -508,14 +527,14 @@ class MarkdownExtra extends \Michelf\Markdown {
} }
} }
// Check for: Indented code block. // Check for: Indented code block.
else if ($tag{0} == "\n" || $tag{0} == " ") { else if ($tag[0] === "\n" || $tag[0] === " ") {
// Indented code block: pass it unchanged, will be handled // Indented code block: pass it unchanged, will be handled
// later. // later.
$parsed .= $tag; $parsed .= $tag;
} }
// Check for: Code span marker // Check for: Code span marker
// Note: need to check this after backtick fenced code blocks // Note: need to check this after backtick fenced code blocks
else if ($tag{0} == "`") { else if ($tag[0] === "`") {
// Find corresponding end marker. // Find corresponding end marker.
$tag_re = preg_quote($tag); $tag_re = preg_quote($tag);
if (preg_match('{^(?>.+?|\n(?!\n))*?(?<!`)' . $tag_re . '(?!`)}', if (preg_match('{^(?>.+?|\n(?!\n))*?(?<!`)' . $tag_re . '(?!`)}',
@ -549,7 +568,7 @@ class MarkdownExtra extends \Michelf\Markdown {
// Check for: Clean tag (like script, math) // Check for: Clean tag (like script, math)
// HTML Comments, processing instructions. // HTML Comments, processing instructions.
else if (preg_match('{^<(?:' . $this->clean_tags_re . ')\b}', $tag) || else if (preg_match('{^<(?:' . $this->clean_tags_re . ')\b}', $tag) ||
$tag{1} == '!' || $tag{1} == '?') $tag[1] === '!' || $tag[1] === '?')
{ {
// Need to parse tag and following text using the HTML parser. // Need to parse tag and following text using the HTML parser.
// (don't check for markdown attribute) // (don't check for markdown attribute)
@ -564,8 +583,11 @@ class MarkdownExtra extends \Michelf\Markdown {
preg_match('{^</?(?:' . $enclosing_tag_re . ')\b}', $tag)) preg_match('{^</?(?:' . $enclosing_tag_re . ')\b}', $tag))
{ {
// Increase/decrease nested tag count. // Increase/decrease nested tag count.
if ($tag{1} == '/') $depth--; if ($tag[1] === '/') {
else if ($tag{strlen($tag)-2} != '/') $depth++; $depth--;
} else if ($tag[strlen($tag)-2] !== '/') {
$depth++;
}
if ($depth < 0) { if ($depth < 0) {
// Going out of parent element. Clean up and break so we // Going out of parent element. Clean up and break so we
@ -595,7 +617,7 @@ class MarkdownExtra extends \Michelf\Markdown {
* Returns an array of that form: ( processed text , remaining text ) * Returns an array of that form: ( processed text , remaining text )
* @param string $text * @param string $text
* @param string $hash_method * @param string $hash_method
* @param string $md_attr * @param bool $md_attr Handle `markdown="1"` attribute
* @return array * @return array
*/ */
protected function _hashHTMLBlocks_inHTML($text, $hash_method, $md_attr) { protected function _hashHTMLBlocks_inHTML($text, $hash_method, $md_attr) {
@ -645,6 +667,7 @@ class MarkdownExtra extends \Michelf\Markdown {
$depth = 0; // Current depth inside the tag tree. $depth = 0; // Current depth inside the tag tree.
$block_text = ""; // Temporary text holder for current text. $block_text = ""; // Temporary text holder for current text.
$parsed = ""; // Parsed text that will be returned. $parsed = ""; // Parsed text that will be returned.
$base_tag_name_re = '';
// Get the name of the starting tag. // Get the name of the starting tag.
// (This pattern makes $base_tag_name_re safe without quoting.) // (This pattern makes $base_tag_name_re safe without quoting.)
@ -664,7 +687,7 @@ class MarkdownExtra extends \Michelf\Markdown {
// In that case, we return original text unchanged and pass the // In that case, we return original text unchanged and pass the
// first character as filtered to prevent an infinite loop in the // first character as filtered to prevent an infinite loop in the
// parent function. // parent function.
return array($original_text{0}, substr($original_text, 1)); return array($original_text[0], substr($original_text, 1));
} }
$block_text .= $parts[0]; // Text before current tag. $block_text .= $parts[0]; // Text before current tag.
@ -674,7 +697,7 @@ class MarkdownExtra extends \Michelf\Markdown {
// Check for: Auto-close tag (like <hr/>) // Check for: Auto-close tag (like <hr/>)
// Comments and Processing Instructions. // Comments and Processing Instructions.
if (preg_match('{^</?(?:' . $this->auto_close_tags_re . ')\b}', $tag) || if (preg_match('{^</?(?:' . $this->auto_close_tags_re . ')\b}', $tag) ||
$tag{1} == '!' || $tag{1} == '?') $tag[1] === '!' || $tag[1] === '?')
{ {
// Just add the tag to the block as if it was text. // Just add the tag to the block as if it was text.
$block_text .= $tag; $block_text .= $tag;
@ -683,8 +706,11 @@ class MarkdownExtra extends \Michelf\Markdown {
// Increase/decrease nested tag count. Only do so if // Increase/decrease nested tag count. Only do so if
// the tag's name match base tag's. // the tag's name match base tag's.
if (preg_match('{^</?' . $base_tag_name_re . '\b}', $tag)) { if (preg_match('{^</?' . $base_tag_name_re . '\b}', $tag)) {
if ($tag{1} == '/') $depth--; if ($tag[1] === '/') {
else if ($tag{strlen($tag)-2} != '/') $depth++; $depth--;
} else if ($tag[strlen($tag)-2] !== '/') {
$depth++;
}
} }
// Check for `markdown="1"` attribute and handle it. // Check for `markdown="1"` attribute and handle it.
@ -696,9 +722,9 @@ class MarkdownExtra extends \Michelf\Markdown {
$tag = preg_replace($markdown_attr_re, '', $tag); $tag = preg_replace($markdown_attr_re, '', $tag);
// Check if text inside this tag must be parsed in span mode. // Check if text inside this tag must be parsed in span mode.
$this->mode = $attr_m[2] . $attr_m[3]; $mode = $attr_m[2] . $attr_m[3];
$span_mode = $this->mode == 'span' || $this->mode != 'block' && $span_mode = $mode === 'span' || ($mode !== 'block' &&
preg_match('{^<(?:' . $this->contain_span_tags_re . ')\b}', $tag); preg_match('{^<(?:' . $this->contain_span_tags_re . ')\b}', $tag));
// Calculate indent before tag. // Calculate indent before tag.
if (preg_match('/(?:^|\n)( *?)(?! ).*?$/', $block_text, $matches)) { if (preg_match('/(?:^|\n)( *?)(?! ).*?$/', $block_text, $matches)) {
@ -729,8 +755,11 @@ class MarkdownExtra extends \Michelf\Markdown {
} }
// Append tag content to parsed text. // Append tag content to parsed text.
if (!$span_mode) $parsed .= "\n\n$block_text\n\n"; if (!$span_mode) {
else $parsed .= "$block_text"; $parsed .= "\n\n$block_text\n\n";
} else {
$parsed .= (string) $block_text;
}
// Start over with a new block. // Start over with a new block.
$block_text = ""; $block_text = "";
@ -875,22 +904,22 @@ class MarkdownExtra extends \Michelf\Markdown {
* @return string * @return string
*/ */
protected function _doAnchors_inline_callback($matches) { protected function _doAnchors_inline_callback($matches) {
$whole_match = $matches[1];
$link_text = $this->runSpanGamut($matches[2]); $link_text = $this->runSpanGamut($matches[2]);
$url = $matches[3] == '' ? $matches[4] : $matches[3]; $url = $matches[3] === '' ? $matches[4] : $matches[3];
$title_quote =& $matches[6];
$title =& $matches[7]; $title =& $matches[7];
$attr = $this->doExtraAttributes("a", $dummy =& $matches[8]); $attr = $this->doExtraAttributes("a", $dummy =& $matches[8]);
// if the URL was of the form <s p a c e s> it got caught by the HTML // if the URL was of the form <s p a c e s> it got caught by the HTML
// tag parser and hashed. Need to reverse the process before using the URL. // tag parser and hashed. Need to reverse the process before using the URL.
$unhashed = $this->unhash($url); $unhashed = $this->unhash($url);
if ($unhashed != $url) if ($unhashed !== $url)
$url = preg_replace('/^<(.*)>$/', '\1', $unhashed); $url = preg_replace('/^<(.*)>$/', '\1', $unhashed);
$url = $this->encodeURLAttribute($url); $url = $this->encodeURLAttribute($url);
$result = "<a href=\"$url\""; $result = "<a href=\"$url\"";
if (isset($title)) { if (isset($title) && $title_quote) {
$title = $this->encodeAttribute($title); $title = $this->encodeAttribute($title);
$result .= " title=\"$title\""; $result .= " title=\"$title\"";
} }
@ -967,7 +996,7 @@ class MarkdownExtra extends \Michelf\Markdown {
$alt_text = $matches[2]; $alt_text = $matches[2];
$link_id = strtolower($matches[3]); $link_id = strtolower($matches[3]);
if ($link_id == "") { if ($link_id === "") {
$link_id = strtolower($alt_text); // for shortcut links like ![this][]. $link_id = strtolower($alt_text); // for shortcut links like ![this][].
} }
@ -980,8 +1009,9 @@ class MarkdownExtra extends \Michelf\Markdown {
$title = $this->encodeAttribute($title); $title = $this->encodeAttribute($title);
$result .= " title=\"$title\""; $result .= " title=\"$title\"";
} }
if (isset($this->ref_attr[$link_id])) if (isset($this->ref_attr[$link_id])) {
$result .= $this->ref_attr[$link_id]; $result .= $this->ref_attr[$link_id];
}
$result .= $this->empty_element_suffix; $result .= $this->empty_element_suffix;
$result = $this->hashPart($result); $result = $this->hashPart($result);
} }
@ -999,16 +1029,16 @@ class MarkdownExtra extends \Michelf\Markdown {
* @return string * @return string
*/ */
protected function _doImages_inline_callback($matches) { protected function _doImages_inline_callback($matches) {
$whole_match = $matches[1];
$alt_text = $matches[2]; $alt_text = $matches[2];
$url = $matches[3] == '' ? $matches[4] : $matches[3]; $url = $matches[3] === '' ? $matches[4] : $matches[3];
$title_quote =& $matches[6];
$title =& $matches[7]; $title =& $matches[7];
$attr = $this->doExtraAttributes("img", $dummy =& $matches[8]); $attr = $this->doExtraAttributes("img", $dummy =& $matches[8]);
$alt_text = $this->encodeAttribute($alt_text); $alt_text = $this->encodeAttribute($alt_text);
$url = $this->encodeURLAttribute($url); $url = $this->encodeURLAttribute($url);
$result = "<img src=\"$url\" alt=\"$alt_text\""; $result = "<img src=\"$url\" alt=\"$alt_text\"";
if (isset($title)) { if (isset($title) && $title_quote) {
$title = $this->encodeAttribute($title); $title = $this->encodeAttribute($title);
$result .= " title=\"$title\""; // $title already quoted $result .= " title=\"$title\""; // $title already quoted
} }
@ -1067,11 +1097,11 @@ class MarkdownExtra extends \Michelf\Markdown {
* @return string * @return string
*/ */
protected function _doHeaders_callback_setext($matches) { protected function _doHeaders_callback_setext($matches) {
if ($matches[3] == '-' && preg_match('{^- }', $matches[1])) { if ($matches[3] === '-' && preg_match('{^- }', $matches[1])) {
return $matches[0]; return $matches[0];
} }
$level = $matches[3]{0} == '=' ? 1 : 2; $level = $matches[3][0] === '=' ? 1 : 2;
$defaultId = is_callable($this->header_id_func) ? call_user_func($this->header_id_func, $matches[1]) : null; $defaultId = is_callable($this->header_id_func) ? call_user_func($this->header_id_func, $matches[1]) : null;
@ -1174,8 +1204,7 @@ class MarkdownExtra extends \Michelf\Markdown {
* @param string $alignname * @param string $alignname
* @return string * @return string
*/ */
protected function _doTable_makeAlignAttr($alignname) protected function _doTable_makeAlignAttr($alignname) {
{
if (empty($this->table_align_class_tmpl)) { if (empty($this->table_align_class_tmpl)) {
return " align=\"$alignname\""; return " align=\"$alignname\"";
} }
@ -1193,6 +1222,7 @@ class MarkdownExtra extends \Michelf\Markdown {
$head = $matches[1]; $head = $matches[1];
$underline = $matches[2]; $underline = $matches[2];
$content = $matches[3]; $content = $matches[3];
$attr = [];
// Remove any tailing pipes for each line. // Remove any tailing pipes for each line.
$head = preg_replace('/[|] *$/m', '', $head); $head = preg_replace('/[|] *$/m', '', $head);
@ -1223,8 +1253,9 @@ class MarkdownExtra extends \Michelf\Markdown {
$text = "<table>\n"; $text = "<table>\n";
$text .= "<thead>\n"; $text .= "<thead>\n";
$text .= "<tr>\n"; $text .= "<tr>\n";
foreach ($headers as $n => $header) foreach ($headers as $n => $header) {
$text .= " <th$attr[$n]>" . $this->runSpanGamut(trim($header)) . "</th>\n"; $text .= " <th$attr[$n]>" . $this->runSpanGamut(trim($header)) . "</th>\n";
}
$text .= "</tr>\n"; $text .= "</tr>\n";
$text .= "</thead>\n"; $text .= "</thead>\n";
@ -1242,8 +1273,9 @@ class MarkdownExtra extends \Michelf\Markdown {
$row_cells = array_pad($row_cells, $col_count, ''); $row_cells = array_pad($row_cells, $col_count, '');
$text .= "<tr>\n"; $text .= "<tr>\n";
foreach ($row_cells as $n => $cell) foreach ($row_cells as $n => $cell) {
$text .= " <td$attr[$n]>" . $this->runSpanGamut(trim($cell)) . "</td>\n"; $text .= " <td$attr[$n]>" . $this->runSpanGamut(trim($cell)) . "</td>\n";
}
$text .= "</tr>\n"; $text .= "</tr>\n";
} }
$text .= "</tbody>\n"; $text .= "</tbody>\n";
@ -1411,8 +1443,6 @@ class MarkdownExtra extends \Michelf\Markdown {
*/ */
protected function doFencedCodeBlocks($text) { protected function doFencedCodeBlocks($text) {
$less_than_tab = $this->tab_width;
$text = preg_replace_callback('{ $text = preg_replace_callback('{
(?:\n|\A) (?:\n|\A)
# 1: Opening marker # 1: Opening marker
@ -1465,9 +1495,10 @@ class MarkdownExtra extends \Michelf\Markdown {
array($this, '_doFencedCodeBlocks_newlines'), $codeblock); array($this, '_doFencedCodeBlocks_newlines'), $codeblock);
$classes = array(); $classes = array();
if ($classname != "") { if ($classname !== "") {
if ($classname{0} == '.') if ($classname[0] === '.') {
$classname = substr($classname, 1); $classname = substr($classname, 1);
}
$classes[] = $this->code_class_prefix . $classname; $classes[] = $this->code_class_prefix . $classname;
} }
$attr_str = $this->doExtraAttributes($this->code_attr_on_pre ? "pre" : "code", $attrs, null, $classes); $attr_str = $this->doExtraAttributes($this->code_attr_on_pre ? "pre" : "code", $attrs, null, $classes);
@ -1493,17 +1524,17 @@ class MarkdownExtra extends \Michelf\Markdown {
* work in the middle of a word. * work in the middle of a word.
* @var array * @var array
*/ */
protected $em_relist = array( protected array $em_relist = array(
'' => '(?:(?<!\*)\*(?!\*)|(?<![a-zA-Z0-9_])_(?!_))(?![\.,:;]?\s)', '' => '(?:(?<!\*)\*(?!\*)|(?<![a-zA-Z0-9_])_(?!_))(?![\.,:;]?\s)',
'*' => '(?<![\s*])\*(?!\*)', '*' => '(?<![\s*])\*(?!\*)',
'_' => '(?<![\s_])_(?![a-zA-Z0-9_])', '_' => '(?<![\s_])_(?![a-zA-Z0-9_])',
); );
protected $strong_relist = array( protected array $strong_relist = array(
'' => '(?:(?<!\*)\*\*(?!\*)|(?<![a-zA-Z0-9_])__(?!_))(?![\.,:;]?\s)', '' => '(?:(?<!\*)\*\*(?!\*)|(?<![a-zA-Z0-9_])__(?!_))(?![\.,:;]?\s)',
'**' => '(?<![\s*])\*\*(?!\*)', '**' => '(?<![\s*])\*\*(?!\*)',
'__' => '(?<![\s_])__(?![a-zA-Z0-9_])', '__' => '(?<![\s_])__(?![a-zA-Z0-9_])',
); );
protected $em_strong_relist = array( protected array $em_strong_relist = array(
'' => '(?:(?<!\*)\*\*\*(?!\*)|(?<![a-zA-Z0-9_])___(?!_))(?![\.,:;]?\s)', '' => '(?:(?<!\*)\*\*\*(?!\*)|(?<![a-zA-Z0-9_])___(?!_))(?![\.,:;]?\s)',
'***' => '(?<![\s*])\*\*\*(?!\*)', '***' => '(?<![\s*])\*\*\*(?!\*)',
'___' => '(?<![\s_])___(?![a-zA-Z0-9_])', '___' => '(?<![\s_])___(?![a-zA-Z0-9_])',
@ -1608,65 +1639,95 @@ class MarkdownExtra extends \Michelf\Markdown {
$text = preg_replace_callback('{F\x1Afn:(.*?)\x1A:}', $text = preg_replace_callback('{F\x1Afn:(.*?)\x1A:}',
array($this, '_appendFootnotes_callback'), $text); array($this, '_appendFootnotes_callback'), $text);
if (!empty($this->footnotes_ordered)) { if ( ! empty( $this->footnotes_ordered ) ) {
$text .= "\n\n"; $this->_doFootnotes();
$text .= "<div class=\"footnotes\" role=\"doc-endnotes\">\n"; if ( ! $this->omit_footnotes ) {
$text .= "<hr" . $this->empty_element_suffix . "\n"; $text .= "\n\n";
$text .= "<ol>\n\n"; $text .= "<div class=\"footnotes\" role=\"doc-endnotes\">\n";
$text .= "<hr" . $this->empty_element_suffix . "\n";
$attr = ""; $text .= $this->footnotes_assembled;
if ($this->fn_backlink_class != "") { $text .= "</div>";
$class = $this->fn_backlink_class;
$class = $this->encodeAttribute($class);
$attr .= " class=\"$class\"";
} }
if ($this->fn_backlink_title != "") { }
$title = $this->fn_backlink_title; return $text;
$title = $this->encodeAttribute($title); }
$attr .= " title=\"$title\"";
$attr .= " aria-label=\"$title\"";
}
$attr .= " role=\"doc-backlink\"";
$backlink_text = $this->fn_backlink_html;
$num = 0;
while (!empty($this->footnotes_ordered)) {
$footnote = reset($this->footnotes_ordered);
$note_id = key($this->footnotes_ordered);
unset($this->footnotes_ordered[$note_id]);
$ref_count = $this->footnotes_ref_count[$note_id];
unset($this->footnotes_ref_count[$note_id]);
unset($this->footnotes[$note_id]);
$footnote .= "\n"; // Need to append newline before parsing. /**
$footnote = $this->runBlockGamut("$footnote\n"); * Generates the HTML for footnotes. Called by appendFootnotes, even if
$footnote = preg_replace_callback('{F\x1Afn:(.*?)\x1A:}', * footnotes are not being appended.
array($this, '_appendFootnotes_callback'), $footnote); * @return void
*/
protected function _doFootnotes() {
$attr = array();
if ($this->fn_backlink_class !== "") {
$class = $this->fn_backlink_class;
$class = $this->encodeAttribute($class);
$attr['class'] = " class=\"$class\"";
}
$attr['role'] = " role=\"doc-backlink\"";
$num = 0;
$attr = str_replace("%%", ++$num, $attr); $text = "<ol>\n\n";
$note_id = $this->encodeAttribute($note_id); while (!empty($this->footnotes_ordered)) {
$footnote = reset($this->footnotes_ordered);
$note_id = key($this->footnotes_ordered);
unset($this->footnotes_ordered[$note_id]);
$ref_count = $this->footnotes_ref_count[$note_id];
unset($this->footnotes_ref_count[$note_id]);
unset($this->footnotes[$note_id]);
// Prepare backlink, multiple backlinks if multiple references $footnote .= "\n"; // Need to append newline before parsing.
$backlink = "<a href=\"#fnref:$note_id\"$attr>$backlink_text</a>"; $footnote = $this->runBlockGamut("$footnote\n");
for ($ref_num = 2; $ref_num <= $ref_count; ++$ref_num) { $footnote = preg_replace_callback('{F\x1Afn:(.*?)\x1A:}',
$backlink .= " <a href=\"#fnref$ref_num:$note_id\"$attr>$backlink_text</a>"; array($this, '_appendFootnotes_callback'), $footnote);
$num++;
$note_id = $this->encodeAttribute($note_id);
// Prepare backlink, multiple backlinks if multiple references
// Do not create empty backlinks if the html is blank
$backlink = "";
if (!empty($this->fn_backlink_html)) {
for ($ref_num = 1; $ref_num <= $ref_count; ++$ref_num) {
if (!empty($this->fn_backlink_title)) {
$attr['title'] = ' title="' . $this->encodeAttribute($this->fn_backlink_title) . '"';
}
if (!empty($this->fn_backlink_label)) {
$attr['label'] = ' aria-label="' . $this->encodeAttribute($this->fn_backlink_label) . '"';
}
$parsed_attr = $this->parseFootnotePlaceholders(
implode('', $attr),
$num,
$ref_num
);
$backlink_text = $this->parseFootnotePlaceholders(
$this->fn_backlink_html,
$num,
$ref_num
);
$ref_count_mark = $ref_num > 1 ? $ref_num : '';
$backlink .= " <a href=\"#fnref$ref_count_mark:$note_id\"$parsed_attr>$backlink_text</a>";
} }
// Add backlink to last paragraph; create new paragraph if needed. $backlink = trim($backlink);
}
// Add backlink to last paragraph; create new paragraph if needed.
if (!empty($backlink)) {
if (preg_match('{</p>$}', $footnote)) { if (preg_match('{</p>$}', $footnote)) {
$footnote = substr($footnote, 0, -4) . "&#160;$backlink</p>"; $footnote = substr($footnote, 0, -4) . "&#160;$backlink</p>";
} else { } else {
$footnote .= "\n\n<p>$backlink</p>"; $footnote .= "\n\n<p>$backlink</p>";
} }
$text .= "<li id=\"fn:$note_id\" role=\"doc-endnote\">\n";
$text .= $footnote . "\n";
$text .= "</li>\n\n";
} }
$text .= "</ol>\n"; $text .= "<li id=\"fn:$note_id\" role=\"doc-endnote\">\n";
$text .= "</div>"; $text .= $footnote . "\n";
$text .= "</li>\n\n";
} }
return $text; $text .= "</ol>\n";
$this->footnotes_assembled = $text;
} }
/** /**
@ -1693,12 +1754,12 @@ class MarkdownExtra extends \Michelf\Markdown {
} }
$attr = ""; $attr = "";
if ($this->fn_link_class != "") { if ($this->fn_link_class !== "") {
$class = $this->fn_link_class; $class = $this->fn_link_class;
$class = $this->encodeAttribute($class); $class = $this->encodeAttribute($class);
$attr .= " class=\"$class\""; $attr .= " class=\"$class\"";
} }
if ($this->fn_link_title != "") { if ($this->fn_link_title !== "") {
$title = $this->fn_link_title; $title = $this->fn_link_title;
$title = $this->encodeAttribute($title); $title = $this->encodeAttribute($title);
$attr .= " title=\"$title\""; $attr .= " title=\"$title\"";
@ -1717,6 +1778,23 @@ class MarkdownExtra extends \Michelf\Markdown {
return "[^" . $matches[1] . "]"; return "[^" . $matches[1] . "]";
} }
/**
* Build footnote label by evaluating any placeholders.
* - ^^ footnote number
* - %% footnote reference number (Nth reference to footnote number)
* @param string $label
* @param int $footnote_number
* @param int $reference_number
* @return string
*/
protected function parseFootnotePlaceholders($label, $footnote_number, $reference_number) {
return str_replace(
array('^^', '%%'),
array($footnote_number, $reference_number),
$label
);
}
/** /**
* Abbreviations - strips abbreviations from text, stores titles in hash * Abbreviations - strips abbreviations from text, stores titles in hash
@ -1783,12 +1861,10 @@ class MarkdownExtra extends \Michelf\Markdown {
$desc = $this->abbr_desciptions[$abbr]; $desc = $this->abbr_desciptions[$abbr];
if (empty($desc)) { if (empty($desc)) {
return $this->hashPart("<abbr>$abbr</abbr>"); return $this->hashPart("<abbr>$abbr</abbr>");
} else {
$desc = $this->encodeAttribute($desc);
return $this->hashPart("<abbr title=\"$desc\">$abbr</abbr>");
} }
} else { $desc = $this->encodeAttribute($desc);
return $matches[0]; return $this->hashPart("<abbr title=\"$desc\">$abbr</abbr>");
} }
return $matches[0];
} }
} }

View file

@ -4,7 +4,7 @@
* *
* @package php-markdown * @package php-markdown
* @author Michel Fortin <michel.fortin@michelf.com> * @author Michel Fortin <michel.fortin@michelf.com>
* @copyright 2004-2018 Michel Fortin <https://michelf.com/projects/php-markdown/> * @copyright 2004-2021 Michel Fortin <https://michelf.com/projects/php-markdown/>
* @copyright (Original Markdown) 2004-2006 John Gruber <https://daringfireball.net/projects/markdown/> * @copyright (Original Markdown) 2004-2006 John Gruber <https://daringfireball.net/projects/markdown/>
*/ */

View file

@ -1,7 +1,9 @@
PHP Markdown PHP Markdown
============ ============
PHP Markdown Lib 1.8.0 - 14 Jan 2018 ![ci.yml](https://github.com/michelf/php-markdown/actions/workflows/ci.yml/badge.svg)
PHP Markdown Lib 1.9.0 - 1 Dec 2019
by Michel Fortin by Michel Fortin
<https://michelf.ca/> <https://michelf.ca/>
@ -34,15 +36,14 @@ program by John Gruber.
Requirement Requirement
----------- -----------
This library package requires PHP 5.3 or later. This library package requires PHP 7.4 or later.
Note: The older plugin/library hybrid package for PHP Markdown and Note: The older plugin/library hybrid package for PHP Markdown and
PHP Markdown Extra is no longer maintained but will work with PHP 4.0.5 and PHP Markdown Extra is no longer maintained but will work with PHP 4.0.5 and
later. later.
Before PHP 5.3.7, pcre.backtrack_limit defaults to 100 000, which is too small You might need to set pcre.backtrack_limit higher than 1 000 000
in many situations. You might need to set it to higher values. Later PHP (the default), though the default is usually fine.
releases defaults to 1 000 000, which is usually fine.
Usage Usage
@ -163,26 +164,54 @@ potential side effects, and future extensibility -- before deciding on
acceptance or rejection. acceptance or rejection.
If you make a pull request that includes changes to the parser please add If you make a pull request that includes changes to the parser please add
tests for what is being changed to [MDTest][] and make a pull request there tests for what is being changed to the `test/` directory. This can be as
too. simple as adding a `.text` (input) file with a corresponding `.xhtml`
(output) file to proper category under `./test/resources/`.
[MDTest]: https://github.com/michelf/mdtest/ Traditionally tests were in a separate repository, [MDTest](https://github.com/michelf/mdtest)
but they are now located here, alongside the source code.
Donations Donations
--------- ---------
If you wish to make a donation that will help me devote more time to If you wish to make a donation that will help me devote more time to
PHP Markdown, please visit [michelf.ca/donate] or send Bitcoin to PHP Markdown, please visit [michelf.ca/donate].
[1HiuX34czvVPPdhXbUAsAu7pZcesniDCGH].
[michelf.ca/donate]: https://michelf.ca/donate/#!Thanks%20for%20PHP%20Markdown [michelf.ca/donate]: https://michelf.ca/donate/#!Thanks%20for%20PHP%20Markdown
[1HiuX34czvVPPdhXbUAsAu7pZcesniDCGH]: bitcoin:1HiuX34czvVPPdhXbUAsAu7pZcesniDCGH
Version History Version History
--------------- ---------------
PHP Markdown Lib 1.9.0 (1 Dec 2019)
* Added `fn_backlink_label` configuration variable to put some text in the
`aria-label` attribute.
(Thanks to Sunny Walker for the implementation.)
* Occurances of "`^^`" in `fn_backlink_html`, `fn_backlink_class`,
`fn_backlink_title`, and `fn_backlink_label` will be replaced by the
corresponding footnote number in the HTML output. Occurances of "`%%`" will be
replaced by a number for the reference (footnotes can have multiple references).
(Thanks to Sunny Walker for the implementation.)
* Added configuration variable `omit_footnotes`. When `true` footnotes are not
appended at the end of the generated HTML and the `footnotes_assembled`
variable will contain the HTML for the footnote list, allowing footnotes to be
moved somewhere else on the page.
(Thanks to James K. for the implementation.)
Note: when placing the content of `footnotes_assembled` on the page, consider
adding the attribute `role="doc-endnotes"` to the `<div>` or `<section>` that will
enclose the list of footnotes so they are reachable to accessibility tools the
same way they would be with the default HTML output.
* Fixed deprecation warnings from PHP about usage of curly braces to access
characters in text strings.
(Thanks to Remi Collet and Frans-Willem Post.)
PHP Markdown Lib 1.8.0 (14 Jan 2018) PHP Markdown Lib 1.8.0 (14 Jan 2018)
* Autoloading with Composer now uses PSR-4. * Autoloading with Composer now uses PSR-4.
@ -371,7 +400,7 @@ Copyright and License
--------------------- ---------------------
PHP Markdown Lib PHP Markdown Lib
Copyright (c) 2004-2016 Michel Fortin Copyright (c) 2004-2019 Michel Fortin
<https://michelf.ca/> <https://michelf.ca/>
All rights reserved. All rights reserved.

View file

@ -9,7 +9,7 @@ spl_autoload_register(function($class){
require str_replace('\\', DIRECTORY_SEPARATOR, ltrim($class, '\\')).'.php'; require str_replace('\\', DIRECTORY_SEPARATOR, ltrim($class, '\\')).'.php';
}); });
// If using Composer, use this instead: // If using Composer, use this instead:
//require 'vendor/autoloader.php'; //require 'vendor/autoload.php';
// Get Markdown class // Get Markdown class
use Michelf\Markdown; use Michelf\Markdown;
@ -21,13 +21,13 @@ $html = Markdown::defaultTransform($text);
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>PHP Markdown Lib - Readme</title> <title>PHP Markdown Lib - Readme</title>
</head> </head>
<body> <body>
<?php <?php
// Put HTML content in the document // Put HTML content in the document
echo $html; echo $html;
?> ?>
</body> </body>
</html> </html>

View file

@ -18,9 +18,27 @@
} }
], ],
"require": { "require": {
"php": ">=5.3.0" "php": ">=7.4"
}, },
"autoload": { "autoload": {
"psr-4": { "Michelf\\": "Michelf/" } "psr-4": { "Michelf\\": "Michelf/" }
},
"require-dev": {
"phpspec/prophecy": "^1.6",
"friendsofphp/php-cs-fixer": "^3.0",
"phpunit/phpunit": "^9.5",
"phpstan/phpstan": "^1.0",
"phpstan/phpstan-phpunit": "^1.0"
},
"scripts": {
"tests": "vendor/bin/phpunit test/",
"phpstan": [
"vendor/bin/phpstan analyse Michelf/ --level=5",
"vendor/bin/phpstan analyse -c test/phpstan.neon test/ --level=5"
],
"codestyle": "vendor/bin/php-cs-fixer fix Michelf --dry-run --verbose --show-progress=none",
"codestyle-fix": "vendor/bin/php-cs-fixer fix Michelf"
} }
} }

View file

@ -23,9 +23,9 @@ class serendipity_event_markdown extends serendipity_event
$propbag->add('requirements', array( $propbag->add('requirements', array(
'serendipity' => '0.7', 'serendipity' => '0.7',
'smarty' => '2.6.7', 'smarty' => '2.6.7',
'php' => '5.3.0' 'php' => '7.4.0'
)); ));
$propbag->add('version', '1.30.1'); $propbag->add('version', '1.31');
$propbag->add('cachable_events', array('frontend_display' => true)); $propbag->add('cachable_events', array('frontend_display' => true));
$propbag->add('event_hooks', array( $propbag->add('event_hooks', array(
'frontend_display' => true, 'frontend_display' => true,