This commit is contained in:
2020-10-06 14:27:47 +07:00
commit 586be80cf6
16613 changed files with 3274099 additions and 0 deletions

View File

@@ -0,0 +1,45 @@
<?php
/**
* @copyright Copyright (c) 2014 Carsten Brandt
* @license https://github.com/cebe/markdown/blob/master/LICENSE
* @link https://github.com/cebe/markdown#readme
*/
namespace cebe\markdown\inline;
/**
* Adds inline code elements
*/
trait CodeTrait
{
/**
* Parses an inline code span `` ` ``.
* @marker `
*/
protected function parseInlineCode($text)
{
if (preg_match('/^(``+)\s(.+?)\s\1/s', $text, $matches)) { // code with enclosed backtick
return [
[
'inlineCode',
$matches[2],
],
strlen($matches[0])
];
} elseif (preg_match('/^`(.+?)`/s', $text, $matches)) {
return [
[
'inlineCode',
$matches[1],
],
strlen($matches[0])
];
}
return [['text', $text[0]], 1];
}
protected function renderInlineCode($block)
{
return '<code>' . htmlspecialchars($block[1], ENT_NOQUOTES | ENT_SUBSTITUTE, 'UTF-8') . '</code>';
}
}

View File

@@ -0,0 +1,80 @@
<?php
/**
* @copyright Copyright (c) 2014 Carsten Brandt
* @license https://github.com/cebe/markdown/blob/master/LICENSE
* @link https://github.com/cebe/markdown#readme
*/
namespace cebe\markdown\inline;
/**
* Adds inline emphasizes and strong elements
*/
trait EmphStrongTrait
{
/**
* Parses empathized and strong elements.
* @marker _
* @marker *
*/
protected function parseEmphStrong($text)
{
$marker = $text[0];
if (!isset($text[1])) {
return [['text', $text[0]], 1];
}
if ($marker == $text[1]) { // strong
// work around a PHP bug that crashes with a segfault on too much regex backtrack
// check whether the end marker exists in the text
// https://github.com/erusev/parsedown/issues/443
// https://bugs.php.net/bug.php?id=45735
if (strpos($text, $marker . $marker, 2) === false) {
return [['text', $text[0] . $text[1]], 2];
}
if ($marker == '*' && preg_match('/^[*]{2}((?:[^*]|[*][^*]*[*])+?)[*]{2}(?![*]{2})/s', $text, $matches) ||
$marker == '_' && preg_match('/^__((?:[^_]|_[^_]*_)+?)__(?!__)/us', $text, $matches)) {
return [
[
'strong',
$this->parseInline($matches[1]),
],
strlen($matches[0])
];
}
} else { // emph
// work around a PHP bug that crashes with a segfault on too much regex backtrack
// check whether the end marker exists in the text
// https://github.com/erusev/parsedown/issues/443
// https://bugs.php.net/bug.php?id=45735
if (strpos($text, $marker, 1) === false) {
return [['text', $text[0]], 1];
}
if ($marker == '*' && preg_match('/^[*]((?:[^*]|[*][*][^*]+?[*][*])+?)[*](?![*][^*])/s', $text, $matches) ||
$marker == '_' && preg_match('/^_((?:[^_]|__[^_]*__)+?)_(?!_[^_])\b/us', $text, $matches)) {
return [
[
'emph',
$this->parseInline($matches[1]),
],
strlen($matches[0])
];
}
}
return [['text', $text[0]], 1];
}
protected function renderStrong($block)
{
return '<strong>' . $this->renderAbsy($block[1]) . '</strong>';
}
protected function renderEmph($block)
{
return '<em>' . $this->renderAbsy($block[1]) . '</em>';
}
}

View File

