diff --git a/Michelf/Markdown.php b/Michelf/Markdown.php index e4c2c23..e7a5976 100644 --- a/Michelf/Markdown.php +++ b/Michelf/Markdown.php @@ -4,7 +4,7 @@ * * @package php-markdown * @author Michel Fortin - * @copyright 2004-2018 Michel Fortin + * @copyright 2004-2019 Michel Fortin * @copyright (Original Markdown) 2004-2006 John Gruber */ @@ -18,7 +18,7 @@ class Markdown implements MarkdownInterface { * Define the package version * @var string */ - const MARKDOWNLIB_VERSION = "1.8.0"; + const MARKDOWNLIB_VERSION = "2.0"; /** * Simple function interface - Initialize the parser and return the result @@ -31,7 +31,7 @@ class Markdown implements MarkdownInterface { */ public static function defaultTransform($text) { // 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 static $parser_list; @@ -49,61 +49,56 @@ public static function defaultTransform($text) { /** * Configuration variables */ - /** * Change to ">" for HTML output. - * @var string */ - public $empty_element_suffix = " />"; + public string $empty_element_suffix = " />"; /** * 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. - * @var boolean */ - public $no_markup = false; - public $no_entities = false; + public bool $no_markup = false; + public bool $no_entities = false; /** * Change to `true` to enable line breaks on \n without two trailling spaces * @var boolean */ - public $hard_wrap = false; + public bool $hard_wrap = false; /** * Predefined URLs and titles for reference links and images. - * @var array */ - public $predef_urls = array(); - public $predef_titles = array(); + public array $predef_urls = array(); + public array $predef_titles = array(); /** * Optional filter function for URLs - * @var callable + * @var callable|null */ public $url_filter_func = null; /** * Optional header id="" generation callback function. - * @var callable + * @var callable|null */ public $header_id_func = null; /** * Optional function for converting code block content to HTML - * @var callable + * @var callable|null */ public $code_block_content_func = null; /** * Optional function for converting code span content to HTML. - * @var callable + * @var callable|null */ public $code_span_content_func = null; @@ -121,32 +116,27 @@ public static function defaultTransform($text) { *
  • List item two
  • *
  • List item three
  • * - * - * @var bool */ - public $enhanced_ordered_list = false; + public bool $enhanced_ordered_list = false; /** * Parser implementation */ - /** * Regex to match balanced [brackets]. * 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_url_parenthesis_depth = 4; + protected int $nested_url_parenthesis_depth = 4; protected $nested_url_parenthesis_re; /** * Table of hash values for escaped characters: - * @var string */ - protected $escape_chars = '\`*_{}[]()>#+-.!'; - protected $escape_chars_re; + protected string $escape_chars = '\`*_{}[]()>#+-.!'; + protected string $escape_chars_re; /** * Constructor function. Initialize appropriate member variables. @@ -175,23 +165,20 @@ public function __construct() { /** * Internal hashes used during transformation. - * @var array */ - protected $urls = array(); + protected array $urls = array(); protected $titles = array(); - protected $html_hashes = array(); + protected array $html_hashes = array(); /** * Status flag to avoid invalid nesting. - * @var boolean */ - protected $in_anchor = false; + protected bool $in_anchor = false; /** * 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. @@ -263,9 +250,8 @@ public function transform($text) { /** * Define the document gamut - * @var array */ - protected $document_gamut = array( + protected array $document_gamut = array( // Strip link definitions, store in hashes. "stripLinkDefinitions" => 20, "runBasicBlockGamut" => 30, @@ -354,7 +340,7 @@ protected function hashHTMLBlocks($text) { $block_tags_b_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|'. 'script|noscript|style|form|fieldset|iframe|math|svg|'. 'article|section|nav|aside|hgroup|header|footer|'. - 'figure'; + 'figure|details|summary'; // Regular expression for the content of a block tag. $nested_tags_level = 4; @@ -525,9 +511,8 @@ protected function hashBlock($text) { /** * Define the block gamut - these are all the transformations that form * block-level tags like paragraphs, headers, and list items. - * @var array */ - protected $block_gamut = array( + protected array $block_gamut = array( "doHeaders" => 10, "doHorizontalRules" => 20, "doLists" => 40, @@ -597,9 +582,8 @@ protected function doHorizontalRules($text) { /** * These are all the transformations that occur *within* block-level * 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 // in one shot. "parseSpan" => -30, @@ -724,7 +708,7 @@ protected function doAnchors($text) { /** * Callback method to parse referenced anchors - * @param string $matches + * @param array $matches * @return string */ protected function _doAnchors_reference_callback($matches) { @@ -763,26 +747,25 @@ protected function _doAnchors_reference_callback($matches) { /** * Callback method to parse inline anchors - * @param string $matches + * @param array $matches * @return string */ protected function _doAnchors_inline_callback($matches) { - $whole_match = $matches[1]; $link_text = $this->runSpanGamut($matches[2]); - $url = $matches[3] == '' ? $matches[4] : $matches[3]; + $url = $matches[3] === '' ? $matches[4] : $matches[3]; $title =& $matches[7]; // If the URL was of the form it got caught by the HTML // tag parser and hashed. Need to reverse the process before using // the URL. $unhashed = $this->unhash($url); - if ($unhashed != $url) + if ($unhashed !== $url) $url = preg_replace('/^<(.*)>$/', '\1', $unhashed); $url = $this->encodeURLAttribute($url); $result = "encodeAttribute($title); $result .= " title=\"$title\""; } @@ -952,7 +935,7 @@ protected function _doHeaders_callback_setext($matches) { return $matches[0]; } - $level = $matches[2]{0} == '=' ? 1 : 2; + $level = $matches[2][0] == '=' ? 1 : 2; // ID attribute generation $idAtt = $this->_generateIdFromHeaderValue($matches[1]); @@ -1106,9 +1089,8 @@ protected function _doLists_callback($matches) { /** * 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 @@ -1218,7 +1200,7 @@ protected function _doCodeBlocks_callback($matches) { $codeblock = $matches[1]; $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, ""); } else { $codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES); @@ -1237,7 +1219,7 @@ protected function _doCodeBlocks_callback($matches) { * @return string */ 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); } else { $code = htmlspecialchars(trim($code), ENT_NOQUOTES); @@ -1249,7 +1231,7 @@ protected function makeCodeSpan($code) { * Define the emphasis operators with their regex matches * @var array */ - protected $em_relist = array( + protected array $em_relist = array( '' => '(?:(? '(? '(? '(?:(? '(? '(? '(?:(? '(? '(?runSpanGamut($span); @@ -1383,7 +1364,7 @@ protected function doItalicsAndBold($text) { } else { // Reached opening three-char emphasis marker. Push on token // stack; will be handled by the special condition above. - $em = $token{0}; + $em = $token[0]; $strong = "$em$em"; array_unshift($token_stack, $token); array_unshift($text_stack, ''); @@ -1576,11 +1557,11 @@ protected function encodeAttribute($text) { * This function is *not* suitable for attributes enclosed in single quotes. * * @param string $url - * @param string &$text Passed by reference + * @param string $text Passed by reference * @return string URL */ 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); } @@ -1694,7 +1675,7 @@ protected function _doAutoLinks_email_callback($matches) { * attribute special characters by Allan Odgaard. * * @param string $text - * @param string &$tail + * @param string $tail Passed by reference * @param integer $head_length * @return string */ @@ -1792,13 +1773,13 @@ protected function parseSpan($str) { * Handle $token provided by parseSpan by determining its nature and * returning the corresponding value that should replace it. * @param string $token - * @param string &$str + * @param string $str Passed by reference * @return string */ protected function handleSpanToken($token, &$str) { - switch ($token{0}) { + switch ($token[0]) { case "\\": - return $this->hashPart("&#". ord($token{1}). ";"); + return $this->hashPart("&#". ord($token[1]). ";"); case "`": // Search for end marker in remaining text. if (preg_match('/^(.*?[^`])'.preg_quote($token).'(?!`)(.*)$/sm', @@ -1827,7 +1808,7 @@ protected function outdent($text) { /** * String length function for detab. `_initDetab` will create a function to * handle UTF-8 if the default function does not exist. - * @var string + * can be a string or function */ protected $utf8_strlen = 'mb_strlen'; @@ -1884,9 +1865,7 @@ protected function _initDetab() { return; } - $this->utf8_strlen = function($text) { - return preg_match_all('/[\x00-\xBF]|[\xC0-\xFF][\x80-\xBF]*/', $text, $m); - }; + $this->utf8_strlen = fn($text) => preg_match_all('/[\x00-\xBF]|[\xC0-\xFF][\x80-\xBF]*/', $text, $m); } /** diff --git a/Michelf/MarkdownExtra.php b/Michelf/MarkdownExtra.php index a8f7f2c..060a4ae 100644 --- a/Michelf/MarkdownExtra.php +++ b/Michelf/MarkdownExtra.php @@ -4,7 +4,7 @@ * * @package php-markdown * @author Michel Fortin - * @copyright 2004-2018 Michel Fortin + * @copyright 2004-2019 Michel Fortin * @copyright (Original Markdown) 2004-2006 John Gruber */ @@ -17,67 +17,86 @@ class MarkdownExtra extends \Michelf\Markdown { /** * Configuration variables */ - /** * Prefix for footnote ids. - * @var string */ - public $fn_id_prefix = ""; + public string $fn_id_prefix = ""; /** - * Optional title attribute for footnote links and backlinks. - * @var string + * Optional title attribute for footnote links. */ - public $fn_link_title = ""; - public $fn_backlink_title = ""; + public string $fn_link_title = ""; /** * Optional class attribute for footnote links and backlinks. - * @var string */ - public $fn_link_class = "footnote-ref"; - public $fn_backlink_class = "footnote-backref"; + public string $fn_link_class = "footnote-ref"; + public string $fn_backlink_class = "footnote-backref"; /** * 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 * 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 = '↩︎'; + public string $fn_backlink_html = '↩︎'; + + /** + * 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) * For instance: 'go-%%' becomes 'go-left' or 'go-right' or 'go-center' * 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. - * @var string */ - public $code_class_prefix = ""; + public string $code_class_prefix = ""; /** * Class attribute for code blocks goes on the `code` tag; * 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. - * @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 # - * @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 @@ -117,21 +136,23 @@ public function __construct() { /** * Extra variables used during extra transformations. - * @var array */ - protected $footnotes = array(); - protected $footnotes_ordered = array(); - protected $footnotes_ref_count = array(); - protected $footnotes_numbers = array(); - protected $abbr_desciptions = array(); - /** @var string */ - protected $abbr_word_re = ''; + protected array $footnotes = array(); + protected array $footnotes_ordered = array(); + protected array $footnotes_ref_count = array(); + protected array $footnotes_numbers = array(); + protected array $abbr_desciptions = array(); + protected string $abbr_word_re = ''; /** * 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. @@ -146,6 +167,7 @@ protected function setup() { $this->abbr_desciptions = array(); $this->abbr_word_re = ''; $this->footnote_counter = 1; + $this->footnotes_assembled = null; foreach ($this->predef_abbr as $abbr_word => $abbr_desc) { if ($this->abbr_word_re) @@ -166,6 +188,9 @@ protected function teardown() { $this->abbr_desciptions = array(); $this->abbr_word_re = ''; + if ( ! $this->omit_footnotes ) + $this->footnotes_assembled = null; + parent::teardown(); } @@ -173,18 +198,15 @@ protected function teardown() { /** * Extra attribute parser */ - /** * 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 - * @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 @@ -202,7 +224,9 @@ protected function teardown() { * @return string */ 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 preg_match_all('/[#.a-z][-_:a-zA-Z0-9=]+/', $attr, $matches); @@ -212,9 +236,9 @@ protected function doExtraAttributes($tag_name, $attr, $defaultIdValue = null, $ $attributes = array(); $id = false; foreach ($elements as $element) { - if ($element{0} == '.') { + if ($element[0] === '.') { $classes[] = substr($element, 1); - } else if ($element{0} == '#') { + } else if ($element[0] === '#') { if ($id === false) $id = substr($element, 1); } else if (strpos($element, '=') > 0) { $parts = explode('=', $element, 2); @@ -222,7 +246,9 @@ protected function doExtraAttributes($tag_name, $attr, $defaultIdValue = null, $ } } - if (!$id) $id = $defaultIdValue; + if ($id === false || $id === '') { + $id = $defaultIdValue; + } // Compose attributes as string $attr_str = ""; @@ -294,37 +320,31 @@ protected function _stripLinkDefinitions_callback($matches) { /** * HTML block parser */ - /** * 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'; + 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 - * @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: - * @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 * 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. - * @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". @@ -486,7 +506,6 @@ protected function _hashHTMLBlocks_inMarkdown($text, $indent = 0, $tag = $parts[1]; // Tag to handle. $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. // Note: need to recheck the whole tag to disambiguate backtick @@ -508,14 +527,14 @@ protected function _hashHTMLBlocks_inMarkdown($text, $indent = 0, } } // 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 // later. $parsed .= $tag; } // Check for: Code span marker // Note: need to check this after backtick fenced code blocks - else if ($tag{0} == "`") { + else if ($tag[0] === "`") { // Find corresponding end marker. $tag_re = preg_quote($tag); if (preg_match('{^(?>.+?|\n(?!\n))*?(?clean_tags_re . ')\b}', $tag) || - $tag{1} == '!' || $tag{1} == '?') + $tag[1] === '!' || $tag[1] === '?') { // Need to parse tag and following text using the HTML parser. // (don't check for markdown attribute) @@ -564,8 +583,11 @@ protected function _hashHTMLBlocks_inMarkdown($text, $indent = 0, preg_match('{^) // Comments and Processing Instructions. if (preg_match('{^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. $block_text .= $tag; @@ -683,8 +706,11 @@ protected function _hashHTMLBlocks_inHTML($text, $hash_method, $md_attr) { // Increase/decrease nested tag count. Only do so if // the tag's name match base tag's. if (preg_match('{^mode = $attr_m[2] . $attr_m[3]; - $span_mode = $this->mode == 'span' || $this->mode != 'block' && - preg_match('{^<(?:' . $this->contain_span_tags_re . ')\b}', $tag); + $mode = $attr_m[2] . $attr_m[3]; + $span_mode = $mode === 'span' || ($mode !== 'block' && + preg_match('{^<(?:' . $this->contain_span_tags_re . ')\b}', $tag)); // Calculate indent before tag. if (preg_match('/(?:^|\n)( *?)(?! ).*?$/', $block_text, $matches)) { @@ -729,8 +755,11 @@ protected function _hashHTMLBlocks_inHTML($text, $hash_method, $md_attr) { } // Append tag content to parsed text. - if (!$span_mode) $parsed .= "\n\n$block_text\n\n"; - else $parsed .= "$block_text"; + if (!$span_mode) { + $parsed .= "\n\n$block_text\n\n"; + } else { + $parsed .= (string) $block_text; + } // Start over with a new block. $block_text = ""; @@ -875,22 +904,22 @@ protected function _doAnchors_reference_callback($matches) { * @return string */ protected function _doAnchors_inline_callback($matches) { - $whole_match = $matches[1]; $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]; $attr = $this->doExtraAttributes("a", $dummy =& $matches[8]); // if the URL was of the form it got caught by the HTML // tag parser and hashed. Need to reverse the process before using the URL. $unhashed = $this->unhash($url); - if ($unhashed != $url) + if ($unhashed !== $url) $url = preg_replace('/^<(.*)>$/', '\1', $unhashed); $url = $this->encodeURLAttribute($url); $result = "encodeAttribute($title); $result .= " title=\"$title\""; } @@ -967,7 +996,7 @@ protected function _doImages_reference_callback($matches) { $alt_text = $matches[2]; $link_id = strtolower($matches[3]); - if ($link_id == "") { + if ($link_id === "") { $link_id = strtolower($alt_text); // for shortcut links like ![this][]. } @@ -980,8 +1009,9 @@ protected function _doImages_reference_callback($matches) { $title = $this->encodeAttribute($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->empty_element_suffix; $result = $this->hashPart($result); } @@ -999,16 +1029,16 @@ protected function _doImages_reference_callback($matches) { * @return string */ protected function _doImages_inline_callback($matches) { - $whole_match = $matches[1]; $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]; $attr = $this->doExtraAttributes("img", $dummy =& $matches[8]); $alt_text = $this->encodeAttribute($alt_text); $url = $this->encodeURLAttribute($url); $result = "\"$alt_text\"";encodeAttribute($title); $result .= " title=\"$title\""; // $title already quoted } @@ -1067,11 +1097,11 @@ protected function doHeaders($text) { * @return string */ protected function _doHeaders_callback_setext($matches) { - if ($matches[3] == '-' && preg_match('{^- }', $matches[1])) { + if ($matches[3] === '-' && preg_match('{^- }', $matches[1])) { 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; @@ -1174,8 +1204,7 @@ protected function _doTable_leadingPipe_callback($matches) { * @param string $alignname * @return string */ - protected function _doTable_makeAlignAttr($alignname) - { + protected function _doTable_makeAlignAttr($alignname) { if (empty($this->table_align_class_tmpl)) { return " align=\"$alignname\""; } @@ -1193,6 +1222,7 @@ protected function _doTable_callback($matches) { $head = $matches[1]; $underline = $matches[2]; $content = $matches[3]; + $attr = []; // Remove any tailing pipes for each line. $head = preg_replace('/[|] *$/m', '', $head); @@ -1223,8 +1253,9 @@ protected function _doTable_callback($matches) { $text = "\n"; $text .= "\n"; $text .= "\n"; - foreach ($headers as $n => $header) + foreach ($headers as $n => $header) { $text .= " " . $this->runSpanGamut(trim($header)) . "\n"; + } $text .= "\n"; $text .= "\n"; @@ -1242,8 +1273,9 @@ protected function _doTable_callback($matches) { $row_cells = array_pad($row_cells, $col_count, ''); $text .= "\n"; - foreach ($row_cells as $n => $cell) + foreach ($row_cells as $n => $cell) { $text .= " " . $this->runSpanGamut(trim($cell)) . "\n"; + } $text .= "\n"; } $text .= "\n"; @@ -1411,8 +1443,6 @@ protected function _processDefListItems_callback_dd($matches) { */ protected function doFencedCodeBlocks($text) { - $less_than_tab = $this->tab_width; - $text = preg_replace_callback('{ (?:\n|\A) # 1: Opening marker @@ -1465,9 +1495,10 @@ protected function _doFencedCodeBlocks_callback($matches) { array($this, '_doFencedCodeBlocks_newlines'), $codeblock); $classes = array(); - if ($classname != "") { - if ($classname{0} == '.') + if ($classname !== "") { + if ($classname[0] === '.') { $classname = substr($classname, 1); + } $classes[] = $this->code_class_prefix . $classname; } $attr_str = $this->doExtraAttributes($this->code_attr_on_pre ? "pre" : "code", $attrs, null, $classes); @@ -1493,17 +1524,17 @@ protected function _doFencedCodeBlocks_newlines($matches) { * work in the middle of a word. * @var array */ - protected $em_relist = array( + protected array $em_relist = array( '' => '(?:(? '(? '(? '(?:(? '(? '(? '(?:(? '(? '(?footnotes_ordered)) { - $text .= "\n\n"; - $text .= "
    \n"; - $text .= "empty_element_suffix . "\n"; - $text .= "
      \n\n"; - - $attr = ""; - if ($this->fn_backlink_class != "") { - $class = $this->fn_backlink_class; - $class = $this->encodeAttribute($class); - $attr .= " class=\"$class\""; + if ( ! empty( $this->footnotes_ordered ) ) { + $this->_doFootnotes(); + if ( ! $this->omit_footnotes ) { + $text .= "\n\n"; + $text .= "
      \n"; + $text .= "empty_element_suffix . "\n"; + $text .= $this->footnotes_assembled; + $text .= "
      "; } - if ($this->fn_backlink_title != "") { - $title = $this->fn_backlink_title; - $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"); - $footnote = preg_replace_callback('{F\x1Afn:(.*?)\x1A:}', - array($this, '_appendFootnotes_callback'), $footnote); - - $attr = str_replace("%%", ++$num, $attr); - $note_id = $this->encodeAttribute($note_id); - - // Prepare backlink, multiple backlinks if multiple references - $backlink = "$backlink_text"; - for ($ref_num = 2; $ref_num <= $ref_count; ++$ref_num) { - $backlink .= " $backlink_text"; + } + return $text; + } + + + /** + * Generates the HTML for footnotes. Called by appendFootnotes, even if + * footnotes are not being appended. + * @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; + + $text = "
        \n\n"; + 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"); + $footnote = preg_replace_callback('{F\x1Afn:(.*?)\x1A:}', + 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 .= " $backlink_text"; } - // 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('{

        $}', $footnote)) { $footnote = substr($footnote, 0, -4) . " $backlink

        "; } else { $footnote .= "\n\n

        $backlink

        "; } - - $text .= "
      1. \n"; - $text .= $footnote . "\n"; - $text .= "
      2. \n\n"; } - $text .= "
      \n"; - $text .= "
    "; + $text .= "
  • \n"; + $text .= $footnote . "\n"; + $text .= "
  • \n\n"; } - return $text; + $text .= "\n"; + + $this->footnotes_assembled = $text; } /** @@ -1693,12 +1754,12 @@ protected function _appendFootnotes_callback($matches) { } $attr = ""; - if ($this->fn_link_class != "") { + if ($this->fn_link_class !== "") { $class = $this->fn_link_class; $class = $this->encodeAttribute($class); $attr .= " class=\"$class\""; } - if ($this->fn_link_title != "") { + if ($this->fn_link_title !== "") { $title = $this->fn_link_title; $title = $this->encodeAttribute($title); $attr .= " title=\"$title\""; @@ -1717,6 +1778,23 @@ protected function _appendFootnotes_callback($matches) { 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 @@ -1783,12 +1861,10 @@ protected function _doAbbreviations_callback($matches) { $desc = $this->abbr_desciptions[$abbr]; if (empty($desc)) { return $this->hashPart("$abbr"); - } else { - $desc = $this->encodeAttribute($desc); - return $this->hashPart("$abbr"); } - } else { - return $matches[0]; + $desc = $this->encodeAttribute($desc); + return $this->hashPart("$abbr"); } + return $matches[0]; } } diff --git a/Michelf/MarkdownInterface.php b/Michelf/MarkdownInterface.php index 7fc0da0..32069df 100644 --- a/Michelf/MarkdownInterface.php +++ b/Michelf/MarkdownInterface.php @@ -4,7 +4,7 @@ * * @package php-markdown * @author Michel Fortin - * @copyright 2004-2018 Michel Fortin + * @copyright 2004-2021 Michel Fortin * @copyright (Original Markdown) 2004-2006 John Gruber */ diff --git a/index.php b/index.php index 2bb2258..e1313ac 100644 --- a/index.php +++ b/index.php @@ -151,7 +151,7 @@ function toHTML($inText) $dir = opendir(PAGES_PATH); while ( $filename = readdir($dir) ) { - if ( $filename{0} == '.' ) + if ( $filename[0] == '.' ) continue; $filename = preg_replace("/(.*?)\.txt/", "\\1", $filename); @@ -214,6 +214,14 @@ function file_put_contents($n, $d) } } } +// Support PHP 8.1 by setting two predefined variables to empty strings if +// not already defined. Fixes a bunch of deprecation warnings. + +if (!isset($_SERVER["PATH_INFO"])) + $_SERVER["PATH_INFO"] = ''; +if (!isset($_REQUEST['page'])) + $_REQUEST['page'] = ''; + // Main code @@ -389,7 +397,7 @@ function file_put_contents($n, $d) while ( $file = readdir($dir) ) { - if ( $file{0} == "." ) + if ( $file[0] == "." ) continue; $afile = preg_replace("/(.*?)\.txt/", "\\1", $file); @@ -424,7 +432,7 @@ function file_put_contents($n, $d) $filelist = array(); while ( $file = readdir($dir) ) { - if ( $file{0} == "." ) + if ( $file[0] == "." ) continue; $filelist[preg_replace("/(.*?)\.txt/", "\\1", $file)] = filemtime(PAGES_PATH . "/$file"); @@ -458,7 +466,7 @@ function file_put_contents($n, $d) while ( $file = readdir($dir) ) { - if ( $file{0} == "." ) + if ( $file[0] == "." ) continue; $text = file_get_contents(PAGES_PATH . "/$file");