From ebbc08fe9b98271c7a15082cbd3e14abc64ed2de Mon Sep 17 00:00:00 2001 From: Dennis Eichhorn Date: Sun, 9 Jul 2023 02:32:02 +0000 Subject: [PATCH] backup --- Ai/Ocr/BasicOcr.php | 13 +- Api/EUVAT/EUVATBffOnline.php | 4 +- Api/EUVAT/EUVATVies.php | 4 +- Application/ApplicationManager.php | 4 +- DataStorage/Database/Mapper/ReadMapper.php | 3 + Message/Mail/Email.php | 2 +- Module/ModuleManager.php | 8 +- System/File/FileUtils.php | 10 + System/File/Local/Directory.php | 2 +- System/MimeType.php | 22 +- Utils/Converter/Currency.php | 2 +- Utils/IO/Zip/Tar.php | 2 +- Utils/IO/Zip/Zip.php | 2 +- Utils/Parser/Markdown/Markdown.php | 3891 +++++++++++------ .../Matrix/EigenvalueDecompositionTest.php | 2 +- tests/System/File/Ftp/DirectoryTest.php | 2 +- tests/System/File/Ftp/FileTest.php | 2 +- tests/System/File/Ftp/FtpStorageTest.php | 2 +- tests/Utils/Converter/CurrencyTest.php | 2 +- 19 files changed, 2506 insertions(+), 1473 deletions(-) diff --git a/Ai/Ocr/BasicOcr.php b/Ai/Ocr/BasicOcr.php index c524dca9b..764433035 100755 --- a/Ai/Ocr/BasicOcr.php +++ b/Ai/Ocr/BasicOcr.php @@ -95,6 +95,8 @@ final class BasicOcr } // $magicNumber = $unpack[1]; + // 2051 === image data (should always be this) + // 2049 === label data if (($read = \fread($fp, 4)) === false || ($unpack = \unpack('N', $read)) === false) { return []; // @codeCoverageIgnore @@ -159,7 +161,10 @@ final class BasicOcr if (($read = \fread($fp, 4)) === false || ($unpack = \unpack('N', $read)) === false) { return []; // @codeCoverageIgnore } - $magicNumber = $unpack[1]; + + // $magicNumber = $unpack[1]; + // 2051 === image data + // 2049 === label data (should always be this) if (($read = \fread($fp, 4)) === false || ($unpack = \unpack('N', $read)) === false) { return []; // @codeCoverageIgnore @@ -258,7 +263,7 @@ final class BasicOcr } \fwrite($out, \pack('N', 2051)); - \fwrite($out, \pack('N', 1)); + \fwrite($out, \pack('N', \count($images))); \fwrite($out, \pack('N', $resolution)); \fwrite($out, \pack('N', $resolution)); @@ -299,7 +304,7 @@ final class BasicOcr } for ($i = 0; $i < $size; ++$i) { - \fwrite($out, \pack('C', \round($mnist[$i] * 255))); + \fwrite($out, \pack('C', (int) \round($mnist[$i] * 255))); } } @@ -325,7 +330,7 @@ final class BasicOcr } \fwrite($out, \pack('N', 2049)); - \fwrite($out, \pack('N', 1)); + \fwrite($out, \pack('N', \count($data))); foreach ($data as $e) { \fwrite($out, \pack('C', $e)); diff --git a/Api/EUVAT/EUVATBffOnline.php b/Api/EUVAT/EUVATBffOnline.php index 282813d59..b23c0bc17 100755 --- a/Api/EUVAT/EUVATBffOnline.php +++ b/Api/EUVAT/EUVATBffOnline.php @@ -81,7 +81,7 @@ final class EUVATBffOnline implements EUVATInterface } $result['status'] = 0; - } catch (\Throwable $t) { + } catch (\Throwable $_) { return $result; } @@ -151,7 +151,7 @@ final class EUVATBffOnline implements EUVATInterface $result['name'] = $matches[1] ?? 'B'; $result['status'] = 0; - } catch (\Throwable $t) { + } catch (\Throwable $_) { return $result; } diff --git a/Api/EUVAT/EUVATVies.php b/Api/EUVAT/EUVATVies.php index 44399b228..5ee4888e2 100755 --- a/Api/EUVAT/EUVATVies.php +++ b/Api/EUVAT/EUVATVies.php @@ -80,7 +80,7 @@ final class EUVATVies implements EUVATInterface $result = \array_merge($result, self::parseResponse($json)); $result['status'] = $json['userError'] === 'VALID' ? 0 : -1; - } catch (\Throwable $t) { + } catch (\Throwable $_) { return $result; } @@ -182,7 +182,7 @@ final class EUVATVies implements EUVATInterface } $result['status'] = $json['userError'] === 'VALID' ? 0 : -1; - } catch (\Throwable $t) { + } catch (\Throwable $_) { return $result; } diff --git a/Application/ApplicationManager.php b/Application/ApplicationManager.php index 3de41156b..9dcd24a1e 100755 --- a/Application/ApplicationManager.php +++ b/Application/ApplicationManager.php @@ -121,7 +121,7 @@ final class ApplicationManager $class::install($this->app, $info, $this->app->appSettings); return true; - } catch (\Throwable $t) { + } catch (\Throwable $_) { return false; // @codeCoverageIgnore } } @@ -155,7 +155,7 @@ final class ApplicationManager $this->uninstallFiles($source); return true; - } catch (\Throwable $t) { + } catch (\Throwable $_) { return false; // @codeCoverageIgnore } } diff --git a/DataStorage/Database/Mapper/ReadMapper.php b/DataStorage/Database/Mapper/ReadMapper.php index ab99cdd7d..13318fe92 100755 --- a/DataStorage/Database/Mapper/ReadMapper.php +++ b/DataStorage/Database/Mapper/ReadMapper.php @@ -877,6 +877,9 @@ final class ReadMapper extends DataMapperAbstract } $objects = $objectMapper->execute(); + if (empty($objects) || (!\is_array($objects) && $objects->id === 0)) { + continue; + } if ($refClass === null) { $refClass = new \ReflectionClass($obj); diff --git a/Message/Mail/Email.php b/Message/Mail/Email.php index e33d4d81d..bb7d2fdad 100755 --- a/Message/Mail/Email.php +++ b/Message/Mail/Email.php @@ -1115,7 +1115,7 @@ class Email implements MessageInterface \PKCS7_DETACHED, $this->signExtracertFiles ); - } catch (\Throwable $t) { + } catch (\Throwable $_) { $sign = false; } diff --git a/Module/ModuleManager.php b/Module/ModuleManager.php index fb6c666c9..5f6508b66 100755 --- a/Module/ModuleManager.php +++ b/Module/ModuleManager.php @@ -523,19 +523,19 @@ final class ModuleManager /* Install providing but only if receiving module is already installed */ $providing = $info->getProviding(); - foreach ($providing as $key => $version) { + foreach ($providing as $key => $_) { if (isset($installed[$key])) { $this->installProviding('/Modules/' . $module, $key); } } /* Install receiving and applications */ - foreach ($this->installed as $key => $value) { + foreach ($this->installed as $key => $_) { $this->installProviding('/Modules/' . $key, $module); } return true; - } catch (\Throwable $t) { + } catch (\Throwable $_) { return false; // @codeCoverageIgnore } } @@ -593,7 +593,7 @@ final class ModuleManager } return true; - } catch (\Throwable $t) { + } catch (\Throwable $_) { return false; // @codeCoverageIgnore } } diff --git a/System/File/FileUtils.php b/System/File/FileUtils.php index fe5784891..d4038cc18 100755 --- a/System/File/FileUtils.php +++ b/System/File/FileUtils.php @@ -280,4 +280,14 @@ final class FileUtils { return !\preg_match('#^[a-z][a-z\d+.-]*://#i', $path); } + + public static function makeSafeFileName(string $name) : string + { + $name = \preg_replace("/[^A-Za-z0-9\-_.]/", '_', $name); + $name = \preg_replace("/_+/", '_', $name); + $name = \trim($name, '_'); + $name = \strtolower($name); + + return $name; + } } diff --git a/System/File/Local/Directory.php b/System/File/Local/Directory.php index 2a94659ae..68ac9d082 100755 --- a/System/File/Local/Directory.php +++ b/System/File/Local/Directory.php @@ -490,7 +490,7 @@ final class Directory extends FileAbstract implements DirectoryInterface try { \mkdir($path, $permission, $recursive); - } catch (\Throwable $t) { + } catch (\Throwable $_) { return false; // @codeCoverageIgnore } diff --git a/System/MimeType.php b/System/MimeType.php index b08d84193..348bdb1cf 100755 --- a/System/MimeType.php +++ b/System/MimeType.php @@ -2023,8 +2023,28 @@ abstract class MimeType extends Enum { try { return (string) (self::getByName('M_' . \strtoupper($extension)) ?? 'application/octet-stream'); - } catch (\Throwable $t) { + } catch (\Throwable $_) { return 'application/octet-stream'; } } + + public static function mimeToExtension(string $mime) : ?string + { + switch($mime) { + case self::M_PDF: + return 'pdf'; + case self::M_JPEG: + case self::M_JPG: + return 'jpg'; + case self::M_BMP: + return 'bmp'; + case self::M_GIF: + return 'gif'; + case self::M_HTML: + case self::M_HTM: + return 'htm'; + default: + return null; + } + } } diff --git a/Utils/Converter/Currency.php b/Utils/Converter/Currency.php index 3717ffdc6..c275e4e93 100755 --- a/Utils/Converter/Currency.php +++ b/Utils/Converter/Currency.php @@ -122,7 +122,7 @@ final class Currency self::$ecbCurrencies[\strtoupper((string) ($attributes['currency']))] = (float) ($attributes['rate']); } - } catch (\Throwable $t) { + } catch (\Throwable $_) { self::$ecbCurrencies = []; // @codeCoverageIgnore } diff --git a/Utils/IO/Zip/Tar.php b/Utils/IO/Zip/Tar.php index 3a208a8ec..dfc8d5653 100755 --- a/Utils/IO/Zip/Tar.php +++ b/Utils/IO/Zip/Tar.php @@ -120,7 +120,7 @@ class Tar implements ArchiveInterface $tar->extractTo($destination . '/'); return true; - } catch (\Throwable $t) { + } catch (\Throwable $_) { return false; } } diff --git a/Utils/IO/Zip/Zip.php b/Utils/IO/Zip/Zip.php index 3dcaa7b89..d53a87fc5 100755 --- a/Utils/IO/Zip/Zip.php +++ b/Utils/IO/Zip/Zip.php @@ -123,7 +123,7 @@ class Zip implements ArchiveInterface $zip->extractTo($destination . '/'); return $zip->close(); - } catch (\Throwable $t) { + } catch (\Throwable $_) { return false; } } diff --git a/Utils/Parser/Markdown/Markdown.php b/Utils/Parser/Markdown/Markdown.php index f7b64184b..298013ec5 100755 --- a/Utils/Parser/Markdown/Markdown.php +++ b/Utils/Parser/Markdown/Markdown.php @@ -5,8 +5,6 @@ * PHP Version 8.1 * * @package phpOMS\Utils\Parser\Markdown - * @copyright Dennis Eichhorn - * @license OMS License 2.0 * @license Original license Emanuil Rusev, erusev.com (MIT) * @version 1.0.0 * @link https://jingga.app @@ -21,22 +19,230 @@ use phpOMS\Uri\UriFactory; * Markdown parser class. * * @package phpOMS\Utils\Parser\Markdown - * @license OMS License 2.0 * @license Original license Emanuil Rusev, erusev.com (MIT) * @link https://jingga.app * @since 1.0.0 */ class Markdown { - /** - * Blocktypes. - * - * @var string[][] - * @since 1.0.0 - */ - protected static $blockTypes = [ + public function text($text) + { + $Elements = $this->textElements($text); + + # convert to markup + $markup = $this->elements($Elements); + + # trim line breaks + $markup = \trim($markup, "\n"); + + # merge consecutive dl elements + + $markup = \preg_replace('/<\/dl>\s+
\s+/', '', $markup); + + # add footnotes + + if (isset($this->DefinitionData['Footnote'])) + { + $Element = $this->buildFootnoteElement(); + + $markup .= "\n" . $this->element($Element); + } + + return $markup; + } + + protected function sortFootnotes($A, $B) # callback + { + return $A['number'] - $B['number']; + } + + protected string $regexAttribute = '(?:[#.][-\w]+[ ]*)'; + + protected function buildFootnoteElement() + { + $Element = [ + 'name' => 'div', + 'attributes' => ['class' => 'footnotes'], + 'elements' => [ + ['name' => 'hr'], + [ + 'name' => 'ol', + 'elements' => [], + ], + ], + ]; + + \uasort($this->DefinitionData['Footnote'], 'self::sortFootnotes'); + + foreach ($this->DefinitionData['Footnote'] as $definitionId => $DefinitionData) + { + if ( ! isset($DefinitionData['number'])) + { + continue; + } + + $text = $DefinitionData['text']; + + $textElements = self::textElements($text); + + $numbers = \range(1, $DefinitionData['count']); + + $backLinkElements = []; + + foreach ($numbers as $number) + { + $backLinkElements[] = ['text' => ' ']; + $backLinkElements[] = [ + 'name' => 'a', + 'attributes' => [ + 'href' => "#fnref$number:$definitionId", + 'rev' => 'footnote', + 'class' => 'footnote-backref', + ], + 'rawHtml' => '↩', + 'allowRawHtmlInSafeMode' => true, + 'autobreak' => false, + ]; + } + + unset($backLinkElements[0]); + + $n = \count($textElements) -1; + + if ($textElements[$n]['name'] === 'p') + { + $backLinkElements = \array_merge( + [ + [ + 'rawHtml' => ' ', + 'allowRawHtmlInSafeMode' => true, + ], + ], + $backLinkElements + ); + + unset($textElements[$n]['name']); + + $textElements[$n] = [ + 'name' => 'p', + 'elements' => \array_merge( + [$textElements[$n]], + $backLinkElements + ), + ]; + } + else + { + $textElements[] = [ + 'name' => 'p', + 'elements' => $backLinkElements + ]; + } + + $Element['elements'][1]['elements'][] = [ + 'name' => 'li', + 'attributes' => ['id' => 'fn:'.$definitionId], + 'elements' => \array_merge( + $textElements + ), + ]; + } + + return $Element; + } + + protected function textElements($text) + { + # make sure no definitions are set + $this->DefinitionData = []; + + # standardize line breaks + $text = \str_replace(["\r\n", "\r"], "\n", $text); + + # remove surrounding line breaks + $text = \trim($text, "\n"); + + # split text into lines + $lines = \explode("\n", $text); + + # iterate through lines to identify blocks + return $this->linesElements($lines); + } + + # + # Setters + # + + public function setBreaksEnabled($breaksEnabled) + { + $this->breaksEnabled = $breaksEnabled; + + return $this; + } + + protected bool $breaksEnabled = false; + + public function setMarkupEscaped($markupEscaped) + { + $this->markupEscaped = $markupEscaped; + + return $this; + } + + protected bool $markupEscaped = false; + + public function setUrlsLinked($urlsLinked) + { + $this->urlsLinked = $urlsLinked; + + return $this; + } + + protected bool $urlsLinked = true; + + function setSafeMode($safeMode) + { + $this->safeMode = (bool) $safeMode; + + return $this; + } + + public bool $safeMode = false; + + function setStrictMode($strictMode) + { + $this->strictMode = (bool) $strictMode; + + return $this; + } + + public bool $strictMode = false; + + protected array $safeLinksWhitelist = [ + 'http://', + 'https://', + 'ftp://', + 'ftps://', + 'mailto:', + 'tel:', + 'data:image/png;base64,', + 'data:image/gif;base64,', + 'data:image/jpeg;base64,', + 'irc:', + 'ircs:', + 'git:', + 'ssh:', + 'news:', + 'steam:', + ]; + + # + # Lines + # + + protected array $BlockTypes = [ '#' => ['Header'], - '*' => ['Rule', 'List'], + '*' => ['Rule', 'List', 'Abbreviation'], '+' => ['List'], '-' => ['SetextHeader', 'Table', 'Rule', 'List'], '0' => ['List'], @@ -49,92 +255,2258 @@ class Markdown '7' => ['List'], '8' => ['List'], '9' => ['List'], - ':' => ['Table'], + ':' => ['Table', 'DefinitionList'], + '<' => ['Comment', 'Markup'], '=' => ['SetextHeader'], '>' => ['Quote'], - '[' => ['Reference'], + '[' => ['Footnote', 'Reference'], '_' => ['Rule'], '`' => ['FencedCode'], '|' => ['Table'], '~' => ['FencedCode'], ]; - /** - * Blocktypes. - * - * @var string[] - * @since 1.0.0 - */ - protected static $unmarkedBlockTypes = [ + # ~ + + protected array $unmarkedBlockTypes = [ 'Code', ]; - /** - * Special reserved characters. - * - * @var string[] - * @since 1.0.0 - */ - protected static $specialCharacters = [ - '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|', + # + # Blocks + # + + protected function lines(array $lines) + { + return $this->elements($this->linesElements($lines)); + } + + protected function linesElements(array $lines) + { + $Elements = []; + $CurrentBlock = null; + + foreach ($lines as $line) + { + if (\rtrim($line) === '') + { + if (isset($CurrentBlock)) + { + $CurrentBlock['interrupted'] = (isset($CurrentBlock['interrupted']) + ? $CurrentBlock['interrupted'] + 1 : 1 + ); + } + + continue; + } + + while (($beforeTab = \strstr($line, "\t", true)) !== false) + { + $shortage = 4 - mb_strlen($beforeTab, 'utf-8') % 4; + + $line = $beforeTab + . \str_repeat(' ', $shortage) + . \substr($line, \strlen($beforeTab) + 1) + ; + } + + $indent = \strspn($line, ' '); + + $text = $indent > 0 ? \substr($line, $indent) : $line; + + # ~ + + $Line = ['body' => $line, 'indent' => $indent, 'text' => $text]; + + # ~ + + if (isset($CurrentBlock['continuable'])) + { + $methodName = 'block' . $CurrentBlock['type'] . 'Continue'; + $Block = $this->$methodName($Line, $CurrentBlock); + + if (isset($Block)) + { + $CurrentBlock = $Block; + + continue; + } + else + { + if ($this->isBlockCompletable($CurrentBlock['type'])) + { + $methodName = 'block' . $CurrentBlock['type'] . 'Complete'; + $CurrentBlock = $this->$methodName($CurrentBlock); + } + } + } + + # ~ + + $marker = $text[0]; + + # ~ + + $blockTypes = $this->unmarkedBlockTypes; + + if (isset($this->BlockTypes[$marker])) + { + foreach ($this->BlockTypes[$marker] as $blockType) + { + $blockTypes []= $blockType; + } + } + + # + # ~ + + foreach ($blockTypes as $blockType) + { + $Block = $this->{"block$blockType"}($Line, $CurrentBlock); + + if (isset($Block)) + { + $Block['type'] = $blockType; + + if ( ! isset($Block['identified'])) + { + if (isset($CurrentBlock)) + { + $Elements[] = $this->extractElement($CurrentBlock); + } + + $Block['identified'] = true; + } + + if ($this->isBlockContinuable($blockType)) + { + $Block['continuable'] = true; + } + + $CurrentBlock = $Block; + + continue 2; + } + } + + # ~ + + if (isset($CurrentBlock) && $CurrentBlock['type'] === 'Paragraph') + { + $Block = $this->paragraphContinue($Line, $CurrentBlock); + } + + if (isset($Block)) + { + $CurrentBlock = $Block; + } + else + { + if (isset($CurrentBlock)) + { + $Elements[] = $this->extractElement($CurrentBlock); + } + + $CurrentBlock = $this->paragraph($Line); + + $CurrentBlock['identified'] = true; + } + } + + # ~ + + if (isset($CurrentBlock['continuable']) && $this->isBlockCompletable($CurrentBlock['type'])) + { + $methodName = 'block' . $CurrentBlock['type'] . 'Complete'; + $CurrentBlock = $this->$methodName($CurrentBlock); + } + + # ~ + + if (isset($CurrentBlock)) + { + $Elements[] = $this->extractElement($CurrentBlock); + } + + # ~ + + return $Elements; + } + + protected function extractElement(array $Component) + { + if ( ! isset($Component['element'])) + { + if (isset($Component['markup'])) + { + $Component['element'] = ['rawHtml' => $Component['markup']]; + } + elseif (isset($Component['hidden'])) + { + $Component['element'] = []; + } + } + + return $Component['element']; + } + + protected function isBlockContinuable($Type) + { + return method_exists($this, 'block' . $Type . 'Continue'); + } + + protected function isBlockCompletable($Type) + { + return method_exists($this, 'block' . $Type . 'Complete'); + } + + # + # Code + + protected function blockCode($Line, $Block = null) + { + if (isset($Block) && $Block['type'] === 'Paragraph' && ! isset($Block['interrupted'])) + { + return; + } + + if ($Line['indent'] >= 4) + { + $text = \substr($Line['body'], 4); + + $Block = [ + 'element' => [ + 'name' => 'pre', + 'element' => [ + 'name' => 'code', + 'text' => $text, + ], + ], + ]; + + return $Block; + } + } + + protected function blockCodeContinue($Line, $Block) + { + if ($Line['indent'] >= 4) + { + if (isset($Block['interrupted'])) + { + $Block['element']['element']['text'] .= \str_repeat("\n", $Block['interrupted']); + + unset($Block['interrupted']); + } + + $Block['element']['element']['text'] .= "\n"; + + $text = \substr($Line['body'], 4); + + $Block['element']['element']['text'] .= $text; + + return $Block; + } + } + + protected function blockCodeComplete($Block) + { + return $Block; + } + + # + # Comment + + protected function blockComment($Line) + { + if ($this->markupEscaped || $this->safeMode) + { + return; + } + + if (strpos($Line['text'], '') !== false) + { + $Block['closed'] = true; + } + + return $Block; + } + } + + protected function blockCommentContinue($Line, array $Block) + { + if (isset($Block['closed'])) + { + return; + } + + $Block['element']['rawHtml'] .= "\n" . $Line['body']; + + if (strpos($Line['text'], '-->') !== false) + { + $Block['closed'] = true; + } + + return $Block; + } + + # + # Fenced Code + + protected function blockFencedCode($Line) + { + $marker = $Line['text'][0]; + + $openerLength = \strspn($Line['text'], $marker); + + if ($openerLength < 3) + { + return; + } + + $infostring = \trim(\substr($Line['text'], $openerLength), "\t "); + + if (strpos($infostring, '`') !== false) + { + return; + } + + $Element = [ + 'name' => 'code', + 'text' => '', + ]; + + if ($infostring !== '') + { + /** + * https://www.w3.org/TR/2011/WD-html5-20110525/elements.html#classes + * Every HTML element may have a class attribute specified. + * The attribute, if specified, must have a value that is a set + * of space-separated tokens representing the various classes + * that the element belongs to. + * [...] + * The space characters, for the purposes of this specification, + * are U+0020 SPACE, U+0009 CHARACTER TABULATION (tab), + * U+000A LINE FEED (LF), U+000C FORM FEED (FF), and + * U+000D CARRIAGE RETURN (CR). + */ + $language = \substr($infostring, 0, strcspn($infostring, " \t\n\f\r")); + + $Element['attributes'] = ['class' => "language-$language"]; + } + + $Block = [ + 'char' => $marker, + 'openerLength' => $openerLength, + 'element' => [ + 'name' => 'pre', + 'element' => $Element, + ], + ]; + + return $Block; + } + + protected function blockFencedCodeContinue($Line, $Block) + { + if (isset($Block['complete'])) + { + return; + } + + if (isset($Block['interrupted'])) + { + $Block['element']['element']['text'] .= \str_repeat("\n", $Block['interrupted']); + + unset($Block['interrupted']); + } + + if (($len = \strspn($Line['text'], $Block['char'])) >= $Block['openerLength'] + && \rtrim(\substr($Line['text'], $len), ' ') === '' + ) { + $Block['element']['element']['text'] = \substr($Block['element']['element']['text'], 1); + + $Block['complete'] = true; + + return $Block; + } + + $Block['element']['element']['text'] .= "\n" . $Line['body']; + + return $Block; + } + + protected function blockFencedCodeComplete($Block) + { + return $Block; + } + + protected function blockAbbreviation($Line) + { + if (\preg_match('/^\*\[(.+?)\]:[ ]*(.+?)[ ]*$/', $Line['text'], $matches)) + { + $this->DefinitionData['Abbreviation'][$matches[1]] = $matches[2]; + + $Block = [ + 'hidden' => true, + ]; + + return $Block; + } + } + + # + # Footnote + + protected function blockFootnote($Line) + { + if (\preg_match('/^\[\^(.+?)\]:[ ]?(.*)$/', $Line['text'], $matches)) + { + $Block = [ + 'label' => $matches[1], + 'text' => $matches[2], + 'hidden' => true, + ]; + + return $Block; + } + } + + protected function blockFootnoteContinue($Line, $Block) + { + if ($Line['text'][0] === '[' && \preg_match('/^\[\^(.+?)\]:/', $Line['text'])) + { + return; + } + + if (isset($Block['interrupted'])) + { + if ($Line['indent'] >= 4) + { + $Block['text'] .= "\n\n" . $Line['text']; + + return $Block; + } + } + else + { + $Block['text'] .= "\n" . $Line['text']; + + return $Block; + } + } + + protected function blockFootnoteComplete($Block) + { + $this->DefinitionData['Footnote'][$Block['label']] = [ + 'text' => $Block['text'], + 'count' => null, + 'number' => null, + ]; + + return $Block; + } + + # + # Definition List + + protected function blockDefinitionList($Line, $Block) + { + if ( ! isset($Block) || $Block['type'] !== 'Paragraph') + { + return; + } + + $Element = [ + 'name' => 'dl', + 'elements' => [], + ]; + + $terms = \explode("\n", $Block['element']['handler']['argument']); + + foreach ($terms as $term) + { + $Element['elements'] []= [ + 'name' => 'dt', + 'handler' => [ + 'function' => 'lineElements', + 'argument' => $term, + 'destination' => 'elements' + ], + ]; + } + + $Block['element'] = $Element; + + $Block = $this->addDdElement($Line, $Block); + + return $Block; + } + + protected function blockDefinitionListContinue($Line, array $Block) + { + if ($Line['text'][0] === ':') + { + $Block = $this->addDdElement($Line, $Block); + + return $Block; + } + else + { + if (isset($Block['interrupted']) && $Line['indent'] === 0) + { + return; + } + + if (isset($Block['interrupted'])) + { + $Block['dd']['handler']['function'] = 'textElements'; + $Block['dd']['handler']['argument'] .= "\n\n"; + + $Block['dd']['handler']['destination'] = 'elements'; + + unset($Block['interrupted']); + } + + $text = \substr($Line['body'], min($Line['indent'], 4)); + + $Block['dd']['handler']['argument'] .= "\n" . $text; + + return $Block; + } + } + + protected function addDdElement(array $Line, array $Block) + { + $text = \substr($Line['text'], 1); + $text = \trim($text); + + unset($Block['dd']); + + $Block['dd'] = [ + 'name' => 'dd', + 'handler' => [ + 'function' => 'lineElements', + 'argument' => $text, + 'destination' => 'elements' + ], + ]; + + if (isset($Block['interrupted'])) + { + $Block['dd']['handler']['function'] = 'textElements'; + + unset($Block['interrupted']); + } + + $Block['element']['elements'] []= & $Block['dd']; + + return $Block; + } + + # + # Header + + protected function blockHeader($Line) + { + $level = \strspn($Line['text'], '#'); + + if ($level > 6) + { + return; + } + + $text = \trim($Line['text'], '#'); + + if ($this->strictMode && isset($text[0]) && $text[0] !== ' ') + { + return; + } + + $text = \trim($text, ' '); + + $Block = [ + 'element' => [ + 'name' => 'h' . $level, + 'handler' => [ + 'function' => 'lineElements', + 'argument' => $text, + 'destination' => 'elements', + ] + ], + ]; + + if ($Block !== null && \preg_match('/[ #]*{('.$this->regexAttribute.'+)}[ ]*$/', $Block['element']['handler']['argument'], $matches, PREG_OFFSET_CAPTURE)) + { + $attributeString = $matches[1][0]; + + $Block['element']['attributes'] = $this->parseAttributeData($attributeString); + + $Block['element']['handler']['argument'] = \substr($Block['element']['handler']['argument'], 0, (int) $matches[0][1]); + } + + return $Block; + } + + # + # List + + protected function blockList($Line, array $CurrentBlock = null) + { + list($name, $pattern) = $Line['text'][0] <= '-' ? ['ul', '[*+-]'] : ['ol', '[0-9]{1,9}+[.\)]']; + + if (\preg_match('/^('.$pattern.'([ ]++|$))(.*+)/', $Line['text'], $matches)) + { + $contentIndent = \strlen($matches[2]); + + if ($contentIndent >= 5) + { + $contentIndent -= 1; + $matches[1] = \substr($matches[1], 0, -$contentIndent); + $matches[3] = \str_repeat(' ', $contentIndent) . $matches[3]; + } + elseif ($contentIndent === 0) + { + $matches[1] .= ' '; + } + + $markerWithoutWhitespace = \strstr($matches[1], ' ', true); + + $Block = [ + 'indent' => $Line['indent'], + 'pattern' => $pattern, + 'data' => [ + 'type' => $name, + 'marker' => $matches[1], + 'markerType' => ($name === 'ul' ? $markerWithoutWhitespace : \substr($markerWithoutWhitespace, -1)), + ], + 'element' => [ + 'name' => $name, + 'elements' => [], + ], + ]; + $Block['data']['markerTypeRegex'] = \preg_quote($Block['data']['markerType'], '/'); + + if ($name === 'ol') + { + $listStart = \ltrim(\strstr($matches[1], $Block['data']['markerType'], true), '0') ?: '0'; + + if ($listStart !== '1') + { + if ( + isset($CurrentBlock) + && $CurrentBlock['type'] === 'Paragraph' + && ! isset($CurrentBlock['interrupted']) + ) { + return; + } + + $Block['element']['attributes'] = ['start' => $listStart]; + } + } + + $Block['li'] = [ + 'name' => 'li', + 'handler' => [ + 'function' => 'li', + 'argument' => !empty($matches[3]) ? [$matches[3]] : [], + 'destination' => 'elements' + ] + ]; + + $Block['element']['elements'] []= & $Block['li']; + + return $Block; + } + } + + protected function blockListContinue($Line, array $Block) + { + if (isset($Block['interrupted']) && empty($Block['li']['handler']['argument'])) + { + return null; + } + + $requiredIndent = ($Block['indent'] + \strlen($Block['data']['marker'])); + + if ($Line['indent'] < $requiredIndent + && ( + ( + $Block['data']['type'] === 'ol' + && \preg_match('/^[0-9]++'.$Block['data']['markerTypeRegex'].'(?:[ ]++(.*)|$)/', $Line['text'], $matches) + ) || ( + $Block['data']['type'] === 'ul' + && \preg_match('/^'.$Block['data']['markerTypeRegex'].'(?:[ ]++(.*)|$)/', $Line['text'], $matches) + ) + ) + ) { + if (isset($Block['interrupted'])) + { + $Block['li']['handler']['argument'] []= ''; + + $Block['loose'] = true; + + unset($Block['interrupted']); + } + + unset($Block['li']); + + $text = isset($matches[1]) ? $matches[1] : ''; + + $Block['indent'] = $Line['indent']; + + $Block['li'] = [ + 'name' => 'li', + 'handler' => [ + 'function' => 'li', + 'argument' => [$text], + 'destination' => 'elements' + ] + ]; + + $Block['element']['elements'] []= & $Block['li']; + + return $Block; + } + elseif ($Line['indent'] < $requiredIndent && $this->blockList($Line)) + { + return null; + } + + if ($Line['text'][0] === '[' && $this->blockReference($Line)) + { + return $Block; + } + + if ($Line['indent'] >= $requiredIndent) + { + if (isset($Block['interrupted'])) + { + $Block['li']['handler']['argument'] []= ''; + + $Block['loose'] = true; + + unset($Block['interrupted']); + } + + $text = \substr($Line['body'], $requiredIndent); + + $Block['li']['handler']['argument'] []= $text; + + return $Block; + } + + if ( ! isset($Block['interrupted'])) + { + $text = \preg_replace('/^[ ]{0,'.$requiredIndent.'}+/', '', $Line['body']); + + $Block['li']['handler']['argument'] []= $text; + + return $Block; + } + } + + protected function blockListComplete(array $Block) + { + if (isset($Block['loose'])) + { + foreach ($Block['element']['elements'] as &$li) + { + if (end($li['handler']['argument']) !== '') + { + $li['handler']['argument'] []= ''; + } + } + } + + return $Block; + } + + # + # Quote + + protected function blockQuote($Line) + { + if (\preg_match('/^>[ ]?+(.*+)/', $Line['text'], $matches)) + { + $Block = [ + 'element' => [ + 'name' => 'blockquote', + 'handler' => [ + 'function' => 'linesElements', + 'argument' => (array) $matches[1], + 'destination' => 'elements', + ] + ], + ]; + + return $Block; + } + } + + protected function blockQuoteContinue($Line, array $Block) + { + if (isset($Block['interrupted'])) + { + return; + } + + if ($Line['text'][0] === '>' && \preg_match('/^>[ ]?+(.*+)/', $Line['text'], $matches)) + { + $Block['element']['handler']['argument'] []= $matches[1]; + + return $Block; + } + + if ( ! isset($Block['interrupted'])) + { + $Block['element']['handler']['argument'] []= $Line['text']; + + return $Block; + } + } + + # + # Rule + + protected function blockRule($Line) + { + $marker = $Line['text'][0]; + + if (\substr_count($Line['text'], $marker) >= 3 && \rtrim($Line['text'], " $marker") === '') + { + $Block = [ + 'element' => [ + 'name' => 'hr', + ], + ]; + + return $Block; + } + } + + # + # Setext + + protected function blockSetextHeader($Line, array $Block = null) + { + if ( ! isset($Block) || $Block['type'] !== 'Paragraph' || isset($Block['interrupted'])) + { + return; + } + + if ($Line['indent'] < 4 && \rtrim(\rtrim($Line['text'], ' '), $Line['text'][0]) === '') + { + $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2'; + } + + if ($Block !== null && \preg_match('/[ ]*{('.$this->regexAttribute.'+)}[ ]*$/', $Block['element']['handler']['argument'], $matches, PREG_OFFSET_CAPTURE)) + { + $attributeString = $matches[1][0]; + + $Block['element']['attributes'] = $this->parseAttributeData($attributeString); + + $Block['element']['handler']['argument'] = \substr($Block['element']['handler']['argument'], 0, (int) $matches[0][1]); + } + + return $Block; + } + + protected function inlineFootnoteMarker($Excerpt) + { + if (\preg_match('/^\[\^(.+?)\]/', $Excerpt['text'], $matches)) + { + $name = $matches[1]; + + if ( ! isset($this->DefinitionData['Footnote'][$name])) + { + return; + } + + $this->DefinitionData['Footnote'][$name]['count'] ++; + + if ( ! isset($this->DefinitionData['Footnote'][$name]['number'])) + { + $this->DefinitionData['Footnote'][$name]['number'] = ++ $this->footnoteCount; # ยป & + } + + $Element = [ + 'name' => 'sup', + 'attributes' => ['id' => 'fnref'.$this->DefinitionData['Footnote'][$name]['count'].':'.$name], + 'element' => [ + 'name' => 'a', + 'attributes' => ['href' => '#fn:'.$name, 'class' => 'footnote-ref'], + 'text' => $this->DefinitionData['Footnote'][$name]['number'], + ], + ]; + + return [ + 'extent' => \strlen($matches[0]), + 'element' => $Element, + ]; + } + } + + private int $footnoteCount = 0; + + # + # Markup + + protected function blockMarkup($Line) + { + if ($this->markupEscaped || $this->safeMode) + { + return; + } + + if (\preg_match('/^<(\w[\w-]*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches)) + { + $element = \strtolower($matches[1]); + + if (\in_array($element, $this->textLevelElements)) + { + return; + } + + $Block = [ + 'name' => $matches[1], + 'depth' => 0, + 'element' => [ + 'rawHtml' => $Line['text'], + 'autobreak' => true, + ], + ]; + + $length = \strlen($matches[0]); + $remainder = \substr($Line['text'], $length); + + if (trim($remainder) === '') + { + if (isset($matches[2]) || \in_array($matches[1], $this->voidElements)) + { + $Block['closed'] = true; + $Block['void'] = true; + } + } + else + { + if (isset($matches[2]) || \in_array($matches[1], $this->voidElements)) + { + return; + } + if (\preg_match('/<\/'.$matches[1].'>[ ]*$/i', $remainder)) + { + $Block['closed'] = true; + } + } + + return $Block; + } + } + + protected function blockMarkupContinue($Line, array $Block) + { + if (isset($Block['closed'])) + { + return; + } + + if (\preg_match('/^<'.$Block['name'].'(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*>/i', $Line['text'])) # open + { + $Block['depth'] ++; + } + + if (\preg_match('/(.*?)<\/'.$Block['name'].'>[ ]*$/i', $Line['text'], $matches)) # close + { + if ($Block['depth'] > 0) + { + $Block['depth'] --; + } + else + { + $Block['closed'] = true; + } + } + + if (isset($Block['interrupted'])) + { + $Block['element']['rawHtml'] .= "\n"; + unset($Block['interrupted']); + } + + $Block['element']['rawHtml'] .= "\n".$Line['body']; + + return $Block; + } + + protected function blockMarkupComplete($Block) + { + if ( ! isset($Block['void'])) + { + $Block['element']['rawHtml'] = $this->processTag($Block['element']['rawHtml']); + } + + return $Block; + } + + protected function processTag($elementMarkup) # recursive + { + # http://stackoverflow.com/q/1148928/200145 + libxml_use_internal_errors(true); + + $DOMDocument = new \DOMDocument(); + + # http://stackoverflow.com/q/11309194/200145 + $elementMarkup = mb_convert_encoding($elementMarkup, 'HTML-ENTITIES', 'UTF-8'); + + # http://stackoverflow.com/q/4879946/200145 + $DOMDocument->loadHTML($elementMarkup); + $DOMDocument->removeChild($DOMDocument->doctype); + $DOMDocument->replaceChild($DOMDocument->firstChild->firstChild->firstChild, $DOMDocument->firstChild); + + $elementText = ''; + + if ($DOMDocument->documentElement->getAttribute('markdown') === '1') + { + foreach ($DOMDocument->documentElement->childNodes as $Node) + { + $elementText .= $DOMDocument->saveHTML($Node); + } + + $DOMDocument->documentElement->removeAttribute('markdown'); + + $elementText = "\n".$this->text($elementText)."\n"; + } + else + { + foreach ($DOMDocument->documentElement->childNodes as $Node) + { + $nodeMarkup = $DOMDocument->saveHTML($Node); + + if ($Node instanceof \DOMElement && ! \in_array($Node->nodeName, $this->textLevelElements)) + { + $elementText .= $this->processTag($nodeMarkup); + } + else + { + $elementText .= $nodeMarkup; + } + } + } + + # because we don't want for markup to get encoded + $DOMDocument->documentElement->nodeValue = 'placeholder\x1A'; + + $markup = $DOMDocument->saveHTML($DOMDocument->documentElement); + $markup = \str_replace('placeholder\x1A', $elementText, $markup); + + return $markup; + } + + # + # Reference + + protected function blockReference($Line) + { + if (strpos($Line['text'], ']') !== false + && \preg_match('/^\[(.+?)\]:[ ]*+?(?:[ ]+["\'(](.+)["\')])?[ ]*+$/', $Line['text'], $matches) + ) { + $id = \strtolower($matches[1]); + + $Data = [ + 'url' => UriFactory::build($matches[2]), + 'title' => isset($matches[3]) ? $matches[3] : null, + ]; + + $this->DefinitionData['Reference'][$id] = $Data; + + $Block = [ + 'element' => [], + ]; + + return $Block; + } + } + + # + # Table + + protected function blockTable($Line, array $Block = null) + { + if ( ! isset($Block) || $Block['type'] !== 'Paragraph' || isset($Block['interrupted'])) + { + return; + } + + if ( + \strpos($Block['element']['handler']['argument'], '|') === false + && \strpos($Line['text'], '|') === false + && \strpos($Line['text'], ':') === false + || \strpos($Block['element']['handler']['argument'], "\n") !== false + ) { + return; + } + + if (\rtrim($Line['text'], ' -:|') !== '') + { + return; + } + + $alignments = []; + + $divider = $Line['text']; + + $divider = \trim($divider); + $divider = \trim($divider, '|'); + + $dividerCells = \explode('|', $divider); + + foreach ($dividerCells as $dividerCell) + { + $dividerCell = \trim($dividerCell); + + if ($dividerCell === '') + { + return; + } + + $alignment = null; + + if ($dividerCell[0] === ':') + { + $alignment = 'left'; + } + + if (\substr($dividerCell, - 1) === ':') + { + $alignment = $alignment === 'left' ? 'center' : 'right'; + } + + $alignments []= $alignment; + } + + # ~ + + $HeaderElements = []; + + $header = $Block['element']['handler']['argument']; + + $header = \trim($header); + $header = \trim($header, '|'); + + $headerCells = \explode('|', $header); + + if (count($headerCells) !== \count($alignments)) + { + return; + } + + foreach ($headerCells as $index => $headerCell) + { + $headerCell = \trim($headerCell); + + $HeaderElement = [ + 'name' => 'th', + 'handler' => [ + 'function' => 'lineElements', + 'argument' => $headerCell, + 'destination' => 'elements', + ] + ]; + + if (isset($alignments[$index])) + { + $alignment = $alignments[$index]; + + $HeaderElement['attributes'] = [ + 'style' => "text-align: $alignment;", + ]; + } + + $HeaderElements []= $HeaderElement; + } + + # ~ + + $Block = [ + 'alignments' => $alignments, + 'identified' => true, + 'element' => [ + 'name' => 'table', + 'elements' => [], + ], + ]; + + $Block['element']['elements'] []= [ + 'name' => 'thead', + ]; + + $Block['element']['elements'] []= [ + 'name' => 'tbody', + 'elements' => [], + ]; + + $Block['element']['elements'][0]['elements'] []= [ + 'name' => 'tr', + 'elements' => $HeaderElements, + ]; + + return $Block; + } + + protected function blockTableContinue($Line, array $Block) + { + if (isset($Block['interrupted'])) + { + return; + } + + if (count($Block['alignments']) === 1 || $Line['text'][0] === '|' || \strpos($Line['text'], '|')) + { + $Elements = []; + + $row = $Line['text']; + + $row = \trim($row); + $row = \trim($row, '|'); + + preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]++`|`)++/', $row, $matches); + + $cells = array_slice($matches[0], 0, \count($Block['alignments'])); + + foreach ($cells as $index => $cell) + { + $cell = \trim($cell); + + $Element = [ + 'name' => 'td', + 'handler' => [ + 'function' => 'lineElements', + 'argument' => $cell, + 'destination' => 'elements', + ] + ]; + + if (isset($Block['alignments'][$index])) + { + $Element['attributes'] = [ + 'style' => 'text-align: ' . $Block['alignments'][$index] . ';', + ]; + } + + $Elements []= $Element; + } + + $Element = [ + 'name' => 'tr', + 'elements' => $Elements, + ]; + + $Block['element']['elements'][1]['elements'] []= $Element; + + return $Block; + } + } + + # + # ~ + # + + protected function paragraph($Line) + { + return [ + 'type' => 'Paragraph', + 'element' => [ + 'name' => 'p', + 'handler' => [ + 'function' => 'lineElements', + 'argument' => $Line['text'], + 'destination' => 'elements', + ], + ], + ]; + } + + protected function paragraphContinue($Line, array $Block) + { + if (isset($Block['interrupted'])) + { + return; + } + + $Block['element']['handler']['argument'] .= "\n".$Line['text']; + + return $Block; + } + + # + # Inline Elements + # + + protected $InlineTypes = [ + '!' => ['Image'], + '&' => ['SpecialCharacter'], + '*' => ['Emphasis'], + ':' => ['Url'], + '<' => ['UrlTag', 'EmailTag', 'Markup'], + '[' => ['FootnoteMarker', 'Link'], + '_' => ['Emphasis'], + '`' => ['Code'], + '~' => ['Strikethrough'], + '\\' => ['EscapeSequence'], ]; - /** - * Regex for strong. - * - * @var string[] - * @since 1.0.0 - */ - protected static $strongRegex = [ - '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*[*])+?)[*]{2}(?![*])/s', - ]; + # ~ + + protected $inlineMarkerList = '!*_&[:<`~\\'; + + # + # ~ + # + + public function line($text, $nonNestables = []) + { + return $this->elements($this->lineElements($text, $nonNestables)); + } + + protected function lineElements($text, $nonNestables = []) + { + # standardize line breaks + $text = \str_replace(["\r\n", "\r"], "\n", $text); + + $Elements = []; + + $nonNestables = (empty($nonNestables) + ? [] + : \array_combine($nonNestables, $nonNestables) + ); + + # $excerpt is based on the first occurrence of a marker + + while ($excerpt = \strpbrk($text, $this->inlineMarkerList)) + { + $marker = $excerpt[0]; + + $markerPosition = \strlen($text) - \strlen($excerpt); + + $Excerpt = ['text' => $excerpt, 'context' => $text]; + + foreach ($this->InlineTypes[$marker] as $inlineType) + { + # check to see if the current inline type is nestable in the current context + + if (isset($nonNestables[$inlineType])) + { + continue; + } + + $Inline = $this->{"inline$inlineType"}($Excerpt); + + if ( ! isset($Inline)) + { + continue; + } + + # makes sure that the inline belongs to "our" marker + + if (isset($Inline['position']) && $Inline['position'] > $markerPosition) + { + continue; + } + + # sets a default inline position + + if ( ! isset($Inline['position'])) + { + $Inline['position'] = $markerPosition; + } + + # cause the new element to 'inherit' our non nestables + + + $Inline['element']['nonNestables'] = isset($Inline['element']['nonNestables']) + ? \array_merge($Inline['element']['nonNestables'], $nonNestables) + : $nonNestables + ; + + # the text that comes before the inline + $unmarkedText = \substr($text, 0, $Inline['position']); + + # compile the unmarked text + $InlineText = $this->inlineText($unmarkedText); + $Elements[] = $InlineText['element']; + + # compile the inline + $Elements[] = $this->extractElement($Inline); + + # remove the examined text + $text = \substr($text, $Inline['position'] + $Inline['extent']); + + continue 2; + } + + # the marker does not belong to an inline + + $unmarkedText = \substr($text, 0, $markerPosition + 1); + + $InlineText = $this->inlineText($unmarkedText); + $Elements[] = $InlineText['element']; + + $text = \substr($text, $markerPosition + 1); + } + + $InlineText = $this->inlineText($text); + $Elements[] = $InlineText['element']; + + foreach ($Elements as &$Element) + { + if ( ! isset($Element['autobreak'])) + { + $Element['autobreak'] = false; + } + } + + return $Elements; + } + + # + # ~ + # + + protected function inlineText($text) + { + $Inline = [ + 'extent' => \strlen($text), + 'element' => [], + ]; + + $Inline['element']['elements'] = self::pregReplaceElements( + $this->breaksEnabled ? '/[ ]*+\n/' : '/(?:[ ]*+\\\\|[ ]{2,}+)\n/', + [ + ['name' => 'br'], + ['text' => "\n"], + ], + $text + ); + + if (isset($this->DefinitionData['Abbreviation'])) + { + foreach ($this->DefinitionData['Abbreviation'] as $abbreviation => $meaning) + { + $this->currentAbreviation = $abbreviation; + $this->currentMeaning = $meaning; + + $Inline['element'] = $this->elementApplyRecursiveDepthFirst( + [$this, 'insertAbreviation'], + $Inline['element'] + ); + } + } + + return $Inline; + } + + protected function inlineCode($Excerpt) + { + $marker = $Excerpt['text'][0]; + + if (\preg_match('/^(['.$marker.']++)[ ]*+(.+?)[ ]*+(? \strlen($matches[0]), + 'element' => [ + 'name' => 'code', + 'text' => $text, + ], + ]; + } + } + + protected function inlineEmailTag($Excerpt) + { + $hostnameLabel = '[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?'; + + $commonMarkEmail = '[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]++@' + . $hostnameLabel . '(?:\.' . $hostnameLabel . ')*'; + + if (strpos($Excerpt['text'], '>') !== false + && \preg_match("/^<((mailto:)?$commonMarkEmail)>/i", $Excerpt['text'], $matches) + ){ + $url = $matches[1]; + + if ( ! isset($matches[2])) + { + $url = "mailto:$url"; + } + + return [ + 'extent' => \strlen($matches[0]), + 'element' => [ + 'name' => 'a', + 'text' => $matches[1], + 'attributes' => [ + 'href' => UriFactory::build($url), + ], + ], + ]; + } + } + + protected function inlineEmphasis($Excerpt) + { + if ( ! isset($Excerpt['text'][1])) + { + return; + } + + $marker = $Excerpt['text'][0]; + + if ($Excerpt['text'][1] === $marker && \preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches)) + { + $emphasis = 'strong'; + } + elseif (\preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches)) + { + $emphasis = 'em'; + } + else + { + return; + } + + return [ + 'extent' => \strlen($matches[0]), + 'element' => [ + 'name' => $emphasis, + 'handler' => [ + 'function' => 'lineElements', + 'argument' => $matches[1], + 'destination' => 'elements', + ] + ], + ]; + } + + protected function inlineEscapeSequence($Excerpt) + { + if (isset($Excerpt['text'][1]) && \in_array($Excerpt['text'][1], $this->specialCharacters)) + { + return [ + 'element' => ['rawHtml' => $Excerpt['text'][1]], + 'extent' => 2, + ]; + } + } + + protected function inlineImage($Excerpt) + { + if ( ! isset($Excerpt['text'][1]) || $Excerpt['text'][1] !== '[') + { + return; + } + + $Excerpt['text']= \substr($Excerpt['text'], 1); + + $Link = $this->inlineLink($Excerpt); + + if ($Link === null) + { + return; + } + + $Inline = [ + 'extent' => $Link['extent'] + 1, + 'element' => [ + 'name' => 'img', + 'attributes' => [ + 'src' => $Link['element']['attributes']['href'], + 'alt' => $Link['element']['handler']['argument'], + ], + 'autobreak' => true, + ], + ]; + + $Inline['element']['attributes'] += $Link['element']['attributes']; + + unset($Inline['element']['attributes']['href']); + + return $Inline; + } + + protected function inlineLink($Excerpt) + { + $Element = [ + 'name' => 'a', + 'handler' => [ + 'function' => 'lineElements', + 'argument' => null, + 'destination' => 'elements', + ], + 'nonNestables' => ['Url', 'Link'], + 'attributes' => [ + 'href' => null, + 'title' => null, + ], + ]; + + $extent = 0; + + $remainder = $Excerpt['text']; + + if (\preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches)) + { + $Element['handler']['argument'] = $matches[1]; + + $extent += \strlen($matches[0]); + + $remainder = \substr($remainder, $extent); + } + else + { + return; + } + + if (\preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*"|\'[^\']*\'))?\s*[)]/', $remainder, $matches)) + { + $Element['attributes']['href'] = UriFactory::build($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['handler']['argument']; + $definition = \strtolower($definition); + + $extent += \strlen($matches[0]); + } + else + { + $definition = \strtolower($Element['handler']['argument']); + } + + if ( ! isset($this->DefinitionData['Reference'][$definition])) + { + return; + } + + $Definition = $this->DefinitionData['Reference'][$definition]; + + $Element['attributes']['href'] = $Definition['url']; + $Element['attributes']['title'] = $Definition['title']; + } + + $Link = [ + 'extent' => $extent, + 'element' => $Element, + ]; + + $remainder = $Link !== null ? \substr($Excerpt['text'], $Link['extent']) : ''; + + if (\preg_match('/^[ ]*{('.$this->regexAttribute.'+)}/', $remainder, $matches)) + { + $Link['element']['attributes'] += $this->parseAttributeData($matches[1]); + + $Link['extent'] += \strlen($matches[0]); + } + + return $Link; + } + + protected function parseAttributeData($attributeString) + { + $Data = []; + + $attributes = preg_split('/[ ]+/', $attributeString, - 1, PREG_SPLIT_NO_EMPTY); + + foreach ($attributes as $attribute) + { + if ($attribute[0] === '#') + { + $Data['id'] = \substr($attribute, 1); + } + else # "." + { + $classes []= \substr($attribute, 1); + } + } + + if (isset($classes)) + { + $Data['class'] = implode(' ', $classes); + } + + return $Data; + } + + private $currentAbreviation; + private $currentMeaning; + + protected function insertAbreviation(array $Element) + { + if (isset($Element['text'])) + { + $Element['elements'] = self::pregReplaceElements( + '/\b'.\preg_quote($this->currentAbreviation, '/').'\b/', + [ + [ + 'name' => 'abbr', + 'attributes' => [ + 'title' => $this->currentMeaning, + ], + 'text' => $this->currentAbreviation, + ] + ], + $Element['text'] + ); + + unset($Element['text']); + } + + return $Element; + } + + protected function inlineMarkup($Excerpt) + { + if ($this->markupEscaped || $this->safeMode || \strpos($Excerpt['text'], '>') === false) + { + return; + } + + if ($Excerpt['text'][1] === '/' && \preg_match('/^<\/\w[\w-]*+[ ]*+>/s', $Excerpt['text'], $matches)) + { + return [ + 'element' => ['rawHtml' => $matches[0]], + 'extent' => \strlen($matches[0]), + ]; + } + + if ($Excerpt['text'][1] === '!' && \preg_match('/^/s', $Excerpt['text'], $matches)) + { + return [ + 'element' => ['rawHtml' => $matches[0]], + 'extent' => \strlen($matches[0]), + ]; + } + + if ($Excerpt['text'][1] !== ' ' && \preg_match('/^<\w[\w-]*+(?:[ ]*+'.$this->regexHtmlAttribute.')*+[ ]*+\/?>/s', $Excerpt['text'], $matches)) + { + return [ + 'element' => ['rawHtml' => $matches[0]], + 'extent' => \strlen($matches[0]), + ]; + } + } + + protected function inlineSpecialCharacter($Excerpt) + { + if (\substr($Excerpt['text'], 1, 1) !== ' ' && \strpos($Excerpt['text'], ';') !== false + && \preg_match('/^&(#?+[0-9a-zA-Z]++);/', $Excerpt['text'], $matches) + ) { + return [ + 'element' => ['rawHtml' => '&' . $matches[1] . ';'], + 'extent' => \strlen($matches[0]), + ]; + } + + return; + } + + protected function inlineStrikethrough($Excerpt) + { + if ( ! isset($Excerpt['text'][1])) + { + return; + } + + if ($Excerpt['text'][1] === '~' && \preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches)) + { + return [ + 'extent' => \strlen($matches[0]), + 'element' => [ + 'name' => 'del', + 'handler' => [ + 'function' => 'lineElements', + 'argument' => $matches[1], + 'destination' => 'elements', + ] + ], + ]; + } + } + + protected function inlineUrl($Excerpt) + { + if ($this->urlsLinked !== true || ! isset($Excerpt['text'][2]) || $Excerpt['text'][2] !== '/') + { + return; + } + + if (strpos($Excerpt['context'], 'http') !== false + && \preg_match('/\bhttps?+:[\/]{2}[^\s<]+\b\/*+/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE) + ) { + $url = $matches[0][0]; + + $Inline = [ + 'extent' => \strlen($matches[0][0]), + 'position' => $matches[0][1], + 'element' => [ + 'name' => 'a', + 'text' => $url, + 'attributes' => [ + 'href' => UriFactory::build($url), + ], + ], + ]; + + return $Inline; + } + } + + protected function inlineUrlTag($Excerpt) + { + if (strpos($Excerpt['text'], '>') !== false && \preg_match('/^<(\w++:\/{2}[^ >]++)>/i', $Excerpt['text'], $matches)) + { + $url = $matches[1]; + + return [ + 'extent' => \strlen($matches[0]), + 'element' => [ + 'name' => 'a', + 'text' => $url, + 'attributes' => [ + 'href' => UriFactory::build($url), + ], + ], + ]; + } + } + + # ~ + + protected function unmarkedText($text) + { + $Inline = $this->inlineText($text); + return $this->element($Inline['element']); + } + + # + # Handlers + # + + protected function handle(array $Element) + { + if (isset($Element['handler'])) + { + if (!isset($Element['nonNestables'])) + { + $Element['nonNestables'] = []; + } + + if (is_string($Element['handler'])) + { + $function = $Element['handler']; + $argument = $Element['text']; + unset($Element['text']); + $destination = 'rawHtml'; + } + else + { + $function = $Element['handler']['function']; + $argument = $Element['handler']['argument']; + $destination = $Element['handler']['destination']; + } + + $Element[$destination] = $this->{$function}($argument, $Element['nonNestables']); + + if ($destination === 'handler') + { + $Element = $this->handle($Element); + } + + unset($Element['handler']); + } + + return $Element; + } + + protected function handleElementRecursive(array $Element) + { + return $this->elementApplyRecursive([$this, 'handle'], $Element); + } + + protected function handleElementsRecursive(array $Elements) + { + return $this->elementsApplyRecursive([$this, 'handle'], $Elements); + } + + protected function elementApplyRecursive($closure, array $Element) + { + $Element = $closure($Element); + + if (isset($Element['elements'])) + { + $Element['elements'] = $this->elementsApplyRecursive($closure, $Element['elements']); + } + elseif (isset($Element['element'])) + { + $Element['element'] = $this->elementApplyRecursive($closure, $Element['element']); + } + + return $Element; + } + + protected function elementApplyRecursiveDepthFirst($closure, array $Element) + { + if (isset($Element['elements'])) + { + $Element['elements'] = $this->elementsApplyRecursiveDepthFirst($closure, $Element['elements']); + } + elseif (isset($Element['element'])) + { + $Element['element'] = $this->elementsApplyRecursiveDepthFirst($closure, $Element['element']); + } + + $Element = $closure($Element); + + return $Element; + } + + protected function elementsApplyRecursive($closure, array $Elements) + { + foreach ($Elements as &$Element) + { + $Element = $this->elementApplyRecursive($closure, $Element); + } + + return $Elements; + } + + protected function elementsApplyRecursiveDepthFirst($closure, array $Elements) + { + foreach ($Elements as &$Element) + { + $Element = $this->elementApplyRecursiveDepthFirst($closure, $Element); + } + + return $Elements; + } + + protected function element(array $Element) + { + if ($this->safeMode) + { + $Element = $this->sanitiseElement($Element); + } + + # identity map if element has no handler + $Element = $this->handle($Element); + + $hasName = isset($Element['name']); + + $markup = ''; + + if ($hasName) + { + $markup .= '<' . $Element['name']; + + if (isset($Element['attributes'])) + { + foreach ($Element['attributes'] as $name => $value) + { + if ($value === null) + { + continue; + } + + $markup .= " $name=\"".self::escape($value).'"'; + } + } + } + + $permitRawHtml = false; + + if (isset($Element['text'])) + { + $text = $Element['text']; + } + // very strongly consider an alternative if you're writing an + // extension + elseif (isset($Element['rawHtml'])) + { + $text = $Element['rawHtml']; + + $allowRawHtmlInSafeMode = isset($Element['allowRawHtmlInSafeMode']) && $Element['allowRawHtmlInSafeMode']; + $permitRawHtml = !$this->safeMode || $allowRawHtmlInSafeMode; + } + + $hasContent = isset($text) || isset($Element['element']) || isset($Element['elements']); + + if ($hasContent) + { + $markup .= $hasName ? '>' : ''; + + if (isset($Element['elements'])) + { + $markup .= $this->elements($Element['elements']); + } + elseif (isset($Element['element'])) + { + $markup .= $this->element($Element['element']); + } + else + { + if (!$permitRawHtml) + { + $markup .= self::escape($text, true); + } + else + { + $markup .= $text; + } + } + + $markup .= $hasName ? '' : ''; + } + elseif ($hasName) + { + $markup .= ' />'; + } + + return $markup; + } + + protected function elements(array $Elements) + { + $markup = ''; + + $autoBreak = true; + + foreach ($Elements as $Element) + { + if (empty($Element)) + { + continue; + } + + $autoBreakNext = (isset($Element['autobreak']) + ? $Element['autobreak'] : isset($Element['name']) + ); + // (autobreak === false) covers both sides of an element + $autoBreak = !$autoBreak ? $autoBreak : $autoBreakNext; + + $markup .= ($autoBreak ? "\n" : '') . $this->element($Element); + $autoBreak = $autoBreakNext; + } + + $markup .= $autoBreak ? "\n" : ''; + + return $markup; + } + + # ~ + + protected function li($lines) + { + $Elements = $this->linesElements($lines); + + if ( ! \in_array('', $lines) + && isset($Elements[0]) && isset($Elements[0]['name']) + && $Elements[0]['name'] === 'p' + ) { + unset($Elements[0]['name']); + } + + return $Elements; + } + + # + # AST Convenience + # /** - * Regex for underline. - * - * @var string[] - * @since 1.0.0 + * Replace occurrences $regexp with $Elements in $text. Return an array of + * elements representing the replacement. */ - protected static $underlineRegex = [ - '_' => '/^__((?:\\\\_|[^_]|_[^_]*_)+?)__(?!_)/us', + protected static function pregReplaceElements($regexp, $Elements, $text) + { + $newElements = []; + + while (\preg_match($regexp, $text, $matches, PREG_OFFSET_CAPTURE)) + { + $offset = (int) $matches[0][1]; + $before = \substr($text, 0, $offset); + $after = \substr($text, $offset + \strlen($matches[0][0])); + + $newElements[] = ['text' => $before]; + + foreach ($Elements as $Element) + { + $newElements[] = $Element; + } + + $text = $after; + } + + $newElements[] = ['text' => $text]; + + return $newElements; + } + + # + # Deprecated Methods + # + + public static function parse($text) + { + $parsedown = new self(); + + $markup = $parsedown->text($text); + + return $markup; + } + + protected function sanitiseElement(array $Element) + { + static $goodAttribute = '/^[a-zA-Z0-9][a-zA-Z0-9-_]*+$/'; + static $safeUrlNameToAtt = [ + 'a' => 'href', + 'img' => 'src', + ]; + + if ( ! isset($Element['name'])) + { + unset($Element['attributes']); + return $Element; + } + + if (isset($safeUrlNameToAtt[$Element['name']])) + { + $Element = $this->filterUnsafeUrlInAttribute($Element, $safeUrlNameToAtt[$Element['name']]); + } + + if ( ! empty($Element['attributes'])) + { + foreach ($Element['attributes'] as $att => $val) + { + # filter out badly parsed attribute + if ( ! \preg_match($goodAttribute, $att)) + { + unset($Element['attributes'][$att]); + } + # dump onevent attribute + elseif (self::striAtStart($att, 'on')) + { + unset($Element['attributes'][$att]); + } + } + } + + return $Element; + } + + protected function filterUnsafeUrlInAttribute(array $Element, $attribute) + { + foreach ($this->safeLinksWhitelist as $scheme) + { + if (self::striAtStart($Element['attributes'][$attribute], $scheme)) + { + return $Element; + } + } + + $Element['attributes'][$attribute] = \str_replace(':', '%3A', $Element['attributes'][$attribute]); + + return $Element; + } + + # + # Static Methods + # + + protected static function escape($text, $allowQuotes = false) + { + return htmlspecialchars($text, $allowQuotes ? ENT_NOQUOTES : ENT_QUOTES, 'UTF-8'); + } + + protected static function striAtStart($string, $needle) + { + $len = \strlen($needle); + + if ($len > \strlen($string)) + { + return false; + } + else + { + return \strtolower(\substr($string, 0, $len)) === \strtolower($needle); + } + } + + static function instance($name = 'default') + { + if (isset(self::$instances[$name])) + { + return self::$instances[$name]; + } + + $instance = new static(); + + self::$instances[$name] = $instance; + + return $instance; + } + + private static array $instances = []; + + # + # Fields + # + + protected $DefinitionData; + + # + # Read-Only + + protected array $specialCharacters = [ + '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|', '~' ]; - /** - * Regex for em. - * - * @var string[] - * @since 1.0.0 - */ - protected static $emRegex = [ + protected array $StrongRegex = [ + '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*+[*])+?)[*]{2}(?![*])/s', + '_' => '/^__((?:\\\\_|[^_]|_[^_]*+_)+?)__(?!_)/us', + ]; + + protected array $EmRegex = [ '*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s', '_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us', ]; - /** - * Regex for identifying html attributes. - * - * @var string - * @since 1.0.0 - */ - protected static $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*(?:\s*=\s*(?:[^"\'=<>`\s]+|"[^"]*"|\'[^\']*\'))?'; + protected string $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*+(?:\s*+=\s*+(?:[^"\'=<>`\s]+|"[^"]*+"|\'[^\']*+\'))?+'; - /** - * Void elements. - * - * @var string[] - * @since 1.0.0 - */ - protected static $voidElements = [ + protected array $voidElements = [ 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', ]; - /** - * Text elements. - * - * @var string[] - * @since 1.0.0 - */ - protected static $textLevelElements = [ + protected array $textLevelElements = [ 'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont', 'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing', 'i', 'rp', 'del', 'code', 'strike', 'marquee', @@ -146,1381 +2518,4 @@ class Markdown 'wbr', 'time', ]; - /** - * Inline identifiers. - * - * @var string[][] - * @since 1.0.0 - */ - protected static $inlineTypes = [ - '"' => ['SpecialCharacter'], - '!' => ['Image'], - '&' => ['SpecialCharacter'], - '*' => ['Emphasis'], - ':' => ['Url'], - '<' => ['UrlTag', 'EmailTag', 'SpecialCharacter'], - '>' => ['SpecialCharacter'], - '[' => ['Link'], - '_' => ['Emphasis'], - '`' => ['Code'], - '~' => ['Strikethrough'], - '\\' => ['EscapeSequence'], - ]; - - /** - * List of inline start markers. - * - * @var string - * @since 1.0.0 - */ - protected static $inlineMarkerList = '!"*_&[:<>`~\\'; - - /** - * Continuable elements. - * - * @var string[] - * @since 1.0.0 - */ - private static $continuable = [ - 'Code', 'FencedCode', 'List', 'Quote', 'Table', - ]; - - /** - * Completable elments. - * - * @var string[] - * @since 1.0.0 - */ - private static $completable = [ - 'Code', 'FencedCode', - ]; - - /** - * Safe link types whitelist. - * - * @var string[] - * @since 1.0.0 - */ - protected static $safeLinksWhitelist = [ - 'http://', 'https://', 'ftp://', 'ftps://', 'mailto:', - 'data:image/png;base64,', 'data:image/gif;base64,', 'data:image/jpeg;base64,', - 'irc:', 'ircs:', 'git:', 'ssh:', 'news:', 'steam:', - ]; - - /** - * Some definition data for elements - * - * @var string[] - * @since 1.0.0 - */ - private static $definitionData = []; - - /** - * Parse markdown - * - * @param string $text Markdown text - * - * @return string - * - * @since 1.0.0 - */ - public static function parse(string $text) : string - { - self::$definitionData = []; - - $text = \str_replace(["\r\n", "\r"], "\n", $text); - $text = \trim($text, "\n"); - $lines = \explode("\n", $text); - $markup = self::lines($lines); - - return \trim($markup, "\n"); - } - - /** - * Parse lines - * - * @param string[] $lines Markdown lines - * - * @return string - * - * @since 1.0.0 - */ - protected static function lines(array $lines) : string - { - $currentBlock = null; - - foreach ($lines as $line) { - if (\rtrim($line) === '') { - if (isset($currentBlock)) { - $currentBlock['interrupted'] = true; - } - - 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]) && $line[$indent] === ' ') { - ++$indent; - } - - $text = $indent > 0 ? \substr($line, $indent) : $line; - $lineArray = ['body' => $line, 'indent' => $indent, 'text' => $text]; - - if (isset($currentBlock['continuable'])) { - $block = self::{'block' . $currentBlock['type'] . 'Continue'}($lineArray, $currentBlock); - - if ($block !== null) { - $currentBlock = $block; - - continue; - } elseif (\in_array($currentBlock['type'], self::$completable)) { - $currentBlock = self::{'block' . $currentBlock['type'] . 'Complete'}($currentBlock); - } - } - - $marker = $text[0]; - $blockTypes = self::$unmarkedBlockTypes; - - if (isset(self::$blockTypes[$marker])) { - foreach (self::$blockTypes[$marker] as $blockType) { - $blockTypes[] = $blockType; - } - } - - foreach ($blockTypes as $blockType) { - $block = self::{'block' . $blockType}($lineArray, $currentBlock); - - if ($block !== null) { - $block['type'] = $blockType; - - if (!isset($block['identified'])) { - $blocks[] = $currentBlock; - - $block['identified'] = true; - } - - if (\in_array($blockType, self::$continuable)) { - $block['continuable'] = true; - } - - $currentBlock = $block; - - continue 2; - } - } - - if (isset($currentBlock) && !isset($currentBlock['type']) && !isset($currentBlock['interrupted'])) { - $currentBlock['element']['text'] .= "\n" . $text; - } else { - $blocks[] = $currentBlock; - $currentBlock = self::paragraph($lineArray); - $currentBlock['identified'] = true; - } - } - - if (isset($currentBlock['continuable']) && \in_array($currentBlock['type'], self::$completable)) { - $currentBlock = self::{'block' . $currentBlock['type'] . 'Complete'}($currentBlock); - } - - $blocks[] = $currentBlock; - unset($blocks[0]); - $markup = ''; - - foreach ($blocks as $block) { - if (isset($block['hidden'])) { - continue; - } - - $markup .= "\n"; - $markup .= isset($block['markup']) ? $block['markup'] : self::element($block['element']); - } - - $markup .= "\n"; - - return $markup; - } - - /** - * Handle block code - * - * @param array $lineArray Line information - * @param array $block Block information - * - * @return null|array - * - * @since 1.0.0 - */ - protected static function blockCode(array $lineArray, array $block = null) : ?array - { - if ($block !== null && !isset($block['type']) && !isset($block['interrupted'])) { - return null; - } - - if ($lineArray['indent'] < 4) { - return null; - } - - return [ - 'element' => [ - 'name' => 'pre', - 'handler' => 'element', - 'text' => [ - 'name' => 'code', - 'text' => \substr($lineArray['body'], 4), - ], - ], - ]; - } - - /** - * Handle continuable block code - * - * @param array $lineArray Line information - * @param array $block Block information - * - * @return null|array - * - * @since 1.0.0 - */ - protected static function blockCodeContinue(array $lineArray, array $block) : ?array - { - if ($lineArray['indent'] < 4) { - return null; - } - - if (isset($block['interrupted'])) { - $block['element']['text']['text'] .= "\n"; - - unset($block['interrupted']); - } - - $block['element']['text']['text'] .= "\n"; - $block['element']['text']['text'] .= \substr($lineArray['body'], 4); - - return $block; - } - - /** - * Handle completed code - * - * @param array $block Block information - * - * @return null|array - * - * @since 1.0.0 - */ - protected static function blockCodeComplete(?array $block) : ?array - { - return $block; - } - - /** - * Handle fenced code - * - * @param array $lineArray Line information - * - * @return null|array - * - * @since 1.0.0 - */ - protected static function blockFencedCode(array $lineArray) : ?array - { - if (!\preg_match('/^[' . $lineArray['text'][0] . ']{3,}[ ]*([^`]+)?[ ]*$/', $lineArray['text'], $matches)) { - return null; - } - - $elementArray = [ - 'name' => 'code', - 'text' => '', - ]; - - if (isset($matches[1])) { - $elementArray['attributes'] = [ - 'class' => 'language-' . $matches[1], - ]; - } - - return [ - 'char' => $lineArray['text'][0], - 'element' => [ - 'name' => 'pre', - 'handler' => 'element', - 'text' => $elementArray, - ], - ]; - } - - /** - * Handle continued fenced code - * - * @param array $lineArray Line information - * @param array $block Block information - * - * @return null|array - * - * @since 1.0.0 - */ - protected static function blockFencedCodeContinue(array $lineArray, array $block) : ?array - { - if (isset($block['complete'])) { - return null; - } - - if (isset($block['interrupted'])) { - $block['element']['text']['text'] .= "\n"; - - unset($block['interrupted']); - } - - if (\preg_match('/^' . $block['char'] . '{3,}[ ]*$/', $lineArray['text'])) { - $block['element']['text']['text'] = \substr($block['element']['text']['text'], 1); - $block['complete'] = true; - - return $block; - } - - $block['element']['text']['text'] .= "\n" . $lineArray['body']; - - return $block; - } - - /** - * Handle completed fenced block code - * - * @param array $block Block information - * - * @return null|array - * - * @since 1.0.0 - */ - protected static function blockFencedCodeComplete(?array $block) : ?array - { - return $block; - } - - /** - * Handle header element - * - * @param array $lineArray Line information - * - * @return null|array - * - * @since 1.0.0 - */ - protected static function blockHeader(array $lineArray) : ?array - { - if (!isset($lineArray['text'][1])) { - return null; - } - - $level = 1; - while (isset($lineArray['text'][$level]) && $lineArray['text'][$level] === '#') { - ++$level; - } - - if ($level > 6) { - return null; - } - - return [ - 'element' => [ - 'name' => 'h' . \min(6, $level), - 'text' => \trim($lineArray['text'], '# '), - 'handler' => 'line', - ], - ]; - } - - /** - * Handle list - * - * @param array $lineArray Line information - * - * @return null|array - * - * @since 1.0.0 - */ - protected static function blockList(array $lineArray) : ?array - { - list($name, $pattern) = $lineArray['text'][0] <= '-' ? ['ul', '[*+-]'] : ['ol', '[0-9]+[.]']; - - if (!\preg_match('/^(' . $pattern . '[ ]+)(.*)/', $lineArray['text'], $matches)) { - return null; - } - - $block = [ - 'indent' => $lineArray['indent'], - 'pattern' => $pattern, - 'element' => [ - 'name' => $name, - 'handler' => 'elements', - ], - ]; - - if ($name === 'ol') { - $listStart = \stristr($matches[0], '.', true); - - if ($listStart !== '1') { - $block['element']['attributes'] = ['start' => $listStart]; - } - } - - $block['li'] = [ - 'name' => 'li', - 'handler' => 'li', - 'text' => [ - $matches[2], - ], - ]; - - $block['element']['text'][] = &$block['li']; - - return $block; - } - - /** - * Handle continue list - * - * @param array $lineArray Line information - * @param array $block Block information - * - * @return null|array - * - * @since 1.0.0 - */ - protected static function blockListContinue(array $lineArray, array $block) : ?array - { - if ($block['indent'] === $lineArray['indent'] && \preg_match('/^' . $block['pattern'] . '(?:[ ]+(.*)|$)/', $lineArray['text'], $matches)) { - if (isset($block['interrupted'])) { - $block['li']['text'][] = ''; - - unset($block['interrupted']); - } - - unset($block['li']); - - $block['li'] = [ - 'name' => 'li', - 'handler' => 'li', - 'text' => [ - isset($matches[1]) ? $matches[1] : '', - ], - ]; - - $block['element']['text'][] = &$block['li']; - - return $block; - } - - if ($lineArray['text'][0] === '[' && self::blockReference($lineArray)) { - return $block; - } - - if (!isset($block['interrupted'])) { - $block['li']['text'][] = \preg_replace('/^[ ]{0,4}/', '', $lineArray['body']); - - return $block; - } - - if ($lineArray['indent'] > 0) { - $block['li']['text'][] = ''; - $block['li']['text'][] = \preg_replace('/^[ ]{0,4}/', '', $lineArray['body']); - - unset($block['interrupted']); - - return $block; - } - - return null; - } - - /** - * Handle block quote - * - * @param array $lineArray Line information - * - * @return null|array - * - * @since 1.0.0 - */ - protected static function blockQuote(array $lineArray) : ?array - { - if (!\preg_match('/^>[ ]?(.*)/', $lineArray['text'], $matches)) { - return null; - } - - return [ - 'element' => [ - 'name' => 'blockquote', - 'handler' => 'lines', - 'text' => (array) $matches[1], - ], - ]; - } - - /** - * Handle continue quote - * - * @param array $lineArray Line information - * @param array $block Block information - * - * @return null|array - * - * @since 1.0.0 - */ - protected static function blockQuoteContinue(array $lineArray, array $block) : ?array - { - if ($lineArray['text'][0] === '>' && \preg_match('/^>[ ]?(.*)/', $lineArray['text'], $matches)) { - if (isset($block['interrupted'])) { - $block['element']['text'][] = ''; - - unset($block['interrupted']); - } - - $block['element']['text'][] = $matches[1]; - - return $block; - } - - if (!isset($block['interrupted'])) { - $block['element']['text'][] = $lineArray['text']; - - return $block; - } - - return null; - } - - /** - * Handle HR element - * - * @param array $lineArray Line information - * - * @return null|array - * - * @since 1.0.0 - */ - protected static function blockRule(array $lineArray) : ?array - { - if (!\preg_match('/^([' . $lineArray['text'][0] . '])([ ]*\1){2,}[ ]*$/', $lineArray['text'])) { - return null; - } - - return [ - 'element' => [ - 'name' => 'hr', - ], - ]; - } - - /** - * Handle header for '=' indicator - * - * @param array $lineArray Line information - * @param array $block Block information - * - * @return null|array - * - * @since 1.0.0 - */ - protected static function blockSetextHeader(array $lineArray, array $block = null) : ?array - { - if (!isset($block) || isset($block['type']) || isset($block['interrupted'])) { - return null; - } - - if (\rtrim($lineArray['text'], $lineArray['text'][0]) !== '') { - return null; - } - - $block['element']['name'] = $lineArray['text'][0] === '=' ? 'h1' : 'h2'; - - return $block; - } - - /** - * Handle content reference - * - * @param array $lineArray Line information - * - * @return null|array - * - * @since 1.0.0 - */ - protected static function blockReference(array $lineArray) : ?array - { - if (!\preg_match('/^\[(.+?)\]:[ ]*?(?:[ ]+["\'(](.+)["\')])?[ ]*$/', $lineArray['text'], $matches)) { - return null; - } - - $data = [ - 'url' => UriFactory::build($matches[2]), - 'title' => $matches[3] ?? null, - ]; - - self::$definitionData['Reference'][\strtolower($matches[1])] = $data; - - return ['hidden' => true]; - } - - /** - * Handle table - * - * @param array $lineArray Line information - * @param array $block Block information - * - * @return null|array - * - * @since 1.0.0 - */ - protected static function blockTable($lineArray, array $block = null) : ?array - { - if (!isset($block) || isset($block['type']) || isset($block['interrupted'])) { - return null; - } - - if (\strpos($block['element']['text'], '|') !== false && \rtrim($lineArray['text'], ' -:|') === '') { - $alignments = []; - $divider = $lineArray['text']; - $divider = \trim($divider); - $divider = \trim($divider, '|'); - $dividerCells = \explode('|', $divider); - - foreach ($dividerCells as $dividerCell) { - $dividerCell = \trim($dividerCell); - - if ($dividerCell === '') { - continue; - } - - $alignment = null; - - if ($dividerCell[0] === ':') { - $alignment = 'left'; - } - - if (\substr($dividerCell, -1) === ':') { - $alignment = $alignment === 'left' ? 'center' : 'right'; - } - - $alignments[] = $alignment; - } - - $headerElements = []; - $header = $block['element']['text']; - $header = \trim($header); - $header = \trim($header, '|'); - $headerCells = \explode('|', $header); - - foreach ($headerCells as $index => $headerCell) { - $headerElement = [ - 'name' => 'th', - 'text' => \trim($headerCell), - 'handler' => 'line', - ]; - - if (isset($alignments[$index])) { - $headerElement['attributes'] = [ - 'style' => 'text-align: ' . $alignments[$index] . ';', - ]; - } - - $headerElements[] = $headerElement; - } - - $block = [ - 'alignments' => $alignments, - 'identified' => true, - 'element' => [ - 'name' => 'table', - 'handler' => 'elements', - ], - ]; - - $block['element']['text'][] = [ - 'name' => 'thead', - 'handler' => 'elements', - ]; - - $block['element']['text'][] = [ - 'name' => 'tbody', - 'handler' => 'elements', - 'text' => [], - ]; - - $block['element']['text'][0]['text'][] = [ - 'name' => 'tr', - 'handler' => 'elements', - 'text' => $headerElements, - ]; - - return $block; - } - - return null; - } - - /** - * Handle continue table - * - * @param array $lineArray Line information - * @param array $block Block information - * - * @return null|array - * - * @since 1.0.0 - */ - protected static function blockTableContinue(array $lineArray, array $block) : ?array - { - if (isset($block['interrupted'])) { - return null; - } - - if ($lineArray['text'][0] === '|' || \strpos($lineArray['text'], '|')) { - $elements = []; - $row = $lineArray['text']; - $row = \trim($row); - $row = \trim($row, '|'); - - \preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]+`|`)+/', $row, $matches); - - foreach ($matches[0] as $index => $cell) { - $element = [ - 'name' => 'td', - 'handler' => 'line', - 'text' => \trim($cell), - ]; - - if (isset($block['alignments'][$index])) { - $element['attributes'] = [ - 'style' => 'text-align: ' . $block['alignments'][$index] . ';', - ]; - } - - $elements[] = $element; - } - - $block['element']['text'][1]['text'][] = [ - 'name' => 'tr', - 'handler' => 'elements', - 'text' => $elements, - ]; - - return $block; - } - } - - /** - * Handle paragraph - * - * @param array $lineArray Line information - * - * @return array - * - * @since 1.0.0 - */ - protected static function paragraph(array $lineArray) : array - { - return [ - 'element' => [ - 'name' => 'p', - 'text' => $lineArray['text'], - 'handler' => 'line', - ], - ]; - } - - /** - * Handle a single line - * - * @param string $text Line of text - * - * @return string - * - * @since 1.0.0 - */ - protected static function line(string $text) : string - { - $markup = ''; - - while ($excerpt = \strpbrk($text, self::$inlineMarkerList)) { - $marker = $excerpt[0]; - $markerPosition = \strpos($text, $marker); - $excerptArray = ['text' => $excerpt, 'context' => $text]; - - foreach (self::$inlineTypes[$marker] as $inlineType) { - $inline = self::{'inline' . $inlineType}($excerptArray); - - if ($inline === null) { - continue; - } - - if (isset($inline['position']) && $inline['position'] > $markerPosition) { - continue; - } - - if (!isset($inline['position'])) { - $inline['position'] = $markerPosition; - } - - $unmarkedText = (string) \substr($text, 0, $inline['position']); - $markup .= self::unmarkedText($unmarkedText); - $markup .= isset($inline['markup']) ? $inline['markup'] : self::element($inline['element']); - $text = (string) \substr($text, $inline['position'] + $inline['extent']); - - continue 2; - } - - $unmarkedText = (string) \substr($text, 0, $markerPosition + 1); - $markup .= self::unmarkedText($unmarkedText); - $text = (string) \substr($text, $markerPosition + 1); - } - - $markup .= self::unmarkedText($text); - - return $markup; - } - - /** - * Handle inline code - * - * @param array $excerpt Markdown excerpt - * - * @return null|array - * - * @since 1.0.0 - */ - protected static function inlineCode(array $excerpt) : ?array - { - $marker = $excerpt['text'][0]; - - if (!\preg_match('/^(' . $marker . '+)[ ]*(.+?)[ ]*(? \strlen($matches[0]), - 'element' => [ - 'name' => 'code', - 'text' => \preg_replace("/[ ]*\n/", ' ', $matches[2]), - ], - ]; - } - - /** - * Handle inline email - * - * @param array $excerpt Markdown excerpt - * - * @return null|array - * - * @since 1.0.0 - */ - protected static function inlineEmailTag(array $excerpt) : ?array - { - if (\strpos($excerpt['text'], '>') === false || !\preg_match('/^<((mailto:)?\S+?@\S+?)>/i', $excerpt['text'], $matches)) { - return null; - } - - $url = $matches[1]; - - if (!isset($matches[2])) { - $url = 'mailto:' . $url; - } - - return [ - 'extent' => \strlen($matches[0]), - 'element' => [ - 'name' => 'a', - 'text' => $matches[1], - 'attributes' => [ - 'href' => UriFactory::build($url), - ], - ], - ]; - } - - /** - * Handle inline emphasis - * - * @param array $excerpt Markdown excerpt - * - * @return null|array - * - * @since 1.0.0 - */ - protected static function inlineEmphasis(array $excerpt) : ?array - { - if (!isset($excerpt['text'][1])) { - return null; - } - - $marker = $excerpt['text'][0]; - - if ($excerpt['text'][1] === $marker && isset(self::$strongRegex[$marker]) && \preg_match(self::$strongRegex[$marker], $excerpt['text'], $matches)) { - $emphasis = 'strong'; - } elseif ($excerpt['text'][1] === $marker && isset(self::$underlineRegex[$marker]) && \preg_match(self::$underlineRegex[$marker], $excerpt['text'], $matches)) { - $emphasis = 'u'; - } elseif (\preg_match(self::$emRegex[$marker], $excerpt['text'], $matches)) { - $emphasis = 'em'; - } else { - return null; - } - - return [ - 'extent' => \strlen($matches[0]), - 'element' => [ - 'name' => $emphasis, - 'handler' => 'line', - 'text' => $matches[1], - ], - ]; - } - - /** - * Handle escape of special char - * - * @param array $excerpt Markdown excerpt - * - * @return null|array - * - * @since 1.0.0 - */ - protected static function inlineEscapeSequence(array $excerpt) : ?array - { - if (!isset($excerpt['text'][1]) || !\in_array($excerpt['text'][1], self::$specialCharacters)) { - return null; - } - - return [ - 'markup' => $excerpt['text'][1], - 'extent' => 2, - ]; - } - - /** - * Handle inline image - * - * @param array $excerpt Markdown excerpt - * - * @return null|array - * - * @since 1.0.0 - */ - protected static function inlineImage(array $excerpt) : ?array - { - if (!isset($excerpt['text'][1]) || $excerpt['text'][1] !== '[') { - return null; - } - - $excerpt['text'] = \substr($excerpt['text'], 1); - $link = self::inlineLink($excerpt); - - if ($link === null) { - return null; - } - - $inline = [ - 'extent' => $link['extent'] + 1, - 'element' => [ - 'name' => 'img', - 'attributes' => [ - 'src' => UriFactory::build($link['element']['attributes']['href']), - 'alt' => $link['element']['text'], - ], - ], - ]; - - $inline['element']['attributes'] += $link['element']['attributes']; - - unset($inline['element']['attributes']['href']); - - return $inline; - } - - /** - * Handle inline link - * - * @param array $excerpt Markdown excerpt - * - * @return null|array - * - * @since 1.0.0 - */ - protected static function inlineLink(array $excerpt) : ?array - { - $element = [ - 'name' => 'a', - 'handler' => 'line', - 'text' => null, - 'attributes' => [ - 'href' => null, - 'title' => null, - ], - ]; - - $extent = 0; - $remainder = $excerpt['text']; - - if (!\preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches)) { - return null; - } - - $element['text'] = $matches[1]; - $extent += \strlen($matches[0]); - $remainder = (string) \substr($remainder, $extent); - - if (\preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*"|\'[^\']*\'))?\s*[)]/', $remainder, $matches)) { - $element['attributes']['href'] = UriFactory::build($matches[1]); - - if (isset($matches[2])) { - $element['attributes']['title'] = (string) \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(self::$definitionData['Reference'][$definition])) { - return null; - } - - $def = self::$definitionData['Reference'][$definition]; - - $element['attributes']['href'] = UriFactory::build($def['url']); - $element['attributes']['title'] = $def['title']; - } - - return [ - 'extent' => $extent, - 'element' => $element, - ]; - } - - /** - * Handle special char to html - * - * @param array $excerpt Markdown excerpt - * - * @return null|array - * - * @since 1.0.0 - */ - protected static function inlineSpecialCharacter(array $excerpt) : ?array - { - if ($excerpt['text'][0] === '&' && !\preg_match('/^&#?\w+;/', $excerpt['text'])) { - return [ - 'markup' => '&', - 'extent' => 1, - ]; - } - - $specialChar = ['>' => 'gt', '<' => 'lt', '"' => 'quot']; - - if (isset($specialChar[$excerpt['text'][0]])) { - return [ - 'markup' => '&' . $specialChar[$excerpt['text'][0]] . ';', - 'extent' => 1, - ]; - } - } - - /** - * Handle inline strike through - * - * @param array $excerpt Markdown excerpt - * - * @return null|array - * - * @since 1.0.0 - */ - protected static function inlineStrikethrough(array $excerpt) : ?array - { - if (!isset($excerpt['text'][1])) { - return null; - } - - if ($excerpt['text'][1] !== '~' || !\preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $excerpt['text'], $matches)) { - return null; - } - - return [ - 'extent' => \strlen($matches[0]), - 'element' => [ - 'name' => 'del', - 'text' => $matches[1], - 'handler' => 'line', - ], - ]; - } - - /** - * Handle inline url - * - * @param array $excerpt Markdown excerpt - * - * @return null|array - * - * @since 1.0.0 - */ - protected static function inlineUrl(array $excerpt) : ?array - { - if (!isset($excerpt['text'][2]) || $excerpt['text'][2] !== '/') { - return null; - } - - if (!\preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $excerpt['context'], $matches, \PREG_OFFSET_CAPTURE)) { - return null; - } - - return [ - 'extent' => \strlen($matches[0][0]), - 'position' => $matches[0][1], - 'element' => [ - 'name' => 'a', - 'text' => $matches[0][0], - 'attributes' => [ - 'href' => UriFactory::build($matches[0][0]), - ], - ], - ]; - } - - /** - * Handle inline url - * - * @param array $excerpt Markdown excerpt - * - * @return null|array - * - * @since 1.0.0 - */ - protected static function inlineUrlTag(array $excerpt) : ?array - { - if (\strpos($excerpt['text'], '>') === false || !\preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $excerpt['text'], $matches)) { - return null; - } - - return [ - 'extent' => \strlen($matches[0]), - 'element' => [ - 'name' => 'a', - 'text' => $matches[1], - 'attributes' => [ - 'href' => UriFactory::build($matches[1]), - ], - ], - ]; - } - - /** - * Clean up normal text - * - * @param string $text Normal text - * - * @return string - * - * @since 1.0.0 - */ - protected static function unmarkedText(string $text) : string - { - $text = \preg_replace('/(?:[ ][ ]+|[ ]*\\\\)\n/', "
\n", $text); - $text = \str_replace(" \n", "\n", $text); - - return $text; - } - - /** - * Handle general html element - * - * @param array $element Html element - * - * @return string - * - * @since 1.0.0 - */ - protected static function element(array $element) : string - { - $element = self::sanitizeAndBuildElement($element); - $markup = '<' . $element['name']; - - if (isset($element['attributes'])) { - foreach ($element['attributes'] as $name => $value) { - if ($value === null) { - continue; - } - - $markup .= ' ' . $name . '="' . self::escape($value) . '"'; - } - } - - if (isset($element['text'])) { - $markup .= '>'; - $markup .= isset($element['handler']) ? self::{$element['handler']}($element['text']) : self::escape($element['text'], true); - $markup .= ''; - } else { - $markup .= ' />'; - } - - return $markup; - } - - /** - * Handle an array of elements - * - * @param array $elements Elements - * - * @return string - * - * @since 1.0.0 - */ - protected static function elements(array $elements) : string - { - $markup = ''; - - foreach ($elements as $element) { - $markup .= "\n" . self::element($element); - } - - $markup .= "\n"; - - return $markup; - } - - /** - * Remove blocks - * - * @param array $lines Lines - * - * @return string - * - * @since 1.0.0 - */ - protected static function li(array $lines) : string - { - $markup = self::lines($lines); - $trimmedMarkup = \trim($markup); - - if (!\in_array('', $lines) && \substr($trimmedMarkup, 0, 3) === '

') { - $markup = $trimmedMarkup; - $markup = (string) \substr($markup, 3); - $position = \strpos($markup, '

'); - $markup = \substr_replace($markup, '', $position, 4); - } - - return $markup; - } - - /** - * Sanitize an element - * - * @param array $element Element to sanitize - * - * @return array - * - * @since 1.0.0 - */ - protected static function sanitizeAndBuildElement(array $element) : array - { - $safeUrlNameToAtt = [ - 'a' => 'href', - 'img' => 'src', - ]; - - if (isset($safeUrlNameToAtt[$element['name']])) { - $element = self::filterUnsafeUrlInAttribute($element, $safeUrlNameToAtt[$element['name']]); - } - - if (!empty($element['attributes'])) { - foreach ($element['attributes'] as $att => $val) { - if (!\preg_match('/^[a-zA-Z0-9][a-zA-Z0-9-_]*+$/', $att)) { - unset($element['attributes'][$att]); - } elseif (self::striAtStart($att, 'on')) { - unset($element['attributes'][$att]); - } - } - } - - return $element; - } - - /** - * Replace unsafe url - * - * @param array $element Element to sanitize - * @param string $attribute Element attribute - * - * @return array - * - * @since 1.0.0 - */ - protected static function filterUnsafeUrlInAttribute(array $element, string $attribute) : array - { - foreach (self::$safeLinksWhitelist as $scheme) { - if (self::striAtStart($element['attributes'][$attribute], $scheme)) { - return $element; - } - } - - $element['attributes'][$attribute] = \str_replace(':', '%3A', $element['attributes'][$attribute]); - - return $element; - } - - /** - * Escape html elements - * - * @param string $text Text to escape - * @param bool $allowQuotes Are quotes allowed - * - * @return string - * - * @since 1.0.0 - */ - protected static function escape(string $text, bool $allowQuotes = false) : string - { - return \htmlspecialchars($text, $allowQuotes ? \ENT_NOQUOTES : \ENT_QUOTES, 'UTF-8'); - } - - /** - * Check if string starts with - * - * @param string $string Text to check against - * @param string $needle Needle to check - * - * @return bool - * - * @since 1.0.0 - */ - protected static function striAtStart(string $string, string $needle) : bool - { - $length = \strlen($needle); - - if ($length > \strlen($string)) { - return false; - } - - return \strtolower((string) \substr($string, 0, $length)) === \strtolower($needle); - } } diff --git a/tests/Math/Matrix/EigenvalueDecompositionTest.php b/tests/Math/Matrix/EigenvalueDecompositionTest.php index 7cd929a0b..6d142b05d 100755 --- a/tests/Math/Matrix/EigenvalueDecompositionTest.php +++ b/tests/Math/Matrix/EigenvalueDecompositionTest.php @@ -386,7 +386,7 @@ final class EigenvalueDecompositionTest extends \PHPUnit\Framework\TestCase $eig = new EigenvalueDecomposition($A); ++$c; } while (true); - } catch (\Throwable $t) { + } catch (\Throwable $_) { var_dump($c); var_dump($array); } diff --git a/tests/System/File/Ftp/DirectoryTest.php b/tests/System/File/Ftp/DirectoryTest.php index f42d4ae7b..2f91bc100 100755 --- a/tests/System/File/Ftp/DirectoryTest.php +++ b/tests/System/File/Ftp/DirectoryTest.php @@ -54,7 +54,7 @@ final class DirectoryTest extends \PHPUnit\Framework\TestCase if (!$mkdir || !$put) { throw new \Exception(); } - } catch (\Throwable $t) { + } catch (\Throwable $_) { self::$con = null; } } diff --git a/tests/System/File/Ftp/FileTest.php b/tests/System/File/Ftp/FileTest.php index cd715b13d..4b519a856 100755 --- a/tests/System/File/Ftp/FileTest.php +++ b/tests/System/File/Ftp/FileTest.php @@ -56,7 +56,7 @@ final class FileTest extends \PHPUnit\Framework\TestCase if (!$mkdir || !$put) { throw new \Exception(); } - } catch (\Throwable $t) { + } catch (\Throwable $_) { self::$con = null; } } diff --git a/tests/System/File/Ftp/FtpStorageTest.php b/tests/System/File/Ftp/FtpStorageTest.php index 8e2ff304d..6a42e358e 100755 --- a/tests/System/File/Ftp/FtpStorageTest.php +++ b/tests/System/File/Ftp/FtpStorageTest.php @@ -57,7 +57,7 @@ final class FtpStorageTest extends \PHPUnit\Framework\TestCase if (!$mkdir || !$put) { throw new \Exception(); } - } catch (\Throwable $t) { + } catch (\Throwable $_) { self::$con = null; } diff --git a/tests/Utils/Converter/CurrencyTest.php b/tests/Utils/Converter/CurrencyTest.php index b19963e6a..f43b662a7 100755 --- a/tests/Utils/Converter/CurrencyTest.php +++ b/tests/Utils/Converter/CurrencyTest.php @@ -42,7 +42,7 @@ final class CurrencyTest extends \PHPUnit\Framework\TestCase Rest::request($request)->getBody(); self::$reachable = true; - } catch (\Throwable $t) { + } catch (\Throwable $_) { self::$reachable = false; } }