@@ -0,0 +1,278 @@
<?php
/**
* @copyright Copyright (c) 2014 Carsten Brandt
* @license https://github.com/cebe/markdown/blob/master/LICENSE
* @link https://github.com/cebe/markdown#readme
*/
namespace cebe\markdown\inline;
// work around https://github.com/facebook/hhvm/issues/1120
defined('ENT_HTML401') || define('ENT_HTML401', 0);
/**
* Addes links and images as well as url markers.
*
* This trait conflicts with the HtmlTrait. If both are used together,
* you have to define a resolution, by defining the HtmlTrait::parseInlineHtml
* as private so it is not used directly:
*
* ```php
* use block\HtmlTrait {
* parseInlineHtml as private parseInlineHtml;
* }
* ```
*
* If the method exists it is called internally by this trait.
*
* Also make sure to reset references on prepare():
*
* ```php
* protected function prepare()
* {
* // reset references
* $this->references = [];
* }
* ```
*/
trait LinkTrait
{
/**
* @var array a list of defined references in this document.
*/
protected $references = [];
/**
* Remove backslash from escaped characters
* @param $text
* @return string
*/
protected function replaceEscape($text)
{
$strtr = [];
foreach($this->escapeCharacters as $char) {
$strtr["\\$char"] = $char;
}
return strtr($text, $strtr);
}
/**
* Parses a link indicated by `[`.
* @marker [
*/
protected function parseLink($markdown)
{
if (!in_array('parseLink', array_slice($this->context, 1)) && ($parts = $this->parseLinkOrImage($markdown)) !== false) {
list($text, $url, $title, $offset, $key) = $parts;
return [
[
'link',
'text' => $this->parseInline($text),
'url' => $url,
'title' => $title,
'refkey' => $key,
'orig' => substr($markdown, 0, $offset),
],
$offset
];
} else {
// remove all starting [ markers to avoid next one to be parsed as link
$result = '[';
$i = 1;
while (isset($markdown[$i]) && $markdown[$i] == '[') {
$result .= '[';
$i++;
}
return [['text', $result], $i];
}
}
/**
* Parses an image indicated by `![`.
* @marker ![
*/
protected function parseImage($markdown)
{
if (($parts = $this->parseLinkOrImage(substr($markdown, 1))) !== false) {
list($text, $url, $title, $offset, $key) = $parts;
return [
[
'image',
'text' => $text,
'url' => $url,
'title' => $title,
'refkey' => $key,
'orig' => substr($markdown, 0, $offset + 1),
],
$offset + 1
];
} else {
// remove all starting [ markers to avoid next one to be parsed as link
$result = '!';
$i = 1;
while (isset($markdown[$i]) && $markdown[$i] == '[') {
$result .= '[';
$i++;
}
return [['text', $result], $i];
}
}
protected function parseLinkOrImage($markdown)
{
if (strpos($markdown, ']') !== false && preg_match('/\[((?>[^\]\[]+|(?R))*)\]/', $markdown, $textMatches)) { // TODO improve bracket regex
$text = $textMatches[1];
$offset = strlen($textMatches[0]);
$markdown = substr($markdown, $offset);
$pattern = <<<REGEXP
/(?(R) # in case of recursion match parentheses
\(((?>[^\s()]+)|(?R))*\)
| # else match a link with title
^\(\s*(((?>[^\s()]+)|(?R))*)(\s+"(.*?)")?\s*\)
)/x
REGEXP;
if (preg_match($pattern, $markdown, $refMatches)) {
// inline link
return [
$text,
isset($refMatches[2]) ? $this->replaceEscape($refMatches[2]) : '', // url
empty($refMatches[5]) ? null: $refMatches[5], // title
$offset + strlen($refMatches[0]), // offset
null, // reference key
];
} elseif (preg_match('/^([ \n]?\[(.*?)\])?/s', $markdown, $refMatches)) {
// reference style link
if (empty($refMatches[2])) {
$key = strtolower($text);
} else {
$key = strtolower($refMatches[2]);
}
return [
$text,
null, // url
null, // title
$offset + strlen($refMatches[0]), // offset
$key,
];
}
}
return false;
}
/**
* Parses inline HTML.
* @marker <
*/
protected function parseLt($text)
{
if (strpos($text, '>') !== false) {
if (!in_array('parseLink', $this->context)) { // do not allow links in links
if (preg_match('/^<([^\s]*?@[^\s]*?\.\w+?)>/', $text, $matches)) {
// email address
return [
['email', $this->replaceEscape($matches[1])],
strlen($matches[0])
];
} elseif (preg_match('/^<([a-z]{3,}:\/\/[^\s]+?)>/', $text, $matches)) {
// URL
return [
['url', $this->replaceEscape($matches[1])],
strlen($matches[0])
];
}
}
// try inline HTML if it was neither a URL nor email if HtmlTrait is included.
if (method_exists($this, 'parseInlineHtml')) {
return $this->parseInlineHtml($text);
}
}
return [['text', '&lt;'], 1];
}
protected function renderEmail($block)
{
$email = htmlspecialchars($block[1], ENT_NOQUOTES | ENT_SUBSTITUTE, 'UTF-8');
return "<a href=\"mailto:$email\">$email</a>";
}
protected function renderUrl($block)
{
$url = htmlspecialchars($block[1], ENT_COMPAT | ENT_HTML401, 'UTF-8');
$decodedUrl = urldecode($block[1]);
$secureUrlText = preg_match('//u', $decodedUrl) ? $decodedUrl : $block[1];
$text = htmlspecialchars($secureUrlText, ENT_NOQUOTES | ENT_SUBSTITUTE, 'UTF-8');
return "<a href=\"$url\">$text</a>";
}
protected function lookupReference($key)
{
$normalizedKey = preg_replace('/\s+/', ' ', $key);
if (isset($this->references[$key]) || isset($this->references[$key = $normalizedKey])) {
return $this->references[$key];
}
return false;
}
protected function renderLink($block)
{
if (isset($block['refkey'])) {
if (($ref = $this->lookupReference($block['refkey'])) !== false) {
$block = array_merge($block, $ref);
} else {
return $block['orig'];
}
}
return '<a href="' . htmlspecialchars($block['url'], ENT_COMPAT | ENT_HTML401, 'UTF-8') . '"'
. (empty($block['title']) ? '' : ' title="' . htmlspecialchars($block['title'], ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE, 'UTF-8') . '"')
. '>' . $this->renderAbsy($block['text']) . '</a>';
}
protected function renderImage($block)
{
if (isset($block['refkey'])) {
if (($ref = $this->lookupReference($block['refkey'])) !== false) {
$block = array_merge($block, $ref);
} else {
return $block['orig'];
}
}
return '<img src="' . htmlspecialchars($block['url'], ENT_COMPAT | ENT_HTML401, 'UTF-8') . '"'
. ' alt="' . htmlspecialchars($block['text'], ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE, 'UTF-8') . '"'
. (empty($block['title']) ? '' : ' title="' . htmlspecialchars($block['title'], ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE, 'UTF-8') . '"')
. ($this->html5 ? '>' : ' />');
}
// references
protected function identifyReference($line)
{
return ($line[0] === ' ' || $line[0] === '[') && preg_match('/^ {0,3}\[(.+?)\]:\s*([^\s]+?)(?:\s+[\'"](.+?)[\'"])?\s*$/', $line);
}
/**
* Consume link references
*/
protected function consumeReference($lines, $current)
{
while (isset($lines[$current]) && preg_match('/^ {0,3}\[(.+?)\]:\s*(.+?)(?:\s+[\(\'"](.+?)[\)\'"])?\s*$/', $lines[$current], $matches)) {
$label = strtolower($matches[1]);
$this->references[$label] = [
'url' => $this->replaceEscape($matches[2]),
];
if (isset($matches[3])) {
$this->references[$label]['title'] = $matches[3];
} else {
// title may be on the next line
if (isset($lines[$current + 1]) && preg_match('/^\s+[\(\'"](.+?)[\)\'"]\s*$/', $lines[$current + 1], $matches)) {
$this->references[$label]['title'] = $matches[1];
$current++;
}
}
$current++;
}
return [false, --$current];
}
}

