From b95b1dff0195a41988ee8b5e16ee7eebb9d832d9 Mon Sep 17 00:00:00 2001 From: Saurabh Dixit Date: Mon, 23 Nov 2015 18:10:21 +0530 Subject: [PATCH] Squashed 'includes/libraries/aihrus-framework/' changes from 4570a11..c79cf54 c79cf54 Version 1.2.4RC1 5c54123 Version 1.2.4RC1 1a33ab9 set sslverify to falst in get_remote_get function d0e93e6 Removed disable_license_notice parameter from aihr_notice_license c62fb47 Version 1.2.3 7b2375a RESOLVE #8 Remove activation helpers 02b4924 Version 1.3.1RC3 4966091 Merge commit '87434dc139ada0d9da3e7a49795c0dd9057de29e' into C16280 87434dc Squashed 'includes/libraries/parsedown/' changes from 0e89e37..fa005fd b066dd8 Merge branch '1.2.3RC1' into C16280 f580146 RELATES #7 Blank license response check 34ede0f Update Parsedown 1.5.4 library fbd6396 Squashed 'includes/libraries/parsedown/' changes from a9dfc97..0e89e37 19b132f Merge commit 'fbd6396c905dd78601db21434bc457121b0058c2' into 1.2.3RC1 26fc0ea Modified aihr_notice_license() function to add disable license error notice 893f2ae RESOLVE michael-cannon/testimonials-widget#184 Remove deprecated constructor calls for WP_Widget b2922b5 Version 1.2.3RC1 fc59104 Squashed 'includes/libraries/parsedown/' changes from 93f7b26..a9dfc97 13885c6 Merge commit 'fc59104ce654a8877ccf12203410cfcf9fd96f28' into 1.2.2 git-subtree-dir: includes/libraries/aihrus-framework git-subtree-split: c79cf54d594a54defbc11a57b1906c8512e28ebc --- CHANGELOG.md | 7 + README.md | 4 +- aihrus-framework.php | 20 +- includes/class-aihrus-licensing.php | 4 +- includes/class-aihrus-widget.php | 2 +- includes/libraries/parsedown/.travis.yml | 10 +- includes/libraries/parsedown/Parsedown.php | 1004 ++++++++++------- includes/libraries/parsedown/README.md | 44 +- includes/libraries/parsedown/phpunit.xml.dist | 2 +- .../parsedown/test/CommonMarkTest.php | 74 ++ .../parsedown/test/ParsedownTest.php | 159 +++ includes/libraries/parsedown/test/Test.php | 65 -- .../parsedown/test/TestParsedown.php | 5 + .../parsedown/test/data/aligned_table.html | 18 +- .../parsedown/test/data/atx_heading.html | 2 +- .../parsedown/test/data/atx_heading.md | 2 +- .../parsedown/test/data/block-level_html.html | 17 +- .../parsedown/test/data/block-level_html.md | 17 +- .../parsedown/test/data/escaping.html | 4 +- .../libraries/parsedown/test/data/escaping.md | 6 +- .../{HTML_Comment.html => html_comment.html} | 0 .../data/{HTML_Comment.md => html_comment.md} | 0 .../parsedown/test/data/image_reference.html | 3 +- .../parsedown/test/data/image_reference.md | 2 + .../parsedown/test/data/image_title.html | 3 +- .../parsedown/test/data/image_title.md | 4 +- .../parsedown/test/data/inline_link.html | 8 +- .../parsedown/test/data/inline_link.md | 6 +- .../test/data/inline_link_title.html | 7 +- .../parsedown/test/data/inline_link_title.md | 12 +- .../parsedown/test/data/simple_table.html | 6 +- .../parsedown/test/data/sparse_html.html | 8 + .../parsedown/test/data/sparse_html.md | 8 + .../test/data/special_characters.html | 2 +- .../test/data/table_inline_markdown.html | 8 +- .../test/data/table_inline_markdown.md | 3 +- 36 files changed, 953 insertions(+), 593 deletions(-) create mode 100644 includes/libraries/parsedown/test/CommonMarkTest.php create mode 100644 includes/libraries/parsedown/test/ParsedownTest.php delete mode 100644 includes/libraries/parsedown/test/Test.php create mode 100644 includes/libraries/parsedown/test/TestParsedown.php rename includes/libraries/parsedown/test/data/{HTML_Comment.html => html_comment.html} (100%) rename includes/libraries/parsedown/test/data/{HTML_Comment.md => html_comment.md} (100%) create mode 100644 includes/libraries/parsedown/test/data/sparse_html.html create mode 100644 includes/libraries/parsedown/test/data/sparse_html.md diff --git a/CHANGELOG.md b/CHANGELOG.md index de58949..1f30ddd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ ## master +## 1.2.3 +* RELATES #7 Add option to disable the license not saved notice +* RESOLVE #8 Remove activation helpers +* RESOLVE michael-cannon/testimonials-widget#184 Remove deprecated constructor calls for WP_Widget +* Tested with WordPress 4.4 +* Update Parsedown 1.5.4 library + ## 1.2.2 * Add Inside Axelerant link * Store branding updates diff --git a/README.md b/README.md index c214b9b..cb8c193 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Aihrus Framework -Tested up to: 4.1.0 -Stable tag: 1.2.2 +Tested up to: 4.4.0 +Stable tag: 1.2.4RC1 A helper library for WordPress plugins by Aihrus and maintained by [Axelerant](//axelerant.com). diff --git a/aihrus-framework.php b/aihrus-framework.php index 3ed9484..dac7f13 100644 --- a/aihrus-framework.php +++ b/aihrus-framework.php @@ -35,7 +35,7 @@ } if ( ! defined( 'AIHR_VERSION' ) ) { - define( 'AIHR_VERSION', '1.2.2' ); + define( 'AIHR_VERSION', '1.2.4RC1' ); } require_once ABSPATH . 'wp-admin/includes/plugin.php'; @@ -232,22 +232,8 @@ function aihr_notice_updated( $text, $class = 'updated' ) { } if ( ! function_exists( 'aihr_notice_version' ) ) { - function aihr_notice_version( $required_base, $required_name, $required_slug, $required_version, $item_name ) { - $is_active = is_plugin_active( $required_base ); - if ( $is_active ) { - $link = sprintf( __( 'update to' ), self_admin_url( 'update-core.php' ) ); - } else { - $plugins = get_plugins(); - if ( empty( $plugins[ $required_base ] ) ) { - $install = esc_url( wp_nonce_url( self_admin_url( 'update.php?action=install-plugin&plugin=' . $required_slug ), 'install-plugin_' . $required_slug ) ); - $link = sprintf( __( 'install' ), $install ); - } else { - $activate = esc_url( wp_nonce_url( admin_url( 'plugins.php?action=activate&plugin=' . $required_base ), 'activate-plugin_' . $required_base ) ); - $link = sprintf( __( 'activate' ), $activate ); - } - } - - $text = sprintf( __( 'Plugin "%3$s" has been deactivated. Please %1$s "%4$s" version %2$s or newer before activating "%3$s".' ), $link, $required_version, $item_name, $required_name ); + function aihr_notice_version( $required_name, $required_version, $item_name ) { + $text = sprintf( __( 'Plugin "%2$s" has been deactivated. Please install and activate "%3$s" version %1$s or newer before activating "%2$s".' ), $required_version, $item_name, $required_name ); aihr_notice_error( $text ); } diff --git a/includes/class-aihrus-licensing.php b/includes/class-aihrus-licensing.php index 9c3d58d..02fedfb 100644 --- a/includes/class-aihrus-licensing.php +++ b/includes/class-aihrus-licensing.php @@ -133,7 +133,7 @@ public function get_api_call( $action ) { public function get_remote_get( $api_call ) { - $response = wp_remote_get( $api_call ); + $response = wp_remote_get( $api_call, array( 'sslverify' => false ) ); return $response; } @@ -158,7 +158,7 @@ public function activate_license() { public function get_license_data( $action = 'check_license' ) { $api_call = $this->get_api_call( $action ); $response = $this->get_remote_get( $api_call ); - if ( is_wp_error( $response ) ) { + if ( empty( $response) || is_wp_error( $response ) ) { return false; } diff --git a/includes/class-aihrus-widget.php b/includes/class-aihrus-widget.php index a898505..90f4658 100644 --- a/includes/class-aihrus-widget.php +++ b/includes/class-aihrus-widget.php @@ -52,7 +52,7 @@ public function __construct( $classname, $description, $id_base, $title ) { ); // Create the widget - $this->WP_Widget( + parent::__construct( static::ID, $title, $widget_ops, diff --git a/includes/libraries/parsedown/.travis.yml b/includes/libraries/parsedown/.travis.yml index dade257..5df49dc 100644 --- a/includes/libraries/parsedown/.travis.yml +++ b/includes/libraries/parsedown/.travis.yml @@ -1,10 +1,16 @@ language: php php: + - 7.0 - 5.6 - 5.5 - 5.4 - 5.3 - - 5.2 - hhvm - \ No newline at end of file + - hhvm-nightly + +matrix: + fast_finish: true + allow_failures: + - php: 7.0 + - php: hhvm-nightly diff --git a/includes/libraries/parsedown/Parsedown.php b/includes/libraries/parsedown/Parsedown.php index e9a8cbd..2be4056 100644 --- a/includes/libraries/parsedown/Parsedown.php +++ b/includes/libraries/parsedown/Parsedown.php @@ -15,28 +15,19 @@ class Parsedown { - # - # Philosophy + # ~ - # Parsedown recognises that the Markdown syntax is optimised for humans so - # it tries to read like one. It goes through text line by line. It looks at - # how lines start to identify blocks. It looks for special characters to - # identify inline elements. + const version = '1.5.4'; - # # ~ function text($text) { # make sure no definitions are set - $this->Definitions = array(); + $this->DefinitionData = array(); # standardize line breaks - $text = str_replace("\r\n", "\n", $text); - $text = str_replace("\r", "\n", $text); - - # replace tabs with spaces - $text = str_replace("\t", ' ', $text); + $text = str_replace(array("\r\n", "\r"), "\n", $text); # remove surrounding line breaks $text = trim($text, "\n"); @@ -57,8 +48,6 @@ function text($text) # Setters # - private $breaksEnabled; - function setBreaksEnabled($breaksEnabled) { $this->breaksEnabled = $breaksEnabled; @@ -66,15 +55,35 @@ function setBreaksEnabled($breaksEnabled) return $this; } + protected $breaksEnabled; + + function setMarkupEscaped($markupEscaped) + { + $this->markupEscaped = $markupEscaped; + + return $this; + } + + protected $markupEscaped; + + function setUrlsLinked($urlsLinked) + { + $this->urlsLinked = $urlsLinked; + + return $this; + } + + protected $urlsLinked = true; + # # Lines # protected $BlockTypes = array( - '#' => array('Atx'), + '#' => array('Header'), '*' => array('Rule', 'List'), '+' => array('List'), - '-' => array('Setext', 'Table', 'Rule', 'List'), + '-' => array('SetextHeader', 'Table', 'Rule', 'List'), '0' => array('List'), '1' => array('List'), '2' => array('List'), @@ -87,8 +96,9 @@ function setBreaksEnabled($breaksEnabled) '9' => array('List'), ':' => array('Table'), '<' => array('Comment', 'Markup'), - '=' => array('Setext'), + '=' => array('SetextHeader'), '>' => array('Quote'), + '[' => array('Reference'), '_' => array('Rule'), '`' => array('FencedCode'), '|' => array('Table'), @@ -97,14 +107,8 @@ function setBreaksEnabled($breaksEnabled) # ~ - protected $DefinitionTypes = array( - '[' => array('Reference'), - ); - - # ~ - protected $unmarkedBlockTypes = array( - 'CodeBlock', + 'Code', ); # @@ -127,6 +131,23 @@ private function lines(array $lines) continue; } + if (strpos($line, "\t") !== false) + { + $parts = explode("\t", $line); + + $line = $parts[0]; + + unset($parts[0]); + + foreach ($parts as $part) + { + $shortage = 4 - mb_strlen($line, 'utf-8') % 4; + + $line .= str_repeat(' ', $shortage); + $line .= $part; + } + } + $indent = 0; while (isset($line[$indent]) and $line[$indent] === ' ') @@ -142,9 +163,9 @@ private function lines(array $lines) # ~ - if (isset($CurrentBlock['incomplete'])) + if (isset($CurrentBlock['continuable'])) { - $Block = $this->{'addTo'.$CurrentBlock['type']}($Line, $CurrentBlock); + $Block = $this->{'block'.$CurrentBlock['type'].'Continue'}($Line, $CurrentBlock); if (isset($Block)) { @@ -154,12 +175,10 @@ private function lines(array $lines) } else { - if (method_exists($this, 'complete'.$CurrentBlock['type'])) + if (method_exists($this, 'block'.$CurrentBlock['type'].'Complete')) { - $CurrentBlock = $this->{'complete'.$CurrentBlock['type']}($CurrentBlock); + $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock); } - - unset($CurrentBlock['incomplete']); } } @@ -167,21 +186,6 @@ private function lines(array $lines) $marker = $text[0]; - if (isset($this->DefinitionTypes[$marker])) - { - foreach ($this->DefinitionTypes[$marker] as $definitionType) - { - $Definition = $this->{'identify'.$definitionType}($Line, $CurrentBlock); - - if (isset($Definition)) - { - $this->Definitions[$definitionType][$Definition['id']] = $Definition['data']; - - continue 2; - } - } - } - # ~ $blockTypes = $this->unmarkedBlockTypes; @@ -199,7 +203,7 @@ private function lines(array $lines) foreach ($blockTypes as $blockType) { - $Block = $this->{'identify'.$blockType}($Line, $CurrentBlock); + $Block = $this->{'block'.$blockType}($Line, $CurrentBlock); if (isset($Block)) { @@ -207,14 +211,14 @@ private function lines(array $lines) if ( ! isset($Block['identified'])) { - $Elements []= $CurrentBlock['element']; + $Blocks []= $CurrentBlock; $Block['identified'] = true; } - if (method_exists($this, 'addTo'.$blockType)) + if (method_exists($this, 'block'.$blockType.'Continue')) { - $Block['incomplete'] = true; + $Block['continuable'] = true; } $CurrentBlock = $Block; @@ -231,9 +235,9 @@ private function lines(array $lines) } else { - $Elements []= $CurrentBlock['element']; + $Blocks []= $CurrentBlock; - $CurrentBlock = $this->buildParagraph($Line); + $CurrentBlock = $this->paragraph($Line); $CurrentBlock['identified'] = true; } @@ -241,59 +245,49 @@ private function lines(array $lines) # ~ - if (isset($CurrentBlock['incomplete']) and method_exists($this, 'complete'.$CurrentBlock['type'])) + if (isset($CurrentBlock['continuable']) and method_exists($this, 'block'.$CurrentBlock['type'].'Complete')) { - $CurrentBlock = $this->{'complete'.$CurrentBlock['type']}($CurrentBlock); + $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock); } # ~ - $Elements []= $CurrentBlock['element']; - - unset($Elements[0]); + $Blocks []= $CurrentBlock; - # ~ - - $markup = $this->elements($Elements); + unset($Blocks[0]); # ~ - return $markup; - } - - # - # Atx + $markup = ''; - protected function identifyAtx($Line) - { - if (isset($Line['text'][1])) + foreach ($Blocks as $Block) { - $level = 1; - - while (isset($Line['text'][$level]) and $Line['text'][$level] === '#') + if (isset($Block['hidden'])) { - $level ++; + continue; } - $text = trim($Line['text'], '# '); + $markup .= "\n"; + $markup .= isset($Block['markup']) ? $Block['markup'] : $this->element($Block['element']); + } - $Block = array( - 'element' => array( - 'name' => 'h' . min(6, $level), - 'text' => $text, - 'handler' => 'line', - ), - ); + $markup .= "\n"; - return $Block; - } + # ~ + + return $markup; } # # Code - protected function identifyCodeBlock($Line) + protected function blockCode($Line, $Block = null) { + if (isset($Block) and ! isset($Block['type']) and ! isset($Block['interrupted'])) + { + return; + } + if ($Line['indent'] >= 4) { $text = substr($Line['body'], 4); @@ -313,7 +307,7 @@ protected function identifyCodeBlock($Line) } } - protected function addToCodeBlock($Line, $Block) + protected function blockCodeContinue($Line, $Block) { if ($Line['indent'] >= 4) { @@ -334,7 +328,7 @@ protected function addToCodeBlock($Line, $Block) } } - protected function completeCodeBlock($Block) + protected function blockCodeComplete($Block) { $text = $Block['element']['text']['text']; @@ -348,12 +342,17 @@ protected function completeCodeBlock($Block) # # Comment - protected function identifyComment($Line) + protected function blockComment($Line) { + if ($this->markupEscaped) + { + return; + } + if (isset($Line['text'][3]) and $Line['text'][3] === '-' and $Line['text'][2] === '-' and $Line['text'][1] === '!') { $Block = array( - 'element' => $Line['body'], + 'markup' => $Line['body'], ); if (preg_match('/-->$/', $Line['text'])) @@ -365,14 +364,14 @@ protected function identifyComment($Line) } } - protected function addToComment($Line, array $Block) + protected function blockCommentContinue($Line, array $Block) { if (isset($Block['closed'])) { return; } - $Block['element'] .= "\n" . $Line['body']; + $Block['markup'] .= "\n" . $Line['body']; if (preg_match('/-->$/', $Line['text'])) { @@ -385,18 +384,18 @@ protected function addToComment($Line, array $Block) # # Fenced Code - protected function identifyFencedCode($Line) + protected function blockFencedCode($Line) { - if (preg_match('/^(['.$Line['text'][0].']{3,})[ ]*([\w-]+)?[ ]*$/', $Line['text'], $matches)) + if (preg_match('/^['.$Line['text'][0].']{3,}[ ]*([\w-]+)?[ ]*$/', $Line['text'], $matches)) { $Element = array( 'name' => 'code', 'text' => '', ); - if (isset($matches[2])) + if (isset($matches[1])) { - $class = 'language-'.$matches[2]; + $class = 'language-'.$matches[1]; $Element['attributes'] = array( 'class' => $class, @@ -416,7 +415,7 @@ protected function identifyFencedCode($Line) } } - protected function addToFencedCode($Line, $Block) + protected function blockFencedCodeContinue($Line, $Block) { if (isset($Block['complete'])) { @@ -444,7 +443,7 @@ protected function addToFencedCode($Line, $Block) return $Block; } - protected function completeFencedCode($Block) + protected function blockFencedCodeComplete($Block) { $text = $Block['element']['text']['text']; @@ -455,10 +454,43 @@ protected function completeFencedCode($Block) return $Block; } + # + # Header + + protected function blockHeader($Line) + { + if (isset($Line['text'][1])) + { + $level = 1; + + while (isset($Line['text'][$level]) and $Line['text'][$level] === '#') + { + $level ++; + } + + if ($level > 6) + { + return; + } + + $text = trim($Line['text'], '# '); + + $Block = array( + 'element' => array( + 'name' => 'h' . min(6, $level), + 'text' => $text, + 'handler' => 'line', + ), + ); + + return $Block; + } + } + # # List - protected function identifyList($Line) + protected function blockList($Line) { list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]+[.]'); @@ -487,9 +519,9 @@ protected function identifyList($Line) } } - protected function addToList($Line, array $Block) + protected function blockListContinue($Line, array $Block) { - if ($Block['indent'] === $Line['indent'] and preg_match('/^'.$Block['pattern'].'[ ]+(.*)/', $Line['text'], $matches)) + if ($Block['indent'] === $Line['indent'] and preg_match('/^'.$Block['pattern'].'(?:[ ]+(.*)|$)/', $Line['text'], $matches)) { if (isset($Block['interrupted'])) { @@ -500,11 +532,13 @@ protected function addToList($Line, array $Block) unset($Block['li']); + $text = isset($matches[1]) ? $matches[1] : ''; + $Block['li'] = array( 'name' => 'li', 'handler' => 'li', 'text' => array( - $matches[1], + $text, ), ); @@ -513,6 +547,11 @@ protected function addToList($Line, array $Block) return $Block; } + if ($Line['text'][0] === '[' and $this->blockReference($Line)) + { + return $Block; + } + if ( ! isset($Block['interrupted'])) { $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']); @@ -539,7 +578,7 @@ protected function addToList($Line, array $Block) # # Quote - protected function identifyQuote($Line) + protected function blockQuote($Line) { if (preg_match('/^>[ ]?(.*)/', $Line['text'], $matches)) { @@ -555,7 +594,7 @@ protected function identifyQuote($Line) } } - protected function addToQuote($Line, array $Block) + protected function blockQuoteContinue($Line, array $Block) { if ($Line['text'][0] === '>' and preg_match('/^>[ ]?(.*)/', $Line['text'], $matches)) { @@ -582,9 +621,9 @@ protected function addToQuote($Line, array $Block) # # Rule - protected function identifyRule($Line) + protected function blockRule($Line) { - if (preg_match('/^(['.$Line['text'][0].'])([ ]{0,2}\1){2,}[ ]*$/', $Line['text'])) + if (preg_match('/^(['.$Line['text'][0].'])([ ]*\1){2,}[ ]*$/', $Line['text'])) { $Block = array( 'element' => array( @@ -599,7 +638,7 @@ protected function identifyRule($Line) # # Setext - protected function identifySetext($Line, array $Block = null) + protected function blockSetextHeader($Line, array $Block = null) { if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted'])) { @@ -617,46 +656,71 @@ protected function identifySetext($Line, array $Block = null) # # Markup - protected function identifyMarkup($Line) + protected function blockMarkup($Line) { - if (preg_match('/^<(\w[\w\d]*)(?:[ ][^>]*)?(\/?)[ ]*>/', $Line['text'], $matches)) + if ($this->markupEscaped) + { + return; + } + + if (preg_match('/^<(\w*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches)) { - if (in_array($matches[1], $this->textLevelElements)) + $element = strtolower($matches[1]); + + if (in_array($element, $this->textLevelElements)) { return; } $Block = array( - 'element' => $Line['body'], + 'name' => $matches[1], + 'depth' => 0, + 'markup' => $Line['text'], ); - if ($matches[2] or $matches[1] === 'hr' or preg_match('/<\/'.$matches[1].'>[ ]*$/', $Line['text'])) + $length = strlen($matches[0]); + + $remainder = substr($Line['text'], $length); + + if (trim($remainder) === '') { - $Block['closed'] = true; + if (isset($matches[2]) or in_array($matches[1], $this->voidElements)) + { + $Block['closed'] = true; + + $Block['void'] = true; + } } else { - $Block['depth'] = 0; - $Block['name'] = $matches[1]; + if (isset($matches[2]) or in_array($matches[1], $this->voidElements)) + { + return; + } + + if (preg_match('/<\/'.$matches[1].'>[ ]*$/i', $remainder)) + { + $Block['closed'] = true; + } } return $Block; } } - protected function addToMarkup($Line, array $Block) + protected function blockMarkupContinue($Line, array $Block) { if (isset($Block['closed'])) { return; } - if (preg_match('/<'.$Block['name'].'([ ][^\/]+)?>/', $Line['text'])) # opening tag + if (preg_match('/^<'.$Block['name'].'(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*>/i', $Line['text'])) # open { $Block['depth'] ++; } - if (stripos($Line['text'], '') !== false) # closing tag + if (preg_match('/(.*?)<\/'.$Block['name'].'>[ ]*$/i', $Line['text'], $matches)) # close { if ($Block['depth'] > 0) { @@ -668,15 +732,51 @@ protected function addToMarkup($Line, array $Block) } } - $Block['element'] .= "\n".$Line['body']; + if (isset($Block['interrupted'])) + { + $Block['markup'] .= "\n"; + + unset($Block['interrupted']); + } + + $Block['markup'] .= "\n".$Line['body']; return $Block; } + # + # Reference + + protected function blockReference($Line) + { + if (preg_match('/^\[(.+?)\]:[ ]*?(?:[ ]+["\'(](.+)["\')])?[ ]*$/', $Line['text'], $matches)) + { + $id = strtolower($matches[1]); + + $Data = array( + 'url' => $matches[2], + 'title' => null, + ); + + if (isset($matches[3])) + { + $Data['title'] = $matches[3]; + } + + $this->DefinitionData['Reference'][$id] = $Data; + + $Block = array( + 'hidden' => true, + ); + + return $Block; + } + } + # # Table - protected function identifyTable($Line, array $Block = null) + protected function blockTable($Line, array $Block = null) { if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted'])) { @@ -710,7 +810,7 @@ protected function identifyTable($Line, array $Block = null) $alignment = 'left'; } - if (substr($dividerCell, -1) === ':') + if (substr($dividerCell, - 1) === ':') { $alignment = $alignment === 'left' ? 'center' : 'right'; } @@ -744,7 +844,7 @@ protected function identifyTable($Line, array $Block = null) $alignment = $alignments[$index]; $HeaderElement['attributes'] = array( - 'align' => $alignment, + 'style' => 'text-align: '.$alignment.';', ); } @@ -783,8 +883,13 @@ protected function identifyTable($Line, array $Block = null) } } - protected function addToTable($Line, array $Block) + protected function blockTableContinue($Line, array $Block) { + if (isset($Block['interrupted'])) + { + return; + } + if ($Line['text'][0] === '|' or strpos($Line['text'], '|')) { $Elements = array(); @@ -794,9 +899,9 @@ protected function addToTable($Line, array $Block) $row = trim($row); $row = trim($row, '|'); - $cells = explode('|', $row); + preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]+`|`)+/', $row, $matches); - foreach ($cells as $index => $cell) + foreach ($matches[0] as $index => $cell) { $cell = trim($cell); @@ -809,7 +914,7 @@ protected function addToTable($Line, array $Block) if (isset($Block['alignments'][$index])) { $Element['attributes'] = array( - 'align' => $Block['alignments'][$index], + 'style' => 'text-align: '.$Block['alignments'][$index].';', ); } @@ -828,35 +933,11 @@ protected function addToTable($Line, array $Block) } } - # - # Definitions - # - - protected function identifyReference($Line) - { - if (preg_match('/^\[(.+?)\]:[ ]*?(?:[ ]+["\'(](.+)["\')])?[ ]*$/', $Line['text'], $matches)) - { - $Definition = array( - 'id' => strtolower($matches[1]), - 'data' => array( - 'url' => $matches[2], - ), - ); - - if (isset($matches[3])) - { - $Definition['data']['title'] = $matches[3]; - } - - return $Definition; - } - } - # # ~ # - protected function buildParagraph($Line) + protected function paragraph($Line) { $Block = array( 'element' => array( @@ -870,92 +951,27 @@ protected function buildParagraph($Line) } # - # ~ - # - - protected function element(array $Element) - { - $markup = '<'.$Element['name']; - - if (isset($Element['attributes'])) - { - foreach ($Element['attributes'] as $name => $value) - { - $markup .= ' '.$name.'="'.$value.'"'; - } - } - - if (isset($Element['text'])) - { - $markup .= '>'; - - if (isset($Element['handler'])) - { - $markup .= $this->$Element['handler']($Element['text']); - } - else - { - $markup .= $Element['text']; - } - - $markup .= ''; - } - else - { - $markup .= ' />'; - } - - return $markup; - } - - protected function elements(array $Elements) - { - $markup = ''; - - foreach ($Elements as $Element) - { - if ($Element === null) - { - continue; - } - - $markup .= "\n"; - - if (is_string($Element)) # because of Markup - { - $markup .= $Element; - - continue; - } - - $markup .= $this->element($Element); - } - - $markup .= "\n"; - - return $markup; - } - - # - # Spans + # Inline Elements # - protected $SpanTypes = array( - '!' => array('Link'), # ? - '&' => array('Ampersand'), + protected $InlineTypes = array( + '"' => array('SpecialCharacter'), + '!' => array('Image'), + '&' => array('SpecialCharacter'), '*' => array('Emphasis'), - '/' => array('Url'), - '<' => array('UrlTag', 'EmailTag', 'Tag', 'LessThan'), + ':' => array('Url'), + '<' => array('UrlTag', 'EmailTag', 'Markup', 'SpecialCharacter'), + '>' => array('SpecialCharacter'), '[' => array('Link'), '_' => array('Emphasis'), - '`' => array('InlineCode'), + '`' => array('Code'), '~' => array('Strikethrough'), '\\' => array('EscapeSequence'), ); # ~ - protected $spanMarkerList = '*_!&[`~\\'; # # ~ @@ -965,64 +981,64 @@ public function line($text) { $markup = ''; - $remainder = $text; + # $excerpt is based on the first occurrence of a marker - $markerPosition = 0; - - while ($excerpt = strpbrk($remainder, $this->spanMarkerList)) + while ($excerpt = strpbrk($text, $this->inlineMarkerList)) { $marker = $excerpt[0]; - $markerPosition += strpos($remainder, $marker); + $markerPosition = strpos($text, $marker); $Excerpt = array('text' => $excerpt, 'context' => $text); - foreach ($this->SpanTypes[$marker] as $spanType) + foreach ($this->InlineTypes[$marker] as $inlineType) { - $handler = 'identify'.$spanType; - - $Span = $this->$handler($Excerpt); + $Inline = $this->{'inline'.$inlineType}($Excerpt); - if ( ! isset($Span)) + if ( ! isset($Inline)) { continue; } - # The identified span can be ahead of the marker. + # makes sure that the inline belongs to "our" marker - if (isset($Span['position']) and $Span['position'] > $markerPosition) + if (isset($Inline['position']) and $Inline['position'] > $markerPosition) { continue; } - # Spans that start at the position of their marker don't have to set a position. + # sets a default inline position - if ( ! isset($Span['position'])) + if ( ! isset($Inline['position'])) { - $Span['position'] = $markerPosition; + $Inline['position'] = $markerPosition; } - $plainText = substr($text, 0, $Span['position']); - - $markup .= $this->readPlainText($plainText); - - $markup .= isset($Span['markup']) ? $Span['markup'] : $this->element($Span['element']); + # the text that comes before the inline + $unmarkedText = substr($text, 0, $Inline['position']); - $text = substr($text, $Span['position'] + $Span['extent']); + # compile the unmarked text + $markup .= $this->unmarkedText($unmarkedText); - $remainder = $text; + # compile the inline + $markup .= isset($Inline['markup']) ? $Inline['markup'] : $this->element($Inline['element']); - $markerPosition = 0; + # remove the examined text + $text = substr($text, $Inline['position'] + $Inline['extent']); continue 2; } - $remainder = substr($excerpt, 1); + # the marker does not belong to an inline - $markerPosition ++; + $unmarkedText = substr($text, 0, $markerPosition + 1); + + $markup .= $this->unmarkedText($unmarkedText); + + $text = substr($text, $markerPosition + 1); } - $markup .= $this->readPlainText($text); + $markup .= $this->unmarkedText($text); return $markup; } @@ -1031,63 +1047,83 @@ public function line($text) # ~ # - protected function identifyUrl($Excerpt) + protected function inlineCode($Excerpt) { - if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '/') - { - return; - } + $marker = $Excerpt['text'][0]; - if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE)) + if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(? strlen($matches[0][0]), - 'position' => $matches[0][1], + 'extent' => strlen($matches[0]), 'element' => array( - 'name' => 'a', - 'text' => $url, - 'attributes' => array( - 'href' => $url, - ), + 'name' => 'code', + 'text' => $text, ), ); } } - protected function identifyAmpersand($Excerpt) + protected function inlineEmailTag($Excerpt) { - if ( ! preg_match('/^&#?\w+;/', $Excerpt['text'])) + if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<((mailto:)?\S+?@\S+?)>/i', $Excerpt['text'], $matches)) { + $url = $matches[1]; + + if ( ! isset($matches[2])) + { + $url = 'mailto:' . $url; + } + return array( - 'markup' => '&', - 'extent' => 1, + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => 'a', + 'text' => $matches[1], + 'attributes' => array( + 'href' => $url, + ), + ), ); } } - protected function identifyStrikethrough($Excerpt) + protected function inlineEmphasis($Excerpt) { if ( ! isset($Excerpt['text'][1])) { return; } - if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches)) + $marker = $Excerpt['text'][0]; + + if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches)) { - return array( - 'extent' => strlen($matches[0]), - 'element' => array( - 'name' => 'del', - 'text' => $matches[1], - 'handler' => 'line', - ), - ); + $emphasis = 'strong'; + } + elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches)) + { + $emphasis = 'em'; + } + else + { + return; } + + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => $emphasis, + 'handler' => 'line', + 'text' => $matches[1], + ), + ); } - protected function identifyEscapeSequence($Excerpt) + protected function inlineEscapeSequence($Excerpt) { if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters)) { @@ -1098,53 +1134,137 @@ protected function identifyEscapeSequence($Excerpt) } } - protected function identifyLessThan() + protected function inlineImage($Excerpt) + { + if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[') + { + return; + } + + $Excerpt['text']= substr($Excerpt['text'], 1); + + $Link = $this->inlineLink($Excerpt); + + if ($Link === null) + { + return; + } + + $Inline = array( + 'extent' => $Link['extent'] + 1, + 'element' => array( + 'name' => 'img', + 'attributes' => array( + 'src' => $Link['element']['attributes']['href'], + 'alt' => $Link['element']['text'], + ), + ), + ); + + $Inline['element']['attributes'] += $Link['element']['attributes']; + + unset($Inline['element']['attributes']['href']); + + return $Inline; + } + + protected function inlineLink($Excerpt) { + $Element = array( + 'name' => 'a', + 'handler' => 'line', + 'text' => null, + 'attributes' => array( + 'href' => null, + 'title' => null, + ), + ); + + $extent = 0; + + $remainder = $Excerpt['text']; + + if (preg_match('/\[((?:[^][]|(?R))*)\]/', $remainder, $matches)) + { + $Element['text'] = $matches[1]; + + $extent += strlen($matches[0]); + + $remainder = substr($remainder, $extent); + } + else + { + return; + } + + if (preg_match('/^[(]((?:[^ ()]|[(][^ )]+[)])+)(?:[ ]+("[^"]*"|\'[^\']*\'))?[)]/', $remainder, $matches)) + { + $Element['attributes']['href'] = $matches[1]; + + if (isset($matches[2])) + { + $Element['attributes']['title'] = substr($matches[2], 1, - 1); + } + + $extent += strlen($matches[0]); + } + else + { + if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches)) + { + $definition = strlen($matches[1]) ? $matches[1] : $Element['text']; + $definition = strtolower($definition); + + $extent += strlen($matches[0]); + } + else + { + $definition = strtolower($Element['text']); + } + + if ( ! isset($this->DefinitionData['Reference'][$definition])) + { + return; + } + + $Definition = $this->DefinitionData['Reference'][$definition]; + + $Element['attributes']['href'] = $Definition['url']; + $Element['attributes']['title'] = $Definition['title']; + } + + $Element['attributes']['href'] = str_replace(array('&', '<'), array('&', '<'), $Element['attributes']['href']); + return array( - 'markup' => '<', - 'extent' => 1, + 'extent' => $extent, + 'element' => $Element, ); } - protected function identifyUrlTag($Excerpt) + protected function inlineMarkup($Excerpt) { - if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(https?:[\/]{2}[^\s]+?)>/i', $Excerpt['text'], $matches)) + if ($this->markupEscaped or strpos($Excerpt['text'], '>') === false) { - $url = str_replace(array('&', '<'), array('&', '<'), $matches[1]); + return; + } + if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w*[ ]*>/s', $Excerpt['text'], $matches)) + { return array( + 'markup' => $matches[0], 'extent' => strlen($matches[0]), - 'element' => array( - 'name' => 'a', - 'text' => $url, - 'attributes' => array( - 'href' => $url, - ), - ), ); } - } - protected function identifyEmailTag($Excerpt) - { - if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\S+?@\S+?)>/', $Excerpt['text'], $matches)) + if ($Excerpt['text'][1] === '!' and preg_match('/^/s', $Excerpt['text'], $matches)) { return array( + 'markup' => $matches[0], 'extent' => strlen($matches[0]), - 'element' => array( - 'name' => 'a', - 'text' => $matches[1], - 'attributes' => array( - 'href' => 'mailto:'.$matches[1], - ), - ), ); } - } - protected function identifyTag($Excerpt) - { - if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<\/?\w.*?>/', $Excerpt['text'], $matches)) + if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w*(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*\/?>/s', $Excerpt['text'], $matches)) { return array( 'markup' => $matches[0], @@ -1153,164 +1273,167 @@ protected function identifyTag($Excerpt) } } - protected function identifyInlineCode($Excerpt) + protected function inlineSpecialCharacter($Excerpt) { - $marker = $Excerpt['text'][0]; + if ($Excerpt['text'][0] === '&' and ! preg_match('/^&#?\w+;/', $Excerpt['text'])) + { + return array( + 'markup' => '&', + 'extent' => 1, + ); + } + + $SpecialCharacter = array('>' => 'gt', '<' => 'lt', '"' => 'quot'); - if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(? '&'.$SpecialCharacter[$Excerpt['text'][0]].';', + 'extent' => 1, + ); + } + } + + protected function inlineStrikethrough($Excerpt) + { + if ( ! isset($Excerpt['text'][1])) + { + return; + } + if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches)) + { return array( 'extent' => strlen($matches[0]), 'element' => array( - 'name' => 'code', - 'text' => $text, + 'name' => 'del', + 'text' => $matches[1], + 'handler' => 'line', ), ); } } - protected function identifyLink($Excerpt) + protected function inlineUrl($Excerpt) { - $extent = $Excerpt['text'][0] === '!' ? 1 : 0; - - if (strpos($Excerpt['text'], ']') and preg_match('/\[((?:[^][]|(?R))*)\]/', $Excerpt['text'], $matches)) - { - $Link = array('text' => $matches[1], 'label' => strtolower($matches[1])); - - $extent += strlen($matches[0]); - - $substring = substr($Excerpt['text'], $extent); - - if (preg_match('/^\s*\[([^][]+)\]/', $substring, $matches)) - { - $Link['label'] = strtolower($matches[1]); - - if (isset($this->Definitions['Reference'][$Link['label']])) - { - $Link += $this->Definitions['Reference'][$Link['label']]; - - $extent += strlen($matches[0]); - } - else - { - return; - } - } - elseif (isset($this->Definitions['Reference'][$Link['label']])) - { - $Link += $this->Definitions['Reference'][$Link['label']]; - - if (preg_match('/^[ ]*\[\]/', $substring, $matches)) - { - $extent += strlen($matches[0]); - } - } - elseif (preg_match('/^\([ ]*(.*?)(?:[ ]+[\'"](.+?)[\'"])?[ ]*\)/', $substring, $matches)) - { - $Link['url'] = $matches[1]; - - if (isset($matches[2])) - { - $Link['title'] = $matches[2]; - } - - $extent += strlen($matches[0]); - } - else - { - return; - } - } - else + if ($this->urlsLinked !== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/') { return; } - $url = str_replace(array('&', '<'), array('&', '<'), $Link['url']); - - if ($Excerpt['text'][0] === '!') + if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE)) { - $Element = array( - 'name' => 'img', - 'attributes' => array( - 'alt' => $Link['text'], - 'src' => $url, + $Inline = array( + 'extent' => strlen($matches[0][0]), + 'position' => $matches[0][1], + 'element' => array( + 'name' => 'a', + 'text' => $matches[0][0], + 'attributes' => array( + 'href' => $matches[0][0], + ), ), ); + + return $Inline; } - else + } + + protected function inlineUrlTag($Excerpt) + { + if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $Excerpt['text'], $matches)) { - $Element = array( - 'name' => 'a', - 'handler' => 'line', - 'text' => $Link['text'], - 'attributes' => array( - 'href' => $url, + $url = str_replace(array('&', '<'), array('&', '<'), $matches[1]); + + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => 'a', + 'text' => $url, + 'attributes' => array( + 'href' => $url, + ), ), ); } + } - if (isset($Link['title'])) + # ~ + + protected function unmarkedText($text) + { + if ($this->breaksEnabled) { - $Element['attributes']['title'] = $Link['title']; + $text = preg_replace('/[ ]*\n/', "
\n", $text); + } + else + { + $text = preg_replace('/(?:[ ][ ]+|[ ]*\\\\)\n/', "
\n", $text); + $text = str_replace(" \n", "\n", $text); } - return array( - 'extent' => $extent, - 'element' => $Element, - ); + return $text; } - protected function identifyEmphasis($Excerpt) - { - if ( ! isset($Excerpt['text'][1])) - { - return; - } + # + # Handlers + # - $marker = $Excerpt['text'][0]; + protected function element(array $Element) + { + $markup = '<'.$Element['name']; - if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches)) + if (isset($Element['attributes'])) { - $emphasis = 'strong'; + foreach ($Element['attributes'] as $name => $value) + { + if ($value === null) + { + continue; + } + + $markup .= ' '.$name.'="'.$value.'"'; + } } - elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches)) + + if (isset($Element['text'])) { - $emphasis = 'em'; + $markup .= '>'; + + if (isset($Element['handler'])) + { + $markup .= $this->{$Element['handler']}($Element['text']); + } + else + { + $markup .= $Element['text']; + } + + $markup .= ''; } else { - return; + $markup .= ' />'; } - return array( - 'extent' => strlen($matches[0]), - 'element' => array( - 'name' => $emphasis, - 'handler' => 'line', - 'text' => $matches[1], - ), - ); + return $markup; } - # - # ~ - - protected function readPlainText($text) + protected function elements(array $Elements) { - $breakMarker = $this->breaksEnabled ? "\n" : " \n"; + $markup = ''; - $text = str_replace($breakMarker, "
\n", $text); + foreach ($Elements as $Element) + { + $markup .= "\n" . $this->element($Element); + } - return $text; + $markup .= "\n"; + + return $markup; } - # # ~ - # protected function li($lines) { @@ -1332,7 +1455,18 @@ protected function li($lines) } # - # Multiton + # Deprecated Methods + # + + function parse($text) + { + $markup = $this->text($text); + + return $markup; + } + + # + # Static Methods # static function instance($name = 'default') @@ -1342,7 +1476,7 @@ static function instance($name = 'default') return self::$instances[$name]; } - $instance = new self(); + $instance = new static(); self::$instances[$name] = $instance; @@ -1351,41 +1485,33 @@ static function instance($name = 'default') private static $instances = array(); - # - # Deprecated Methods - # - - /** - * @deprecated in favor of "text" - */ - function parse($text) - { - $markup = $this->text($text); - - return $markup; - } - # # Fields # - protected $Definitions; + protected $DefinitionData; # - # Read-only + # Read-Only protected $specialCharacters = array( - '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', + '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|', ); protected $StrongRegex = array( - '*' => '/^[*]{2}((?:[^*]|[*][^*]*[*])+?)[*]{2}(?![*])/s', - '_' => '/^__((?:[^_]|_[^_]*_)+?)__(?!_)/us', + '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*[*])+?)[*]{2}(?![*])/s', + '_' => '/^__((?:\\\\_|[^_]|_[^_]*_)+?)__(?!_)/us', ); protected $EmRegex = array( - '*' => '/^[*]((?:[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s', - '_' => '/^_((?:[^_]|__[^_]*__)+?)_(?!_)\b/us', + '*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s', + '_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us', + ); + + protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*(?:\s*=\s*(?:[^"\'=<>`\s]+|"[^"]*"|\'[^\']*\'))?'; + + protected $voidElements = array( + 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', ); protected $textLevelElements = array( diff --git a/includes/libraries/parsedown/README.md b/includes/libraries/parsedown/README.md index 40c9065..0498523 100644 --- a/includes/libraries/parsedown/README.md +++ b/includes/libraries/parsedown/README.md @@ -1,18 +1,22 @@ ## Parsedown -Better [Markdown](http://en.wikipedia.org/wiki/Markdown) parser for PHP. +[![Build Status](https://img.shields.io/travis/erusev/parsedown/master.svg?style=flat-square)](https://travis-ci.org/erusev/parsedown) + -[[ demo ]](http://parsedown.org/demo) +Better Markdown Parser in PHP + +[Demo](http://parsedown.org/demo) | +[Benchmarks](http://parsedown.org/speed) | +[Tests](http://parsedown.org/tests/) | +[Documentation](https://github.com/erusev/parsedown/wiki/) ### Features -* [Fast](http://parsedown.org/speed) -* [Consistent](http://parsedown.org/consistency) -* [GitHub Flavored](https://help.github.com/articles/github-flavored-markdown) -* [Tested](http://parsedown.org/tests/) in PHP 5.2, 5.3, 5.4, 5.5, 5.6 and [hhvm](http://www.hhvm.com/) +* Super Fast +* [GitHub flavored](https://help.github.com/articles/github-flavored-markdown) * Extensible -* [Markdown Extra extension](https://github.com/erusev/parsedown-extra) new -* [JavaScript port](https://github.com/hkdobrev/parsedown.js) under development new +* Tested in 5.3 to 5.6 +* [Markdown Extra extension](https://github.com/erusev/parsedown-extra) ### Installation @@ -26,18 +30,24 @@ $Parsedown = new Parsedown(); echo $Parsedown->text('Hello _Parsedown_!'); # prints:

Hello Parsedown!

``` -More examples in [the wiki](https://github.com/erusev/parsedown/wiki/Usage) and in [this video tutorial](http://youtu.be/wYZBY8DEikI). +More examples in [the wiki](https://github.com/erusev/parsedown/wiki/) and in [this video tutorial](http://youtu.be/wYZBY8DEikI). ### Questions -**How does Parsedown work?**
-Parsedown recognises that the Markdown syntax is optimised for humans so it tries to read like one. It goes through text line by line. It looks at how lines start to identify blocks. It looks for special characters to identify inline elements. +**How does Parsedown work?** + +It tries to read Markdown like a human. First, it looks at the lines. It’s interested in how the lines start. This helps it recognise blocks. It knows, for example, that if a line start with a `-` then it perhaps belong to a list. Once it recognises the blocks, it continues to the content. As it reads, it watches out for special characters. This helps it recognise inline elements (or inlines). + +We call this approach "line based". We believe that Parsedown is the first Markdown parser to use it. Since the release of Parsedown, other developers have used the same approach to develop other Markdown parsers in PHP and in other languages. + +**Is it compliant with CommonMark?** + +It passes most of the CommonMark tests. Most of the tests that don't pass deal with cases that are quite uncommon. Still, as CommonMark matures, compliance should improve. + +**Who uses it?** -**Why doesn’t Parsedown use namespaces?**
-Using namespaces would mean dropping support for PHP 5.2. We believe that since Parsedown is a single class with an uncommon name, making this trade wouldn't be worth it. +[phpDocumentor](http://www.phpdoc.org/), [October CMS](http://octobercms.com/), [Bolt CMS](http://bolt.cm/), [Kirby CMS](http://getkirby.com/), [Grav CMS](http://getgrav.org/), [Statamic CMS](http://www.statamic.com/), [RaspberryPi.org](http://www.raspberrypi.org/) and [more](https://www.versioneye.com/php/erusev:parsedown/references). -**Is Parsedown compliant with CommonMark?**
-We are [working on it](https://github.com/erusev/parsedown/tree/commonmark). +**How can I help?** -**Who uses Parsedown?**
-[phpDocumentor](http://www.phpdoc.org/), [Bolt CMS](http://bolt.cm/), [RaspberryPi.org](http://www.raspberrypi.org/) and [more](https://www.versioneye.com/php/erusev:parsedown/references). +Use it, star it, share it and if you feel generous, [donate](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=528P3NZQMP8N2). diff --git a/includes/libraries/parsedown/phpunit.xml.dist b/includes/libraries/parsedown/phpunit.xml.dist index 875167a..b2d5e9d 100644 --- a/includes/libraries/parsedown/phpunit.xml.dist +++ b/includes/libraries/parsedown/phpunit.xml.dist @@ -2,7 +2,7 @@ - test/Test.php + test/ParsedownTest.php \ No newline at end of file diff --git a/includes/libraries/parsedown/test/CommonMarkTest.php b/includes/libraries/parsedown/test/CommonMarkTest.php new file mode 100644 index 0000000..9b8d116 --- /dev/null +++ b/includes/libraries/parsedown/test/CommonMarkTest.php @@ -0,0 +1,74 @@ +setUrlsLinked(false); + + $actualHtml = $Parsedown->text($markdown); + $actualHtml = $this->normalizeMarkup($actualHtml); + + $this->assertEquals($expectedHtml, $actualHtml); + } + + function data() + { + $spec = file_get_contents(self::SPEC_URL); + $spec = strstr($spec, '', true); + + $tests = array(); + $currentSection = ''; + + preg_replace_callback( + '/^\.\n([\s\S]*?)^\.\n([\s\S]*?)^\.$|^#{1,6} *(.*)$/m', + function($matches) use ( & $tests, & $currentSection, & $testCount) { + if (isset($matches[3]) and $matches[3]) { + $currentSection = $matches[3]; + } else { + $testCount++; + $markdown = $matches[1]; + $markdown = preg_replace('/→/', "\t", $markdown); + $expectedHtml = $matches[2]; + $expectedHtml = $this->normalizeMarkup($expectedHtml); + $tests []= array( + $currentSection, # section + $markdown, # markdown + $expectedHtml, # html + ); + } + }, + $spec + ); + + return $tests; + } + + private function normalizeMarkup($markup) + { + $markup = preg_replace("/\n+/", "\n", $markup); + $markup = preg_replace('/^\s+/m', '', $markup); + $markup = preg_replace('/^((?:<[\w]+>)+)\n/m', '$1', $markup); + $markup = preg_replace('/\n((?:<\/[\w]+>)+)$/m', '$1', $markup); + $markup = trim($markup); + + return $markup; + } +} diff --git a/includes/libraries/parsedown/test/ParsedownTest.php b/includes/libraries/parsedown/test/ParsedownTest.php new file mode 100644 index 0000000..c922ab1 --- /dev/null +++ b/includes/libraries/parsedown/test/ParsedownTest.php @@ -0,0 +1,159 @@ +dirs = $this->initDirs(); + $this->Parsedown = $this->initParsedown(); + + parent::__construct($name, $data, $dataName); + } + + private $dirs, $Parsedown; + + /** + * @return array + */ + protected function initDirs() + { + $dirs []= dirname(__FILE__).'/data/'; + + return $dirs; + } + + /** + * @return Parsedown + */ + protected function initParsedown() + { + $Parsedown = new Parsedown(); + + return $Parsedown; + } + + /** + * @dataProvider data + * @param $test + * @param $dir + */ + function test_($test, $dir) + { + $markdown = file_get_contents($dir . $test . '.md'); + + $expectedMarkup = file_get_contents($dir . $test . '.html'); + + $expectedMarkup = str_replace("\r\n", "\n", $expectedMarkup); + $expectedMarkup = str_replace("\r", "\n", $expectedMarkup); + + $actualMarkup = $this->Parsedown->text($markdown); + + $this->assertEquals($expectedMarkup, $actualMarkup); + } + + function data() + { + $data = array(); + + foreach ($this->dirs as $dir) + { + $Folder = new DirectoryIterator($dir); + + foreach ($Folder as $File) + { + /** @var $File DirectoryIterator */ + + if ( ! $File->isFile()) + { + continue; + } + + $filename = $File->getFilename(); + + $extension = pathinfo($filename, PATHINFO_EXTENSION); + + if ($extension !== 'md') + { + continue; + } + + $basename = $File->getBasename('.md'); + + if (file_exists($dir . $basename . '.html')) + { + $data []= array($basename, $dir); + } + } + } + + return $data; + } + + public function test_no_markup() + { + $markdownWithHtml = <<_content_ + +sparse: + +
+
+_content_ +
+
+ +paragraph + + + +comment + + +MARKDOWN_WITH_MARKUP; + + $expectedHtml = <<<div>content</div>

+

sparse:

+

<div> +<div class="inner"> +content +</div> +</div>

+

paragraph

+

<style type="text/css"> +p { +color: red; +} +</style>

+

comment

+

<!-- html comment -->

+EXPECTED_HTML; + $parsedownWithNoMarkup = new Parsedown(); + $parsedownWithNoMarkup->setMarkupEscaped(true); + $this->assertEquals($expectedHtml, $parsedownWithNoMarkup->text($markdownWithHtml)); + } + + public function testLateStaticBinding() + { + include 'test/TestParsedown.php'; + + $parsedown = Parsedown::instance(); + $this->assertInstanceOf('Parsedown', $parsedown); + + // After instance is already called on Parsedown + // subsequent calls with the same arguments return the same instance + $sameParsedown = TestParsedown::instance(); + $this->assertInstanceOf('Parsedown', $sameParsedown); + $this->assertSame($parsedown, $sameParsedown); + + $testParsedown = TestParsedown::instance('test late static binding'); + $this->assertInstanceOf('TestParsedown', $testParsedown); + + $sameInstanceAgain = TestParsedown::instance('test late static binding'); + $this->assertSame($testParsedown, $sameInstanceAgain); + } +} diff --git a/includes/libraries/parsedown/test/Test.php b/includes/libraries/parsedown/test/Test.php deleted file mode 100644 index 5171d84..0000000 --- a/includes/libraries/parsedown/test/Test.php +++ /dev/null @@ -1,65 +0,0 @@ -dataDir = dirname(__FILE__).'/data/'; - - parent::__construct($name, $data, $dataName); - } - - private $dataDir; - - /** - * @dataProvider data - */ - function test_($filename) - { - $markdown = file_get_contents($this->dataDir . $filename . '.md'); - - $expectedMarkup = file_get_contents($this->dataDir . $filename . '.html'); - - $expectedMarkup = str_replace("\r\n", "\n", $expectedMarkup); - $expectedMarkup = str_replace("\r", "\n", $expectedMarkup); - - $actualMarkup = Parsedown::instance()->text($markdown); - - $this->assertEquals($expectedMarkup, $actualMarkup); - } - - function data() - { - $data = array(); - - $Folder = new DirectoryIterator($this->dataDir); - - foreach ($Folder as $File) - { - /** @var $File DirectoryIterator */ - - if ( ! $File->isFile()) - { - continue; - } - - $filename = $File->getFilename(); - - $extension = pathinfo($filename, PATHINFO_EXTENSION); - - if ($extension !== 'md') - { - continue; - } - - $basename = $File->getBasename('.md'); - - if (file_exists($this->dataDir . $basename . '.html')) - { - $data []= array($basename); - } - } - - return $data; - } -} diff --git a/includes/libraries/parsedown/test/TestParsedown.php b/includes/libraries/parsedown/test/TestParsedown.php new file mode 100644 index 0000000..7024dfb --- /dev/null +++ b/includes/libraries/parsedown/test/TestParsedown.php @@ -0,0 +1,5 @@ + -header 1 -header 2 -header 2 +header 1 +header 2 +header 2 -cell 1.1 -cell 1.2 -cell 1.3 +cell 1.1 +cell 1.2 +cell 1.3 -cell 2.1 -cell 2.2 -cell 2.3 +cell 2.1 +cell 2.2 +cell 2.3 \ No newline at end of file diff --git a/includes/libraries/parsedown/test/data/atx_heading.html b/includes/libraries/parsedown/test/data/atx_heading.html index 3ab3fd5..751f873 100644 --- a/includes/libraries/parsedown/test/data/atx_heading.html +++ b/includes/libraries/parsedown/test/data/atx_heading.html @@ -4,6 +4,6 @@

h3

h4

h5
h6
-
h6
+

####### not a heading

closed h1

#

\ No newline at end of file diff --git a/includes/libraries/parsedown/test/data/atx_heading.md b/includes/libraries/parsedown/test/data/atx_heading.md index 5339a5b..ad97b44 100644 --- a/includes/libraries/parsedown/test/data/atx_heading.md +++ b/includes/libraries/parsedown/test/data/atx_heading.md @@ -10,7 +10,7 @@ ###### h6 -####### h6 +####### not a heading # closed h1 # diff --git a/includes/libraries/parsedown/test/data/block-level_html.html b/includes/libraries/parsedown/test/data/block-level_html.html index 86ff865..6443a4a 100644 --- a/includes/libraries/parsedown/test/data/block-level_html.html +++ b/includes/libraries/parsedown/test/data/block-level_html.html @@ -1,13 +1,12 @@
_content_
-

sparse:

+

paragraph

-
-_content_ -
+
+ _content_ +
-

paragraph

\ No newline at end of file + p {color: #789;} + +
+ home
\ No newline at end of file diff --git a/includes/libraries/parsedown/test/data/block-level_html.md b/includes/libraries/parsedown/test/data/block-level_html.md index 679832c..17cbc22 100644 --- a/includes/libraries/parsedown/test/data/block-level_html.md +++ b/includes/libraries/parsedown/test/data/block-level_html.md @@ -1,17 +1,16 @@
_content_
-sparse: +paragraph
-
-_content_ -
+
+ _content_ +
-paragraph - + +
+ home
\ No newline at end of file diff --git a/includes/libraries/parsedown/test/data/escaping.html b/includes/libraries/parsedown/test/data/escaping.html index 64676cb..ab1c41f 100644 --- a/includes/libraries/parsedown/test/data/escaping.html +++ b/includes/libraries/parsedown/test/data/escaping.html @@ -1,4 +1,6 @@

escaped *emphasis*.

escaped \*emphasis\* in a code span

escaped \*emphasis\* in a code block
-

\ ` * _ { } [ ] ( ) > # + - . !

\ No newline at end of file +

\ ` * _ { } [ ] ( ) > # + - . !

+

one_two one_two

+

one*two one*two

\ No newline at end of file diff --git a/includes/libraries/parsedown/test/data/escaping.md b/includes/libraries/parsedown/test/data/escaping.md index 164039f..9f174e9 100644 --- a/includes/libraries/parsedown/test/data/escaping.md +++ b/includes/libraries/parsedown/test/data/escaping.md @@ -4,4 +4,8 @@ escaped \*emphasis\*. escaped \*emphasis\* in a code block -\\ \` \* \_ \{ \} \[ \] \( \) \> \# \+ \- \. \! \ No newline at end of file +\\ \` \* \_ \{ \} \[ \] \( \) \> \# \+ \- \. \! + +_one\_two_ __one\_two__ + +*one\*two* **one\*two** \ No newline at end of file diff --git a/includes/libraries/parsedown/test/data/HTML_Comment.html b/includes/libraries/parsedown/test/data/html_comment.html similarity index 100% rename from includes/libraries/parsedown/test/data/HTML_Comment.html rename to includes/libraries/parsedown/test/data/html_comment.html diff --git a/includes/libraries/parsedown/test/data/HTML_Comment.md b/includes/libraries/parsedown/test/data/html_comment.md similarity index 100% rename from includes/libraries/parsedown/test/data/HTML_Comment.md rename to includes/libraries/parsedown/test/data/html_comment.md diff --git a/includes/libraries/parsedown/test/data/image_reference.html b/includes/libraries/parsedown/test/data/image_reference.html index b3249cb..67fbd2c 100644 --- a/includes/libraries/parsedown/test/data/image_reference.html +++ b/includes/libraries/parsedown/test/data/image_reference.html @@ -1 +1,2 @@ -

Markdown Logo

\ No newline at end of file +

Markdown Logo

+

![missing reference]

\ No newline at end of file diff --git a/includes/libraries/parsedown/test/data/image_reference.md b/includes/libraries/parsedown/test/data/image_reference.md index dcb1414..1e11d94 100644 --- a/includes/libraries/parsedown/test/data/image_reference.md +++ b/includes/libraries/parsedown/test/data/image_reference.md @@ -1,3 +1,5 @@ ![Markdown Logo][image] [image]: /md.png + +![missing reference] \ No newline at end of file diff --git a/includes/libraries/parsedown/test/data/image_title.html b/includes/libraries/parsedown/test/data/image_title.html index 82c155f..957c950 100644 --- a/includes/libraries/parsedown/test/data/image_title.html +++ b/includes/libraries/parsedown/test/data/image_title.html @@ -1 +1,2 @@ -

alt

\ No newline at end of file +

alt

+

blank title

\ No newline at end of file diff --git a/includes/libraries/parsedown/test/data/image_title.md b/includes/libraries/parsedown/test/data/image_title.md index 3e58ee5..7ce2849 100644 --- a/includes/libraries/parsedown/test/data/image_title.md +++ b/includes/libraries/parsedown/test/data/image_title.md @@ -1 +1,3 @@ -![alt](/md.png "title") \ No newline at end of file +![alt](/md.png "title") + +![blank title](/md.png "") \ No newline at end of file diff --git a/includes/libraries/parsedown/test/data/inline_link.html b/includes/libraries/parsedown/test/data/inline_link.html index 2b9e649..5ad564a 100644 --- a/includes/libraries/parsedown/test/data/inline_link.html +++ b/includes/libraries/parsedown/test/data/inline_link.html @@ -1,4 +1,6 @@ -

link and another link

+

link

+

link with parentheses in URL

+

(link) in parentheses

link

-

MD Logo

-

MD Logo and text

\ No newline at end of file +

MD Logo

+

MD Logo and text

\ No newline at end of file diff --git a/includes/libraries/parsedown/test/data/inline_link.md b/includes/libraries/parsedown/test/data/inline_link.md index cd8e5a6..6bac0b3 100644 --- a/includes/libraries/parsedown/test/data/inline_link.md +++ b/includes/libraries/parsedown/test/data/inline_link.md @@ -1,4 +1,8 @@ -[link](http://example.com) and [another link](/tests/) +[link](http://example.com) + +[link](/url-(parentheses)) with parentheses in URL + +([link](/index.php)) in parentheses [`link`](http://example.com) diff --git a/includes/libraries/parsedown/test/data/inline_link_title.html b/includes/libraries/parsedown/test/data/inline_link_title.html index 70e589a..ecdfd03 100644 --- a/includes/libraries/parsedown/test/data/inline_link_title.html +++ b/includes/libraries/parsedown/test/data/inline_link_title.html @@ -1 +1,6 @@ -

single quotes and double quotes

\ No newline at end of file +

single quotes

+

double quotes

+

single quotes blank

+

double quotes blank

+

space

+

parentheses

\ No newline at end of file diff --git a/includes/libraries/parsedown/test/data/inline_link_title.md b/includes/libraries/parsedown/test/data/inline_link_title.md index 162b832..6e1c5af 100644 --- a/includes/libraries/parsedown/test/data/inline_link_title.md +++ b/includes/libraries/parsedown/test/data/inline_link_title.md @@ -1 +1,11 @@ -[single quotes](http://example.com 'Title') and [double quotes](http://example.com "Title") \ No newline at end of file +[single quotes](http://example.com 'Title') + +[double quotes](http://example.com "Title") + +[single quotes blank](http://example.com '') + +[double quotes blank](http://example.com "") + +[space](http://example.com "2 Words") + +[parentheses](http://example.com/url-(parentheses) "Title") \ No newline at end of file diff --git a/includes/libraries/parsedown/test/data/simple_table.html b/includes/libraries/parsedown/test/data/simple_table.html index 64b7a9a..237d7ef 100644 --- a/includes/libraries/parsedown/test/data/simple_table.html +++ b/includes/libraries/parsedown/test/data/simple_table.html @@ -20,17 +20,17 @@ - + - + - + diff --git a/includes/libraries/parsedown/test/data/sparse_html.html b/includes/libraries/parsedown/test/data/sparse_html.html new file mode 100644 index 0000000..9e89627 --- /dev/null +++ b/includes/libraries/parsedown/test/data/sparse_html.html @@ -0,0 +1,8 @@ +
+line 1 + +line 2 +line 3 + +line 4 +
\ No newline at end of file diff --git a/includes/libraries/parsedown/test/data/sparse_html.md b/includes/libraries/parsedown/test/data/sparse_html.md new file mode 100644 index 0000000..9e89627 --- /dev/null +++ b/includes/libraries/parsedown/test/data/sparse_html.md @@ -0,0 +1,8 @@ +
+line 1 + +line 2 +line 3 + +line 4 +
\ No newline at end of file diff --git a/includes/libraries/parsedown/test/data/special_characters.html b/includes/libraries/parsedown/test/data/special_characters.html index 8199abc..3b652c3 100644 --- a/includes/libraries/parsedown/test/data/special_characters.html +++ b/includes/libraries/parsedown/test/data/special_characters.html @@ -1,6 +1,6 @@

AT&T has an ampersand in their name

this & that

-

4 < 5 and 6 > 5

+

4 < 5 and 6 > 5

http://example.com/autolink?a=1&b=2

inline link

reference link

\ No newline at end of file diff --git a/includes/libraries/parsedown/test/data/table_inline_markdown.html b/includes/libraries/parsedown/test/data/table_inline_markdown.html index 53d0eb8..f4cebec 100644 --- a/includes/libraries/parsedown/test/data/table_inline_markdown.html +++ b/includes/libraries/parsedown/test/data/table_inline_markdown.html @@ -11,8 +11,12 @@ - - + + + + + +
header 1header 1 header 2
cell 1.1cell 1.1 cell 1.2
cell 2.1cell 2.1 cell 2.2
cell 1.2
cell 2.1cell 2.2| 2.1| 2.2
\| 2.1link
\ No newline at end of file diff --git a/includes/libraries/parsedown/test/data/table_inline_markdown.md b/includes/libraries/parsedown/test/data/table_inline_markdown.md index c2fe108..2f3c620 100644 --- a/includes/libraries/parsedown/test/data/table_inline_markdown.md +++ b/includes/libraries/parsedown/test/data/table_inline_markdown.md @@ -1,4 +1,5 @@ | _header_ 1 | header 2 | | ------------ | ------------ | | _cell_ 1.1 | ~~cell~~ 1.2 | -| `cell` 2.1 | cell 2.2 | \ No newline at end of file +| `|` 2.1 | \| 2.2 | +| `\|` 2.1 | [link](/) | \ No newline at end of file