View File

@@ -0,0 +1,37 @@
<?php
/**
* @copyright Copyright (c) 2014 Carsten Brandt
* @license https://github.com/cebe/markdown/blob/master/LICENSE
* @link https://github.com/cebe/markdown#readme
*/
namespace cebe\markdown\inline;
/**
* Adds strikeout inline elements
*/
trait StrikeoutTrait
{
/**
* Parses the strikethrough feature.
* @marker ~~
*/
protected function parseStrike($markdown)
{
if (preg_match('/^~~(.+?)~~/', $markdown, $matches)) {
return [
[
'strike',
$this->parseInline($matches[1])
],
strlen($matches[0])
];
}
return [['text', $markdown[0] . $markdown[1]], 2];
}
protected function renderStrike($block)
{
return '<del>' . $this->renderAbsy($block[1]) . '</del>';
}
}

View File

@@ -0,0 +1,50 @@
<?php
/**
* @copyright Copyright (c) 2014 Carsten Brandt
* @license https://github.com/cebe/markdown/blob/master/LICENSE
* @link https://github.com/cebe/markdown#readme
*/
namespace cebe\markdown\inline;
// work around https://github.com/facebook/hhvm/issues/1120
defined('ENT_HTML401') || define('ENT_HTML401', 0);
/**
* Adds auto linking for URLs
*/
trait UrlLinkTrait
{
/**
* Parses urls and adds auto linking feature.
* @marker http
* @marker ftp
*/
protected function parseUrl($markdown)
{
$pattern = <<<REGEXP
/(?(R) # in case of recursion match parentheses
\(((?>[^\s()]+)|(?R))*\)
| # else match a link with title
^(https?|ftp):\/\/(([^\s()]+)|(?R))+(?<![\.,:;\'"!\?\s])
)/x
REGEXP;
if (!in_array('parseLink', $this->context) && preg_match($pattern, $markdown, $matches)) {
return [
['autoUrl', $matches[0]],
strlen($matches[0])
];
}
return [['text', substr($markdown, 0, 4)], 4];
}
protected function renderAutoUrl($block)
{
$href = htmlspecialchars($block[1], ENT_COMPAT | ENT_HTML401, 'UTF-8');
$decodedUrl = urldecode($block[1]);
$secureUrlText = preg_match('//u', $decodedUrl) ? $decodedUrl : $block[1];
$text = htmlspecialchars($secureUrlText, ENT_NOQUOTES | ENT_SUBSTITUTE, 'UTF-8');
return "<a href=\"$href\">$text</a>";
}
}